/* ==================================================================== fonts */
/* Inter (SIL Open Font License 1.1), served locally so the app renders
   identically offline and makes no third-party requests. */

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('./assets/fonts/inter-latin-400.woff2') format('woff2');
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('./assets/fonts/inter-latin-500.woff2') format('woff2');
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('./assets/fonts/inter-latin-600.woff2') format('woff2');
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('./assets/fonts/inter-latin-700.woff2') format('woff2');
}

/* ============================================================ design tokens */

/* Soft-grey dark chrome, in the Notion / Claude register: mid greys rather than
   near-black, so the surface recedes without the harshness of #000. Nothing here
   is pure neutral — a trace of warmth keeps large dark areas from reading blue.

   The block palette stays exactly as sampled from `Reference Photos/`; only the
   surfaces around it are greyscale, so colour in the interface means "a block",
   never decoration. The reference's surface *ordering* is preserved too: weekend
   columns sit a step lighter than weekdays, today a step lighter again. Coral is
   the single accent, reserved for today. */
:root {
  --bg: #191918;              /* page ground, outside the grid */
  --sidebar: #202020;
  --canvas: #1E1E1D;          /* a weekday column */
  --canvas-weekend: #242423;  /* weekends read *lighter* than weekdays */
  --canvas-today: #302F2E;
  --surface: #1E1E1D;
  --surface-2: #2A2A28;
  --active: #383836;          /* selected segmented / nav state */
  --line: rgba(255, 255, 255, 0.08);
  --line-strong: rgba(255, 255, 255, 0.16);

  --text: #E9E8E5;
  --text-dim: #9C9B96;
  --text-faint: #6E6D68;

  --accent: #E87161;
  --accent-soft: rgba(232, 113, 97, 0.15);
  --coral: #E87161;
  --on-accent: #FFFFFF;

  /* Semantic surfaces that have to invert between themes. */
  --hover: rgba(255, 255, 255, 0.05);
  --hover-strong: rgba(255, 255, 255, 0.07);
  --fill-subtle: rgba(255, 255, 255, 0.04);
  --track: rgba(255, 255, 255, 0.08);
  --scrollbar: rgba(255, 255, 255, 0.12);
  --week-line: rgba(255, 255, 255, 0.09);
  --ring: #FFFFFF;
  --draft-outline: rgba(255, 255, 255, 0.3);
  --dot-halo: rgba(255, 255, 255, 0.06);
  --dot-halo-hover: rgba(255, 255, 255, 0.12);
  --tab-track: rgba(0, 0, 0, 0.25);

  --btn-bg: #F0EFEC;
  --btn-bg-hover: #FBFAF7;
  --btn-fg: #191918;

  --mark-bg: #131312;
  --cancelled-fill: #3A3A37;
  --cancelled-ink: #A3A29C;
  --avatar-a: #3A3A37;
  --avatar-b: #272725;
  --avatar-fg: #FFFFFF;

  --shadow-sm: 0 6px 20px rgba(0, 0, 0, 0.4);
  --shadow-md: 0 8px 20px rgba(0, 0, 0, 0.45);
  --shadow-panel: -18px 0 48px rgba(0, 0, 0, 0.45);
  --shadow-lg: 0 14px 40px rgba(0, 0, 0, 0.55);

  --radius: 8px;
  --rail-w: 74px;

  /* The reference sets Inter. It is used when present locally; otherwise this
     falls back to SF Pro, which is metrically the closest system face. */
  --font: 'Inter', 'Inter var', -apple-system, BlinkMacSystemFont, 'SF Pro Text',
          'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}

/* ------------------------------------------------------------- day theme */
/* Notion-style light: warm off-white grounds with white content columns, and
   Notion's signature warm near-black for text rather than pure #000. The block
   palette is identical in both themes — a block's colour means the same thing
   whatever the surface behind it. */
:root[data-theme="light"] {
  --bg: #F7F6F3;
  --sidebar: #FBFAF8;
  --canvas: #FFFFFF;
  --canvas-weekend: #F5F4F0;
  --canvas-today: #ECEAE4;
  --surface: #FFFFFF;
  --surface-2: #FFFFFF;
  --active: #E9E7E2;
  --line: rgba(55, 53, 47, 0.10);
  --line-strong: rgba(55, 53, 47, 0.22);

  --text: #37352F;
  --text-dim: #6E6B64;
  --text-faint: #9A968D;

  --accent-soft: rgba(232, 113, 97, 0.14);

  --hover: rgba(55, 53, 47, 0.055);
  --hover-strong: rgba(55, 53, 47, 0.08);
  --fill-subtle: rgba(55, 53, 47, 0.04);
  --track: rgba(55, 53, 47, 0.10);
  --scrollbar: rgba(55, 53, 47, 0.20);
  --week-line: rgba(55, 53, 47, 0.12);
  --ring: #37352F;
  --draft-outline: rgba(55, 53, 47, 0.35);
  --dot-halo: rgba(55, 53, 47, 0.08);
  --dot-halo-hover: rgba(55, 53, 47, 0.16);
  --tab-track: rgba(55, 53, 47, 0.07);

  --btn-bg: #37352F;
  --btn-bg-hover: #4A473F;
  --btn-fg: #FFFFFF;

  --mark-bg: #EDEBE6;
  --cancelled-fill: #E4E2DC;
  --cancelled-ink: #86837B;
  --avatar-a: #E9E7E2;
  --avatar-b: #DAD7D0;
  --avatar-fg: #37352F;

  --shadow-sm: 0 4px 14px rgba(15, 15, 15, 0.10);
  --shadow-md: 0 8px 20px rgba(15, 15, 15, 0.12);
  --shadow-panel: -18px 0 48px rgba(15, 15, 15, 0.12);
  --shadow-lg: 0 14px 40px rgba(15, 15, 15, 0.18);
}

* { box-sizing: border-box; }

html, body {
  height: 100%;
  margin: 0;
}

body {
  font-family: var(--font);
  background: var(--bg);
  color: var(--text);
  font-size: 14px;
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
}

body.is-dragging-block { cursor: grabbing; user-select: none; }

kbd {
  display: inline-block;
  min-width: 17px;
  padding: 1px 4px;
  border: 1px solid var(--line-strong);
  border-bottom-width: 2px;
  border-radius: 4px;
  background: var(--fill-subtle);
  font: 500 10px/1.4 var(--font);
  color: var(--text-dim);
  text-align: center;
}

/* The row is pinned, not just the container. A grid row defaults to `auto`,
   which sizes to its content and will happily grow past `height: 100vh` — and
   because the timeline measures the viewport to decide how tall to draw the
   grid, that turns into a feedback loop that pushes the floating controls off
   the bottom of the screen. Pinning the row breaks it at the source. */
#app {
  display: grid;
  grid-template-columns: 236px minmax(0, 1fr);
  grid-template-rows: 100vh;
  height: 100vh;
}

/* On phones the URL bar makes 100vh taller than what you can actually see, so
   the floating controls sit below the fold until you scroll. dvh tracks the
   real visible height; the vh rules above stay as the fallback. */
@supports (height: 100dvh) {
  #app { grid-template-rows: 100dvh; height: 100dvh; }
}

/* ================================================================= sidebar */

.sidebar {
  min-height: 0;
  background: var(--sidebar);
  border-right: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  padding: 16px 12px;
  gap: 18px;
  overflow-y: auto;
}

.brand { display: flex; align-items: center; gap: 9px; padding: 0 4px; }

/* A recessed tile sets the mark apart from the sidebar. The glyph itself is the
   logo's white symbol, redrawn as vector and inheriting the theme's text colour
   — the source icon's blue gradient is dropped. */
.brand-mark {
  width: 34px; height: 34px;
  flex: none;
  display: grid; place-items: center;
  border-radius: 10px;
  background: var(--mark-bg);
  box-shadow: inset 0 0 0 1px var(--line);
}

.brand-glyph {
  width: 21px;
  height: auto;
  fill: currentColor;
  color: var(--text);
}

/* Tracking is tight because the wordmark is mixed-case, not the all-caps it
   was originally set for. `line-height: 1` drops the font's built-in leading,
   which otherwise sits the single line a touch high against the mark. */
.brand-name {
  font-weight: 700; letter-spacing: -0.005em; font-size: 16px;
  line-height: 1;
}

.side-search { position: relative; }

.side-search input {
  width: 100%;
  padding: 8px 10px 8px 30px;
  border-radius: 8px;
  border: 1px solid var(--line);
  background: var(--fill-subtle);
  color: var(--text);
  font: inherit;
  font-size: 13px;
}
.side-search input:focus { outline: none; border-color: var(--line-strong); background: var(--hover-strong); }
.side-search input::placeholder { color: var(--text-faint); }

.search-icon {
  position: absolute; left: 10px; top: 50%;
  transform: translateY(-50%);
  color: var(--text-faint); font-size: 15px;
}

.search-clear {
  position: absolute; right: 6px; top: 50%;
  transform: translateY(-50%);
  border: 0; background: transparent;
  color: var(--text-faint);
  font: inherit; font-size: 11px;
  padding: 4px 6px; border-radius: 6px;
  cursor: pointer;
}
.search-clear:hover { background: var(--active); color: var(--text); }

/* The results list. Capped and scrollable so a broad query cannot push the
   tag grid off the bottom of the sidebar. */
.search-results {
  display: flex; flex-direction: column;
  gap: 4px;
  max-height: 40vh;
  overflow-y: auto;
  margin: -4px 0 2px;
  /* The sidebar is a flex column that already overflows, so without this the
     list is shrunk to zero height — present in the DOM, invisible on screen. */
  flex: none;
}

.search-more, .search-empty {
  font-size: 10px;
  letter-spacing: 0.04em;
  color: var(--text-faint);
  padding: 2px 6px 4px;
  line-height: 1.45;
}

.search-hits { display: flex; flex-direction: column; gap: 2px; }

