/* meercal: the calendar half of the suite. Light + dark, following the system
   unless the theme is forced.

   The palette is meerail's, deliberately: the two programs sit side by side on
   the same screen and a second definition of "dark" would show. What is new
   here is only what a calendar needs: the rail, the day rows, the lanes. */

:root {
  /* Which half of every light-dark() pair below is used. `light dark` means the
     OS decides, and it also puts scrollbars and form controls on the same side
     as the rest of the app, which a hand-rolled palette cannot. */
  color-scheme: light dark;
  --font: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --accent: light-dark(#1d6ff2, #3b82f6);
  --accent-weak: light-dark(#e8f0fe, #1e2a44);
  --accent-soft: light-dark(#6ba4f8, #7cb0fb);
  /* Today's band. Deliberately not the accent tint: every chip is filled with
     its calendar's colour at 14-25% alpha, so a blue day behind a blue chip
     erases it. A neutral wash plus an edge says "today" without competing with
     anything drawn on top of it. */
  --today-tint: light-dark(#f4f4f6, #232326);
  --today-line: light-dark(#9cc2f8, #46688f);
  --bg: light-dark(#ffffff, #1c1c1e);
  --bg-elev: light-dark(#f5f5f7, #2c2c2e);
  --sidebar: light-dark(#e8e8ea, #232325);
  --sidebar-section: #8a8a8e;
  --text: light-dark(#1d1d1f, #f5f5f7);
  --text-muted: light-dark(#86868b, #8d8d92);
  --text-faint: light-dark(#a1a1a6, #6a6a6e);
  --border: light-dark(#dededf, #313133);
  --border-strong: light-dark(#cbcbcd, #3a3a3c);
  --danger: #d70015;
  --ok: light-dark(#1a7f37, #3fb950);
  --warning: light-dark(#96610a, #f5a623);
  --now: light-dark(#d70015, #ff453a);
  --shadow: 0 1px 3px light-dark(rgba(0,0,0,.10), rgba(0,0,0,.5)),
            0 10px 30px light-dark(rgba(0,0,0,.08), rgba(0,0,0,.4));
  --radius: 8px;
  --hover-tint: light-dark(rgba(0,0,0,.05), rgba(255,255,255,.06));
  --hover-tint-soft: light-dark(rgba(0,0,0,.035), rgba(255,255,255,.05));
  /* The hours outside day_start..day_end in the week and day grids. A wash
     rather than a colour: it has to read as "less of the same" over a plain
     column and over today's tint alike, and anything with a hue of its own
     would be a third thing on a grid that already has calendars in it. */
  --off-hours: light-dark(rgba(0,0,0,.045), rgba(0,0,0,.22));
  --hover-tint-strong: light-dark(rgba(0,0,0,.06), rgba(255,255,255,.08));
  /* Weekend columns and rows. A tint rather than a different surface: the
     weekend is still part of the week, and a panel edge through the middle of
     a month grid reads as a break in the data. */
  --weekend: light-dark(rgba(0,0,0,.025), rgba(255,255,255,.028));
  /* The span rail. One lane is deliberately narrow: the bars are the shape of
     the thing, and their titles run down them. */
  --lane-w: 24px;
  --gutter-w: 64px;
}

:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"] { color-scheme: dark; }

* { box-sizing: border-box; }
/* `hidden` outranks the layout rules below. Without this, any element given
   `display: grid` or `flex` (the login overlay, the search panel) ignores
   the attribute entirely, which is a whole class of "why is that on screen". */
[hidden] { display: none !important; }
html, body { margin: 0; height: 100%; font-family: var(--font); color: var(--text);
  background: var(--bg); -webkit-font-smoothing: antialiased; overflow: hidden; }
.muted { color: var(--text-muted); }
.small { font-size: .8rem; }
.grow { flex: 1; }
kbd { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .7rem;
  background: var(--bg-elev); border: 1px solid var(--border); border-bottom-width: 2px;
  border-radius: 4px; padding: .05rem .3rem; color: var(--text-muted); }
code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .78em; }
button { font-family: inherit; color: inherit; }

/* --- Layout --- */
.app { display: grid; grid-template-columns: 236px 1fr; height: 100vh; }
.main { display: flex; flex-direction: column; min-width: 0; overflow: hidden; }
/* Off on desktop, where the sidebar is a column and Create is inside it. */
.menu-btn, .fab, .scrim { display: none; }

/* --- Sidebar --- */
.sidebar { background: var(--sidebar); border-right: 1px solid var(--border);
  display: flex; flex-direction: column; overflow: hidden; }
.sidebar-toolbar { display: flex; align-items: center; justify-content: space-between;
  padding: .55rem .75rem .35rem .9rem; -webkit-app-region: drag; }
.brand { font-weight: 700; font-size: .98rem; letter-spacing: -.01em;
  display: flex; align-items: center; gap: .4rem; margin-right: auto; }
.brand-logo { height: 22px; width: auto; display: block; }
.sidebar-toolbar .icon-btn, .sidebar-tools .icon-btn { -webkit-app-region: no-drag; }
.sidebar-tools { display: flex; justify-content: flex-end; gap: .1rem; padding: 0 .75rem .4rem; }

.icon-btn { display: inline-flex; align-items: center; justify-content: center;
  background: none; border: 0; cursor: pointer; border-radius: 6px;
  padding: .25rem .4rem; font-size: .95rem; line-height: 1; color: var(--text-muted); }
.icon-btn:hover { background: var(--hover-tint-strong); color: var(--text); }
/* Only the glyph turns. Spinning the button spun its hover fill and its focus
   outline as well, and a rotating rounded rectangle reads as a bug. */
#btn-refresh.spin svg { animation: spin 1s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }

/* The Create button. Big, first, and the only filled thing in the sidebar:
   with twenty calendars under it, the action has to win on shape rather than
   on position alone. */
.create-btn { display: flex; align-items: center; gap: .5rem; margin: 0 .75rem .7rem;
  padding: .55rem .8rem; border: 1px solid var(--accent); border-radius: 22px;
  background: var(--accent); color: #fff; font-size: .92rem; font-weight: 600;
  cursor: pointer; box-shadow: var(--shadow); }
.create-btn:hover { filter: brightness(1.06); }
.create-btn:active { transform: translateY(1px); }
.create-plus { display: inline-flex; }

.agent-warning { margin: 0 .75rem .5rem; padding: .4rem .55rem; border-radius: 6px;
  background: light-dark(#fff4e5, #3a2a12); color: var(--warning); border: 1px solid var(--border);
  font-size: .78rem; text-align: left; cursor: pointer; }

.cal-tree { flex: 1; overflow-y: auto; padding: .25rem .5rem 1rem; }
.tree-section { font-size: .68rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .04em; color: var(--sidebar-section); padding: .8rem .55rem .25rem;
  display: flex; align-items: center; gap: .35rem; }

.cal-row { display: flex; align-items: center; gap: .5rem; padding: .3rem .5rem;
  border-radius: 6px; cursor: pointer; font-size: .86rem; user-select: none; }
.cal-row:hover { background: var(--hover-tint); }
.cal-row.off { color: var(--text-faint); }
/* The dot is the calendar's colour everywhere in the app (sidebar, chip,
   bar, modal) so that a colour is only ever learned once. Hidden calendars
   keep the hue and lose the fill: still identifiable, plainly switched off. */
.cal-dot { width: 10px; height: 10px; border-radius: 50%; background: var(--c);
  flex: 0 0 auto; box-shadow: inset 0 0 0 1px rgba(0,0,0,.12); }
/* The dot's hit area. The dot stays ten pixels because that is the size it
   reads at in a list of twenty; the button around it is what a pointer is
   actually aimed at. */
.cal-paint { display: flex; align-items: center; border: 0; background: none; cursor: pointer;
  padding: .3rem .15rem; margin: -.3rem -.05rem -.3rem -.15rem; border-radius: 5px; }
.cal-paint:hover .cal-dot { box-shadow: 0 0 0 2px var(--sidebar), 0 0 0 3px var(--c); }

/* The panel's calendar picker: the list, with the colour it is choosing. */
.cal-pick { display: flex; align-items: center; gap: .5rem; }
.cal-pick .in { flex: 1; min-width: 0; }
.cal-row.off .cal-dot { background: transparent; box-shadow: inset 0 0 0 2px var(--c); opacity: .6; }
.cal-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cal-warn { color: var(--danger); font-weight: 700; }
.cal-solo { opacity: 0; display: inline-flex; border: 0; background: none; cursor: pointer;
  color: var(--text-muted); padding: 0 .15rem; }
.cal-row:hover .cal-solo { opacity: 1; }
.cal-solo:hover { color: var(--accent); }

.set-row { display: flex; align-items: center; border-radius: 6px; }
.set-row:hover { background: var(--hover-tint); }
/* The set you are currently looking at. Marked, because with several sets the
   first question is which one is on. */
.set-row.active { background: var(--accent-weak); }
.set-row.active .set-name { font-weight: 620; }
.set-apply { flex: 1; display: flex; align-items: center; gap: .5rem; min-width: 0;
  padding: .3rem .5rem; border: 0; background: none; cursor: pointer; font-size: .86rem;
  text-align: left; }
.set-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.set-edit { opacity: 0; display: inline-flex; border: 0; background: none; cursor: pointer;
  color: var(--text-muted); padding: .2rem .45rem; }
.set-row:hover .set-edit { opacity: 1; }
.set-edit:hover { color: var(--accent); }

.cal-actions { display: flex; align-items: center; gap: .5rem; padding: .6rem .55rem 0; flex-wrap: wrap; }
.link { background: none; border: 0; color: var(--accent); cursor: pointer; font-size: .78rem; padding: 0; }
.link:hover { text-decoration: underline; }

.shortcut-box { border-top: 1px solid var(--border); padding: .3rem .55rem .5rem;
  font-size: .74rem; color: var(--text-muted); max-height: 34vh; display: flex;
  flex-direction: column; }
/* The header is the way out: once the keys are learned the box is furniture,
   and furniture that cannot be moved out of the way is a nuisance. Clicking it
   folds the sheet to this one line, and the choice is remembered. */
.shortcut-head { display: flex; align-items: center; gap: .35rem; width: 100%;
  border: 0; background: none; cursor: pointer; font: inherit; font-size: .66rem;
  font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  color: var(--sidebar-section); padding: .3rem .35rem; border-radius: 6px; }
.shortcut-head:hover { background: var(--hover-tint); color: var(--text-muted); }
.shortcut-glyph { display: flex; margin-left: auto; }
/* Pointing down at the list it closes; flipped when closed, so it points back
   up at the line it will reopen. */
.shortcut-box.collapsed .shortcut-glyph { transform: rotate(180deg); }
.shortcut-body { overflow-y: auto; padding: .15rem .35rem 0; }
.shortcut-box.collapsed .shortcut-body { display: none; }
/* Folded open, the box shows the first nine bindings: everything you reach for
   without thinking. `?` or the "n more" button below opens the rest. */
.shortcut-box:not(.open) .shortcut-row:nth-child(n+10) { display: none; }
.shortcut-more { border: 0; background: none; padding: .25rem 0 0; cursor: pointer;
  color: var(--accent); font-size: .72rem; }
.shortcut-more:hover { text-decoration: underline; }
.shortcut-row { display: flex; gap: .5rem; align-items: center; padding: .1rem 0; }
.shortcut-row kbd { min-width: 2.1rem; text-align: center; }

/* --- The month's number --- */
/* A bubble beside a month's name, wherever a month is named: the toolbar's
   title, the Ribbon's headers, the week and day labels, the picker, and the
   year view's twelve. It is the number `g 1`-`g 12` takes, which is the whole
   reason it is on screen -- so it is drawn as a key would be, quiet enough to
   read past and boxed enough to read as a thing you could type.

   `inline-grid` rather than `flex`, because one of its homes is the toolbar
   title, and that has to stay an ordinary line of text for the ellipsis a
   phone puts on it. */
.month-no { display: inline-grid; place-items: center; vertical-align: middle;
  min-width: 1.25rem; height: 1.25rem; padding: 0 .25rem; border-radius: 999px;
  background: var(--bg-elev); border: 1px solid var(--border); color: var(--text-muted);
  font-size: .68rem; font-weight: 700; font-variant-numeric: tabular-nums;
  letter-spacing: 0; text-transform: none; }
/* The week grid's day labels have room for the number but not for the bubble
   at full size, so it shrinks rather than being left out. */
.month-no.sm { min-width: .95rem; height: .95rem; padding: 0 .18rem; font-size: .56rem;
  border-radius: 999px; }

/* --- Toolbar --- */
.toolbar { display: flex; align-items: center; gap: .5rem; padding: .5rem .8rem;
  border-bottom: 1px solid var(--border); background: var(--bg); -webkit-app-region: drag; }
.toolbar button, .toolbar input, .toolbar label { -webkit-app-region: no-drag; }
.view-title { font-size: 1.02rem; font-weight: 650; margin: 0 .3rem; letter-spacing: -.01em;
  white-space: nowrap; }
.view-title .month-no { min-width: 1.15rem; height: 1.15rem; margin-right: .4rem; }
.hidden-note { font-size: .74rem; color: var(--text-muted); background: var(--bg-elev);
  border: 1px solid var(--border); border-radius: 20px; padding: .1rem .5rem; }
/* A calendar in the wrong zone still looks like a calendar, so this is the one
   note that has to be read rather than glanced past: the warning colour, and
   the whole explanation on hover. */
.zone-note { font-size: .74rem; color: var(--warning); background: var(--bg-elev);
  border: 1px solid var(--warning); border-radius: 20px; padding: .1rem .5rem;
  cursor: help; }
.nav-pair { display: flex; }
.nav-pair .icon-btn { padding: .25rem .35rem; }

.btn { border: 1px solid var(--border-strong); background: var(--bg); border-radius: 7px;
  padding: .3rem .7rem; font-size: .84rem; cursor: pointer; }
.btn:hover { background: var(--hover-tint-soft); }
.btn.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.btn.primary:hover { filter: brightness(1.06); }
.btn.danger { color: var(--danger); border-color: var(--border); }
.btn:disabled { opacity: .5; cursor: not-allowed; }

.view-switch { display: flex; border: 1px solid var(--border-strong); border-radius: 7px;
  overflow: hidden; }
.view-switch button { border: 0; background: var(--bg); padding: .3rem .6rem; font-size: .82rem;
  cursor: pointer; border-right: 1px solid var(--border); }
.view-switch button:last-child { border-right: 0; }
.view-switch button:hover { background: var(--hover-tint-soft); }
.view-switch button.active { background: var(--accent); color: #fff; }

/* --- Filter --- */
.filter-region { border-bottom: 1px solid var(--border); background: var(--bg); position: relative; }
.filter-bar { display: flex; align-items: center; gap: .4rem; padding: .35rem .8rem; }
.filter-icon { color: var(--text-muted); display: inline-flex; }
#filter-input { flex: 1; border: 1px solid var(--border); background: var(--bg-elev);
  border-radius: 7px; padding: .3rem .6rem; font-size: .84rem; color: var(--text); min-width: 0; }
#filter-input:focus { outline: 2px solid var(--accent-soft); outline-offset: -1px; }
.rx-toggle { display: flex; align-items: center; gap: .25rem; font-size: .78rem; color: var(--text-muted); }

.search-results { position: absolute; z-index: 30; left: .8rem; right: .8rem; top: 100%;
  background: var(--bg); border: 1px solid var(--border-strong); border-radius: var(--radius);
  box-shadow: var(--shadow); max-height: 50vh; overflow-y: auto; padding: .3rem; }
.hit { display: flex; align-items: center; gap: .55rem; width: 100%; border: 0; background: none;
  padding: .35rem .5rem; border-radius: 6px; cursor: pointer; font-size: .84rem; text-align: left; }
.hit:hover { background: var(--hover-tint); }
.hit.past { color: var(--text-muted); }
.hit-date { font-variant-numeric: tabular-nums; color: var(--text-muted); font-size: .78rem; }
.hit-title { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hit-cal { font-size: .75rem; color: var(--text-faint); }

/* --- Stage --- */
#stage { flex: 1; min-height: 0; overflow: auto; }

/* =========================================================================
   The Ribbon
   ========================================================================= */

.ribbon { background: var(--bg); }
.ribbon-grid { display: grid;
  grid-template-columns: var(--gutter-w) repeat(var(--lanes, 1), var(--lane-w)) minmax(0, 1fr);
  align-items: stretch; }
.ribbon:not(.has-rail) .ribbon-grid { --lane-w: 0px; }

/* Month, sticky at the top of the scroll: the one label that says where you
   are once the grid has scrolled past its own header. */
.rb-month { position: sticky; top: 0; z-index: 6; background: var(--bg);
  border-bottom: 1px solid var(--border-strong); padding: .5rem .8rem .25rem;
  font-weight: 700; font-size: .8rem; letter-spacing: .02em; text-transform: uppercase;
  color: var(--text-muted); display: flex; align-items: center; gap: .4rem; }

.rb-gutter { display: flex; flex-direction: column; align-items: flex-end; gap: 0;
  padding: .4rem .55rem .4rem 0; border-right: 1px solid var(--border);
  position: relative; }
.rb-gutter.rule, .rb-day.rule { border-top: 1px solid var(--border-strong); }
.rb-dnum { font-size: 1rem; font-weight: 620; font-variant-numeric: tabular-nums; line-height: 1.1; }
.rb-dow { font-size: .68rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: .03em; }
.rb-week { font-size: .62rem; color: var(--text-faint); font-variant-numeric: tabular-nums;
  margin-top: .1rem; }
.rb-gutter.today .rb-dnum { background: var(--accent); color: #fff; border-radius: 50%;
  width: 1.6rem; height: 1.6rem; display: grid; place-items: center; }
/* Two calendars want the same hour. Marked in the gutter as well as on the
   chips, because the whole point is to see it without reading the row. */
.rb-clash { position: absolute; left: .35rem; top: .45rem; color: var(--danger);
  font-weight: 800; font-size: .8rem; }

.rb-day { display: flex; flex-wrap: wrap; align-items: flex-start; gap: .25rem;
  padding: .4rem .8rem .4rem .6rem; min-height: 2.3rem; position: relative;
  border-bottom: 1px solid var(--border); }
.rb-day.today { background: var(--today-tint); box-shadow: inset 0 0 0 1px var(--today-line); }
.rb-empty { min-height: 1rem; }

/* A sweep down the day rows, while it is being made. The accent rather than a
   calendar's colour, for the reason the week's ghost uses it: which calendar
   this lands in is the panel's first question, and the days being swept must
   not look as though they have answered it. */
.rb-day.rb-picking { background: var(--accent-weak); box-shadow: inset 0 0 0 1px var(--accent-soft); }
.rb-pickcap { position: absolute; right: .5rem; top: .35rem; pointer-events: none;
  background: var(--accent); color: #fff; border-radius: 10px; padding: .05rem .4rem;
  font-size: .68rem; font-weight: 650; font-variant-numeric: tabular-nums; }

/* A day's events, as pills. Dense on purpose: a fortnight of twenty calendars
   has to fit on a screen, and the row grows only as far as its own content. */
.chip { display: inline-flex; align-items: baseline; gap: .35rem; max-width: 100%;
  border: 1px solid transparent; background: var(--c-weak); border-left: 3px solid var(--c);
  border-radius: 5px; padding: .12rem .45rem; font-size: .8rem; cursor: pointer;
  text-align: left; }
.chip:hover { filter: brightness(.97); border-color: var(--c); }
.chip-time { font-variant-numeric: tabular-nums; color: var(--text-muted); font-size: .74rem; }
/* `min-width: 0` is what actually lets it shrink: a flex item's automatic
   minimum is its content, so without this the ellipsis never kicks in and a
   long title pushes the chip off the side of the screen. */
.chip-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  max-width: 26rem; min-width: 0; }
.chip-where { color: var(--text-faint); font-size: .72rem; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; max-width: 10rem; }
.chip-mark { color: var(--text-faint); font-size: .68rem; }
.chip.all-day { background: var(--c); color: #fff; border-left-color: var(--c); }
.chip.all-day .chip-time, .chip.all-day .chip-where, .chip.all-day .chip-mark { color: rgba(255,255,255,.8); }
/* TRANSP: a note in the time rather than a claim on it. Hatched so that the
   distinction survives being three pixels tall in a dense week. */
.chip.free { background: repeating-linear-gradient(135deg, var(--c-weak) 0 6px, transparent 6px 12px); }
/* An all-day chip is white on its calendar's colour -- but the hatch above
   takes that colour away and leaves white text on the page's own background,
   which is no text at all. Every birthday is all-day *and* TRANSP, so a whole
   class of event was being drawn as an empty pill. Hand the ink back, the way
   .wk-bar.free and .mo-bar.free already do. */
.chip.all-day.free { color: var(--text); }
.chip.all-day.free .chip-time, .chip.all-day.free .chip-where,
.chip.all-day.free .chip-mark { color: var(--text-faint); }
.chip.clash { box-shadow: 0 0 0 1px var(--danger) inset; }
.chip.cancelled .chip-title { text-decoration: line-through; opacity: .7; }

.rb-now { font-size: .58rem; font-weight: 700; color: #fff; background: var(--now);
  border-radius: 20px; padding: 0 .32rem; margin-top: .15rem; font-variant-numeric: tabular-nums; }
/* Today's chips that have already happened. Dimmed rather than hidden: what
   was on this morning is still the answer to "did I miss it". */
.chip.past { opacity: .55; }
.chip.past:hover { opacity: 1; }

.rb-quiet { border: 0; background: none; cursor: pointer; width: 100%; text-align: left;
  padding: .2rem .8rem .2rem .6rem; font-size: .72rem; color: var(--text-faint);
  border-bottom: 1px dashed var(--border); }
.rb-quiet:hover { color: var(--accent); background: var(--hover-tint-soft); }

/* --- The rail --- */
/* A long event is one bar, however many weeks it covers, because time did not
   wrap, and the view did not either. The title runs down it, and stays on screen
   while the bar does: `sticky` is what turns a colour into "you are on day 4
   of 19". */
/* `clip-path`, not `overflow: hidden`, and that is the whole trick: an
   ancestor with a clipping overflow *is* the scrollport its descendants stick
   to, so the sticky label below was sticking to a box that never scrolls and
   rode off the top of the screen with the bar's first day. clip-path keeps the
   label inside a bar shorter than its own title without claiming to be a
   scroller, which leaves #stage as the scrollport, which is the one that
   actually moves. */
.span { position: relative; margin: 1px 3px 1px 1px; border-radius: 7px;
  background: var(--c-weak); border: 1px solid var(--c); border-left-width: 3px;
  cursor: pointer; clip-path: inset(0 round 7px); min-height: 1.5rem; }
.span:hover { background: var(--c); }
.span:hover .span-label { color: #fff; }
.span.free { background: repeating-linear-gradient(135deg, var(--c-weak) 0 5px, transparent 5px 10px); }
/* Runs past the loaded window: no cap on that end, and a fade to say so. */
.span.open-top { border-top: 0; border-top-left-radius: 0; border-top-right-radius: 0;
  mask-image: linear-gradient(to bottom, transparent 0, #000 18px); }
.span.open-bottom { border-bottom: 0; border-bottom-left-radius: 0; border-bottom-right-radius: 0;
  mask-image: linear-gradient(to top, transparent 0, #000 18px); }
.span.open-top.open-bottom {
  mask-image: linear-gradient(to bottom, transparent 0, #000 18px, #000 calc(100% - 18px), transparent 100%); }
/* Below the month header, not under it: the header is sticky at the top of the
   same scroller and opaque, so a label that stopped at the very top had its
   first few letters swallowed. --rb-head is the header's measured height,
   written by the Ribbon after each render. */
.span-label { position: sticky; top: calc(var(--rb-head, 2.1rem) + 6px); writing-mode: vertical-rl; text-orientation: mixed;
  display: flex; align-items: center; gap: .5rem; padding: .45rem .15rem; font-size: .74rem;
  color: var(--text); max-height: calc(100vh - 12rem); overflow: hidden; }
.span-title { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  max-height: 22rem; }
.span-count { color: var(--text-muted); font-size: .68rem; font-variant-numeric: tabular-nums;
  white-space: nowrap; }
.span:hover .span-count { color: rgba(255,255,255,.85); }

/* =========================================================================
   Somebody else is on it
   =========================================================================

   One figure, drawn wherever an event is drawn: the event has guests, or it
   arrived as an invitation. It is an aside about the event and not a second
   title, so it takes the quiet ink every view already uses for its marks
   (`currentColor` throughout, which is what lets the same span sit on a pale
   chip and on a bar filled with the calendar's colour without being told
   twice). `flex: 0 0 auto` is the load-bearing part: in every one of these
   containers the title is the item that gives way, and without it the figure
   is what gets squeezed to nothing in a narrow column. */
.guest-mark { display: inline-flex; align-items: center; flex: 0 0 auto;
  color: var(--text-faint); opacity: .9; }
.guest-mark svg { display: block; }

/* The Ribbon's pills line up on the baseline; an icon has none worth using. */
.chip .guest-mark { align-self: center; }
/* The count, back where it belongs: hard against the figure, so it reads as
   "three of these" rather than as a stray digit at the end of the pill. */
.chip-guest-n { margin-left: -.25rem; }

/* A span's label is set sideways, and its Latin letters are turned with it.
   The figure is turned the same way rather than left upright: upright next to
   rotated text is the one orientation that reads as a mistake, and the label
   is meant to be taken in with the head tilted, all of it at once. An SVG is
   a replaced box and so is not turned by the writing mode on its own. */
.span-guests { transform: rotate(90deg); }
.span:hover .guest-mark { color: rgba(255,255,255,.85); }

/* In the week grid the block is a column when it is tall and a row when it is
   too short for two lines, so there is no one place in the flow that works for
   both. The corner is that place. `.has-guests` hands back the room it takes,
   which is what keeps the title ellipsising before the figure and not
   underneath it. */
.wk-event.has-guests { padding-right: 1rem; }
.wk-guests { position: absolute; top: .12rem; right: .22rem; }

/* The bars are plain text buttons with an ellipsis on them, so the figure goes
   in front of the title where the ellipsis cannot reach it. */
.wk-bar-guests, .mo-bar-guests { vertical-align: -2px; margin-right: .15rem;
  color: inherit; opacity: .85; }
.wk-bar.free .guest-mark, .mo-bar.free .guest-mark { color: var(--text); }

.mo-guests { margin-left: auto; }
.yr-guests { margin-left: .3rem; }


/* =========================================================================
   Week / Day
   ========================================================================= */

/* One scroller, with the weeks drawn one under the other in it: #stage does
   the scrolling, exactly as it does for the Ribbon, and a week is a block in
   the column rather than a page that replaces the last one. */
.week { background: var(--bg); }
.wk-period { position: relative; }
/* The labels and the all-day strip, stuck to the top of the screen for as long
   as their own week is on it -- `sticky` is constrained to the block it is in,
   so the next week's labels push these off rather than appearing over them,
   and the handover is the thing that reads as one continuous scroll. */
.wk-fixed { position: sticky; top: 0; z-index: 6; background: var(--bg);
  border-bottom: 1px solid var(--border-strong); }
.wk-head { display: grid; grid-template-columns: var(--gutter-w) repeat(var(--cols, 7), 1fr); }
.wk-corner { font-size: .68rem; color: var(--text-faint); display: grid; place-items: center; }
.wk-daylabel { display: flex; flex-direction: column; align-items: center; padding: .35rem 0 .3rem;
  border-left: 1px solid var(--border); }
.wk-dow { font-size: .68rem; text-transform: uppercase; letter-spacing: .04em; color: var(--text-muted); }
.wk-dnum { font-size: 1.15rem; font-weight: 620; font-variant-numeric: tabular-nums; }
.wk-daylabel.today .wk-dnum { background: var(--accent); color: #fff; border-radius: 50%;
  width: 1.9rem; height: 1.9rem; display: grid; place-items: center; }
.wk-daylabel.today .wk-dow { color: var(--accent); }
/* Which month, on the first of it and at the top of each block. With the weeks
   running on without a break, a bare "1" stops saying where in the year you
   have scrolled to. */
.wk-mon { display: flex; align-items: center; gap: .22rem;
  font-size: .6rem; text-transform: uppercase; letter-spacing: .04em;
  color: var(--text-faint); margin-top: .05rem; }
.wk-daylabel.today .wk-mon { color: var(--accent); }
.wk-daylabel.today .month-no { border-color: var(--accent); color: var(--accent); }

/* The all-day strip. Bars run across the columns without breaking, and an
   arrow says the thing continues past the edge of the week, which is the one
   fact the usual all-day row leaves out. */
.wk-strip { display: grid; grid-template-columns: var(--gutter-w) 1fr; align-items: start;
  border-top: 1px solid var(--border); min-height: 1.8rem; }
.wk-striplabel { font-size: .64rem; color: var(--text-faint); text-transform: uppercase;
  letter-spacing: .04em; padding: .35rem .5rem 0 0; text-align: right; }
.wk-stripgrid { display: grid; grid-template-columns: repeat(var(--cols, 7), 1fr);
  gap: 2px; padding: .2rem .2rem .3rem; }
.wk-bar { border: 0; border-radius: 5px; background: var(--c); color: #fff; font-size: .74rem;
  padding: .1rem .4rem; text-align: left; overflow: hidden; white-space: nowrap;
  text-overflow: ellipsis; cursor: pointer; }
.wk-bar.free { background: var(--c-weak); color: var(--text);
  box-shadow: inset 0 0 0 1px var(--c); }
.wk-bar.open-left { border-top-left-radius: 0; border-bottom-left-radius: 0;
  background-image: linear-gradient(to right, rgba(0,0,0,.25), transparent 18px); }
.wk-bar.open-right { border-top-right-radius: 0; border-bottom-right-radius: 0;
  background-image: linear-gradient(to left, rgba(0,0,0,.25), transparent 18px); }

.wk-body { display: grid; grid-template-columns: var(--gutter-w) repeat(var(--cols, 7), 1fr);
  position: relative; }
.wk-hours { display: flex; flex-direction: column; }
.wk-hour { position: relative; border-top: 1px solid var(--border); }
.wk-hour span { position: absolute; top: -.55rem; right: .4rem; font-size: .66rem;
  color: var(--text-faint); background: var(--bg); padding: 0 .2rem;
  font-variant-numeric: tabular-nums; }
.wk-col { position: relative; border-left: 1px solid var(--border); }
/* Edges rather than a box: the column runs the whole day, so a top and a
   bottom rule would land wherever the scroll happens to be. */
.wk-col.today { background: var(--today-tint);
  box-shadow: inset 1px 0 0 var(--today-line), inset -1px 0 0 var(--today-line); }
.wk-line { position: absolute; left: 0; right: 0; border-top: 1px solid var(--border); }
/* Outside the hours you keep. Still the grid -- the rules run through it, an
   event in it is drawn like any other, and a drag across it means what it means
   anywhere else, which is why it is deaf to the pointer. */
.wk-off { position: absolute; left: 0; right: 0; pointer-events: none;
  background: var(--off-hours); }
.wk-hour.off span { opacity: .5; }

.wk-event { position: absolute; border: 0; border-left: 3px solid var(--c); background: var(--c-weak);
  border-radius: 5px; padding: .1rem .3rem; font-size: .74rem; text-align: left; cursor: pointer;
  overflow: hidden; display: flex; flex-direction: column; line-height: 1.15; }
/* Short enough that two lines do not fit: the time and the title share one,
   which is the difference between "9:30" and "9:30 Standup". */
.wk-event.short { flex-direction: row; align-items: baseline; gap: .3rem; }
.wk-event:hover { filter: brightness(.97); }
.wk-event.free { background: repeating-linear-gradient(135deg, var(--c-weak) 0 6px, transparent 6px 12px); }
.wk-event.cancelled .wk-title { text-decoration: line-through; opacity: .7; }
.wk-time { color: var(--text-muted); font-size: .66rem; font-variant-numeric: tabular-nums; }
.wk-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 550; }
.wk-now { position: absolute; left: 0; right: 0; border-top: 2px solid var(--now); z-index: 4; }
.wk-now::before { content: ""; position: absolute; left: -4px; top: -5px; width: 8px; height: 8px;
  border-radius: 50%; background: var(--now); }

/* --- drawing on the grid --- */
/* The two ends of an event, as targets of their own. Six pixels: enough for a
   pointer, and little enough that the middle of even a half-hour meeting is
   still the part you grab to move it. Marked only on hover, because a grid of
   events each wearing two handles is a grid of handles. */
.wk-grip { position: absolute; left: 0; right: 0; height: 6px; cursor: ns-resize; }
.wk-grip.top { top: 0; }
.wk-grip.bottom { bottom: 0; }
.wk-event:hover .wk-grip::after { content: ""; position: absolute; left: 50%; width: 22px;
  height: 2px; margin-left: -11px; border-radius: 2px; background: var(--c); opacity: .5; }
.wk-grip.top::after { top: 1px; }
.wk-grip.bottom::after { bottom: 1px; }

/* The event under the pointer, while it is being moved or resized: lifted off
   the grid and given the full width of its day, because during the drag it is
   the only thing in that column that matters. */
.wk-event.wk-dragging { z-index: 5; box-shadow: var(--shadow); opacity: .95;
  filter: none; cursor: grabbing; }
/* A drag reads as one gesture across the whole window: no text selection, and
   the same cursor wherever the pointer wanders to. */
body.dragging-time { cursor: grabbing; user-select: none; }
body.dragging-time .wk-event, body.dragging-time .wk-col { cursor: grabbing; }

/* The new event, while it is still only a sweep of the mouse. Dashed and in
   the accent rather than a calendar's colour: which calendar it lands in is
   the panel's first question, and this must not appear to have answered it. */
.wk-ghost { position: absolute; left: 0; width: calc(100% - 3px); z-index: 5;
  pointer-events: none; border: 1px dashed var(--accent); border-radius: 5px;
  background: var(--accent-weak); display: flex; align-items: flex-start;
  padding: .1rem .3rem; overflow: hidden; }
.wk-ghost-time { font-size: .68rem; font-variant-numeric: tabular-nums; color: var(--accent);
  font-weight: 600; white-space: nowrap; }

/* The same ghost, one axis over: the grid's is drawn down a day and says
   hours, the strip's is drawn across a week and says days. Laid on the strip's
   own columns rather than measured in pixels, so it covers exactly the days
   the finished bar will cover, and across every lane rather than into one --
   it is not packed with the events yet. */
.wk-stripghost { z-index: 6; pointer-events: none; border: 1px dashed var(--accent);
  border-radius: 5px; background: var(--accent-weak); display: flex; align-items: center;
  padding: .1rem .4rem; overflow: hidden; min-height: 1.1rem; }
.wk-stripghost span { font-size: .7rem; font-variant-numeric: tabular-nums;
  color: var(--accent); font-weight: 600; white-space: nowrap; }

/* =========================================================================
   Month
   ========================================================================= */

.month { display: flex; flex-direction: column; overflow: hidden; }
.mo-head { display: grid; grid-template-columns: repeat(7, 1fr); border-bottom: 1px solid var(--border); }
.mo-dow { text-align: center; font-size: .68rem; text-transform: uppercase; letter-spacing: .04em;
  color: var(--text-muted); padding: .3rem 0; }
/* minmax(0, 1fr), not 1fr: a bare 1fr is minmax(auto, 1fr), so one busy week
   grows past its share and pushes the rest of the month off the bottom. */
.mo-grid { flex: 1; display: grid; grid-template-rows: repeat(var(--weeks, 6), minmax(0, 1fr));
  min-height: 0; }
/* Clipped to its row. Both layers inside are grid items, and a grid item's
   automatic minimum size is its content, so without this a week with more in
   it than fits simply grows and pushes the rest of the month down. */
.mo-week { position: relative; display: grid; min-height: 0; overflow: hidden; }
.mo-cells { display: grid; grid-template-columns: repeat(7, 1fr); grid-area: 1 / 1;
  border-bottom: 1px solid var(--border); min-height: 0; }
.mo-cell { border-left: 1px solid var(--border); padding: .15rem .25rem; overflow: hidden;
  display: flex; flex-direction: column; gap: 1px; min-height: 0; }
.mo-cell.other { background: var(--bg-elev); color: var(--text-faint); }
.mo-cell.today .mo-dnum span:first-child { background: var(--accent); color: #fff;
  border-radius: 50%; width: 1.4rem; height: 1.4rem; display: inline-grid; place-items: center; }
/* The gap under the date is the band the week's bars are drawn in; see
   app.month.js. It is zero in a week with no long events, so an ordinary week
   loses nothing to it. */
.mo-dnum { display: flex; align-items: center; justify-content: space-between;
  font-size: .78rem; font-variant-numeric: tabular-nums; height: 1.15rem;
  margin-bottom: calc(var(--lanes, 0) * (1.15rem + 2px)); }
.mo-week-no { font-size: .6rem; color: var(--text-faint); }
/* The bars sit over the cells, so one can cross day boundaries; the lanes are
   assigned across the whole month, so a bar keeps its line when the grid wraps. */
/* The band lines up with the gap the cells leave: same row height, same gap,
   and a top offset of exactly the date line above it. */
/* `grid-auto-rows`, not `repeat(var(--lanes), …)`: a week with no long events
   in it has --lanes: 0, and `repeat(0, …)` is invalid CSS that would take the
   whole declaration with it. The bars place themselves by row anyway. */
.mo-bars { grid-area: 1 / 1; display: grid; grid-template-columns: repeat(7, 1fr);
  grid-auto-rows: 1.15rem; gap: 2px;
  padding: calc(.15rem + 1.15rem) .2rem 0; pointer-events: none; align-content: start; }
.mo-bar { pointer-events: auto; border: 0; border-radius: 4px; background: var(--c); color: #fff;
  font-size: .7rem; padding: 0 .35rem; text-align: left; overflow: hidden; white-space: nowrap;
  text-overflow: ellipsis; cursor: pointer; }
.mo-bar.free { background: var(--c-weak); color: var(--text); box-shadow: inset 0 0 0 1px var(--c); }
.mo-bar.open-left { border-top-left-radius: 0; border-bottom-left-radius: 0; }
.mo-bar.open-right { border-top-right-radius: 0; border-bottom-right-radius: 0;
  background-image: linear-gradient(to left, rgba(0,0,0,.28), transparent 16px); }
.mo-chip { display: flex; align-items: center; gap: .25rem; border: 0; background: none;
  font-size: .72rem; padding: 0 .15rem; cursor: pointer; text-align: left; border-radius: 4px; }
.mo-chip:hover { background: var(--hover-tint); }
.mo-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--c); flex: 0 0 auto; }
.mo-chip.free .mo-dot { background: transparent; box-shadow: inset 0 0 0 1.5px var(--c); }
.mo-time { color: var(--text-muted); font-variant-numeric: tabular-nums; }
.mo-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mo-more { border: 0; background: none; color: var(--text-muted); font-size: .68rem;
  cursor: pointer; text-align: left; padding: 0 .15rem; }
.mo-more:hover { color: var(--accent); }

/* =========================================================================
   Modal
   ========================================================================= */

.modal-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,.35); z-index: 100;
  display: grid; place-items: center; padding: 1rem; }
.modal-card { background: var(--bg); border: 1px solid var(--border-strong); border-radius: 12px;
  box-shadow: var(--shadow); width: min(560px, 100%); max-height: 90vh; display: flex;
  flex-direction: column; overflow: hidden; }
.modal-card.small { width: min(340px, 100%); }
.modal-head { display: flex; align-items: center; gap: .5rem; padding: .7rem .9rem;
  border-bottom: 1px solid var(--border); }
.modal-title { font-weight: 650; flex: 1; }
.modal-body { padding: .8rem .9rem; overflow-y: auto; display: flex; flex-direction: column; gap: .55rem; }
.modal-foot { display: flex; align-items: center; gap: .5rem; padding: .7rem .9rem;
  border-top: 1px solid var(--border); background: var(--bg-elev); }
.modal-error { color: var(--danger); font-size: .82rem; }
.modal-note { font-size: .78rem; color: var(--text-muted); background: var(--bg-elev);
  border-radius: 6px; padding: .4rem .5rem; }

/* --- reminders, in the event panel ---------------------------------------
   Three states per channel, and `auto` is not the same as `on`: a mute has to
   survive a rule you write next month, so the switch has to be able to say
   which of the two it means. The dot carries what actually happens; the
   toggle carries what you have said about it. */
.rem-slot:empty { display: none; }
.rem-block { gap: .35rem; }
.rem-block .fld-label { display: flex; align-items: center; gap: .35rem; }
.rem-block .fld-label svg { opacity: .75; }
.rem-status { margin-left: auto; text-transform: none; letter-spacing: 0;
  color: var(--text-faint); font-size: .68rem; }

.rem-scope { display: flex; gap: .25rem; margin-bottom: .1rem; }
.rem-scope-btn { border: 1px solid var(--border-strong); background: var(--bg);
  color: var(--text-muted); border-radius: 6px; padding: .15rem .45rem;
  font: inherit; font-size: .72rem; cursor: pointer; }
.rem-scope-btn.on { background: var(--accent-weak); border-color: var(--accent-soft);
  color: var(--text); }

.rem-rows { display: flex; flex-direction: column; gap: .1rem; }
.rem-row { display: flex; align-items: center; gap: .45rem; padding: .22rem .4rem;
  border-radius: 6px; font-size: .82rem; }
.rem-row:hover { background: var(--hover-tint-soft); }
.rem-dot { width: 7px; height: 7px; border-radius: 50%; flex: none;
  background: var(--accent); }
.rem-row.off .rem-dot { background: transparent; box-shadow: inset 0 0 0 1.5px var(--text-faint); }
.rem-name { font-weight: 550; }
.rem-row.off .rem-name { color: var(--text-muted); }
.rem-why { color: var(--text-faint); font-size: .74rem; flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.rem-toggle { border: 1px solid var(--border-strong); background: var(--bg);
  color: var(--text-muted); border-radius: 6px; padding: .1rem .4rem;
  font: inherit; font-size: .7rem; text-transform: uppercase; letter-spacing: .04em;
  cursor: pointer; flex: none; min-width: 3.1rem; }
.rem-toggle:hover { background: var(--hover-tint); }
.rem-toggle.st-on { background: var(--accent-weak); border-color: var(--accent-soft);
  color: var(--text); }
.rem-toggle.st-off { background: var(--bg-elev); border-color: var(--danger); color: var(--danger); }
.rem-note { font-size: .72rem; color: var(--text-faint); padding: .1rem .4rem; }

.fld { display: flex; flex-direction: column; gap: .2rem; flex: 1; min-width: 0; }
.fld.inline { flex-direction: row; align-items: center; gap: .4rem; font-size: .85rem; }
.fld-row { display: flex; gap: .6rem; }
.fld-label { font-size: .7rem; text-transform: uppercase; letter-spacing: .04em; color: var(--text-muted); }
.fld-hint { font-size: .7rem; color: var(--text-faint); }
.in { border: 1px solid var(--border-strong); background: var(--bg); color: var(--text);
  border-radius: 7px; padding: .35rem .5rem; font-size: .88rem; font-family: inherit; width: 100%; }
.in:focus { outline: 2px solid var(--accent-soft); outline-offset: -1px; }
.title-in { font-size: 1.05rem; font-weight: 600; }
textarea.in { resize: vertical; }

/* The configured places, under the Where field. Small, quiet, and out of the
   way of typing something that is not on the list. */
.places { display: flex; flex-wrap: wrap; gap: .3rem; padding-top: .35rem; }
.place { border: 1px solid var(--border); background: var(--bg-elev); border-radius: 999px;
  padding: .12rem .55rem; font-size: .76rem; color: var(--text-muted); cursor: pointer; }
.place:hover { border-color: var(--accent); color: var(--accent); }
.place.on { background: var(--accent); border-color: var(--accent); color: #fff; }

/* The invitation, one bubble per person.

   The box is a text field that happens to be full of people: same border, same
   focus ring, and a click anywhere in it puts the caret in the input, so the
   only thing that changed from the field this replaced is that an address
   becomes a thing you can see the state of and take back.

   The reply is a mark, not a colour on the whole bubble. Five people in five
   states would otherwise turn the panel into a traffic light, and the one
   state anyone is actually looking for -- yes -- is the one that gets the
   drawn check. A no is a struck-through name, which cannot be mistaken for the
   × that removes it. */
.tokens { display: flex; flex-wrap: wrap; align-items: center; gap: .3rem;
  padding: .22rem .28rem; cursor: text; }
.tokens:focus-within { outline: 2px solid var(--accent-soft); outline-offset: -1px; }
.tokens.bad { border-color: var(--danger); }
/* Read-only: the guest list is still worth reading, so it keeps its bubbles and
   its replies and loses only the border that says "type here". */
.tokens:not(:focus-within):has(.token-in[hidden]) { border-color: transparent; padding-left: 0; cursor: default; }
.token-in { flex: 1 1 8rem; min-width: 6rem; border: 0; background: none; color: var(--text);
  font: inherit; font-size: .88rem; padding: .16rem .22rem; }
.token-in:focus { outline: none; }

.tok { display: inline-flex; align-items: center; gap: .3rem; max-width: 100%;
  font-size: .78rem; padding: .1rem .16rem .1rem .45rem;
  border: 1px solid var(--border-strong); border-radius: 999px;
  background: var(--bg-elev); color: var(--text); }
.tok-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* A dot the size of the check that replaces it, so a bubble is the same width
   whatever the answer was and a list of them does not jump about as replies
   land. */
.tok-mark { display: inline-flex; align-items: center; justify-content: center;
  flex: none; width: 11px; height: 11px; border-radius: 999px;
  border: 1px solid var(--text-faint); box-sizing: border-box; }
/* The check is drawn at 11px from a 24-unit grid, which scales the shared
   1.7 stroke down to under a pixel and leaves it looking like a smudge. Set
   here rather than in the icon, where the weight is right at the sizes every
   other button uses it at. */
.tok.st-yes .tok-mark { border: 0; color: var(--ok); }
.tok-mark svg { stroke-width: 2.8; overflow: visible; }
.tok.st-no .tok-mark { background: var(--danger); border-color: var(--danger); }
.tok.st-no .tok-name { text-decoration: line-through; color: var(--text-muted); }
.tok.st-maybe .tok-mark { background: var(--warning); border-color: var(--warning); }
/* NEEDS-ACTION: hollow, and left that way. Nobody has answered yet, and an
   empty circle is the only honest thing to draw for that. */
.tok.st-wait .tok-mark { background: transparent; }
.tok-x { display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 16px; height: 16px; padding: 0; border: 0; border-radius: 999px;
  background: none; color: var(--text-faint); cursor: pointer; }
.tok-x:hover { background: var(--hover-tint-strong); color: var(--danger); }
.tok-x:focus-visible { outline: 2px solid var(--accent-soft); outline-offset: 1px; }
/* A finger is not a mouse pointer. The × is the one control here that is easy
   to miss and expensive to hit by accident, so on a touch screen it gets a
   target worth the name and the bubble grows to hold it. */
@media (pointer: coarse) {
  .tok { font-size: .82rem; padding: .18rem .2rem .18rem .55rem; }
  .tok-x { width: 22px; height: 22px; }
}

.people { display: flex; flex-direction: column; gap: 1px; }
.person { display: flex; align-items: baseline; gap: .5rem; border: 0; background: none;
  padding: .25rem .4rem; border-radius: 6px; cursor: pointer; text-align: left; font-size: .82rem; }
.person:hover { background: var(--hover-tint); }
.person-mail { color: var(--text-muted); font-size: .76rem; }
.person-count { margin-left: auto; color: var(--text-faint); font-size: .7rem; }

/* Who normally comes with the people already invited, from meerail's
   co-recipient graph. Dashed rather than solid: these are offers nobody asked
   for, and they should not read as the chips under Where, which are settings
   the user wrote down themselves. The row disappears when it has nothing left
   to say, so it never sits there empty taking up the panel's height. */
.with-row:empty { display: none; }
.with-row { display: flex; flex-wrap: wrap; align-items: center; gap: .3rem; padding-top: .35rem; }
.with-label { color: var(--text-faint); font-size: .72rem; }
.with-chip { display: inline-flex; align-items: center; gap: .25rem; max-width: 100%;
  font: inherit; font-size: .76rem; padding: .12rem .55rem; cursor: pointer;
  border: 1px dashed var(--border-strong); border-radius: 999px;
  background: transparent; color: var(--text-muted); }
.with-chip:hover { border-style: solid; border-color: var(--accent); color: var(--accent); }
.with-plus { color: var(--accent); font-weight: 600; }
.with-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* meerail is configured and did not answer. Worth a line: an unreachable mail
   database and an empty address book look the same from a text field. */
.people-note { padding-top: .3rem; font-size: .72rem; color: var(--warning); }

.fatal { padding: 3rem; max-width: 40rem; }
.fatal h2 { margin: 0 0 .5rem; }

/* =========================================================================
   The key-sequence hint
   ========================================================================= */

/* Bottom centre, out of the way of everything and readable without moving your
   eyes off the calendar. */
.key-hint { position: fixed; z-index: 150; left: 50%; bottom: 1.6rem;
  transform: translateX(-50%); display: flex; align-items: center; gap: .55rem;
  background: light-dark(#1d1d1f, #f5f5f7); color: light-dark(#f5f5f7, #1d1d1f);
  border-radius: 999px; padding: .45rem .9rem; font-size: .84rem;
  box-shadow: var(--shadow); pointer-events: none; }
.key-hint kbd { background: rgba(127,127,127,.25); border: 0; color: inherit;
  font-size: .78rem; padding: .1rem .4rem; }
.key-hint b { font-weight: 700; }


/* =========================================================================
   The set editor
   ========================================================================= */

/* The number keys, as a row of them. A key that another set already answers to
   says whose it is, because taking it is allowed and silently stealing it is
   not; see PATCH /api/sets. */
.key-row { display: flex; flex-wrap: wrap; gap: .3rem; }
.key-btn { display: flex; flex-direction: column; align-items: center; gap: .1rem;
  min-width: 2.2rem; padding: .25rem .35rem; border: 1px solid var(--border-strong);
  background: var(--bg); border-radius: 7px; cursor: pointer; font-size: .85rem;
  font-variant-numeric: tabular-nums; }
.key-btn:hover { background: var(--hover-tint-soft); }
.key-btn.on { background: var(--accent); border-color: var(--accent); color: #fff; }
.key-owner { font-size: .58rem; color: var(--text-faint); max-width: 4.5rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.key-btn.on .key-owner { color: rgba(255,255,255,.8); }

.pick-list { display: flex; flex-direction: column; gap: 1px; max-height: 15rem;
  overflow-y: auto; border: 1px solid var(--border); border-radius: 7px; padding: .25rem; }
.pick { display: flex; align-items: center; gap: .5rem; padding: .22rem .35rem;
  border-radius: 5px; cursor: pointer; font-size: .86rem; }
.pick:hover { background: var(--hover-tint); }

/* =========================================================================
   Import: the drop target, and the dialog that asks where the file goes
   ========================================================================= */

/* Over everything, and deaf to the pointer. A drop target that captured the
   drag would sit between the file and the window it is being dropped on, and
   the drop event would never reach the handler that wants it. */
.drop-zone { position: fixed; inset: 0; z-index: 200; pointer-events: none;
  display: grid; place-items: center; padding: 2rem;
  background: light-dark(rgba(255,255,255,.72), rgba(0,0,0,.6)); }
.drop-card { display: flex; flex-direction: column; align-items: center; gap: .3rem;
  padding: 2.2rem 3rem; border-radius: 14px; background: var(--bg);
  border: 2px dashed var(--accent); box-shadow: var(--shadow); text-align: center; }
.drop-title { font-size: 1.1rem; font-weight: 650; }
.drop-sub { font-size: .85rem; color: var(--text-muted); }

/* The file's own name, so not upper-cased the way `.fld-label` is. */
.import-file { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.import-summary { font-size: .82rem; color: var(--text-muted); }
.fld-hint.warn { color: var(--warning); }

/* The last row is "New calendar" and its name field, which has to be the wide
   part of the row rather than a full-width input squashing the label out. */
.import-targets .pick { padding: .3rem .35rem; }
.import-targets .pick-fixed { flex: none; }
.import-targets .in { flex: 1; width: auto; min-width: 7rem; padding: .2rem .4rem; }

/* =========================================================================
   Filter help
   ========================================================================= */

#filter-help-card { width: min(640px, 100%); }
.help-lede { margin: 0 0 .8rem; font-size: .88rem; color: var(--text); line-height: 1.5; }
.modal-body h3 { margin: 1.1rem 0 .4rem; font-size: .8rem; text-transform: uppercase;
  letter-spacing: .04em; color: var(--text-muted); }
.help-table { width: 100%; border-collapse: collapse; font-size: .86rem; }
.help-table td { padding: .3rem .5rem .3rem 0; vertical-align: top;
  border-bottom: 1px solid var(--border); }
.help-table td:first-child { white-space: nowrap; width: 1%; }
.help-table code, .help-lede code, .modal-body code {
  background: var(--bg-elev); border: 1px solid var(--border); border-radius: 5px;
  padding: .02rem .3rem; font-size: .82em; }
.help-code { background: var(--bg-elev); border: 1px solid var(--border); border-radius: 7px;
  padding: .6rem .7rem; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .8rem; margin: 0; white-space: pre; overflow-x: auto; }

/* =========================================================================
   Year
   ========================================================================= */

.year { padding: .8rem 1rem 2rem; overflow-y: auto; }

/* The band: every long event of the year, laid across one twelve-month axis.
   It is the same data the Ribbon's rail draws, at the zoom where the question
   is "what is this year shaped like". */
.yr-band { border: 1px solid var(--border); border-radius: 10px; padding: .6rem .7rem;
  margin-bottom: 1.2rem; background: var(--bg-elev); }
.yr-band-head { display: flex; align-items: flex-end; gap: .6rem; padding-bottom: .35rem;
  border-bottom: 1px solid var(--border); margin-bottom: .35rem; }
.yr-band-title { width: 11rem; flex: 0 0 auto; font-size: .68rem; text-transform: uppercase;
  letter-spacing: .04em; color: var(--text-muted); }
.yr-ruler { flex: 1; display: flex; min-width: 0; }
.yr-ruler-cell { font-size: .64rem; color: var(--text-faint); text-align: left;
  border-left: 1px solid var(--border); padding-left: .2rem; overflow: hidden; }
.yr-row { display: flex; align-items: center; gap: .6rem; height: 1.35rem; }
.yr-row-label { width: 11rem; flex: 0 0 auto; display: flex; align-items: center; gap: .35rem;
  min-width: 0; font-size: .78rem; }
.yr-row-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.yr-track { position: relative; flex: 1; height: 100%; min-width: 0; }
.yr-bar { position: absolute; top: .18rem; bottom: .18rem; border: 0; border-radius: 4px;
  background: var(--c); cursor: pointer; padding: 0 .25rem; color: #fff; font-size: .6rem;
  text-align: left; overflow: hidden; }
.yr-bar.free { background: var(--c-weak); box-shadow: inset 0 0 0 1px var(--c); color: var(--text); }
.yr-bar:hover { filter: brightness(1.08); }
.yr-bar-days { font-variant-numeric: tabular-nums; opacity: .85; }
.yr-more { margin-top: .4rem; border: 0; background: none; color: var(--accent);
  font-size: .78rem; cursor: pointer; padding: 0; }
.yr-more:hover { text-decoration: underline; }

.yr-months { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 1rem; }
/* min-width: 0 for the same reason as the chips: a grid item's automatic
   minimum is its content, so twelve months that each want 160px would not fit
   two-up on a phone however small the track is told to be. */
.yr-month { border: 1px solid var(--border); border-radius: 10px;
  padding: .5rem .55rem .6rem; min-width: 0; }
.yr-month-name { display: flex; align-items: center; gap: .4rem; border: 0; background: none;
  font-weight: 620; font-size: .86rem; cursor: pointer; padding: 0 0 .3rem; color: var(--text); }
.yr-month-name:hover { color: var(--accent); }
.yr-month-name:hover .month-no { border-color: var(--accent); color: var(--accent); }
.yr-dows { display: grid; grid-template-columns: repeat(7, 1fr); font-size: .6rem;
  color: var(--text-faint); text-align: center; padding-bottom: .15rem; }
.yr-days { display: grid; grid-template-columns: repeat(7, 1fr); gap: 1px; }
.yr-day { position: relative; aspect-ratio: 1; border: 0; background: none; cursor: pointer;
  border-radius: 5px; display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 1px; padding: 0; font-size: .68rem;
  font-variant-numeric: tabular-nums; color: var(--text); }
.yr-day.blank { visibility: hidden; }
.yr-day:hover { background: var(--hover-tint-strong); }
/* A day inside a long event: tinted, and underlined in that calendar's colour.
   Tint alone disappears at this size on a dark surface; the line does not. */
.yr-day.covered { background: var(--cover); box-shadow: inset 0 -2px 0 var(--cover-line); }
.yr-day.today { outline: 2px solid var(--accent); outline-offset: -2px; font-weight: 700; }
.yr-dnum { line-height: 1; }
.yr-dots { display: flex; gap: 1px; height: 3px; align-items: center; }
.yr-dots i { width: 3px; height: 3px; border-radius: 50%; background: var(--c); display: block; }


/* =========================================================================
   When: the date and time pills, and what drops out of them
   ========================================================================= */

.when-row { display: flex; align-items: center; gap: .35rem; flex-wrap: wrap; }
.when-dash { color: var(--text-muted); padding: 0 .1rem; }
.pill { border: 1px solid transparent; background: var(--bg-elev); border-radius: 8px;
  padding: .35rem .6rem; font-size: .88rem; cursor: pointer; color: var(--text);
  font-variant-numeric: tabular-nums; }
.pill:hover { background: var(--hover-tint-strong); }
.pill:focus-visible { outline: 2px solid var(--accent-soft); outline-offset: 1px; }
/* The date carries the weekday, so it is the wide one and reads as a sentence;
   the times are numbers and are sized as numbers. */
.pill-date { font-weight: 550; }
.pill-time { min-width: 4.2rem; text-align: center; }
.fld.inline.all-day { margin-top: .45rem; color: var(--text-muted); }

.pop { position: fixed; z-index: 200; background: var(--bg); border: 1px solid var(--border-strong);
  border-radius: 10px; box-shadow: var(--shadow); padding: .5rem; }
.pop-head { display: flex; align-items: center; gap: .25rem; padding: .1rem .2rem .45rem; }
.pop-month { flex: 1; font-weight: 650; font-size: .9rem;
  display: flex; align-items: center; gap: .35rem; }
.pop-nav { border: 0; background: none; cursor: pointer; color: var(--text-muted);
  font-size: 1.05rem; line-height: 1; padding: .1rem .4rem; border-radius: 6px; }
.pop-nav:hover { background: var(--hover-tint); color: var(--text); }
.pop-days { display: grid; grid-template-columns: repeat(7, 2rem); gap: 1px; }
.pop-dow { text-align: center; font-size: .64rem; text-transform: uppercase;
  letter-spacing: .03em; color: var(--text-faint); padding-bottom: .2rem; }
.pop-day { height: 2rem; border: 0; background: none; border-radius: 50%; cursor: pointer;
  font-size: .82rem; color: var(--text); font-variant-numeric: tabular-nums; }
.pop-day:hover { background: var(--hover-tint-strong); }
.pop-day.other { color: var(--text-faint); }
/* Today is outlined, the chosen day is filled: two different statements, and a
   month where they coincide should still say both. */
.pop-day.today { box-shadow: inset 0 0 0 1px var(--accent); }
.pop-day.on { background: var(--accent); color: #fff; font-weight: 650; }

/* A field over the list, because the two ways of answering "what time" are
   different questions. Aiming at 14:00 in a list of quarters is one click and
   the list is the right shape for it; reaching 07:45 is three flicks of a
   wheel, and 09:07 is not in the list at all. The field has focus the moment
   the menu opens, so the fast path is the one your hands are already on. */
.pop-clock { min-width: 11rem; }
.pop-time-in { width: 100%; box-sizing: border-box; margin-bottom: .35rem;
  padding: .3rem .5rem; border: 1px solid var(--border-strong); border-radius: 6px;
  background: var(--bg); color: var(--text); font: inherit; font-size: .85rem;
  font-variant-numeric: tabular-nums; }
.pop-time-in:focus { outline: none; border-color: var(--accent); }
/* Only once what is in it cannot become a time: "9:" on the way to "9:15" is
   half a word, and a field that goes red at every keystroke is a field that
   is wrong more often than the person typing into it. */
.pop-time-in.bad { border-color: var(--danger); }

/* Positioned, so a row's offsetTop is measured against the list it scrolls in
   rather than against the popover around it. */
.pop-times { max-height: 15rem; overflow-y: auto; min-width: 11rem; position: relative; }
.pop-time { display: flex; width: 100%; align-items: baseline; justify-content: space-between;
  gap: 1rem; border: 0; background: none; cursor: pointer; padding: .3rem .5rem;
  border-radius: 6px; font-size: .85rem; color: var(--text); text-align: left;
  font-variant-numeric: tabular-nums; }
.pop-time:hover { background: var(--hover-tint); }
/* Two different statements that one row can carry at once: `on` is the time
   the pill already holds, `hot` is the one Enter would take. When they are the
   same row it has to go on saying both, so the outline is drawn over the fill
   rather than replacing it. */
.pop-time.on { background: var(--accent-weak); font-weight: 620; }
.pop-time.hot { background: var(--hover-tint-strong); }
.pop-time.on.hot { background: var(--accent-weak); box-shadow: inset 0 0 0 1px var(--accent); }
/* A time off the quarter grid, which is the only reason to have typed one. */
.pop-time.exact { font-weight: 650; }
.pop-none { padding: .4rem .5rem; font-size: .8rem; color: var(--text-muted); }
.pop-dur { color: var(--text-muted); font-size: .74rem; }

/* The calendar palette. Five by two, at a size that can be aimed at, with the
   current colour ringed rather than ticked: a tick drawn on a colour is a
   contrast problem in one of the ten. */
.pop-colors { display: grid; grid-template-columns: repeat(5, 1.7rem); gap: .35rem; padding: .1rem; }
.pop-color { width: 1.7rem; height: 1.7rem; border: 0; border-radius: 50%; cursor: pointer;
  background: var(--c); box-shadow: inset 0 0 0 1px rgba(0,0,0,.15); }
.pop-color:hover { transform: scale(1.12); }
.pop-color.on { box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--c); }
/* The way out of the ten. A colour well, drawn as one more swatch: it is the
   same decision, with the system's picker behind it. */
.pop-color-any { width: 1.7rem; height: 1.7rem; padding: 0; border: 1px dashed var(--border-strong);
  border-radius: 50%; background: none; cursor: pointer; }
.pop-color-any::-webkit-color-swatch-wrapper { padding: 2px; }
.pop-color-any::-webkit-color-swatch { border: 0; border-radius: 50%; }
.pop-color-any::-moz-color-swatch { border: 0; border-radius: 50%; }


/* =========================================================================
   "An update is out"
   ========================================================================= */

/* A strip, not a modal. In the accent colour rather than a warning colour: a
   newer release is news, not a fault. */
.update-notice { display: flex; align-items: center; gap: .25rem;
  margin: .1rem .75rem .6rem; padding: .3rem .35rem .3rem .5rem;
  border-radius: 6px; font-size: .76rem; color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent); }
.update-notice .un-text { display: flex; align-items: center; gap: .4rem;
  min-width: 0; flex: 1 1 auto; color: inherit; text-decoration: none; }
.update-notice .un-text:hover { text-decoration: underline; }
.update-notice .un-icon { flex: 0 0 auto; display: flex; }
.update-notice .un-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.update-notice .un-close { flex: 0 0 auto; display: flex; align-items: center;
  padding: .15rem; border: 0; background: none; cursor: pointer; color: inherit;
  opacity: .6; border-radius: 4px; }
.update-notice .un-close:hover { opacity: 1;
  background: color-mix(in srgb, var(--accent) 18%, transparent); }


/* NOTE ON ORDER: this block is last in the file on purpose. It overrides the
   sections above it, and CSS resolves ties by document order. While it sat in
   the middle, every rule added below it (the year view, the pickers) quietly
   won against its narrow counterpart, and the year came out one month per row
   on a phone with the media query looking perfectly correct. Keep new sections
   *above* this line.
*/

/* =========================================================================
   Narrow: phones, and a window shared with something else
   =========================================================================

   The sidebar becomes a drawer rather than disappearing: hiding it took the
   calendars, the sets and Create with it, which is most of the app. The
   toolbar wraps instead of overflowing, because what fell off the end of it
   was the view switch, the control you need *most* on a small screen, where
   less of the calendar fits at once. */
@media (max-width: 900px) {
  .app { grid-template-columns: 1fr; }

  .menu-btn { display: inline-flex; }

  /* A drawer over the calendar, not a column beside it. */
  .sidebar { position: fixed; z-index: 70; top: 0; bottom: 0; left: 0;
    width: min(82vw, 320px); transform: translateX(-101%);
    transition: transform .18s ease-out; box-shadow: var(--shadow); }
  body.drawer-open .sidebar { transform: none; }
  .scrim { display: block; position: fixed; inset: 0; z-index: 65;
    background: rgba(0,0,0,.35); opacity: 0; pointer-events: none;
    transition: opacity .18s ease-out; }
  body.drawer-open .scrim { opacity: 1; pointer-events: auto; }

  /* Two rows rather than one that runs off the edge. */
  .toolbar { flex-wrap: wrap; gap: .35rem; padding: .45rem .6rem; }
  .view-title { font-size: .95rem; margin: 0 .2rem; flex: 1; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .hidden-note, .zone-note { order: 5; }
  /* Full width on its own line, and scrollable if even that is not enough:
     five views on a 320px screen do not fit any other way. */
  .view-switch { order: 6; width: 100%; overflow-x: auto; scrollbar-width: none; }
  .view-switch::-webkit-scrollbar { display: none; }
  .view-switch button { flex: 1 0 auto; padding: .35rem .7rem; font-size: .82rem; }
  .toolbar .grow { display: none; }

  .filter-bar { padding: .3rem .6rem; }
  #filter-input { font-size: 16px; }   /* iOS zooms into anything smaller */

  /* Create, as the floating button it has to be once the sidebar is a drawer. */
  .fab { display: inline-flex; align-items: center; justify-content: center;
    position: fixed; z-index: 50; right: 1rem; bottom: 1.15rem;
    width: 3.4rem; height: 3.4rem; border-radius: 50%; border: 0;
    background: var(--accent); color: #fff; box-shadow: var(--shadow);
    cursor: pointer; }
  .fab:active { transform: translateY(1px); }

  /* The views themselves. */
  .chip-where { display: none; }
  .chip-title { max-width: none; }
  .rb-day { min-width: 0; }
  :root { --gutter-w: 52px; --lane-w: 20px; }
  .rb-day { padding: .4rem .5rem .4rem .45rem; }

  /* A week of seven columns does not fit a phone, so it scrolls sideways:
     better than squeezing seven columns into 44px each, where the titles say
     nothing. The header cannot drift out of step with the grid because there
     is only one scroller now -- #stage moves both of them, in both directions. */
  .wk-period { min-width: 680px; }

  .yr-band-title, .yr-row-label { width: 7.5rem; font-size: .74rem; }
  .yr-ruler-cell { font-size: .55rem; }
  .yr-months { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: .6rem; }
  .yr-month-name { font-size: .78rem; }
  .yr-day { font-size: .6rem; }

  .modal-card { width: 100%; max-height: 92vh; }
  .fld-row { flex-wrap: wrap; }
  .key-hint { bottom: 5.6rem; }        /* clear of the floating button */
}

/* A phone, rather than a small window: the shortcut list is furniture for a
   keyboard that is not there. */
@media (max-width: 900px) and (pointer: coarse) {
  .shortcut-box { display: none; }
}
