/* Meridian: Morgen's frame (rail | tasks | calendar), iOS-style frosted panels, one accent. */
[x-cloak] { display: none !important; }
:root {
  /* Salmon and green on white. Every value here is measured, not picked by
     eye: ink on the salmon fill is 5.09:1 and white on the green is 4.87:1,
     so the two action colours both clear AA for their own labels — which the
     old white-on-lilac button (2.87:1) never did. --accent-2 is the darker
     salmon, the only one that carries text on a white panel. */
  --accent: #f48268; --accent-2: #9c4216; --accent-ink: #2f3138;
  --accent-soft: color-mix(in srgb, var(--accent) 16%, transparent);
  --green: #2e7d5b; --green-2: #226045; --sage: #9ec9ae;
  --block: #f7b3a1;   /* time blocked for a task, so it reads apart from a meeting */
  /* --ink3 was #736f70, which measures 4.39:1 on the salmon-tinted count
     badge and 4.43:1 on the day picker — both just under. It carries the
     small counts, dates and hints everywhere, so it is the one token most
     worth having margin on: #6b6768 is 4.94 and 4.98 on those two and still
     reads a step lighter than --ink2. */
  --ink: #2f3138; --ink2: #5a5658; --ink3: #6b6768;
  --line: rgba(47,49,56,.08); --glass: rgba(255,255,255,.78); --glass-2: rgba(255,255,255,.55);

  /* Spacing. A 4px base with the useful middles; every padding, gap and inset
     in this file comes from here. Before it existed there were 24 padding
     combinations including 7px 11px and 6px 9px, which is why no interval read
     as a step and each new component invented a 25th value. */
  --s1: 2px; --s2: 4px; --s3: 6px; --s4: 8px; --s5: 12px; --s6: 16px; --s7: 20px; --s8: 28px; --s9: 40px;

  /* Type. Morgen's calendar is dense and it is what makes a week legible at a
     glance, so this is a real scale rather than thirty values chosen one at a
     time. Nothing functional goes below --t-xs; that is the floor where a
     label stops being readable. */
  --hour: 46px;
  --t-xs: 11px; --t-sm: 11.5px; --t-md: 12.5px; --t-lg: 13.5px; --t-xl: 15px;
  --t-h: 19px; --t-stat: 26px; --t-stat-sm: 19px;
  --lh: 1.45;

  /* Hairlines. One pixel at a low alpha, which is what makes a grid read as
     ruled rather than drawn. */
  --hair: rgba(47,49,56,.055);

  /* A divider may be faint. A mark that carries information may not.
     The streak pips beside each habit have three states — done, due and
     missed, not due — and the middle one was bg-black/10: 1.25:1 on the
     light page and 1.01:1 on the dark one, where a day you missed looked
     exactly like a day the habit was never scheduled. --mark is the floor
     for anything that is a mark rather than a rule: 3.29:1 on white and
     3.42:1 on the dark page. */
  --mark: rgba(47,49,56,.55);

  /* Trouble and warning. These were Tailwind literals in the markup —
     text-red-500 on overdue dates, text-red-600 on every error line,
     text-amber-700 once — and a literal cannot be right on two pages: on the
     light one red-500 measured 3.56:1, on the dark one red-600 was 3.54 and
     amber-700 was 3.40. Each failed in exactly the theme it was not picked
     against. As a pair they clear 4.5 on both.

     The light value is #b83f31 rather than #c2483a because --bad also lands
     on a ghost button ("Erase everything"), whose own tint is a shade darker
     than the page: #c2483a measured 4.64 against the page and 4.49 there. */
  --bad: #b83f31; --warn: #8a5a12;

  /* The page's own surface, behind a tile or under a floating control bar.
     Two rules asked for this token and nothing ever defined it, so both
     `background: var(--page)` and the color-mix() built on it were invalid
     and dropped: the shop's product tiles were 220px of nothing with the art
     floating in the middle, and the terrarium's control bar had no panel
     behind it at all. */
  --page: #fbf8f6;

  /* Radii. Five steps: control, field, button, card, frame, pill. */
  --r-xs: 8px; --r-sm: 12px; --r-md: 14px; --r-lg: 16px; --r: 20px; --r-full: 9999px;

  /* Stacking, named in paint order. 44 and 45 used to sit next to each other
     as bare numbers and it was never clear which was meant to win. */
  --z-ev: 5; --z-ev-hover: 6; --z-tabbar: 30; --z-now: 34; --z-pop: 40; --z-scrim-panel: 44;
  --z-panel: 45; --z-drag: 50; --z-scrim: 60; --z-sheet: 61; --z-sheet-left: 62; --z-toast: 70; --z-onboard: 80; --z-dragchip: 90;
}
/* --ink3 was #a79b96, which measures 4.50:1 where body.mesh's gradient
   lifts the backdrop brightest — right on the line, and only visible once
   the probe started reading rendered pixels instead of the declared stack.
   #b0a49f is 5.01 there and 7.75 on the flat page. */