.search-hit {
  display: flex; align-items: center; gap: 8px;
  width: 100%;
  padding: 6px 8px;
  border: 0; border-radius: 8px;
  background: transparent;
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.search-hit:hover { background: var(--hover-strong); }

.hit-swatch {
  width: 8px; height: 8px;
  border-radius: 3px;
  flex: none;
}

/* min-width:0 lets the title ellipsis instead of forcing the row wider. */
.hit-body { display: flex; flex-direction: column; gap: 1px; min-width: 0; }

.hit-title {
  font-size: 12px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.hit-date { font-size: 10px; color: var(--text-faint); }

.side-nav { display: flex; flex-direction: column; gap: 2px; }

.side-head {
  font-size: 10px;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--text-faint);
  padding: 0 6px 6px;
}

.side-item {
  display: flex; align-items: center; gap: 8px;
  width: 100%;
  padding: 7px 8px;
  border: 0;
  border-radius: 7px;
  background: transparent;
  color: var(--text-dim);
  font: inherit; font-size: 13px;
  cursor: pointer;
  text-align: left;
}
.side-item:hover { background: var(--hover); color: var(--text); }
.side-item.is-on { background: var(--active); color: var(--text); }
.side-item-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.side-count { font-size: 11px; color: var(--text-faint); }
.side-empty { padding: 4px 8px; font-size: 12px; color: var(--text-faint); }

.side-foot { margin-top: auto; display: flex; flex-direction: column; gap: 6px; padding: 12px 6px 0; border-top: 1px solid var(--line); }

/* ------------------------------------------------- the once-a-day streak card */

.streak-cel {
  position: fixed; inset: 0; z-index: 120;
  /* flex-start plus auto margins centres the card when it fits and lets the
     overlay scroll when it does not — a landscape phone is shorter than the
     card, and centring alone clips the top and the button off both ends. */
  display: flex; align-items: flex-start; justify-content: center;
  padding: 24px;
  overflow-y: auto;
}
.streak-cel-scrim {
  position: absolute; inset: 0;
  background: rgba(0, 0, 0, 0.62);
  backdrop-filter: blur(3px);
  animation: streak-fade 0.3s ease both;
}
.streak-cel-card {
  position: relative;
  margin: auto;
  width: min(340px, 100%);
  padding: 30px 26px 24px;
  text-align: center;
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: 20px;
  box-shadow: var(--shadow-lg);
  animation: streak-rise 0.52s cubic-bezier(0.2, 1.5, 0.35, 1) both;
}
/* A milestone gets warmth behind it rather than a different layout. */
.streak-cel-card.is-milestone {
  border-color: var(--accent);
  box-shadow: var(--shadow-lg), 0 0 46px -12px rgba(232, 113, 97, 0.75);
}
.streak-cel.is-leaving .streak-cel-card { animation: streak-sink 0.22s ease-in both; }
.streak-cel.is-leaving .streak-cel-scrim { animation: streak-unfade 0.22s ease-in both; }

.streak-cel-burst {
  position: relative;
  height: 84px;
  display: flex; align-items: center; justify-content: center;
  margin-bottom: 6px;
}
.streak-cel-flame {
  font-size: 46px; line-height: 1;
  animation: streak-pop 0.62s cubic-bezier(0.2, 1.6, 0.4, 1) 0.08s both;
}
.streak-cel-ring {
  position: absolute; width: 62px; height: 62px;
  border: 2px solid var(--accent);
  border-radius: 50%;
  animation: streak-ring 1.15s ease-out 0.1s both;
}
.streak-cel-ring.is-late { animation-delay: 0.42s; }
.streak-spark {
  position: absolute;
  width: var(--s); height: var(--s);
  border-radius: 50%;
  background: var(--accent);
  animation: streak-spark 0.95s ease-out var(--delay) both;
}

.streak-cel-eyebrow {
  margin: 0; font-size: 11px; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--accent); font-weight: 600;
}
.streak-cel-count { margin: 2px 0 0; font-size: 58px; font-weight: 700; line-height: 1.05; }
.streak-cel-unit { margin: 0; font-size: 13px; color: var(--text-dim); }
.streak-cel-note { margin: 14px 0 0; font-size: 13px; color: var(--text); line-height: 1.5; }
.streak-cel-best { margin: 6px 0 0; font-size: 11.5px; color: var(--text-faint); }
.streak-cel-go { margin-top: 18px; width: 100%; }

@keyframes streak-fade { from { opacity: 0; } to { opacity: 1; } }
/* A distinct name, not `streak-fade reverse`: the same name at the same index
   restarts nothing, it retargets the finished entrance, and the scrim vanished
   in a single frame. */
@keyframes streak-unfade { from { opacity: 1; } to { opacity: 0; } }
@keyframes streak-rise {
  from { opacity: 0; transform: translateY(18px) scale(0.9); }
  to   { opacity: 1; transform: none; }
}
@keyframes streak-sink {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translateY(8px) scale(0.97); }
}
@keyframes streak-pop {
  0%   { transform: scale(0.3) rotate(-14deg); opacity: 0; }
  60%  { transform: scale(1.18) rotate(4deg); opacity: 1; }
  100% { transform: none; opacity: 1; }
}
@keyframes streak-ring {
  from { transform: scale(0.55); opacity: 0.85; }
  to   { transform: scale(2.1); opacity: 0; }
}
@keyframes streak-spark {
  0%   { transform: rotate(var(--a)) translateY(0) scale(0.3); opacity: 0; }
  18%  { opacity: 1; }
  100% { transform: rotate(var(--a)) translateY(calc(var(--d) * -1)) scale(0.85); opacity: 0; }
}

/* Everything above is decoration. Honour the setting that asks for none of it:
   the card still appears and still says the same thing, it just stops moving. */
@media (prefers-reduced-motion: reduce) {
  .streak-cel-scrim, .streak-cel-card, .streak-cel-flame,
  .streak-cel-ring, .streak-spark,
  /* The exit rules are three classes deep and a media query adds no specificity
     of its own, so they have to be named here or they keep animating for
     someone who asked for stillness. */
  .streak-cel.is-leaving .streak-cel-card,
  .streak-cel.is-leaving .streak-cel-scrim { animation: none; }
  .streak-cel-ring { display: none; }
}

/* The footer line is hidden: the once-a-day card says the same thing with more
   ceremony, and saying it twice made the quiet copy the one you stopped seeing.
   Left wired — renderStreak() still runs and still keeps it current — so this is
   `flex` again to bring it back.

   Were it visible, the author `display` here would beat the UA's
   `[hidden] { display: none }` however specific that attribute selector looks,
   so the hidden case stays spelled out below. Left implicit once before, and an
   invisible full-screen scrim swallowed every touch on the timeline. */
.streak-row { display: none; align-items: center; gap: 6px; font-size: 11.5px; color: var(--text-dim); }
.streak-row[hidden] { display: none; }
.streak-flame { font-size: 12px; line-height: 1; }

/* --------------------------------------- dragging a tag onto the timeline */

/* Follows the cursor from the tag list to the grid. `translate` is set from
   script on every pointermove, so the offsets live here — the element sits
   slightly below and right of the pointer, clear of what is being aimed at. */
.tag-ghost {
  position: fixed; top: 0; left: 0;
  z-index: 130;
  display: flex; align-items: center; gap: 6px;
  margin: 10px 0 0 12px;
  padding: 5px 10px 5px 7px;
  border-radius: 8px;
  background: var(--fill-subtle);
  color: var(--text);
  box-shadow: var(--shadow-lg);
  font-size: 11.5px; font-weight: 600;
  text-transform: capitalize;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0.72;
  transition: opacity 0.12s ease, transform 0.06s linear;
}
/* Over a day it firms up; over anything else it stays faint, which is the
   only signal that letting go here will do nothing. */
.tag-ghost.is-over { opacity: 1; }
.tag-ghost svg { width: 13px; height: 13px; }

body.is-dragging-tag { cursor: grabbing; }
/* A drag that starts on a button otherwise selects the label text under it. */
body.is-dragging-tag * { user-select: none; }
.tag-dot { cursor: grab; }

.store-row { display: flex; align-items: center; gap: 7px; font-size: 11px; color: var(--text-dim); }
.store-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--text-faint); flex: none; }
.store-dot.is-saved { background: #4ECB71; }
.store-dot.is-saving { background: var(--accent); }
.store-dot.is-warn { background: #E0A21A; }
/* Export/Import are hidden from the sidebar for now. Kept wired up in the DOM
   and JS so turning them back on is just changing `none` to `flex` here —
   display:none rather than the [hidden] attribute, because this rule's own
   display would otherwise override the attribute and hide nothing. */
.store-actions { display: none; gap: 6px; margin-bottom: 6px; }
.store-actions .ghost-btn { flex: 1; text-align: center; }
.kbd-row { display: flex; align-items: center; gap: 5px; font-size: 11px; color: var(--text-faint); }
.kbd-row span { margin-left: 3px; }
.kbd-row .dim { margin-left: 0; line-height: 1.5; }

/* ==================================================================== main */

.main { display: flex; flex-direction: column; min-width: 0; min-height: 0; overflow: hidden; position: relative; background: var(--bg); }

.topbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px;
  padding: 14px 20px;
  border-bottom: 1px solid var(--line);
  flex: none;
}

.topbar-left { display: flex; align-items: baseline; gap: 12px; }
.topbar h1 { margin: 0; font-size: 17px; font-weight: 600; letter-spacing: 0.01em; }

.count-pill { font-size: 11px; color: var(--text-faint); }

.topbar-right { display: flex; align-items: center; gap: 14px; }

/* "Show done" is hidden for now. It filters correctly — but almost nothing is
   ever marked done (none of the 6,567 blocks on the live calendar, 15 of 14,221
   in the local archive), so it reads as a control that does nothing. Left wired
   in the DOM and the state; state.js pins showDone true while this is hidden,
   so no calendar can be stranded with its done blocks invisible and no way to
   bring them back. Turn both back on together. */
.switch { display: none; align-items: center; gap: 7px; font-size: 12px; color: var(--text-dim); cursor: pointer; }
.switch input { accent-color: var(--accent); width: 14px; height: 14px; }

.primary-btn {
  border: 0;
  border-radius: 8px;
  padding: 8px 15px;
  background: var(--btn-bg);
  color: var(--btn-fg);
  font: inherit; font-size: 13px; font-weight: 600;
  cursor: pointer;
}
.primary-btn:hover { background: var(--btn-bg-hover); }

/* ================================================================ timeline */

/* --tl-scale is set from layout.js DENSITY. It scales only what lives inside
   the calendar — the sidebar and topbar keep their own sizes. */
/* Fallback only — layout.js overwrites this on init. Keep it mirroring DENSITY
   so there is no flash at the wrong size before the script runs. */
.timeline { flex: 1; display: flex; min-height: 0; --tl-scale: 0.84; }

.tl-rail {
  width: var(--rail-w);
  flex: none;
  border-right: 1px solid var(--line);
  background: var(--bg);
  display: flex; flex-direction: column;
}
.tl-rail-head { height: calc(64px * var(--tl-scale)); border-bottom: 1px solid var(--line); flex: none; }
.tl-rail-body { padding: 8px 0; display: flex; justify-content: center; }

.tl-avatar {
  width: calc(34px * var(--tl-scale)); height: calc(34px * var(--tl-scale));
  border-radius: 50%;
  display: grid; place-items: center;
  background: linear-gradient(145deg, var(--avatar-a), var(--avatar-b));
  border: 1px solid var(--line-strong);
  font-weight: 700; font-size: calc(14px * var(--tl-scale)); color: var(--avatar-fg);
}

.tl-scroll { flex: 1; overflow: auto; position: relative; }
.tl-scroll::-webkit-scrollbar { width: 11px; height: 11px; }
.tl-scroll::-webkit-scrollbar-thumb { background: var(--scrollbar); border-radius: 6px; border: 3px solid var(--bg); }
.tl-scroll::-webkit-scrollbar-track { background: transparent; }

.tl-canvas { position: relative; min-width: 100%; }

/* ---- header ---- */

.tl-header {
  position: sticky; top: 0; z-index: 20;
  background: var(--bg);
  border-bottom: 1px solid var(--line);
  height: calc(64px * var(--tl-scale));
}

.tl-tier-top { position: relative; height: calc(26px * var(--tl-scale)); }
.tl-tier-days { position: relative; height: calc(37px * var(--tl-scale)); }

.tl-month {
  position: absolute; top: 8px;
  font-size: calc(9.5px * var(--tl-scale)); font-weight: 700; letter-spacing: 0.13em;
  color: var(--text-faint);
  opacity: 1;
  white-space: nowrap; overflow: hidden; text-overflow: clip;
  pointer-events: none;
}

.tl-weektick { position: absolute; top: 0; bottom: 0; border-left: 1px solid var(--line); }
.tl-weektick span {
  position: absolute; top: 2px; left: 3px;
  font-size: calc(9px * var(--tl-scale)); color: var(--text-faint);
}

.tl-day {
  position: absolute; top: 0; bottom: 0;
  display: flex; align-items: center; justify-content: center;
}
.tl-day-label {
  font-size: calc(12px * var(--tl-scale)); font-weight: 500; color: var(--text-dim);
  padding: calc(4px * var(--tl-scale)) calc(10px * var(--tl-scale)); border-radius: 6px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  max-width: 100%;
}
.tl-day.is-weekend .tl-day-label { color: var(--text-faint); }
.tl-day.is-today .tl-day-label {
  background: var(--coral);
  color: var(--on-accent);
  font-weight: 600;
  min-width: calc(62px * var(--tl-scale)); text-align: center;
}

/* At year density the per-column label is unreadable, so drop it and let the
   month banner + week numbers carry the header. Today keeps a coral marker.
   Week-grouped headers are exempt: their labels span seven columns, so they
   stay readable however narrow the individual days get. */
.tl-canvas.is-micro:not(.is-weeklabels) .tl-day-label { display: none; }
.tl-canvas.is-micro:not(.is-weeklabels) .tl-day.is-today::after {
  content: '';
  position: absolute; left: 2px; right: 2px; top: 11px; height: 15px;
  background: var(--coral); border-radius: 4px;
}

/* ---- body ---- */

.tl-body { position: relative; }

.tl-cols { position: absolute; inset: 0; }
/* A hairline between every day, as in the reference — so Saturday and Sunday
   are still separated even though they share a shade. `box-sizing: border-box`
   is global, so the border sits inside the column and shifts nothing. */
.tl-col { position: absolute; top: 0; bottom: 0; background: var(--canvas);
          border-right: 1px solid var(--line); }
.tl-col.is-weekend { background: var(--canvas-weekend); }
.tl-col.is-today { background: var(--canvas-today); }
.tl-col.is-month-start { border-left: 1px solid var(--line-strong); }
.tl-col.is-week-start { border-left: 1px solid var(--week-line); }

.tl-hint {
  position: absolute;
  border: 1px dashed var(--line-strong);
  border-radius: 6px;
  pointer-events: none;
  z-index: 1;
}
.tl-hint::after {
  content: '+';
  position: absolute; inset: 0;
  display: grid; place-items: center;
  color: var(--text-faint); font-size: 15px;
}

.tl-chips { position: absolute; inset: 0; }

/* ---- chips ---- */

/* Title on the first line, time / meta right-aligned underneath — a chip with
   no meta ends up vertically centred, which is how the reference reads. */
.chip {
  position: absolute;
  display: flex; flex-direction: column;
  /* Top-aligned, not centred: a chip with no time would otherwise centre its
     single line and sit lower than its timed neighbours. Anchoring to the top
     puts every title on the same baseline across the whole grid. */
  align-items: flex-start; justify-content: flex-start; gap: calc(1px * var(--tl-scale));
  /* Vertical padding sets where the title sits. Room for it comes from the
     tightened title/time gap — the two-line chips have little slack. */
  padding: calc(5px * var(--tl-scale)) calc(10px * var(--tl-scale));
  border-radius: calc(5px * var(--tl-scale));
  background: var(--fill);
  color: var(--ink);
  line-height: 1.22;
  white-space: nowrap;
  overflow: hidden;
  cursor: grab;
  user-select: none;
  transition: box-shadow 0.12s ease, filter 0.12s ease;
}
.chip:hover { filter: brightness(1.04); }
.chip.is-span .chip-title { font-weight: 600; letter-spacing: 0.015em; }

.chip-title {
  width: 100%; min-width: 0;
  overflow: hidden; text-overflow: ellipsis;
  font-size: calc(13px * var(--tl-scale)); font-weight: 500; letter-spacing: -0.005em;
}
.chip-title em { opacity: 0.55; font-style: italic; }

/* Month view: columns are too narrow for an ellipsis to be worth its own two
   characters, so titles clip hard at the edge as in the reference. */
.tl-canvas[data-zoom="month"] .chip-title { text-overflow: clip; }

/* The time/meta line always sits on its own row beneath the title — it never
   shares a line with it, so a long title can never crowd the timestamp out. */
.chip-meta {
  display: flex; align-items: center; justify-content: space-between; gap: 7px;
  width: 100%;
  font-size: calc(11px * var(--tl-scale)); font-weight: 500; opacity: 0.78; flex: none;
  overflow: hidden; white-space: nowrap;
  font-variant-numeric: tabular-nums;
  /* Takes the slack, so the time sits on the bottom padding however tall the
     block is. A block stretched over several rows used to keep its time tucked
     under the title with the empty space below it, which read as though the
     time belonged to the top of the block rather than to the block. On a
     single-row chip there is no slack to take, so those are untouched and the
     times stay on one line across the grid. */
  margin-top: auto;
}
.chip-badges { display: flex; align-items: center; gap: 6px; min-width: 0; overflow: hidden; }
.chip-time { flex: none; margin-left: auto; }
.chip-check { font-variant-numeric: tabular-nums; }
.chip-icon { font-size: calc(10px * var(--tl-scale)); }

/* An unnamed draft: present enough to see where it will land, faded enough to
   read as "not real yet". Discarded if the user never names it. */
.chip.is-draft {
  opacity: 0.62;
  box-shadow: inset 0 0 0 1px var(--draft-outline);
}
.chip.is-draft .chip-title { font-style: italic; }

.chip.is-done { opacity: 0.5; }
.chip.is-done .chip-title { text-decoration: line-through; }

.chip.is-selected {
  box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--ring);
  z-index: 5;
}

/* Multi-select. Blocks come in 45 colours, so the mark cannot rely on hue —
   it uses a ring plus a brightness lift, which reads on every fill in both
   themes. The ring is drawn *inside* the chip: an outside ring on two stacked
   blocks puts two halos either side of a 3px gap, and they read as one thick
   smear rather than two selected blocks. Inset, they can never collide. */
.chip.is-marked {
  box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.92);
  filter: brightness(1.06);
  z-index: 6;
}
:root[data-theme="light"] .chip.is-marked,
.theme-light .chip.is-marked {
  box-shadow: inset 0 0 0 2px rgba(24, 24, 28, 0.85);
}
/* Selected *and* marked: the accent ring stays outside, the mark stays inside,
   so the two states remain tellable apart. Only one block is ever selected, so
   this outer ring cannot collide with a neighbour's. */
.chip.is-marked.is-selected {
  box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.92),
              0 0 0 2px var(--bg), 0 0 0 4px var(--ring);
}
:root[data-theme="light"] .chip.is-marked.is-selected,
.theme-light .chip.is-marked.is-selected {
  box-shadow: inset 0 0 0 2px rgba(24, 24, 28, 0.85),
              0 0 0 2px var(--bg), 0 0 0 4px var(--ring);
}

.mark-bar {
  position: fixed;
  left: 50%; bottom: 22px;
  transform: translateX(-50%);
  display: flex; align-items: center; gap: 14px;
  padding: 10px;
  border-radius: 12px;
  /* --panel was never defined anywhere in this file, so this bar had no
     background at all and the calendar showed straight through it. Harmless
     while it held a count and two buttons; not once the palette moved in, with
     block titles running underneath the swatches. --surface-2 is what the rest
     of the app raises a surface with, and it is defined for both themes. */
  background: var(--surface-2);
  backdrop-filter: blur(8px);
  border: 1px solid var(--line-strong);
  box-shadow: 0 12px 34px rgba(0, 0, 0, 0.45);
  z-index: 80;
}
.mark-bar[hidden] { display: none; }
.mark-count { font-size: 13px; color: var(--text); white-space: nowrap; }

/* The count and the two buttons stack into one column beside the palette.
   Spread along the bar instead, they were three items centred against nine rows
   of swatches, so both flanks of the bar were empty and it was three times
   wider than anything in it. */
.mark-side {
  display: flex; flex-direction: column; gap: 10px;
  align-items: stretch;
}
.mark-actions { display: flex; gap: 8px; }
.mark-actions button { flex: 1; white-space: nowrap; }

/* The palette, laid out exactly as the block drawer lays it out — .picker-grid
   and .swatch are the drawer's own classes, not a second copy of them, so the
   two cannot drift into looking like different features. Only the rules that
   place it inside this bar live here. */
.mark-picker {
  border-right: 1px solid var(--line);
  padding-right: 14px;
}
.mark-picker .swatch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.chip.is-dragging {
  z-index: 60;
  cursor: grabbing;
  filter: brightness(1.06);
  box-shadow: 0 14px 30px rgba(0, 0, 0, 0.45), 0 2px 6px rgba(0, 0, 0, 0.3);
  /* No transform transition: it has to track the pointer exactly. */
  transition: box-shadow 0.15s ease, filter 0.15s ease;
  will-change: transform;
}
.chip.is-dragging.is-copying { box-shadow: 0 14px 30px rgba(0, 0, 0, 0.45), 0 0 0 2px var(--accent); }

.chip-handle { position: absolute; top: 0; bottom: 0; width: 9px; cursor: ew-resize; }
.chip-handle-l { left: 0; }
.chip-handle-r { right: 0; }
.chip:hover .chip-handle::after {
  content: '';
  position: absolute; top: 30%; bottom: 30%; width: 2px;
  background: var(--ink); opacity: 0.35; border-radius: 2px;
}
.chip-handle-l::after { left: 3px; }
.chip-handle-r::after { right: 3px; }

/* Bottom edge: drag to make a block taller. Sits above the side handles at the
   corners so a narrow chip still has somewhere to grab. */
.chip-handle-b {
  top: auto; bottom: 0; left: 0; right: 0;
  width: auto; height: 7px;
  cursor: ns-resize;
  z-index: 2;
}
.chip:hover .chip-handle-b::after {
  content: '';
  position: absolute; left: 38%; right: 38%; bottom: 2px;
  top: auto; width: auto; height: 2px;
  background: var(--ink); opacity: 0.35; border-radius: 2px;
}