html.dark { --page: #221a18; --bad: #ff8a7a; --warn: #e8b86a; --mark: rgba(242,236,233,.40); --hair: rgba(242,236,233,.085); --accent: #f48268; --accent-2: #ffab95; --accent-ink: #21160f; --green: #6cc39a; --green-2: #a8dcc0; --ink: #f2ece9; --ink2: #c9bfba; --ink3: #b0a49f; --line: rgba(242,236,233,.12); --glass: rgba(34,26,24,.72); --glass-2: rgba(255,255,255,.05); }
html { -webkit-text-size-adjust: 100%; }
body.mesh {
  color: var(--ink);
  /* White, with the two colours breathed into the corners rather than washed
     over the whole thing. The app is a tool; the colour belongs to what you
     act on, not to the paper. */
  background:
    radial-gradient(64% 56% at 0% 0%, rgba(244, 130, 104, .12), transparent 62%),
    radial-gradient(58% 48% at 100% 0%, rgba(169, 207, 180, .30), transparent 62%),
    radial-gradient(60% 58% at 100% 100%, rgba(244, 130, 104, .10), transparent 62%),
    radial-gradient(48% 48% at 0% 100%, rgba(63, 125, 95, .10), transparent 62%),
    #ffffff;
  background-attachment: fixed;
}
html.dark body.mesh { background: radial-gradient(70% 60% at 0% 0%, rgba(160, 70, 48, .30), transparent 60%), radial-gradient(60% 50% at 100% 0%, rgba(50, 105, 80, .32), transparent 60%), radial-gradient(60% 60% at 100% 100%, rgba(140, 62, 44, .22), transparent 60%), #17100e; }
/* These used to restate the dark ink values as literals, which is the exact
   trap tailwind.config.js warns about in its own comment — the utilities it
   generates are already `color: var(--ink3)`, and --ink3 is redefined under
   html.dark, so they follow the theme on their own. The copies here did not:
   when --ink3 moved to #b0a49f they kept #a79b96, and the 34 places using
   text-ink-3 stayed on the value that measured 4.50:1. Tokens only. */
html.dark .bg-ink { background: var(--ink); color: var(--page) }

.glass { background: var(--glass); border: 1px solid rgba(255,255,255,.7); box-shadow: 0 6px 24px rgba(74, 58, 52,.06); }
html.dark .glass { border-color: rgba(255,255,255,.08); box-shadow: 0 8px 30px rgba(0,0,0,.3); }

/* frame */
/* Safe areas on all four edges. Top and bottom were handled; left and right
   were not, so an iPhone held sideways put the Dynamic Island over the icon
   rail and the home indicator over the calendar. max() keeps the ordinary
   inset when there is no cutout. */
.app {
  display: flex; gap: var(--s5); height: 100vh; height: 100dvh; box-sizing: border-box;
  padding: max(var(--s5), env(safe-area-inset-top)) max(var(--s5), env(safe-area-inset-right)) max(var(--s5), env(safe-area-inset-bottom)) max(var(--s5), env(safe-area-inset-left));
}
.rail { flex: 0 0 60px; border-radius: var(--r); display: flex; flex-direction: column; align-items: center; padding: var(--s4) 0; gap: var(--s2); }
.rail-btn { width: 40px; height: 40px; border-radius: var(--r-md); display: flex; align-items: center; justify-content: center; color: var(--ink3); transition: background .15s, color .15s; }
.rail-btn:hover { background: rgba(0,0,0,.05); color: var(--ink); }
.rail-btn.active { background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--accent-2); }
.rail-pet { width: 44px; height: 44px; border-radius: var(--r-lg); display:flex; align-items:center; justify-content:center; background: rgba(255,255,255,.6); border: 1px solid rgba(255,255,255,.9); }
.rail-pet.active { outline: 2px solid var(--accent); }
html.dark .rail-pet { background: rgba(255,255,255,.08); border-color: rgba(255,255,255,.1); }
.left { flex: 0 0 300px; border-radius: var(--r); display: flex; flex-direction: column; min-height: 0; min-width: 0; }
.main { flex: 1 1 auto; border-radius: var(--r); min-width: 0; min-height: 0; overflow: hidden; position: relative; }
.head { display: flex; align-items: center; gap: var(--s3); padding: var(--s5) var(--s5) var(--s4); }
.foot { font-size: var(--t-xs); color: var(--ink3); padding: var(--s4) var(--s5); border-top: 1px solid var(--line); }
/* Wrapping is not a phone rule. It was written as one, so a 744pt iPad — above
   the 700px breakpoint, and coarse enough that every control is 44pt — ran the
   Event button off the right edge with nowhere for the row to go. A toolbar
   that cannot fit its controls should wrap wherever it is. */
.toolbar { display: flex; flex-wrap: wrap; row-gap: var(--s3); align-items: center; gap: var(--s3); padding: var(--s4) var(--s5); border-bottom: 1px solid var(--line); position: relative; }
/* type scale: one step per job, so weight and size carry the hierarchy */
.label { font-size: var(--t-xs); letter-spacing: .09em; text-transform: uppercase; color: var(--ink3); font-weight: 600; margin-bottom: var(--s4); }
.title { font-size: var(--t-xl); font-weight: 600; letter-spacing: -.01em; }
.stat { font-size: var(--t-stat); font-weight: 700; line-height: 1.05; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.stat-sm { font-size: var(--t-stat-sm); font-weight: 700; line-height: 1.1; font-variant-numeric: tabular-nums; }
.stat-label { font-size: var(--t-xs); color: var(--ink3); margin-top: var(--s1); }
.muted { color: var(--ink3); }
.empty { color: var(--ink3); font-size: var(--t-md); padding: var(--s3) 0; }
.card { background: rgba(255,255,255,.62); border: 1px solid rgba(47,49,56,.07); border-radius: var(--r-lg); padding: var(--s7); container-type: inline-size; }
.card + .card { margin-top: 0; }
.card-head { display: flex; align-items: baseline; gap: var(--s4); margin-bottom: var(--s5); }
.card-head h3, .card-head p { margin: 0; }
html.dark .card { border-color: rgba(255,255,255,.07); }

/* controls */
:where(.ib) { display: inline-flex; }
.ib { width: 30px; height: 30px; border-radius: var(--r-xs); align-items: center; justify-content: center; color: var(--ink3); flex-shrink: 0; }
.ib:hover { background: rgba(0,0,0,.05); color: var(--ink); }
.ib.active { background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent-2); }
.btn-ghost { display:inline-flex; align-items:center; gap: var(--s2); border-radius: var(--r-sm); padding: var(--s2) var(--s4); font-weight: 600; color: var(--ink); background: rgba(0,0,0,.04); }
.btn-ghost:hover { background: rgba(0,0,0,.08); }
html.dark .btn-ghost { background: rgba(255,255,255,.07); } html.dark .btn-ghost:hover { background: rgba(255,255,255,.12); }
/* Ink on salmon, not white on salmon. White read at 2.87:1 on the old lilac
   and would be 2.55:1 here; ink on this salmon is 5.09:1. The warm label is
   also simply better looking than a washed-out white one. */
.btn-primary { display:inline-flex; align-items:center; gap: var(--s2); border-radius: var(--r-sm); padding: var(--s3) var(--s5); font-weight: 600; color: var(--accent-ink); background: var(--accent); box-shadow: 0 3px 10px color-mix(in srgb, var(--accent) 34%, transparent); transition: background .15s, box-shadow .15s; }
.btn-primary:hover { background: color-mix(in srgb, var(--accent) 88%, var(--accent-2)); box-shadow: 0 4px 14px color-mix(in srgb, var(--accent) 42%, transparent); }
/* A disabled primary used to be the filled button at 50% opacity, which
   made it as loud as the one you can actually press and put its label at
   2.53:1 in light and 1.40:1 in dark. In the shop that label is the only
   thing saying how many coins you are short, so it has to be readable.
   Now it recedes to an outline and the text stays at full strength. */
.btn-primary:disabled {
  background: transparent; color: var(--ink2);
  box-shadow: inset 0 0 0 1px var(--line);
  opacity: 1; cursor: not-allowed;
}
/* The quieter of the two actions: green carries white at 4.87:1. */
.btn-go { display:inline-flex; align-items:center; gap: var(--s2); border-radius: var(--r-sm); padding: var(--s3) var(--s5); font-weight: 600; color: #fff; background: var(--green); transition: background .15s; } html.dark .btn-go { color: #21160f; }
.btn-go:hover { background: var(--green-2); }
/* One class for every native checkbox and radio, so the accent is set once. */
.tick { accent-color: var(--accent); }
.sel { appearance: none; -webkit-appearance: none; background: rgba(0,0,0,.04) url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24' fill='none' stroke='%23918fa0' stroke-width='3' stroke-linecap='round'><path d='m6 9 6 6 6-6'/></svg>") no-repeat right 8px center; border-radius: var(--r-sm); padding: var(--s2) var(--s7) var(--s2) var(--s4); font-weight: 600; color: var(--ink); outline: none; }
html.dark .sel { background-color: rgba(255,255,255,.07); } html.dark .sel option { color: #111; }
:where(.field-row) { display: flex; }
.field-row { align-items:center; gap: var(--s4); background: rgba(0,0,0,.04); border-radius: var(--r-sm); padding: var(--s3) var(--s5); transition: box-shadow .15s; }
.field-row:focus-within { box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 35%, transparent); background: rgba(255,255,255,.8); }
.field-row input { background: transparent; outline: none; font-size: var(--t-md); min-width: 0; color: var(--ink); }
html.dark .field-row { background: rgba(255,255,255,.07); }
.input { background: rgba(0,0,0,.04); border: 1px solid transparent; border-radius: var(--r-sm); padding: var(--s3) var(--s4); font-size: var(--t-md); outline: none; color: var(--ink); min-width: 0; }
/* Tailwind loads before this file, so `.input { width: 100% }` at normal
   specificity silently beat every `w-20` and `w-full` in the markup — and two
   time inputs in one flex row each demanded the whole row and printed on top
   of each other. :where() gives the default zero specificity: it still applies
   everywhere, and any utility overrides it. Same for the display properties
   below, which is what `hidden md:flex` kept losing to. */
:where(.input) { width: 100%; }
.input:focus { border-color: transparent; box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 35%, transparent); background: rgba(255,255,255,.8); }
html.dark .input { background: rgba(255,255,255,.07); } html.dark .input:focus { background: rgba(0,0,0,.3); } html.dark .input option { color: #111; }
.field { display:flex; flex-direction:column; gap: var(--s1); font-size: var(--t-xs); color: var(--ink3); min-width: 0; }
/* A grid cell's default min-width is auto, which is the input's intrinsic width —
   a date input asks for ~130px, so two of them in a 320px card pushed a
   horizontal scrollbar across the card and shoved the footer out of view. */
.hint { font-size: var(--t-xs); color: var(--ink3); line-height: 1.35; }
.row { display:flex; align-items:center; gap: var(--s4); padding: var(--s3) var(--s4); border-radius: var(--r-sm); text-align:left; width: 100%; }
.row:hover { background: rgba(0,0,0,.04); } .row.active { background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent-2); font-weight: 600; }
.cbox { width: 14px; height: 14px; border-radius: var(--r-xs); border: 2px solid var(--c); display:inline-flex; align-items:center; justify-content:center; color:#fff; flex-shrink:0; }
.cbox.on { background: var(--c); }
.check { width: 20px; height: 20px; border-radius: var(--r-full); border: 2px solid rgba(47,49,56,.2); flex-shrink:0; display:inline-flex; align-items:center; justify-content:center; color:#fff; transition: transform .18s cubic-bezier(.2,.8,.2,1), background .15s, border-color .15s; }
.check:active { transform: scale(.85); }
.check:hover { border-color: var(--accent); }
.check.on { background: var(--sage); border-color: var(--sage); }
html.dark .check { border-color: rgba(255,255,255,.3); }
/* A task title clipped to one line loses the end of the sentence, which for a
   task is usually the part that says what to do: "Email Dana about the…" is
   not a task. Morgen wraps to two lines and clips there. .truncate cannot be
   made to do this — it sets white-space: nowrap, so there is no second line to
   clamp to. */
.wrap2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; overflow-wrap: anywhere; white-space: normal; }
/* Routines. A step row is deliberately quieter than a task row: the steps are
   inside one thing you are doing, so the routine's own title is the only line
   that competes with the rest of the page. */
.routine { padding: var(--s4) 0; border-bottom: 1px solid var(--line); }
.routine:last-of-type { border-bottom: 0; }
.routine.done { opacity: .72; }
.routine.done:hover { opacity: 1; }
.rhead { display:flex; align-items: center; gap: var(--s4); }
.rsteps { display:flex; flex-direction: column; gap: var(--s2); margin: var(--s4) 0 var(--s2) 26px; }
.rstep { display:flex; align-items:center; gap: var(--s4); font-size: var(--t-sm); }
.rmin { width: 64px; text-align: right; }
/* The day circles, the "every day" note and the delete control share one row
   and must be allowed to fall onto a second: at this card's width a "Delete
   routine" button squeezed the seven days until the text broke mid-word. */
.rfoot { display:flex; align-items:center; gap: var(--s3); flex-wrap: wrap; font-size: var(--t-xs); color: var(--ink3); margin-top: var(--s3); }
/* Pushed to the end of the row, and onto its own line when the row runs out:
   an unlabelled x after the seven day circles reads as "remove a day". */
.rdel { margin-left: auto; white-space: nowrap; color: var(--ink3); }
.rdel:hover { color: var(--bad); }
@media (pointer: coarse) { .rdel { min-height: 44px; } }
.rday { width: 26px; height: 26px; border-radius: 999px; background: rgba(0,0,0,.05); color: var(--ink); flex-shrink: 0; }
.rday.on { background: var(--accent); color: var(--accent-ink); }
html.dark .rday { background: rgba(255,255,255,.08); }
@media (pointer: coarse) { .rday { width: 44px; height: 44px; } }
/* The make-a-profile row: one field and one button, side by side on a wide
   card and stacked once the card is narrow enough that they would squeeze.
   The "+" in the avatar keeps the same ink as a real initial — tinting it
   with the accent put it at 2.21:1 on the card behind it. */
.acct-make { display: flex; align-items: center; gap: var(--s3); flex-shrink: 0; }
.acct-make .input { width: 11rem; }
@media (max-width: 42rem) {
  .acct-row { flex-wrap: wrap; }
  .acct-make { width: 100%; }
  .acct-make .input { width: auto; flex: 1 1 auto; }
}
/* Dropdowns that replaced the side-pane copies. They reuse .minimonth and
   .sidecal so there is one implementation of each, not two that drift. */
.pop-mini { left: 6rem; width: 17rem; padding: var(--s4); }
.pop-cals { right: 6rem; width: 17rem; max-height: 70vh; overflow: auto; }
.minimonth-pop { border-bottom: 0; padding: 0; }
.viewseg { flex-shrink: 0; }
/* Today, obvious. It was a 24px pill around the date number and nothing
   else, so on a seven-column week you had to read the numbers to find out
   which column was now. The column and the month cell carry a wash, and the
   header a rule along the top, which is what you actually see when you glance
   at the grid. Kept faint on purpose: it is orientation, not an alert. */
.daycol.is-today, .monthcell.is-today { background: color-mix(in srgb, var(--accent) 7%, transparent); }
.dayhead.is-today { background: color-mix(in srgb, var(--accent) 9%, transparent); box-shadow: inset 0 2px 0 var(--accent); }
html.dark .daycol.is-today, html.dark .monthcell.is-today { background: color-mix(in srgb, var(--accent) 12%, transparent); }
html.dark .dayhead.is-today { background: color-mix(in srgb, var(--accent) 15%, transparent); }
/* The off-hours wash sits inside the column, so it has to stay above the
   today wash or today's working hours look the same as its evenings. */
.daycol.is-today .offhours { background: color-mix(in srgb, var(--ink) 4%, transparent); }
.task { display:flex; align-items:center; gap: var(--s5); padding: var(--s5) var(--s5); border-radius: var(--r-md); cursor: grab; touch-action: none; user-select: none; -webkit-user-select: none; }
.task > .grow > p:first-child { font-weight: 600; }
.task:hover { background: rgba(0,0,0,.04); } html.dark .task:hover { background: rgba(255,255,255,.06); }
.meta { font-size: var(--t-sm); color: var(--ink3); display:flex; gap: var(--s4); flex-wrap: wrap; margin-top: var(--s1); }
/* P1 and P2 override this with a colour mix, so they read on either page.
   P3 and P4 fall through to the base, and a 6% black tint is 15 of 255 on
   the light page and 1 on the dark one — so the two lower priorities had no
   pill at all in dark mode and rendered as bare text beside two that did.
   Every other surface in this file already pairs its black tint with a white
   one; this was the one that did not. */
.pri { font-size: var(--t-xs); font-weight: 600; padding: var(--s1) var(--s2); border-radius: var(--r-xs); background: rgba(0,0,0,.07); color: var(--ink2); }
html.dark .pri { background: rgba(255,255,255,.09); }
.pri { border-radius: var(--r-xs); }
/* Both of these were hardcoded and neither followed the theme: p1 still
   carried #5f52a8, the lilac from the palette before this one, and on a dark
   page the two read at 1.94:1 and 1.27:1 — the second is all but invisible.
   --accent-2 and --green-2 already flip, and --accent-2 is the accent chosen
   to be legible as text rather than as a fill. */
/* 20% accent behind --accent-2 lands the P1 chip at exactly 4.5:1, which is
   the threshold and not a margin — any nudge to the accent drops it under.
   A lighter fill gives the same chip some room. */
.pri.p1 { background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent-2); }
.pri.p2 { background: color-mix(in srgb, var(--sage) 35%, transparent); color: var(--green-2); }
.bar { height: 5px; border-radius: var(--r-full); background: rgba(0,0,0,.08); overflow: hidden; }
.bar > div { height: 100%; border-radius: var(--r-full); background: linear-gradient(90deg, var(--sage), var(--accent)); transform-origin: left; transition: transform .5s cubic-bezier(.2,.8,.2,1); width: 100%; }
html.dark .bar { background: rgba(255,255,255,.1); }
.pop { position: absolute; top: 46px; z-index: var(--z-pop); background: rgba(255,255,255,.92); border: 1px solid rgba(255,255,255,.9); border-radius: var(--r-lg); padding: var(--s5); box-shadow: 0 12px 36px rgba(74, 58, 52,.14); }
/* Every raised surface in dark mode is warm, because the page is. These
   were rgba(30,30,40) and rgba(22,36,31) — blue at 240deg and green at
   159deg floating on a page at 12deg — so the sheets, the palette, the
   dialog and the welcome card all read as a different product's panels
   dropped onto this one. Each is a little lighter than the page so it
   still reads as raised. */
html.dark .pop { background: rgba(46,35,32,.95); border-color: rgba(255,255,255,.08); }

/* calendar grid */
.daycol { touch-action: pan-y; background-image: repeating-linear-gradient(to bottom, var(--hair) 0 1px, transparent 1px var(--hour)); contain: layout paint; }
.daycol.weekend { background-color: rgba(0,0,0,.015); } html.dark .daycol.weekend { background-color: rgba(255,255,255,.022); }
.offhours { background: rgba(0,0,0,.025); } html.dark .offhours { background: rgba(0,0,0,.18); }
.ev { position:absolute; min-height: 18px; border-radius: var(--r-xs); padding: var(--s2) var(--s4); overflow:hidden; cursor: grab; background: color-mix(in srgb, var(--c) 28%, white); border: 1px solid color-mix(in srgb, var(--c) 45%, white); user-select:none; -webkit-user-select:none; z-index: var(--z-ev); transition: box-shadow .15s, transform .15s cubic-bezier(.2,.8,.2,1), opacity .15s; touch-action: none; container-type: size; }
.ev:hover { box-shadow: 0 4px 12px color-mix(in srgb, var(--c) 30%, transparent); z-index: var(--z-ev-hover); }
.ev.lifted { opacity: .3; }
/* A cascaded event sits over the one before it, so it needs an edge of its
   own or two overlapping chips read as one block. Applied by the layout,
   which is the only thing that knows an event is stacked. */
.ev.stacked { box-shadow: -2px 0 0 var(--page), -4px 0 8px rgba(0,0,0,.12); }
.ev.stacked:hover { box-shadow: -2px 0 0 var(--page), -4px 0 12px rgba(0,0,0,.2); }

/* A short chip had 2px of padding top and bottom, which left 11px of a 15px
   chip to hold an 11px line at 1.2 — a 13.2px line box in an 11px hole, so
   `overflow: hidden` sliced the bottom third off the word. A 15-minute event
   is 11.5px of grid at --hour 46, which cannot hold a line of type at all,
   so the chip also gets a floor: 18px, the way every other calendar does it.
   It is centred by flex already, so it needs no vertical padding of its own. */
.ev.short { padding: 0 var(--s4); display:flex; align-items:center; gap: var(--s3); } .ev.short .ev-title { font-size: var(--t-xs); line-height: 1.1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .ev.short .ev-time { display: none; }
.ev:active { cursor: grabbing; }
.ev.block { background: color-mix(in srgb, var(--block) 30%, white); border-color: color-mix(in srgb, var(--block) 60%, white); }
.ev.free { background: transparent; border: 1.5px dashed var(--c); }
.ev.ghost { left: 2px; right: 2px; background: color-mix(in srgb, var(--c) 34%, white); border: 1px solid color-mix(in srgb, var(--c) 60%, white); box-shadow: 0 10px 24px color-mix(in srgb, var(--c) 35%, transparent); z-index: var(--z-drag); pointer-events:none; transition: top .06s linear, height .06s linear; transform: scale(1.015); }
.ev.ghost .ev-time { color: var(--ink); opacity: .7; }
.dragging, .dragging * { cursor: grabbing !important; user-select: none !important; -webkit-user-select: none !important; }
.dragging .ev:not(.ghost) { pointer-events: none; }
.ev-title { font-size: var(--t-sm); font-weight: 600; line-height: 1.2; color: var(--ink); }
/* --ink3 is tuned against the page, and this sits on a coloured chip: 4.3:1
   on a light green one and 2.33:1 on a dark one. Ink at 78% keeps the time
   quieter than the title while staying readable on whatever the chip is. */
.ev-time { font-size: var(--t-xs); color: color-mix(in srgb, var(--ink) 88%, transparent); }
/* Whether the text fits is a question about the box, not about the meeting.
   The one-line mode used to be chosen by duration alone, so a 45-minute event
   in a 104px column wrapped its title to two lines inside a 42px chip and
   painted the top half of the second one. Ask the chip instead. */
@container (max-height: 52px) { .ev .ev-time { display: none; } }
@container (max-height: 34px) { .ev .ev-title { -webkit-line-clamp: 1; -webkit-box-orient: vertical; display: -webkit-box; overflow: hidden; } }
@container (max-width: 96px) { .ev .ev-time { display: none; } }
/* A title wider than its column was being cut through the letterform. One
   line with an ellipsis says the same thing and looks deliberate. */
@container (max-width: 116px) { .ev .ev-title { -webkit-line-clamp: 1; -webkit-box-orient: vertical; display: -webkit-box; overflow: hidden; overflow-wrap: anywhere; } }
.ev-resize { position:absolute; left:0; right:0; bottom:0; height: 8px; cursor: ns-resize; }
.ev-allday { display:block; width:100%; text-align:left; font-size: var(--t-xs); font-weight: 600; padding: var(--s1) var(--s4); border-radius: var(--r-xs); background: color-mix(in srgb, var(--c) 30%, white); color: var(--ink); overflow:hidden; text-overflow: ellipsis; white-space: nowrap; }
html.dark .ev, html.dark .ev-allday { background: color-mix(in srgb, var(--c) 38%, #2a201d); border-color: color-mix(in srgb, var(--c) 50%, #2a201d); } html.dark .ev.block { background: color-mix(in srgb, var(--block) 35%, #2a201d); }

/* sheets: slide in from the right, like an iOS side panel. The 10px margin is
   flush against the physical edge on an iPhone in landscape, where the right
   edge (this panel's anchor) is the one under the sensor housing — so it
   needs the same env(safe-area-inset-*) clearance the tab bar and task panel
   already get, or the panel renders under the notch instead of beside it. */
/* The scrim behind every overlay: the sheets, the palette, the shortcuts
   dialog, the welcome. Ink at 16% darkens a white page by about 32 of 255,
   which reads. On the dark page it *lightens* by 2 to 5 — the opposite of a
   scrim and invisible — so in dark mode every overlay had no backdrop at all
   and sat on the page rather than above it. Same mistake --accent-2 made:
   a colour tuned for white, put on near-black. */
.scrim { position: fixed; inset: 0; background: rgba(47,49,56,.16); z-index: var(--z-scrim); }
html.dark .scrim { background: rgba(0,0,0,.5); }
.sheet { position: fixed; top: max(10px, env(safe-area-inset-top)); right: max(10px, env(safe-area-inset-right)); bottom: calc(env(safe-area-inset-bottom) + 10px); width: min(420px, calc(100vw - 20px)); z-index: var(--z-sheet); border-radius: var(--r); background: rgba(255,255,255,.82); border: 1px solid rgba(255,255,255,.9); box-shadow: 0 20px 50px rgba(74, 58, 52,.18); transform: translateX(calc(100% + 20px)); transition: transform .3s cubic-bezier(.32,.72,0,1); overflow: hidden; }
.sheet.open { transform: translateX(0); }
html.dark .sheet { background: rgba(42,32,29,.94); border-color: rgba(255,255,255,.08); }
.dialog { position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); width: min(520px, calc(100vw - 24px)); z-index: var(--z-sheet); border-radius: var(--r); background: rgba(255,255,255,.88); border: 1px solid rgba(255,255,255,.9); padding: var(--s6); box-shadow: 0 24px 60px rgba(74, 58, 52,.25); }
html.dark .dialog { background: rgba(42,32,29,.94); border-color: rgba(255,255,255,.08); }

/* tablet: rail stays, the tasks panel floats over the calendar */
.scrim-panel { display: none; }
@media (max-width: 1100px) { .scrim-panel { display: block; z-index: var(--z-scrim-panel); background: rgba(59,47,63,.08); } }
.tabbar { display: none; }
@media (max-width: 1100px) and (min-width: 701px) {
  .left { position: fixed; left: calc(max(var(--s5), env(safe-area-inset-left)) + 64px); top: calc(env(safe-area-inset-top) + 12px); bottom: calc(env(safe-area-inset-bottom) + 12px); width: 300px; z-index: var(--z-panel); box-shadow: 0 24px 60px rgba(74, 58, 52,.25); }
}
/* phone: single column, bottom tab bar, tasks as a slide-up sheet, bigger targets */
@media (max-width: 700px) {
  body { font-size: var(--t-xl); }
  .app { gap: var(--s3); padding: max(var(--s3), env(safe-area-inset-top)) max(var(--s3), env(safe-area-inset-right)) calc(env(safe-area-inset-bottom) + 72px) max(var(--s3), env(safe-area-inset-left)); }
  .rail, .left.lists { display: none !important; }
  .main { border-radius: var(--r); }
  .left { position: fixed; left: max(var(--s3), env(safe-area-inset-left)); right: max(var(--s3), env(safe-area-inset-right)); bottom: calc(env(safe-area-inset-bottom) + 6px); top: auto; height: min(70vh, 560px); width: auto; z-index: var(--z-sheet-left); box-shadow: 0 -10px 40px rgba(74, 58, 52,.2); }
  .left .head { padding-top: 14px; }
  /* Cross-links in a toolbar that go somewhere the tab bar already goes.
     On a phone they are duplicates of a tab sitting two inches below them,
     and they are what pushed the header onto a second row — a wrapped
     toolbar costs about 60px of a screen that has none to spare. The Shop
     button is deliberately not one of these: the shop has no tab. */
  .tab-dupe { display: none; }
  .tabbar { display: flex; position: fixed; left: max(var(--s3), env(safe-area-inset-left)); right: max(var(--s3), env(safe-area-inset-right)); bottom: calc(env(safe-area-inset-bottom) + 6px); border-radius: var(--r-full); padding: var(--s3); justify-content: space-around; z-index: var(--z-tabbar); }
  .tab { width: 46px; height: 46px; border-radius: var(--r-full); display:flex; align-items:center; justify-content:center; color: var(--ink3); flex: 0 1 46px; }
  @media (max-width: 380px) { .tab { width: 42px; height: 42px; flex-basis: 42px; } }
  .tab.active { background: color-mix(in srgb, var(--accent) 18%, transparent); color: var(--accent-2); }
  /* Two rows, not three. Ten controls wrapping on their own produced a third
     row right-aligned under a left-aligned second one, so no two rows shared
     an edge and the chrome ate 131px of an 844px screen. Row one navigates and
     says where you are; row two acts. */
  .toolbar { flex-wrap: wrap; padding: var(--s4) var(--s4); row-gap: var(--s3); }
  .cal-toolbar > .ml-auto { flex: 1 0 100%; justify-content: flex-end; margin-left: 0; }
  .cal-toolbar > button.truncate { flex: 1 1 auto; min-width: 0; text-align: left; }
  .toolbar h1, .toolbar > button.text-\[15px\] { font-size: var(--t-xl); }
  .ib { width: 36px; height: 36px; }
  .btn-ghost, .btn-primary, .sel { padding-top: 8px; padding-bottom: 8px; min-height: 36px; }
  .check { width: 24px; height: 24px; }
  .task { padding: var(--s4) var(--s4); gap: var(--s5); }   /* the two lines carry the height; the coarse-pointer floor keeps the target */
  .ev { padding: var(--s1) var(--s3); } .ev-title { font-size: var(--t-md); } .ev-time { font-size: var(--t-xs); }
  /* The bottom edge here is the one a home indicator actually sits on, unlike
     the side-panel .sheet above — same fix, same reason: the Save/Delete row
     in .sheet-foot was rendering under it, not above it. */
  .sheet { top: auto; left: max(6px, env(safe-area-inset-left)); right: max(6px, env(safe-area-inset-right)); bottom: calc(env(safe-area-inset-bottom) + 6px); width: auto; height: min(86vh, 720px); transform: translateY(calc(100% + 20px)); }
  .sheet.open { transform: translateY(0); }
  .dialog { width: calc(100vw - 16px); }
  .pop { left: 8px !important; right: 8px !important; width: auto !important; }
  /* week and 3-day: keep columns readable and scroll sideways */
  .gridrow, .dayhead { min-width: max(100%, calc(48px + var(--cols, 7) * 118px)); }
  .grow.overflow-auto.relative { overflow-x: auto; }
  .dayhead { overflow-x: hidden; flex-shrink: 0; }
}
@media (max-width: 700px) {
  .world .hud.top-3.left-3 { left: 8px; right: 8px; top: 8px; }
  .world .hud.top-3.right-3 { left: 8px; right: 8px; top: 62px; }
}
/* touch: reveal hover-only controls */
/* Touch follows the input device, not the window width. Sizing targets by
   breakpoint meant an iPad at 1024pt logical width got the 30px pointer
   controls, because the phone rules stop at 700. A coarse pointer gets 44pt
   everywhere, which is Apple's floor and the one this ships against. The mark
   stays the size it was; the target grows around it. */
@media (pointer: coarse) {
  .ib, .rail-btn { min-width: 44px; min-height: 44px; }
  .rail-pet { width: 44px; height: 44px; }
  .btn-ghost, .btn-primary, .sel, .seg { min-height: 44px; min-width: 44px; }
  /* Three controls sized by utility classes rather than a component class, so
     the block below never reached them: the toolbar's date, which opens the
     mini calendar and was 22px tall; the day number in the column header,
     28x28; and the weekday pickers in the repeat row, also 28x28. The mark
     stays the size it was — the target grows around it, which is why these
     take padding and a min size rather than a bigger font. */
  .titlebtn { min-height: 44px; display: inline-flex; align-items: center; }
  .daynum, .daypick { min-width: 44px; min-height: 44px; }
  .donehead, .sechead, .newlist { min-height: 44px; }
  /* `.tab` is a flex item, so the narrow-phone rule's flex-basis decided its
     width and the coarse rule's `width` never got a say — 42pt on a 375pt
     screen. Five tabs at 44 come to 220 and fit with room over. */
  .tab { min-width: 44px; flex-basis: 44px; }
  /* The circle stays 26 and the target grows around it — a tick you have to
     aim at is the most-pressed control in the app. Sizing only the mark left
     it a 26pt target on a coarse pointer, under the 44pt floor every other
     control in this block clears. */
  .check { width: 26px; height: 26px; box-sizing: content-box; padding: 9px; margin: -9px; }
  .task, .row { min-height: 44px; }
  .tab { width: 46px; height: 46px; }
}
@media (hover: none) {
  .ev:hover { box-shadow: none; }
}
/* print: the agenda or the grid, nothing else */
@media print {
  body.mesh { background: #fff; }
  .rail, .left, .tabbar, .toolbar .ml-auto, .sheet, .scrim, .dialog, .dragchip { display: none !important; }
  .app { display: block; padding: 0; height: auto; }
  .main, .glass { border: 0; box-shadow: none; background: #fff; border-radius: 0; }
  .ev { break-inside: avoid; }
}
::-webkit-scrollbar { width: 8px; height: 8px; } ::-webkit-scrollbar-thumb { background: rgba(28,27,34,.15); border-radius: var(--r-xs); }

.dragchip { position: fixed; z-index: var(--z-dragchip); pointer-events: none; background: rgba(255,255,255,.95); border: 1px solid var(--accent); color: var(--ink); font-weight: 600; padding: var(--s3) var(--s5); border-radius: var(--r-sm); box-shadow: 0 8px 20px color-mix(in srgb, var(--accent) 25%, transparent); max-width: 240px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* The chip that follows the cursor while a task is dragged onto the grid.
   It was a 95% white pill in both themes with `color: var(--ink)`, which is
   near-white in dark — 1.06:1, so the task's name was invisible for the
   whole drag. Warm and raised, like every other floating surface. */
html.dark .dragchip { background: rgba(46,35,32,.96); }
::selection { background: color-mix(in srgb, var(--accent) 28%, transparent); }
:focus-visible { outline: 2px solid color-mix(in srgb, var(--accent) 75%, transparent); outline-offset: 2px; border-radius: var(--r-xs); }
input, textarea, select { caret-color: var(--accent); }
/* This was the dark-mode thumb written without its scope, so it overrode the
   light one for both themes and the bar came out dark grey on a dark surface:
   every scrolling panel in the app looked like it simply ended. The side
   pane's Calendars list hid a whole account below the fold that way. */
html.dark ::-webkit-scrollbar-thumb { background: rgba(255,255,255,.24); }
html.dark ::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,.38); }
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { transition-duration: .01ms !important; animation-duration: .01ms !important; } }

/* world */
.world { border-radius: var(--r); overflow: hidden; }
.world canvas { touch-action: none; cursor: grab; }
.world { background: radial-gradient(60% 50% at 50% 60%, rgba(255,255,255,.55), transparent 70%); }
.hud { position: absolute; background: rgba(255,255,255,.72); border: 1px solid rgba(255,255,255,.8); border-radius: var(--r-md); padding: var(--s4) var(--s5); box-shadow: 0 6px 20px rgba(74, 58, 52,.08); }
html.dark .hud { background: rgba(46,35,32,.7); border-color: rgba(255,255,255,.08); }

.palette { position: fixed; left: 50%; top: 14vh; transform: translateX(-50%); width: min(560px, calc(100vw - 24px)); z-index: var(--z-sheet-left); border-radius: var(--r-lg); background: rgba(255,255,255,.9); border: 1px solid rgba(255,255,255,.9); padding: var(--s5); box-shadow: 0 24px 60px rgba(74, 58, 52,.22); }
html.dark .palette { background: rgba(42,32,29,.94); border-color: rgba(255,255,255,.08); }

.chip-area { border-radius: var(--r-full); padding: var(--s1) var(--s4); font-size: var(--t-xs); font-weight: 600; color: var(--ink); background: color-mix(in srgb, var(--c) 30%, white); border: 1px solid transparent; }
.chip-area.on { border-color: var(--c); background: color-mix(in srgb, var(--c) 55%, white); }
html.dark .chip-area { background: color-mix(in srgb, var(--c) 30%, #2a201d); }

/* stat row: numbers as the hero, with a hairline between */
.stats { display: grid; gap: var(--s1); background: rgba(47,49,56,.07); border-radius: var(--r-lg); overflow: hidden; border: 1px solid rgba(47,49,56,.07); box-shadow: 0 1px 2px rgba(47,49,56,.04), 0 8px 24px rgba(47,49,56,.035); }
.stats > div { background: rgba(255,255,255,.66); padding: var(--s6) var(--s6); }
html.dark .stats { background: rgba(255,255,255,.08); border-color: rgba(255,255,255,.08); box-shadow: none; } html.dark .stats > div { background: rgba(255,255,255,.045); }
html.dark .card { background: rgba(255,255,255,.045); border-color: rgba(255,255,255,.08); box-shadow: none; }
/* a bar that still reads at zero */
.bar { position: relative; }
.bar::after { content: ''; position: absolute; inset: 0; border-radius: inherit; border: 1px solid rgba(47,49,56,.05); }
html.dark .bar::after { border-color: rgba(255,255,255,.06); }
/* column chart with a baseline, so an empty week looks empty on purpose */
.chart { display: flex; align-items: flex-end; gap: var(--s4); height: 132px; }
.chart > div { flex: 1; display: flex; flex-direction: column; justify-content: flex-end; align-items: center; height: 100%; gap: var(--s2); }
.chart .col { width: 100%; border-radius: var(--r-xs) var(--r-xs) 3px 3px; background: linear-gradient(180deg, var(--accent), color-mix(in srgb, var(--accent) 55%, white)); min-height: 4px; }
.chart .col.zero { background: rgba(47,49,56,.08); }
html.dark .chart .col.zero { background: rgba(255,255,255,.1); }
.chart .tick { font-size: var(--t-xs); color: var(--ink3); padding-top: 7px; border-top: 1px solid var(--line); width: 100%; text-align: center; }
/* page shells: content breathes and is centred, never hugging one corner */
.page { padding: var(--s8); max-width: 1080px; margin: 0 auto; width: 100%; }
.page-grid { display: grid; gap: var(--s8); }
/* The cards under the two-column row were a third child of that row, so they
   were packed into its first column and the column beside Routines stayed
   empty for the whole height of the page. They are their own row now, in the
   same proportions as the row above so the cards line up down the page, with
   the full-width list closing it off rather than leaving a ragged half-row. */
.home-grid { align-items: start; }
@media (min-width: 64rem) {
  .home-grid { grid-template-columns: 1.35fr 1fr; }
  .home-grid > .home-wide { grid-column: 1 / -1; }
}
@media (max-width: 700px) { .page { padding: var(--s6); } }

.swatch { width: 22px; height: 22px; border-radius: var(--r-full); border: 2px solid transparent; box-shadow: inset 0 0 0 1px rgba(47,49,56,.08); }
.swatch.on { border-color: var(--ink); }
input[type="color"] { -webkit-appearance: none; appearance: none; border: 0; padding: 0; background: none; cursor: pointer; }
input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
input[type="color"]::-webkit-color-swatch { border: 1px solid rgba(47,49,56,.12); border-radius: var(--r-full); }

:where(.seg-group) { display: inline-flex; }
.seg-group { gap: var(--s1); padding: var(--s1); background: rgba(0,0,0,.04); border-radius: var(--r-sm); }
.seg-group .seg { border-radius: var(--r-xs); padding: var(--s2) var(--s4); font-size: var(--t-md); font-weight: 600; color: var(--ink3); white-space: nowrap; }
.seg-group .seg:hover { color: var(--ink); }
.seg-group .seg.on { background: #fff; color: var(--ink); box-shadow: 0 1px 3px rgba(47,49,56,.1); }
html.dark .seg-group { background: rgba(255,255,255,.07); } html.dark .seg-group .seg.on { background: rgba(255,255,255,.16); color: #fff; }

/* onboarding: one card, four screens, centred and never taller than the phone */
.onboard { position: fixed; inset: 0; z-index: var(--z-onboard); display: grid; place-items: center; padding: var(--s7); pointer-events: none; }
.onboard-card { pointer-events: auto; width: min(460px, 100%); max-height: min(88vh, 720px); display: flex; flex-direction: column; background: rgba(255,255,255,.9); border: 1px solid rgba(255,255,255,.9); border-radius: var(--r); box-shadow: 0 24px 60px rgba(74, 58, 52,.25); overflow: hidden; }
html.dark .onboard-card { background: rgba(40,30,27,.94); border-color: var(--line); }
.onboard-dots { display: flex; align-items: center; gap: var(--s3); padding: var(--s5) var(--s6) 0; }
.onboard-dots .dot { width: 18px; height: 4px; border-radius: var(--r-full); background: var(--line); transition: background .2s ease; }
.onboard-dots .dot.on { background: var(--accent); }
.onboard-body { padding: var(--s4) var(--s8) var(--s2); text-align: center; overflow: auto; }
.onboard-art { display: grid; place-items: center; margin: 4px 0 6px; }
.onboard-title { font-size: var(--t-h); font-weight: 600; letter-spacing: -.01em; margin-bottom: var(--s3); }
.onboard-lede { color: var(--ink3); font-size: var(--t-lg); line-height: 1.55; max-width: 36ch; margin: 0 auto; }
.onboard-foot { display: flex; align-items: center; gap: var(--s4); padding: var(--s6) var(--s7) var(--s7); }
.onboard-habits { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s4); margin-top: var(--s6); text-align: left; }
.habit-pick { display: flex; align-items: center; gap: var(--s4); padding: var(--s4) var(--s5); border: 1px solid var(--line); border-radius: var(--r-md); font-size: var(--t-md); font-weight: 600; background: transparent; transition: border-color .15s ease, background .15s ease; }
.habit-pick.on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); }
.chip { border: 1px solid var(--line); border-radius: var(--r-full); padding: var(--s2) var(--s5); font-size: var(--t-sm); font-weight: 600; color: var(--ink3); }
.chip:hover { border-color: var(--accent); color: var(--ink); }
@media (max-width: 420px) { .onboard-habits { grid-template-columns: 1fr; } }

/* A pair of controls that share one field. Each input used to be width:100%
   in a flex row, so at 820 the end time and the next control printed on top
   of one another. The track states the split once. */
.field-pair { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: var(--s4); }
.field-pair > .input { width: 100%; min-width: 0; }

/* Field grids answer to the card they are in, not to the window. `sm:` and
   `md:` both fire at 820, where a 329px card is nowhere near wide enough for
   two columns — which is how two controls ended up sharing 155px. */
.card-fields { display: grid; gap: var(--s5); }
@container (min-width: 420px) { .card-fields { grid-template-columns: 1fr 1fr; } }
.card-fields .span-2 { grid-column: 1 / -1; }

/* Row affordances that only appear on hover must not hold the space while
   they are invisible: the Schedule button reserved 76px on every task row, so
   a phone title truncated 100px early with blank space beside it. */
.rowact { position: absolute; right: var(--s4); top: 50%; transform: translateY(-50%); opacity: 0; pointer-events: none; transition: opacity .15s; }
.group:hover .rowact, .group:focus-within .rowact { opacity: 1; pointer-events: auto; }
/* On touch there is no hover to reveal anything, and a permanently half-lit
   button on every row is noise. Small icon controls simply show; the task
   row's Schedule does not, because tapping the row opens the editor and its
   Schedule button is one tap away. Nothing becomes unreachable. */
@media (hover: none) {
  .group .opacity-0 { opacity: 1; }
  .rowact { display: none; }
}

/* Capacity. The bar is the free time you have; the fill is what the waiting
   work would take. Past the end it stops and the overflow is drawn beyond it,
   so a day that is twice over does not look like one that is merely full. */
.cap { margin-bottom: var(--s5); }
.cap-bar { position: relative; display: flex; height: 6px; border-radius: var(--r-full); background: rgba(0,0,0,.06); overflow: hidden; }
html.dark .cap-bar { background: rgba(255,255,255,.1); }
.cap-fill { height: 100%; border-radius: var(--r-full); background: var(--sage); }
.cap.over .cap-fill { border-radius: var(--r-full) 0 0 var(--r-full); }
.cap-over { height: 100%; background: #e0a08a; border-radius: 0 var(--r-full) var(--r-full) 0; box-shadow: inset 2px 0 0 rgba(255,255,255,.7); }
.cap.tight .cap-fill { background: #e6c47a; }

.cap-line { font-size: var(--t-sm); color: var(--ink); margin-top: var(--s3); line-height: 1.45; }
.cap.clear .cap-line, .cap.fits .cap-line { color: var(--ink3); }
.cap-risk { margin-top: var(--s4); padding-top: var(--s4); border-top: 1px solid var(--line); }
.cap-risk-row { display: flex; align-items: center; gap: var(--s4); width: 100%; padding: var(--s2) var(--s3); border-radius: var(--r-xs); font-size: var(--t-md); }
.cap-risk-row:hover { background: rgba(0,0,0,.04); }
html.dark .cap-risk-row:hover { background: rgba(255,255,255,.06); }

/* Buffer time. Drawn as a soft edge around the meeting rather than a bigger
   block, because the meeting is still half an hour — it is the minutes on
   either side that are spoken for. Under the events, never over them. */
.buffer { position: absolute; left: 3px; right: 3px; z-index: 1; background: color-mix(in srgb, var(--accent) 9%, transparent); pointer-events: none; }
html.dark .travel { background: color-mix(in srgb, var(--ink3) 10%, transparent); }
.buffer.before { border-radius: var(--r-xs) var(--r-xs) 0 0; }
.buffer.after { border-radius: 0 0 var(--r-xs) var(--r-xs); }
html.dark .buffer { background: color-mix(in srgb, var(--accent) 7%, transparent); }

/* Battle. The classic arrangement — them up and to the right, you down and to
   the left, a text box across the bottom — because it is the one layout
   everybody already knows how to read. */
.fighter-card { display: flex; gap: var(--s5); align-items: flex-start; }
.statgrid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s1) var(--s6); }
.stat-row { display: flex; align-items: center; gap: var(--s3); font-size: var(--t-md); }
.opp-grid { display: grid; gap: var(--s4); }
.opp { display: flex; align-items: center; gap: var(--s5); padding: var(--s5); border: 1px solid var(--line); border-radius: var(--r-md); transition: border-color .15s, background .15s; }
.opp:hover { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 6%, transparent); }
.opp-dot { width: 10px; height: 10px; border-radius: var(--r-full); flex-shrink: 0; }

.arena {
  position: relative; margin: var(--s6) auto; max-width: 780px; padding: var(--s7) var(--s7) var(--s6);
  display: grid; grid-template-rows: auto auto auto; gap: var(--s6);
  border-radius: var(--r); border: 1px solid var(--line);
  /* The ground the two of them are standing on, not a texture for its own sake. */
  background:
    radial-gradient(60% 40% at 78% 26%, color-mix(in srgb, var(--sage) 45%, transparent), transparent 70%),
    radial-gradient(64% 42% at 24% 66%, color-mix(in srgb, var(--accent) 22%, transparent), transparent 70%),
    var(--glass);
}
.side { display: flex; align-items: center; gap: var(--s6); }
.side.foe { justify-content: flex-end; }
.side.me { justify-content: flex-start; }
.nameplate { width: min(300px, 62%); background: rgba(255,255,255,.72); border: 1px solid var(--line); border-radius: var(--r-md); padding: var(--s4) var(--s5); }
html.dark .nameplate { background: rgba(255,255,255,.07); }
.sprite { display: grid; place-items: center; }
.foe-sprite { transform: scaleX(-1); }
.hpbar { height: 7px; border-radius: var(--r-full); background: rgba(0,0,0,.08); overflow: hidden; margin: var(--s3) 0 var(--s1); }
html.dark .hpbar { background: rgba(255,255,255,.12); }
.hpfill { display: block; width: 100%; height: 100%; border-radius: var(--r-full); background: var(--green); transform-origin: left center; transform: scaleX(var(--p, 1)); transition: transform .45s cubic-bezier(.2,.8,.2,1), background .3s; }
.hpfill.mid { background: #e6b45a; }
.hpfill.low { background: var(--accent); }
/* A hit should land. 320ms, small travel, and it stops. */
@keyframes btshake { 0%,100% { transform: translateX(0) } 20% { transform: translateX(-7px) } 45% { transform: translateX(6px) } 70% { transform: translateX(-3px) } }
.side.shake .sprite { animation: btshake .32s cubic-bezier(.36,.07,.19,.97); }

.battlebox { border-top: 1px solid var(--line); padding-top: var(--s5); }
.btlog { height: 86px; overflow: auto; font-size: var(--t-lg); line-height: 1.5; margin-bottom: var(--s5); scrollbar-width: thin; }
.btlog p { margin: 0 0 2px; }
.btlog p:last-child { font-weight: 600; }
.movegrid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s4); }
.movebtn { display: flex; flex-direction: column; align-items: flex-start; gap: 1px; text-align: left; padding: var(--s4) var(--s5); border-radius: var(--r-md); border: 1px solid color-mix(in srgb, var(--c) 45%, transparent); background: color-mix(in srgb, var(--c) 14%, transparent); transition: background .15s, transform .12s; }
.movebtn:hover:not(:disabled) { background: color-mix(in srgb, var(--c) 26%, transparent); }
.movebtn:active:not(:disabled) { transform: translateY(1px); }
.movebtn:disabled { opacity: .45; }
.mv-eff { font-size: var(--t-xs); font-weight: 600; border-radius: var(--r-full); padding: 0 6px; }
/* --green flips from a dark forest to a light mint between themes, so a
   literal white sits at 1.74:1 on the dark one. */
.mv-eff.strong { background: var(--green); color: #fff; } html.dark .mv-eff.strong { color: #21160f; }
.mv-eff.weak { background: rgba(0,0,0,.1); color: var(--ink3); } html.dark .mv-eff.weak { background: rgba(255,255,255,.12); }
@media (max-width: 700px) {
  .arena { margin: var(--s4) 0; padding: var(--s5); gap: var(--s5); }
  .nameplate { width: min(220px, 64%); }
  .side { gap: var(--s4); }
  .statgrid { grid-template-columns: 1fr; }
}
/* The opponent is drawn from the same creature, so it is tinted to its own
   type — otherwise you are fighting yourself in a different mood. */
.foe-sprite svg { filter: hue-rotate(0deg) drop-shadow(0 0 0 transparent); }
.foe-sprite { position: relative; }
.foe-sprite::after { content: ''; position: absolute; inset: 14%; border-radius: var(--r-full); background: var(--c); opacity: .5; mix-blend-mode: color; pointer-events: none; }

/* Done is a section under the open work, not a list you switch to. Shut by
   default: it is a record, not a queue. */
.donesec { margin-top: var(--s5); border-top: 1px solid var(--line); padding-top: var(--s3); }
.donehead { display: flex; align-items: center; gap: var(--s3); width: 100%; padding: var(--s3) var(--s4); border-radius: var(--r-xs); font-size: var(--t-xs); font-weight: 600; letter-spacing: .09em; text-transform: uppercase; color: var(--ink3); }
.donehead:hover { background: rgba(0,0,0,.04); color: var(--ink); }
html.dark .donehead:hover { background: rgba(255,255,255,.06); }
.chev { display: inline-flex; transition: transform .18s cubic-bezier(.2,.8,.2,1); }
.chev.open { transform: rotate(90deg); }
.task.is-done { opacity: .72; }
.task.is-done:hover { opacity: 1; }

/* Travel sits outside the buffer, because the journey wraps the whole trip. */
.travel { position: absolute; left: 0; right: 0; z-index: 0; background: color-mix(in srgb, var(--ink3) 11%, transparent); pointer-events: none; }

.place { display: flex; align-items: center; gap: var(--s3); margin-bottom: var(--s3); }

/* The booking page. One column, one decision. */
.bk { max-width: 560px; margin: 0 auto; padding: var(--s9) var(--s6) var(--s8); }
.bk-day { margin-top: var(--s6); }
.bk-times { display: grid; grid-template-columns: repeat(auto-fill, minmax(92px, 1fr)); gap: var(--s3); }
.bk-slot { padding: var(--s4) var(--s3); border: 1px solid var(--line); border-radius: var(--r-sm); font-weight: 600; font-size: var(--t-lg); transition: border-color .15s, background .15s; }
.bk-slot:hover { border-color: var(--accent); }
.bk-slot.on { border-color: var(--accent); background: var(--accent); color: var(--accent-ink); }
.bk-form { margin-top: var(--s7); padding-top: var(--s6); border-top: 1px solid var(--line); }
.bk-foot { text-align: center; color: var(--ink3); font-size: var(--t-xs); margin-top: var(--s7); }

/* Something is happening. An indeterminate line rather than a percentage,
   because twenty calendars answer at twenty different speeds and a bar that
   claims to know how far along it is would be making it up. */
.loadbar { position: absolute; top: 0; left: 0; right: 0; height: 2px; overflow: hidden; z-index: var(--z-pop); border-radius: var(--r) var(--r) 0 0; }
.loadbar::after { content: ''; position: absolute; inset: 0; width: 38%; border-radius: var(--r-full); background: linear-gradient(90deg, transparent, var(--accent), transparent); animation: loadslide 1.1s cubic-bezier(.45,.05,.55,.95) infinite; }
@keyframes loadslide { from { transform: translateX(-100%) } to { transform: translateX(300%) } }
@media (prefers-reduced-motion: reduce) { .loadbar::after { animation: none; width: 100%; opacity: .5; } }

/* Something is happening. An indeterminate line rather than a percentage,
   because twenty calendars answer at twenty different speeds and a bar that
   claimed to know how far along it was would be making it up. */
.loadbar { position: absolute; top: 0; left: 0; right: 0; height: 2px; overflow: hidden; z-index: var(--z-pop); border-radius: var(--r) var(--r) 0 0; pointer-events: none; background: color-mix(in srgb, var(--accent) 16%, transparent); }
.loadbar::after { content: ''; position: absolute; inset: 0; width: 38%; border-radius: var(--r-full); background: linear-gradient(90deg, transparent, var(--accent), transparent); animation: loadslide 1.1s cubic-bezier(.45,.05,.55,.95) infinite; }
@keyframes loadslide { from { transform: translateX(-100%) } to { transform: translateX(300%) } }
@media (prefers-reduced-motion: reduce) { .loadbar::after { animation: none; width: 100%; opacity: .45; } }

/* A meeting that crosses midnight is one meeting drawn in two places. Square
   off the edge where it carries on, so it reads as continuing rather than as
   two separate things that happen to touch the same line. */
.ev.runs-on { border-bottom-left-radius: 0; border-bottom-right-radius: 0; border-bottom-style: dashed; }
.ev.ran-on { border-top-left-radius: 0; border-top-right-radius: 0; border-top-style: dashed; }
.ev.ran-on .ev-title::before { content: '↳ '; opacity: .6; }

/* The companion is a pale filled drawing sitting among six stroked icons, so
   at tab size it was the one tab you could not see. The same ring it wears in
   the desktop rail gives it the weight the others get from their stroke. */
.tab.pet { background: color-mix(in srgb, var(--accent) 12%, transparent); border: 1px solid color-mix(in srgb, var(--accent) 22%, transparent); }
.tab.pet.active { background: color-mix(in srgb, var(--accent) 24%, transparent); border-color: color-mix(in srgb, var(--accent) 45%, transparent); }

/* Density. The base size lives here rather than as a Tailwind class on <body>,
   so the whole app moves with the scale. */
body { font-size: var(--t-md); line-height: var(--lh); }

/* The editor as a card beside what you clicked, not a panel down the side of
   the screen. A full-height sheet to change a start time is a lot of
   furniture for a small decision; Morgen opens next to the event and so does
   this. The sheet survives for phones, where there is nowhere to anchor to. */
.sheet.card {
  top: auto; right: auto; bottom: auto;
  width: 320px; height: auto; max-height: min(74vh, 620px);
  transform: translateY(-6px) scale(.985); opacity: 0;
  transition: transform .16s cubic-bezier(.2,.8,.2,1), opacity .12s;
  border-radius: var(--r-lg);
}
.sheet.card.open { transform: translateY(0) scale(1); opacity: 1; }
/* The inner wrapper is `h-full`, and a percentage height against a parent
   whose own height is auto-under-a-max-height resolves to auto — so with
   More options open the wrapper grew past the capped card and carried the
   footer out the bottom. Make the card the flex container and let the
   wrapper take the space that is actually there. */
.sheet.card { display: flex; flex-direction: column; }
.sheet.card > div { flex: 1 1 auto; min-height: 0; height: auto; }
.sheet-head { padding: var(--s5) var(--s5) 0; }
.sheet.card .sheet-head { padding: var(--s4) var(--s4) 0; }
.sheet.card .sheet-body { padding: var(--s3) var(--s4); }
.sheet.card .sheet-foot { padding: var(--s3) var(--s4) var(--s4); }
.sheet.card .sheet-title { font-size: var(--t-lg); }
.sheet-body { padding: var(--s4) var(--s5); }
.sheet-foot { padding: var(--s4) var(--s5) var(--s5); border-top: 1px solid var(--hair); flex-wrap: wrap; }
.sheet-title { font-size: var(--t-xl); font-weight: 600; letter-spacing: -.01em; }
.sheet.card .field { font-size: var(--t-xs); }
.sheet.card .input { padding: var(--s2) var(--s3); font-size: var(--t-sm); }
.sheet.card .sheet-body { overflow-x: hidden; min-height: 0; }
.sheet.card .btn-primary, .sheet.card .btn-ghost { padding: var(--s2) var(--s4); font-size: var(--t-sm); }
.moretoggle { display: flex; align-items: center; gap: var(--s2); align-self: flex-start; padding: var(--s2) 0; font-size: var(--t-xs); font-weight: 600; letter-spacing: .05em; text-transform: uppercase; color: var(--ink3); }
.moretoggle:hover { color: var(--ink); }
/* On a phone it stays the bottom sheet it was; there is nothing to sit beside. */
@media (max-width: 700px) {
  /* Same home-indicator clearance as the plain .sheet fallback just above —
     the card variant falls back to a bottom sheet here too, and inherited
     the same unguarded var(--s3) edges. */
  .sheet.card { left: max(var(--s3), env(safe-area-inset-left)) !important; right: max(var(--s3), env(safe-area-inset-right)); top: auto !important; bottom: calc(env(safe-area-inset-bottom) + var(--s3));
    width: auto; max-height: min(82vh, 640px) !important; transform: translateY(calc(100% + 20px)); }
  .sheet.card.open { transform: translateY(0); }
}
/* Behind a small anchored card the scrim only needs to catch the click that
   dismisses it. Dimming the whole calendar to change one start time makes the
   card feel like a modal, which is the thing it is not. */
.scrim.quiet { background: transparent; }

/* Animal collection: one selected companion beside a browsable roster. */
.collection-layout { display:grid; grid-template-columns:minmax(260px,320px) minmax(0,1fr); gap:32px; max-width:1280px; margin-inline:auto; align-items:start; }
.companion-detail { min-width:0; padding:0 8px 24px; }
.companion-stage { height:240px; display:grid; place-items:center; background:radial-gradient(ellipse at 50% 70%,color-mix(in srgb,var(--animal-color) 24%,transparent),transparent 65%); }
.companion-stage img { width:280px; height:280px; object-fit:contain; }
.companion-heading,.skill-heading,.collection-heading { display:flex; align-items:center; justify-content:space-between; gap:12px; }
.companion-heading h2 { font-size:var(--t-stat); font-weight:600; }
.collection-heading h2 { font-size:var(--t-h); font-weight:600; }
.collection-heading { margin-bottom:16px; flex-wrap:wrap; }
.training-progress { width:100%; height:6px; display:block; margin:14px 0 6px; border:0; border-radius:4px; overflow:hidden; background:var(--line); accent-color:var(--green); }
.training-progress::-webkit-progress-bar { background:var(--line); }
.training-progress::-webkit-progress-value { background:var(--green); }
.training-caption { font-size:var(--t-xs); margin:6px 0 12px; }
.companion-stats { display:flex; justify-content:space-between; gap:8px; padding:12px 0; margin-bottom:16px; border-block:1px solid var(--line); font-size:var(--t-xs); }
.companion-stats b { display:block; font-size:var(--t-stat-sm); }
.skill-heading h3 { font-size:var(--t-xl); font-weight:600; }
.skill-heading span { font-size:var(--t-xs); color:var(--green); }
.skill-row { display:flex; gap:8px; padding:12px 0; border-bottom:1px solid var(--line); align-items:center; }
.skill-row>div { flex:1; min-width:0; }
.skill-row b { font-size:var(--t-md); }
.skill-rank,.skill-row small { display:block; font-size:var(--t-xs); color:var(--ink2); }
.skill-upgrade { flex-shrink:0; min-height:40px; }
.encounter-cta { width:100%; justify-content:center; margin-top:16px; }
.animal-search { width:180px; }
.habitat-tabs { display:flex; gap:4px; flex-wrap:wrap; margin:0 0 16px; }
.habitat-tabs button { text-transform:capitalize; font-size:var(--t-xs); }
.habitat-tabs [aria-pressed=true] { background:var(--accent-soft); color:var(--accent-2); box-shadow:inset 0 0 0 1px var(--accent); }
.animal-grid { display:grid; grid-template-columns:repeat(6,minmax(0,1fr)); gap:8px; }
.animal-option { min-width:0; display:flex; flex-direction:column; align-items:center; padding:4px 3px 12px; border:1px solid transparent; border-radius:12px; transition:background .15s,border-color .15s; }
.animal-option:hover { background:var(--glass); }
.animal-option.selected { border-color:var(--green); background:color-mix(in srgb,var(--sage) 18%,transparent); }
.animal-option img { width:100%; height:auto; aspect-ratio:1; object-fit:contain; }
.animal-option b { font-size:var(--t-xs); line-height:1.3; }
/* Was 10px, a step below --t-xs. Nothing functional goes under that floor
   anywhere else in this file; this was the one label that did. */
.animal-option small { font-size:var(--t-xs); color:var(--ink2); }
.monster-section { grid-column:1/-1; grid-row:2; border-top:1px solid var(--line); padding-top:28px; }
.monster-grid { display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:16px; }
.monster-option { display:flex; flex-direction:column; align-items:center; text-align:center; padding:8px 12px 16px; border:1px solid var(--line); border-radius:12px; }
.monster-option img { height:152px; width:152px; max-width:100%; object-fit:contain; }
.monster-option.locked img { filter:saturate(.35); }
.monster-option h3 { font-size:var(--t-lg); font-weight:600; }
.monster-option p { font-size:var(--t-xs); margin:8px 0 12px; color:var(--ink2); }
.monster-option button { margin-top:auto; width:100%; justify-content:center; white-space:normal; font-size:var(--t-xs); }
.collection-friends { grid-column:1/-1; grid-row:3; }
.sprite img { object-fit:contain; width:220px; height:220px; max-width:100%; }
@media(max-width:1050px) { .animal-grid { grid-template-columns:repeat(4,minmax(0,1fr)); } }
@media(max-width:740px) { .collection-layout { grid-template-columns:minmax(0,1fr); gap:24px; padding:16px; } .animal-grid { grid-template-columns:repeat(3,minmax(0,1fr)); } .monster-section,.collection-friends { grid-column:auto; grid-row:auto; } .collection-friends { order:4; } .monster-grid { grid-template-columns:repeat(2,minmax(0,1fr)); gap:8px; } .companion-stage { height:180px; } .companion-stage img { width:220px; height:220px; } .sprite img { width:150px; height:150px; } .animal-option,.habitat-tabs button { min-height:44px; } }

/* Frosted glass, applied to a layer rather than to the surface itself.
   WebKit in the iOS app refused to composite any element carrying
   backdrop-filter — not just the blur, the element. Every glass surface went
   with it: the toolbar, the calendar, the tab bar, the onboarding card, all
   painting nothing, so the app opened as an empty warm rectangle with its
   whole DOM present and correct underneath. Turning the property off was the
   only thing that brought it back.

   On a ::before the worst case is a blur that does not arrive. The surface
   keeps its own translucent background and its contents either way, which is
   what a progressive enhancement is supposed to do, and what this was not. */
:where(.glass, .pop, .sheet, .dialog, .hud, .palette, .onboard-card) { position: relative; isolation: isolate; }
:where(.glass, .pop, .sheet, .dialog, .hud, .palette, .onboard-card)::before {
  content: ''; position: absolute; inset: 0; z-index: -1; border-radius: inherit; pointer-events: none;
  backdrop-filter: blur(var(--blur, 20px)) saturate(140%);
  -webkit-backdrop-filter: blur(var(--blur, 20px)) saturate(140%);
}
.pop, .dialog, .palette, .onboard-card { --blur: 28px; }
.sheet { --blur: 28px; }
.hud { --blur: 16px; }

/* Typing must not move the page. iOS zooms the whole view whenever a focused
   input is under 16px, and every field here is 12.5px, so tapping any of them
   threw the layout sideways and left it zoomed. The fix people reach for is
   user-scalable=no on the viewport, which also takes pinch-zoom away from
   anyone who needs it; 16px on a coarse pointer costs nothing but a little
   density on the one surface where density matters least. */
@media (pointer: coarse) {
  :where(.input, .field-row input, input, textarea, select) { font-size: 16px; }
  .sheet.card .input { font-size: 16px; }
}

/* No rubber-banding past the ends, and no page-level scroll behind the app:
   in a webview that reads as the whole interface coming loose. */
html, body { overscroll-behavior: none; -webkit-text-size-adjust: 100%; }
body { overflow: hidden; }
/* Momentum stays inside the panes that are meant to scroll. */
:where(.main, .left, .sheet-body, .pop, .dialog, .palette, .onboard-body) { -webkit-overflow-scrolling: touch; overscroll-behavior: contain; }

/* One account and everything under it, as one block. */
.calgroup { border: 1px solid var(--line); border-radius: var(--r-md); padding: var(--s4); margin-bottom: var(--s4); }
.calgroup-head { display: flex; align-items: center; gap: var(--s3); padding-bottom: var(--s3); margin-bottom: var(--s2); border-bottom: 1px solid var(--hair); }

/* A month is 42 cells and roughly four screens tall on a phone, so most of it
   is never looked at. content-visibility lets the browser skip rendering the
   cells that are off screen; the intrinsic size keeps the scrollbar honest so
   nothing jumps when they come into view. */
.monthcell { content-visibility: auto; contain-intrinsic-size: auto 96px; }

.world-controls { position:absolute; bottom:20px; right:20px; display:flex; gap:6px; flex-wrap:wrap; max-width:440px; padding:8px; border-radius:12px; background:var(--page); box-shadow:0 6px 24px #263c2c18; }
.world-controls .btn-ghost,.world-controls .btn-primary { min-height:40px; }
.world canvas { background:radial-gradient(ellipse at 50% 70%,#e4e5d9 0%,#f1f0e8 45%,#faf9f4 80%); }
.retreat-button { grid-column:1/-1; justify-self:end; }
.shop-page { max-width:1040px; margin:auto; }
.shop-intro { max-width:540px; margin:8px 0 28px; }
.shop-intro h2 { font-size:var(--t-h); font-weight:600; margin-bottom:8px; }
.shop-intro p { margin-bottom:8px; }
.coin-balance { background:color-mix(in srgb,var(--sage) 24%,transparent); color:var(--ink); padding:6px 12px; border-radius:16px; font-weight:600; margin-left:12px; }
.shop-grid { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:24px; }
.shop-item { min-width:0; display:flex; flex-direction:column; }
.shop-image { display:grid; place-items:center; background:color-mix(in srgb,var(--sage) 10%,var(--page)); border:1px solid var(--line); border-radius:12px; }
.shop-image img { width:100%; height:220px; object-fit:contain; }
.shop-item-title { display:flex; flex-wrap:wrap; gap:8px; justify-content:space-between; margin:16px 0 8px; }
.shop-item-title h3 { font-size:var(--t-lg); font-weight:600; }
.shop-item-title span { font-size:var(--t-xs); }
.shop-item p { color:var(--ink2); margin-bottom:16px; }
.shop-item button { margin-top:auto; min-height:44px; justify-content:center; }
@media(max-width:700px) { .world-controls { bottom:16px; left:12px; right:12px; max-width:none; justify-content:center; } .world-controls button { min-height:44px; } .shop-grid { grid-template-columns:repeat(2,minmax(0,1fr)); gap:20px 12px; } .shop-image img { height:160px; } .shop-item button { white-space:normal; } }

/* Morgen's task panel: sections you collapse, each with its mark, its name
   and its count, and a row to start a new one at the foot. */
.tasksec { margin-bottom: var(--s2); }
.sechead { display: flex; align-items: center; gap: var(--s3); width: 100%; padding: var(--s3) var(--s4); border-radius: var(--r-sm); font-size: var(--t-md); font-weight: 600; text-align: left; color: var(--ink); }
.sechead:hover { background: rgba(0,0,0,.035); }
html.dark .sechead:hover { background: rgba(255,255,255,.05); }
.secmark { display: inline-flex; align-items: center; justify-content: center; width: 16px; flex-shrink: 0; color: var(--ink3); }
.secmark .dot { width: 7px; height: 7px; border-radius: var(--r-full); border: 2px solid currentColor; }
/* The time sections carry their urgency in the mark, not in the row. */
.tone-late  { color: #c2483a; }
.tone-today { color: #d08133; }
.tone-soon  { color: var(--green); }
.tone-inbox { color: #5b7fb5; }
html.dark .tone-late { color: #f09083; } html.dark .tone-today { color: #e8b072; }
.seccount { flex-shrink: 0; min-width: 18px; padding: 0 var(--s2); border-radius: var(--r-full); background: rgba(0,0,0,.06); font-size: var(--t-xs); font-weight: 600; color: var(--ink2); text-align: center; }
html.dark .seccount { background: rgba(255,255,255,.09); }
.secempty { padding: var(--s2) var(--s4) var(--s4) 38px; font-size: var(--t-xs); color: var(--ink3); }
.newlist { display: flex; align-items: center; gap: var(--s3); width: 100%; padding: var(--s4); margin-top: var(--s2); border-radius: var(--r-sm); font-size: var(--t-md); color: var(--ink3); }
.newlist:hover { background: rgba(0,0,0,.035); color: var(--ink); }
/* The check button (20px) plus the row gap, so a subtask's title starts
   where its parent's does. 34px was that sum written out by hand, which is
   the one number on the page that belonged to no scale. */
.tasksec .task { padding-left: calc(var(--s5) + 20px + var(--s2)); }

/* The week number in the corner above the gutter, and the now line's own
   time beside it — both things Morgen puts there and the gutter had spare. */
.weekno { position: absolute; left: 0; top: 0; bottom: 0; display: flex; align-items: center; justify-content: center; font-size: var(--t-xs); font-weight: 600; color: var(--ink3); pointer-events: none; }
/* The hour gutter is 48px. "11:38pm" is 51px at --t-xs, and with a 6px
   inset it needs 57 — so between 10pm and 1am the leading digit was clipped
   by the pane's overflow and the line read 1:38pm at half past eleven. The
   hour labels above it already establish am from pm, so the marker drops the
   suffix and fits with room to spare. */
.nowtime { position: absolute; right: 4px; transform: translateY(-50%); z-index: 21; font-size: var(--t-xs); font-weight: 600; color: var(--accent-2); background: var(--glass); padding: 0 var(--s3); border-radius: var(--r-xs); white-space: nowrap; }

/* Morgen chips: the time on its own line above the title, the place under it
   in the same quiet ink, and the whole block tinted rather than outlined. */
.ev-where { font-size: var(--t-xs); color: color-mix(in srgb, var(--ink) 62%, transparent); line-height: 1.25; overflow: hidden; }
.ev-time { font-weight: 600; }
.ev { display: flex; flex-direction: column; gap: 1px; }
/* Not confirmed: Morgen dashes the border and leaves the fill pale. */
.ev.tentative { background: color-mix(in srgb, var(--c) 10%, white); border-style: dashed; border-width: 1px; }
html.dark .ev.tentative { background: color-mix(in srgb, var(--c) 16%, #1a1412); }
@container (max-height: 34px) { .ev .ev-where { display: none; } }

/* A blocked task keeps its checkbox on the calendar, so finishing something
   does not mean going back to the list to find it. */
.evtick { width: 12px; height: 12px; flex-shrink: 0; margin-right: 4px; border-radius: 3px; border: 1.5px solid color-mix(in srgb, var(--ink) 45%, transparent); vertical-align: -1px; display: inline-block; }
.evtick:hover { border-color: var(--ink); background: color-mix(in srgb, var(--ink) 12%, transparent); }
.ev-title { display: flex; align-items: baseline; }
/* The strip above the grid, named, the way Morgen names it. */
.allday-label { position: absolute; left: 0; bottom: 2px; width: 48px; text-align: center; font-size: var(--t-xs); color: var(--ink3); pointer-events: none; }

/* The nudge: blocks whose time has passed with the task still open. Morgen
   floats this at the foot of the calendar. It sits above the tab bar on a
   phone so it never covers the thing you would tap to get away from it. */
.nudge { position: fixed; left: 50%; transform: translateX(-50%); bottom: var(--s6); z-index: var(--z-sheet);
  display: flex; align-items: flex-start; gap: var(--s4); width: min(560px, calc(100vw - 24px));
  padding: var(--s4) var(--s5); border-radius: var(--r-lg); background: rgba(255,255,255,.94);
  border: 1px solid rgba(255,255,255,.9); box-shadow: 0 18px 50px rgba(74, 58, 52,.22); }
html.dark .nudge { background: rgba(38,29,26,.96); border-color: rgba(255,255,255,.08); }
.nudge-mark { flex-shrink: 0; width: 26px; height: 26px; opacity: .9; }
.nudge-said { font-size: var(--t-md); line-height: 1.4; }
.nudge-acts { display: flex; flex-wrap: wrap; gap: var(--s4); margin-top: var(--s3); }
.nudge-acts button { font-size: var(--t-sm); font-weight: 600; color: var(--accent-2); }
.nudge-acts button:hover { text-decoration: underline; }
.nudge-x { flex-shrink: 0; margin-left: auto; }
@media (max-width: 700px) {
  .nudge { bottom: calc(env(safe-area-inset-bottom) + 76px); left: var(--s3); right: var(--s3); width: auto; transform: none; }
}
.toastbar { position: fixed; left: 50%; transform: translateX(-50%); bottom: var(--s7); z-index: var(--z-toast);
  /* The toast is ink-on-page inverted, so its text has to be the page and
     not a literal white: --ink is #2f3138 in light and #f2ece9 in dark, so
     `color: #fff` gave white on a near-white pill at 1.17:1 — every
     confirmation the app makes was unreadable in dark mode. */
  padding: var(--s3) var(--s5); border-radius: var(--r-md); background: var(--ink); color: var(--page);
  font-size: var(--t-md); box-shadow: 0 8px 24px rgba(74,58,52,.22); max-width: calc(100vw - 24px); }
.toastbar.raised { bottom: 116px; }
@media (max-width: 700px) {
  .toastbar { bottom: calc(env(safe-area-inset-bottom) + 76px); }
  .toastbar.raised { bottom: calc(env(safe-area-inset-bottom) + 188px); }
}

/* The account row on the home page. */
.acct { grid-column: 1 / -1; }
.acct-row { display: flex; align-items: center; gap: var(--s5); flex-wrap: wrap; }
.acct-face { width: 38px; height: 38px; border-radius: var(--r-full); flex-shrink: 0; object-fit: cover; }
.acct-initial { display: inline-flex; align-items: center; justify-content: center; background: var(--accent-soft); color: var(--accent-2); font-weight: 600; font-size: var(--t-lg); }

/* An account and the calendars that came with it, as one block. */
.acctblock { border: 1px solid var(--line); border-radius: var(--r-md); padding: var(--s4); margin-bottom: var(--s4); }
.acctblock-head { display: flex; align-items: center; gap: var(--s4); padding-bottom: var(--s3); border-bottom: 1px solid var(--hair); margin-bottom: var(--s2); }
.acctblock-mark { flex-shrink: 0; width: 26px; height: 26px; border-radius: var(--r-full); display: inline-flex; align-items: center; justify-content: center; font-size: var(--t-sm); font-weight: 600; }
.acctblock-mark.is-google { background: var(--accent-soft); color: var(--accent-2); }
.acctblock-mark.is-local { background: color-mix(in srgb, var(--sage) 30%, transparent); color: var(--green-2); }
.acctblock-mark.is-morgen { background: color-mix(in srgb, #b9a7e0 32%, transparent); color: #6b52a8; }
html.dark .acctblock-mark.is-morgen { color: #d4c3ee; }
.acctblock-lists { margin-top: var(--s3); padding-top: var(--s3); border-top: 1px solid var(--hair); }
.calrow { display: flex; align-items: center; gap: var(--s4); padding: var(--s2) 0; }
.calswatch { width: 18px; height: 18px; flex-shrink: 0; border: 0; border-radius: var(--r-full); background: transparent; padding: 0; }
.calbadge { flex-shrink: 0; font-size: var(--t-xs); color: var(--ink2); background: rgba(0,0,0,.06); border-radius: var(--r-xs); padding: 0 var(--s2); } html.dark .calbadge { background: rgba(255,255,255,.09); }
.calrow-def { display: flex; align-items: center; gap: var(--s2); font-size: var(--t-xs); color: var(--ink2); flex-shrink: 0; }

/* Ask: a conversation that leaves the app changed behind it. */
.askintro { max-width: 520px; margin: var(--s8) auto; text-align: center; color: var(--ink2); font-size: var(--t-md); }
.askverbs { display: grid; gap: var(--s2); margin-top: var(--s6); text-align: left; }
.askverb { display: flex; gap: var(--s4); align-items: baseline; padding: var(--s3) var(--s4); border-radius: var(--r-sm); font-size: var(--t-sm); }
.askverb:hover { background: rgba(0,0,0,.04); }
.askverb code { color: var(--accent-2); font-weight: 600; white-space: nowrap; }
.askverb span { color: var(--ink3); min-width: 0; }
/* One column for the conversation, with both speakers inside it.
   .askmsg capped at 640px and centred, while .you overrode that with 80% of
   the pane, gave the two sides different column widths: on a 1440px screen
   the assistant sat in a 640px block and a one-word "ok" became a 1450px
   band running nearly edge to edge. The wrapper holds the column, and each
   bubble is only as wide as what is in it. */
/* The column is the scroller's own padding, not a rule on its children: a
   `max-width` on the children loses to .askmsg's own, and a wrapper element
   would have to go inside the thing that scrolls. */
.asklog { padding-inline: max(var(--s5), calc((100% - 680px) / 2)); }
.askmsg { width: fit-content; max-width: 92%; margin: 0 0 var(--s4); padding: var(--s4) var(--s5); border-radius: var(--r-md); font-size: var(--t-md); line-height: 1.5; }
.askmsg.you { background: var(--accent-soft); color: var(--ink); margin-left: auto; margin-right: 0; max-width: 85%; }
.askmsg.claude { background: rgba(0,0,0,.035); }
html.dark .askmsg.claude { background: rgba(255,255,255,.05); }
.askdid { margin-top: var(--s3); display: grid; gap: var(--s2); }
.askdid li { display: flex; align-items: center; gap: var(--s3); font-size: var(--t-sm); color: var(--green-2); }
.askdid.refused li { color: #c2483a; }
.askdid code { color: inherit; }
.askbar { display: flex; gap: var(--s4); align-items: center; padding: var(--s4) var(--s5); border-top: 1px solid var(--line); }

/* Ask, inline in the tasks panel. */
.askline.busy { opacity: .7; }
.asklast { margin: var(--s2) var(--s4) 0; font-size: var(--t-xs); color: var(--ink2); line-height: 1.4; }

/* Ask about one task, from its own row. */
.taskask { position: relative; flex-shrink: 0; }
.taskask-menu { position: absolute; right: 0; top: calc(100% + 4px); z-index: var(--z-pop); width: 210px;
  background: rgba(255,255,255,.97); border: 1px solid var(--line); border-radius: var(--r-md);
  box-shadow: 0 14px 36px rgba(74,58,52,.18); padding: var(--s2); }
html.dark .taskask-menu { background: rgba(38,29,26,.98); }
.taskask-of { padding: var(--s3) var(--s4) var(--s2); font-size: var(--t-xs); color: var(--ink3); border-bottom: 1px solid var(--hair); margin-bottom: var(--s2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.taskask-menu button { display: block; width: 100%; text-align: left; padding: var(--s3) var(--s4); border-radius: var(--r-sm); font-size: var(--t-sm); }
.taskask-menu button:hover { background: var(--accent-soft); color: var(--accent-2); }
@media (pointer: coarse) { .taskask-menu button { min-height: 44px; } }

/* Streak pips. The ring is what makes the shape readable: a moss fill is
   1.71:1 on a white card, which is the area's colour doing its job and not
   something to darken, so the edge carries the legibility instead. */
.pip { width: 8px; height: 8px; border-radius: var(--r-full); box-sizing: border-box; }
.pip.due { box-shadow: inset 0 0 0 1.5px var(--mark); }
.pip.done { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--mark) 70%, transparent); }

.txt-bad { color: var(--bad); }
.txt-warn { color: var(--warn); }

/* "+2 more" in a month cell. It was a <p>: the one place a person looks when
   they want the events the cell could not fit, and it did nothing. The day
   number beside it has always opened the day; this says so too. */
.morelink { display: block; width: 100%; text-align: left; padding: 0 var(--s2); margin-top: 1px;
  font-size: var(--t-xs); color: var(--ink3); border-radius: var(--r-xs); }
.morelink:hover { color: var(--ink); background: rgba(0,0,0,.05); }
html.dark .morelink:hover { background: rgba(255,255,255,.08); }

/* The pinned mini month in the left pane. Sized to the 300px pane with the
   same hairlines as everything else: it is a way to move around, not a
   second calendar competing with the one beside it. */
.minimonth { padding: var(--s5) var(--s5) var(--s4); border-bottom: 1px solid var(--line); }
.minimonth-head { display: flex; align-items: center; gap: var(--s2); margin-bottom: var(--s3); }
.minimonth-dow, .minimonth-grid { display: grid; grid-template-columns: repeat(7, 1fr); }
.minimonth-dow { font-size: var(--t-xs); color: var(--ink3); text-align: center; margin-bottom: 2px; }
/* Fixed height rather than a square: at 300px the pane gives each column
   ~38px, and six rows of squares made the month taller than the task list
   under it. 26px is a readable row and keeps the whole month under 180px. */
.minicell { position: relative; height: 26px; display: flex; align-items: center; justify-content: center;
  font-size: var(--t-xs); border-radius: var(--r-full); color: var(--ink); }
/* 26px is a fine row to read and far too small to hit. On a touch screen
   every one of these 42 cells is a tap target, so they take the same 44px
   floor as every other control. devices.mjs caught this the moment the mini
   month landed: 39x26 on an iPad in landscape. */
@media (pointer: coarse) { .minicell { height: 44px; } }
/* Seven 44px cells need 308px of content, and the pane gives 300 minus its
   padding — so the cells came out 39 wide however tall they were. On a
   tablet the pane widens enough to fit them. Scoped above the phone
   breakpoint, where .left is a bottom sheet and sizes itself. */
@media (pointer: coarse) and (min-width: 701px) { .left { flex: 0 0 348px; } }
.minicell:hover { background: rgba(0,0,0,.05); }
html.dark .minicell:hover { background: rgba(255,255,255,.08); }
.minicell.out { color: var(--ink3); }
.minicell.on { background: color-mix(in srgb, var(--accent) 16%, transparent); font-weight: 600; }
.minicell.today { background: var(--accent); color: var(--accent-ink); font-weight: 600; }
.minidot { position: absolute; bottom: 2px; left: 50%; transform: translateX(-50%);
  width: 3px; height: 3px; border-radius: var(--r-full); background: var(--accent); }
.minicell.today .minidot { background: var(--accent-ink); }

/* Calendars in the side pane, under the mini month. */
/* flex: 0 0 auto, not the default shrink. As a shrinkable row in the pane it
   lost height to the task list, so with two accounts connected the third
   group was pushed out of a box that was already shorter than its own
   content. It caps at 34vh and scrolls inside that; it does not get squeezed
   below it. */
.sidecals { flex: 0 0 auto; padding: var(--s4) var(--s5); border-bottom: 1px solid var(--line); max-height: 34vh; overflow: auto; }
.sidecals-head { display: flex; align-items: center; gap: var(--s2); width: 100%; margin-bottom: var(--s2); }
.sidecal { display: flex; align-items: center; gap: var(--s3); padding: var(--s1) var(--s2); border-radius: var(--r-xs);
  font-size: var(--t-sm); color: var(--ink); cursor: pointer; min-height: 26px; }
.sidecal:hover { background: rgba(0,0,0,.04); }
html.dark .sidecal:hover { background: rgba(255,255,255,.07); }
/* The row is the control, not a 13px checkbox inside it — which was both an
   impossible tap target and not how a calendar list reads. The chip shows
   the colour the calendar draws in, filled when it is on and hollow when it
   is not, so the state is the same mark as the events it governs. */
.calchip { flex: none; width: 13px; height: 13px; border-radius: 4px; background: var(--c);
  box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--c) 70%, black); }
.calchip.off { background: transparent; }
.sidecal .is-off { color: var(--ink3); }
@media (pointer: coarse) { .sidecal { min-height: 44px; } .sidecals-head { min-height: 44px; } }