.tl-canvas.is-micro .chip { padding: 0 2px; border-radius: 4px; }
.tl-canvas.is-micro .chip-title,
.tl-canvas.is-micro .chip-meta { display: none; }
.tl-canvas.is-micro .chip.is-span .chip-title { display: block; font-size: 10px; }

/* ---- floating controls ---- */

.floating {
  position: absolute; right: 18px; bottom: 18px;
  display: flex; align-items: center; gap: 8px;
  z-index: 25;
}

.zoom-group {
  display: flex;
  background: var(--surface-2);
  border: 1px solid var(--line-strong);
  border-radius: 9px;
  overflow: hidden;
  box-shadow: var(--shadow-sm);
}
.zoom-btn {
  border: 0; background: transparent;
  padding: 8px 13px;
  color: var(--text-dim);
  font: inherit; font-size: 12px; font-weight: 500;
  cursor: pointer;
}
.zoom-btn:hover { color: var(--text); background: var(--hover); }
.zoom-btn.is-on { background: var(--active); color: var(--text); }

.float-btn {
  display: flex; align-items: center; gap: 7px;
  padding: 8px 13px;
  border: 1px solid var(--line-strong);
  border-radius: 9px;
  background: var(--surface-2);
  color: var(--text);
  font: inherit; font-size: 12px; font-weight: 500;
  cursor: pointer;
  box-shadow: var(--shadow-sm);
}
.float-btn:hover { background: var(--active); }

/* The date input lies invisibly over the whole button, so a click anywhere on
   it opens the native picker directly — no showPicker() needed for the mouse
   path, which keeps it working if that API is missing. */
.float-jump { position: relative; overflow: hidden; }
.float-jump input[type="date"] {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  opacity: 0;
  border: 0; padding: 0;
  cursor: pointer;
}
.float-jump input[type="date"]::-webkit-calendar-picker-indicator {
  width: 100%; height: 100%;
  margin: 0; padding: 0;
  cursor: pointer;
}

/* =================================================================== panel */

/* The drawer floats *over* the timeline rather than taking a grid column.
   That is what the reference does, and it means opening or closing it never
   changes the timeline's width — so the scroll position cannot shift. */
.panel {
  position: fixed;
  top: 0; right: 0; bottom: 0;
  width: min(442px, 92vw);
  z-index: 60;
  background: var(--surface);
  border-left: 1px solid var(--line-strong);
  box-shadow: var(--shadow-panel);
  display: flex; flex-direction: column;
  transform: translateX(100%);
  transition: transform 0.24s cubic-bezier(0.22, 0.61, 0.36, 1);
  will-change: transform;
  pointer-events: none;
}
.panel.is-open { transform: translateX(0); pointer-events: auto; }

@media (prefers-reduced-motion: reduce) {
  .panel { transition: none; }
}

.panel-top {
  display: flex; align-items: center; gap: 8px;
  padding: 13px 16px;
  border-bottom: 1px solid var(--line);
  font-size: 11px; color: var(--text-faint);
  flex: none;
}
.panel-id { color: var(--text-dim); font-variant-numeric: tabular-nums; }
.panel-meta.is-draft { color: var(--accent); }
.panel-sep { opacity: 0.4; }
.panel-top-actions { margin-left: auto; display: flex; gap: 4px; }

.icon-btn {
  border: 0; background: transparent;
  color: var(--text-dim);
  width: 26px; height: 26px;
  border-radius: 6px;
  cursor: pointer; font-size: 13px;
  display: grid; place-items: center;
}
.icon-btn:hover { background: var(--hover-strong); color: var(--text); }
.icon-btn.subtle { opacity: 0; }
.check-item:hover .icon-btn.subtle { opacity: 1; }

/* The shortcut delete sits next to Close, so it stays quiet until hovered —
   it should not compete with the title for attention every time the drawer
   opens, but it must read as destructive the moment you reach for it. */
.icon-btn.is-danger:hover { background: rgba(232, 79, 79, 0.15); color: #FF9C9C; }
.bin-icon { width: 14px; height: 14px; fill: currentColor; display: block; }

.panel-scroll { flex: 1; overflow-y: auto; padding: 18px 20px 40px; }

.panel-title-row { display: flex; align-items: center; gap: 11px; position: relative; }

.color-dot {
  width: 15px; height: 15px; flex: none;
  border-radius: 50%;
  border: 0;
  cursor: pointer;
  box-shadow: 0 0 0 3px var(--dot-halo);
}
.color-dot:hover { box-shadow: 0 0 0 4px var(--dot-halo-hover); }

.panel-title {
  flex: 1; min-width: 0;
  border: 0; background: transparent;
  color: var(--text);
  font: 600 20px/1.3 var(--font);
  padding: 2px 0;
}
.panel-title:focus { outline: none; border-bottom: 1px solid var(--line-strong); }
.panel-title::placeholder { color: var(--text-faint); }

.panel-range { margin: 8px 0 20px 26px; font-size: 12px; color: var(--text-faint); }

.prop { display: flex; align-items: flex-start; gap: 12px; padding: 7px 0; }
.prop-label { width: 82px; flex: none; font-size: 12px; color: var(--text-faint); padding-top: 7px; }
.prop-value { flex: 1; min-width: 0; display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }

.seg { display: inline-flex; gap: 0; border: 1px solid var(--line); border-radius: 7px; overflow: hidden; }
.seg-btn {
  border: 0; background: transparent;
  padding: 6px 12px;
  color: var(--text-dim); font: inherit; font-size: 12px;
  cursor: pointer;
}
.seg-btn:hover { background: var(--hover); }
.seg-btn.is-on { background: var(--active); color: var(--text); }

.prop-value input[type="date"],
.prop-value input[type="time"],
.prop-value input[type="url"],
.tag-input, .hex-input, .check-new, .check-item input, .panel-notes {
  background: var(--fill-subtle);
  border: 1px solid var(--line);
  border-radius: 7px;
  color: var(--text);
  font: inherit; font-size: 13px;
  padding: 7px 9px;
  color-scheme: dark;
}
.prop-value input:focus, .tag-input:focus, .hex-input:focus,
.check-new:focus, .check-item input:focus, .panel-notes:focus {
  outline: none; border-color: var(--line-strong); background: var(--hover-strong);
}

.dates { gap: 7px; }
.arrow { color: var(--text-faint); font-size: 12px; }

.ghost-btn {
  border: 1px solid var(--line);
  background: transparent; color: var(--text-dim);
  border-radius: 6px; padding: 6px 9px;
  font: inherit; font-size: 11px; cursor: pointer;
}
.ghost-btn:hover { color: var(--text); border-color: var(--line-strong); }
/* Anchors styled as buttons (the usage page's "Back to calendar") need the
   underline removed and a baseline matching real buttons. */
a.ghost-btn { text-decoration: none; display: inline-block; line-height: 1.3; }
/* The `display` above beats the browser's own `[hidden] { display: none }`, so
   any such anchor given the `hidden` attribute would still show without this. */
a.ghost-btn[hidden] { display: none; }

.meeting { gap: 7px; }
.meeting input { flex: 1; min-width: 160px; }

.join-btn {
  padding: 7px 13px; border-radius: 7px;
  font-size: 12px; font-weight: 600; text-decoration: none;
  color: var(--on-accent); background: var(--accent);
  white-space: nowrap;
}
.join-btn.is-zoom { background: #2D8CFF; }
.join-btn.is-meet { background: #00897B; }
.join-btn.is-teams { background: #5059C9; }

.tags { gap: 6px; }
.tag {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 6px 4px 9px;
  border-radius: 20px;
  background: var(--active);
  color: var(--text);
  font-size: 11.5px;
}
.tag button { border: 0; background: transparent; color: inherit; cursor: pointer; opacity: 0.6; padding: 0 2px; font-size: 10px; }
.tag button:hover { opacity: 1; }
.tag-input { width: 100px; padding: 5px 8px; font-size: 12px; }

.section { margin-top: 26px; }
.section-head {
  display: flex; align-items: center; gap: 8px;
  font-size: 10px; letter-spacing: 0.13em; text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: 10px;
}
.check-count { margin-left: auto; letter-spacing: 0; font-size: 11px; text-transform: none; }

.panel-notes { width: 100%; resize: vertical; line-height: 1.55; min-height: 84px; }

.check-bar { height: 3px; border-radius: 3px; background: var(--track); margin-bottom: 10px; overflow: hidden; }
.check-bar i { display: block; height: 100%; background: var(--accent); transition: width 0.18s ease; }

.check-list { list-style: none; margin: 0 0 8px; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.check-item { display: flex; align-items: center; gap: 8px; }
.check-item input { flex: 1; min-width: 0; border-color: transparent; background: transparent; padding: 5px 7px; }
.check-item:hover input { background: var(--fill-subtle); }
.check-item.is-done input { color: var(--text-faint); text-decoration: line-through; }

.check-box {
  width: 17px; height: 17px; flex: none;
  border-radius: 5px;
  border: 1px solid var(--line-strong);
  background: transparent;
  color: var(--btn-fg); line-height: 1;
  cursor: pointer;
  /* A button carries the browser's own padding unless told otherwise, which
     left the mark centred inside a content box three pixels wide. */
  padding: 0;
  display: grid; place-items: center;
}
/* Bigger than the character it replaces, and centred by its own geometry —
   see CHECK_ICON. Stroked in currentColor so it follows the ticked state. */
.check-tick {
  width: 13px; height: 13px;
  display: block;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.check-item.is-done .check-box { background: var(--btn-bg); border-color: var(--btn-bg); color: var(--btn-fg); }

/* Quiet until you reach for the row, like the ✕ beside it — a column of grips
   down the drawer would compete with the items themselves for attention. */
.check-grip {
  flex: none;
  width: 12px; padding: 0;
  border: 0; background: transparent;
  color: var(--text-faint);
  font-size: 12px; line-height: 1;
  cursor: grab;
  opacity: 0;
  transition: opacity 120ms ease;
  /* On the grip alone, so a finger dragging here reorders while a finger
     anywhere else in the drawer still scrolls it. */
  touch-action: none;
}
.check-item:hover .check-grip { opacity: 1; }
.check-grip:active { cursor: grabbing; }
.check-grip:focus-visible { opacity: 1; outline: 2px solid var(--accent); outline-offset: 2px; }
/* Nothing hovers on a touch screen, so there it is always faintly there. */
@media (hover: none) {
  .check-grip { opacity: 0.55; }
}
.check-item.is-gripped {
  opacity: 0.85;
  background: var(--fill-subtle);
  border-radius: 7px;
}
.check-item.is-gripped .check-grip { opacity: 1; }
/* The drag is the gesture; selecting text across the rows it crosses is not. */
.check-item.is-gripped, .check-item.is-gripped input { user-select: none; }

.check-new { width: 100%; }

.action-list { display: flex; flex-direction: column; gap: 2px; }
.action {
  display: flex; align-items: center; gap: 9px;
  border: 0; background: transparent;
  color: var(--text-dim);
  padding: 8px 9px; border-radius: 7px;
  font: inherit; font-size: 13px; text-align: left;
  cursor: pointer;
}
.action:hover { background: var(--hover); color: var(--text); }
.action.is-danger:hover { background: rgba(232, 79, 79, 0.15); color: #FF9C9C; }

/* ---- colour picker ---- */

.picker {
  position: absolute; top: 30px; left: 0; z-index: 60;
  background: var(--surface-2);
  border: 1px solid var(--line-strong);
  border-radius: 11px;
  padding: 10px;
  box-shadow: var(--shadow-lg);
}

.picker-tabs { display: flex; gap: 2px; background: var(--tab-track); border-radius: 7px; padding: 2px; margin-bottom: 10px; }
.picker-tabs button {
  flex: 1; border: 0; background: transparent;
  color: var(--text-dim); font: inherit; font-size: 12px;
  padding: 5px 14px; border-radius: 6px; cursor: pointer;
}
.picker-tabs button.is-on { background: var(--active); color: var(--text); }

.picker-grid { display: grid; grid-template-columns: repeat(5, 26px); gap: 7px; }
.swatch {
  width: 26px; height: 26px;
  border: 0; border-radius: 50%;
  cursor: pointer;
  position: relative;
}
.swatch:hover { transform: scale(1.12); }
.swatch.is-on::after {
  content: '✓';
  position: absolute; inset: 0;
  display: grid; place-items: center;
  color: #FFFFFF; font-size: 12px;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}

.picker-custom { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; width: 208px; }
.picker-custom input[type="color"] { width: 42px; height: 34px; padding: 2px; border: 1px solid var(--line); border-radius: 7px; background: transparent; cursor: pointer; }
.hex-input { width: 96px; text-transform: uppercase; }
.picker-note { width: 100%; margin: 0; font-size: 11px; color: var(--text-faint); line-height: 1.4; }
/* In the tag panel it sits straight under the swatches now, rather than inside
   the custom-colour row that used to carry the spacing for it. */
.icon-picker > .picker-note { margin-top: 9px; }

/* ============================================================ calendar switch */

.cal-switch {
  display: flex; align-items: center; gap: 8px;
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--fill-subtle);
  color: var(--text);
  font: inherit;
  cursor: pointer;
  text-align: left;
}
.cal-switch:hover { background: var(--hover); border-color: var(--line-strong); }
.cal-switch-label { flex: 1; min-width: 0; }
.cal-switch-name {
  display: block; font-size: 13px; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cal-switch-owner { display: block; font-size: 11px; color: var(--text-faint); }
.cal-switch-chevron { color: var(--text-faint); font-size: 14px; line-height: 1; }

/* ================================================================== admin */

.admin { position: fixed; inset: 0; z-index: 80; display: none; }
.admin.is-open { display: block; }

.admin-scrim { position: absolute; inset: 0; background: rgba(0, 0, 0, 0.5); }

.admin-sheet {
  position: relative;
  width: min(760px, calc(100vw - 48px));
  max-height: calc(100vh - 64px);
  margin: 32px auto;
  display: flex; flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: 14px;
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}

.admin-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 16px;
  padding: 20px 22px 16px;
  border-bottom: 1px solid var(--line);
}
.admin-head h2 { margin: 0; font-size: 17px; font-weight: 600; }
.admin-sub { margin: 4px 0 0; font-size: 12px; color: var(--text-faint); }

.admin-error {
  margin: 14px 22px 0;
  padding: 9px 12px;
  border-radius: 8px;
  background: rgba(232, 79, 79, 0.14);
  color: #FF9C9C;
  font-size: 12.5px;
}

.admin-body { padding: 18px 22px 24px; overflow-y: auto; }

.admin-section + .admin-section { margin-top: 26px; }
.admin-section-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 10px;
}
.admin-section-head h3 {
  margin: 0; font-size: 10px; letter-spacing: 0.13em;
  text-transform: uppercase; color: var(--text-faint); font-weight: 600;
}

.client-form { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
/* `display: flex` beats the `hidden` attribute's UA `display: none`. */
.client-form[hidden] { display: none; }
.client-form input {
  flex: 1; min-width: 150px;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--fill-subtle);
  color: var(--text);
  font: inherit; font-size: 13px;
}
.client-form input:focus { outline: none; border-color: var(--line-strong); background: var(--hover-strong); }

.cal-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }

.cal-row {
  display: flex; align-items: center; gap: 14px;
  padding: 11px 13px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--fill-subtle);
}
.cal-row.is-current { border-color: var(--accent); background: var(--accent-soft); }
.cal-row.is-archived { opacity: 0.55; }
/* A client with no calendar. Dashed rather than dimmed: this is not a quiet
   state to be ignored like an archived calendar, it is an account left
   half-removed that wants dealing with. */
.cal-row.is-orphan { border-style: dashed; }
.cal-main { flex: 1; min-width: 0; }
.cal-name { font-size: 13.5px; font-weight: 600; display: flex; align-items: center; gap: 7px; }
.cal-meta { margin-top: 3px; font-size: 11.5px; color: var(--text-faint); }
.cal-actions { display: flex; gap: 6px; flex: none; }
.cal-empty { padding: 14px; font-size: 12.5px; color: var(--text-faint); text-align: center;
             border: 1px dashed var(--line); border-radius: 10px; }

.cal-badge {
  font-size: 9.5px; letter-spacing: 0.08em; text-transform: uppercase;
  padding: 2px 6px; border-radius: 20px;
  background: var(--accent); color: var(--on-accent); font-weight: 600;
}
.cal-badge.is-muted { background: var(--active); color: var(--text-dim); }

.ghost-btn.is-danger:hover { color: #FF9C9C; border-color: rgba(232, 79, 79, 0.5); }

.admin-toggle { margin-top: 18px; }

.admin-note {
  margin: 22px 0 0;
  padding: 12px 14px;
  border-radius: 10px;
  background: var(--fill-subtle);
  border: 1px solid var(--line);
  font-size: 12px; line-height: 1.55; color: var(--text-dim);
}
.admin-note strong { color: var(--text); }

/* ================================================================ auth page */

.auth-body {
  display: grid; place-items: center;
  min-height: 100vh; padding: 24px;
  background: var(--bg);
  overflow: auto;
}

.auth-card {
  width: min(400px, 100%);
  padding: 32px 32px 28px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 16px;
  box-shadow: var(--shadow-lg);
}

.auth-brand { display: flex; align-items: center; gap: 9px; margin-bottom: 26px; }
.auth-loading { color: var(--text-faint); font-size: 13px; margin: 0; }

.auth-title { margin: 0; font-size: 21px; font-weight: 600; letter-spacing: -0.01em; }
.auth-sub { margin: 6px 0 22px; font-size: 13px; color: var(--text-dim); line-height: 1.5; }

.auth-form { display: flex; flex-direction: column; gap: 14px; }
.auth-field { display: flex; flex-direction: column; gap: 6px; }
.auth-field > span { font-size: 12px; color: var(--text-dim); }
.auth-field small { font-size: 11px; color: var(--text-faint); }
.auth-field input {
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--fill-subtle);
  color: var(--text);
  font: inherit; font-size: 14px;
}
.auth-field input:focus { outline: none; border-color: var(--line-strong); background: var(--hover-strong); }
.auth-field input:disabled { color: var(--text-faint); }

.auth-error {
  margin: 0;
  padding: 9px 11px;
  border-radius: 8px;
  background: rgba(232, 79, 79, 0.14);
  color: #FF9C9C;
  font-size: 12.5px;
}

.auth-submit { width: 100%; padding: 11px; font-size: 14px; text-align: center; text-decoration: none; display: block; }
.auth-foot { margin: 20px 0 0; font-size: 11.5px; color: var(--text-faint); line-height: 1.5; }
/* Without this the reset and sign-in links render as the browser's default
   blue underline, which is unreadable against the dark card. */
.auth-foot a { color: var(--text-dim); text-decoration: none; border-bottom: 1px solid var(--line-strong); }
.auth-foot a:hover { color: var(--text); border-bottom-color: var(--text-dim); }

/* ---- account row + invites ---- */

.side-account {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  margin-top: 10px; padding-top: 10px;
  border-top: 1px solid var(--line);
}
.side-account-name {
  font-size: 11.5px; color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.cal-switch.is-static { pointer-events: none; }
.cal-switch.is-static .cal-switch-chevron { display: none; }

.cal-pending { color: var(--accent); }

.admin-invite {
  margin: 14px 22px 0;
  padding: 14px;
  border: 1px solid var(--accent);
  background: var(--accent-soft);
  border-radius: 11px;
}
.admin-invite-head {
  display: flex; align-items: center; justify-content: space-between;
  font-size: 12.5px; color: var(--text); margin-bottom: 9px;
}
.admin-invite-url {
  width: 100%;
  padding: 9px 10px;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  background: var(--surface);
  color: var(--text);
  font: 12px ui-monospace, SFMono-Regular, Menlo, monospace;
}
.admin-invite-actions { display: flex; align-items: center; gap: 12px; margin-top: 10px; }
.admin-invite-note { font-size: 11.5px; color: var(--text-dim); line-height: 1.45; }

/* ================================================================ recurrence */

.repeat { gap: 10px; }
.repeat select {
  padding: 7px 9px;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--fill-subtle);
  color: var(--text);
  font: inherit; font-size: 13px;
  cursor: pointer;
}
.repeat select:focus { outline: none; border-color: var(--line-strong); }
.repeat-until { display: flex; align-items: center; gap: 7px; font-size: 12px; color: var(--text-faint); }

.repeat-summary {
  margin: 2px 0 0 94px;
  padding-right: 8px;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--accent);
  /* Wrap rather than clip — the sentence grows with an end date and skips. */
  white-space: normal;
  overflow-wrap: anywhere;
}

/* Projected occurrences read slightly lighter than a one-off block. */
.chip.is-repeating .chip-icon { opacity: 0.9; }

/* ---- week-grouped header (quarter) ---- */

.tl-day.is-weeklabel { justify-content: center; }
.tl-day.is-weeklabel .tl-day-label { font-size: 11px; }

/* Today is a column, not a label, at this density — mark it directly. */
.tl-today-tick {
  position: absolute; top: 8px; bottom: 6px;
  background: var(--coral);
  border-radius: 3px;
  pointer-events: none;
}


/* ---- colour rules ---- */

.rule-swatch {
  width: 12px; height: 12px; flex: none;
  border-radius: 4px;
  background: var(--swatch);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2);
}

.rule-prompt {
  display: flex; align-items: flex-start; gap: 10px;
  margin-top: 14px; padding: 13px 14px;
  border: 1px solid var(--accent);
  background: var(--accent-soft);
  border-radius: 11px;
}
.rule-prompt .rule-swatch { margin-top: 2px; }
.rule-body { flex: 1; min-width: 0; }
.rule-body p { margin: 0 0 10px; font-size: 12.5px; line-height: 1.5; color: var(--text); }
.rule-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.rule-actions .primary-btn { padding: 6px 12px; font-size: 12px; }

.rule-note {
  display: flex; align-items: center; gap: 8px;
  margin-top: 12px; padding: 8px 11px;
  border: 1px solid var(--line);
  border-radius: 9px;
  font-size: 11.5px; color: var(--text-dim);
}
.rule-note strong { color: var(--text); }
.rule-note .ghost-btn { margin-left: auto; padding: 4px 9px; font-size: 11px; }

.chip-badges { gap: 7px; }

/* ============================================================ google sync */

.gcal-hint { margin: 6px 0 0; font-size: 11px; color: var(--text-faint); line-height: 1.5; }
.gcal-connect { width: 100%; text-align: center; }
.gcal-account {
  display: flex; align-items: center; gap: 7px;
  font-size: 11.5px; color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gcal-dot { width: 6px; height: 6px; border-radius: 50%; background: #4ECB71; flex: none; }
.gcal-actions { display: flex; gap: 6px; margin-top: 8px; }
.gcal-actions .ghost-btn { flex: 1; text-align: center; }

/* A cancelled meeting keeps its place and its contents, but stops competing
   for attention — grey and faded rather than deleted. */
.chip.is-cancelled {
  background: var(--cancelled-fill) !important;
  color: var(--cancelled-ink) !important;
  opacity: 0.8;
}
.chip.is-cancelled .chip-title { text-decoration: line-through; text-decoration-thickness: 1px; }
.chip-cancelled {
  font-size: calc(9.5px * var(--tl-scale));
  letter-spacing: 0.04em;
  text-transform: uppercase;
  opacity: 0.9;
}

.cancelled-note, .synced-note {
  margin: 12px 0 0;
  padding: 11px 13px;
  border: 1px solid var(--line);
  border-radius: 10px;
  font-size: 12px; line-height: 1.55; color: var(--text-dim);
}
.cancelled-note p { margin: 0 0 9px; }
.synced-note { color: var(--text-faint); font-size: 11.5px; }

/* =================================================================== dialog */

.dialog { position: fixed; inset: 0; z-index: 120; display: none; }
.dialog.is-open { display: block; }

.dialog-scrim {
  position: absolute; inset: 0;
  background: rgba(0, 0, 0, 0.55);
  animation: dialog-fade 0.15s ease;
}

.dialog-sheet {
  position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  width: min(400px, calc(100vw - 40px));
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: 16px;
  box-shadow: var(--shadow-lg);
  overflow: hidden;
  animation: dialog-rise 0.18s cubic-bezier(0.22, 0.61, 0.36, 1);
}

.dialog-body { padding: 24px 24px 20px; }

.dialog-icon {
  width: 38px; height: 38px;
  display: grid; place-items: center;
  margin-bottom: 14px;
  border-radius: 11px;
  background: var(--active);
  font-size: 17px;
}
.dialog-sheet.is-danger .dialog-icon {
  background: rgba(232, 79, 79, 0.16);
}

.dialog-title {
  margin: 0; font-size: 16.5px; font-weight: 600;
  letter-spacing: -0.01em; line-height: 1.35;
}
.dialog-text {
  margin: 8px 0 0;
  font-size: 13px; line-height: 1.6;
  color: var(--text-dim);
}

.dialog-actions {
  display: flex; justify-content: flex-end; gap: 8px;
  padding: 14px 20px;
  border-top: 1px solid var(--line);
  background: var(--fill-subtle);
}
.dialog-actions .ghost-btn,
.dialog-actions .primary-btn { padding: 8px 15px; font-size: 13px; }

.primary-btn.is-danger { background: #D8453F; color: #FFFFFF; }
.primary-btn.is-danger:hover { background: #E4534D; }

.dialog-actions button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

@keyframes dialog-fade { from { opacity: 0; } }
@keyframes dialog-rise {
  from { opacity: 0; transform: translate(-50%, calc(-50% + 8px)) scale(0.97); }
}

@media (prefers-reduced-motion: reduce) {
  .dialog-scrim, .dialog-sheet { animation: none; }
}

/* ================================================================ tag icons */

.side-head { display: flex; align-items: center; justify-content: space-between; }
.tag-edit {
  border: 0; background: transparent; cursor: pointer;
  color: var(--text-faint); font-size: 12px; line-height: 1;
  padding: 2px 4px; border-radius: 5px;
}
.tag-edit:hover { color: var(--text); background: var(--hover); }
.tag-edit.is-on { color: var(--on-accent); background: var(--accent); }

/* Laid out like the colour picker: a grid of dots rather than a list of words. */
.tag-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; padding: 2px 2px 0; }

.tag-dot {
  position: relative;
  aspect-ratio: 1;
  display: grid; place-items: center;
  border: 0; border-radius: 50%;
  background: var(--active);
  color: var(--text-dim);
  cursor: pointer;
  transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.tag-dot:hover { transform: scale(1.08); }
.tag-dot.is-on { box-shadow: 0 0 0 2px var(--sidebar), 0 0 0 4px var(--text); }
.tag-dot svg { width: 58%; height: 58%; fill: currentColor; }

.tag-dot-count {
  position: absolute; right: -2px; bottom: -2px;
  min-width: 14px; height: 14px; padding: 0 3px;
  border-radius: 8px;
  background: var(--sidebar);
  border: 1px solid var(--line-strong);
  color: var(--text-dim);
  font-size: 9px; line-height: 12px; font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.tag-grid.is-editing .tag-dot { cursor: crosshair; }
.tag-grid.is-editing .tag-dot:hover { box-shadow: 0 0 0 2px var(--accent); }
.tag-editing-hint { margin: 9px 2px 0; font-size: 11px; color: var(--accent); }

/* ---- icon picker ---- */

.icon-picker {
  margin-top: 10px; padding: 10px;
  background: var(--surface-2);
  border: 1px solid var(--line-strong);
  border-radius: 11px;
  box-shadow: var(--shadow-md);
}
.icon-picker-head {
  display: flex; align-items: center; justify-content: space-between;
  font-size: 11.5px; color: var(--text-dim); margin-bottom: 9px;
}
.icon-picker-head strong { color: var(--text); }
/* The panel carries two things now, so each says which it is. */
.picker-section {
  display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
  margin: 13px 0 7px;
  font-size: 10px; letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--text-faint); font-weight: 600;
}
.picker-section:first-of-type { margin-top: 2px; }
.picker-auto { text-transform: none; letter-spacing: 0; font-weight: 400; }
.picker-reset {
  border: 0; background: transparent; padding: 0; cursor: pointer;
  font: inherit; text-transform: none; letter-spacing: 0; font-weight: 400;
  color: var(--accent);
}
.picker-reset:hover { text-decoration: underline; }
.icon-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 7px; }
.icon-choice {
  aspect-ratio: 1;
  display: grid; place-items: center;
  border: 0; border-radius: 50%;
  background: var(--active); color: var(--text-dim);
  cursor: pointer;
}
.icon-choice:hover { transform: scale(1.1); }
.icon-choice.is-on { box-shadow: 0 0 0 2px var(--accent); }
.icon-choice svg { width: 56%; height: 56%; fill: currentColor; }

/* ---- on the block ---- */

.chip-tagicon {
  width: calc(11px * var(--tl-scale));
  height: calc(11px * var(--tl-scale));
  fill: currentColor;
  display: block;
}

/* Tag icons live in the top-right corner, clear of the link/checklist row. */
.chip-tags {
  position: absolute;
  top: calc(4px * var(--tl-scale));
  right: calc(7px * var(--tl-scale));
  display: flex; gap: calc(3px * var(--tl-scale));
  opacity: 0.75;
  pointer-events: none;
}
/* Reserve the corner so a long title never runs underneath the icons. */
.chip.has-tagicons .chip-title { padding-right: calc(18px * var(--tl-scale)); }
.chip.has-tagicons.is-span .chip-title { padding-right: calc(24px * var(--tl-scale)); }

.chip-link {
  width: calc(9px * var(--tl-scale));
  height: calc(9px * var(--tl-scale));
  fill: currentColor;
  display: block;
}

.tl-canvas.is-micro .chip-tags { display: none; }

/* Month columns are 58px, so a title and a corner icon fight for the same few
   pixels. Dropping the icon to the bottom-right frees the whole top line. */
.tl-canvas[data-zoom="month"] .chip-tags {
  top: auto;
  bottom: calc(3px * var(--tl-scale));
}
.tl-canvas[data-zoom="month"] .chip.has-tagicons .chip-title,
.tl-canvas[data-zoom="month"] .chip.has-tagicons.is-span .chip-title {
  padding-right: 0;
}

/* Quarter chips are colour squares with no title, so the icon is the only thing
   in them — centred, and shown despite is-micro. Placed after that rule because
   the two selectors have equal specificity. */
.tl-canvas[data-zoom="quarter"] .chip-tags {
  display: flex;
  inset: 0;
  align-items: center;
  justify-content: center;
  opacity: 0.85;
}
/* A spanning quarter block shows a title, so it is typeset like the month view
   rather than inheriting the compressed `is-micro` sizing, which exists for the
   single-day colour squares that have no text at all. */
.tl-canvas[data-zoom="quarter"] .chip.is-span {
  padding: calc(5px * var(--tl-scale)) calc(10px * var(--tl-scale));
  border-radius: calc(5px * var(--tl-scale));
}
.tl-canvas[data-zoom="quarter"] .chip.is-span .chip-title {
  font-size: calc(13px * var(--tl-scale));
  text-overflow: clip;
}

/* …but a block spanning several days does show its title, so a centred icon
   floats in the middle of empty space. Those go back to the top-right corner,
   the same as week view. */
.tl-canvas[data-zoom="quarter"] .chip.is-span .chip-tags {
  inset: auto;
  top: calc(4px * var(--tl-scale));
  right: calc(7px * var(--tl-scale));
  align-items: flex-start;
  justify-content: flex-end;
}
.tl-canvas[data-zoom="quarter"] .chip.is-span.has-tagicons .chip-title {
  padding-right: calc(20px * var(--tl-scale));
}

/* One icon only: a second would not fit at 23px. */
.tl-canvas[data-zoom="quarter"] .chip-tags > span:nth-child(n + 2) { display: none; }
.tl-canvas[data-zoom="quarter"] .chip-tagicon {
  width: calc(12px * var(--tl-scale));
  height: calc(12px * var(--tl-scale));
}

/* A default tag with nothing in it yet: present, but clearly unused. */
.tag-dot.is-empty { opacity: 0.38; }
.tag-dot.is-empty:hover { opacity: 0.7; }
.tag-dot.is-empty .tag-dot-count { display: none; }

/* Where the block will land. Shown while dragging, since the block itself is
   following the pointer rather than snapping. */
.tl-drop {
  position: absolute;
  z-index: 3;
  border: 2px dashed var(--line-strong);
  border-radius: calc(5px * var(--tl-scale));
  background: rgba(127, 127, 127, 0.08);
  pointer-events: none;
  display: flex; align-items: center; justify-content: center;
  font-size: calc(11px * var(--tl-scale));
  font-weight: 600;
  color: var(--text-faint);
  letter-spacing: 0.02em;
}
.tl-drop.is-copy {
  border-color: var(--accent);
  border-style: solid;
  background: var(--accent-soft);
  color: var(--accent);
}

body.is-dragging-block .chip:not(.is-dragging) { opacity: 0.72; }

/* The stand-in left behind during a ⌘-copy: looks like the block still sitting
   in its slot, but inert. */
.chip.is-residual {
  pointer-events: none;
  z-index: 1;
  opacity: 0.72;
}

/* ============================================================== responsive */

/* Removes the 300ms tap delay on the two things you actually tap on a phone.
   Pan and pinch are untouched — the timeline still scrolls under a finger. */
.chip, .side-item, .tag-dot, .search-hit, .zoom-btn, .float-btn, .primary-btn,
.ghost-btn, .icon-btn { touch-action: manipulation; }

/* The hamburger and its scrim only exist below the breakpoint. */
.nav-toggle, .nav-scrim { display: none; }

@media (max-width: 860px) {
  /* One column: the sidebar leaves the grid and slides over the timeline. */
  #app { grid-template-columns: minmax(0, 1fr); }

  /* 74px of a 320px screen is 23% spent on a one-letter avatar. The avatar
     itself is ~28px, so 44px still centres it and hands the rest to the grid. */
  .timeline { --rail-w: 44px; }

  .sidebar {
    position: fixed;
    top: 0; bottom: 0; left: 0;
    width: min(300px, 86vw);
    z-index: 60;
    transform: translateX(-100%);
    transition: transform 0.22s ease;
    box-shadow: var(--shadow-lg);
    padding-top: max(16px, env(safe-area-inset-top));
    padding-bottom: max(16px, env(safe-area-inset-bottom));
  }
  #app.is-nav-open .sidebar { transform: none; }

  /* Display is driven by .is-nav-open, never by the `hidden` attribute alone:
     an author `display: block` here beats the UA's `[hidden] { display: none }`
     no matter how specific the attribute selector looks, which left an
     invisible full-screen scrim swallowing every touch on the timeline. The
     attribute is still toggled, for the accessibility tree. */
  .nav-scrim {
    position: fixed; inset: 0;
    z-index: 55;
    background: rgba(0, 0, 0, 0.45);
  }
  #app.is-nav-open .nav-scrim { display: block; }

  .nav-toggle {
    display: flex; flex-direction: column; justify-content: center;
    gap: 4px;
    /* 40px keeps the target at a comfortable size for a thumb even though the
       three bars themselves are only 16px wide. */
    width: 40px; height: 40px;
    margin-left: -8px;
    padding: 0 10px;
    border: 0; border-radius: 9px;
    background: transparent;
    cursor: pointer;
    flex: none;
  }
  .nav-toggle span {
    display: block; height: 2px; width: 18px;
    border-radius: 2px;
    background: var(--text);
  }
  .nav-toggle:active { background: var(--active); }

  .topbar {
    padding: 10px 12px;
    padding-top: max(10px, env(safe-area-inset-top));
    gap: 8px;
  }
  .topbar-left { align-items: center; gap: 8px; min-width: 0; }
  .topbar h1 {
    font-size: 15px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  /* The block count is the first thing to go — the period label matters more
     and the two together do not fit beside the button at 375px. */
  .count-pill { display: none; }
  .topbar-right { gap: 10px; flex: none; }
  .switch span { display: none; }
  .switch input { width: 18px; height: 18px; }
  .primary-btn { padding: 9px 12px; font-size: 12px; }

  /* Full-bleed drawer: at this width a 442px sheet would leave a useless
     sliver of timeline behind it. */
  .panel {
    width: 100%;
    box-shadow: none;
    padding-bottom: env(safe-area-inset-bottom);
  }

  .floating {
    right: 10px;
    bottom: calc(10px + env(safe-area-inset-bottom));
    gap: 6px;
  }
  .zoom-btn { padding: 10px 12px; }
  .float-btn { padding: 10px 12px; }
  /* No physical keyboard to hint at — in the footer, on the floating
     controls, or beside the view names. */
  .float-btn kbd, .kbd-row, .side-item kbd { display: none; }


  .mark-bar {
    left: 10px; right: 10px;
    bottom: calc(10px + env(safe-area-inset-bottom));
    /* The base rule centres the bar by pulling it back half its own width. Here
       it is pinned to both edges instead, so that pull has nothing to correct
       and simply drags it off the left of the screen. */
    transform: none;
    justify-content: center;
    flex-wrap: wrap;
  }
  /* A phone has the width for one column, not two: the count and its buttons
     take the top line and the palette sits under them, both centred. Stacked
     the other way round the palette pushes the buttons off the bottom of the
     screen, and Delete is the one control that must always be reachable. */
  .mark-side {
    order: 1;
    width: 100%;
    align-items: center;
  }
  .mark-actions { width: 100%; }
  .mark-picker {
    order: 2;
    border: 0;
    padding: 4px 0 0;
  }

  .search-results { max-height: 46vh; }

  /* The workspace sheet on a phone.
     Every row is a name plus a set of buttons, and .cal-actions is flex: none —
     so at this width the buttons keep their size and the name column is
     squeezed to a few characters, while the meta line (owner, email, block
     count, edited when) wraps into a ragged stack that reads as though it is
     interleaved with the buttons. None of that meta is worth the space on a
     phone: the name says which calendar it is and the buttons say what you can
     do with it. Drop the meta, give the buttons their own line. */
  .admin-sheet {
    width: calc(100vw - 20px);
    max-height: calc(100vh - 24px);
    margin: 12px auto;
  }
  .admin-head { padding: 16px 16px 13px; }
  .admin-body { padding: 15px 16px calc(20px + env(safe-area-inset-bottom)); }

  .cal-row { flex-direction: column; align-items: stretch; gap: 9px; }
  .cal-meta { display: none; }
  .cal-name { font-size: 15px; }
  /* A grid rather than a wrapping flex row: flex grows the items on each line
     independently, so a row that wraps four buttons into 3 + 1 stretches that
     last one to the full width and it reads as a different, more important
     control. Equal columns keep every button the same size however many there
     are. */
  .cal-actions {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(92px, 1fr));
    gap: 8px;
  }
  .cal-actions .ghost-btn {
    padding: 9px 6px;
    font-size: 12px;
    white-space: nowrap;
    text-align: center;
  }
}

/* Small phones (SE-class, 320px). The three view buttons plus Today and Jump
   are 318px wide at the padding above, which runs off the left edge — the
   floating row is right-anchored, so it overflows leftward rather than
   scrolling. Tightening the padding is enough to bring it back inside. */
@media (max-width: 380px) {
  /* Four views plus Today and Jump is wider than an SE screen on one line, so
     the row wraps: the view group keeps its own line and the two buttons sit
     under it, both right-aligned. */
  .floating {
    right: 8px; gap: 4px;
    flex-wrap: wrap;
    justify-content: flex-end;
    max-width: calc(100% - 16px);
  }
  .zoom-btn { padding: 9px 8px; font-size: 11px; }
  .float-btn { padding: 9px 10px; font-size: 11px; }
}

@media (prefers-reduced-motion: reduce) {
  .sidebar { transition: none; }
}

/* ================================================================== agenda */

/* Its own scroll container, sitting where the grid would be. `flex: 1` because
   .timeline is a flex row and the rail is hidden in this view. */
.tl-agenda {
  flex: 1;
  min-width: 0;
  overflow-y: auto;
  overflow-x: hidden;
  /* No top padding: the headings pin at `top: 0` of this box, but their
     offsetTop is measured including any padding above them, so the two
     disagree by exactly that amount and the pinned heading clips the day's
     first row. The headings carry their own 14px, so nothing is lost. */
  padding: 0 0 96px;
  /* Makes the day headings' offsetTop relative to this box, which is what
     agendaScrollToDate measures against. */
  position: relative;
}

/* An explicit `display` in a rule above beats the browser's own
   `[hidden] { display: none }` — author styles win over user-agent styles
   whatever the specificity. Both of these set display, so both need saying
   again here, or the grid stays on screen underneath the agenda. */
.tl-agenda[hidden], .tl-rail[hidden], .tl-scroll[hidden] { display: none; }

/* The readable measure. A list stretched to 1600px puts the tag icon a screen
   away from the title it belongs to. */
.agenda-group { max-width: 720px; margin: 0 auto; padding: 0 16px; }

.agenda-day {
  position: sticky; top: 0;
  z-index: 2;
  display: flex; align-items: center; gap: 8px;
  padding: 14px 4px 7px;
  background: var(--bg);
  font-size: 12px;
  color: var(--text-dim);
}
.agenda-date { font-weight: 500; }
.agenda-count { margin-left: auto; font-size: 11px; color: var(--text-faint); }

.agenda-today {
  background: var(--accent);
  color: var(--on-accent);
  font-size: 11px;
  padding: 2px 8px;
  border-radius: 20px;
}
.agenda-day.is-today .agenda-date { color: var(--text); }

.agenda-row {
  display: flex; align-items: stretch; gap: 10px;
  width: 100%;
  padding: 7px 6px;
  /* Holds every row to the height of a two-line one, so a list mixing titles
     with and without a detail line stays even. Rows that have only a title
     centre it inside this height (.is-single below) rather than pinning it to
     the top edge. */
  min-height: 46px;
  border: 0; border-radius: 8px;
  background: transparent;
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.agenda-row:hover { background: var(--hover-strong); }

/* Fixed width so every title starts on the same x, timed or not. */
.agenda-time {
  width: 38px; flex: none;
  padding-top: 1px;
  font-size: 11px;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}

.agenda-bar { width: 3px; flex: none; border-radius: 2px; }

.agenda-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
.agenda-title {
  font-size: 13px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.agenda-detail {
  font-size: 11px; color: var(--text-faint);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* Title-only rows: centre the title, and bring the time in the gutter with it
   so the two stay on the same line. Rows that do have a detail keep the title
   on top with the time beside it, which is where it already lined up. */
.agenda-row.is-single .agenda-text { justify-content: center; }
.agenda-row.is-single .agenda-time {
  display: flex; align-items: center;
  padding-top: 0;
}

.agenda-row.is-done .agenda-title { text-decoration: line-through; color: var(--text-faint); }

/* The block's colour is already carried by the bar on the left, so the icon is
   read as a symbol rather than a second colour chip — it takes the text colour,
   which keeps it legible against the page in either theme. */
.agenda-icon { flex: none; display: flex; align-items: center; color: var(--text); }

/* Without `fill`, an inline SVG paints black regardless of `color` — that is
   what made these disappear into the dark background. */
.agenda-tagicon { width: 15px; height: 15px; fill: currentColor; display: block; }

.agenda-divider {
  display: flex; align-items: center; gap: 8px;
  padding: 9px 6px 3px;
}
.agenda-divider span {
  font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--text-faint);
}
.agenda-divider::after {
  content: ''; flex: 1; height: 1px; background: var(--line);
}

.agenda-more {
  margin: 2px 0 0 48px;
  padding: 5px 9px;
  border: 0; border-radius: 7px;
  background: var(--fill-subtle);
  color: var(--text-dim);
  font: inherit; font-size: 11px;
  cursor: pointer;
}
.agenda-more:hover { background: var(--active); color: var(--text); }

.agenda-empty { padding: 32px 20px; text-align: center; color: var(--text-faint); font-size: 13px; }

@media (max-width: 860px) {
  .tl-agenda { padding-bottom: 92px; }
  .agenda-group { padding: 0 12px; }
  /* Padding deliberately not overridden here: it feeds the natural two-line
     height that min-height is set against, so changing it on mobile alone
     would make the two disagree. */
}

/* ============================================================ usage (admin) */

/* A standalone page, so it sets its own page frame rather than living in the
   app's grid. */
body.stats-page { overflow: auto; }

#stats { max-width: 940px; margin: 0 auto; padding: 32px 20px 64px; }

.stats-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 16px; margin-bottom: 22px;
}
.stats-head h1 { margin: 0; font-size: 22px; font-weight: 600; letter-spacing: -0.01em; }

.stats-top {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 10px; margin-bottom: 22px;
}
.stats-top-item {
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 13px 15px;
  display: flex; flex-direction: column; gap: 3px;
}
.stats-top-item span { font-size: 20px; font-weight: 600; }
.stats-top-item small { font-size: 11px; color: var(--text-faint); }

.stats-list { display: flex; flex-direction: column; gap: 10px; }

.stats-card {
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 15px 17px;
}

.stats-card-head { display: flex; align-items: center; gap: 10px; }

.stats-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.stats-dot.is-live { background: #4ECB71; }
.stats-dot.is-warm { background: #E0A21A; }
.stats-dot.is-cold { background: var(--text-faint); }

.stats-who { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
.stats-name { font-size: 14px; font-weight: 500; }
.stats-email {
  font-size: 11px; color: var(--text-faint);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.stats-role {
  font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--text-dim);
  background: var(--fill-subtle);
  border-radius: 20px;
  padding: 3px 9px;
  flex: none;
}

.stats-last { font-size: 12px; color: var(--text-dim); margin: 9px 0 13px; }
.stats-last strong { font-weight: 500; color: var(--text); }
.stats-flag { color: #E0A21A; }

.stats-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(84px, 1fr));
  gap: 9px;
}
.stat {
  background: var(--fill-subtle);
  border-radius: 8px;
  padding: 9px 10px;
  display: flex; flex-direction: column; gap: 2px;
}
.stat-value { font-size: 15px; font-weight: 500; font-variant-numeric: tabular-nums; }
.stat-label { font-size: 10px; color: var(--text-faint); }

.stats-foot { margin: 20px 0 0; font-size: 11px; color: var(--text-faint); text-align: center; }
.stats-msg { padding: 40px 0; text-align: center; color: var(--text-dim); font-size: 13px; }
.stats-msg a { color: var(--accent); }

@media (max-width: 860px) {
  #stats { padding: 20px 14px 48px; padding-top: max(20px, env(safe-area-inset-top)); }
  .stats-head { flex-direction: column; gap: 10px; }
}

/* =============================================================== read only */

/* Shown when an admin opens a client's calendar. Deliberately in the topbar
   next to the date, not a dismissible toast — it has to be true for as long as
   the state is true. */
.view-only-pill {
  font-size: 11px;
  color: var(--accent);
  background: var(--accent-soft);
  border-radius: 20px;
  padding: 3px 10px;
  white-space: nowrap;
}

/* Belt-and-braces on top of the state-layer guards: even if a control is drawn,
   it cannot be operated. The server refuses the write regardless. */
#app.is-readonly .chip { cursor: default; }
#app.is-readonly .chip-handle,
#app.is-readonly .chip-handle-b { display: none; }
#app.is-readonly .tl-hint { display: none !important; }

/* Nothing to reorder on a calendar you can only read, and a handle that does
   nothing when pulled is worse than no handle. The drag is refused in JS too. */
.panel.is-readonly .check-grip { display: none; }

.panel.is-readonly input,
.panel.is-readonly textarea,
.panel.is-readonly select,
.panel.is-readonly .seg-btn,
.panel.is-readonly .swatch,
.panel.is-readonly .checklist-item input {
  pointer-events: none;
  opacity: 0.72;
}
/* Actions that only make sense on your own calendar. Tags stay visible — they
   are information — but lose the ✕ that would remove them, and the colour-rule
   row loses its "Stop" control. */
.panel.is-readonly .panel-actions,
.panel.is-readonly .icon-btn.is-danger,
.panel.is-readonly .panel-delete,
.panel.is-readonly .tag-add,
.panel.is-readonly .tag button,
.panel.is-readonly .rule-note .ghost-btn,
.panel.is-readonly .rule-ask { display: none; }

/* Account headroom in the workspace header. Neutral until it matters, then
   coloured — a number you only notice when it is about to stop you. */
.admin-cap { color: var(--text-faint); }
.admin-cap-hit { color: var(--accent); font-weight: 500; }
.admin-section-head .primary-btn[disabled] { opacity: 0.45; cursor: not-allowed; }

/* A lapsed Google authorisation. Amber, not red: nothing is broken and no data
   is lost — it just needs redoing. */
.gcal-warn { color: #E0A21A; }

/* Per-client actions on an admin card. */
.stats-actions {
  display: flex; flex-wrap: wrap; gap: 6px;
  margin-top: 12px; padding-top: 12px;
  border-top: 1px solid var(--line);
}
.stats-actions .ghost-btn { text-decoration: none; }
.stats-cap-hit { color: var(--accent); }

/* A freshly minted invite, shown once at the top of the page. */
.stats-invite {
  background: var(--surface-2);
  border: 1px solid var(--border-accent, var(--line-strong));
  border-radius: 12px;
  padding: 14px 16px;
  margin-bottom: 16px;
}
.stats-invite-head { font-size: 13px; margin-bottom: 8px; }
.stats-invite-url {
  width: 100%; padding: 8px 10px;
  border: 1px solid var(--line); border-radius: 8px;
  background: var(--fill-subtle); color: var(--text);
  font: inherit; font-size: 12px;
}
.stats-invite-actions { display: flex; align-items: center; gap: 10px; margin-top: 9px; }
.stats-invite-note { font-size: 11px; color: var(--text-faint); }

/* Reminders toggle in the sidebar footer. */
/* ------------------------------------------------- reminder settings sheet */

.rem-modal {
  position: fixed; inset: 0; z-index: 110;
  display: flex; align-items: flex-start; justify-content: center;
  padding: 24px;
  overflow-y: auto;
}
.rem-scrim { position: absolute; inset: 0; background: rgba(0, 0, 0, 0.55); }
.rem-sheet {
  position: relative;
  margin: auto;
  width: min(440px, 100%);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: 14px;
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}
.rem-head { padding: 18px 20px 12px; border-bottom: 1px solid var(--line); }
.rem-head h2 { margin: 0; font-size: 16px; font-weight: 600; }
.rem-head p { margin: 4px 0 0; font-size: 12px; color: var(--text-faint); }

.rem-list { max-height: min(48vh, 420px); overflow-y: auto; padding: 8px 10px; }
.rem-empty { margin: 12px; font-size: 12.5px; color: var(--text-faint); text-align: center; }

.rem-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px;
  padding: 7px 10px;
  border-radius: 9px;
}
.rem-row.is-on { background: var(--fill-subtle); }
.rem-pick { display: flex; align-items: center; gap: 9px; min-width: 0; cursor: pointer; }
.rem-pick input { accent-color: var(--accent); width: 15px; height: 15px; flex: none; }
.rem-dot {
  width: 24px; height: 24px; border-radius: 50%; flex: none;
  display: flex; align-items: center; justify-content: center;
  background: var(--active); color: var(--text-dim);
}
.rem-dot svg { width: 13px; height: 13px; }
.rem-name {
  font-size: 13px; text-transform: capitalize;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Dimmed until ticked, so the ticked rows are what the eye finds. */
.rem-lead { display: flex; align-items: center; gap: 5px; flex: none; opacity: 0.45; }
.rem-row.is-on .rem-lead { opacity: 1; }
.rem-num {
  width: 54px; padding: 5px 6px; text-align: right;
  border: 1px solid var(--line); border-radius: 6px;
  background: var(--fill-subtle); color: var(--text); font: inherit; font-size: 12.5px;
}
.rem-unit {
  padding: 5px 4px;
  border: 1px solid var(--line); border-radius: 6px;
  background: var(--fill-subtle); color: var(--text); font: inherit; font-size: 12px;
}
.rem-before { font-size: 11.5px; color: var(--text-faint); }

.rem-note { margin: 0; padding: 10px 20px; font-size: 11.5px; color: var(--text-faint); line-height: 1.5; }
.rem-note.is-warn { color: #E0A21A; }
.rem-actions {
  display: flex; justify-content: flex-end; gap: 8px;
  padding: 12px 16px 16px; border-top: 1px solid var(--line);
}

@media (max-width: 860px) {
  /* The unit dropdown and "before" are the first things to go: at 375px the row
     would otherwise wrap and the tag name loses its line. */
  .rem-before { display: none; }
  .rem-num { width: 48px; }
  .rem-list { max-height: 56vh; }
}

.reminders-btn { width: 100%; text-align: center; margin-top: 2px; }
.reminders-btn.is-on { color: var(--accent); border-color: var(--accent); }
/* Theme toggle — a tappable alternative to the D shortcut, so a phone (where
   the keyboard hints are hidden) can still switch day/night. */
/* The theme toggle is the phone's substitute for pressing D, so a desktop does
   not need it — the D hint a few lines below says the same thing. It returns at
   the narrow breakpoint, where that hint is hidden and there is no key to press.
   The override is kept here rather than with the other narrow-screen rules
   because those sit earlier in the file: at equal specificity the later rule
   wins, so a `display: block` up there would lose to the `display: none` here. */
.theme-btn { display: none; width: 100%; text-align: center; margin-top: 2px; }
@media (max-width: 860px) {
  .theme-btn { display: block; }
}

/* Freshness indicator on the read-only banner. */
.view-only-pill { cursor: pointer; user-select: none; }
.view-only-fresh { color: var(--text-faint); }
.view-only-refresh { margin-left: 2px; opacity: 0.7; }
.view-only-pill:hover .view-only-refresh { opacity: 1; }
.view-only-pill.is-refreshing .view-only-refresh { animation: spin 0.7s linear infinite; display: inline-block; }
@keyframes spin { to { transform: rotate(360deg); } }
