/* ==========================================================================
   Coordin8 — components layered on top of the base visual system.
   Same tokens, same warm/off-white language as styles.css.
   ========================================================================== */

/* --- Month cells ----------------------------------------------------------
   Two calendars ask two different questions, so they use two different
   scales. Your own day asks "how much of this day is still mine?"; a group
   day asks "how many of us are free?". Sharing one palette between them was
   what made both of them hard to read.                                     */

/* One person's day: green while it is yours, fading to neutral as it fills. */
:root{
  /* Monotonic lightness, darkest = most of the day is still yours.
     L = 0.66 → 0.76 → 0.84 → 0.89. Free and Busy used to sit 0.8% apart and
     were told apart by hue alone, which failed on a glance and failed
     completely for red-green colour blindness. */
  --d-free:#b0d8c0;   --d-free-line:#8bc3a2;
  --d-most:#cbe5d6;   --d-most-line:#a9d3bd;
  --d-some:#e2ebe0;   --d-some-line:#cbd8c8;
  --d-busy:#f3f1eb;   --d-busy-line:#ddd7cc;
  --d-unset-line:#dcd6cb;
}
.month-day{aspect-ratio:1;min-height:0;justify-content:flex-start;gap:3px;padding:5px 5px var(--s1);
  transition:background .12s ease}
.month-day .day-num{font-size:var(--t-label)}
.month-day.free{background:var(--d-free);border-color:var(--d-free-line)}
.month-day.most{background:var(--d-most);border-color:var(--d-most-line)}
.month-day.some{background:var(--d-some);border-color:var(--d-some-line)}
.month-day.busy{background:var(--d-busy);border-color:var(--d-busy-line)}
.month-day.busy .day-num{color:#7d7970}
.month-day.unset{background:#fff;border-style:dashed;border-color:var(--d-unset-line)}
.month-day.other{background:transparent}
.month-day.today{box-shadow:none;border-color:var(--ink)}
.month-day.today .day-num{background:var(--ink);color:#fff;border-radius:999px;
  min-width:17px;height:17px;line-height:17px;text-align:center;padding:0 4px;margin:-1px 0 0 -1px}

/* a hint that something is on, not what it is */
.day-dots{display:flex;gap:3px;margin-top:auto;padding-bottom:1px}
.day-dots i{width:4px;height:4px;border-radius:50%;background:#8b877e;display:block}
.day-dots i.plan{background:var(--green)}
.month-day.busy .day-dots i{background:#9a958b}

/* A group's day: empty, then blue, then blue-green, then the brand green
   when it is everyone. */
.month-day.lvl0{background:#fff;border-color:#e8e3da}
.month-day.lvl1{background:#e4eff6;border-color:#cfe2ee}
.month-day.lvl2{background:#c2dce6;border-color:#a6cbdb}
.month-day.lvl3{background:#a0c9c7;border-color:#84b6b4}
.month-day.lvl4{background:#74b394;border-color:#5c9d7d}
.month-day.lvl4 .day-num{color:#12130f}
.month-day.lvl5{background:var(--green);border-color:var(--green)}
.month-day.lvl5 .day-num{color:#fff}
.month-day.lvl0 .day-num{color:#b4b0a8}

.cal-legend{gap:var(--s3);font-size:var(--t-label)}
.cal-legend .lg-free:before{background:var(--d-free);border-color:var(--d-free-line)}
.cal-legend .lg-most:before{background:var(--d-most);border-color:var(--d-most-line)}
.cal-legend .lg-some:before{background:var(--d-some);border-color:var(--d-some-line)}
.cal-legend .lg-busy:before{background:var(--d-busy);border-color:var(--d-busy-line)}
.cal-legend .lg-unset:before{background:#fff;border-style:dashed;border-color:var(--d-unset-line)}
.cal-legend .lg-none:before{background:#fff;border-color:#e8e3da}
.cal-legend .lg-few:before{background:#c2dce6;border-color:#a6cbdb}
.cal-legend .lg-mostppl:before{background:#74b394;border-color:#5c9d7d}
.cal-legend .lg-all:before{background:var(--green);border-color:var(--green)}

/* mini calendars carry the same group scale */
.mini-day.lvl0{background:#fff;border-color:#eeeae3}
.mini-day.lvl1{background:#e4eff6;border-color:#d6e6f0}
.mini-day.lvl2{background:#c2dce6;border-color:#b0d2df}
.mini-day.lvl3{background:#a0c9c7;border-color:#90bfbd}
.mini-day.lvl4{background:#74b394;border-color:#67a888}
.mini-day.lvl5{background:var(--green);border-color:var(--green)}

/* --- Week grid ------------------------------------------------------------- */
.week-grid{border:1px solid var(--line);border-radius:var(--r-control);overflow:hidden;background:#fff}
.wk-head{display:grid;grid-template-columns:38px repeat(7,1fr);background:var(--paper);border-bottom:1px solid var(--line)}
.wk-gutter{font-size:var(--t-label);color:var(--muted2);font-weight:var(--w-med);display:grid;place-items:center;text-align:center;line-height:1.2}
.wk-day{border:0;border-left:1px solid var(--line);background:transparent;padding:var(--s2) 2px;cursor:pointer;display:flex;flex-direction:column;align-items:center;gap:2px}
.wk-day span{font-size:var(--t-dense);font-weight:var(--w-bold);color:var(--muted2);letter-spacing:.04em}
.wk-day b{font-size:var(--t-meta);letter-spacing:-.03em;width:24px;height:24px;border-radius:50%;display:grid;place-items:center}
.wk-day.today b{background:var(--ink);color:#fff}
.wk-day.available b{background:var(--green-soft);color:var(--green)}
.wk-day.today.available b{background:var(--ink);color:#fff}
.wk-day.busy span{color:#b4b1aa}
.wk-allday{display:grid;grid-template-columns:38px repeat(7,1fr);border-bottom:1px solid var(--line);background:#fdfcfa;min-height:26px}
.wk-allday .wk-col{border-left:1px solid var(--line);padding:3px}
.wk-allday-chip{display:block;font-size:var(--t-dense);font-weight:var(--w-bold);background:var(--gray-soft);border:1px solid var(--line);border-radius:4px;padding:2px 3px;margin-bottom:2px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
.wk-allday-chip.plan{background:var(--green-soft);border-color:#cfe0d4;color:var(--green)}
.wk-body{display:grid;grid-template-columns:38px repeat(7,1fr);max-height:452px;overflow-y:auto;position:relative}
.wk-gutter-col{position:sticky;left:0;background:#fff;z-index:2}
.wk-hour-label{height:40px;font-size:var(--t-dense);color:var(--muted2);font-weight:var(--w-bold);padding:2px 0 0 5px;border-top:1px solid #f2efe8}
.wk-col{position:relative;border-left:1px solid var(--line)}
.wk-slot{appearance:none;-webkit-appearance:none;display:block;width:100%;height:40px;border:0;border-top:1px solid #f2efe8;
  border-radius:0;background:transparent;cursor:pointer;padding:0;margin:0;font:inherit}
.wk-slot:hover{background:#f7f5f0}
.wk-block{position:absolute;left:2px;right:2px;border-radius:6px;border:1px solid var(--line2);background:var(--gray-soft);
  padding:2px 3px;text-align:left;overflow:hidden;cursor:pointer;color:#4d4b46;z-index:1;
  appearance:none;-webkit-appearance:none;font-family:inherit}
.wk-block b{display:block;font-size:var(--t-dense);font-weight:var(--w-bold);line-height:1.15;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.wk-block i{display:block;font-style:normal;font-size:var(--t-dense);color:var(--muted);margin-top:1px}
.wk-block.plan{background:var(--green-soft);border-color:#cfe0d4;color:var(--green)}
.wk-block.plan i{color:#4a7a5f}

/* --- Auth ------------------------------------------------------------------ */
.auth-wrap{min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:var(--s5) var(--s4) 40px;gap:var(--s4)}
.auth-card{width:100%;max-width:400px;background:var(--card);border:1px solid var(--line);border-radius:var(--r-xl);padding:var(--s5) var(--s5) 20px;box-shadow:var(--shadow)}
.auth-brand{display:flex;align-items:center;gap:var(--s2);margin-bottom:var(--s5)}
.auth-card h1{margin:0;font-size:var(--t-title);line-height:1.05;letter-spacing:-.045em}
.auth-sub{margin:var(--s2) 0 var(--s5);font-size:var(--t-label);line-height:1.5;color:var(--muted)}
.auth-actions{display:flex;flex-direction:column;gap:8px;margin-bottom:var(--s2)}
.auth-foot{font-size:var(--t-label);color:var(--muted2);text-align:center;max-width:360px;line-height:1.5;margin:0}
.handle-input{display:flex;align-items:center;gap:0;border:1px solid var(--line2);border-radius:var(--r-control);background:#fff;padding-left:var(--s3)}
.handle-input>span{font-size:var(--t-label);font-weight:var(--w-med);color:var(--muted2)}
.handle-input .input{border:0;background:transparent;padding-left:3px}
.handle-status{font-size:var(--t-label);color:var(--muted);margin:-4px 2px var(--s2);font-weight:var(--w-med)}
.handle-status.ok{color:var(--green)}
.handle-status.bad{color:var(--danger)}
.my-handle{display:flex;align-items:center;justify-content:space-between;gap:var(--s2);padding:var(--s3);border:1px solid var(--line);
  border-radius:var(--r-control);background:var(--warm);margin-bottom:var(--s4)}
.my-handle span{display:block;font-size:var(--t-label);letter-spacing:.12em;color:#85837d;font-weight:var(--w-med);margin-bottom:4px}
.my-handle strong{font-size:var(--t-meta);letter-spacing:-.02em}
.request-banner{display:flex;align-items:center;gap:var(--s3);padding:var(--s3);margin-bottom:12px;width:100%;text-align:left;cursor:pointer;border:1px solid var(--line2);background:var(--paper)}
.request-banner strong{display:block;font-size:var(--t-label)}
.request-banner span{font-size:var(--t-label);color:var(--muted)}
.request-banner div:nth-child(2){flex:1;min-width:0}
.request-banner .avatars{flex:0 0 auto}

/* --- shared small pieces ------------------------------------------------- */
.muted-note{font-size:var(--t-label);color:var(--muted);line-height:1.45;margin:8px 2px 0}
.row-between{display:flex;align-items:center;justify-content:space-between;gap:var(--s2)}
.stack-sm>*+*{margin-top:var(--s2)}
.badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:var(--s1) var(--s2);font-size:var(--t-label);font-weight:var(--w-med);border:1px solid var(--line);background:var(--warm);color:#5d5b55}
.badge.deal{background:#f4efe2;border-color:#e6dcc4;color:#8a6d24}
.badge.free{background:var(--green-soft);border-color:#d4e3d8;color:var(--green)}
.badge.hot{background:var(--danger-soft);border-color:#efd5d0;color:var(--danger)}
.est-note{font-size:var(--t-label);color:var(--muted2);margin-top:var(--s2);line-height:1.4}
.divider{height:1px;background:var(--line);margin:14px 0}

/* --- You: Outlook-style personal day detail ----------------------------- */
.day-summary{display:flex;gap:var(--s2);flex-wrap:wrap;margin-bottom:12px}
.allday-area{border:1px solid var(--line);border-radius:var(--r-control);background:#fff;padding:var(--s2) var(--s2);margin-bottom:var(--s2)}
.allday-area h4{margin:0 0 var(--s2);font-size:var(--t-label);letter-spacing:.12em;color:#85837d;font-weight:var(--w-med)}
.allday-chip{display:block;width:100%;text-align:left;font:inherit;color:inherit;cursor:pointer;
  border-radius:9px;padding:var(--s2) var(--s2);font-size:var(--t-label);font-weight:var(--w-med);background:var(--gray-soft);border:1px solid var(--line);margin-top:5px}
.allday-chip:first-of-type{margin-top:0}
.allday-chip.plan{background:var(--green-soft);border-color:#d4e3d8;color:var(--green)}
.time-grid{position:relative;border:1px solid var(--line);border-radius:var(--r-control);background:#fff;overflow:hidden}
.hour-rows{position:relative}
/* The row is 44px including its border, which left the tappable slot inside it
   at 43 — one pixel under the target, on the control you use most on this
   screen. */
.hour-row{display:grid;grid-template-columns:52px 1fr;height:45px;border-top:1px solid #f0ece4}
.hour-row .hr-slot{min-height:44px}
.hour-row:first-child{border-top:0}
.hour-row .hr-label{font-size:var(--t-label);color:var(--muted2);font-weight:var(--w-med);padding:3px 0 0 9px}
/* the slot is a button so an empty hour is tappable — strip the native chrome */
.hour-row .hr-slot{appearance:none;-webkit-appearance:none;width:100%;height:100%;padding:0;margin:0;
  background:transparent;border:0;border-left:1px solid #f0ece4;border-radius:0;cursor:pointer;font:inherit}
.hour-row .hr-slot:hover{background:#faf8f4}
.blocks-layer{position:absolute;left:52px;right:8px;top:0;bottom:0;pointer-events:none}
.tblock{position:absolute;left:0;right:0;border-radius:var(--r-control);padding:5px var(--s2);font-size:var(--t-label);line-height:1.25;overflow:hidden;
  pointer-events:auto;background:var(--gray-soft);border:1px solid var(--line2);color:#4d4b46;
  appearance:none;-webkit-appearance:none;text-align:left;font-family:inherit;cursor:pointer;display:block}
.tblock strong{display:block;font-size:var(--t-label);font-weight:var(--w-med)}
.tblock span{display:block;font-size:var(--t-label);color:var(--muted);margin-top:2px}
.tblock.plan{background:var(--green-soft);border-color:#cfe0d4;color:var(--green)}
.tblock.plan span{color:#4a7a5f}
.tblock.tiny{padding:0 7px;display:flex;align-items:center;line-height:1}
.tblock.tiny strong{font-size:var(--t-label);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  display:block;width:100%;line-height:1.1}
.tblock.narrow{right:52%}
.tblock.narrow2{left:50%}
.now-line{position:absolute;left:0;right:0;height:1px;background:var(--danger);opacity:.55}
.now-line:before{content:"";position:absolute;left:-4px;top:-3px;width:7px;height:7px;border-radius:50%;background:var(--danger)}
.free-window{display:flex;flex-wrap:wrap;gap:var(--s2);margin-top:var(--s2)}
.free-window span{font-size:var(--t-label);font-weight:var(--w-med);color:var(--green);background:var(--green-soft);border:1px solid #d4e3d8;border-radius:999px;padding:var(--s2) 8px}

/* --- Friends: group day availability ------------------------------------ */
.count-hero{display:flex;align-items:baseline;gap:8px;margin-bottom:4px}
.count-hero b{font-size:var(--t-title);letter-spacing:-.04em}
.count-hero span{font-size:var(--t-label);color:var(--muted)}
.window-count{display:flex;align-items:center;justify-content:space-between;gap:var(--s2);padding:var(--s2) var(--s3);border-radius:var(--r-control);background:var(--green-soft);border:1px solid #d4e3d8;margin:var(--s2) 0 0}
.window-count strong{font-size:var(--t-label);color:var(--green)}
.window-count span{font-size:var(--t-label);color:#4a7a5f;display:block;margin-top:2px}
.privacy-note{font-size:var(--t-label);color:var(--muted2);line-height:1.45;margin:var(--s2) 2px 0}
.busy-range{font-size:var(--t-label);color:var(--muted);display:block;margin-top:2px}
.live-count{padding:var(--s2) var(--s2);border-radius:12px;background:var(--green-soft);border:1px solid #d4e3d8;font-size:var(--t-label);font-weight:var(--w-med);color:var(--green);margin:2px 0 12px;text-align:center}
.live-count.low{background:var(--gray-soft);border-color:var(--line2);color:#6b6963}
/* The count sits beside Select all rather than above it: two full-width bars
   stacked pushed the first friend below the fold on a small phone, and the
   count is a glance, not a paragraph. The count keeps the flexible width so
   the button never wraps mid-word. */
.live-count-row{display:flex;align-items:center;gap:8px;margin:2px 0 12px}
.live-count-row .live-count{flex:1;margin:0}
.link-btn{flex:none;min-height:44px;padding:0 12px;border:1px solid var(--line2);border-radius:12px;
  background:var(--paper);color:var(--ink);font-size:var(--t-label);font-weight:var(--w-med);
  cursor:pointer;white-space:nowrap}
.link-btn:hover{border-color:#b9b2a6}
.link-btn:active{background:var(--warm)}

/* --- Favourites --------------------------------------------------------- */
.star{border:0;background:transparent;color:var(--muted2);font-size:var(--t-body);line-height:1;padding:5px;cursor:pointer;flex:0 0 auto}
.star:hover{color:#c7a63f}
.star.on{color:#d6a72a}
.star.big{font-size:var(--t-title);padding:8px}
.star-inline{color:#d6a72a;font-size:var(--t-label)}
.title-row{display:flex;align-items:flex-start;justify-content:space-between;gap:8px}
.friend-row .check{background:#fff;cursor:pointer}
.friend-row.selected .check{background:var(--green);border-color:var(--green);color:#fff}

/* --- A friend's calendar ------------------------------------------------- */
.friend-hero{display:flex;align-items:center;gap:12px;margin-bottom:var(--s4)}
.friend-hero .avatar{width:52px;height:52px;font-size:var(--t-body);margin:0}
.friend-hero h3{margin:0;font-size:var(--t-title);letter-spacing:-.035em}
.friend-hero p{margin:4px 0 0;font-size:var(--t-label);color:var(--muted)}
.friend-hero div{flex:1;min-width:0}
.block-list{padding:var(--s2) 12px}
.block-row{display:flex;align-items:center;gap:var(--s2);padding:var(--s3) 2px;border-top:1px solid var(--line)}
.block-row:first-child{border-top:0}
.block-row strong{font-size:var(--t-label);flex:1}
.block-row span:last-child{font-size:var(--t-label);font-weight:var(--w-med);letter-spacing:.06em;text-transform:uppercase}
.block-dot{width:9px;height:9px;border-radius:3px;flex:0 0 auto}
.block-row.open .block-dot{background:var(--green-soft);border:1px solid #cfe0d4}
.block-row.open span:last-child{color:var(--green)}
.block-row.closed .block-dot{background:var(--gray-mid);border:1px solid var(--line2)}
.block-row.closed span:last-child{color:var(--muted)}

/* --- Connected calendars -------------------------------------------------- */
.account-card{padding:var(--s4);margin-bottom:8px}
.account-top{display:flex;align-items:center;gap:var(--s3)}
.account-icon{width:40px;height:40px;border-radius:var(--r-control);background:var(--warm);border:1px solid var(--line);display:grid;place-items:center;font-size:var(--t-card);flex:0 0 auto}
.account-top div{flex:1;min-width:0}
.account-top strong{display:block;font-size:var(--t-label)}
.account-top span{display:block;font-size:var(--t-label);color:var(--muted);margin-top:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.sub-cal{display:flex;align-items:center;justify-content:space-between;gap:var(--s2);padding:var(--s2) 2px;border-top:1px solid var(--line);font-size:var(--t-label)}
.sub-cal:first-of-type{margin-top:var(--s2)}
.switch{width:38px;height:22px;border-radius:999px;border:1px solid var(--line2);background:var(--gray-soft);position:relative;cursor:pointer;flex:0 0 auto;transition:.15s ease}
.switch:after{content:"";position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:#fff;box-shadow:0 1px 3px rgba(40,35,28,.25);transition:.15s ease}
.switch.on{background:var(--green);border-color:var(--green)}
.switch.on:after{left:18px}
.scope-note{margin:var(--s2) 0 0;padding:var(--s2) var(--s3);border-radius:12px;background:var(--warm);border:1px solid var(--line);font-size:var(--t-label);color:#6b6960;line-height:1.5}
.scope-note b{display:block;font-size:var(--t-label);letter-spacing:.1em;color:#85837d;margin-bottom:5px}
.src-tag{font-size:var(--t-dense);font-weight:var(--w-bold);letter-spacing:.05em;color:var(--muted2);border:1px solid var(--line);border-radius:999px;padding:2px 5px;margin-left:var(--s2);vertical-align:1px}

/* --- Editable settings + profile ---------------------------------------- */
button.setting-row{width:100%;background:transparent;border:0;border-top:1px solid var(--line);text-align:left;cursor:pointer;font:inherit;color:inherit}
button.setting-row:first-child{border-top:0}
button.setting-row:hover{background:#faf8f4}
button.setting-row .setting-value{color:var(--muted);white-space:nowrap}
.profile-card{align-items:center}
.profile-card div{flex:1;min-width:0}
.avatar-row{display:flex;gap:8px;margin:2px 0 16px;flex-wrap:wrap}
.avatar.pick{width:42px;height:42px;font-size:var(--t-label);margin:0;border:2px solid #fff;cursor:pointer;opacity:.5}
.avatar.pick.on{opacity:1;box-shadow:0 0 0 2px var(--ink)}

/* --- Profile photos ------------------------------------------------------- */
.emoji-face{font-size:1.75em;line-height:1;display:block}
.avatar.photo{background-size:cover;background-position:center;background-color:var(--gray-mid)}
.avatar.xl{width:74px;height:74px;font-size:var(--t-title);margin:0;flex:0 0 auto}
.profile-dot.has-photo{background:var(--gray-mid);background-size:cover;background-position:center;overflow:hidden;font-size:0}
.profile-dot .emoji-face{font-size:var(--t-card)}
.photo-hero{display:flex;align-items:center;gap:var(--s4);margin-bottom:16px}
.photo-hero div{flex:1;min-width:0}
.photo-hero strong{display:block;font-size:var(--t-meta)}
.photo-hero p{margin:4px 0 0;font-size:var(--t-label);color:var(--muted);line-height:1.5}
.upload-tile{display:flex;align-items:center;gap:12px;padding:var(--s4);border:1px dashed var(--line2);border-radius:var(--r-card);
  background:#fff;cursor:pointer;margin-bottom:4px}
.upload-tile .upload-icon{width:42px;height:42px;border-radius:var(--r-control);background:var(--warm);display:grid;place-items:center;
  font-size:var(--t-card);font-weight:var(--w-bold);flex:0 0 auto}
.upload-tile strong{display:block;font-size:var(--t-label)}
.upload-tile span:last-child{display:block;font-size:var(--t-label);color:var(--muted);margin-top:3px;line-height:1.45}
.photo-grid{display:grid;grid-template-columns:repeat(6,1fr);gap:8px}
@media(max-width:400px){ .photo-grid{grid-template-columns:repeat(5,1fr)} }
.photo-tile{aspect-ratio:1;border-radius:50%;border:2px solid #fff;cursor:pointer;display:grid;place-items:center;
  font-size:var(--t-meta);padding:0;box-shadow:0 1px 3px rgba(40,35,28,.12)}
.photo-tile.on{box-shadow:0 0 0 2px var(--ink)}
.photo-tile.tone1{background:linear-gradient(145deg,#b7c4bd,#7f978a)}
.photo-tile.tone2{background:linear-gradient(145deg,#c6a798,#9b796b)}
.photo-tile.tone3{background:linear-gradient(145deg,#a7b2c4,#73829d)}
.photo-tile.tone4{background:linear-gradient(145deg,#d0bb87,#aa8e54)}
.photo-tile.tone5{background:linear-gradient(145deg,#c2a9c7,#9978a0)}
.photo-tile.tone6{background:linear-gradient(145deg,#9cbcc0,#6e9498)}

/* --- Onboarding ---------------------------------------------------------- */
.ob-points{display:flex;flex-direction:column;gap:var(--s2);margin-top:4px}
.ob-point{display:flex;gap:12px;padding:var(--s4);border:1px solid var(--line);border-radius:var(--r-card);background:#fff}
.ob-point .ob-emoji{font-size:var(--t-title);width:42px;height:42px;border-radius:var(--r-control);background:var(--warm);display:grid;place-items:center;flex:0 0 auto}
.ob-point strong{display:block;font-size:var(--t-meta);letter-spacing:-.01em}
.ob-point p{margin:5px 0 0;font-size:var(--t-label);color:var(--muted);line-height:1.5}
.pref-tile.picked{border-color:#cdded2;background:var(--green-soft)}
.pref-tile.picked span:last-child{color:var(--green);font-weight:var(--w-med)}
.text-btn.wide{width:100%;text-align:center}

/* --- Proposal: how far / scope ------------------------------------------ */
.scope-list{display:flex;flex-direction:column;gap:var(--s2)}
.scope-card{display:flex;align-items:center;gap:12px;padding:var(--s4);border-radius:var(--r-card);border:1px solid var(--line);background:#fff;text-align:left;cursor:pointer;width:100%}
.scope-card .scope-emoji{font-size:var(--t-title);width:46px;height:46px;border-radius:var(--r-control);background:var(--warm);display:grid;place-items:center;flex:0 0 auto}
.scope-card strong{display:block;font-size:var(--t-meta);letter-spacing:-.02em}
.scope-card p{margin:4px 0 0;font-size:var(--t-label);color:var(--muted);line-height:1.45}
.scope-card.selected{border-color:#cdded2;background:var(--green-soft)}
.scope-card.selected .scope-emoji{background:#fff}
.range-row{display:flex;gap:var(--s2);flex-wrap:wrap;margin-top:var(--s2)}

/* --- Discovery: sorting, deals, savings --------------------------------- */
.sort-row{display:flex;gap:var(--s2);overflow-x:auto;padding:2px 0 4px;margin:0 0 var(--s2);-webkit-overflow-scrolling:touch}
.sort-row::-webkit-scrollbar{display:none}
.sort-chip{white-space:nowrap;border:1px solid var(--line);background:#fff;border-radius:999px;padding:var(--s2) var(--s3);font-size:var(--t-label);font-weight:var(--w-med);color:#66645f;cursor:pointer}
.sort-chip.active{background:var(--ink);border-color:var(--ink);color:#fff}
.savings-bar{position:sticky;top:0;z-index:6;margin:0 0 12px;padding:var(--s3) 12px;border-radius:var(--r-control);background:var(--ink);color:#fff;display:flex;align-items:center;justify-content:space-between;gap:var(--s2)}
.savings-bar strong{font-size:var(--t-label);display:block}
.savings-bar span{font-size:var(--t-label);color:#cfccc4;display:block;margin-top:3px}
.savings-bar button{border:1px solid rgba(255,255,255,.32);background:rgba(255,255,255,.1);color:#fff;border-radius:var(--r-control);padding:var(--s2);font-size:var(--t-label);font-weight:var(--w-med);cursor:pointer}
.reco-card .reco-info em{font-style:normal;display:block;font-size:var(--t-label);color:var(--green);margin-top:4px;font-weight:var(--w-med)}
.reco-tags{display:flex;gap:5px;flex-wrap:wrap;margin-top:var(--s2)}
.price-line{display:flex;align-items:baseline;gap:var(--s2);margin-top:4px}
.price-line b{font-size:var(--t-label)}
.price-line s{font-size:var(--t-label);color:var(--muted2)}
.opp-card{padding:var(--s4);margin-bottom:8px;background:linear-gradient(150deg,#fffdf9,#f6f2e8);border:1px solid #e8e0cf}
.opp-card strong{font-size:var(--t-label);display:block}
.opp-card p{margin:5px 0 0;font-size:var(--t-label);color:#6b6760;line-height:1.45}

/* --- Events ------------------------------------------------------------- */
.event-card{padding:var(--s4);margin-bottom:8px;display:grid;grid-template-columns:46px minmax(0,1fr) auto;gap:var(--s3);align-items:start;cursor:pointer}
.event-date{border-radius:var(--r-control);background:var(--warm);border:1px solid var(--line);text-align:center;padding:var(--s2) 0}
.event-date b{display:block;font-size:var(--t-meta);letter-spacing:-.03em}
.event-date span{display:block;font-size:var(--t-label);font-weight:var(--w-med);color:var(--muted);letter-spacing:.08em}
.event-body strong{font-size:var(--t-label);display:block}
.event-body .event-meta{font-size:var(--t-label);color:var(--muted);margin-top:3px;line-height:1.45}
.event-card.selected{border-color:#cdded2;background:#fbfdfb}

/* --- Destination imagery --------------------------------------------------
   Generated artwork renders instantly; the real photo fades in over it. */
.dest-art{display:block;width:100%;border-radius:var(--r-control);margin-bottom:12px;background:var(--gray-soft)}
.place-photo{position:relative;width:100%;border-radius:var(--r-control);overflow:hidden;margin-bottom:12px;background:var(--gray-soft)}
.place-photo .dest-art{margin:0;border-radius:0;height:100%}
.place-img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0;transition:opacity .45s ease}
.place-img.in{opacity:1}
.art-gallery{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s2);margin-bottom:var(--s2)}
.art-gallery .dest-art,.art-gallery .place-photo{margin:0;border-radius:12px}
.photo-credit{margin:0 2px var(--s4)}

/* --- Navigation icons ------------------------------------------------------ */
.nav-icon svg{width:21px;height:21px;display:block}
.side-item .nav-icon svg{width:18px;height:18px}
.nav-photo{width:23px;height:23px;border-radius:50%;display:grid;place-items:center;overflow:hidden;
  background-size:cover;background-position:center;background-color:var(--gray-mid);font-size:var(--t-micro)}
.nav-photo .emoji-face{font-size:var(--t-meta)}
.nav-item.active .nav-photo{box-shadow:0 0 0 1.5px var(--ink)}
.side-item .nav-photo{width:20px;height:20px}

/* --- Travel: destinations, flights, price calendar ---------------------- */
.dest-card{padding:var(--s4);margin-bottom:var(--s2);cursor:pointer}
.dest-top{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--s2)}
.dest-card h3{margin:0;font-size:var(--t-card);letter-spacing:-.03em}
.dest-card .dest-sub{font-size:var(--t-label);color:var(--muted);margin-top:3px}
.dest-price{text-align:right;flex:0 0 auto}
.dest-price b{display:block;font-size:var(--t-body);letter-spacing:-.03em}
.dest-price span{display:block;font-size:var(--t-label);color:var(--muted);margin-top:2px}
.why-list{margin:var(--s3) 0 0;display:flex;flex-direction:column;gap:5px}
.why-list div{font-size:var(--t-label);color:#54524d;padding-left:var(--s3);position:relative;line-height:1.4}
.why-list div:before{content:"";position:absolute;left:0;top:5px;width:5px;height:5px;border-radius:50%;background:var(--green)}
.dest-foot{display:flex;align-items:center;justify-content:space-between;gap:var(--s2);margin-top:12px;padding-top:var(--s3);border-top:1px solid var(--line)}
.dest-badges{display:flex;gap:5px;flex-wrap:wrap}
.dest-card.selected{border-color:#cdded2;background:#fbfdfb}
.flight-card{padding:var(--s4);margin-bottom:var(--s2);display:flex;align-items:center;gap:var(--s3);cursor:pointer}
.flight-card .leg{flex:1;min-width:0}
.flight-card .leg strong{font-size:var(--t-label);display:block}
.flight-card .leg span{font-size:var(--t-label);color:var(--muted);display:block;margin-top:3px}
.flight-card .fare{text-align:right}
.flight-card .fare b{font-size:var(--t-meta);display:block;letter-spacing:-.02em}
.flight-card .fare span{font-size:var(--t-label);color:var(--muted)}
.flight-card.selected{border-color:#cdded2;background:#fbfdfb}
.price-cal{display:grid;grid-template-columns:repeat(7,1fr);gap:4px}
.pc-day{min-height:48px;border:1px solid var(--line);border-radius:var(--r-control);background:#fff;padding:5px 4px;display:flex;flex-direction:column;align-items:center;justify-content:flex-start;gap:2px;cursor:pointer}
.pc-day .pcd-num{font-size:var(--t-micro);font-weight:var(--w-bold)}
.pc-day .pcd-fare{font-size:var(--t-dense);font-weight:var(--w-bold);color:var(--muted)}
.pc-day.other{opacity:.3}
.pc-day.blocked{background:var(--gray-soft);color:var(--muted2)}
.pc-day.good{background:var(--green-soft);border-color:#d7e5db}
.pc-day.good .pcd-fare{color:var(--green)}
.pc-day.best{background:var(--green);border-color:var(--green);color:#fff}
.pc-day.best .pcd-fare{color:#e4f0e8}
.pc-day.inrange{box-shadow:inset 0 0 0 1px var(--ink)}
.window-card{padding:var(--s4);margin-bottom:8px;cursor:pointer}
.window-card .row-between strong{font-size:var(--t-meta);letter-spacing:-.02em}
.window-card .wc-meta{font-size:var(--t-label);color:var(--muted);margin-top:4px}
.window-card.selected{border-color:#cdded2;background:#fbfdfb}

/* --- Cost / savings breakdown ------------------------------------------- */
.cost-table{padding:var(--s4)}
.cost-row{display:flex;align-items:center;justify-content:space-between;gap:var(--s2);padding:8px 0;border-top:1px solid var(--line);font-size:var(--t-label)}
.cost-row:first-of-type{border-top:0}
.cost-row span{color:#54524d}
.cost-row b{font-weight:var(--w-med)}
.cost-total{margin-top:var(--s2);padding-top:var(--s3);border-top:1px solid var(--line2);display:flex;align-items:baseline;justify-content:space-between}
.cost-total strong{font-size:var(--t-body);letter-spacing:-.03em}
.cost-total em{font-style:normal;font-size:var(--t-label);color:var(--green);font-weight:var(--w-med)}

/* --- Confirmed plan: execution mode ------------------------------------- */
.exec-head{margin-bottom:12px}
.exec-head h3{margin:0;font-size:var(--t-card);letter-spacing:-.035em}
.exec-head p{margin:5px 0 0;font-size:var(--t-label);color:var(--muted)}
.task-card{padding:var(--s4);margin-bottom:8px}
.task-top{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--s2)}
.task-top strong{font-size:var(--t-label);display:block}
.task-top .task-meta{font-size:var(--t-label);color:var(--muted);margin-top:3px;line-height:1.45}
.task-progress{font-size:var(--t-label);font-weight:var(--w-med);color:var(--green);white-space:nowrap}
.task-progress.pending{color:var(--muted)}
.person-list{margin-top:var(--s3);display:flex;flex-direction:column;gap:2px}
.person-check{display:flex;align-items:center;gap:8px;padding:var(--s2) 8px;border-radius:var(--r-control);font-size:var(--t-label);background:#fdfcfa;border:1px solid transparent}
.person-check .pc-box{width:17px;height:17px;border-radius:6px;border:1px solid var(--line2);display:grid;place-items:center;font-size:var(--t-label);font-weight:var(--w-med);flex:0 0 auto;background:#fff}
.person-check.done .pc-box{background:var(--green);border-color:var(--green);color:#fff}
.person-check .pc-name{flex:1}
.person-check.me{border-color:var(--line2);background:#fff;cursor:pointer}
.person-check.me .pc-name:after{content:" · you";color:var(--muted2);font-weight:var(--w-med)}
.assignee{display:flex;align-items:center;gap:var(--s2);font-size:var(--t-label);color:var(--muted);margin-top:var(--s2)}
.task-add-row{display:flex;gap:var(--s2);margin:12px 0 4px}
.task-add-row button{flex:1}
.dup-warning{padding:var(--s2) var(--s3);border-radius:12px;background:#f7f1e3;border:1px solid #e8dcc2;font-size:var(--t-label);color:#7d6524;line-height:1.45;margin-bottom:var(--s2)}
.checklist-group-title{font-size:var(--t-label);letter-spacing:.12em;color:#85837d;margin:var(--s4) 2px var(--s2);font-weight:var(--w-med)}

/* --- misc --------------------------------------------------------------- */
.inline-tabs{display:flex;gap:var(--s2);margin:0 0 var(--s3)}
.inline-tabs button{flex:1;border:1px solid var(--line);background:var(--warm);border-radius:12px;padding:var(--s2) var(--s2);font-size:var(--t-label);font-weight:var(--w-med);color:var(--muted);cursor:pointer}
.inline-tabs button.active{background:#fff;color:var(--ink);border-color:var(--line2)}
.mini-pop.wide{max-width:470px}
.sheet .section-head{margin:var(--s5) 2px var(--s2)}
.chip-toggle{display:flex;gap:var(--s2);flex-wrap:wrap;margin-top:8px}
.empty-inline{padding:16px;text-align:center;font-size:var(--t-label);color:var(--muted);border:1px dashed var(--line2);border-radius:var(--r-control);background:#fdfcfa}

/* ==========================================================================
   Native iOS shell — the SwiftUI TabView supplies the tab bar, so the web
   one is hidden and the safe-area padding comes back off the shell.
   ========================================================================== */
.native-shell .bottom-nav{display:none!important}
.native-shell .app-shell{padding-bottom:var(--s4)}
.native-shell .floating-actions{bottom:12px}
.native-shell .toast{bottom:24px}

/* ==========================================================================
   Desktop — sidebar navigation. Phones keep the bottom tab bar exactly as-is.
   ========================================================================== */
.side-nav{display:none}

@media(min-width:860px){
  .app-wrap{padding:28px;align-items:flex-start}
  /* height, not just min-height. A grid whose container height is auto sizes its
     rows to content first; min-height then grows the BOX and leaves the extra
     space unallocated, so minmax(0,1fr) had nothing to divide and .content
     stopped 96px short with bare shell underneath it on every page. A definite
     height gives the fr unit something real, and .content scrolls. */
  .app-shell{max-width:1440px;min-height:calc(100vh - 56px);height:calc(100vh - 56px);padding-bottom:0;
    display:grid;grid-template-columns:242px minmax(0,1fr);grid-template-rows:auto minmax(0,1fr);
    border:1px solid #d8d2c8;border-radius:30px;overflow:hidden;box-shadow:0 28px 90px rgba(48,42,31,.14)}
  .bottom-nav{display:none!important}
  /* 1 / -1, not span 2. The rail is meant to be full height, and `span 2` only
     said that while the grid had exactly two rows - adding a row for the
     profile banner turned it into 'rows 1-2', which forced row 2 to the
     rail's leftover height (150px) and left .content short by that much with
     dead shell underneath it. -1 means the last line, whatever the count. */
  .side-nav{display:flex;flex-direction:column;grid-row:1 / -1;background:var(--paper);
    border-right:1px solid var(--line);padding:var(--s5) var(--s4) 18px;gap:var(--s2)}
  .side-brand{display:flex;align-items:center;gap:var(--s2);padding:0 8px var(--s2)}
  .side-brand .brand-mark{width:32px;height:32px;border-radius:var(--r-control);font-size:var(--t-body)}
  .side-brand strong{display:block;font-size:var(--t-body);font-weight:var(--w-med);letter-spacing:-.02em}
  .side-brand span{display:block;font-size:var(--t-label);color:var(--muted);margin-top:2px}
  .side-items{display:flex;flex-direction:column;gap:3px;margin-top:12px}
  .side-item{display:flex;align-items:center;gap:var(--s3);border:0;background:transparent;border-radius:var(--r-control);
    padding:var(--s3) 12px;font-size:var(--t-label);font-weight:var(--w-med);color:#7d7b74;cursor:pointer;text-align:left;position:relative}
  .side-item:hover{background:#f1ede4;color:var(--ink)}
  .side-item.active{background:var(--warm);color:var(--ink);box-shadow:inset 0 0 0 1px var(--line)}
  .side-item .nav-icon{font-size:var(--t-body)}
  .side-badge{margin-left:auto;min-width:19px;height:19px;padding:0 6px;border-radius:999px;background:var(--ink);
    color:#fff;font-size:var(--t-label);font-weight:var(--w-med);line-height:19px;text-align:center}
  .side-foot{margin-top:auto;display:flex;align-items:center;gap:var(--s2);padding:12px 8px 0;border-top:1px solid var(--line)}
  .side-foot .avatar{margin:0}
  .side-foot strong{display:block;font-size:var(--t-label)}
  .side-foot span{display:block;font-size:var(--t-label);color:var(--muted);margin-top:2px}
  .topbar{grid-column:2;grid-row:1;border-radius:0!important;padding:var(--s5) 30px var(--s4)}
  .topbar .brand{display:none}
  .topbar .top-context{font-size:var(--t-label);color:var(--muted);margin:0}
  .topbar-row{align-items:flex-start}
  .content{grid-column:2;grid-row:2;padding:0 30px 44px;overflow-y:auto}
  .content>.screen{max-width:none}
  .screen-title h1{font-size:var(--t-hero)}
  .month-day{min-height:78px}
  .month-day .day-num{font-size:var(--t-label)}
  .group-card{grid-template-columns:minmax(0,1fr) 230px}
  .floating-actions{bottom:16px}
  /* sheets become centred modals rather than phone drawers */
  .sheet{top:50%;bottom:auto!important;transform:translate(-50%,-50%);max-width:620px;max-height:86vh;
    border-radius:26px!important;padding:var(--s4) 24px 26px}
  @keyframes sheetIn{from{transform:translate(-50%,-46%);opacity:.3}to{transform:translate(-50%,-50%);opacity:1}}
  .grabber{display:none}
  .backdrop{border-radius:0}
  .toast{bottom:34px}
  .vote-lock{position:static}
}

/* --- Dragging an event ----------------------------------------------------- */
/* pan-y, not none. These cover about two thirds of a busy day sheet, and with
   touch-action:none a thumb placed on them could not scroll the page at all —
   the sheet simply felt frozen. drag.js switches an individual block to none
   once a hold has armed it. */
.tblock,.wk-block{touch-action:pan-y}
.tblock.drag-armed,.wk-block.drag-armed{touch-action:none}
.tblock:not(.locked),.wk-block:not(.locked){cursor:grab}
.tblock.dragging,.wk-block.dragging{cursor:grabbing;z-index:9;opacity:.92;
  box-shadow:0 10px 26px rgba(45,39,29,.22);transform:scale(1.02);transition:none}
.tblock.locked,.wk-block.locked{cursor:pointer}
body.is-dragging{user-select:none;-webkit-user-select:none}
body.is-dragging .hr-slot,body.is-dragging .wk-slot{pointer-events:none}
.drag-time{position:fixed;z-index:90;transform:translate(-50%,0);pointer-events:none;
  background:var(--ink);color:#fff;border-radius:999px;padding:var(--s2) var(--s3);font-size:var(--t-label);font-weight:var(--w-med);
  box-shadow:0 8px 22px rgba(38,33,25,.3);white-space:nowrap}

/* --- Preference chips: an emoji each, and room to breathe ------------------ */
.chip-cloud{gap:var(--s2)}
.pref-chip{display:inline-flex;align-items:center;gap:var(--s2);padding:var(--s2) var(--s3);font-size:var(--t-label);line-height:1.2}
.pref-chip .chip-emoji{font-size:var(--t-meta);line-height:1}
.pref-chip.selected{box-shadow:inset 0 0 0 1px rgba(47,111,79,.18)}

/* --- Links out of the app -------------------------------------------------- */
.link-row{display:flex;gap:var(--s2);flex-wrap:wrap;margin-top:8px}
.go-link{display:inline-flex;align-items:center;gap:4px;border:1px solid var(--line2);background:#fff;
  border-radius:999px;padding:var(--s2) var(--s2);font-size:var(--t-label);font-weight:var(--w-med);color:var(--ink);text-decoration:none}
.go-link:hover{background:var(--warm);border-color:var(--ink)}
.event-links{margin-top:var(--s2)}

/* --- Unclaimed bring items ------------------------------------------------- */
.task-card.unclaimed{border-style:dashed;background:#fdfcfa}
.task-card.unclaimed .task-top{align-items:center}
.person-check.me.done .pc-name{color:var(--muted);text-decoration:line-through}

/* ==========================================================================
   Desktop / phone switch
   The site opens in the desktop layout on a computer. Switching to Phone
   renders the exact build the iOS app runs, inside a device frame, so both
   can be reviewed from one browser.
   ========================================================================== */
.view-switch{display:none;gap:3px;padding:3px;border-radius:12px;background:#ece8df;border:1px solid var(--line);margin-top:auto}
.view-switch button{flex:1;border:0;background:transparent;border-radius:9px;padding:var(--s2);
  font-size:var(--t-label);font-weight:var(--w-med);color:var(--muted);cursor:pointer;white-space:nowrap;font-family:inherit}
.view-switch button.on{background:#fff;color:var(--ink);box-shadow:0 1px 3px rgba(40,35,28,.1)}
.floating-view{display:none;position:fixed;z-index:60;top:22px;right:22px;padding:4px;border-radius:var(--r-control);
  background:rgba(255,253,249,.96);border:1px solid var(--line2);box-shadow:0 12px 30px rgba(46,40,30,.16)}
.floating-view .view-switch{display:flex;margin:0;border:0;background:transparent;padding:0}

@media(min-width:860px){
  .side-nav .view-switch{display:flex}
  /* --- forced phone view: the iOS build, framed ------------------------- */
  html.force-phone .app-wrap{padding:46px var(--s5) 60px;align-items:flex-start;justify-content:center;
    background:linear-gradient(165deg,#d5ddd6,#c3ccc9 55%,#cdc6ba)}
  html.force-phone .app-shell{max-width:390px;width:390px;min-height:auto;height:844px;
    display:block;grid-template-columns:none;grid-template-rows:none;
    border:11px solid #16181a;border-radius:52px;overflow:hidden;position:relative;
    padding-bottom:96px;box-shadow:0 30px 70px rgba(24,28,26,.4),0 0 0 2px #2c2f31}
  html.force-phone .app-shell:before{content:"";position:absolute;z-index:40;top:9px;left:50%;
    transform:translateX(-50%);width:104px;height:26px;border-radius:999px;background:#16181a}
  html.force-phone .side-nav{display:none}
  html.force-phone .bottom-nav{display:grid!important;position:absolute;bottom:14px;left:50%;
    width:calc(100% - 24px);max-width:none}
  html.force-phone .topbar{grid-column:auto;grid-row:auto;border-radius:0!important;padding:44px var(--s5) 14px}
  html.force-phone .topbar .brand{display:flex}
  html.force-phone .topbar .top-context{font-size:var(--t-label)}
  html.force-phone .content{grid-column:auto;grid-row:auto;padding:0 16px 34px;max-height:calc(844px - 116px);overflow-y:auto}
  html.force-phone .content>.screen{max-width:none}
  html.force-phone .screen-title h1{font-size:var(--t-title)}
  html.force-phone .month-day{min-height:0}
  html.force-phone .group-card{grid-template-columns:minmax(0,1fr) 174px}
  /* sheets sit inside the phone rather than centred on the desktop canvas */
  html.force-phone .backdrop{position:fixed}
  html.force-phone .sheet{top:auto;bottom:0!important;transform:translateX(-50%);
    max-width:390px;max-height:74vh;border-radius:28px 28px 0 0!important;padding:var(--s2) 16px 24px}
  html.force-phone .grabber{display:block}
  html.force-phone .floating-view{display:block}
  html.force-phone .toast{bottom:120px}
}

/* Below 900px the phone layout is the only sensible one, so hide the switch. */
@media(max-width:899px){
  .view-switch,.floating-view{display:none!important}
  html.force-phone .app-shell{width:100%;max-width:540px;border:0;border-radius:0;height:auto;box-shadow:none}
  html.force-phone .app-shell:before{display:none}
  html.force-phone .topbar{padding:var(--s5) var(--s5) 14px}
}
/* the native shell never shows browser chrome of any kind */
html.native-shell .floating-view,html.native-shell .view-switch{display:none!important}

/* --- Live data ------------------------------------------------------------- */
.badge.live{background:var(--green-soft);border-color:#cfe0d4;color:var(--green);
  font-size:var(--t-label);padding:2px var(--s2);vertical-align:1px;margin-left:5px}
.reco-photo{width:52px;height:52px;border-radius:var(--r-control);background-size:cover;background-position:center;
  background-color:var(--gray-soft);flex:0 0 auto}
.reco-card .reco-photo+.reco-info{min-width:0}

/* --- Your usual week ------------------------------------------------------- */
.routine-list{display:flex;flex-direction:column;gap:8px;margin-bottom:12px}
.routine-row{display:flex;align-items:flex-start;gap:var(--s3);padding:var(--s3);cursor:pointer}
.routine-row.on{border-color:#cdded2;background:#fbfdfb}
.routine-emoji{width:38px;height:38px;border-radius:12px;background:var(--warm);border:1px solid var(--line);
  display:grid;place-items:center;font-size:var(--t-card);flex:0 0 auto}
.routine-main{flex:1;min-width:0}
.routine-main strong{display:block;font-size:var(--t-label)}
.routine-main>span{display:block;font-size:var(--t-label);color:var(--muted);margin-top:2px}
.routine-days{display:flex;gap:3px;margin-top:8px}
.rd{width:21px;height:21px;border-radius:7px;border:1px solid var(--line);background:#fff;
  font-size:var(--t-label);font-weight:var(--w-med);color:var(--muted2);cursor:pointer;padding:0;font-family:inherit}
.rd.on{background:var(--green);border-color:var(--green);color:#fff}
.rd:disabled{opacity:.4;cursor:default}
.routine-side{display:flex;flex-direction:column;align-items:flex-end;gap:var(--s2);flex:0 0 auto}
.routine-side .quiet{padding:var(--s2) var(--s2);font-size:var(--t-label)}
.routine-row .check{width:22px;height:22px;font-size:var(--t-label)}
.routine-row.on .check{background:var(--green);border-color:var(--green);color:#fff}

/* --- Auth errors: visible until dealt with --------------------------------- */
.auth-error{display:none;max-width:400px;width:100%;margin-top:12px;padding:var(--s3) var(--s4);border-radius:var(--r-control);
  background:var(--danger-soft);border:1px solid #efd5d0;color:var(--danger);font-size:var(--t-label);
  line-height:1.5;font-weight:var(--w-bold)}

/* --- Inviting someone who is not on the app yet ---------------------------- */
.invite-preview{background:var(--warm);border:1px solid var(--line);border-radius:var(--r-control);padding:var(--s3);
  font-size:var(--t-label);line-height:1.5;color:#54524d;margin:4px 0 var(--s4)}

/* --- Empty states that point somewhere ------------------------------------ */
.empty button{display:inline-block}

/* ==========================================================================
   Three destinations, actions in the corner
   ========================================================================== */
.top-actions{display:flex;align-items:center;gap:var(--s2)}
.top-btn{width:36px;height:36px;border-radius:12px;border:1px solid var(--line);background:#fff;
  display:grid;place-items:center;cursor:pointer;position:relative;color:var(--ink);padding:0}
.top-btn:hover{background:var(--warm)}
.top-btn svg{width:18px;height:18px}
.top-badge{position:absolute;top:-5px;right:-5px;min-width:17px;height:17px;padding:0 4px;border-radius:999px;
  background:var(--danger);color:#fff;font-size:var(--t-label);font-weight:var(--w-med);line-height:17px;text-align:center}
/* Four: Coordin8, Plans, Friends, Calendar. On the narrowest phone that is
   87px a column — well over the 44px touch minimum. Settings is on the avatar
   rather than a fifth column, which would be 70px. */
.bottom-nav{grid-template-columns:repeat(4,1fr)}

/* --- Notifications --------------------------------------------------------- */
.notif-section{margin-bottom:16px}
.notif-head{font-size:var(--t-label);letter-spacing:.12em;font-weight:var(--w-med);color:var(--ink);margin:0 2px var(--s2);text-transform:uppercase}
.notif-head.quiet{color:#85837d}
.notif-clear{padding:var(--s4);border-radius:var(--r-control);background:var(--green-soft);border:1px solid #d4e3d8;
  color:var(--green);font-size:var(--t-label);font-weight:var(--w-med);text-align:center;margin-bottom:16px}
/* .notif-action / .notif-top replaced by the shared .answer-card above */
.notif-icon{width:30px;height:30px;border-radius:var(--r-control);background:var(--warm);border:1px solid var(--line);
  display:grid;place-items:center;font-size:var(--t-meta);flex:0 0 auto}
.notif-icon.small{width:26px;height:26px;font-size:var(--t-label)}
.notif-age{font-size:var(--t-label);color:var(--muted2);font-weight:var(--w-med);flex:0 0 auto}
.notif-row{display:flex;align-items:center;gap:var(--s3);width:100%;text-align:left;padding:12px 4px;
  border:0;background:transparent;border-bottom:1px solid var(--line);cursor:pointer;font:inherit}
.notif-row:last-child{border-bottom:0}
.notif-row:hover{background:#faf8f4}
.notif-row strong{display:block;font-size:var(--t-meta);line-height:1.35;font-weight:var(--w-med)}
.notif-row span{display:block;font-size:var(--t-meta);color:var(--muted);margin-top:2px}
.notif-row div{flex:1;min-width:0}
.notif-count{color:var(--muted2);font-weight:var(--w-med);font-size:var(--t-label)}

/* --- Waiting on you, pinned above Plans ------------------------------------ */
.waiting-block{margin-bottom:var(--s4)}
.waiting-head{font-size:var(--t-label);letter-spacing:.12em;font-weight:var(--w-med);color:var(--danger);margin:0 2px 8px}
/* ONE answer card, used by "Waiting on you" on Plans and by Action needed in
   Notifications, because they are the same thing: a question with two answers.
   They were two components — one stacked its buttons in a row carrying
   `max-width:340px; margin-left:auto`, which pinned them to the far edge and
   left a lane of empty card between the question and the answer, so on a wide
   screen the two ends of the same sentence sat 700px apart.
   Question on the left, answers on the right, one line. */
.answer-card{padding:var(--s4);margin-bottom:8px;border-color:var(--line2);
  display:flex;align-items:center;gap:var(--s3);flex-wrap:wrap}
.answer-card.urgent{border-color:#efd5d0;background:#fffdfc}
.answer-icon{width:34px;height:34px;border-radius:var(--r-control);background:var(--warm);border:1px solid var(--line);
  display:grid;place-items:center;font-size:var(--t-card);flex:0 0 auto}
.answer-icon.small{width:28px;height:28px;font-size:var(--t-meta);border-radius:9px}
.answer-text{min-width:min(100%,200px);flex:1 1 200px}
.answer-card strong{display:block;font-size:var(--t-card);letter-spacing:-.01em;line-height:1.3}
.answer-card .answer-text > span{display:block;font-size:var(--t-meta);color:var(--muted);margin-top:3px;line-height:1.4}
.answer-actions{display:flex;gap:var(--s2);flex:0 0 auto;margin-left:auto}
.answer-actions button{min-height:44px;padding:0 18px;white-space:nowrap}
.text-btn.inline{display:inline-block;width:auto;padding:var(--s2) 0;margin-top:4px;font-size:var(--t-meta)}
/* On a phone the two answers get the full width rather than crowding into a
   corner — this is the one tap the screen exists to collect. */
@media(max-width:520px){
  .answer-card{align-items:stretch}
  .answer-actions{width:100%;margin-left:0}
  .answer-actions button{flex:1 1 0}
}

/* --- Quick plan ------------------------------------------------------------- */
.free-now{display:flex;align-items:center;gap:12px;padding:var(--s4);margin-bottom:8px;width:100%;
  text-align:left;cursor:pointer}
.free-now strong{display:block;font-size:var(--t-label)}
.free-now span{display:block;font-size:var(--t-label);color:var(--muted);margin-top:2px}
.free-now>div:first-child{flex:1;min-width:0}
.free-now-count{text-align:center;min-width:44px}
.free-now-count b{display:block;font-size:var(--t-card);letter-spacing:-.03em;color:var(--muted2)}
.free-now-count.has b{color:var(--green)}
.free-now-count span{font-size:var(--t-label);color:var(--muted2);font-weight:var(--w-med);letter-spacing:.05em}
.face-picker{display:flex;flex-wrap:wrap;gap:8px}
.face{display:flex;flex-direction:column;align-items:center;gap:5px;width:64px;padding:var(--s2) 4px;
  border:1px solid var(--line);border-radius:var(--r-control);background:#fff;cursor:pointer;opacity:.5}
.face span{font-size:var(--t-label);font-weight:var(--w-med);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}
.face .avatar{margin:0}
.face.on{opacity:1;border-color:#cdded2;background:var(--green-soft)}

/* --- Cold start ------------------------------------------------------------- */
.handle-hero{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:var(--s4)}
.handle-hero span{display:block;font-size:var(--t-label);letter-spacing:.12em;color:#85837d;font-weight:var(--w-med)}
.handle-hero strong{display:block;font-size:var(--t-title);letter-spacing:-.03em;margin-top:5px}
.handle-hero p{margin:5px 0 0;font-size:var(--t-label);color:var(--muted)}
.sample-wrap{position:relative;border:1px dashed var(--line2);border-radius:var(--r-card);padding:var(--s4);background:#fdfcfa}
/* The preview is the one picture of what the product does, and it was rendering
   about 120px wide against the left edge of a full-width card. Give it a real
   size and centre it. */
/* .mini-cal is a <button>, so it is inline-block by default: margin:0 auto
   cannot centre it and a max-width cannot stretch it — it shrink-wrapped to
   112px against a 320px panel. */
.mini-cal{display:block;width:100%}
.sample-wrap .mini-cal{cursor:default;pointer-events:none;background:#fff;
  max-width:360px;margin:0 auto;padding:var(--s3)}
.sample-wrap .mini-title{font-size:var(--t-meta);font-weight:var(--w-med);margin-bottom:8px}
.sample-wrap .mini-week span{font-size:var(--t-micro)}
/* Square days. .mini-day was a fixed 18px tall with its width left to the grid,
   so every cell was a rectangle whose shape depended on how wide its container
   happened to be — and the whole point of this preview is that it looks like a
   month. */
.mini-day{aspect-ratio:1;height:auto;min-height:0}
.sample-label{text-align:center;font-size:var(--t-micro);color:var(--muted2);font-weight:var(--w-bold);margin-top:8px;
  letter-spacing:.06em;text-transform:uppercase}

/* --- Unknown availability: marked, never counted as free -------------------- */
.month-day.unknown:after,.mini-day.unknown:after{content:"";position:absolute;top:3px;right:3px;
  width:4px;height:4px;border-radius:50%;background:#b4b0a8;box-shadow:0 0 0 1.5px rgba(255,255,255,.9)}
.mini-day{position:relative}
.mini-day.unknown:after{width:3px;height:3px;top:2px;right:2px}
.cal-legend .lg-unknown:before{background:#fff;border-color:var(--line2);position:relative}
.cal-legend .lg-unknown{position:relative}
.cal-legend .lg-unknown:after{content:"";position:absolute;left:5px;top:2px;width:3px;height:3px;
  border-radius:50%;background:#b4b0a8}
/* .breakdown went with the second copy of the group-day screen. The one that
   survived emits .breakdown-line, styled below. */

/* --- Tonight: two taps ------------------------------------------------------ */
/* the tonight-only strip lived here — replaced by the week strip below */
.filter-chip.big{padding:var(--s3) var(--s4);font-size:var(--t-label)}
@media(min-width:1080px){
  .week-strip-head{align-items:center}
}

/* --- One person away -------------------------------------------------------- */
.nudge{padding:var(--s4);margin-bottom:var(--s4);background:linear-gradient(150deg,#fffdf9,#f2f7f3);
  border-color:#cfe0d4}
.nudge-head strong{display:block;font-size:var(--t-meta);line-height:1.3;letter-spacing:-.015em}
.nudge-head span{display:block;font-size:var(--t-label);color:var(--muted);margin-top:4px;line-height:1.4}
.nudge-actions{display:flex;gap:var(--s2);margin-top:12px}
.nudge-actions button{flex:1;padding:var(--s2) 8px;font-size:var(--t-label)}
.nudge-actions .quiet{flex:0 0 auto;padding:var(--s2) 12px}

/* the pending vote, so a still count does not read as broken */
.vote-count.pending{color:var(--green);font-weight:var(--w-med)}

/* --- Desktop: month and day side by side ------------------------------------
   Selecting a date used to replace the month. On a wide screen the whole point
   is comparing one day against the others, so both stay on screen. */
@media(min-width:1080px){
  html.force-desktop .sheet:has(.group-workspace){ max-width:1120px; width:calc(100vw - 96px) }
  .group-workspace{display:grid;grid-template-columns:minmax(0,1fr) 380px;gap:var(--s5);align-items:start}
  .group-workspace .gw-cal{min-width:0}
  .group-workspace .month-day{aspect-ratio:1}
  .gw-panel{position:sticky;top:0;max-height:74vh;overflow-y:auto;padding:var(--s4) 16px var(--s5);
    background:var(--card);border:1px solid var(--line);border-radius:var(--r-lg)}
  .gw-head strong{font-size:var(--t-body);letter-spacing:-.02em}
  .gw-panel h3{margin-top:16px}
  .gw-empty{padding:34px 12px;text-align:center}
  .gw-empty strong{display:block;font-size:var(--t-meta)}
  .gw-empty span{display:block;font-size:var(--t-label);color:var(--muted);margin-top:var(--s2);line-height:1.5}
  .month-day.picked{box-shadow:inset 0 0 0 2px var(--ink)}
}
.breakdown-line{font-size:var(--t-label);color:var(--muted);margin:2px 2px var(--s2)}
.breakdown-line strong{color:#8a6d24;font-weight:var(--w-med)}

/* --- The tour, once you are inside ------------------------------------------ */
/* the old three-card modal tour lived here — replaced by coach marks */

/* --- Dietary fit, small and factual ----------------------------------------- */
.diet-row{display:flex;flex-wrap:wrap;gap:5px;margin-top:var(--s2)}
.diet-badge{font-size:var(--t-label);font-weight:var(--w-med);padding:3px var(--s2);border-radius:999px;cursor:help;
  border:1px solid var(--line);letter-spacing:.02em}
.diet-badge.ok{background:var(--green-soft);border-color:#cfe0d4;color:var(--green)}
.diet-badge.no{background:#f7f1e3;border-color:#e8dcc2;color:#8a6d24}

/* --- Best window, and times you can argue with ------------------------------ */
.best-window{display:flex;align-items:center;justify-content:space-between;gap:var(--s2);width:100%;
  padding:var(--s3) 12px;margin:2px 0 12px;border-radius:var(--r-control);border:1px solid #cfe0d4;
  background:var(--green-soft);cursor:pointer;text-align:left}
.best-window strong{display:block;font-size:var(--t-label);color:var(--green)}
.best-window span{font-size:var(--t-label);color:#4a7a5f}
.best-window.on{background:var(--green);border-color:var(--green)}
.best-window.on strong,.best-window.on span{color:#fff}
.time-warn{margin-top:var(--s2);font-size:var(--t-label);line-height:1.45;color:#8a6d24;background:#f7f1e3;
  border:1px solid #e8dcc2;border-radius:9px;padding:var(--s2) 8px}
.timeline-time.editable{border:0;background:transparent;padding:0;cursor:pointer;font:inherit;
  font-size:var(--t-label);font-weight:var(--w-med);text-align:left;border-bottom:1px dashed var(--line2)}
.timeline-time.editable:hover{color:var(--green);border-bottom-color:var(--green)}

/* --- Desktop: use the screen -------------------------------------------------
   Everything was capped at 700px, so a third of a 1440px monitor sat empty and
   Plans was a six-card column. Prose still wants a readable measure, so text
   keeps a cap while calendars and lists take the width they are given. */
@media(min-width:1080px){
  .content>.screen{max-width:none}
  .screen-title p,.muted-note,.privacy-note,.est-note{max-width:640px}

  /* Friends: list on the left, the month as the hero on the right */
  .screen.friends-wide{display:grid;grid-template-columns:minmax(320px,380px) minmax(0,1fr);
    gap:24px;align-items:start}
  .friends-wide .fw-list{min-width:0}
  .friends-wide .fw-hero{position:sticky;top:0;min-width:0}
  .friends-wide .group-card{grid-template-columns:minmax(0,1fr);gap:var(--s2)}
  .friends-wide .group-card .mini-cal{display:none}
  .fw-hero-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin:0 2px var(--s2)}
  .fw-hero-head h2{margin:0;font-size:var(--t-card);letter-spacing:-.03em}
  .fw-hero-head span{font-size:var(--t-label);color:var(--muted)}

  /* Plans: a board, not a list */
  .plans-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;align-items:start}
  .plans-grid .plan-card{margin-bottom:0}
  .wk-body{max-height:none}
}
@media(min-width:1400px){
  .plans-grid{grid-template-columns:repeat(3,minmax(0,1fr))}
}

/* The rail should survive a laptop window. 820-1079px used to drop to a 540px
   phone centred in an 880px window, which was the worst state in the app. */
@media(min-width:820px) and (max-width:1079px){
  html.force-desktop .app-shell{max-width:100%;display:grid;
    grid-template-columns:196px minmax(0,1fr);grid-template-rows:auto minmax(0,1fr);
    border-radius:0;min-height:100vh}
  html.force-desktop .side-nav{display:flex;grid-row:1 / -1}
  html.force-desktop .bottom-nav{display:none!important}
  html.force-desktop .topbar{grid-column:2;grid-row:1}
  html.force-desktop .content{grid-column:2;grid-row:2;overflow-y:auto;padding:0 22px 40px}
  html.force-desktop .content>.screen{max-width:none}
}

/* the + says what it does */
.top-btn.labelled{width:auto;padding:0 12px;gap:var(--s2);display:inline-flex;align-items:center}
.top-btn.labelled span{font-size:var(--t-label);font-weight:var(--w-med)}
@media(max-width:520px){ .top-btn.labelled span{display:none} .top-btn.labelled{width:38px;padding:0} }

/* --- Sign in with Google / Apple --------------------------------------------
   One clear first choice. Three identical white buttons is not a hierarchy —
   the provider leads, email is the alternative, demo is a footnote. */
.social-row{display:flex;flex-direction:column;gap:var(--s2);margin-bottom:var(--s4)}
.social-btn{display:flex;align-items:center;justify-content:center;gap:var(--s2);width:100%;
  padding:var(--s4) 16px;border-radius:var(--r-control);border:1px solid var(--line2);background:#fff;
  font-size:var(--t-meta);font-weight:var(--w-med);cursor:pointer;color:var(--ink);
  box-shadow:0 1px 2px rgba(45,39,29,.06);transition:.14s ease}
.social-btn:hover{border-color:#b9b2a6;box-shadow:0 4px 14px rgba(45,39,29,.10);transform:translateY(-1px)}
.social-btn:active{transform:none}
.social-btn.apple{background:var(--ink);border-color:var(--ink);color:#fff}
.social-btn.apple:hover{box-shadow:0 4px 16px rgba(18,19,15,.28)}
.social-mark{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;flex:0 0 auto}
.social-mark svg{display:block}
.or-line{display:flex;align-items:center;gap:12px;margin:0 0 var(--s4);color:var(--muted2);
  font-size:var(--t-label);font-weight:var(--w-med);letter-spacing:.06em;text-transform:uppercase}
.or-line:before,.or-line:after{content:"";flex:1;height:1px;background:var(--line)}
.auth-actions .quiet{background:transparent;border:1px solid var(--line);font-weight:var(--w-med)}
.auth-actions .quiet:hover{background:var(--warm)}

/* --- Signed out: no navigation, so no grid ---------------------------------- */
html.signed-out .app-shell{display:block!important;max-width:none!important;border:0;
  box-shadow:none;background:var(--bg);min-height:100vh}
html.signed-out .side-nav,html.signed-out .bottom-nav,html.signed-out .topbar{display:none!important}
.auth-wrap{width:100%}
.auth-card{margin:0 auto}

/* --- Avatars are spans, and that was the bug -------------------------------
   A dozen rules like `.free-now span{display:block}` out-specify `.avatar`
   (0,1,1 beats 0,1,0), so the circle lost its grid centring and the letter
   dropped to the top-left corner. Doubling the class raises specificity above
   any single-class descendant rule without reaching for !important. */
.avatar.avatar{display:grid;place-items:center;line-height:1;color:#fff;margin-top:0;
  text-align:center;overflow:hidden}
.avatar.avatar .emoji-face{line-height:1}

/* ==========================================================================
   TYPE SYSTEM — the last word on size and weight.

   Before this the app rendered 16 distinct font sizes with an average step of
   1.07x, which is below the point a person perceives as a deliberate change,
   and 73% of every text element was set in weight 800-850 at an average of
   10.3px. Everything was small and everything was bold, so nothing was first
   and the whole page read like a receipt.

   Five sizes. Each step is at least 1.17x, so every one of them is visible:
     hero  40 / 700   the answer — the number someone came for
     title 24 / 700   screen and group titles
     card  17 / 600   the name of a thing you can act on
     body  14 / 400   dates, times, meta, sentences
     label 12 / 600   weekday heads, pills, legends

   Weights are 400 / 600 / 700 only. The old 800, 850 and 900 rendered
   identically anyway — the system face has no 850 — so three tokens were
   producing one result.
   ========================================================================== */
:root{
  /* The scale the CSS actually uses, not the one it claimed to. --t-body was
     14px and rendered on exactly one element app-wide, while 15px and 13px did
     all the body work with no token at all — so an engineer reading --t-body
     got a size nothing else on the page used. 13px had no rung; it does now. */
  --t-hero:40px; --t-title:24px; --t-card:17px;
  --t-body:15px; --t-meta:13px; --t-label:12px;
  /* Below --t-label there were eight different values — 6, 6.5, 7, 7.5, 8, 8.5,
     9, 9.5 — and 6px is not a font size, it is a texture. Two rungs: --t-micro
     for tags and mini-calendar labels, --t-dense for the week grid, where
     fitting an hour of the day into a column is the actual constraint. */
  --t-micro:11px; --t-dense:10px;
  --w-reg:400; --w-med:600; --w-bold:700;
}

/* one weight ladder, everywhere */
.screen-title h1,.sheet-head h2,.mini-pop h3,.auth-card h1,
.count-hero b,.answer-hero strong{font-weight:var(--w-bold)}

/* --- headings ------------------------------------------------------------- */
.screen-title h1{font-size:var(--t-title);letter-spacing:-.02em;line-height:1.15}
.screen-title p{font-size:var(--t-body);font-weight:var(--w-reg);line-height:1.5;color:#5f5e57}
.sheet-head h2{font-size:var(--t-title);letter-spacing:-.02em}
.sheet-head p{font-size:var(--t-body);font-weight:var(--w-reg);line-height:1.5;color:#5f5e57}
.section-head h2{font-size:var(--t-card);font-weight:var(--w-med);letter-spacing:-.01em}
.section-head p{font-size:var(--t-body);font-weight:var(--w-reg);color:#5f5e57}
/* Pinned, exactly like .sheet-head.
   -------------------------------------------------------------------------
   A .mini-pop scrolls (max-height:min(86vh,100vh-48px)) and its heading was
   an ordinary block, so on any popup taller than the screen — the day answer,
   the time editor, the filter list — the title scrolled off the top and left
   you inside a scrolling box with nothing naming it. The sheet header was
   fixed for exactly this; the popup never got the same treatment.

   z-index 4 clears the grabber overlay at 2, the same way .sheet-head does. */
.mini-pop h3{font-size:var(--t-card);letter-spacing:-.02em;
  position:sticky;top:0;z-index:4;background:var(--paper);
  margin:0 0 var(--s2);padding:var(--s1) 0 var(--s2)}
.mini-pop p{font-size:var(--t-body);font-weight:var(--w-reg);line-height:1.5;color:#5f5e57}

/* --- cards and rows -------------------------------------------------------- */
.group-main h3,.plan-card h3,.invite-card h3,.dest-card h3,.friend-hero h3,
.plan-chat-head h3,.exec-head h3{font-size:var(--t-card);font-weight:var(--w-med);letter-spacing:-.015em}
.friend-info strong,.task-top strong,.account-top strong,.reco-info strong,
.event-body strong,.notif-top strong,.window-card strong,.scope-card strong,
.nudge-head strong{font-size:var(--t-body);font-weight:var(--w-med)}
.friend-info span,.task-top .task-meta,.account-top span,.reco-info span,
.event-meta,.plan-meta,.member-names,.notif-row span,.nudge-head span,
.next-action{font-size:var(--t-meta);font-weight:var(--w-reg);color:#5f5e57;line-height:1.45}

/* --- labels and pills ------------------------------------------------------ */
.pill,.badge,.state-label,.seg-btn,.plan-filter button,.inline-tabs button,
.filter-chip,.sort-chip,.pref-chip,.time-chip,.nav-label,.side-item,
.weekdays span,.cal-legend,.field span,.sheet h3{font-size:var(--t-label);font-weight:var(--w-med)}
.weekdays span{letter-spacing:.06em;color:#77766f}
.sheet h3{letter-spacing:.1em;color:#6d6b64}

/* --- buttons --------------------------------------------------------------- */
.primary,.secondary,.quiet,.danger{font-size:var(--t-body);font-weight:var(--w-med);
  min-height:44px;padding:var(--s3) var(--s4)}
.text-btn{font-size:var(--t-meta);font-weight:var(--w-med)}

/* --- the floor -------------------------------------------------------------
   Nothing that is meant to be read goes below 12px. Calendar internals are
   deliberately excluded: they are glyphs in a grid, not prose. */
.est-note,.muted-note,.privacy-note,.auth-foot,.handle-status,.scope-note{
  font-size:var(--t-label);font-weight:var(--w-reg);line-height:1.55;color:#5f5e57}

/* --- calendar internals, sized on purpose ---------------------------------- */
.month-day .day-num{font-size:var(--t-label);font-weight:var(--w-med)}
/* lvl4 is NOT white. It already has ink at components.css:51 - 7.66:1 - and
   this line overrode it to white on #74b394, which is 2.44:1. lvl5 is dark
   enough to need white and keeps it. */
.month-day.lvl5 .day-num{color:#fff}
.wk-day b{font-size:var(--t-body);font-weight:var(--w-bold)}
.wk-day span{font-size:var(--t-micro);font-weight:var(--w-med)}
.mini-title{font-size:var(--t-micro);font-weight:var(--w-med)}
.mini-week span{font-size:var(--t-micro);font-weight:var(--w-med)}

/* --- contrast: the muted grey was 4.1:1 ------------------------------------ */
:root{--muted:#5f5e57;--muted2:#6b6a62}

/* --- avatars were 1.80:1 white-on-pale at 8px ------------------------------ */
.avatar{font-size:var(--t-label);font-weight:var(--w-bold);
  background:linear-gradient(145deg,#8fa89a,#5d7568)}
.avatar.small{font-size:var(--t-micro)}
.avatar.tone2{background:linear-gradient(145deg,#a8836f,#7d5a49)}
.avatar.tone3{background:linear-gradient(145deg,#7f8fa8,#54637d)}
.avatar.tone4{background:linear-gradient(145deg,#b09a5f,#836c33)}
.avatar.tone5{background:linear-gradient(145deg,#a3849f,#75577a)}
.avatar.tone6{background:linear-gradient(145deg,#729397,#4b6f73)}

/* --- touch targets --------------------------------------------------------- */
.star,.check,.close-btn,.arrow-btn,.nav-item{min-width:44px;min-height:44px}
.star{font-size:var(--t-card)}

/* --- The answer, where the question used to be ------------------------------
   Find My's promise is not a beautiful map. It is that you do not have to
   look. This is the computed answer at the top of the screen; the calendar
   below it is the evidence, not the interface. */
.answer-hero{padding:var(--s1) 2px var(--s5)}
.ah-when{font-size:var(--t-label);font-weight:var(--w-med);letter-spacing:.06em;
  text-transform:uppercase;color:var(--muted2);margin-bottom:var(--s2)}
.answer-hero strong{display:block;font-size:var(--t-hero);line-height:1.05;
  letter-spacing:-.035em;font-weight:var(--w-bold);color:var(--ink)}
.answer-hero p{margin:8px 0 0;font-size:var(--t-body);font-weight:var(--w-reg);color:var(--muted)}
.ah-actions{display:flex;gap:var(--s2);margin-top:16px}
.ah-actions .primary{padding:var(--s3) var(--s5)}
@media(max-width:520px){ .answer-hero strong{font-size:var(--t-title)} }

/* The last two strays: buttons sat at 14 and inherited <p>/<b> at 14, one step
   from the 15 used by every card title. Six sizes total now — 40 / 24 / 17 /
   15 / 13 / 12 — plus the deliberately smaller glyphs inside calendar grids. */
.primary,.secondary,.quiet,.danger{font-size:var(--t-body)}
.tng b,.count-hero b{font-size:var(--t-card)}

/* ==========================================================================
   MEASURE, SPACE AND SURFACE

   Three problems the audit measured, fixed together because they are the same
   problem: values chosen one at a time instead of from a system.

   1. Nothing had a maximum width. On Plans the two buttons in a waiting card
      were 521x42 each — half a metre wide — on a page whose content ended at
      y617 of a 900px viewport. Stretched sideways, starved vertically.
   2. Twenty distinct padding values and nine radii, where the tokens define
      four. 5px and 4px accounted for 202 uses between them.
   3. Nine light neutrals, three of which sat within 3/255 of each other, plus
      one cool grey (#f0f0f0) in an otherwise warm system.
   ========================================================================== */
:root{
  --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:24px; --s6:32px;
  --r-card:20px; --r-control:12px; --r-pill:999px;
  --surface-page:#f5f2ea; --surface-card:#ffffff; --surface-sunk:#eeeae1;
}

/* --- measure --------------------------------------------------------------- */
@media(min-width:860px){
  .content > .screen{max-width:1100px}
  .content > .screen.friends-wide{max-width:none}
}
/* buttons are as wide as their label, not as wide as the room */
.button-row > *,.sheet-actions > *,.invite-actions > *{flex:0 1 auto}
.button-row,.sheet-actions{justify-content:flex-end}
.button-row .quiet:first-child,.sheet-actions .secondary:first-child{margin-right:auto}
.primary.wide,.secondary.wide,.danger.wide,.text-btn.wide{width:100%;flex:1 1 auto}
.plan-filter,.segmented,.inline-tabs{max-width:320px}

/* --- surfaces: three, not nine --------------------------------------------- */
.card{background:var(--surface-card)}
.segmented,.plan-filter button,.inline-tabs button,.quiet{background:var(--surface-sunk)}

/* --- radii: three, not nine ------------------------------------------------ */
.card,.sheet,.mini-pop,.auth-card{border-radius:var(--r-card)}
.primary,.secondary,.quiet,.danger,.input,.month-day,.mini-cal,
.segmented,.filter-chip,.social-btn{border-radius:var(--r-control)}
.pill,.badge,.pref-chip,.time-chip,.sort-chip,.chip-toggle button{border-radius:var(--r-pill)}
.avatar,.profile-dot,.nav-photo{border-radius:50%}

/* --- one shadow ------------------------------------------------------------ */
.card,.social-btn{box-shadow:0 1px 2px rgba(40,35,28,.06)}
.sheet,.mini-pop{box-shadow:0 24px 70px rgba(38,33,25,.20)}

/* --- the last stray size --------------------------------------------------- */
.ob-point p,.empty span{font-size:var(--t-body)}

/* --- remaining tap targets ------------------------------------------------- */
.seg-btn,.plan-filter button,.inline-tabs button,.filter-chip,.pref-chip,
.time-chip,.sort-chip,.text-btn,.vote-option,.person-check.me{min-height:44px}
.custom-link,.notif-row{min-height:44px}

/* ==========================================================================
   TWO PANES ON DESKTOP

   The Calendar tab was a 700px month with the day hidden behind a modal, on a
   1440px screen — a phone layout with a rail bolted on. Ink coverage measured
   under 5%. Month left, day right, same pattern the group workspace already
   proved.
   ========================================================================== */
@media(min-width:1080px){
  .cal-wide{display:grid;grid-template-columns:minmax(0,1fr) 380px;gap:var(--s5);align-items:start}
  .cal-panel{background:var(--surface-card);border:1px solid var(--line);
    border-radius:var(--r-card);padding:var(--s4);position:sticky;top:var(--s4);
    max-height:calc(100vh - 140px);overflow:auto}
  .cal-panel-head h2{font-size:var(--t-card);font-weight:var(--w-med);letter-spacing:-.015em;margin:0 0 var(--s3)}
  .cal-panel .time-grid{max-height:340px;overflow:auto}
  .cal-panel .choice-grid{grid-template-columns:1fr}
  /* the day you are looking at, marked on the month */
  .month-day.picked{box-shadow:inset 0 0 0 2px var(--ink)}
  /* Plans stops being a single column of full-width cards */
  .plans-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--s3);align-items:start}
  .plans-grid .plan-card{margin-bottom:0}
}

/* --- the month has to fit the window it is in ------------------------------
   Fixed cell heights meant the last row sat below the fold on any short
   window — 28 of 42 cells visible at 900px tall. Square cells are right on a
   phone, where the page scrolls anyway. On desktop the rows take whatever
   height is left after the chrome, floored at a comfortable tap target. */
@media(min-width:1080px){
  /* --month-row is measured by fitMonth() in ui-core.js after every render and
     on every resize. The fallback is only what shows for the one frame before
     that runs. */
  .month-day{aspect-ratio:auto;min-height:var(--month-row, 56px);height:var(--month-row, 56px)}
  /* the screen title repeats what the top bar and the rail already say, and
     on a short window it costs a whole row of the month */
  .cal-wide ~ .cal-legend,.cal-wide{margin-top:0}
}
@media(min-width:1080px) and (max-height:900px){
  .content > .screen > .screen-title{margin-bottom:var(--s2)}
  .content > .screen > .screen-title h1{font-size:var(--t-title)}
  .content > .screen > .screen-title p{display:none}
}
@media(min-width:1080px) and (max-height:760px){
  .day-dots{display:none}
  /* on a genuinely short window the evening strip is the thing to give,
     not another row of the month */
  .week-strip{padding:var(--s3)}
  .week-payoff{display:none}
}

/* --- the week grid was a scroller inside a scroller ------------------------
   63% of the day visible through a nested scrollbar. Let the page scroll. */
@media(min-width:1080px){
  .wk-body{max-height:none;overflow:visible}
}

/* === INTERACTION STATES ======================================================
   An audit of the whole stylesheet found one :active rule (and it was a rule
   that *removed* the effect), one :disabled rule, and hover on 14 selectors out
   of 1,128. Eight classes carried cursor:pointer with no hover at all, so on a
   desktop mouse most of the app was dead to the touch, and on a phone — where
   there is no hover — nothing anywhere acknowledged a press. These are not
   decoration: they are the only confirmation the app heard you.             */

/* Pressed. The transform is what a finger reads; the filter is what a mouse
   reads. Both are cheap and neither reflows. */
.primary:active,.secondary:active,.quiet:active,.danger:active,
.social-btn:active,.text-btn:active,.top-btn:active,.profile-dot:active,
.nav-item:active,.side-item:active,.chip-toggle button:active,
.segmented button:active,.view-switch button:active{transform:translateY(1px)}

/* Hover on everything that already said cursor:pointer. Pointer-only so a
   touch device never gets a hover state stuck on after a tap. */
@media(hover:hover){
  .friend-row:hover,.plan-card:hover,.group-card:hover,.invite-card:hover,
  .reco-card:hover,.vote-option:hover,.setting-row:hover,.audience-card:hover,
  .location-card:hover,.choice:hover,.pref-tile:hover,.category:hover,
  .routine-row:hover,.block-row:hover,.notif-row:hover{
    border-color:var(--line2);background:#fdfdfc}
  .secondary:hover,.quiet:hover{border-color:var(--line2);background:var(--warm)}
  .danger:hover{background:#fdf3f2}
  .top-btn:hover,.profile-dot:hover{background:var(--warm)}
}

/* Disabled. Before this, the 42 greyed-out days from the neighbouring months
   carried a real disabled attribute, computed cursor:pointer, and gave no
   feedback at all when clicked — they looked live and weren't. */
button[disabled],button:disabled{cursor:not-allowed;opacity:.45}
.month-day[disabled],.month-day.other{cursor:default;pointer-events:none}
button[disabled]:active{transform:none}

/* Focus must survive the scroll containers. .content and .app-shell both clip
   overflow, so an outline drawn 2px outside the element was cut off at the
   container edge. A ring drawn with box-shadow cannot clip. Scoped to things a
   user actually tabs to — a bare :focus-visible would also ring the sheet,
   which takes focus programmatically when it opens. */
button:focus-visible,a:focus-visible,input:focus-visible,
select:focus-visible,textarea:focus-visible,[role="button"]:focus-visible{
  outline:none;box-shadow:0 0 0 2px #fff,0 0 0 4px var(--green)}
.month-day:focus-visible{box-shadow:inset 0 0 0 2px var(--green)}

/* Touch targets. The earlier pass named five classes and missed the rest —
   47 buttons on Calendar were still under 44px at phone width. The month cell
   is deliberately left out of this list: on desktop its height is a clamp that
   fills the window, and a flat min-height here would win on source order and
   pin every row to 44px on a tall screen. */
.top-btn,.profile-dot,.view-switch button,.segmented button,
.chip-toggle button,.filter-chip,.pref-chip,.day-chip,
.view-toggle button,.group-open{min-height:44px}
@media(max-width:859px){ .month-day{min-height:44px} }

/* Async has to land somewhere. There is a live backend behind this UI and no
   loading or error style existed anywhere, so the first slow network would
   have every screen either flash empty or freeze. */
.is-loading{opacity:.55;pointer-events:none}
.is-loading .primary{position:relative;color:transparent}
.is-loading .primary:after{content:"";position:absolute;inset:0;margin:auto;
  width:16px;height:16px;border-radius:50%;border:2px solid rgba(255,255,255,.4);
  border-top-color:#fff;animation:spin .7s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}
.form-error{display:block;color:var(--danger);font-size:var(--t-label);
  font-weight:var(--w-med);margin:var(--s2) 0 0}
@media(prefers-reduced-motion:reduce){
  *,*:before,*:after{animation-duration:.01ms!important;transition-duration:.01ms!important}
}

/* === COACH MARKS ============================================================
   The tour points at the real screen instead of covering it. The dim is drawn
   as a huge spread box-shadow on a transparent "hole", which cuts a genuine
   window through the overlay — no SVG mask, no second canvas, and the element
   underneath stays perfectly crisp because nothing is drawn on top of it.   */
.coach-layer{position:fixed;inset:0;z-index:120;pointer-events:none}
.coach-hole{position:absolute;border-radius:var(--r-control);
  box-shadow:0 0 0 9999px rgba(28,25,20,.58), 0 0 0 2px var(--green) inset;
  transition:top .22s ease, left .22s ease, width .22s ease, height .22s ease}
.coach-card{position:absolute;pointer-events:auto;background:#fff;border:1px solid var(--line);
  border-radius:var(--r-card);padding:var(--s4);box-shadow:0 18px 44px rgba(40,35,28,.24);
  transition:top .22s ease, left .22s ease}
.coach-count{font-size:var(--t-label);letter-spacing:.12em;font-weight:var(--w-med);color:var(--muted2);margin-bottom:var(--s2)}
.coach-card h3{margin:0 0 var(--s2);font-size:var(--t-card);letter-spacing:-.02em}
.coach-card p{margin:0;font-size:var(--t-meta);line-height:1.45;color:#57544e}
.coach-actions{display:flex;align-items:center;justify-content:space-between;gap:var(--s3);margin-top:var(--s4)}
.coach-nav{display:flex;gap:var(--s2)}
.coach-actions button{min-height:44px}
.coach-actions .primary,.coach-actions .secondary{padding:0 16px}
/* The card owns the whole bottom of a phone screen rather than floating in a
   corner of it. */
@media(max-width:520px){
  .coach-card{left:12px!important;right:12px;width:auto!important}
  .coach-nav{flex:1;justify-content:flex-end}
}

/* === THIS WEEK strip ========================================================
   Replaces the tonight-only strip. Seven taps' worth of answer available in
   one glance, and the payoff line under it says why answering is worth it. */
.week-nav{display:flex;align-items:center;gap:var(--s2);flex:0 0 auto}
.week-days{display:grid;grid-template-columns:repeat(7,1fr);gap:var(--s2);margin-top:var(--s3)}
/* The day is a label; the button under it is the answer. Making the whole tile
   the control meant the only way to find out what tapping did was to tap it. */
.week-day{display:grid;gap:3px;place-items:center;padding:var(--s2) 4px var(--s2);
  border:1px solid var(--line);border-radius:var(--r-control);background:#fff}
.wd-set{width:100%;min-height:26px;border:1px solid var(--line);border-radius:8px;
  background:#fff;font-size:var(--t-micro);font-weight:var(--w-med);color:var(--muted);
  cursor:pointer;padding:0 2px}
.week-day.free .wd-set{background:var(--green);border-color:var(--green);color:#fff}
.week-day.busy .wd-set{background:var(--gray-mid);border-color:var(--gray-mid);color:#5f5d57}
.week-day.unset .wd-set{border-style:dashed}
.wd-dow{font-size:var(--t-micro);letter-spacing:.06em;color:var(--muted2);font-weight:var(--w-med)}
.wd-num{font-size:var(--t-body);font-weight:var(--w-med);letter-spacing:-.02em}
/* Same three states, same colours, as the personal ramp on the Calendar tab —
   this strip is a shortcut into that calendar, not a second vocabulary. */
.week-day.free{background:var(--d-free);border-color:var(--d-free-line)}
.week-day.busy{background:var(--d-busy);border-color:var(--d-busy-line)}
.week-day.busy .wd-num{color:#7d7970}
.week-day.unset{border-style:dashed;border-color:var(--d-unset-line)}
@media(max-width:400px){
  .week-day{min-height:50px;border-radius:var(--r-control)}
  .wd-num{font-size:var(--t-meta)}
}

/* the SMS link is an <a> so the OS handler fires, but it has to read as a button */
.invite-sms{margin-top:var(--s2);display:block;text-align:center;text-decoration:none;
  padding:var(--s3) var(--s4);border-radius:var(--r-control);min-height:44px;line-height:20px}
/* Username row: two actions now that it can be changed, not one */
.handle-actions{display:flex;gap:var(--s2);flex:0 0 auto}

/* === GROUP FREE-DAYS ========================================================
   A group card used to be a name beside a whole shaded month. The month is the
   right thing for browsing and the wrong thing for answering: finding the days
   that work means reading sixty squares and comparing them by eye. So the card
   leads with the answer — the days that work — and the month moved behind
   "See the month". */
.free-filter{display:flex;align-items:center;gap:var(--s2);flex-wrap:wrap;margin:0 2px var(--s3)}
.ff-label,.ff-suffix{font-size:var(--t-meta);color:var(--muted)}
.ff-chips{display:flex;align-items:center;gap:var(--s2);flex-wrap:wrap}
.ff-chips .filter-chip{min-height:44px;padding:0 14px}
.filter-chip.selected{background:var(--ink);border-color:var(--ink);color:#fff}

.group-days{padding:var(--s4);margin-bottom:var(--s3)}
.gd-head{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--s3)}
.gd-id{display:flex;align-items:center;gap:var(--s3);min-width:0}
.gd-id h3{margin:0;font-size:var(--t-card);letter-spacing:-.025em}
.gd-id span{display:block;font-size:var(--t-meta);color:var(--muted);margin-top:2px}
.gd-foot{margin-top:var(--s3);padding-top:var(--s3);border-top:1px solid var(--line)}

/* The list scrolls inside the card instead of growing it. Two months of good
   days is 20–40 rows, and a card that tall pushes every other group off the
   screen — the point of this view is comparing groups, which you cannot do if
   only one fits. Fixed rectangle, scroll inside. */
.free-days{display:grid;gap:var(--s2);margin-top:var(--s3);
  max-height:216px;overflow-y:auto;overscroll-behavior:contain;
  padding-right:2px;scrollbar-width:thin}
.free-days-more{margin-top:var(--s2);font-size:var(--t-label);color:var(--muted2);text-align:center}
.free-day{display:grid;grid-template-columns:minmax(96px,auto) auto minmax(60px,1fr);
  align-items:center;gap:var(--s3);width:100%;text-align:left;min-height:44px;
  padding:var(--s2) var(--s3);border:1px solid var(--line);border-radius:var(--r-control);background:#fff;cursor:pointer}
.fd-when{font-size:var(--t-meta);letter-spacing:-.01em}
.fd-when b{font-weight:var(--w-med)}
.fd-count{font-size:var(--t-meta);color:var(--muted);font-weight:var(--w-med);white-space:nowrap}
/* The bar is the scannable part: you compare days by length, not by reading
   two numbers and doing the division yourself. */
.fd-bar{height:6px;border-radius:999px;background:var(--gray-soft);overflow:hidden}
.fd-bar i{display:block;height:100%;border-radius:999px;background:var(--green)}
.fd-unknown{grid-column:1 / -1;font-size:var(--t-label);color:var(--muted2)}
@media(max-width:520px){
  .free-day{grid-template-columns:1fr auto;gap:var(--s2) var(--s3)}
  .fd-bar{grid-column:1 / -1}
}

/* === COLD START =============================================================
   One narrow column and nothing else. The `.content >` ancestor is not
   decoration: `.content > .screen{max-width:880px}` is set further up and beats
   a plain `.cold-start` on specificity, which is why this screen kept rendering
   880px wide. The sample month that used to sit under it is gone — a calendar
   with nobody in it is a picture of the thing you do not have.             */
.content > .screen.cold-start{max-width:360px;margin:0 auto;padding-top:var(--s6)}
.cold-start h1{margin:0;font-size:var(--t-card);letter-spacing:-.02em}
.cold-start .cs-sub{margin:var(--s2) 0 var(--s4);font-size:var(--t-meta);color:var(--muted);line-height:1.45}
.cold-start .handle-hero{padding:var(--s3) var(--s4)}
.cold-start .handle-hero strong{font-size:var(--t-body);margin-top:2px}
.cs-actions{display:grid;grid-template-columns:1fr 1fr;gap:var(--s2);margin-top:var(--s2)}
.cs-actions button{min-height:46px}
@media(max-width:400px){ .cs-actions{grid-template-columns:1fr} }

/* === FRIENDS: month, then the day beside it =================================
   The same shape the Calendar tab uses. Friends already had list + month; the
   day still opened as a sheet on top, which hides the month you were comparing
   against. Three columns is a lot of screen, so below 1400 the day sits under
   the month instead of beside it, and the whole thing collapses to one column
   below 1080 where the sheet takes over again. */
@media(min-width:1080px){
  .fw-hero .cal-wide{grid-template-columns:minmax(0,1fr);gap:var(--s4)}
  .fw-hero .cal-panel{position:static;max-height:none;border-top:1px solid var(--line);
    padding-top:var(--s4)}
}
@media(min-width:1400px){
  .friends-wide{grid-template-columns:minmax(300px,340px) minmax(0,1fr)}
  .fw-hero .cal-wide{grid-template-columns:minmax(0,1fr) 320px;align-items:start}
  .fw-hero .cal-panel{border-top:0;padding-top:0;position:sticky;top:0;
    max-height:calc(100vh - 150px);overflow:auto}
}
/* the day the panel is describing, marked on the month */
.fw-hero .month-day.picked{box-shadow:inset 0 0 0 2px var(--ink)}

/* the group you already are, before you have made one */
.empty-group{display:block;width:100%;text-align:left;cursor:pointer}
.empty-group .gd-head{align-items:center}
.gd-chev{font-size:var(--t-card);color:var(--muted2)}

/* Held while the first pull is in flight — see bootSkeleton(). Shapes only, no
   invented names or numbers: a placeholder that guesses at content is just a
   different way of showing the wrong thing. */
.boot-skeleton{padding:var(--s4) 0}
.sk-line{height:14px;border-radius:8px;background:var(--gray-soft);margin-bottom:var(--s2)}
.sk-line.lg{width:60%;height:24px}
.sk-line.sm{width:38%;margin-bottom:var(--s5)}
.sk-card{height:96px;border-radius:var(--r-lg);background:var(--gray-soft);margin-bottom:var(--s3)}
.boot-skeleton > *{animation:skPulse 1.4s ease-in-out infinite}
.sk-card:nth-child(4){animation-delay:.12s}
.sk-card:nth-child(5){animation-delay:.24s}
@keyframes skPulse{0%,100%{opacity:.55}50%{opacity:.9}}

/* the group sheet's two views */
.gw-switch{margin-bottom:var(--s4)}
.free-days.tall{max-height:min(52vh,420px)}
.request-card .avatar{flex:0 0 auto}

/* === GROUP PANEL: days first, month behind a tab ============================
   The panel beside the friends list used to be a whole shaded month with a day
   panel permanently open beside it — for a day nobody had picked, which on an
   empty group read "0 of 0 free this day" next to a Propose button. It leads
   with the answer now: a short list of the days that work, in sentences.    */
.hero-tabs{display:flex;background:var(--warm);border:1px solid var(--line);padding:3px;border-radius:var(--r-control);gap:2px}
.hero-tabs button{border:0;background:transparent;border-radius:8px;padding:0 12px;min-height:38px;
  font-size:var(--t-label);font-weight:var(--w-med);color:var(--muted);cursor:pointer}
.hero-tabs button.on{background:#fff;color:var(--ink);box-shadow:0 1px 3px rgba(40,35,28,.08)}
.fw-hero-head{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--s3)}
.fw-hero-head h2{margin:0}

.hero-days{display:grid;gap:var(--s2);margin-top:var(--s3);
  max-height:calc(100vh - 260px);overflow-y:auto;padding-right:2px}
.hero-day{display:grid;grid-template-columns:1fr auto;gap:2px var(--s3);width:100%;text-align:left;
  padding:var(--s3) var(--s3);min-height:44px;border:1px solid var(--line);border-radius:var(--r-control);background:#fff;cursor:pointer}
.hd-who{font-size:var(--t-body);font-weight:var(--w-med);letter-spacing:-.01em}
.hd-when{font-size:var(--t-meta);color:var(--muted);grid-column:1}
.hd-count{grid-row:1 / span 2;align-self:center;font-size:var(--t-meta);color:var(--green);
  font-weight:var(--w-med);white-space:nowrap}
@media(hover:hover){ .hero-day:hover{border-color:var(--line2);background:#fdfdfc} }

/* Square month cells wherever a month is shown beside something else. The fit
   logic sets an explicit row height for the full-width Calendar tab; a month
   sharing a column with a panel has room to just be square. */
.fw-hero .month-day,.cal-panel ~ .cal-main .month-day{aspect-ratio:1;height:auto;min-height:0}

/* "See the month" was a grey slab the width of its text. It is a link. */
.gd-foot{display:flex;justify-content:space-between;align-items:center;gap:var(--s3)}
.group-open{background:transparent;border:0;padding:8px 0;min-height:44px;
  font-size:var(--t-meta);font-weight:var(--w-med);color:var(--green);cursor:pointer}
@media(hover:hover){ .group-open:hover{text-decoration:underline} }
.hero-foot{display:flex;justify-content:space-between;align-items:center;gap:var(--s3);
  margin-top:var(--s3);padding-top:var(--s3);border-top:1px solid var(--line)}
.text-btn.danger-text{color:var(--danger)}

/* === FRIENDS LIST ===========================================================
   A friend row is a name, a username, and the next three windows when you are
   both free. It used to say "Free most of today" — a fact about one day that
   you cannot act on. These are three taps that each start a plan at a time
   that already works for both of you.                                       */
.list-head{display:flex;align-items:center;justify-content:space-between;gap:var(--s3);
  margin:var(--s5) 2px var(--s3)}
.list-head.compact{margin:var(--s3) 2px var(--s2)}
.list-head h2{margin:0;font-size:var(--t-body);letter-spacing:-.02em}
.list-tools{display:flex;align-items:center;gap:var(--s2)}
.filter-btn{display:inline-flex;align-items:center;gap:var(--s2);min-height:38px;padding:0 12px;
  border:1px solid var(--line);border-radius:999px;background:#fff;cursor:pointer;
  font-size:var(--t-label);font-weight:var(--w-med);color:var(--muted)}
.filter-btn.on{background:var(--ink);border-color:var(--ink);color:#fff}
@media(hover:hover){ .filter-btn:hover{border-color:var(--line2)} }

.friend-row{display:grid;grid-template-columns:minmax(140px,1fr) auto auto;
  align-items:center;gap:var(--s3);padding:var(--s3) var(--s4);margin-bottom:var(--s2)}
.fr-id{display:flex;align-items:center;gap:var(--s3);min-width:0;background:transparent;
  border:0;padding:0;text-align:left;cursor:pointer;min-height:44px}
.fr-id .friend-info{display:block;min-width:0}
.fr-id strong{display:block;font-size:var(--t-body);letter-spacing:-.01em}
.fr-id .friend-info > span{display:block;font-size:var(--t-meta);color:var(--muted);margin-top:1px}
.fr-times{display:flex;gap:var(--s2);flex-wrap:wrap;justify-content:flex-end}
.time-slot{display:grid;gap:1px;place-items:center;min-height:44px;padding:var(--s2) var(--s2);
  border:1px solid var(--line);border-radius:12px;background:#fff;cursor:pointer;white-space:nowrap}
.time-slot b{font-size:var(--t-label);font-weight:var(--w-med)}
.time-slot span{font-size:var(--t-micro);color:var(--muted)}
@media(hover:hover){ .time-slot:hover{border-color:var(--green);background:var(--green-soft)} }
.fr-none{font-size:var(--t-meta);color:var(--muted2)}
.fr-actions{display:flex;align-items:center;gap:2px}
@media(max-width:720px){
  .friend-row{grid-template-columns:1fr auto}
  .fr-times{grid-column:1 / -1;justify-content:flex-start}
}

/* the filter menu */
.filter-menu{display:grid;gap:var(--s2)}
.filter-opt{display:grid;gap:2px;text-align:left;width:100%;padding:var(--s3) var(--s4);min-height:44px;
  border:1px solid var(--line);border-radius:var(--r-control);background:#fff;cursor:pointer}
.filter-opt strong{font-size:var(--t-body);font-weight:var(--w-med)}
.filter-opt span{font-size:var(--t-meta);color:var(--muted)}
.filter-opt.on{border-color:var(--ink);background:var(--warm)}

/* group card head + the group's own page */
.gd-id{display:flex;align-items:center;gap:var(--s3);min-width:0;background:transparent;border:0;
  padding:0;text-align:left;cursor:pointer;min-height:44px}
.gd-id strong{display:block;font-size:var(--t-card);letter-spacing:-.02em}
.gd-id > span > span{display:block;font-size:var(--t-meta);color:var(--muted);margin-top:1px}
/* Right-hand end of the group card, tight. Was three controls with an 8px
   gap; the third has gone and the remaining two belong together - a filter
   and a favourite are both 'how I look at this group'. */
.gd-tools{display:flex;align-items:center;justify-content:flex-end;gap:var(--s1);flex:0 0 auto;margin-left:auto}
.gp-people{display:flex;flex-wrap:wrap;gap:var(--s3)}
.gp-person{display:flex;align-items:center;gap:var(--s2);font-size:var(--t-meta)}

/* === VOTING: counters, IDC, adding an option ================================ */
.counter-line{display:flex;align-items:center;justify-content:space-between;gap:var(--s3);
  margin-top:var(--s2);padding:var(--s2) var(--s3);border-radius:12px;background:var(--warm);
  border:1px solid var(--line);font-size:var(--t-meta);color:#5f5d57;line-height:1.4}
.counter-line b{font-weight:var(--w-med);color:var(--ink)}
.text-btn.small{padding:4px 8px;font-size:var(--t-label);color:var(--green);flex:0 0 auto;min-height:36px}
.idc-line{margin-top:var(--s2);font-size:var(--t-meta);color:var(--muted2)}
.vote-tools{flex-wrap:wrap}

/* who owns a task — several people, not one */
.who-pick{display:flex;flex-wrap:wrap;gap:var(--s2);margin-top:var(--s2)}
.who-chip{min-height:40px;padding:0 12px;border:1px solid var(--line);border-radius:999px;
  background:#fff;font-size:var(--t-label);font-weight:var(--w-med);color:var(--muted);cursor:pointer}
.who-chip.on{background:var(--ink);border-color:var(--ink);color:#fff}

/* === THE DESKTOP SHELL ======================================================
   On a computer the app was a rounded card floating on a tinted page, with the
   real window showing round the outside of it. That frame is right for the
   phone build inside its device mock and wrong for the desktop app, which
   should just be the screen. Ovals are for avatars; everything else is a
   rectangle or a square.                                                    */
@media(min-width:860px){
  html.force-desktop, html.force-desktop body{background:var(--bg)}
  html.force-desktop .app-wrap{padding:0;background:var(--bg);min-height:100vh}
  html.force-desktop .app-shell{max-width:none;width:100%;min-height:100vh;height:100vh;
    border:0;border-radius:0;box-shadow:none;overflow:hidden}
  html.force-desktop .topbar{border-radius:0}
  html.force-desktop .backdrop{border-radius:0}
  html.force-desktop .side-nav{border-right:1px solid var(--line)}
}

/* === FRIENDS / GROUPS layout ================================================ */
.segmented.centred{max-width:340px;margin-left:auto;margin-right:auto}
.list-head .secondary{min-height:40px;padding:0 14px}

/* Checkbox first: it is what turns the list into a multi-select, and it was
   sitting past the times where you had to hunt for it. */
.friend-row{grid-template-columns:auto minmax(130px,1fr) auto}
/* Select and favourite sit together on the left. The checkbox is what turns
   the list into a multi-select and it was past the times, where you had to
   hunt for it; the star belongs next to it rather than at the far end. */
.fr-picks{display:flex;align-items:center;gap:2px}
.fr-label{font-size:var(--t-label);color:var(--muted2);font-weight:var(--w-med);
  align-self:center;margin-right:2px;white-space:nowrap}

/* Two months of days scroll inside the card rather than making the card two
   months tall. */
.free-days.scrolls{max-height:260px;overflow-y:auto;padding-right:3px}
.free-days.scrolls.tall{max-height:min(46vh,420px)}

/* the group's own page */
.gp-top{display:flex;align-items:center;justify-content:space-between;gap:var(--s3);flex-wrap:wrap}
.gp-people{display:flex;flex-wrap:wrap;gap:var(--s2) var(--s3)}
.gp-person{display:inline-flex;align-items:center;gap:var(--s2);font-size:var(--t-meta)}
.gp-person .avatar{margin:0}
/* Delete on the left, leave on the right — the destructive one is not where a
   thumb lands by accident. */
.gp-foot{display:flex;align-items:center;justify-content:space-between;gap:var(--s3);
  margin-top:var(--s4);padding-top:var(--s3);border-top:1px solid var(--line)}

/* the filter sheet: conditions stack, so they are laid out as sections */
.filter-menu{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:var(--s2)}
.filter-opt{min-height:44px;padding:var(--s2) 12px}
.filter-opt strong{font-size:var(--t-meta)}

/* === THE PLAN AS A PAGE =====================================================
   A plan used to open as a sheet you had to dismiss to see anything else. It
   is a conversation with a vote and a checklist attached, which is a screen. */
.plan-page{display:flex;flex-direction:column;min-height:0}
.pp-head{display:flex;align-items:flex-start;gap:var(--s3);margin-bottom:var(--s3)}
.back-btn{width:40px;height:44px;border:0;background:transparent;font-size:26px;line-height:1;
  color:var(--ink);cursor:pointer;flex:0 0 auto;margin-left:-8px}
.pp-title{flex:1;min-width:0}
.pp-title h1{margin:0;font-size:var(--t-title);letter-spacing:-.035em;line-height:1.05}
.pp-title p{margin:5px 0 0;font-size:var(--t-meta);color:var(--muted)}
.pp-actions{display:flex;gap:var(--s2);flex-wrap:wrap;margin-bottom:var(--s3)}
.pp-actions button{min-height:44px;padding:0 14px}
.pp-summary{padding:var(--s3) var(--s4);border:1px solid var(--line);border-radius:var(--r-card);
  background:var(--warm);font-size:var(--t-meta);line-height:1.5;margin-bottom:var(--s3)}
.pp-summary b{font-weight:var(--w-med)}

/* The thread scrolls on its own and opens at the newest message. A page that
   grows without limit puts the composer below the fold exactly when there is
   most to say. */
.pp-chat{flex:1;min-height:200px;max-height:min(52vh,460px);overflow-y:auto;
  display:flex;flex-direction:column;gap:var(--s3);padding:var(--s4);
  border:1px solid var(--line);border-radius:var(--r-card);background:var(--surface-card,#fff)}
.pp-chat .message{display:flex;gap:var(--s2);align-items:flex-start}
.pp-chat .message.mine{flex-direction:row-reverse}
.pp-chat .message.mine .bubble{background:var(--green-soft);border-color:#cddfd2}
.pp-composer{display:flex;gap:var(--s2);margin-top:var(--s2)}
.pp-composer input{flex:1;border:1px solid var(--line2);border-radius:var(--r-control);
  padding:var(--s3) var(--s4);font-size:var(--t-body);background:#fff;min-height:48px}
.pp-composer button{width:48px;min-height:48px;border-radius:var(--r-control);font-size:var(--t-card)}

/* an editable itinerary time reads as editable */
.timeline-time.editable{border:1px dashed var(--line2);background:#fff;border-radius:var(--r-control);
  padding:4px 8px;min-height:36px;cursor:pointer;font:inherit;color:inherit}
@media(hover:hover){ .timeline-time.editable:hover{border-color:var(--green);color:var(--green)} }

/* === ACTIONS THAT STAY WITH YOU =============================================
   A long list of options put Continue at the very bottom, so choosing the
   second of forty things meant scrolling past the other thirty-eight to move
   on. The actions stick to the bottom of the sheet instead.                 */
.sticky-actions{position:sticky;bottom:0;z-index:5;display:flex;gap:var(--s2);
  justify-content:flex-end;align-items:center;margin:var(--s4) calc(var(--s4) * -1) calc(var(--s4) * -1);
  padding:var(--s3) var(--s4);
  background:linear-gradient(180deg, rgba(255,253,249,0), var(--paper) 38%);
  border-top:1px solid var(--line)}
.sticky-actions button{min-height:48px;padding:0 20px}
.sticky-actions .primary{flex:0 0 auto}
/* Every sheet's action row gets the same treatment — the pattern is the point,
   not one screen's exception. */
.sheet .sheet-actions{position:sticky;bottom:0;z-index:5;
  background:linear-gradient(180deg, rgba(255,253,249,0), var(--paper) 38%);
  padding-top:var(--s3);padding-bottom:var(--s2)}

/* === DESTINATION FILTERS ==================================================== */
.dest-filters{display:grid;gap:var(--s2);margin-bottom:var(--s3);padding:var(--s3);
  border:1px solid var(--line);border-radius:var(--r-card);background:var(--warm)}
.df-group{display:flex;align-items:center;gap:var(--s2);flex-wrap:wrap}
.df-label{font-size:var(--t-label);color:var(--muted2);font-weight:var(--w-med);
  min-width:78px;flex:0 0 auto}
.df-group .filter-chip{min-height:36px;padding:0 11px;font-size:var(--t-label)}
.dest-card{position:relative}
.dest-shortlist{position:absolute;top:10px;right:10px;z-index:2;min-height:36px;padding:0 12px;
  border:1px solid var(--line2);border-radius:999px;background:rgba(255,255,255,.94);
  font-size:var(--t-label);font-weight:var(--w-med);cursor:pointer;backdrop-filter:blur(6px)}
.dest-shortlist.on{background:var(--ink);border-color:var(--ink);color:#fff}

/* === GROUP CARDS SIDE BY SIDE =============================================== */
@media(min-width:720px){
  /* auto-FIT, not auto-fill. auto-fill keeps the empty column tracks, so a
     single group sat in a 434px card with 574px of empty page beside it —
     the grid was reserving space for groups that do not exist. auto-fit
     collapses the empty tracks, so one group fills the row, two sit side by
     side, and three wrap. */
  .groups-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(340px,1fr));
    gap:var(--s3);align-items:stretch}
  .groups-grid .group-days{margin-bottom:0}
}
.group-days{padding:var(--s3)}
.gd-id strong{font-size:var(--t-body)}
.group-days .free-days.scrolls{max-height:190px}
.group-days .free-day{padding:var(--s2);min-height:40px}

/* === GROUP CARDS: one size, two per row ======================================
   Cards sized by their contents made a wall of different-height boxes, and a
   group with one good day looked like a mistake next to one with nine. Every
   card is the same height; the days scroll inside it. */
/* Equal heights are worth having when cards sit in a row together — a group
   with one good day should not look like a mistake beside one with nine. They
   are not worth having when there is one card: a fixed 290px box holding four
   days is 180px of blank card. Stretch inside a row, shrink to fit alone. */
.group-days{display:flex;flex-direction:column;max-height:290px}
.group-days .gd-head{flex:0 0 auto}
/* As many day columns as the card is actually wide enough for. It was pinned
   at two, so a card given the full width still scrolled its days two-abreast
   through a tiny window while the rest of the card sat empty. */
.free-days.grid{flex:1;min-height:0;overflow-y:auto;margin-top:var(--s3);padding-right:3px;
  display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));
  gap:var(--s2);align-content:start}
.free-days.grid .free-day{display:grid;grid-template-columns:1fr;gap:1px;text-align:left;
  align-content:center;min-height:56px;padding:var(--s2)}
.free-days.grid .fd-when{font-size:var(--t-meta);font-weight:var(--w-med);letter-spacing:-.01em}
.free-days.grid .fd-sub{font-size:var(--t-micro);color:var(--muted2)}
.free-days.grid .fd-sub:empty{display:none}
.free-days.grid .fd-count{font-size:var(--t-micro);color:var(--green);font-weight:var(--w-med)}
/* Green means everyone. A multi-day run was outlined green as well, so the
   colour meant two different things on the same card — "more days" and "all of
   you" — and the eye cannot tell them apart. A run says how many days it is in
   words; green is reserved for the count, and only when nobody is missing. */
.free-days.grid .fd-count{color:var(--muted)}
.free-days.grid .free-day.all .fd-count{color:var(--green)}
.wide-cell{grid-column:1 / -1}
@media(max-width:420px){ .free-days.grid{grid-template-columns:1fr} }

/* === THE PROPOSAL, AS A PAGE ================================================ */
.proposal-page{display:flex;flex-direction:column;min-height:0}
.pr-steps{display:flex;gap:var(--s2);margin:0 0 var(--s4)}
.pr-step{display:flex;align-items:center;gap:var(--s2);flex:1;padding:var(--s2);border-radius:12px;
  background:var(--warm);border:1px solid var(--line);font-size:var(--t-label);color:var(--muted)}
.pr-step b{width:20px;height:20px;border-radius:50%;display:grid;place-items:center;
  background:#fff;border:1px solid var(--line2);font-size:var(--t-micro);font-weight:var(--w-med)}
.pr-step.on{background:#fff;border-color:var(--ink);color:var(--ink)}
.pr-step.on b{background:var(--ink);border-color:var(--ink);color:#fff}
.pr-step.done b{background:var(--green);border-color:var(--green);color:#fff}
.pr-body{flex:1;min-height:0}
.pr-when{display:grid;gap:var(--s3);margin-top:var(--s3)}

/* scope, categories and options on one page — the collapse that removes five
   screens from the middle of this */
.pr-scopes{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:var(--s2)}
.pr-scope{display:flex;align-items:flex-start;gap:var(--s2);text-align:left;padding:var(--s3) var(--s4);
  min-height:64px;border:1px solid var(--line);border-radius:var(--r-card);background:#fff;cursor:pointer}
.pr-scope.on{border-color:var(--ink);background:var(--warm)}
.pr-scope-emoji{font-size:var(--t-card);line-height:1.2}
.pr-scope strong{display:block;font-size:var(--t-meta);font-weight:var(--w-med)}
.pr-scope small{display:block;font-size:var(--t-label);color:var(--muted);margin-top:2px}
.pr-cats{display:flex;flex-wrap:wrap;gap:var(--s2)}
.pr-cat{display:inline-flex;align-items:center;gap:var(--s2);min-height:40px;padding:0 12px;
  border:1px solid var(--line);border-radius:999px;background:#fff;cursor:pointer;
  font-size:var(--t-label);font-weight:var(--w-med);color:var(--muted)}
.pr-cat.on{background:var(--ink);border-color:var(--ink);color:#fff}
.pr-options{margin-top:var(--s4)}
.pr-summary{padding:var(--s4)}
.pr-summary h3{margin:var(--s2) 0 2px;font-size:var(--t-card);letter-spacing:-.02em}
.pr-summary > p{margin:0;font-size:var(--t-meta);color:var(--muted)}
.pr-picks{margin:var(--s3) 0 0;padding-left:var(--s4);font-size:var(--t-meta);line-height:1.6}
.pr-picks b{font-weight:var(--w-med)}
.pr-travel{margin-top:var(--s3)}

/* === A PROFILE PICTURE IS A CIRCLE ==========================================
   The avatar took its height from its content, so a two-letter monogram and a
   one-letter one were different shapes, and any image that was not square came
   out an oval. Fixed box, square aspect, cover-cropped. */
.avatar,.profile-dot,.nav-photo,.photo-tile{aspect-ratio:1;border-radius:50%;overflow:hidden;
  flex:0 0 auto;object-fit:cover;background-size:cover;background-position:center}
.avatar{display:grid;place-items:center;line-height:1}
.avatar.photo,.profile-dot.has-photo{background-size:cover;background-position:center}
.avatar > img,.profile-dot > img{width:100%;height:100%;object-fit:cover;display:block}

/* The 44px minimum touch target was setting min-height on .profile-dot while
   its width stayed 38 — so the one element that is unambiguously a circle came
   out 38x44. A tap target and a circle are not in conflict; the box just has to
   be square at the larger of the two. */
.profile-dot{width:44px;height:44px;min-width:44px;min-height:44px}
.top-btn:not(.labelled){width:44px}

/* === WHO, AND HOW MUCH OF THE DAY ===========================================
   A count says four people are free. It cannot say that three of them are free
   all day and the fourth only after six — which is the difference between a
   plan and a disappointment.

   Each face carries a ring drawn to the share of the day that person actually
   has open: a complete ring is a whole day, a partial one is a window. Someone
   who is not free at all keeps their face and loses the ring, so you can see
   who is missing rather than only how many. */
.fd-who{display:flex;flex-wrap:wrap;gap:3px;grid-column:1 / -1;margin-top:var(--s2)}
.fd-face{position:relative;display:inline-grid;place-items:center;padding:2px;border-radius:50%}
.fd-face .avatar{width:22px;height:22px;font-size:var(--t-dense);margin:0}
/* The ring. conic-gradient means no SVG and no extra element. */
.fd-face.all-day{background:var(--green)}
.fd-face.part{background:conic-gradient(var(--green) var(--open), var(--line2) 0)}
.fd-face.out{background:transparent}
.fd-face.out .avatar{opacity:.35;filter:grayscale(1)}

/* the row grows a third line, so it needs the room */
.free-days.grid .free-day{min-height:auto;padding:var(--s2) var(--s2)}
.free-days.grid{grid-auto-rows:min-content}

/* A group of twenty cannot show twenty faces on a row. Free people first, then
   a count of the rest — the question is who can come, not a full register. */
.fd-more{display:inline-grid;place-items:center;width:26px;height:26px;border-radius:50%;
  background:var(--warm);border:1px solid var(--line);font-size:var(--t-dense);
  font-weight:var(--w-med);color:var(--muted)}
.free-days.grid .fd-sub{color:var(--muted);font-size:var(--t-micro)}

/* === THE ONE-TAP ANSWER =====================================================
   This is the most valuable control in the product: availability is the whole
   business, and this is where it comes from. It was a card with a headline, a
   subtitle and three full-width buttons, sitting under a hero making nearly
   the same statement about the same day. One line. */
/* max-width so the buttons stay beside the sentence. Full width put "Free"
   900px from "3 of Group One are free Sat Aug 22", which is the two-ends-of-a-
   sentence problem this app has already fixed twice elsewhere. */
.nudge-line{display:flex;align-items:center;gap:var(--s2);flex-wrap:wrap;
  padding:var(--s2) var(--s3);margin-bottom:var(--s3);border:1px solid var(--line);
  border-radius:12px;background:#fff;max-width:720px}
.nudge-line.urgent{border-color:#efd5d0;background:#fffdfc}
.nl-text{flex:1;min-width:min(100%,200px);font-size:var(--t-label);color:var(--muted);line-height:1.35}
.nl-text b{color:var(--ink);font-weight:var(--w-med)}
.nl-actions{display:flex;align-items:center;gap:var(--s2);flex:0 0 auto}
.nl-actions button{min-height:34px;padding:0 11px;font-size:var(--t-label)}
.nl-connect{display:inline;border:0;background:none;padding:0;cursor:pointer;
  color:var(--green);font:inherit;font-weight:var(--w-med);text-decoration:underline;
  text-underline-offset:2px}
@media(max-width:520px){ .nl-actions{width:100%} .nl-actions .primary,.nl-actions .secondary{flex:1} }

/* === THE ASK LINK ===========================================================
   Somebody who does not have this app, on a phone, in a text thread. One
   question, three buttons, a name. Nothing else on the screen and no mention
   of installing anything until after they have answered.                    */
html.ask-mode .app-shell{display:block!important;background:var(--bg)}
html.ask-mode .side-nav,html.ask-mode .bottom-nav,html.ask-mode .topbar,
html.ask-mode .floating-view{display:none!important}
/* The planning chat is the whole screen too, and for the same reason: while
   you are answering it there is nothing else to be doing, and a tab bar under
   a text field is a row of chrome between your thumb and the keyboard. */
html.chat-mode .side-nav,html.chat-mode .bottom-nav,html.chat-mode .topbar,
html.chat-mode .profile-banner,html.chat-mode .floating-view{display:none!important}
/* NOT .content{padding:0}. .chat-page already bleeds edge to edge with
   margin:0 -16px and width:calc(100% + 32px), which is exactly the gutter
   .content provides - so removing that padding did not widen the page, it
   pushed it 16px off BOTH sides. The back button was cut in half on the left
   and the mic on the right, and nothing about it looked deliberate. */
.ask-wrap{min-height:100vh;display:grid;place-items:center;padding:var(--s4)}
.ask-card{width:100%;max-width:420px;background:#fff;border:1px solid var(--line);
  border-radius:var(--r-xl);padding:var(--s5);box-shadow:var(--shadow)}
.ask-brand{font-size:var(--t-label);letter-spacing:.16em;font-weight:var(--w-med);
  color:var(--muted2);margin-bottom:var(--s3)}
.ask-card h1{margin:0;font-size:26px;line-height:1.12;letter-spacing:-.03em}
.ask-q{margin:8px 0 0;font-size:var(--t-body);color:var(--muted)}
.ask-days{display:grid;gap:var(--s3);margin:var(--s5) 0}
.ask-day{display:grid;gap:var(--s2)}
.ask-when{font-size:var(--t-meta);font-weight:var(--w-med)}
.ask-opts{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s2)}
.ask-opt{min-height:46px;border:1px solid var(--line2);border-radius:var(--r-control);background:#fff;
  font-size:var(--t-label);font-weight:var(--w-med);color:var(--muted);cursor:pointer}
.ask-opt.on{border-color:var(--ink);background:var(--ink);color:#fff}
.ask-opt.on.free{background:var(--green);border-color:var(--green)}
.ask-opt.on.busy{background:var(--danger);border-color:var(--danger)}
.ask-name{margin-bottom:var(--s3)}
.ask-note{margin:var(--s3) 0 0;font-size:var(--t-label);color:var(--muted);line-height:1.45}
.ask-after{margin-top:var(--s5);padding-top:var(--s4);border-top:1px solid var(--line)}
.ask-after strong{display:block;font-size:var(--t-body)}
.ask-after p{margin:6px 0 var(--s3);font-size:var(--t-meta);color:var(--muted);line-height:1.45}
.ask-loading{padding:var(--s5);text-align:center;color:var(--muted)}

/* picking the days to ask about */
.ask-pick{display:grid;grid-template-columns:repeat(7,1fr);gap:var(--s2)}
.ask-pick-day{display:grid;gap:1px;place-items:center;min-height:52px;padding:var(--s2) 0;
  border:1px solid var(--line);border-radius:12px;background:#fff;cursor:pointer}
.ask-pick-day b{font-size:var(--t-micro);color:var(--muted2);font-weight:var(--w-med)}
.ask-pick-day span{font-size:var(--t-body);font-weight:var(--w-med)}
.ask-pick-day.on{background:var(--ink);border-color:var(--ink);color:#fff}
.ask-pick-day.on b{color:rgba(255,255,255,.7)}

/* Waiting on N — the organiser's actual problem, and the one thing every
   scheduling tool creates and none of them solves. */
.pp-waiting{display:flex;align-items:center;justify-content:space-between;gap:var(--s3);
  flex-wrap:wrap;padding:var(--s2) var(--s3);margin-bottom:var(--s3);border:1px solid #efd5d0;
  border-radius:var(--r-card);background:#fffdfc;font-size:var(--t-meta);color:var(--muted)}
.pp-waiting b{color:var(--ink);font-weight:var(--w-med)}
.pp-waiting button{min-height:38px;padding:0 13px;flex:0 0 auto}

/* answers from people who never installed anything */
.ask-answers{padding:var(--s4);margin-bottom:var(--s3)}
.aa-row{display:flex;align-items:baseline;justify-content:space-between;gap:var(--s3);
  padding:var(--s2) 0;border-top:1px solid var(--line)}
.aa-row:first-of-type{border-top:0}
.aa-who{font-size:var(--t-meta);font-weight:var(--w-med)}
.aa-who em{display:block;font-style:normal;font-size:var(--t-micro);
  color:var(--muted2);font-weight:var(--w-reg)}
.aa-said{font-size:var(--t-meta);color:var(--muted);text-align:right}

/* "everyone" instead of N identical avatars — see whoIsFree() */
.fd-who.all-free{font-size:var(--t-micro);font-weight:var(--w-med);color:var(--green);
  letter-spacing:.04em}

/* === THE SELECT BOX =========================================================
   .check was a 24px rounded square, and the 44px touch-target rule stretched
   it into a 44x44 empty bordered box — which reads as a broken image, not a
   checkbox. The BUTTON stays 44px because thumbs need it; the box you can see
   goes back to 22px and is drawn inside it. */
.friend-row .check{border:0;background:transparent;font-size:0;position:relative;
  width:44px;height:44px;min-width:44px;min-height:44px;display:grid;place-items:center}
.friend-row .check::before{content:'';width:22px;height:22px;border-radius:7px;
  border:1.5px solid var(--line2);background:#fff;display:block;transition:.12s ease}
.friend-row.selected .check::before{background:var(--green);border-color:var(--green)}
/* the tick is drawn, so it cannot inherit font-size:0 */
.friend-row.selected .check::after{content:'';position:absolute;width:11px;height:6px;
  border-left:2px solid #fff;border-bottom:2px solid #fff;transform:rotate(-45deg) translate(1px,-2px)}
@media(hover:hover){ .friend-row .check:hover::before{border-color:var(--green)} }

/* === ONE SCROLLBAR PER REGION ===============================================
   The Calendar tab had three stacked on the right edge: the page, the day
   panel, and the hour grid inside it. The panel is the region that should
   scroll; the grid inside it scrolling as well means two gestures to reach one
   afternoon. */
@media(min-width:1080px){
  .cal-panel .time-grid{max-height:none;overflow:visible}
}


/* The Desktop/Phone switch is gone — it was a developer's tool sitting in the
   corner of every user's screen. Anything still emitting one stays hidden
   rather than leaving a stray control behind. */
.view-switch,.floating-view,.side-foot:empty{display:none!important}

/* === IDEAS ==================================================================
   Named things to do, not raw windows. "Grab lunch Thursday" is a plan; "free
   2:00 PM–7:00 PM" is a fact somebody still has to turn into one. Scrolls, and
   is not capped, because a free Tuesday evening and a free three-day weekend
   are different sizes of idea and both belong. */
/* The .ideas strip that lived here is gone — see groupCard(). The
   .idea-scroll / .idea-chip rules further down are a different thing: they
   style the group-NAME picker, and are still in use. */

/* === IDEAS ==================================================================
   Named things to do, not raw windows. Scrolls sideways, and is not capped at
   one size of idea: a free Tuesday evening and a clear three-day weekend are
   different things and both belong. */
.ideas{display:flex;gap:var(--s2);overflow-x:auto;padding:var(--s3) 2px var(--s2);
  scroll-snap-type:x proximity;-webkit-overflow-scrolling:touch}
.idea{flex:0 0 auto;min-width:178px;max-width:250px;text-align:left;scroll-snap-align:start;
  display:grid;gap:3px;align-content:start;padding:var(--s3) var(--s3);min-height:44px;
  border:1px solid var(--line);border-radius:var(--r-control);background:#fff;cursor:pointer}
.idea-title{font-size:var(--t-meta);font-weight:var(--w-med);letter-spacing:-.01em}
.idea-body{font-size:var(--t-label);color:var(--muted);line-height:1.35}
/* A stretch of clear days is a trip and should not look like a coffee. */
.idea.away{background:var(--green-soft);border-color:#cddfd2;min-width:216px}
.idea.away .idea-title{color:var(--green)}
@media(hover:hover){ .idea:hover{border-color:var(--line2)} .idea.away:hover{border-color:var(--green)} }

/* --- the time wheel ---------------------------------------------------------
   Three columns that scroll, snap, and cannot be typed into. The 40 / 60 / 160
   numbers below are a contract with WHEEL_ITEM and WHEEL_PAD in timewheel.js:
   the value is read as round(scrollTop / 40), so a row that is not 40px tall
   reads back as the wrong time. Change both or neither. */
.timewheel{margin:0 0 var(--s2);min-width:0}
.tw-label{display:block;font-size:var(--t-label);color:var(--muted);
  font-weight:var(--w-med);letter-spacing:.06em;margin:0 0 5px 2px}
.tw-cols{position:relative;display:grid;grid-template-columns:1fr 1fr 1fr;
  border:1px solid var(--line2);border-radius:var(--r-control);background:#fff;
  padding:0 4px;overflow:hidden}
/* The lane the chosen row sits in, so the centre reads as the answer rather
   than as the row that happens to be darkest. */
.tw-line{position:absolute;left:4px;right:4px;top:60px;height:40px;border-radius:var(--r-control);
  background:var(--warm);pointer-events:none}
.wheel-col{position:relative;height:160px;overflow-y:auto;overscroll-behavior:contain;
  scroll-snap-type:y mandatory;text-align:center;outline:none;
  scrollbar-width:none;-ms-overflow-style:none;
  -webkit-overflow-scrolling:touch;touch-action:pan-y}
.wheel-col::-webkit-scrollbar{display:none}
.wheel-pad{height:60px}
.wheel-item{height:40px;line-height:40px;font-size:var(--t-body);color:var(--muted2);
  scroll-snap-align:center;cursor:pointer;user-select:none;-webkit-user-select:none;
  font-variant-numeric:tabular-nums;transition:color .12s ease}
.wheel-item.on{color:var(--ink);font-weight:var(--w-med)}
.wheel-col:focus-visible{border-radius:var(--r-control);box-shadow:inset 0 0 0 2px var(--ink)}
/* Two pickers side by side is the common case and must not squeeze the AM/PM
   column off the end on a narrow phone. */
.tw-pair{display:grid;grid-template-columns:1fr 1fr;gap:8px}
@media(max-width:400px){ .tw-pair{grid-template-columns:1fr} }
@media(prefers-reduced-motion:reduce){ .wheel-item{transition:none} }

/* --- before / after ---------------------------------------------------------
   Saying "busy" is really saying which half of the evening is gone. */
.ba-tabs{display:grid;grid-template-columns:1fr 1fr;gap:var(--s2);margin:2px 0 var(--s2);
  padding:3px;background:var(--warm);border-radius:var(--r-control)}
.ba-tabs button{padding:var(--s2) 8px;border:0;background:transparent;border-radius:var(--r-control);
  font-size:var(--t-label);font-weight:var(--w-med);color:var(--muted);cursor:pointer}
.ba-tabs button.on{background:#fff;color:var(--ink);box-shadow:0 1px 2px rgba(40,35,28,.10)}
.ba-read{margin:0 2px 2px;font-size:var(--t-label);color:var(--muted);line-height:1.45}
.ba-read b{color:var(--ink);font-weight:var(--w-med)}

/* --- a standing rule, not an answer ------------------------------------------
   "Free after my planned events" is the one setting that stops this app
   needing to be maintained, so it sits on the week strip where the work is. */
.open-avail{display:flex;align-items:center;gap:var(--s2);width:100%;text-align:left;
  margin-top:var(--s2);padding:var(--s2) var(--s3);border:1px solid var(--line);border-radius:var(--r-control);
  background:#fff;cursor:pointer}
.open-avail:hover{border-color:var(--line2)}
.open-avail .oa-box{flex:0 0 auto;width:20px;height:20px;border-radius:6px;
  border:1.5px solid var(--line2);background:#fff;display:grid;place-items:center;
  color:transparent;font-size:12px;line-height:1}
.open-avail.on .oa-box{background:var(--ink);border-color:var(--ink);color:#fff}
.open-avail div{min-width:0}
.open-avail strong{display:block;font-size:var(--t-label);font-weight:var(--w-med)}
.open-avail span{display:block;font-size:var(--t-label);color:var(--muted);
  line-height:1.4;margin-top:2px}

/* --- a sheet you can throw away ---------------------------------------------
   The grabber was decoration. Now it is the control: drag the sheet down and
   let go. The × is gone from sheets that have a Save, because two ways out
   with different consequences in the same corner is how people lose work. */
.sheet{touch-action:pan-y}
.sheet.dragging{animation:none;transition:none;will-change:transform}
.sheet.settling{animation:none;transition:transform .2s ease}
/* A target the size of the gesture, not the size of the picture.
   -------------------------------------------------------------------------
   The pill is 38px wide, and the thing you had to hit was barely bigger: 60
   wide by about 20 tall, centred, at the top of a sheet. A thumb is roughly 45
   across and arrives at an angle, so the usual outcome was scrolling the sheet
   instead of dismissing it, twice, before anybody found the edge of it.

   The drawn pill stays small because it is a hint. What grew is the part that
   listens: the whole width of the sheet, 44 tall, which is Apple's own floor
   for anything you have to hit. */
/* And it takes no room.
   -------------------------------------------------------------------------
   As a block element it was 46px of vertical space at the top of every sheet
   and popup — to draw a 5px pill. On a phone that is 46px taken off the
   bottom of the content, which is how "Fill out your week" ended up needing a
   scroll to reach its own Save button.

   Absolute, so the pill floats over the top edge and the content starts where
   it would have anyway. The 44px listening area is unchanged; it now sits over
   the heading, which is where a thumb reaching for the top of a sheet lands.
   The close button is lifted above it so it stays pressable. */
/* No `position:relative` here: .sheet and .mini-pop are already position:fixed,
   which is its own containing block for an absolute child. Adding relative
   would OVERRIDE the fixed — components.css loads second — and drop every
   sheet in the app out of the viewport and into the bottom of the document. */
.grabber{cursor:grab;position:absolute;top:0;left:0;right:52px;height:44px;
  padding:0;margin:0;width:auto;background:none;border:0;border-radius:0;
  touch-action:none;display:block;z-index:2}
/* Stops 52px short of the right edge, because the × lives there and the
   grabber would otherwise sit on top of it. A child cannot climb out of its
   parent's stacking context, so raising .close-btn alone did nothing — the
   grabber still swallowed every tap on it. Leaving the corner alone is the
   fix that does not need a z-index argument to be correct.

   52 clears the × (44 wide, 16 in from the edge = 60) but NOT .sheet-save,
   which is a labelled pill about 60 wide and therefore starts around 76 in —
   its left third sat under the strip, and the strip preventDefaults its
   pointerdown, so on the day-detail sheet the left of Close was dead. The
   header gets its own stacking context above the strip instead of the strip
   getting narrower, so the drag area stays the full width of everything that
   is not a button. */
.grabber:before{content:"";position:absolute;top:6px;left:0;right:-52px;
  margin:0 auto;width:48px;height:5px;background:#d8d3ca;border-radius:999px}
/* The header's own buttons sit above the drag strip. The header's position and
   z-index are set once, further down, where it is made sticky — setting them
   here as well just lost to that rule 250 lines later and left the corner
   button under the strip anyway. */
.sheet-head .close-btn,.sheet-head .sheet-save{position:relative;z-index:4}

/* The pill needs somewhere to be, on every sheet and not just a phone one.
   Scoped to force-phone this cleared the heading in the iOS shell and landed
   directly on the first line of it everywhere else — the browser, the desktop
   layout, and any phone the shell class had not been applied to yet. 16px is
   the rung; the pill sits at 6 and ends at 11, so the text clears it by 5.

   The force-phone selectors are repeated on purpose. Unscoping the rule
   dropped its specificity from 0-2-1 to 0-1-0, which then LOST to the
   `html.force-phone .sheet{padding:…}` shorthand up at line 564 — so inside
   the iOS shell and on iPad the padding fell back to 8px and the pill landed
   on the heading: the precise bug this rule exists to prevent, reintroduced by
   fixing it everywhere else. Same specificity, later in the file, so it
   wins. */
.sheet,.mini-pop,
html.force-phone .sheet,html.force-phone .mini-pop{padding-top:var(--s4)}
.grabber:active:before{background:#c2bcb1}
.sheet.dragging .grabber{cursor:grabbing}
.sheet-save{flex:0 0 auto;padding:var(--s2) var(--s4);border-radius:999px;border:1px solid var(--ink);
  background:var(--ink);color:#fff;font-size:var(--t-label);font-weight:var(--w-med);cursor:pointer}
.sheet-save:hover{opacity:.9}
@media(prefers-reduced-motion:reduce){ .sheet.settling{transition:none} }

/* --- a day, at the length it actually is -------------------------------------
   The grid used to run 6 AM to midnight whatever was on it, so a day with one
   lunch was seventeen empty rows and one block. It now spans what is there. */
.day-jump{display:flex;align-items:center;gap:8px;margin:2px 0 var(--s2)}
.day-jump button{flex:1}
.day-empty{margin:4px 0 12px;padding:var(--s4);border:1px dashed var(--line2);
  border-radius:var(--r-control);text-align:center;color:var(--muted);font-size:var(--t-label);
  background:var(--warm)}
.grid-edge{display:block;margin:var(--s2) 2px 0;font-size:var(--t-label);color:var(--muted2)}

/* The `hidden` attribute is a UA rule of display:none, and any class that sets
   display beats it on specificity. .tw-pair is display:grid, so a hidden pair
   of wheels stayed on screen. This is the one place !important earns its keep:
   hidden means hidden, whatever else the element is. */
[hidden]{display:none!important}

/* --- the paid tier ----------------------------------------------------------
   Shown when somebody hits a limit, never on launch. */
.pro-list{display:grid;gap:8px;margin:4px 0 16px}
.pro-row{padding:var(--s3) var(--s3);border:1px solid var(--line);border-radius:var(--r-control);background:#fff}
.pro-row strong{display:block;font-size:var(--t-meta);font-weight:var(--w-med);letter-spacing:-.01em}
.pro-row span{display:block;font-size:var(--t-label);color:var(--muted);line-height:1.4;margin-top:3px}
.pro-buys{display:grid;gap:8px}

/* The two plans, as one control. See proPriceMarkup(): the saving is printed
   on the tab rather than left as a sum to do between two stacked buttons. */
.pro-plan-tabs{
  display:flex; gap:4px; padding:4px; margin-bottom:var(--s3);
  background:var(--warm); border-radius:var(--r-pill);
}
.pro-plan{
  flex:1; min-height:44px; padding:6px var(--s2);
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:1px;
  border:0; border-radius:var(--r-pill); background:transparent; cursor:pointer;
}
.pro-plan b{ font-size:var(--t-meta); font-weight:var(--w-med); color:var(--muted) }
.pro-plan small{ font-size:var(--t-label); color:var(--green); font-weight:var(--w-med) }
.pro-plan.on{ background:var(--paper); box-shadow:0 1px 3px rgba(38,33,25,.10) }
.pro-plan.on b{ color:var(--ink); font-weight:var(--w-bold) }
/* Two lines in the button: what you pay now on top, what you pay after it
   underneath. The second is the one people say they were not told. */
.pro-buy{ display:flex; flex-direction:column; gap:1px; min-height:56px }
.pro-buy b{ font-size:var(--t-body); font-weight:var(--w-bold) }
.pro-buy span{ font-size:var(--t-label); font-weight:var(--w-reg); opacity:.82 }

/* --- the rating, on its own line -------------------------------------------
   4.6 from eleven reviews and 4.6 from four thousand are different facts, and
   the second is the one that settles where eight people are eating. */
.rating-line{display:flex;align-items:baseline;gap:var(--s2);margin:3px 0 2px;flex-wrap:wrap}
.rating-line .stars{color:#e0a12a;font-size:var(--t-label);letter-spacing:.5px;line-height:1}
.rating-line b{font-size:var(--t-label);font-weight:var(--w-med);color:var(--ink)}
.rating-line span:not(.stars){font-size:var(--t-label);color:var(--muted)}

/* --- a button living inside a text field ------------------------------------
   The examples list has to be reachable from the name box itself. Anywhere
   else and it reads as a separate decision rather than help with this one. */
.input-with-btn{position:relative;display:block}
.input-with-btn .input{padding-right:92px}
.in-field-btn{position:absolute;right:5px;top:50%;transform:translateY(-50%);
  padding:var(--s2) var(--s3);border-radius:999px;border:1px solid var(--line2);background:var(--warm);
  font-size:var(--t-label);font-weight:var(--w-med);color:var(--muted);cursor:pointer}
.in-field-btn:hover{border-color:#b9b2a6;color:var(--ink)}

/* --- forty kinds of group, scrolling past -----------------------------------
   Capped and scrollable: the whole point is that you skim past things you did
   not come here for and one of them turns out to be a group you have. */
.idea-scroll{max-height:min(52vh,420px);overflow-y:auto;margin:var(--s1) -4px var(--s2);padding:0 4px;
  -webkit-overflow-scrolling:touch;overscroll-behavior:contain}
.idea-group{margin-bottom:var(--s4)}
.idea-group h4{margin:0 2px var(--s2);font-size:var(--t-label);letter-spacing:.12em;
  text-transform:uppercase;color:#85837d;font-weight:var(--w-med)}
.idea-chips{display:flex;flex-wrap:wrap;gap:var(--s2)}
.idea-chip{display:inline-flex;align-items:center;gap:var(--s2);padding:8px 12px;min-height:38px;
  border:1px solid var(--line);border-radius:999px;background:#fff;cursor:pointer;
  font-size:var(--t-label);font-weight:var(--w-med);color:var(--ink);text-align:left}
.idea-chip span{font-size:15px;line-height:1}
.idea-chip:hover{border-color:var(--line2);background:var(--warm)}

/* --- the headcount ----------------------------------------------------------
   The number a standing group actually wants, and the reason a run club would
   install anything. Big, because it is the answer. */
.headcount{margin-bottom:var(--s3)}
.hc-top{display:flex;align-items:baseline;gap:var(--s2);margin:2px 0 var(--s2)}
.hc-num{font-size:34px;line-height:1;letter-spacing:-.04em;font-weight:var(--w-med)}
.hc-when{font-size:var(--t-label);color:var(--muted)}
.hc-row{display:flex;gap:var(--s2);padding:var(--s2) 0;border-top:1px solid var(--line);
  font-size:var(--t-label);line-height:1.45}
.hc-row b{flex:0 0 58px;font-weight:var(--w-med)}
.hc-row span{color:var(--muted);min-width:0}
.hc-row.in b{color:var(--green)}
.hc-row.out b{color:var(--muted2)}
.hc-actions{display:flex;align-items:center;justify-content:space-between;
  gap:var(--s2);margin-top:var(--s2);flex-wrap:wrap}
.hc-actions .est-note{margin:0}

/* What the link is worth to the person tapping it, not to the person who sent
   it — the thank-you screen is the only pitch they will ever see. */
.ask-sell{margin:var(--s2) 0 var(--s4);padding:0;list-style:none;display:grid;gap:var(--s2)}
.ask-sell li{font-size:var(--t-label);color:var(--muted);line-height:1.5;
  padding-left:16px;position:relative}
.ask-sell li:before{content:"";position:absolute;left:0;top:8px;width:6px;height:6px;
  border-radius:50%;background:var(--green)}
.ask-sell b{color:var(--ink);font-weight:var(--w-med)}

/* --- the agreement line -----------------------------------------------------
   Present on both screens that can create an account, above the fold of the
   button that does it. Quiet, but the links are real links: an agreement
   nobody can read before agreeing is not one. */
.auth-legal{margin:12px 2px 0;font-size:var(--t-label);line-height:1.5;
  color:var(--muted);text-align:center}
.auth-legal a{color:var(--ink);text-decoration:underline;text-underline-offset:2px}
.auth-legal a:hover{opacity:.75}
/* Reachable after sign-up too, not only before it. */
.legal-links{margin:var(--s4) 2px var(--s1);text-align:center;font-size:var(--t-label);color:var(--muted2)}
.legal-links a{color:var(--muted);text-decoration:underline;text-underline-offset:2px}
.legal-links span{margin:0 7px}

/* --- deleting an account ----------------------------------------------------
   What goes and what does not, before the button — a plan other people are in
   does not vanish from their phones, and finding that out afterwards is worse
   than reading it here. */
.danger-text{color:var(--danger,#b4462f)}
.del-list{display:grid;gap:var(--s2);margin:12px 0 4px}
.del-list div{padding:var(--s3) var(--s3);border:1px solid var(--line);border-radius:var(--r-control);background:#fff}
.del-list b{display:block;font-size:var(--t-label);font-weight:var(--w-med);margin-bottom:3px}
.del-list span{display:block;font-size:var(--t-label);color:var(--muted);line-height:1.45}

/* --- a popup you can throw away too -----------------------------------------
   Clicking the backdrop always worked, but on a phone the backdrop is a thin
   margin around the card and the natural gesture is to push the thing down.
   The grabber is hidden on desktop, where the pointer is precise and Escape
   exists — same rule the sheet grabber already follows. */
.mini-pop{touch-action:pan-y}
.mini-pop.dragging{transition:none;will-change:transform}
.mini-pop.settling{transition:transform .2s ease}
/* margin no longer applies — it is absolutely positioned. */
@media(prefers-reduced-motion:reduce){ .mini-pop.settling{transition:none} }

/* The seeded example, marked as one. A demo group that looks like real friends
   is a lie the user finds out about later. */
.badge.example{background:var(--warm);color:var(--muted);border:1px solid var(--line2);
  font-size:var(--t-label);font-weight:var(--w-med);padding:2px var(--s2);border-radius:999px;
  margin-left:var(--s2);vertical-align:middle;letter-spacing:.02em}

/* --- a group's free days, as a list you can actually read --------------------
   This was a three-column grid with nothing in the middle column, so every row
   was a date, a gap the width of a thumb, and a fraction. The proportion is the
   thing being scanned down a long list — the numbers only matter on the row you
   stop at — so the middle column now carries a bar and the fraction shrinks.

   Scrolls vertically with a soft edge at the bottom, because a hard cut at a
   scroll boundary reads as the end of the list. */
.day-list{position:relative;display:grid;gap:var(--s2);margin-top:var(--s3);
  max-height:min(52vh,440px);overflow-y:auto;overscroll-behavior:contain;
  padding:2px 3px 2px 0;scrollbar-width:thin;
  -webkit-mask-image:linear-gradient(to bottom,#000 calc(100% - 22px),transparent);
          mask-image:linear-gradient(to bottom,#000 calc(100% - 22px),transparent)}
.day-row{display:grid;grid-template-columns:minmax(104px,1.1fr) minmax(48px,1fr) auto;
  align-items:center;gap:var(--s3);width:100%;text-align:left;min-height:48px;
  padding:8px 12px;border:1px solid var(--line);border-radius:var(--r-control);background:#fff;
  cursor:pointer;transition:border-color .12s ease,transform .12s ease}
.day-row:hover{border-color:var(--line2);transform:translateY(-1px)}
.day-row:active{transform:none}
.dr-when{display:grid;gap:1px;min-width:0}
.dr-when b{font-size:var(--t-meta);font-weight:var(--w-med);letter-spacing:-.01em;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.dr-when i{font-style:normal;font-size:var(--t-label);color:var(--muted2)}
.dr-when i:empty{display:none}
.dr-bar{height:6px;border-radius:999px;background:var(--warm);overflow:hidden}
.dr-bar i{display:block;height:100%;border-radius:999px;background:#c3cfc6}
.dr-count{font-size:var(--t-label);font-weight:var(--w-med);color:var(--muted);
  font-variant-numeric:tabular-nums;white-space:nowrap}
.dr-count em{font-style:normal;color:var(--muted2)}
/* Everyone free is the answer the whole screen exists to surface. */
.day-row.all{border-color:#cddfd2;background:var(--green-soft)}
.day-row.all .dr-bar{background:#d4e3d8}
.day-row.all .dr-bar i{background:var(--green)}
.day-row.all .dr-count{color:var(--green)}
@media(max-width:420px){
  .day-row{grid-template-columns:1fr auto;gap:var(--s2)}
  .dr-bar{grid-column:1 / -1;order:3}
}
@media(prefers-reduced-motion:reduce){ .day-row{transition:none} .day-row:hover{transform:none} }

/* The call to action on the answer screen — the one place a person with no
   account is already engaged, so it gets to look like a button and not a link. */
.ask-cta{display:block;text-align:center;text-decoration:none;padding:var(--s4);
  border-radius:var(--r-control);margin-top:4px}

/* --- not up for anything -----------------------------------------------------
   Dimmed, not recoloured. A snoozed day keeps whatever state it really has
   underneath — this is a second axis, not a third calendar state, and making
   it look like "busy" would undo the entire point. */
.week-day.snoozed{opacity:.45}
.week-day.snoozed .wd-set{font-size:var(--t-label);letter-spacing:.06em;color:var(--muted2)}
.dow-pick{display:grid;grid-template-columns:repeat(7,1fr);gap:var(--s2)}
.dow-chip{min-height:42px;border:1px solid var(--line);border-radius:12px;background:#fff;
  font-size:var(--t-meta);font-weight:var(--w-med);color:var(--muted);cursor:pointer}
.dow-chip.on{background:var(--ink);border-color:var(--ink);color:#fff}
@media(hover:hover){ .dow-chip:hover{border-color:var(--line2)} }

/* --- the zoom nobody asks for -------------------------------------------------
   iOS zooms the whole page in when you focus any input whose font-size is
   under 16px, and every field here is 12px. Tap a name box and the layout
   lurches; iOS then does not zoom back out, so you are left scrolling
   sideways through a form.

   Disabling pinch does not touch this — it is a different mechanism — and the
   usual "fix" of user-scalable=no in the viewport takes real zoom away from
   people who need it to read. 16px on touch devices only leaves the desktop
   type scale exactly as designed, and is the better size to type into on a
   phone regardless. */
@media (pointer: coarse) {
  .input,
  input[type="text"], input[type="email"], input[type="password"],
  input[type="search"], input[type="number"], input[type="tel"],
  input[type="url"], input[type="date"], input[type="time"],
  textarea, select {
    font-size: 16px;
  }
}

/* The privacy promise on the sign-in screen. It is the whole pitch, so it sits
   above the buttons rather than in a footnote under them — and it is the only
   thing on that screen allowed to look like a callout. */
.auth-promise{margin:0 0 var(--s4);padding:var(--s3) var(--s4);border-radius:var(--r-control);
  background:var(--green-soft);border:1px solid #cddfd2;
  font-size:var(--t-label);line-height:1.5;color:var(--ink)}
.auth-promise strong{display:block;color:var(--green);font-weight:var(--w-med);margin-bottom:2px}

/* --- one-handed on a bus -----------------------------------------------------
   Measured at 375x812, not estimated. Every number below is a rendered size. */

/* The app's most-tapped control was 27.8 x 26px — 39% of Apple's minimum on
   its narrow axis, seven in a row 8px apart. A miss there does not do nothing;
   it answers the wrong day. The whole tile is the target now. */
.week-day{padding:0}
.week-day .wd-set{min-height:44px;border-radius:12px;font-size:var(--t-meta)}
.wd-dow{font-size:var(--t-label)}

/* The home indicator owns the bottom 34px of a notched phone, and the nav sat
   12px from the edge — so its lower third was inside the system swipe-up zone.
   Reaching for Friends sent you to the app switcher. */
.bottom-nav{bottom:calc(12px + env(safe-area-inset-bottom))}
/* .app-shell's 96px clearance moved OUT of this block - see the note below it.
   A coarse pointer is not a phone. */
.toast{bottom:calc(96px + env(safe-area-inset-bottom))}
.floating-actions{bottom:calc(86px + env(safe-area-inset-bottom))}

/* A sheet is up to 92vh of scrolling content and its header scrolled away with
   it — measured 1083px above the viewport on Settings. The only ways out were
   scrolling back a thousand pixels, or an 85px backdrop sliver at the very top.
   Sticky keeps the exit where the eye expects it. */
/* z-index 3, not 2: the grabber is an absolute overlay at 2, and a tie is
   resolved by DOM order — the grabber comes second, so at 2 it painted over
   the header and swallowed taps on the corner button. */
.sheet-head{position:sticky;top:0;z-index:3;background:var(--paper);
  padding-top:2px;margin-top:-2px}
/* The strip above the pinned header.
   -------------------------------------------------------------------------
   Sticky top:0 pins to the scroll container's CONTENT box, so the sheet's 16px
   of top padding sits ABOVE the pinned header - and whatever is scrolling shows
   through it. On the Get Away sheet that is a slice of the first card's
   photograph, floating above the word "Get Away", which reads as a rendering
   fault rather than a scroll position.

   The padding cannot simply go: it is what keeps the grab pill off the heading,
   and there is a comment sixty lines up about the last time somebody removed
   it. So the header paints over the band instead. The grabber is absolute and
   scrolls away with the content, so it only needs to win at rest - which is why
   the grabber's own rule further down is a 4 and not a 3. */
.sheet-head:before{
  content:""; position:absolute; left:0; right:0; bottom:100%;
  height:var(--s4); background:var(--paper);
}
/* The HEAD is what has to stay put, not the grab bar.
   -------------------------------------------------------------------------
   This was `.sheet .grabber{position:sticky;background:var(--paper)}` — a solid
   strip pinned to the top of every sheet, 46px tall, drawn so that content
   would scroll cleanly under it. That is a real problem and this was the wrong
   half of the answer: the exit people want kept in place is the x, and the x
   lives in .sheet-head, which is sticky in its own right two lines above.

   All the strip added was an opaque band sitting on the first line of text —
   which is why onboarding already had an override turning it off, one screen
   at a time, instead of the rule being wrong everywhere. The base rule floats
   it: absolute, transparent, no layout space, scrolls away with the content.
   z-index 4, and it stops 52px short of the right edge so the x underneath is
   still pressable.

   It was 3 "so it stays above the sticky head" - which was a tie, and a tie is
   decided by document order, and the head comes second. That did not show until
   the head grew a :before to mask the strip above it, at which point the mask
   painted straight over the pill. A number that only worked because nothing was
   drawn there yet. */
.sheet .grabber{z-index:4}

/* Opposite meanings, 35px tall, 6px apart. Picking the wrong one publishes the
   inverse of your evening. */
.ba-tabs button{min-height:44px}
.ba-read{font-size:var(--t-body)}

/* Later, more specific selectors were re-shrinking things the 44px pass had
   already fixed — specificity beating the remediation rule. */
.text-btn.small{min-height:44px}
.df-group .filter-chip{min-height:44px}
.timeline-time.editable{min-height:44px}
.dest-shortlist{min-height:44px}
.in-field-btn{min-height:38px}

/* Nested scrollers inside modals refused to chain, so the natural
   swipe-down-to-dismiss scrolled a list forever instead. There is nothing
   behind a modal worth protecting from a scroll chain. */
.idea-scroll, .day-list, .free-days{overscroll-behavior:auto}

/* A popup with no height cap ran off both edges of a 667px phone — grabber
   above the top of the screen, Save button below the bottom, nothing
   scrollable. The people it trapped were the ones with a busy evening. */
.mini-pop{max-height:min(86vh, 100vh - 48px);overflow-y:auto}
/* Same as the sheet: floated by the base rule, nothing to pin. */

/* The routine day picker was 21x21 with 3px gaps, and a miss fell through to
   the row handler and switched the whole routine off. Aiming for Tuesday and
   deleting "Work 9-5" was one 3px slip away. */
.rd{width:auto;flex:1;min-height:40px}
.routine-days{gap:4px}

/* Information, not decoration. The hour labels are the only thing telling you
   what row of the week grid you are reading. */
.wk-hour-label{font-size:var(--t-label)}
.cal-legend{font-size:var(--t-label)}

/* An armed block, so a long-press that took looks different from one that did
   not. Without this a hold and a tap are indistinguishable until something
   moves, and people let go too early. */
.tblock.drag-armed,.wk-block.drag-armed{
  box-shadow:0 0 0 2px var(--ink);transform:scale(1.02);transition:transform .1s ease}
@media(prefers-reduced-motion:reduce){
  .tblock.drag-armed,.wk-block.drag-armed{transform:none;transition:none}
}

/* Why an option is ranked where it is. A ranked list with no visible reason
   reads as arbitrary — naming the people is what makes it checkable. */
.group-like{display:flex;align-items:center;gap:var(--s2);margin:2px 0 4px;
  font-size:var(--t-label);font-weight:var(--w-med);color:var(--green)}
.gl-dot{width:6px;height:6px;border-radius:50%;background:var(--green);flex:0 0 auto}

/* --- one thing nobody asked for ----------------------------------------------
   Ranking by what a group already likes means the group only ever sees what it
   already likes. This is the row that breaks that, and the only place the app
   learns an interest nobody typed into a setup screen. Deliberately quieter
   than a real option — it is a question, not a recommendation. */
.wildcard-row{display:flex;align-items:center;gap:12px;margin-top:var(--s2);padding:var(--s3) var(--s4);
  border:1px dashed var(--line2);border-radius:var(--r-control);background:var(--warm)}
.wc-text{display:grid;gap:2px;min-width:0;flex:1}
.wc-tag{font-size:var(--t-label);letter-spacing:.1em;color:var(--muted2);font-weight:var(--w-med)}
.wc-text strong{font-size:var(--t-meta);font-weight:var(--w-med);letter-spacing:-.01em;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.wc-text span:last-child{font-size:var(--t-label);color:var(--muted);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.wc-btn{flex:0 0 auto;min-height:44px}
@media(max-width:400px){
  .wildcard-row{flex-direction:column;align-items:stretch;gap:8px}
  .wc-btn{width:100%}
}

/* --- a menu anchored to the thing it acts on ---------------------------------- */
.menu-backdrop{position:fixed;inset:0;z-index:72}
.anchored-menu{position:fixed;z-index:73;min-width:210px;max-width:calc(100vw - 16px);
  background:var(--paper);border:1px solid var(--line2);border-radius:var(--r-control);padding:var(--s2);
  box-shadow:0 18px 44px rgba(38,33,25,.20);animation:menuIn .12s ease both}
@keyframes menuIn{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}
.menu-row{display:flex;align-items:center;gap:var(--s2);width:100%;min-height:44px;
  padding:var(--s2) 12px;border:0;border-radius:var(--r-control);background:transparent;cursor:pointer;
  font-size:var(--t-meta);color:var(--ink);text-align:left}
.menu-row:hover{background:var(--warm)}
.menu-row.danger{color:var(--red,#a3312a)}
/* No icons in the menu. A column of glyphs invented to fill a slot reads as
   noise, and every row here is already one short verb. */
.menu-row{padding-left:var(--s4)}
/* 44pt, Apple minimum. */
.menu-btn{width:44px;min-height:44px;border:1px solid var(--line);border-radius:var(--r-control);
  background:#fff;cursor:pointer;color:var(--muted);font-size:var(--t-card);line-height:1;
  flex:0 0 auto;position:relative}
/* A target the size of the gesture, not the size of the picture — the same
   rule the sheet grabber follows. This is the ⋯ on every plan card, drawn at
   36 square, which is under the 44 the rest of the app holds itself to and a
   real miss on a phone. The drawn button stays 36 because at 44 it competes
   with the status pill beside it; what grows is the part that listens. */
.menu-btn:after{content:"";position:absolute;top:50%;left:50%;
  width:44px;height:44px;transform:translate(-50%,-50%)}
.menu-btn:hover{border-color:var(--line2);color:var(--ink)}
@media(prefers-reduced-motion:reduce){ .anchored-menu{animation:none} }

/* Status and its menu, together, so the card header does not reflow when one
   of them is absent. */
.plan-top-right{display:flex;align-items:center;gap:8px;flex:0 0 auto}

/* --- swipe a day free or busy -------------------------------------------------
   touch-action:pan-y so the page still scrolls vertically through the strip;
   the JS gives up the gesture the moment it looks more vertical than
   horizontal, because a strip of seven small tiles lives inside a scrolling
   page and the page has to win anything ambiguous. */
.week-day{touch-action:pan-y;transition:transform .12s ease,background .12s ease}
@media(prefers-reduced-motion:reduce){ .week-day{transition:none} }

/* Suggested categories, where the instruction used to be. Quieter than the
   full grid above it — this is a shortcut past the guessing, not a filter on
   what is available. */
.suggested-cats{margin-top:var(--s2);padding:var(--s3);border:1px dashed var(--line2);
  border-radius:var(--r-control);background:var(--warm)}
.sc-head{font-size:var(--t-label);letter-spacing:.1em;color:var(--muted2);
  font-weight:var(--w-med);margin-bottom:8px}

/* ==========================================================================
   The Coordin8 tab — one question at a time.

   Each block is a heading and its answer, and a block only exists once the one
   above it has been answered. That is why there is no card chrome: cards imply
   things sitting side by side, and this is a sentence being finished.
   ========================================================================== */
.co-screen{padding-bottom:8px}
.co-block{margin-bottom:var(--s5)}
.co-block:last-child{margin-bottom:0}
.co-q{font-size:var(--t-card);font-weight:var(--w-med);color:var(--ink);margin:0 0 4px;letter-spacing:-.01em}
.co-hint{font-size:var(--t-body);color:var(--muted);margin:0 0 var(--s4)}
.co-block .checklist-group-title{margin-top:0}

/* "Just get together" is deliberately not one of the category tiles. It is a
   different kind of answer — a refusal to answer yet — and putting it in the
   grid made it read as an activity called Just Get Together. */
.co-open{display:flex;flex-direction:column;align-items:flex-start;gap:2px;width:100%;margin-top:12px;
  padding:var(--s3) var(--s4);border:1px dashed var(--line2);border-radius:var(--r-control);background:transparent;
  text-align:left;cursor:pointer;min-height:44px}
.co-open b{font-size:var(--t-body);font-weight:var(--w-med);color:var(--ink)}
.co-open span{font-size:var(--t-label);color:var(--muted)}
.co-open.on{border-style:solid;border-color:var(--green);background:var(--green-soft)}
.co-open:active{background:var(--warm)}

.co-chips{display:flex;flex-wrap:wrap;gap:8px}
/* The count and the badges ride inside the chip so a long group name and a
   two-digit size cannot end up on separate lines from each other. */
.co-chips .who-chip{display:inline-flex;align-items:center;gap:var(--s2);min-height:44px}
.co-chips .who-chip i{font-style:normal;font-size:var(--t-label);color:var(--muted);
  background:var(--warm);border-radius:999px;padding:1px var(--s2)}
.co-chips .who-chip.on i{background:rgba(255,255,255,.35);color:inherit}
.co-chips .who-chip u{text-decoration:none;font-size:var(--t-label);text-transform:uppercase;
  letter-spacing:.08em;color:var(--muted);border:1px solid var(--line2);border-radius:6px;padding:0 5px}
.co-chips .who-chip em{font-style:normal;color:var(--green)}
.co-chips .who-chip.on em{color:inherit}

/* Something waiting on you outranks starting something new, so it sits above
   the first question — but as one line, because this is not the Plans tab. */
.co-waiting{display:flex;align-items:center;justify-content:space-between;gap:var(--s2);width:100%;
  margin-bottom:var(--s5);padding:var(--s3) var(--s4);border:1px solid #e6d9b8;border-radius:var(--r-control);
  background:#fdf6e3;text-align:left;cursor:pointer;min-height:44px}
.co-waiting b{font-size:var(--t-body);font-weight:var(--w-med);color:var(--ink)}
.co-waiting span{font-size:var(--t-label);color:var(--muted);white-space:nowrap}

/* --- notes and links on a plan ---------------------------------------------
   Attached to the plan rather than lost in the chat. The note is set in the
   reading typeface at body size because it is a sentence somebody wrote, not
   a field value.                                                            */
.pr-notes{min-height:76px;resize:vertical;font:inherit;line-height:1.45;padding:var(--s3) 12px}
.field-label{display:block;font-size:var(--t-label);letter-spacing:.1em;text-transform:uppercase;
  font-weight:var(--w-med);color:var(--muted);margin:0 2px var(--s2)}
.pr-links{display:flex;flex-direction:column;gap:var(--s2);margin-bottom:var(--s2)}
.pr-link{display:flex;align-items:center;gap:var(--s2);padding:var(--s2) var(--s2);border:1px solid var(--line2);
  border-radius:var(--r-control);background:var(--paper)}
.pr-link-icon{flex:none;font-size:15px;line-height:1}
/* min-width:0 or a long URL pushes the remove button off the right edge. */
.pr-link-text{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}
.pr-link-text b{font-size:var(--t-body);font-weight:var(--w-med);color:var(--ink);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pr-link-text i{font-style:normal;font-size:var(--t-label);color:var(--muted);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pr-link-x{flex:none;width:32px;height:32px;border:0;border-radius:9px;background:transparent;
  color:var(--muted);font-size:19px;line-height:1;cursor:pointer}
.pr-link-x:hover{background:var(--warm);color:var(--ink)}

.pp-extras{margin:0 0 var(--s4);padding:var(--s3);border:1px solid var(--line2);border-radius:var(--r-control);
  background:var(--warm)}
.pp-note{margin:0;font-size:var(--t-body);line-height:1.5;color:var(--ink);white-space:pre-wrap}
.pp-note + .pp-link{margin-top:var(--s2)}
.pp-link{display:flex;align-items:center;gap:var(--s2);padding:var(--s2) var(--s2);margin-top:var(--s2);
  border:1px solid var(--line2);border-radius:var(--r-control);background:var(--paper);
  text-decoration:none;min-height:44px}
.pp-link:first-child{margin-top:0}
.pp-link-go{flex:none;color:var(--muted);font-size:14px}
.pp-link:hover{border-color:#b9b2a6}

/* --- where: real venues on the Coordin8 tab ---------------------------------
   The block that makes this a planning app rather than a scheduler. A date is
   not a plan; a date with a place in it is. So these carry the things you
   actually decide on — the rating, how many people rated it, whether it is
   open right now — and a route out to Directions, Google and Yelp.          */
.co-places{display:flex;flex-direction:column;gap:var(--s2)}
/* Show more, under either list. Sits apart from the last card so it reads as
   an action on the list rather than another thing in it, and holds its height
   while it says "Finding more" so the page does not jump under the thumb. */
.co-more{margin-top:var(--s2);min-height:40px}
.co-more[disabled]{opacity:.6}
.co-place{border:1px solid var(--line2);border-radius:var(--r-card);background:var(--paper);overflow:hidden}
.co-place.on{border-color:var(--green);box-shadow:0 0 0 1px var(--green)}
.co-place-main{display:flex;align-items:center;gap:var(--s3);width:100%;padding:var(--s3);
  background:transparent;border:0;text-align:left;cursor:pointer;min-height:44px}
.co-place-img{flex:none;width:58px;height:58px;border-radius:var(--r-control);object-fit:cover;background:var(--warm)}
/* min-width:0 or a long venue name stops the tick from ever being on screen. */
.co-place-text{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
.co-place-text b{font-size:var(--t-body);font-weight:var(--w-med);color:var(--ink);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.co-place-meta{font-size:var(--t-label);color:var(--muted);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.co-place-meta em{font-style:normal;font-weight:var(--w-med);color:var(--ink)}
.co-place-note{font-size:var(--t-label);color:var(--green);font-weight:var(--w-med)}
/* Closed is not an error, but it is the one fact that changes the answer. */
.co-place-note.shut{color:var(--danger)}
.co-place-tick{flex:none;width:22px;text-align:center;color:var(--green);font-size:15px}

.co-place-links{display:flex;flex-wrap:wrap;gap:var(--s2);padding:0 11px var(--s3)}
.co-place-links a{font-size:var(--t-label);color:var(--ink);text-decoration:none;
  border:1px solid var(--line2);border-radius:999px;padding:var(--s2) var(--s2);background:var(--paper);
  min-height:32px;display:inline-flex;align-items:center;gap:4px}
.co-place-links a:hover{border-color:#b9b2a6;background:var(--warm)}

/* Placeholders while Google answers. Same height as the real card, so the
   block does not jump when results land under the reader's thumb. */
.co-place.skeleton{padding:var(--s4) 12px;display:flex;flex-direction:column;gap:8px;min-height:82px}
.co-place.skeleton .sk-line{height:11px;border-radius:6px;background:var(--warm);animation:skpulse 1.4s ease-in-out infinite}
.co-place.skeleton .sk-line.lg{width:62%}
.co-place.skeleton .sk-line.sm{width:38%}
@keyframes skpulse{0%,100%{opacity:1}50%{opacity:.45}}

/* --- onboarding: the two asks at the end -----------------------------------
   Each permission gets its own card with its own reason above its own button.
   Stacked plainly rather than dressed up: somebody deciding whether to hand
   over their address book is reading, not skimming.                        */
.ob-ask{padding:var(--s4);margin-bottom:12px}
.ob-ask-top{display:flex;gap:var(--s3);align-items:flex-start;margin-bottom:var(--s3)}
.ob-ask-top .ob-emoji{flex:none;font-size:var(--t-title);line-height:1.1}
.ob-ask-top strong{display:block;font-size:var(--t-body);font-weight:var(--w-med);color:var(--ink);margin-bottom:2px}
.ob-ask-top p{margin:0;font-size:var(--t-label);color:var(--muted);line-height:1.45}
.ob-ask .scope-note{margin-bottom:var(--s3)}
.ob-ask .primary:disabled{opacity:.55;cursor:default}

/* --- the loop prompt -------------------------------------------------------
   Two doors, each with its own reason under it. Full-width rows rather than
   side-by-side buttons: the reason is the part that decides it, and a reason
   squeezed into half a phone is a reason nobody reads.                      */
.loop-opts{display:flex;flex-direction:column;gap:var(--s2);margin:12px 0 var(--s2)}
.loop-opt{display:flex;align-items:flex-start;gap:var(--s3);width:100%;padding:var(--s3);
  border:1px solid var(--line2);border-radius:var(--r-control);background:var(--paper);
  text-align:left;cursor:pointer;min-height:44px}
.loop-opt:hover{border-color:#b9b2a6}
.loop-opt:active{background:var(--warm)}
.loop-emoji{flex:none;font-size:20px;line-height:1.2}
.loop-opt b{display:block;font-size:var(--t-body);font-weight:var(--w-med);color:var(--ink);margin-bottom:2px}
.loop-opt small{display:block;font-size:var(--t-label);color:var(--muted);line-height:1.45}

/* ==========================================================================
   Calendar typography — the iOS layout, our data.

   WHAT WAS BORROWED, AND WHY ONLY THIS MUCH
   The month as a heading, the year stepped back into it, a quiet single-letter
   weekday rule, and today as a filled circle. That arrangement is what
   everybody's eye is already trained on, it costs nothing, and it makes the
   screen read as competent rather than homemade.

   WHAT WAS DELIBERATELY NOT BORROWED
   The dots. iOS dots mean "you have events on this day", and this grid means
   something a calendar cannot say: how many of your group can make it. That
   is the fill — white, blue, blue-green, then brand green when it is
   everyone — and it is the only reason to open this app rather than the one
   already on the phone. Replacing it with dots would be building a worse copy
   of the thing we are trying not to be. Dots survive for exactly one job, the
   one where they mean what Apple means: your own confirmed plans.

   AND NOT THE RED
   iOS marks today in red because red is Apple Calendar's brand colour. Ours
   is a meaning — busy, closed, gone — and spending it on decoration would
   make every real warning quieter. Today is ink.
   ========================================================================== */
/* The heading gets its own line on a phone.
   -------------------------------------------------------------------------
   At the size a month name wants to be, "December 2026" plus the Month/Week
   toggle came to six pixels wider than a 375px card — so the longest months
   of the year pushed the toggle off the screen while the short ones fit, and
   the bug would only have shown up in September. Stacking is also what iOS
   does: controls on one line, the month large on its own underneath.

   Side by side again from 860px, where there is genuinely room. */
.calendar-header{flex-direction:column;align-items:stretch;gap:var(--s2)}
.calendar-header .view-toggle{order:-1;align-self:flex-end}
.calendar-month{justify-content:space-between}
@media(min-width:860px){
  .calendar-header{flex-direction:row;align-items:center;gap:12px}
  .calendar-header .view-toggle{order:0;align-self:auto}
  .calendar-month{justify-content:flex-start}
}
html.force-phone .calendar-header{flex-direction:column;align-items:stretch}
html.force-phone .calendar-header .view-toggle{order:-1;align-self:flex-end}
.calendar-month strong{font-size:var(--t-title);font-weight:var(--w-med);letter-spacing:-.025em;line-height:1.15}
/* The year steps back in size as well as colour — same weight and size in a
   lighter grey still reads as part of the heading rather than behind it. */
.calendar-month.range strong{font-size:var(--t-card);letter-spacing:-.015em}
.calendar-month strong i{font-style:normal;color:var(--muted2);font-weight:var(--w-med);
  font-size:var(--t-card);letter-spacing:-.01em}

/* A rule under the weekdays, not boxes around them: it separates the labels
   from the grid without adding a sixth kind of border to the screen. */
.weekdays{border-bottom:1px solid var(--line);padding-bottom:5px;margin-bottom:var(--s2);gap:5px}
.weekdays span{color:var(--muted2);letter-spacing:.04em;padding:2px 0}
.weekdays span i{font-style:normal}
.weekdays span b{display:none;font-weight:inherit}
@media(min-width:860px){
  .weekdays span i{display:none}
  .weekdays span b{display:inline}
}
/* Inside the phone frame on a desktop the column is phone-width again, so the
   long spelling has to go back. */
html.force-phone .weekdays span i{display:inline}
html.force-phone .weekdays span b{display:none}

/* Today: a filled circle on the number, the way every calendar does it, and
   never a second border around the cell — the cell border is already carrying
   how free the group is. */
.month-day.today{box-shadow:none;border-color:var(--line)}
.month-day.today .day-num{background:var(--ink);color:#fff;border-radius:999px;
  min-width:21px;height:21px;line-height:21px;text-align:center;padding:0 5px;
  display:inline-block;margin:-1px 0 0 -1px}
/* On a dark cell the ink circle disappears into the fill, so it inverts. */
.month-day.lvl4.today .day-num,.month-day.lvl5.today .day-num{background:#fff;color:var(--ink)}


/* The one instruction on the answer page. People assumed the days were radio
   buttons and answered one, so the ask came back a seventh as useful as it
   should have been. */
.ask-hint{font-size:var(--t-label);color:var(--muted);margin:-2px 0 var(--s4)}

/* --- the Coordin8 wizard ------------------------------------------------------
   One question per screen. The old version stacked all five down a page, so
   the answer to the first had scrolled away by the third, the page grew under
   your thumb as blocks appeared, and changing an earlier answer meant hunting
   back up for it.

   No subheadings. Each question is short enough to be its own instruction and
   the grey line under it was answering things nobody had asked yet.        */
.co-wiz{padding-top:2px}
.co-wiz-head{display:flex;align-items:center;justify-content:space-between;gap:var(--s2);margin-bottom:16px}
.co-wiz-spacer{width:34px;flex:none}
.co-wiz-head .back-btn{flex:none}
.co-dots{display:flex;gap:var(--s2);align-items:center}
.co-dot{width:6px;height:6px;border-radius:50%;background:var(--line2);transition:width .18s ease}
.co-dot.done{background:#b9b2a6}
/* The current step is a bar rather than a bigger dot: it reads as progress
   along a line instead of one item being louder than the others. */
.co-dot.on{width:18px;border-radius:3px;background:var(--ink)}

.co-q{font-size:var(--t-title);font-weight:var(--w-med);color:var(--ink);
  margin:0 0 16px;letter-spacing:-.02em;line-height:1.2}

/* Rows, not tiles. Four squares in a grid made four short labels compete for
   width and pushed the sub-line onto three lines; a full-width row gives the
   label and its explanation a whole line each and reads top to bottom, which
   is the order you are choosing in. */
.co-rows{display:flex;flex-direction:column;gap:var(--s2)}
.co-row{display:flex;align-items:center;gap:12px;width:100%;padding:var(--s4);min-height:44px;
  border:1px solid var(--line2);border-radius:var(--r-control);background:var(--paper);
  text-align:left;cursor:pointer}
.co-row:hover{border-color:#b9b2a6}
.co-row:active{background:var(--warm)}
.co-row.on{border-color:var(--green);background:var(--green-soft)}
.co-row-emoji{flex:none;font-size:var(--t-title);line-height:1.2}
.co-row-text{flex:1;min-width:0}
.co-row-text b{display:block;font-size:var(--t-body);font-weight:var(--w-med);color:var(--ink);margin-bottom:2px}
.co-row-text small{display:block;font-size:var(--t-label);color:var(--muted);line-height:1.4}
.co-row.on .co-row-text small{color:var(--green)}
.co-row-go{flex:none;color:var(--muted2);font-size:17px}

.co-next{margin-top:var(--s4)}
.co-next:disabled{opacity:.45;cursor:default}

/* "Just get together" is not one of the activity tiles. It is a different kind
   of answer -- a refusal to answer yet -- and in the grid it read as an
   activity called Just Get Together. */
.co-open{display:flex;flex-direction:column;align-items:flex-start;gap:2px;width:100%;margin-top:12px;
  padding:var(--s3) var(--s4);border:1px dashed var(--line2);border-radius:var(--r-control);background:transparent;
  text-align:left;cursor:pointer;min-height:44px}
.co-open b{font-size:var(--t-body);font-weight:var(--w-med);color:var(--ink)}
.co-open span{font-size:var(--t-label);color:var(--muted)}
.co-open.on{border-style:solid;border-color:var(--green);background:var(--green-soft)}
.co-open:active{background:var(--warm)}

.co-chips{display:flex;flex-wrap:wrap;gap:8px}
/* The count and badges ride inside the chip so a long group name and a
   two-digit size cannot end up on separate lines from each other. */
.co-chips .who-chip{display:inline-flex;align-items:center;gap:var(--s2);min-height:44px}
.co-chips .who-chip i{font-style:normal;font-size:var(--t-label);color:var(--muted);
  background:var(--warm);border-radius:999px;padding:1px var(--s2)}
.co-chips .who-chip.on i{background:rgba(255,255,255,.35);color:inherit}
.co-chips .who-chip u{text-decoration:none;font-size:var(--t-label);text-transform:uppercase;
  letter-spacing:.08em;color:var(--muted);border:1px solid var(--line2);border-radius:6px;padding:0 5px}
.co-chips .who-chip em{font-style:normal;color:var(--green)}
.co-chips .who-chip.on em{color:inherit}

/* Something waiting on you outranks starting something new, so it sits above
   the wizard -- but as one line, because this is not the Plans tab. */
.co-waiting{display:flex;align-items:center;justify-content:space-between;gap:var(--s2);width:100%;
  margin-bottom:var(--s4);padding:var(--s3) var(--s4);border:1px solid #e6d9b8;border-radius:var(--r-control);
  background:#fdf6e3;text-align:left;cursor:pointer;min-height:44px}
.co-waiting b{font-size:var(--t-body);font-weight:var(--w-med);color:var(--ink)}
.co-waiting span{font-size:var(--t-label);color:var(--muted);white-space:nowrap}

/* Which build is on this device, and how to replace it. Plain and last on the
   screen — it is a diagnostic, not a feature. */
.build-card{display:flex;align-items:center;gap:12px;justify-content:space-between}
.build-card strong{font-family:var(--font-mono,ui-monospace,SFMono-Regular,Menlo,monospace);
  font-size:var(--t-body);font-weight:var(--w-med);color:var(--ink)}
.build-card p{margin:3px 0 0;font-size:var(--t-label);color:var(--muted);line-height:1.4;
  overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
.build-card button{flex:none}

/* --- an older app than the web it is showing ---------------------------------
   The iOS tab bar is native and the web one is hidden behind it, so a binary
   that predates a tab cannot reach it and nothing on screen says so. This is
   the escape hatch: only the missing tabs, only on an outdated shell, and it
   removes itself the moment somebody installs a current build.             */
.legacy-bar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;
  margin:0 12px 8px;padding:var(--s2) var(--s3);border:1px solid #e6d9b8;border-radius:var(--r-control);
  background:#fdf6e3}
.legacy-bar span{flex:1;min-width:130px;font-size:var(--t-label);color:#7a6a3f;line-height:1.4}
.legacy-bar button{flex:none;min-height:36px;padding:0 12px;border-radius:var(--r-control);
  border:1px solid #e0cf9f;background:var(--paper);color:var(--ink);
  font-size:var(--t-label);font-weight:var(--w-med);cursor:pointer}
.legacy-bar button.on{background:var(--ink);color:#fff;border-color:var(--ink)}
/* Only ever inside the native shell. A browser has the real nav. */
html:not(.native-legacy) .legacy-bar{display:none}

/* ==========================================================================
   The week strip, in two colours.

   It had four states drawn four ways — green for free, grey for busy, a dashed
   outline for unanswered, and half-opacity for skipped — so reading it meant
   decoding a legend every time. The only question it answers is "which
   evenings am I free", and that has one answer worth colouring.

   Green means free. Everything else is white, because everything else is not
   free, and the difference between busy, unanswered and skipped matters when
   you tap a day, not when you glance at the row.
   ========================================================================== */
.week-day.free{background:var(--green-soft);border-color:#cfe0d4}
.week-day.busy,
.week-day.unset,
.week-day.snoozed{background:var(--paper);border-color:var(--line2);border-style:solid}
.week-day.busy .wd-num,
.week-day.unset .wd-num,
.week-day.snoozed .wd-num{color:var(--ink)}
/* Skipped stays legible rather than faded to 45%: a day you have deliberately
   set aside is information, not something to squint at. */
.week-day.snoozed{opacity:1}
.week-day.snoozed .wd-dow,
.week-day.snoozed .wd-num{color:var(--muted)}

/* The button inside follows the same rule: filled only when free. */
.week-day.free .wd-set{background:var(--green);border-color:var(--green);color:#fff}
.week-day.busy .wd-set,
.week-day.unset .wd-set,
.week-day.snoozed .wd-set{background:var(--paper);border-color:var(--line2);color:var(--muted);border-style:solid}


/* --- the way in --------------------------------------------------------------
   Phone or email first, then Continue, then the providers under a divider.
   The identifier is the primary route, so it comes first and the alternatives
   are introduced by "or" rather than the other way round.                    */
.auth-id{margin-bottom:var(--s2)}
.auth-switch{margin:16px 0 0;text-align:center;font-size:var(--t-label);color:var(--muted2)}
.link-inline{border:0;background:none;padding:0;font:inherit;font-weight:var(--w-med);
  color:var(--ink);text-decoration:underline;cursor:pointer}
.link-inline:hover{opacity:.75}
/* One character per box would be six inputs to manage; one field, spaced, is
   the same thing to look at and a tenth of the code. */
.code-input{font-size:22px;letter-spacing:.34em;text-align:center;font-weight:var(--w-med);
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
.auth-legal{margin-top:var(--s4)}

/* Sign out and delete, which were 10px apart and 44 tall in a place where the
   consequences are not remotely the same. */
.account-danger{margin-top:var(--s5);padding-top:var(--s4);border-top:1px solid var(--line)}
.account-danger .danger{margin:0}
.account-danger .danger-text{margin-top:16px;font-size:var(--t-label)}

/* Which build this is. A diagnostic, so it reads like one: one grey line at
   the very bottom rather than a card competing with the settings above it. */
.build-line{display:flex;align-items:center;justify-content:space-between;gap:12px;
  margin:var(--s5) 2px 0;font-size:var(--t-label);color:var(--muted2)}
.build-line code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
.build-line button{border:0;background:none;padding:0;font:inherit;color:var(--muted);
  text-decoration:underline;cursor:pointer;min-height:32px}

/* Our button, next to Apple's. Apple's guidelines allow black, white or
   outlined only — so ours is the one that has to move. Green is the brand
   colour and the one already used for "free", which is the right association
   for the button that gets you in. */
.auth-go{background:var(--green);border-color:var(--green);color:#fff}
.auth-go:hover{opacity:.92}
.auth-go:disabled{opacity:.5}
/* The wordmark is gone from the auth card; the heading carries the name. */
.auth-card h1{margin-top:0}

/* ==========================================================================
   The real app inside a picture of a phone.

   The force-phone rules above 860px exist for the DESKTOP preview: a 390x844
   shell with an 11px bezel, a drawn notch and a gradient desk behind it, so
   the iOS build can be looked at without a Mac. The iOS shell also sets
   force-phone — for the layout, not the picture — and every iPad orientation
   is over 860px. So on an iPad the actual app rendered inside a mock iPhone,
   and `html.force-phone .bottom-nav{display:grid!important}` (0,2,1) beat
   `.native-shell .bottom-nav{display:none!important}` (0,2,0), putting the web
   tab bar on top of the native one.

   `no-frame` is set by the shell and by nothing else. Same layout, no costume.
   ========================================================================== */
@media(min-width:860px){
  html.force-phone.no-frame .app-wrap{padding:0;background:var(--bg);align-items:stretch}
  html.force-phone.no-frame .app-shell{max-width:none;width:100%;height:auto;min-height:100vh;
    border:0;border-radius:0;box-shadow:none;padding-bottom:0;overflow:visible}
  html.force-phone.no-frame .app-shell:before{display:none}
  html.force-phone.no-frame .content{max-height:none;padding:0 16px 34px}
  html.force-phone.no-frame .topbar{padding:var(--s4) var(--s5)}
  /* The native bar is the only navigation in the shell. */
  html.force-phone.no-frame .bottom-nav{display:none!important}
  html.force-phone.no-frame .sheet{max-width:520px}
  html.force-phone.no-frame .toast{bottom:calc(24px + env(safe-area-inset-bottom))}
}

/* --- iOS zooms any input under 16px, and twelve of them were 12 -------------
   The guard matched `.input` or an explicit type attribute, so every search
   box and both chat composers — the highest-frequency text inputs in the app —
   were missed, because an <input> with no type attribute is still a text
   input. Tapping one zoomed the page in and iOS does not zoom back out.
   Specificity matters too: .search-box input is (0,1,1) and beat the (0,1,0)
   guard, so this has to be at least as specific.                            */
@media (pointer: coarse){
  .search-box input,.search-inline input,.composer input,.pp-composer input,
  .handle-input input,input:not([type]),textarea{font-size:16px}
}

/* --- draggable blocks own their vertical gesture -----------------------------
   touch-action is resolved when the finger lands, so drag.js setting it 350ms
   later on arm never affected the gesture already in flight — iOS had already
   handed it to the scroller and fired pointercancel. Declared up front here
   instead. pan-x still allows a horizontal swipe past the block; what is taken
   is the vertical pan, which is the one the drag needs and the only one that
   was being stolen.                                                          */
.tblock,.wk-block{touch-action:pan-x}
/* The sheet header is a drag handle too — bindDragDismiss binds it — but it
   had no touch-action, so dragging it just scrolled the sheet. It worked with
   a mouse, which is where it was tested. */
.sheet-head{touch-action:none}

/* ==========================================================================
   Onboarding: nothing over it, nothing under it, nothing behind it.
   ========================================================================== */

/* This used to turn off a sticky, solid grab bar that sat over the first line
   of every setup screen. Every sheet in the app behaves that way now, so the
   position and background overrides are gone — what stays is the fainter pill,
   because during setup there is nothing to drag the sheet away with anyway: the
   × is removed and dismissSheet is guarded. */
html.onboarding .sheet .grabber:before{opacity:.35}

/* The last button was landing under the home indicator. */
html.onboarding .sheet{padding-bottom:calc(30px + env(safe-area-inset-bottom))}
html.onboarding .sheet .sheet-actions{margin-bottom:var(--s2)}
html.onboarding .sheet .text-btn.wide{margin-bottom:2px}

/* The tab bar has no business being on screen during setup — none of it is
   reachable, and on the native shell it is drawn by SwiftUI underneath. */
html.onboarding .bottom-nav,
html.onboarding .side-nav,
html.onboarding .floating-actions{display:none!important}

/* --- blocking out a week, small enough to scan ------------------------------
   The rows were a card each with an emoji, a label, a seven-day strip and a
   Time button — a full screen for three of them, on a step people are trying
   to get through. A block is a label and its hours; the days come after you
   have said yes to it.                                                      */
.block-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.block-tile{display:flex;align-items:center;gap:8px;padding:var(--s3) var(--s2);min-height:44px;
  border:1px solid var(--line2);border-radius:var(--r-control);background:var(--paper);
  text-align:left;cursor:pointer}
.block-tile:active{background:var(--warm)}
.block-tile.on{border-color:var(--green);background:var(--green-soft)}
.block-tile .be{flex:none;font-size:17px;line-height:1}
.block-tile .bt{flex:1;min-width:0}
.block-tile .bt b{display:block;font-size:var(--t-label);font-weight:var(--w-med);color:var(--ink);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.block-tile .bt i{display:block;font-style:normal;font-size:var(--t-micro);color:var(--muted);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.block-tile.on .bt i{color:var(--green)}
@media(max-width:339px){ .block-grid{grid-template-columns:1fr} }

/* The time step: start, then end, then save. End stays shut until start is
   answered, because "ends before it starts" is not a real answer to correct. */
.bt-step{margin-top:var(--s4)}
.bt-step.locked{opacity:.4;pointer-events:none}
.bt-label{font-size:var(--t-label);letter-spacing:.08em;text-transform:uppercase;
  font-weight:var(--w-med);color:var(--muted);margin:0 2px var(--s2)}
.bt-days{display:flex;gap:var(--s2);margin-top:4px}
.bt-days button{flex:1;min-height:40px;border:1px solid var(--line2);border-radius:var(--r-control);
  background:var(--paper);font-size:var(--t-label);font-weight:var(--w-med);color:var(--muted);cursor:pointer}
.bt-days button.on{background:var(--green);border-color:var(--green);color:#fff}

/* --- the day answer, in the order people answer it --------------------------
   Two buttons side by side for the answer that is right nine times out of ten,
   a rule, then the partial-evening wheel underneath for the tenth. The popup
   is taller so Save is on screen when it opens — it used to sit below the fold
   and looked like it did not exist.                                          */
.ba-pair{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:12px}
.ba-pair button{min-height:48px;font-size:var(--t-body)}
.ba-or{display:flex;align-items:center;gap:var(--s2);margin:16px 0 12px;color:var(--muted2);
  font-size:var(--t-label)}
.ba-or:before,.ba-or:after{content:"";flex:1;height:1px;background:var(--line)}
.mini-pop .ba-read{margin:var(--s2) 0 12px}
/* Taller, so Save is visible without scrolling. */
.mini-pop{max-height:88vh}

/* A square that says what it is by colour, not by a word. */
.week-day .wd-set{font-size:var(--t-body);font-weight:var(--w-med)}
.week-day.unset .wd-set{border-style:dashed}

/* ==========================================================================
   One calendar. The week strip is gone; the month is the page.
   ========================================================================== */

/* The nag that replaced seven squares, a snooze row and a payoff line. It says
   the number and opens the week — the one thing the strip could do that the
   month cannot. */
.week-nag{display:flex;align-items:center;justify-content:space-between;gap:var(--s2);width:100%;
  margin-bottom:var(--s4);padding:var(--s3) var(--s4);min-height:44px;
  border:1px solid #e6d9b8;border-radius:var(--r-control);background:#fdf6e3;
  text-align:left;cursor:pointer}
.week-nag b{font-size:var(--t-body);font-weight:var(--w-med);color:#7a6a3f}
.wn-go{font-size:var(--t-label);white-space:nowrap}
.week-nag .wn-go{color:#9a8a58}

/* This week, inside the month. A ring rather than a fill, so it never argues
   with the availability colour the cell is already carrying. */
.month-day.wk{box-shadow:inset 0 0 0 1px var(--line2)}
.month-day.wk.today{box-shadow:none}
.month-day.picked{outline:2px solid var(--ink);outline-offset:-2px}

/* The answer, under the grid, on the cell you tapped. */
.day-answer{margin-top:var(--s2);padding:var(--s3);border:1px solid var(--line2);
  border-radius:var(--r-control);background:var(--paper)}
.da-head{display:flex;align-items:center;justify-content:space-between;gap:var(--s2);margin-bottom:var(--s2)}
.da-head b{font-size:var(--t-body);font-weight:var(--w-med);color:var(--ink)}
.da-on{margin:0 0 var(--s2);font-size:var(--t-label);color:var(--muted);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.da-btns{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.da-btns button{min-height:46px;border:1px solid var(--line2);border-radius:12px;
  background:var(--paper);font-size:var(--t-body);font-weight:var(--w-med);
  color:var(--ink);cursor:pointer}
.da-btns button.on{background:var(--green);border-color:var(--green);color:#fff}
.da-btns button:active{background:var(--warm)}

/* The who-is-free list inside the composer.
   -------------------------------------------------------------------------
   Same component as the group day sheet, one step tighter: this sits between a
   summary card and the day and time fields, so it has to read as supporting
   detail rather than as the subject of the screen. Capped and scrolled past
   six people, because a group of twelve would otherwise push the Day field off
   the bottom on a phone — and the day is what the screen is for. */
/* No cap and no nested scroll. It shows four and offers the rest, so there is
   never a scroll region inside a page that already scrolls — which on a phone
   is a coin flip over which one moves — and never a cut edge that reads as
   truncated. --s1 rather than 6px: tighter than the group sheet's --s2, which
   is the intent, and on the scale. */
.pr-who-list{margin-top:var(--s2)}
.pr-who-list .availability-person{padding:var(--s1) var(--s3)}

/* Busy is grey and neutral; "has not said" is an outline, because it is an
   absence rather than a state. Shared with the group day sheet. */
.state-label.unknown{background:transparent;border:1px solid var(--line2);color:var(--muted)}
/* A long name must squeeze the name, not push the pill off the row. */
.availability-person > div{min-width:0}
.availability-person strong,.availability-person small{overflow-wrap:anywhere}
.state-label{flex:0 0 auto;white-space:nowrap}

/* ==========================================================================
   PLAN AND GROUP CARDS

   The complaint was "it looks like texts wrapped a bunch", and it was
   measurable: the plan card's meta line — "Sun Aug 23 · 7:00 PM · Sacramento,
   CA" — was rendering over FOUR lines. .plan-top is a flex row with the title
   on the left and the pills plus the menu button on the right, and the right
   side is `flex:0 0 auto` while the left had no `min-width:0`. A flex item
   will not shrink below its content by default, so the title block was pinned
   at its longest word and the meta had roughly a third of the card to wrap in.

   Three rules fix the shape; the rest is spacing.
   ========================================================================== */
.plan-top > div:first-child{min-width:0;flex:1 1 auto}
.plan-top h3{margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* One line, and an ellipsis rather than a paragraph. Date, time and place are
   scanned, not read — and the full text is on the plan itself. */
.plan-meta{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:3px}

/* The status pills wrap to their own row on a narrow phone instead of
   strangling the title. The menu button stays put at the top right. */
.plan-top-right{align-self:flex-start}
@media(max-width:400px){
  .plan-card .pill.status{font-size:var(--t-label);padding:4px 7px}
}

/* Room to breathe between the parts, and none wasted inside them. */
.plan-card .progress-line{margin-top:var(--s3)}
.plan-bottom{margin-top:var(--s3)}
.next-action{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  max-width:58%;text-align:right}

/* The group card's identity row had the same squeeze: a long group name and
   five avatars against a filter chip, a star and a menu. */
.gd-head{align-items:flex-start}
.gd-id{min-width:0;flex:1 1 auto}
.gd-id > span{min-width:0}
.gd-id strong,.gd-id > span > span{display:block;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.gd-tools{flex:0 0 auto;align-self:flex-start}
/* The filter chip drops its label when there is no room for it; the icon and
   the state colour still say what it is. */
@media(max-width:420px){
  .gd-tools .filter-btn span{display:none}
  .gd-tools .filter-btn{padding:0 var(--s2)}
}

/* A day that has been and gone.
   -------------------------------------------------------------------------
   Dimmed and struck, and still tappable: looking up what you did last Thursday
   is a real thing to want, and a calendar that refuses to open its own past is
   a calendar with a hole in it. The mark is a thin diagonal rather than a
   drawn glyph so it never competes with the date underneath it. */
.month-day.past{opacity:.55}
.month-day.past .day-num{position:relative}
.month-day.past .day-num:after{content:"";position:absolute;left:-2px;right:-2px;top:50%;
  height:1px;background:currentColor;opacity:.5;transform:rotate(-12deg)}
.month-day.past.today{opacity:1}
.day-answer.past .da-head b{opacity:.7}
.da-past{margin:var(--s2) 0 0;font-size:var(--t-label);color:var(--muted);line-height:1.45}

/* A block you set yourself, told apart from everything else on the day. */
.day-dots i.blocked{background:var(--ink);opacity:.45}

/* The answer is part of the calendar card, so it loses its own frame and
   gains the divider that says "same object, next section". */
.calendar-card .day-answer{margin:var(--s3) 0 0;padding:var(--s3) 0 0;
  border-top:1px solid var(--line);border-radius:0;background:transparent}

/* The city type-ahead.
   -------------------------------------------------------------------------
   Anchored to the field rather than pushed below it, so the rest of the form
   does not jump every time a letter is typed — which on a phone means the
   Continue button moving under a thumb already on its way to it. */
.city-field{position:relative}
.city-suggest{position:absolute;left:0;right:0;top:100%;z-index:6;margin-top:var(--s1);
  background:var(--paper);border:1px solid var(--line2);border-radius:var(--r-control);
  box-shadow:0 12px 30px rgba(40,35,28,.14);overflow:hidden}
.city-opt{display:flex;align-items:baseline;justify-content:space-between;gap:var(--s2);
  width:100%;min-height:44px;padding:var(--s2) var(--s3);border:0;background:transparent;
  text-align:left;cursor:pointer;font-size:var(--t-body);color:var(--ink)}
.city-opt + .city-opt{border-top:1px solid var(--line)}
.city-opt small{font-size:var(--t-label);color:var(--muted);flex:0 0 auto}
.city-opt:hover,.city-opt:focus-visible{background:var(--warm)}

/* One line under each of the three promises on the welcome screen. */
.ob-point small{display:block;margin-top:3px;font-size:var(--t-label);
  line-height:1.45;color:var(--muted)}

/* What this group has done before, said quietly.
   -------------------------------------------------------------------------
   All of it is a nudge rather than a decision: the habit line is a note, the
   ringed day is still one of ten, and "been here" is a label on a card that
   sits in the same list as everywhere else. An app that remembers should feel
   like a friend who remembers, not like a filter that has decided. */
.co-habit{margin:-4px 2px var(--s3);font-size:var(--t-meta);color:var(--muted)}
.day-row.usual{border-color:var(--ink)}
.day-row.usual .dr-when b:after{content:"usual";margin-left:var(--s2);padding:2px var(--s2);
  border:1px solid var(--line);border-radius:999px;font-size:var(--t-label);
  font-weight:var(--w-med);color:var(--muted);letter-spacing:.04em}
.co-been{margin-left:var(--s2);padding:2px var(--s2);border:1px solid var(--line);border-radius:999px;
  font-size:var(--t-label);font-weight:var(--w-med);color:var(--muted);letter-spacing:.02em}

/* What else is on that night, and what it will cost.
   -------------------------------------------------------------------------
   Deliberately quieter than the venue cards above it. These are not answers to
   the question that was asked — they are the thing the app knows that you did
   not, which earns a mention and not the top of the screen. */
.co-onnight{margin-top:16px;padding-top:var(--s4);border-top:1px solid var(--line2)}
.con-head{margin:0 0 8px 2px;font-size:var(--t-label);letter-spacing:.1em;
  text-transform:uppercase;color:var(--muted)}
.con-row{display:flex;align-items:center;justify-content:space-between;gap:12px;width:100%;
  text-align:left;padding:var(--s2) 12px;margin-bottom:var(--s2);background:var(--paper);cursor:pointer;
  border:1px solid var(--line2);border-radius:var(--r-control)}
.con-row.on{border-color:var(--ink);background:var(--warm)}
.con-text{display:flex;flex-direction:column;gap:2px;min-width:0}
.con-text b{font-size:var(--t-body);font-weight:var(--w-med);color:var(--ink);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.con-text span{font-size:var(--t-label);color:var(--muted);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.con-tick{flex:none;width:24px;text-align:center;color:var(--muted);font-size:var(--t-body)}
.con-row.on .con-tick{color:var(--ink)}
.co-cost{text-align:center;margin-top:-4px}
/* Loud enough to read, quiet enough that it is plainly not a blocker — the
   plan is still sendable and the copy says so. */
.co-sky-warn{margin:var(--s4) 0 0;padding:var(--s2) 12px;border-radius:12px;background:var(--warm);
  border:1px solid var(--line);font-size:var(--t-meta);line-height:1.45;color:#5f5e57}

/* Filling the week in one sitting. */
/* Seven rows, a snooze and a Save, with no scrolling.
   -------------------------------------------------------------------------
   This is a sheet whose whole promise is "answer the week in one sitting", and
   the Done button was below the fold — so the last two days and the way out
   were both things you had to go looking for. Rows are 46 rather than 56, and
   between that and the grabber the whole thing clears a small phone. */
.fw-list{display:flex;flex-direction:column;gap:5px}
.fw-row{display:flex;align-items:center;justify-content:space-between;gap:var(--s2);
  padding:3px var(--s2);border:1px solid var(--line2);border-radius:12px;background:var(--paper)}
/* This sheet is the one place a row is competing with six others and a Save
   button for a single screen, so its own head and snooze row are tighter than
   the shared ones — scoped here rather than shrinking every sheet in the app
   to solve one of them. */
.fw-sheet .sheet-head{margin-bottom:var(--s2)}
.fw-snooze{padding-top:var(--s2);padding-bottom:var(--s2);margin-top:8px}
.fw-sheet .est-note{margin:6px 2px}
.fw-row.free{background:var(--green-soft);border-color:#cfe0d4}
.fw-when b{font-size:var(--t-body);font-weight:var(--w-med);color:var(--ink)}
.fw-when i{font-style:normal;font-size:var(--t-label);color:var(--muted);margin-left:var(--s2)}
.fw-btns{display:flex;gap:var(--s2);flex:none}
.fw-btns button{min-height:34px;padding:0 14px;border:1px solid var(--line2);border-radius:var(--r-control);
  background:var(--paper);font-size:var(--t-label);font-weight:var(--w-med);
  color:var(--muted);cursor:pointer}
.fw-btns button.on{background:var(--green);border-color:var(--green);color:#fff}

/* ==========================================================================
   Complete Profile — the banner, the flow, and the settings rows that open it
   ========================================================================== */

/* The banner. Blue on purpose: every other surface in this app is warm, and
   this is the one thing on screen that is not about a plan. It reads as a
   status line rather than a card, and it is gone the moment it has nothing
   left to report. */
:root{
  --pf-blue:#1d5fd6;
  --pf-blue-soft:#eaf1fe;
  --pf-blue-line:#c9dcfb;
}
.profile-banner{
  display:flex; align-items:center; gap:var(--s2);
  /* No width. It was calc(100% - 16px) with 24px of margins - over-constrained,
     so the browser dropped margin-right and the banner sat 12px from the left
     and 4px from the right, on every screen, until the profile was finished.
     The margins alone are unambiguous. */
  margin:var(--s2) var(--s4) 0;
  padding:var(--s2) var(--s3);
  background:var(--pf-blue-soft); border:1px solid var(--pf-blue-line);
  border-radius:var(--r-control); text-align:left; cursor:pointer;
  font:inherit; color:var(--ink);
}
.profile-banner:active{ transform:scale(.995) }
.pb-dot{
  flex:0 0 auto; width:8px; height:8px; border-radius:var(--r-pill);
  background:var(--pf-blue);
}
.pb-text{ flex:1 1 auto; min-width:0; display:flex; flex-direction:column; gap:1px }
.pb-text b{ font-size:var(--t-body); font-weight:650; color:var(--pf-blue) }
.pb-text small{
  font-size:var(--t-micro); color:var(--muted);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.pb-count{
  flex:0 0 auto; font-size:var(--t-meta); font-weight:700; color:var(--pf-blue);
  background:#fff; border:1px solid var(--pf-blue-line);
  border-radius:var(--r-pill); padding:2px 8px;
}
.pb-go{ flex:0 0 auto; color:var(--pf-blue); font-size:var(--t-card); line-height:1 }

/* The flow. One button, at the bottom, always reachable; back is a small arrow
   that does not compete with it. */
.pf-top{
  display:flex; align-items:center; justify-content:space-between;
  margin:0 0 var(--s2);
}
.pf-back{
  width:36px; height:36px; padding:0; flex:0 0 auto;
  display:flex; align-items:center; justify-content:center;
  font-size:20px; line-height:1; color:var(--ink);
  background:var(--gray-soft); border:1px solid var(--line);
  border-radius:var(--r-pill); cursor:pointer;
}
.pf-back:active{ background:var(--line) }
.pf-step{ font-size:var(--t-micro); color:var(--muted); letter-spacing:.04em }
.pf-head h2{ font-size:var(--t-title); margin:0 0 4px }
.pf-head p{ font-size:var(--t-body); color:var(--muted); margin:0 0 var(--s3) }
.pf-body{ margin-bottom:var(--s3) }
/* Sticky, so the one button on the screen is never the thing below the fold.
   Full width and at the bottom because that is where the thumb already is. */
.pf-foot{
  position:sticky; bottom:0; z-index:5;
  margin:var(--s3) calc(var(--s3) * -1) 0;
  padding:var(--s2) var(--s3) calc(var(--s3) + env(safe-area-inset-bottom));
  background:var(--card);
  border-top:1px solid var(--line);
}
.pf-next{ min-height:50px; font-size:var(--t-card) }
/* The sheet gives up its bottom padding for this flow.
   -------------------------------------------------------------------------
   A sticky bottom:0 element is held by the scroll container's CONTENT box, not
   its padding box, so the sheet's 24px bottom padding parked the one button on
   the screen 33px up with a dead strip of card beneath it. A negative margin
   does not help - it moves the box, not the constraint. Removing the padding
   moves the constraint, and .pf-foot carries the spacing itself. */
.sheet.pf-sheet{ padding-bottom:0 }

/* Settings: real groups, one row shape. */
.settings-group{
  font-size:var(--t-micro); letter-spacing:.08em; color:var(--muted);
  margin:var(--s4) 2px var(--s2); text-transform:uppercase;
}
.settings-nudge{
  display:flex; align-items:center; gap:var(--s2); width:100%; text-align:left;
  margin-top:var(--s2); padding:var(--s3);
  background:var(--pf-blue-soft); border:1px solid var(--pf-blue-line);
  border-radius:var(--r-card); cursor:pointer; font:inherit; color:var(--ink);
}
.settings-nudge span:nth-child(2){ flex:1 1 auto; min-width:0; display:flex; flex-direction:column }
.settings-nudge b{ color:var(--pf-blue); font-size:var(--t-body) }
.settings-nudge small{ color:var(--muted); font-size:var(--t-micro) }

/* The calendar offer. One thing to look at, one thing to press. */
.cal-offer{
  text-align:center; padding:var(--s4) var(--s3);
  background:var(--gray-soft); border:1px solid var(--line);
  border-radius:var(--r-card);
}
.cal-offer.on{ background:var(--pf-blue-soft); border-color:var(--pf-blue-line) }
.cal-mark{ font-size:34px; line-height:1; display:block; margin-bottom:var(--s2) }
.cal-offer.on .cal-mark{ color:var(--pf-blue); font-weight:700 }
.cal-offer h3{ font-size:var(--t-card); margin:0 0 4px }
.cal-offer p{
  font-size:var(--t-body); color:var(--muted);
  margin:0 auto var(--s3); max-width:34ch;
}
.cal-offer.on p{ margin-bottom:0 }
.cal-providers{ display:flex; flex-direction:column; gap:var(--s2) }
.cal-provider{
  display:flex; align-items:center; gap:var(--s2);
  padding:var(--s2) var(--s3); text-align:left;
  background:var(--card); border:1px solid var(--line);
  border-radius:var(--r-control); cursor:pointer; font:inherit;
}
.cal-provider-icon{ flex:0 0 auto; font-size:20px }
.cal-provider-name{ flex:1 1 auto; font-weight:600; font-size:var(--t-body) }
.cal-provider-go{ flex:0 0 auto; color:var(--muted) }
/* Three facts, not a paragraph. The bolded half of each line is the answer;
   the rest is the detail for anybody who wants it. */
.cal-facts{ list-style:none; margin:var(--s3) 0 var(--s2); padding:0 }
.cal-facts li{
  font-size:var(--t-meta); color:var(--muted);
  padding:var(--s2) 0; border-bottom:1px solid var(--line);
}
.cal-facts li:last-child{ border-bottom:0 }
.cal-facts b{ color:var(--ink); font-weight:650 }

/* ==========================================================================
   The day you tapped, under the month
   ========================================================================== */
.da-head{ display:flex; align-items:baseline; justify-content:space-between; gap:var(--s2) }
.da-state{ font-size:var(--t-micro); color:var(--muted) }

/* One control with two ends rather than two buttons where one is always the
   wrong one to press. It also states the current answer without anybody having
   to work out which of two things looks pressed. */
.da-switch-row{ display:flex; align-items:center; gap:var(--s2); margin:var(--s2) 0 }
.da-switch{
  flex:1 1 auto; display:flex; align-items:center; gap:var(--s2);
  padding:var(--s2) var(--s3); min-height:48px;
  background:var(--card); border:1px solid var(--line);
  border-radius:var(--r-control); cursor:pointer; font:inherit;
}
.da-track{
  flex:0 0 auto; width:46px; height:28px; border-radius:var(--r-pill);
  background:var(--line); position:relative; transition:background .16s ease;
}
.da-knob{
  position:absolute; top:3px; left:3px; width:22px; height:22px;
  border-radius:50%; background:#fff; box-shadow:0 1px 3px rgba(0,0,0,.28);
  transition:transform .16s ease;
}
.da-switch.busy .da-track{ background:var(--ink) }
.da-switch.busy .da-knob{ transform:translateX(18px) }
.da-switch.free .da-track{ background:var(--green, #3f9d52) }
.da-switch-label{ font-weight:650; font-size:var(--t-body) }
/* Only exists once the answer is busy, so it never sits there inert. */
.da-part-go{
  flex:0 0 auto; width:48px; min-height:48px; padding:0;
  display:flex; align-items:center; justify-content:center;
  font-size:20px; line-height:1; color:var(--ink);
  background:var(--card); border:1px solid var(--line);
  border-radius:var(--r-control); cursor:pointer;
}
.da-part-go:active{ background:var(--gray-soft) }

/* What is actually on the day. This used to be a whole second screen behind a
   "More" button, to show a list that fits here. */
.da-blocks{ margin-top:var(--s3); border-top:1px solid var(--line); padding-top:var(--s2) }
.da-blocks-head{
  display:flex; align-items:center; justify-content:space-between;
  font-size:var(--t-micro); color:var(--muted); letter-spacing:.04em;
  text-transform:uppercase; margin-bottom:var(--s2);
}
/* 44, not 32. Apple's minimum is 44pt and this is the control that adds an
   event to the day you are looking at - the one thing on that panel somebody
   reaches for without looking. */
.da-add{
  width:44px; height:44px; padding:0; flex:0 0 auto; margin:-6px -6px -6px 0;
  display:flex; align-items:center; justify-content:center;
  font-size:20px; line-height:1; color:var(--ink);
  background:var(--gray-soft); border:1px solid var(--line);
  border-radius:var(--r-pill); cursor:pointer;
}
.da-block{
  display:flex; align-items:baseline; gap:var(--s2); width:100%; text-align:left;
  padding:var(--s2) 0; background:none; border:0; border-bottom:1px solid var(--line);
  cursor:pointer; font:inherit;
}
.da-block:last-child{ border-bottom:0 }
.da-block-when{
  flex:0 0 auto; font-size:var(--t-micro); color:var(--muted);
  font-variant-numeric:tabular-nums; min-width:11ch;
}
.da-block-what{
  flex:1 1 auto; min-width:0; font-size:var(--t-body);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.da-repeats{
  font-style:normal; font-size:var(--t-micro); color:var(--muted);
  border:1px solid var(--line); border-radius:var(--r-pill);
  padding:1px 6px; margin-left:6px;
}
.da-empty{ font-size:var(--t-meta); color:var(--muted); margin:0 0 var(--s2) }
.day-answer.past .da-blocks{ opacity:.75 }
/* scrollIntoView({block:'nearest'}) stops the instant the box is technically
   inside the viewport, which put the last row flush against the bottom edge
   with a pixel to spare. This is the breathing room it should stop with. */
.day-answer{ scroll-margin-block:var(--s4) }

/* ==========================================================================
   The plan page, which is a chat page
   --------------------------------------------------------------------------
   Three bands: a fixed head, a thread that takes everything left over, and a
   fixed foot. The page itself never scrolls - only .cp-thread does - which is
   what stops the composer from wandering off the bottom of a long thread and
   the header from scrolling away from the title it is naming.
   ========================================================================== */
.chat-page{
  display:flex; flex-direction:column;
  /* Height comes from fitChatPage() in ui-core.js, measured after every render.
     There is nothing here to be a percentage of: .content is a plain block of
     automatic height inside a scrolling #app, so height:100% resolved to
     "however tall the messages are" and the composer left the screen. */
  min-height:0; padding:0;
}
.cp-head{
  flex:0 0 auto; padding:var(--s2) var(--s3) var(--s3);
  border-bottom:1px solid var(--line); background:var(--card);
}
.cp-head-row{ display:flex; align-items:center; gap:var(--s2) }
.cp-back{
  width:40px; height:40px; padding:0; margin-left:-8px; flex:0 0 auto;
  display:flex; align-items:center; justify-content:center;
  font-size:26px; line-height:1; color:var(--ink);
  background:none; border:0; cursor:pointer;
}
.cp-head-row .pill.status{ margin-left:auto }
.cp-menu-btn{
  width:40px; height:40px; padding:0; flex:0 0 auto;
  display:flex; align-items:center; justify-content:center;
  font-size:22px; line-height:1; color:var(--ink);
  background:var(--gray-soft); border:1px solid var(--line);
  border-radius:var(--r-pill); cursor:pointer;
}
.cp-menu-btn:active{ background:var(--line) }
/* Full width, under the arrow, rather than squeezed between two controls where
   a three-word plan title wrapped to two lines. */
.cp-title{
  font-size:var(--t-title); margin:var(--s2) 0 2px; line-height:1.15;
  overflow-wrap:anywhere;
}
.cp-sub{ font-size:var(--t-meta); color:var(--muted); margin:0 }

.cp-thread{
  flex:1 1 auto; min-height:0; overflow-y:auto; -webkit-overflow-scrolling:touch;
  padding:var(--s3); display:flex; flex-direction:column; gap:var(--s2);
  overscroll-behavior:contain;
}
.cp-thread > .pp-extras,
.cp-thread > .pp-summary{ flex:0 0 auto }

.cp-foot{
  flex:0 0 auto; padding:var(--s2) var(--s3) calc(var(--s2) + env(safe-area-inset-bottom));
  border-top:1px solid var(--line); background:var(--card);
}
.cp-foot .pp-composer{ margin:0 }
.cp-foot .primary.wide{ margin-bottom:var(--s2) }
.cp-who-finalizes{ text-align:center; margin:0 0 var(--s2) }

/* The dropdown. Body-appended so a render cannot delete it mid-tap, anchored
   under the icon it came from. */
.pm-layer{ position:fixed; inset:0; z-index:70 }
.pm-menu{
  position:absolute; top:calc(env(safe-area-inset-top) + 96px); right:var(--s3);
  min-width:212px; padding:var(--s1);
  background:var(--card); border:1px solid var(--line);
  border-radius:var(--r-card); box-shadow:0 12px 32px rgba(0,0,0,.18);
  animation:pmIn .12s ease-out;
}
@keyframes pmIn{ from{ opacity:0; transform:translateY(-6px) } to{ opacity:1; transform:none } }
.pm-menu button{
  display:flex; align-items:center; gap:var(--s2); width:100%; text-align:left;
  padding:var(--s2) var(--s3); min-height:46px;
  background:none; border:0; border-radius:var(--r-control);
  font:inherit; font-size:var(--t-body); color:var(--ink); cursor:pointer;
}
.pm-menu button:active{ background:var(--gray-soft) }
.pm-menu button span{ width:22px; flex:0 0 auto; text-align:center }

/* ==========================================================================
   Pull to refresh
   --------------------------------------------------------------------------
   The strip lives inside #app, just above it, so the page sliding down reveals
   it. No z-index games and nothing to keep in sync — it is carried by the same
   transform.
   ========================================================================== */
#app{ overscroll-behavior-y:contain }
#app.ptr-animating{ transition:transform .26s cubic-bezier(.22,.61,.36,1) }
.ptr{
  position:absolute; top:-56px; left:0; right:0; height:56px;
  display:flex; align-items:center; justify-content:center;
  pointer-events:none;
}
.ptr-ring{
  width:22px; height:22px; border-radius:50%;
  border:2px solid var(--line); border-top-color:var(--muted);
  opacity:.55; transition:opacity .15s ease, transform .15s ease;
}
/* Pulled far enough to count, before letting go. */
.ptr.ready .ptr-ring{ opacity:1; transform:scale(1.12); border-top-color:var(--ink) }
.ptr.spinning .ptr-ring{ opacity:1; border-top-color:var(--ink); animation:ptrSpin .7s linear infinite }
@keyframes ptrSpin{ to{ transform:rotate(360deg) } }
@media (prefers-reduced-motion:reduce){
  #app.ptr-animating{ transition:none }
  .ptr.spinning .ptr-ring{ animation-duration:2s }
}

/* ==========================================================================
   The chat page, edge to edge
   --------------------------------------------------------------------------
   .content pads every screen by 16px, which is right for cards and wrong for a
   chat: a thread inset from both edges reads as a widget sitting on a page
   rather than the page itself. The variable is declared here and used by the
   padding directly below it, so the breakout below cannot drift out of step
   with the thing it is breaking out of.
   ========================================================================== */
.content{ --content-pad-x:16px; padding-left:var(--content-pad-x); padding-right:var(--content-pad-x) }
.chat-page{
  margin-left:calc(var(--content-pad-x) * -1);
  margin-right:calc(var(--content-pad-x) * -1);
}
/* The hairlines under the header and over the composer now run the full width,
   which is the detail that makes it read as a screen rather than a card. */
.cp-head{ padding-left:var(--s4); padding-right:var(--s4) }
.cp-foot{ padding-left:var(--s4); padding-right:var(--s4) }
.cp-thread{ padding-left:var(--s4); padding-right:var(--s4) }

/* Bubbles, the way a phone draws them: no border, properly round, mine tinted
   and on the right, theirs plain and on the left with the small avatar a group
   thread needs and a 1:1 does not. The old ones were bordered white boxes with
   the sender's name repeated over every single line. */
/* The confirmed-plan chat is a card in a sheet rather than a page, so it can
   never be edge to edge - but the messages inside it are the same messages and
   get the same bubbles. */
.cp-thread .message,
.chat-box .message{ display:flex; gap:6px; align-items:flex-end; margin:0 0 var(--s2) }
.cp-thread .message.mine,
.chat-box .message.mine{ flex-direction:row-reverse }
.cp-thread .message .avatar,
.chat-box .message .avatar{ width:26px; height:26px; font-size:11px; flex:0 0 auto; margin:0 }
.cp-thread .message.mine .avatar,
.chat-box .message.mine .avatar{ display:none }
.cp-thread .bubble,
.chat-box .bubble{
  max-width:78%; padding:8px 13px; border:0; border-radius:19px;
  background:var(--gray-soft); font-size:var(--t-body); line-height:1.35;
  overflow-wrap:anywhere;
}
.cp-thread .message.mine .bubble,
.chat-box .message.mine .bubble{ background:var(--green); color:#fff; border:0 }
.cp-thread .bubble strong,
.chat-box .bubble strong{
  display:block; font-size:var(--t-micro); font-weight:600;
  color:var(--muted); margin-bottom:2px;
}
/* You know who you are. */
.cp-thread .message.mine .bubble strong,
.chat-box .message.mine .bubble strong{ display:none }

/* A pill and a round send button, which is what every thumb on iOS expects to
   find at the bottom of a thread. */
.cp-foot .pp-composer{ gap:var(--s2); align-items:center }
.cp-foot .pp-composer input{
  flex:1; min-height:42px; padding:10px var(--s4);
  border:1px solid var(--line2); border-radius:var(--r-pill);
}
.cp-foot .pp-composer button{
  width:42px; height:42px; min-height:42px; padding:0; flex:0 0 auto;
  border-radius:50%; font-size:var(--t-card);
}

/* ==========================================================================
   Sheet footers stay on the screen
   --------------------------------------------------------------------------
   The action row sat at the end of the content, so on any sheet longer than
   the screen the button you were looking for was somewhere below the fold and
   the only way to find it was to scroll to the bottom and hope. It sticks now,
   the same way the Complete Profile footer does.

   .sheet gives up its bottom padding when it has one, because a sticky
   bottom:0 element is held by the scroll container's CONTENT box - the padding
   would hold the bar up off the edge with a dead strip beneath it.
   ========================================================================== */
.sheet.has-pinned-foot{ padding-bottom:0 }
/* :not(button) — the same mistake as .is-sheet-foot, in the other rule.
   ---------------------------------------------------------------------------
   openSheet pins the last action by adding BOTH .sheet-pinned and
   .is-sheet-foot. The .is-sheet-foot rule below already excludes buttons and
   paints its band with a box-shadow instead, precisely so a .primary keeps its
   dark fill - and this one, which does the same job, did not. So when the
   pinned action IS the button, it was repainted var(--card) with .primary's
   white label still on top: white on white, at the bottom of every sheet whose
   last element is a single primary button. That is the invisible "Plan this" on
   the event detail. */
.sheet .sheet-pinned:not(button){
  position:sticky; bottom:0; z-index:5;
  margin:var(--s3) calc(var(--s3) * -1) 0;
  padding:var(--s2) var(--s3) calc(var(--s3) + env(safe-area-inset-bottom));
  background:var(--card);
  border-top:1px solid var(--line);
}
/* Inside the wrapper it is just a row again. */
.sheet .sheet-pinned .sheet-actions{
  position:static; margin:0; padding:0; background:none; border-top:0;
}
.sheet .sheet-pinned .text-btn.wide{ margin-top:var(--s2) }

/* The What summary, on the Proposal step. One line saying what was already
   chosen upstream, with a way to reopen the picker - instead of a whole step
   that existed to read those choices back. */
.pr-what-line{
  display:flex; align-items:center; gap:var(--s2);
  margin-top:var(--s3); padding:var(--s2) var(--s3);
  background:var(--gray-soft); border:1px solid var(--line);
  border-radius:var(--r-control); font-size:var(--t-meta);
}
.pr-what-line span{ flex:1 1 auto; min-width:0 }
.pr-what-line b{ font-weight:650 }
.pr-what-line .text-btn{ flex:0 0 auto }

/* Your own snooze, on your own calendar. A ring rather than a fill, because it
   is not busy - the day really is free, you are simply not being put forward.
   Nobody else's app draws this; see snoozedOn() in engine.js. */
.month-day.snoozed .day-num{
  position:relative;
}
.month-day.snoozed .day-num:before{
  content:""; position:absolute; inset:-4px -6px;
  border:1.5px dashed var(--muted2); border-radius:var(--r-pill);
  opacity:.85; pointer-events:none;
}
.da-snoozed{
  margin:var(--s2) 0 0; padding:var(--s2) var(--s3);
  background:var(--gray-soft); border:1px dashed var(--line2);
  border-radius:var(--r-control);
  font-size:var(--t-micro); color:var(--muted); line-height:1.45;
}
.da-snoozed .text-btn{ font-size:var(--t-micro) }

/* A bare primary button pinned as the footer needs the bar's padding around it,
   which .sheet-actions gets for free by being a flex row. */
/* A pinned button keeps its own fill and paints its band with a shadow spread,
   the same way .is-sheet-foot does - a box-shadow paints OUTSIDE the border box
   and so cannot swallow the label. */
.sheet button.sheet-pinned{
  display:block; width:100%; border-radius:var(--r-control);
  position:sticky; bottom:calc(var(--s3) + env(safe-area-inset-bottom)); z-index:5;
  margin:var(--s4) 0 0;
  box-shadow:0 0 0 var(--s3) var(--card);
}

/* ==========================================================================
   The profile banner is a grid child on desktop
   --------------------------------------------------------------------------
   .app-shell is a two-column, two-row grid at >=860px: rail | topbar over
   content. The banner was added between the topbar and the content as a plain
   sibling, with no placement - so the grid auto-placed it into column 1, row 3,
   under the sidebar, and pushed .content out of the row it was told to occupy.
   On a phone the shell is display:block and none of this applied, which is why
   it only ever showed up on desktop: a band of empty shell under a .content
   that had stopped filling its row.

   A row of its own, in the content column. When the profile is finished the
   banner is not rendered and the row collapses to nothing.
   ========================================================================== */
@media(min-width:860px){
  .app-shell{ grid-template-rows:auto auto minmax(0,1fr) }
  /* align-self:start, or the grid stretches it: a 43px status line came out
     142px tall on desktop and took a 150px row with it. */
  /* The PAGE copy only. The banner also lives at the foot of the side rail on a
     wide screen, and these were written when the page was its only home - so
     `html.force-desktop .profile-banner` (0,2,1) outranked the rail's own rule
     (0,2,0) and dragged the rail copy 22px in with `margin:0 22px`. That is the
     22px, and no amount of margin-left:0 on a weaker selector could move it. */
  #app > .profile-banner{ grid-column:2; grid-row:2; align-self:start;
    width:auto; margin:0 30px var(--s2) }
  .content{ grid-row:3 }
}
@media(min-width:1080px){
  html.force-desktop .app-shell{ grid-template-rows:auto auto minmax(0,1fr) }
  html.force-desktop #app > .profile-banner{ grid-column:2; grid-row:2; align-self:start; margin:0 22px var(--s2) }
  html.force-desktop .content{ grid-row:3 }
}

/* Are you in? The one question an invited person came to answer. */
.pp-answer{
  display:flex; align-items:center; gap:var(--s3); flex-wrap:wrap;
  margin:0 var(--s4) var(--s2); padding:var(--s3);
  background:var(--green-soft); border:1px solid var(--green-mid);
  border-radius:var(--r-control); font-size:var(--t-meta);
}
.pp-answer span{ flex:1 1 200px; min-width:0 }
.pp-answer-btns{ display:flex; gap:var(--s2); flex:0 0 auto }
.pp-answer-btns button{ min-height:40px; padding:0 var(--s3) }
.pp-answered{
  margin:0 var(--s4) var(--s2); font-size:var(--t-micro); color:var(--muted);
}

/* ==========================================================================
   The wizard's forward button stays on the screen
   --------------------------------------------------------------------------
   pinSheetFooter handles sheets, and the Coordin8 wizard is not one - it is a
   full screen, so its Next sat at the end of the content wherever that landed.
   On the activity step that is below nineteen category chips, and on the who
   step below every friend and group you have: the one control that moves the
   flow forward was the one thing you had to go looking for.

   Sticky rather than fixed, so it rides the bottom of the scroller it is in
   (.content on desktop, the document on a phone) and cannot end up floating
   over a different screen after a render.
   ========================================================================== */
/* Sticky is inert on a phone here.
   -------------------------------------------------------------------------
   .content carries overflow-y:auto, so it owns the sticky scrollport - but on a
   phone .content does not scroll, the DOCUMENT does. A bottom:0 sticky element
   therefore never moves relative to the box it is stuck to, and simply sat at
   its natural position: measured 105px below the fold on the activity step,
   reachable only by scrolling to the very end, which is the thing being fixed.

   Fixed to the viewport instead, clearing the tab bar. Safe because .co-wiz
   only exists while the wizard is the current screen and render() rebuilds the
   page, so it cannot be left floating over anything else. */
/* (a stray `}` sat here, left over from an edit. It closed nothing, and CSS
   recovers from that by dropping the rule it lands in - so it also took the
   .co-next block below it out of the stylesheet.) */
.co-next{
  position:sticky; bottom:0; z-index:4;
  margin:var(--s4) calc(var(--s4) * -1) 0;
  padding:var(--s2) var(--s4) calc(var(--s3) + env(safe-area-inset-bottom));
  width:calc(100% + var(--s4) * 2);
  /* NOT background:var(--bg).
     ------------------------------------------------------------
     This repainted the BUTTON in the page colour to make a bar, and left
     .primary's white text on top of it - white on cream, which is the faint
     ghost of a "Send it without a place" bar stretched across the screen with
     no readable label. The button keeps its own dark fill and white text; the
     band behind it is a box-shadow spread in the page colour, which paints
     outside the border box and so cannot swallow the label. */
  box-shadow:0 0 0 var(--s3) var(--bg);
  min-height:52px;
}
/* The send step's primary is the same control by another name. */
#coSend{
  position:sticky; bottom:0; z-index:4;
  margin:var(--s3) calc(var(--s4) * -1) 0;
  padding:var(--s2) var(--s4) calc(var(--s3) + env(safe-area-inset-bottom));
  width:calc(100% + var(--s4) * 2);
  box-shadow:0 0 0 var(--s3) var(--bg);
  min-height:52px;
}
/* The lines under Send explain it; they belong above the bar, not under a
   pinned button where they would be covered. */
#coSend ~ .est-note{ position:relative; z-index:3 }

/* Placed at the END of the file on purpose.
   -------------------------------------------------------------------------
   This exact block sat directly ABOVE `.co-next{position:sticky}` and lost to
   it - same specificity, later rule wins, media query or not. It measured as
   still-not-fixed and still 105px below the fold, which looked like the fix not
   working rather than the fix being outranked by four lines of its own file. */
@media(max-width:859px){
  .co-next, #coSend{
    position:fixed; left:0; right:0; bottom:70px; width:100%;
    margin:0; border-radius:0; z-index:40;
  }
  /* No tab bar inside the app shell - the real one is native, below the web
     view - so there is nothing to clear. */
  html.native-shell .co-next, html.native-shell #coSend{ bottom:0 }
  /* Room for it, so the last row of content is not sitting underneath. */
  .co-wiz{ padding-bottom:96px }
  /* The landing has no pinned button, so it needs no room for one - but on the
     WEB the bottom tab bar is a fixed element inside the page, and "Plan
     something else" is the last row on the screen. In the native shell the bar
     is below the web view and nothing overlaps. */
  .co-landing{ padding-bottom:var(--s4) }
  html:not(.native-shell) .co-landing{
    padding-bottom:calc(84px + env(safe-area-inset-bottom));
  }
}
/* ^^ THIS BRACE WAS MISSING.
   ------------------------------------------------------------------------
   The @media(max-width:859px) above was never closed, so every rule from here
   to the end of the file - about a thousand lines, and every single thing
   added to this stylesheet recently - was nested inside it. They applied on a
   phone and silently did nothing on a desktop.

   That is why past days stayed green at 1280px, why the scrollbars stayed
   chunky, and why the 96px dead band could not be beaten no matter how much
   specificity was thrown at it: the rule meant to beat it was itself inside a
   media query that does not match a wide window. It also explains why each of
   those fixes verified fine at 375px - which is exactly where I kept testing
   them. A brace, not a cascade. */


/* "Or pick a day anyway", on the step that used to be a dead end. */
.co-any-day{ margin-top:var(--s3) }
.co-any-day span{ font-size:var(--t-micro); letter-spacing:.06em; color:var(--muted) }

/* ==========================================================================
   The wizard screen fades; it does not slide
   --------------------------------------------------------------------------
   .screen animates in from translateY(5px), and a transformed element becomes
   the CONTAINING BLOCK for every position:fixed descendant. The wizard's
   forward button is position:fixed, so while that transform is applied it is
   anchored to the screen box instead of the viewport - and the screen box is
   taller than the viewport on the long steps. Measured: fixed, bottom:70px,
   and sitting at top 835 on an 812pt phone. Off the bottom of the screen.

   styles.css already uses `backwards` rather than `both` for this exact
   reason, and the comment there says so - but `backwards` only helps AFTER the
   animation finishes. For the 180ms it is running, the transform is live and
   the button is mispositioned, on a screen that re-renders on every single tap.
   And if the animation never advances - a backgrounded tab, a page that is not
   compositing - the start frame stays applied and the button never comes back.

   Fading costs nothing here. The slide was the least noticeable part of the
   transition and it is the part that breaks the only control on the screen.
   ========================================================================== */
.screen.co-screen{ animation:coScreenIn .18s ease both }
@keyframes coScreenIn{ from{ opacity:.25 } to{ opacity:1 } }

/* The pinned action row, applied by openSheet to whichever row is last.
   Flush to the bottom of the sheet with nothing showing beneath it. */
/* Flush with the bottom edge, not 7px past it.
   -------------------------------------------------------------------------
   The negative bottom margin cancelled the sheet's own padding so the bar
   could sit flush - and then the bar's padding-bottom stacked on top of that,
   putting the button's bottom at 819 on an 812pt screen. Seven pixels under
   the edge of the display, which does not read as "slightly clipped", it reads
   as the Save button being hidden behind something.

   The sheet drops its bottom padding instead (openSheet adds .has-pinned-foot)
   and the bar owns the spacing, so there is exactly one source of it. */
.sheet.has-pinned-foot{ padding-bottom:0 }
/* A ROW gets the bar treatment... */
.sheet .is-sheet-foot:not(button){
  position:sticky; bottom:0; z-index:5;
  margin:var(--s4) calc(var(--s5) * -1) 0;
  padding:var(--s3) var(--s5) calc(var(--s3) + env(safe-area-inset-bottom));
  background:var(--paper);
  border-top:1px solid var(--line);
}
/* ...but when the footer IS the button, painting it var(--paper) puts the
   button's own white label on a near-white bar and the whole control vanishes.
   That is the blank strip where "Done" should be at the bottom of Fill out your
   week - and it is the second time I have made exactly this mistake, the first
   being .co-next. The button keeps its own fill; the band behind it comes from
   a box-shadow, which paints outside the border box and cannot swallow text. */
.sheet button.is-sheet-foot{
  position:sticky; bottom:calc(var(--s3) + env(safe-area-inset-bottom)); z-index:5;
  margin-top:var(--s4);
  box-shadow:0 0 0 var(--s3) var(--paper);
}
.sheet .is-sheet-foot > .text-btn{ margin-top:var(--s2) }

/* "2 new friend requests" — one row, under the tabs, above the list. */
.request-line{
  display:flex; align-items:center; gap:var(--s2); width:100%;
  margin:var(--s3) 0 0; padding:var(--s2) var(--s3);
  background:var(--green-soft); border:1px solid var(--green-mid);
  border-radius:var(--r-control); font-size:var(--t-meta); text-align:left;
  min-height:48px; cursor:pointer;
}
.request-line > span:nth-child(2){ flex:1 1 auto; min-width:0 }
.rq-dot{ width:8px; height:8px; border-radius:50%; background:var(--green); flex:0 0 auto }
.rq-faces{ display:flex; flex:0 0 auto }
.rq-go{ color:var(--muted); flex:0 0 auto }

/* The update button turns its own icon while it works. */
.build-line #forceRefresh{ display:inline-flex; align-items:center; gap:6px }
.rf-icon{
  width:12px; height:12px; flex:0 0 auto; border-radius:50%;
  border:1.5px solid currentColor; border-top-color:transparent;
}
#forceRefresh.spinning .rf-icon{ animation:ptrSpin .7s linear infinite }
#forceRefresh:disabled{ opacity:.7 }

/* ==========================================================================
   The bottom-nav clearance belongs to phones, not to touchscreens
   --------------------------------------------------------------------------
   .app-shell reserves 96px at the bottom for the floating bottom nav. That
   lived in an `@media (pointer: coarse)` block, which is about the INPUT
   DEVICE and says nothing about the window - and a very large number of
   Windows laptops have a touchscreen and report a coarse pointer. So on those
   machines, at any width, every page ended in 96px of empty shell in the page
   colour: the "big grey box covering half the screen, on every page".

   The nav it is clearing is `display:none` above 860px, where the side rail
   replaces it, so there was nothing to clear. Both conditions now, because it
   takes both to be a phone: a coarse pointer AND a narrow window.

   Worth knowing for the next one of these: `pointer: coarse` on its own is
   never a proxy for "phone". It was also why this could not be reproduced by
   resizing a desktop browser - the media query does not depend on the size.
   ========================================================================== */
@media (pointer: coarse) and (max-width:859px){
  .app-shell{ padding-bottom:calc(96px + env(safe-area-inset-bottom)) }
}

/* ==========================================================================
   A sheet that never animates must still be in the right place
   --------------------------------------------------------------------------
   .sheet is centred by a BASE transform, translateX(-50%), and sheetIn
   overrides it with translate(-50%, 20px) -> translate(-50%, 0). It carried
   `fill: both`, so the start frame is held whenever the animation has not
   advanced — and then the sheet sits 20px lower than fixed;bottom:0 says,
   which pushes the pinned Save row 20px below the bottom of the screen. It
   does not look clipped; it looks like the button is behind something.

   With no fill the sheet falls back to its base transform whenever the
   animation is not actively running, so the failure mode is "no animation"
   rather than "permanently 20px out of place". Same lesson as .screen and the
   wizard button: an entry animation that moves an element must never be the
   thing deciding where that element finally rests.
   ========================================================================== */
.sheet{ animation-fill-mode:none }

/* ==========================================================================
   A day that has gone has no availability worth colouring
   --------------------------------------------------------------------------
   Past days kept their state colour and were only dimmed, so on the 21st the
   first three weeks of the month were a solid block of green announcing that
   you were free on days that have already happened. It is louder than the days
   you can actually act on, and it is telling you something that cannot matter.

   They keep the strike-through and the dimming - you can still tap one and see
   what was on - but they stop competing for attention with the days ahead.
   ========================================================================== */
.month-day.past.free, .month-day.past.available, .month-day.past.most,
.month-day.past.some, .month-day.past.busy, .month-day.past.unknown,
.month-day.past.notset{
  background:var(--card);
  border-color:var(--line);
}
.month-day.past .day-dots i{ background:var(--muted2) }

/* Thin, quiet scrollbars.
   -------------------------------------------------------------------------
   Windows paints a 17px track with arrow buttons at each end, and the app has
   a scrolling content column on every page - so the chrome of the scrollbar was
   a permanent grey stripe down the side of a otherwise soft-edged design. */
*{ scrollbar-width:thin; scrollbar-color:#c9c3b8 transparent }
::-webkit-scrollbar{ width:8px; height:8px }
::-webkit-scrollbar-track{ background:transparent }
::-webkit-scrollbar-thumb{ background:#c9c3b8; border-radius:999px }
::-webkit-scrollbar-thumb:hover{ background:#b3aca0 }
::-webkit-scrollbar-button{ display:none }
::-webkit-scrollbar-corner{ background:transparent }

/* 40pt minimum, like everything else that gets tapped. */
.filter-btn{ min-height:40px }

/* The profile banner, at the foot of the rail.
   .side-items has to grow for "auto" on the top margin to have anything to push
   against - without it the banner just sits under Settings, which is where it
   landed first. */
.side-nav{ display:flex; flex-direction:column }
.side-nav .side-items{ flex:1 1 auto; min-height:0 }
.side-nav .profile-banner{
  margin:0 var(--s3) var(--s3); width:calc(100% - var(--s3) * 2);
  min-height:44px; flex:0 0 auto;
}
.side-nav .profile-banner .pb-text b{ font-size:var(--t-micro) }

/* Plan header: back arrow, title, menu - one row.
   The title takes the space between the two buttons and truncates rather than
   wrapping, because a wrapped title puts the row back to two lines and that is
   what this change exists to remove. */
.cp-head-row .cp-title{
  flex:1 1 auto; min-width:0; margin:0;
  font-size:var(--t-card); line-height:1.15; letter-spacing:-.02em;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.cp-sub .pill.status{ margin-right:var(--s2); vertical-align:middle }

/* ==========================================================================
   A pinned action bar sits on the bottom edge, not 44px above it
   --------------------------------------------------------------------------
   .content carries padding-bottom:34px, and a position:sticky element pins to
   the scrollport's CONTENT box - inside that padding. So the wizard's Next and
   the composer's action row both floated 44px up, with the page still visible
   underneath them, which reads as a button that failed to stick rather than one
   deliberately placed.

   The padding moves onto .screen instead. Sticky resolves against the
   scrollport, not against the parent's padding, so the bar now reaches the real
   bottom while ordinary pages keep the same breathing room they had.
   ========================================================================== */
@media(min-width:860px){
  .content{ padding-bottom:0 }
  .content > .screen{ padding-bottom:34px }
}

/* The banner lines up with the nav items above it - they start at 16px and it
   was starting at 38, which is the sort of 22px that looks like a mistake
   rather than a margin. */
/* The rail is a flex column, and its align-items centres a child that does not
   fill the width - the banner is 165px in a 210px column, so it was being
   pushed (210-165)/2 = 22px to the right. That is the 22px, and no amount of
   margin-left:0 was ever going to move it: the margin was already zero and the
   alignment was doing it. */
.side-nav .profile-banner{
  align-self:stretch; margin-left:0; margin-right:0; width:auto;
}

/* The chosen option's own details, on the plan. */
.pp-sum-row{ display:block; margin-bottom:var(--s2) }
.pp-sum-row:last-child{ margin-bottom:0 }
.pp-sum-detail{ display:block; color:var(--muted); font-size:var(--t-micro); margin-top:2px }
.pp-sum-link{
  display:inline-block; margin-top:4px; font-size:var(--t-micro);
  font-weight:600; color:var(--green); text-decoration:none;
}
.pp-sum-link:hover{ text-decoration:underline }

/* Where to, on a day trip or a few days away. */
.co-dests{ display:grid; grid-template-columns:repeat(auto-fill,minmax(150px,1fr)); gap:var(--s2) }
.co-dest{
  display:flex; flex-direction:column; gap:2px; text-align:left;
  padding:var(--s3); min-height:64px;
  background:var(--card); border:1px solid var(--line); border-radius:var(--r-control);
  cursor:pointer;
}
.co-dest:hover{ border-color:var(--line2) }
.co-dest-city{ font-weight:650; font-size:var(--t-meta) }
.co-dest-meta{ color:var(--muted); font-size:var(--t-micro) }

/* Where & tickets */
.pd-block{ margin-bottom:var(--s3) }
.pd-block:last-of-type{ margin-bottom:0 }
.pd-head{ display:flex; flex-direction:column; gap:2px }
.pd-cat{ font-size:var(--t-micro); letter-spacing:.08em; text-transform:uppercase; color:var(--muted) }
.pd-facts{ margin:var(--s2) 0 0; color:var(--muted); font-size:var(--t-micro); line-height:1.45 }
.pd-links{ display:flex; flex-wrap:wrap; gap:var(--s2); margin-top:var(--s3) }
.pd-links a{
  display:inline-flex; align-items:center; gap:5px; min-height:40px; padding:0 var(--s3);
  background:var(--gray-soft); border:1px solid var(--line); border-radius:var(--r-pill);
  font-size:var(--t-micro); font-weight:600; color:var(--ink); text-decoration:none;
}
.pd-links a:hover{ border-color:var(--line2) }
.pd-extra{ margin-top:var(--s4) }

/* A short sheet is raised so its content sits where you are looking, rather
   than as a strip along the bottom edge. Phones only - on a wide screen the
   sheet is already centred. */
@media(max-width:859px){
  .sheet.is-short{ min-height:46vh }
}

/* Two per row, and the label on its own line.
   --------------------------------------------------------------------------
   .fr-times was a flex row with the "Both available:" label as its first item,
   so the label ate the width of the first line and exactly ONE chip fitted
   beside it - then two on every line after. The first row looked like a mistake
   because it was one: a label and a slot sharing a line that was meant for two
   slots. A grid puts the label across the top and the days in even pairs. */
/* (moved into the touch-only block at the end of this file) */

/* A set amount, that scrolls — guaranteed without JavaScript.
   --------------------------------------------------------------------------
   fitChatPage() measures the page and gives it an exact height, and the thread
   is flex:1 inside that, so it is already fixed and scrolls. But every bit of
   that depends on the measurement having run: if the page is drawn before
   layout settles, or an old build is loaded that has no fitChatPage at all, the
   thread has nothing bounding it and grows with the conversation until the
   composer is pushed off the bottom of the screen.

   This is the floor under that. A cap in the stylesheet needs no JS, cannot be
   raced by a render, and does nothing at all when the measured height is
   smaller — which it normally is. */
.cp-thread{ max-height:62vh }
@media(min-width:860px){ .cp-thread{ max-height:70vh } }

/* ==========================================================================
   A touch device never gets the desktop rail. Full stop.
   --------------------------------------------------------------------------
   The rail is turned on by `@media(min-width:860px)` and nothing else, so it
   appears whenever the LAYOUT VIEWPORT is 860 or wider - which on a phone is
   supposed to be impossible, and is not: a page loaded in a context where the
   viewport meta has not applied lays out at ~980px, the query matches, and an
   iPhone renders the sidebar across the top above the real native tab bar.
   That is the top nav in the screenshots, and no amount of class-juggling in
   app.js could fix it because the class was never what turned it on.

   This is the device asking, not the width. `pointer:coarse and hover:none` is
   a finger with no mouse behind it - an iPhone, an iPad. A desktop browser
   driven by a mouse or trackpad reports `pointer:fine`, so it CANNOT match this
   block, whatever the window is doing. The desktop rail is untouched.

   The friend row goes back to one line for the same reason: its three-column
   grid is a wide-screen rule that a mis-measured phone was picking up.
   ========================================================================== */
@media (pointer:coarse) and (hover:none){
  .side-nav,
  html.force-desktop .side-nav{ display:none !important }
  .app-shell{
    display:block !important;
    grid-template-columns:none !important;
    grid-template-rows:none !important;
    max-width:none !important; height:auto !important; min-height:100vh !important;
    border:0 !important; border-radius:0 !important; box-shadow:none !important;
  }
  .app-wrap{ padding:0 !important; align-items:stretch !important }
  /* The web tab bar comes back for a phone browser or a PWA - and stays hidden
     in the native shell, where the real one is SwiftUI below the web view. */
  .bottom-nav{ display:flex !important }
  html.native-shell .bottom-nav{ display:none !important }
}

/* The friend row: select, who, star. In that order, on one line.
   --------------------------------------------------------------------------
   The grid was `minmax(140px,1fr) auto auto` for four children, so the times
   block had no column of its own and got wedged wherever it fitted. Three
   columns for the three controls, and the times take a row underneath. */
/* (moved into the touch-only block at the end of this file) */

/* ==========================================================================
   PHONE ONLY. Everything below is inside (pointer:coarse) and (hover:none) -
   a finger with no mouse behind it. A desktop browser reports pointer:fine and
   cannot match this block, so none of it can reach the desktop layout.

   These were asked for as phone fixes and I first shipped them as shared
   markup and shared CSS, which changed the desktop too. Redone here, in CSS,
   using `order` rather than rewriting the DOM - so the desktop keeps the exact
   markup and the exact rules it had.
   ========================================================================== */
@media (pointer:coarse) and (hover:none){

  /* --- the friend row: select, name, star on the right ------------------ */
  .friend-row{
    display:grid;
    grid-template-columns:auto minmax(0,1fr) auto;
    align-items:center;
  }
  /* fr-picks holds the tick and the star together on the left. Unwrap it so
     the two can sit at opposite ends without touching the markup. */
  .friend-row .fr-picks{ display:contents }
  .friend-row .check{ order:1 }
  .friend-row .fr-id{ order:2; min-width:0 }
  .friend-row .star{ order:3; justify-self:end }
  .friend-row .fr-times{ order:4; grid-column:1 / -1 }
  /* The @handle repeats what the name already says. */
  .friend-row .fr-id .friend-info > span{ display:none }

  /* --- two per row, label on its own line ------------------------------- */
  .fr-times{
    display:grid; grid-template-columns:1fr 1fr; gap:var(--s2);
    justify-content:stretch;
  }
  .fr-times .fr-label,
  .fr-times .fr-none{ grid-column:1 / -1 }
  .fr-times .time-slot{ width:100% }

  /* --- Create group / Propose, above the list --------------------------- */
  .content > .screen{ display:flex; flex-direction:column }
  .content > .screen > .list-head{ order:1 }
  .content > .screen > .floating-actions{
    order:2;
    position:static; display:flex; gap:var(--s2);
    margin:0 0 var(--s3); padding:0;
    background:transparent; box-shadow:none; border:0;
  }
  .content > .screen > .floating-actions button{ flex:1; min-height:44px }
  .content > .screen > .section,
  .content > .screen > .friend-list,
  .content > .screen > .groups-grid,
  .content > .screen > .empty-inline{ order:3 }
}

/* The line that says which hours are actually free. */
.da-free{
  margin:var(--s2) 0 var(--s3); font-size:var(--t-meta); color:var(--ink);
  font-weight:600;
}
.day-answer.past .da-free{ color:var(--muted); font-weight:400 }

/* A back control that looks like one. The composer's was a full-size secondary
   button competing with the primary beside it; going back is a correction and
   should not have equal weight with going on. */
.back-chip{
  display:inline-flex; align-items:center; gap:4px;
  min-height:48px; padding:0 var(--s3); margin-right:auto;
  background:transparent; border:0; cursor:pointer;
  color:var(--muted); font-size:var(--t-meta); font-weight:600;
}
.back-chip span{ font-size:var(--t-meta) }
.back-chip:hover{ color:var(--ink) }

/* On a phone the composer's action row carries the only way back to Review, so
   it has to survive a long picker rather than scrolling off the end of it. */
@media (pointer:coarse) and (hover:none){
  .sticky-actions{
    padding-bottom:calc(var(--s3) + env(safe-area-inset-bottom));
  }
}

/* ==========================================================================
   The planning conversation
   ========================================================================== */
.as-slots{ display:grid; grid-template-columns:repeat(4,1fr); gap:var(--s2); margin-bottom:var(--s3) }
.as-slot{
  display:flex; flex-direction:column; gap:2px; align-items:flex-start;
  padding:var(--s2); min-height:52px; text-align:left; cursor:pointer;
  background:var(--gray-soft); border:1px solid var(--line);
  border-radius:var(--r-control);
}
.as-slot:disabled{ cursor:default; opacity:.65 }
.as-slot.on{ background:var(--green-soft); border-color:var(--green-mid) }
.as-slot-label{ font-size:var(--t-micro); color:var(--muted); text-transform:uppercase; letter-spacing:.06em }
.as-slot-val{
  font-size:var(--t-micro); font-weight:650; color:var(--ink);
  max-width:100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.as-thread{
  display:flex; flex-direction:column; gap:var(--s2);
  max-height:38vh; overflow-y:auto; padding:var(--s2) 0; margin-bottom:var(--s3);
  overscroll-behavior:contain;
}
.as-msg{ display:flex }
.as-msg.you{ justify-content:flex-end }
.as-msg span{
  max-width:82%; padding:8px 13px; border-radius:18px;
  font-size:var(--t-body); line-height:1.35; overflow-wrap:anywhere;
}
.as-msg.assistant span{ background:var(--gray-soft) }
.as-msg.you span{ background:var(--green); color:#fff }
.as-msg.thinking span{ opacity:.6; letter-spacing:2px }
.as-compose{ display:flex; gap:var(--s2); align-items:center }
.as-compose input{ flex:1; min-height:44px; border-radius:var(--r-pill); padding:0 var(--s4) }
.as-compose button{ width:44px; height:44px; min-height:44px; padding:0; border-radius:50% }
@media(max-width:420px){ .as-slots{ grid-template-columns:repeat(2,1fr) } }

/* ==========================================================================
   The wizard's progress: named steps, your answer under each, tappable back
   --------------------------------------------------------------------------
   Five dots said there were five of something. They did not say what, did not
   say what you had chosen, and gave no way back to one without pressing Back
   through every step between. Scrolls sideways rather than shrinking five
   labels until none of them read.
   ========================================================================== */
.co-prog{
  display:flex; gap:var(--s2); align-items:stretch;
  overflow-x:auto; scrollbar-width:none;
  margin:0 calc(var(--s4) * -1) var(--s4); padding:0 var(--s4) var(--s2);
  scroll-snap-type:x proximity;
}
.co-prog::-webkit-scrollbar{ display:none }
.co-prog-step{
  display:flex; flex-direction:column; gap:2px; align-items:flex-start;
  flex:0 0 auto; min-width:86px; max-width:132px; scroll-snap-align:start;
  padding:var(--s2) var(--s3); min-height:48px; text-align:left;
  background:transparent; border:0; border-top:2px solid var(--line);
  cursor:default; color:var(--muted2);
}
.co-prog-step.done{ cursor:pointer; color:var(--muted); border-top-color:var(--green) }
.co-prog-step.on{ color:var(--ink); border-top-color:var(--ink) }
.co-prog-label{
  font-size:var(--t-micro); text-transform:uppercase; letter-spacing:.07em;
  font-weight:700;
}
.co-prog-answer{
  font-size:var(--t-micro); font-weight:600;
  max-width:100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.co-prog-step.on .co-prog-answer{ color:var(--muted) }
.co-prog-step.done:hover{ color:var(--ink) }

/* ==========================================================================
   Near you this week
   ========================================================================== */
.cu{ margin-bottom:var(--s5) }
.cu-head{ display:flex; align-items:baseline; justify-content:space-between; gap:var(--s3); margin-bottom:var(--s2) }
.cu-head h2{ margin:0; font-size:var(--t-card); letter-spacing:-.02em }
.cu-head span{ font-size:var(--t-micro); color:var(--muted) }
.cu-row{
  display:flex; align-items:stretch; gap:0;
  background:var(--card); border:1px solid var(--line);
  border-radius:var(--r-control); margin-bottom:var(--s2); overflow:hidden;
}
.cu-main{
  display:flex; align-items:center; gap:var(--s3); flex:1; min-width:0;
  padding:var(--s3); background:transparent; border:0; text-align:left; cursor:pointer;
}
.cu-when{
  flex:0 0 auto; width:42px; text-align:center;
  font-size:var(--t-micro); font-weight:700; text-transform:uppercase;
  letter-spacing:.06em; color:var(--green);
}
.cu-text{ display:flex; flex-direction:column; gap:2px; min-width:0; flex:1 }
.cu-text strong{ font-size:var(--t-meta); overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
.cu-meta{ font-size:var(--t-micro); color:var(--muted); overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
.cu-who{ font-size:var(--t-micro); color:var(--green); font-weight:650 }
.cu-go{ flex:0 0 auto; color:var(--muted2); font-size:var(--t-card) }
.cu-x{
  flex:0 0 auto; width:44px; background:transparent; border:0;
  border-left:1px solid var(--line); color:var(--muted2);
  font-size:var(--t-card); cursor:pointer;
}
.cu-x:hover{ color:var(--ink) }

/* ==========================================================================
   Design pass: contrast, targets, and one size for one rank
   --------------------------------------------------------------------------
   Findings from a review against Apple's HIG and conventional mobile practice.
   Everything here is a correction toward the ordinary pattern, not a redesign.
   ========================================================================== */

/* 44pt minimum, on the controls that missed the earlier sweep. The switch also
   goes to iOS's own 51x31 - it was 38x22, which is both under target and
   visibly smaller than every other switch on the phone. */
.switch{ width:51px; height:31px; min-height:31px }
.switch::after,.switch .knob{ width:27px; height:27px }
.pp-answer-btns button{ min-height:44px }
.df-group .filter-chip{ min-height:44px }
.text-btn.small{ min-height:44px }
.fw-btns button,.nl-actions button,.build-line button{ min-height:44px }
.co-place-links a{ min-height:44px; display:inline-flex; align-items:center }
.pf-back,.cp-back,.back-btn{
  width:44px; height:44px; min-height:44px;
  display:inline-flex; align-items:center; justify-content:center;
  font-size:26px; background:transparent; border:0; color:var(--ink);
}
.cp-menu-btn{ width:44px; height:44px; min-height:44px }

/* Nothing meant to be read below 12px. The file already says so at :976 and
   these escaped it - including the wizard's own step names and answers, on the
   tab the app is named after, and the whole Coming Up card. */
.co-prog-label,.co-prog-answer,
.cp-thread .bubble strong,
.cu-when,.cu-meta,.cu-who,
.pp-answered,.pp-sum-detail,.pp-sum-link,
.settings-group,.pd-facts,.pd-links a,.da-block-when,
.as-slot-label,.as-slot-val{ font-size:var(--t-label) }
.cp-thread .message .avatar{ font-size:var(--t-label) }

/* Uppercase, letterspaced, 12px at 3.39:1 was the least legible text here.
   .sheet h3 was already corrected to this value; the rest were not. */
.checklist-group-title,.idea-group h4,.notif-head.quiet,
.handle-hero span,.my-handle span,.allday-area h4,.scope-note b{ color:#6d6b64 }

/* One rank, one size. Step 1's options are 15px and step 2's were 12px - the
   same choice, the same gesture, two screens apart. */
.category{ font-size:var(--t-body); padding:var(--s4) }

/* The segmented control was inset ~10px further than the cards below it and
   had no gap before the first one. */
@media(max-width:719px){
  .segmented.centred{ max-width:none }
}
.segmented{ margin-bottom:var(--s4) }

/* One gap down the Plans screen instead of 16 / 16 / 24 / 0. */
.cu{ margin-bottom:var(--s4) }

/* A label belongs on the same edge as the box it labels. */
.field span{ margin:0 0 var(--s1) 0 }

/* The two cards a user flips between most had different padding. */
.friend-row{ padding:var(--s4) }
.reco-card{ padding:var(--s4) }
.location-card{ padding:var(--s3) }

/* 650 is not a weight this file uses - 400/600/700, per its own rule at :909 -
   and it is indistinguishable from 600 in San Francisco. */
.cu-who,.co-prog-answer,.as-slot-val,.pr-what-line b{ font-weight:600 }

/* Dead: coProgress() renders .co-prog-step, and nothing renders .co-dot. */
.co-dots,.co-dot{ display:none }

/* ==========================================================================
   Days that already work, above the wizard
   --------------------------------------------------------------------------
   Small on purpose. The wizard underneath is still the whole screen - this is
   a shortcut for the case where the app already knows the answer, not a
   replacement for the case where it does not.
   ========================================================================== */
.co-best{ display:flex; flex-direction:column; gap:6px; margin-bottom:var(--s4) }
.co-best-row{
  display:flex; align-items:center; gap:var(--s3);
  padding:var(--s2) var(--s3); min-height:44px;
  background:var(--card); border:1px solid var(--line);
  border-radius:var(--r-control); cursor:pointer; text-align:left;
}
.co-best-row:hover{ border-color:var(--green-mid) }
.co-best-day{
  flex:0 0 auto; min-width:52px;
  font-size:var(--t-label); font-weight:700; letter-spacing:.04em;
  color:var(--green);
}
.co-best-text{ display:flex; flex-direction:column; gap:1px; min-width:0; flex:1 }
.co-best-text b{ font-size:var(--t-label); font-weight:600 }
.co-best-text small{
  font-size:var(--t-label); color:var(--muted);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.co-best-go{ flex:0 0 auto; color:var(--muted2); font-size:var(--t-body) }

/* ==========================================================================
   Second design pass — the new surfaces, against what was already here
   ========================================================================== */

/* An inert chip should be readable, not faded. opacity composites the whole
   button, and every slot is disabled the moment the assistant opens - so
   WHERE / WHAT / WHO / WHEN, the four labels naming the panel, resolved to
   2.67:1. A lighter background is the conventional way to say "not yet". */
.as-slot:disabled{ opacity:1; background:var(--paper); border-style:dashed }
.as-slot:disabled .as-slot-val{ color:var(--muted2) }

/* .cu-row and .co-best-row are .card and .co-row now, so they stop declaring
   their own surface and inherit the radius the cards beneath them use - the
   Coming Up rows had 12px corners directly above 20px plan cards. */
.cu-row{ background:none; border:0; border-radius:inherit; padding:0 }
.co-best-row{ background:none; border:0; padding:var(--s2) var(--s3); min-height:44px }
.co-best-row:hover{ border-color:var(--green-mid) }

/* The last string the 12px floor missed. */
.cu-head span{ font-size:var(--t-label) }

/* One letter-spacing for one role. The app's established value is .06em -
   .weekdays span, .ah-when, .pill.status - and these three invented .04, .06
   and .07 between them, two of which are visible together at step 0. */
.co-best-day,.cu-when,.co-prog-label{ letter-spacing:.06em }
/* And one chevron size, matching .co-row-go which sits inches away. */
.co-best-go,.cu-go{ font-size:17px }

/* Tokens, not one-offs. */
.co-best{ gap:var(--s1) }
.co-best-text,.cu-text,.co-prog-step,.as-slot{ gap:2px }
.co-best-head{
  font-size:var(--t-label); font-weight:700; letter-spacing:.06em;
  text-transform:uppercase; color:var(--muted); margin:0 0 var(--s2) 2px;
}

/* The assistant's bubbles are the app's bubbles. */
.as-msg span{ padding:8px 13px; border-radius:19px }

/* Space is reserved while Coming Up is loading, so the segmented control and
   the first plan card do not jump out from under a thumb a second after the
   tab opens. Seven sequential fetches is a long time to leave a page moving. */
.cu-skeleton{ display:flex; flex-direction:column; gap:var(--s2); margin-bottom:var(--s4) }
.cu-skeleton .sk-row{
  height:68px; border-radius:var(--r-card);
  background:linear-gradient(90deg,var(--gray-soft),var(--paper),var(--gray-soft));
  background-size:200% 100%; animation:skShimmer 1.4s ease-in-out infinite;
}
@keyframes skShimmer{ from{ background-position:200% 0 } to{ background-position:-200% 0 } }
@media(prefers-reduced-motion:reduce){ .cu-skeleton .sk-row{ animation:none } }

/* ==========================================================================
   The Coordin8 landing — days first, questions on request

   The tab opened on the wizard: a five-label progress bar with nothing under
   any of the labels, then "Where do you want to go?", with the days that
   already work as a small strip crammed above it. Sixty-three pixels of
   contents page for a form nobody had started, on the screen the app is named
   after — and the thing people came for pushed under it.

   Inverted. The days are the page. The wizard is one tap away, and its
   progress bar appears from step 1, when it has answers to carry.
   ========================================================================== */
.co-landing{ display:flex; flex-direction:column }
.co-lead{ margin:var(--s2) 0 var(--s4) }
.co-lead h1{
  margin:0; font-size:var(--t-title); line-height:1.02; letter-spacing:-.045em;
}
.co-lead p{
  margin:8px 0 0; font-size:var(--t-meta); line-height:1.45;
  color:var(--muted); max-width:420px;
}

/* Bigger than the strip it replaces: this is the primary content now, not a
   shortcut tucked above something else. */
.co-best{ gap:var(--s2) }
.co-best-row{ padding:var(--s3); min-height:60px; gap:var(--s3) }
.co-best-day{ min-width:58px; font-size:var(--t-body); letter-spacing:.03em }
.co-best-text b{ font-size:var(--t-body) }
.co-best-text small{ font-size:var(--t-meta) }

/* Set apart from the days above it: a rule and real space, so the eye does not
   read it as a fourth day. */
.co-start{
  margin-top:var(--s4); padding-top:var(--s4);
  border-top:1px solid var(--line); border-radius:0;
  background:transparent;
}
.co-start:hover{ border-color:var(--line) }
.co-start-icon{
  flex:0 0 auto; display:grid; place-items:center;
  width:34px; height:34px; border-radius:50%;
  background:var(--paper); border:1px solid var(--line);
  font-size:var(--t-body); color:var(--muted); line-height:1;
}
.co-start .co-best-text b{ color:var(--ink) }

/* ==========================================================================
   The four scopes, as a 2x2

   They were full-width rows: 322px of screen for a four-way choice whose
   longest answer is three words, with the fourth needing a scroll on a small
   phone. A four-way pick should be one glance.
   ========================================================================== */
.co-grid{
  display:grid; grid-template-columns:1fr 1fr; gap:var(--s2);
}
.co-tile{
  display:flex; flex-direction:column; align-items:flex-start; gap:2px;
  padding:var(--s3); min-height:104px;
  border:1px solid var(--line2); border-radius:var(--r-card);
  background:var(--paper); text-align:left; cursor:pointer;
}
.co-tile:hover{ border-color:#b9b2a6 }
.co-tile:active{ background:var(--warm) }
.co-tile.on{ border-color:var(--green); background:var(--green-soft) }
.co-tile-emoji{ font-size:var(--t-title); line-height:1.1; margin-bottom:2px }
.co-tile b{
  font-size:var(--t-body); font-weight:var(--w-med); color:var(--ink);
  line-height:1.2;
}
.co-tile small{
  font-size:var(--t-label); color:var(--muted); line-height:1.35;
}
.co-tile.on small{ color:var(--green) }

/* One column where two would make each tile narrower than its own label —
   a 320pt phone with Larger Text set. */
@media (max-width:340px){
  .co-grid{ grid-template-columns:1fr }
  .co-tile{ min-height:0 }
}

/* ==========================================================================
   Assistant — answers you can tap

   Every question in the chat was a text box with a cursor in it, and the app
   already knew most of the answers: which groups exist, which days those
   groups are free, what this person has said they like. Asking somebody to
   type that is asking them to guess at information the app is sitting on, and
   then hoping a model reads the guess correctly.

   A row of real options costs nothing to read and cannot be misparsed.
   ========================================================================== */
.as-picks{
  display:flex; flex-wrap:wrap; gap:var(--s2);
  margin:var(--s3) 0 var(--s2);
}
.as-pick{
  display:flex; flex-direction:column; align-items:flex-start; gap:1px;
  padding:8px var(--s3); min-height:44px;
  border:1px solid var(--line2); border-radius:var(--r-pill);
  background:var(--paper); cursor:pointer; text-align:left;
}

/* An option that fetches you something to answer WITH, rather than answering.
   Dashed, because the difference matters before it is pressed: everything else
   in this row commits an answer and these two do not. */
.as-pick.as-act{ border-style:dashed; border-color:var(--line2); background:transparent }
.as-pick.as-act b{ color:var(--green) }
.as-pick:hover{ border-color:#b9b2a6 }
.as-pick:active{ background:var(--warm) }
.as-pick.on{ border-color:var(--green); background:var(--green-soft) }
.as-pick b{
  font-size:var(--t-label); font-weight:var(--w-med); color:var(--ink);
  line-height:1.25; white-space:nowrap;
}
/* The count under a day — "3 of 4 free" — is the reason to pick that day, so
   it reads as information rather than as a caption. */
.as-pick small{
  font-size:var(--t-micro); color:var(--green); line-height:1.2; white-space:nowrap;
}
.as-pick.on small{ color:var(--green) }

/* ==========================================================================
   Discover — the Coordin8 tab

   Three modes, a date you can walk, and a list. The tab used to open on a
   four-button question asked before it had shown a single thing you could do.
   ========================================================================== */
.disc-modes{
  display:flex; gap:4px; padding:4px; margin-bottom:var(--s3);
  background:var(--warm); border-radius:var(--r-pill);
}
.disc-mode{
  /* 44, not 38. The pill around it was 46 tall, which reads as big enough and
     is not the number that matters - the BUTTON is the target, and a 38pt one
     is under the 44pt minimum on the control people hit most on this screen. */
  flex:1; min-height:44px; padding:8px var(--s2);
  border:0; border-radius:var(--r-pill); background:transparent;
  font-size:var(--t-label); font-weight:var(--w-med); color:var(--muted);
  cursor:pointer; white-space:nowrap;
}
.disc-mode.on{ background:var(--paper); color:var(--ink); box-shadow:0 1px 3px rgba(38,33,25,.10) }

/* Filter, and what it is currently looking for. The second half matters as
   much as the button: this tab searched your saved interests and nothing else,
   forever, with nothing on screen saying so - which is how a list of the three
   things you asked for reads as random. */
.disc-filter-row{
  display:flex; align-items:center; gap:var(--s2);
  margin:0 0 var(--s3); min-width:0;
}
.disc-filter{
  flex:0 0 auto; min-height:44px; padding:0 var(--s3);
  display:inline-flex; align-items:center; gap:6px;
  border:1px solid var(--line); border-radius:var(--r-pill); background:var(--paper);
  font-size:var(--t-label); font-weight:var(--w-med); color:var(--ink); cursor:pointer;
}
.disc-filter.on{ border-color:var(--green); color:var(--green); background:var(--green-soft) }
.disc-looking{
  flex:1 1 auto; min-width:0; font-size:var(--t-label); color:var(--muted);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}

.df-head{
  margin:var(--s4) 0 var(--s2); font-size:var(--t-label); font-weight:var(--w-bold);
  letter-spacing:.09em; color:var(--muted);
}
.df-head small{ font-weight:var(--w-reg); letter-spacing:0; text-transform:none }
.df-cats{ display:grid; grid-template-columns:repeat(2,1fr); gap:var(--s2) }
.df-cat{
  position:relative; min-height:48px; padding:var(--s2) var(--s3);
  display:flex; align-items:center; gap:var(--s2); text-align:left;
  border:1px solid var(--line); border-radius:var(--r-control);
  background:var(--paper); cursor:pointer;
}
.df-cat b{ font-size:var(--t-label); font-weight:var(--w-med); color:var(--ink); min-width:0 }
.df-cat.on{ border-color:var(--green); background:var(--green-soft) }
.df-cat.on b{ color:var(--green); font-weight:var(--w-bold) }
/* A dot, next to the thing it is about. Same mark the assistant uses for the
   same fact, so it does not have to be learned twice. */
.df-mine{
  position:absolute; top:6px; right:6px; width:6px; height:6px;
  border-radius:50%; background:var(--green);
}
.df-row{ display:flex; gap:var(--s2); flex-wrap:wrap }
.df-pill{
  flex:1 1 auto; min-height:44px; padding:0 var(--s3);
  border:1px solid var(--line); border-radius:var(--r-pill);
  background:var(--paper); font-size:var(--t-label); font-weight:var(--w-med);
  color:var(--ink); cursor:pointer;
}
.df-pill.on{ border-color:var(--green); background:var(--green-soft); color:var(--green) }

/* --- the plan the app made -----------------------------------------------
   Above the mode tabs and outside the browsing UI on purpose. It is not one of
   the things on the list, it is a finished answer, and anything dropped into
   the flow of a list gets read as another row of the list and scrolled past.

   Tinted rather than boxed in an accent colour: this is the app being helpful,
   not the app selling something, and it sits on the first screen of every
   launch. A card that shouts on Monday is a card that is ignored by Friday. */
.sp-card{
  display:flex; flex-direction:column; gap:2px;
  padding:var(--s4); margin-bottom:var(--s4);
  border:1px solid var(--green-mid); border-radius:var(--r-card);
  background:linear-gradient(180deg, var(--green-soft), var(--paper) 78%);
}
.sp-top{ display:flex; align-items:baseline; gap:var(--s2); margin-bottom:var(--s1) }
.sp-tag{
  /* 12, not 10. components.css has a rule called "the floor" saying nothing
     meant to be read goes below 12px, and a label is read. */
  font-size:var(--t-label); font-weight:var(--w-bold);
  letter-spacing:.08em; text-transform:uppercase; color:var(--green);
}
.sp-free{ margin-left:auto; font-size:var(--t-label); color:var(--muted) }
.sp-what{
  font-size:var(--t-card); font-weight:var(--w-bold);
  color:var(--ink); letter-spacing:-.01em;
}
.sp-when{ font-size:var(--t-meta); color:var(--ink) }
.sp-why{ font-size:var(--t-label); color:var(--muted); line-height:1.45; margin-top:var(--s1) }
.sp-acts{ display:flex; align-items:center; gap:var(--s3); margin-top:var(--s3) }
/* The primary takes the room and "Not this one" does not pretend to be one of
   two equal choices - because it is not. Both stay 44 tall; declining is the
   tap somebody makes while walking, and a small target is how you decline by
   accident and then wonder where the plan went. */
.sp-acts .primary{ flex:1 1 auto; min-height:44px }
.sp-acts .text-btn{ flex:0 0 auto; min-height:44px; padding:0 var(--s2); color:var(--muted) }

/* The date. The two arrows are the same size whether or not both are there —
   the left one becomes an empty box of its own width on today, so the date
   does not jump sideways the moment you step forward and back. */
.disc-date{
  display:flex; align-items:center; gap:var(--s2);
  margin-bottom:var(--s4);
}
.disc-arrow{
  flex:0 0 auto; width:44px; height:44px; border-radius:50%;
  border:1px solid var(--line); background:var(--paper);
  font-size:var(--t-body); color:var(--ink); cursor:pointer;
}
.disc-arrow.ghost{ border-color:transparent; background:transparent; pointer-events:none }
.disc-arrow:hover{ border-color:#b9b2a6 }
.disc-day{
  flex:1; min-height:44px; padding:6px var(--s3);
  display:flex; flex-direction:column; align-items:center; gap:1px;
  border:1px solid var(--line); border-radius:var(--r-control);
  background:var(--paper); cursor:pointer;
}
.disc-day b{ font-size:var(--t-body); font-weight:var(--w-med); color:var(--ink) }
.disc-day-hint{ font-size:var(--t-micro); color:var(--muted) }

.disc-search{ margin-bottom:var(--s3) }

.disc-list{ display:flex; flex-direction:column; gap:var(--s2) }
.disc-card{
  display:flex; align-items:center; gap:var(--s3);
  padding:var(--s3); min-height:60px; cursor:pointer; text-align:left; width:100%;
}
.disc-card:hover{ border-color:var(--green-mid) }
.disc-card-main{ flex:1; min-width:0; display:flex; flex-direction:column; gap:2px }
.disc-card-main strong{ font-size:var(--t-body); font-weight:var(--w-med); color:var(--ink) }
.disc-meta{ font-size:var(--t-label); color:var(--muted) }
.disc-where{
  font-size:var(--t-micro); color:var(--muted2);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.disc-go{ flex:0 0 auto; color:var(--muted2); font-size:var(--t-body) }

/* --- the month picker sheet ------------------------------------------------ */
.dc-head{
  display:flex; align-items:center; justify-content:space-between;
  gap:var(--s3); margin-bottom:var(--s3);
}
.dc-head b{ font-size:var(--t-card); font-weight:var(--w-med) }
.dc-nav{
  width:40px; height:40px; border-radius:50%;
  border:1px solid var(--line); background:var(--paper);
  font-size:var(--t-body); cursor:pointer;
}
.dc-dow{
  display:grid; grid-template-columns:repeat(7,1fr); gap:2px;
  margin-bottom:4px;
}
.dc-dow span{
  text-align:center; font-size:var(--t-micro); color:var(--muted);
  text-transform:uppercase; letter-spacing:.06em;
}
.dc-grid{ display:grid; grid-template-columns:repeat(7,1fr); gap:2px }
.dc-pad{ display:block }
.dc-day{
  position:relative; display:flex; flex-direction:column; align-items:center;
  justify-content:center; gap:1px;
  min-height:44px; border:1px solid transparent; border-radius:var(--r-control);
  background:transparent; cursor:pointer; color:var(--ink);
}
.dc-day b{ font-size:var(--t-label); font-weight:var(--w-med) }
.dc-day:hover:not(:disabled){ background:var(--warm) }
.dc-day.on{ background:var(--green-soft); border-color:var(--green) }
/* Past days are unreachable, and they say so by being quiet rather than by
   vanishing — the shape of the month is part of how you read a calendar. */
.dc-day.past{ color:var(--muted2); cursor:default; opacity:.55 }
.dc-dot{
  width:4px; height:4px; border-radius:50%; background:var(--green); display:block;
}

/* ==========================================================================
   The + — start a plan from nothing, from anywhere

   A floating button in the PAGE rather than a fifth tab, because on iOS the
   tab bar is native SwiftUI below the web view: a fifth tab is an App Store
   submission and a review wait, and this ships the moment it deploys, into the
   build already on people's phones.
   ========================================================================== */
.new-plan-fab{
  position:fixed; z-index:41;
  right:var(--s4); bottom:calc(84px + env(safe-area-inset-bottom));
  width:56px; height:56px; border-radius:50%;
  border:0; background:var(--ink); color:#fff;
  font-size:30px; line-height:1; font-weight:300;
  box-shadow:0 6px 20px rgba(38,33,25,.28);
  cursor:pointer;
}
.new-plan-fab:active{ transform:scale(.96) }
/* No web tab bar in the app — the real one is native, below the web view — so
   there is nothing to clear down there. */
html.native-shell .new-plan-fab{ bottom:calc(20px + env(safe-area-inset-bottom)) }
@media(min-width:860px){
  .new-plan-fab{ bottom:var(--s4) }
}

/* The wizard, opened over the tab rather than being it. */
.wiz-bar{
  display:flex; align-items:center; justify-content:space-between;
  gap:var(--s3); margin:0 0 var(--s3);
}
.wiz-bar b{ font-size:var(--t-label); text-transform:uppercase; letter-spacing:.08em; color:var(--muted) }

/* The app, offered on the page that exists so nobody has to install it. A line
   above the card, never a wall in front of it — somebody who taps Coming and
   leaves has done exactly what the page is for. */
.ask-store{
  display:flex; align-items:baseline; gap:var(--s2);
  margin:0 auto var(--s3); padding:10px var(--s3);
  max-width:420px; border-radius:var(--r-pill);
  background:var(--paper); border:1px solid var(--line);
  text-decoration:none; min-height:44px; box-sizing:border-box;
}
.ask-store b{
  font-size:var(--t-label); font-weight:700; letter-spacing:.08em; color:var(--ink);
}
.ask-store span{ font-size:var(--t-label); color:var(--muted); margin-left:auto }

/* ==========================================================================
   The planning chat, as a page

     top     back, and the four things being collected
     middle  the conversation, and the answers you can tap
     bottom  type it, or say it

   Nothing else. Every other control this screen could carry — a progress bar,
   a skip, an "I already know what and when" — is a way of not answering the
   question on screen, and the question is the whole page.
   ========================================================================== */
.as-page{
  display:flex; flex-direction:column;
  height:100dvh; min-height:100dvh;
  background:var(--bg);
}
.as-top{
  flex:0 0 auto;
  display:flex; align-items:center; gap:var(--s2);
  padding:calc(env(safe-area-inset-top) + var(--s2)) var(--s3) var(--s2);
  border-bottom:1px solid var(--line);
  background:var(--paper);
}
.as-back{
  flex:0 0 auto; width:44px; height:44px; border-radius:50%;
  border:1px solid var(--line); background:var(--paper);
  font-size:20px; line-height:1; color:var(--ink); cursor:pointer;
}
.as-back:hover{ border-color:#b9b2a6 }
/* The four slots share the row with the back button, so they get what is left
   rather than their own line — this bar is chrome and should read as one. */
.as-top .as-slots{ flex:1; margin:0; gap:4px }

.as-thread{
  flex:1 1 auto; min-height:0; overflow-y:auto;
  padding:var(--s3) var(--s3) var(--s4);
  display:flex; flex-direction:column; gap:var(--s2);
  -webkit-overflow-scrolling:touch;
}
.as-thread .as-picks{ margin-top:var(--s2) }

.as-compose{
  flex:0 0 auto;
  display:flex; align-items:center; gap:var(--s2);
  padding:var(--s2) var(--s3) calc(var(--s2) + env(safe-area-inset-bottom));
  border-top:1px solid var(--line);
  background:var(--paper);
}
.as-compose .input{ flex:1; min-height:44px }
.as-mic{
  flex:0 0 auto; width:44px; height:44px; border-radius:50%;
  border:0; background:var(--ink); color:#fff;
  font-size:18px; line-height:1; cursor:pointer;
}
.as-mic:active{ transform:scale(.96) }

/* ==========================================================================
   The + in the tab bar, where the microphone was
   ========================================================================== */
.nav-plus{
  flex:0 0 auto; width:44px; height:44px; margin-left:4px;
  border-radius:50%; border:0;
  background:var(--ink); color:#fff;
  font-size:26px; line-height:1; font-weight:300; cursor:pointer;
}
.nav-plus:active{ transform:scale(.96) }
.bottom-nav{ align-items:center }

/* ==========================================================================
   Events — cards, tags, ranges

   A card is bigger than a row because a picture is most of the reason anybody
   goes anywhere, and a list of names is a directory.
   ========================================================================== */

/* A disabled arrow, not a missing one. The row keeps its shape as you step
   through it, so the date does not jump sideways under your thumb — and a
   greyed control still says the direction exists and you are at the end of it. */
.disc-arrow:disabled{ opacity:.4; cursor:default; border-color:var(--line) }
.disc-arrow:disabled:hover{ border-color:var(--line) }

.disc-card{ align-items:flex-start; gap:var(--s3); padding:var(--s3); min-height:88px }
.disc-photo{
  flex:0 0 auto; width:72px; height:72px; border-radius:var(--r-control);
  background-size:cover; background-position:center;
  background-color:var(--warm);
}
.disc-photo.none{
  display:grid; place-items:center; font-size:26px;
  border:1px solid var(--line);
}
.disc-card-main{ gap:3px; padding-top:2px }

/* TODAY is red because it expires today. THIS WEEKEND is warm and quiet: it is
   information, not a countdown. */
.disc-tag{
  align-self:flex-start;
  font-size:var(--t-micro); font-weight:700; letter-spacing:.08em;
  padding:2px 7px; border-radius:var(--r-pill);
  background:var(--warm); color:var(--muted);
}
.disc-tag.now{ background:#f6e3e0; color:#8c2f24 }

.disc-hero{
  height:170px; border-radius:var(--r-card); margin-bottom:var(--s3);
  background-size:cover; background-position:center; background-color:var(--warm);
}
.disc-why{ margin:var(--s2) 0 0; padding-left:18px; color:var(--muted) }
.disc-why li{ font-size:var(--t-label); line-height:1.5; margin-bottom:3px }

/* ==========================================================================
   Your face, and everything waiting behind it
   ========================================================================== */
.profile-dot{ position:relative }
/* A dot, not a number. The counts are in the menu where there is room to say
   what they are; out here the only question is whether to open it. */
.dot-mark{
  position:absolute; top:-1px; right:-1px;
  width:11px; height:11px; border-radius:50%;
  background:var(--pf-blue, #2f6fd0);
  border:2px solid var(--paper);
}
.dot-mark.urgent{ background:#c0392b }

/* ==========================================================================
   The guest conversation

   Answering used to be the end of the page: tap Coming, get a thank-you and a
   signup pitch. But "I'm in" is the START of the conversation — what time, who
   is driving, can I bring someone — and every one of those went back to the
   group chat this product exists to get plans out of.
   ========================================================================== */
.ask-chat{
  margin:var(--s4) 0;
  border:1px solid var(--line); border-radius:var(--r-card);
  background:var(--bg); overflow:hidden;
}
.ask-chat-head{
  padding:10px var(--s3);
  font-size:var(--t-micro); text-transform:uppercase; letter-spacing:.08em;
  font-weight:700; color:var(--muted);
  border-bottom:1px solid var(--line);
}
/* Capped rather than growing: this sits above the thing that explains what the
   app is, and a long thread should not push that off the page. */
.ask-msgs{
  max-height:240px; overflow-y:auto;
  padding:var(--s3);
  display:flex; flex-direction:column; gap:var(--s2);
  -webkit-overflow-scrolling:touch;
}
.ask-msg{ display:flex; flex-direction:column; gap:1px; max-width:85% }
.ask-msg b{
  font-size:var(--t-micro); font-weight:700; color:var(--muted);
  letter-spacing:.02em;
}
.ask-msg span{
  font-size:var(--t-label); line-height:1.45; color:var(--ink);
  background:var(--paper); border:1px solid var(--line);
  border-radius:var(--r-control); padding:8px 10px;
  overflow-wrap:anywhere;
}
.ask-msg.mine{ align-self:flex-end; align-items:flex-end }
.ask-msg.mine span{ background:var(--warm) }

.ask-compose{
  display:flex; gap:var(--s2); padding:var(--s2) var(--s3) var(--s3);
  border-top:1px solid var(--line); background:var(--paper);
}
.ask-compose .input{ flex:1; min-height:44px }
.ask-compose .primary{ flex:0 0 auto; min-height:44px; padding:0 var(--s3) }

/* A count in the avatar menu, in a colour that means something rather than
   more words. Red is somebody waiting on you; blue is a job with nobody at the
   other end of it. */
.menu-row{ display:flex; align-items:center; gap:var(--s2) }
.menu-row > span:first-of-type{ flex:1; text-align:left }
.menu-count{
  flex:0 0 auto; min-width:22px; padding:1px 7px;
  border-radius:999px; text-align:center;
  font-size:var(--t-micro); font-weight:700; font-variant-numeric:tabular-nums;
  background:var(--pf-blue-soft); color:var(--pf-blue);
}
.menu-count.urgent{ background:#f6e3e0; color:#8c2f24 }

/* ==========================================================================
   Cleaner: a smaller title, less air above it, and a top bar that is a logo
   and your face rather than a wordmark and a stack of controls
   ========================================================================== */

/* The wordmark was set at body size and the page title at --t-title, so the
   heading was the loudest thing on every screen and pushed the first real
   content a third of the way down. Both come down; the content comes up. */
@media (pointer:coarse) and (hover:none){
  .screen-title{ margin:0 0 var(--s3) }
  .screen-title h1{ font-size:1.375rem; letter-spacing:-.03em }
  .screen-title p{ margin-top:4px; font-size:var(--t-label) }
  .topbar-row{ padding-top:var(--s2); padding-bottom:var(--s2) }
  .disc-modes{ margin-bottom:var(--s2) }
  .disc-date{ margin-bottom:var(--s3) }
}

.brand-logo{
  display:block; width:28px; height:28px; border-radius:8px;
  object-fit:cover;
}

/* The dot is a sibling of the avatar, not a child: inside, it sat under the
   avatar's own background and the button's border-radius clipped its corner —
   a blue crescent behind your face instead of a marker on it. */
.you-wrap{ position:relative; display:inline-flex; flex:0 0 auto }
/* The button stays 44 and keeps the whole tap target; it just stops being the
   thing you can see. The visible circle is .pd-face inside it, at 32. */
.you-wrap .profile-dot{
  width:44px; height:44px; padding:0;
  background:none; border:0; border-radius:50%;
  display:grid; place-items:center;
}
.pd-face{
  width:32px; height:32px; border-radius:50%;
  border:1px solid var(--line2);
  background:linear-gradient(145deg,#d8c3b7,#8ea69a);
  background-size:cover; background-position:center;
  display:grid; place-items:center;
  color:#fff; font-size:var(--t-micro); font-weight:var(--w-med);
}
.pd-face.has-photo{ background-image:none }
.you-wrap .dot-mark{
  position:absolute; top:4px; right:4px; z-index:2;
  width:11px; height:11px; border-radius:50%;
  background:var(--pf-blue); border:2px solid var(--bg);
  pointer-events:none;
}
.you-wrap .dot-mark.urgent{ background:#c0392b }

/* ==========================================================================
   The chat bar: what you have answered, on one line
   ========================================================================== */
.as-crumbs{
  flex:1; min-width:0;                /* min-width:0 — without it a flex child
                                         refuses to shrink below its content and
                                         takes the whole row off the screen */
  display:flex; align-items:center; gap:6px;
  overflow-x:auto; scrollbar-width:none;
}
.as-crumbs::-webkit-scrollbar{ display:none }
.as-title{
  font-size:var(--t-label); font-weight:var(--w-med); color:var(--muted);
  letter-spacing:.02em; white-space:nowrap;
}
.as-crumb{
  flex:0 0 auto; display:inline-flex; align-items:center; gap:5px;
  min-height:30px; padding:4px 8px 4px 10px;
  border:1px solid var(--green-mid); border-radius:var(--r-pill);
  background:var(--green-soft); color:var(--green);
  font-size:var(--t-label); font-weight:var(--w-med);
  white-space:nowrap; cursor:pointer; max-width:11rem;
  overflow:hidden; text-overflow:ellipsis;
}
.as-crumb i{ font-style:normal; opacity:.6; font-size:var(--t-body); line-height:1 }
.as-crumb:hover i{ opacity:1 }

/* The page is sized by fitChatPage(), which measures the real space left under
   the header — 100dvh guessed, and guessed high inside a web view that does
   not own the whole screen, which left the compose row floating above a band
   of empty page. */
.as-page{ height:auto; min-height:0 }

/* ==========================================================================
   The chat page, actually filling the page

   The compose row floated in the middle of the screen with a band of empty
   background under it, and I blamed 100dvh and removed the height. That was
   not it. .as-thread is declared twice: once for the SHEET this used to be,
   with `max-height:38vh`, and once for the page. The page version sets
   flex:1 1 auto and never resets the cap — so the thread stopped growing at
   38% of the viewport and everything below it sat where that ran out.
   ========================================================================== */
.as-page .as-thread{
  max-height:none;
  margin-bottom:0;          /* the sheet version's 12px, showing as a gap */
  padding:var(--s3) var(--s3) var(--s4);
}

/* fitChatPage() measures the space and sets an exact height, then .content's
   own bottom padding is added underneath it and the page pans under the thumb
   on a screen that is supposed to be fixed. */
html.chat-mode .content{ padding-bottom:0 }
html.chat-mode .app-shell{ padding-bottom:0 }
html.chat-mode .screen{ animation:none }   /* measured mid-transform, 5px short */

/* A crumb is a control, so it gets a control's target. text-overflow does
   nothing on a flex container — the label needs to be the thing that ellipses,
   so it is its own span now. */
.as-crumb{ min-height:44px; overflow:visible }
.as-crumb > span{
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; min-width:0;
}

/* An answer chip can be a group name of any length. Without a ceiling one chip
   is wider than the thread and the whole conversation pans sideways. */
.as-pick{ max-width:100% }
.as-pick b{ overflow:hidden; text-overflow:ellipsis; max-width:100% }

/* ==========================================================================
   The web tab bar has five children and a four-column grid

   bottomNav() emits four tabs plus the +, so on the web the + was placed on an
   implicit second row inside a fixed 70px box and hung out of the pill. The
   phone media query flips it to flex, where nothing gave the items a flex
   basis, so five items packed left with dead space on the right.

   Invisible in the App Store build — the native bar replaces this one — and
   wrong for everybody using it in a browser.
   ========================================================================== */
.bottom-nav{ grid-template-columns:repeat(4,1fr) auto }
@media (pointer:coarse) and (hover:none){
  .bottom-nav{ display:flex !important; align-items:center; gap:3px }
  .bottom-nav .nav-item{ flex:1 1 0; min-width:0 }
  .bottom-nav .nav-plus{ flex:0 0 auto }
}

/* 3.54:1 against the bar it sits on, at 12px. The tab you are NOT on still has
   to be readable — that is the whole job of a tab bar. */
.nav-item{ color:#6f6d66 }

/* ==========================================================================
   The planning chat: answers you tap into the box, then send

   Options used to commit on press, which meant a multi-answer question needed
   its own "Done" button — two different ways of finishing one question on one
   screen. A tap writes into the field now. Send is the only thing that submits.
   ========================================================================== */

/* Rectangles. An oval says "chip, tap me, I vanish"; these are options being
   assembled into an answer, and they sit still while you pick more than one. */
.as-pick{
  border-radius:var(--r-control);
  padding:11px var(--s3);
  min-height:48px;
  gap:8px;
  flex-direction:row; align-items:center;
}
.as-pick b{ font-weight:var(--w-med) }
.as-pick small{ color:var(--muted); font-size:var(--t-label) }
.as-picks{ gap:8px }

/* A dot, not the words "you like this" — a mark saying "this one is yours"
   beside the thing it is about. */
.as-mine{
  flex:0 0 auto; width:7px; height:7px; border-radius:50%;
  background:var(--green); display:block;
  animation:asPulse 2.2s ease-in-out infinite;
}
@keyframes asPulse{
  0%,100%{ transform:scale(1);   opacity:1 }
  50%    { transform:scale(1.5); opacity:.45 }
}
@media (prefers-reduced-motion:reduce){ .as-mine{ animation:none } }

/* The mic belongs to the FIELD — it is another way of filling it in — so it
   lives inside it. Send is the action, and takes the outside position. */
.as-field{
  flex:1; min-width:0; position:relative; display:flex; align-items:center;
}
.as-field .input{ width:100%; padding-right:52px }
.as-mic{
  position:absolute; right:6px;
  width:32px; height:32px; border-radius:50%;
  border:0; background:transparent; color:var(--muted);
  font-size:15px; line-height:1; cursor:pointer;
  display:grid; place-items:center;
}
.as-mic:active{ background:var(--warm) }
.as-send{
  flex:0 0 auto; width:44px; height:44px; border-radius:50%;
  border:0; background:var(--ink); color:#fff;
  font-size:19px; line-height:1; cursor:pointer;
}
.as-send:active{ transform:scale(.96) }

/* The plan's name, not a read-back of the last four taps. */
.as-title{
  font-size:var(--t-body); font-weight:var(--w-med); color:var(--ink);
  letter-spacing:-.01em; white-space:nowrap;
  overflow:hidden; text-overflow:ellipsis; min-width:0;
}

/* ==========================================================================
   The group month: how many, and which of them

   The shading says how many are free, which is the number you scan a month
   for. What it could not say is WHO — and "most of them" means something
   different depending on whether the missing one is the person with the car.
   ========================================================================== */
.month-day{ position:relative }
.gd-dots{
  display:flex; gap:2px; justify-content:center;
  margin-top:2px; min-height:5px;
}
.gd-dot{ width:4px; height:4px; border-radius:50%; display:block }
.gd-dot.free{ background:var(--green) }
.gd-dot.busy{ background:#b8b2a6 }
.gd-dot.unknown{ background:#dfd9cf }
/* On a shaded cell the dots have to sit on that shade, not fight it. */
.month-day.d3 .gd-dot.free, .month-day.d4 .gd-dot.free{ background:#1f5138 }

/* The day you picked, before you commit to it. */
.month-day.picked{
  outline:2px solid var(--green); outline-offset:-2px;
  border-radius:var(--r-control);
}

.gd-sum{ margin-top:var(--s3); padding:var(--s3); display:flex; flex-direction:column; gap:var(--s2) }
.gd-sum-top{ display:flex; align-items:baseline; justify-content:space-between; gap:var(--s3) }
.gd-sum-top strong{ font-size:var(--t-body); font-weight:var(--w-med) }
.gd-sum-tag{
  font-size:var(--t-label); font-weight:var(--w-med); color:var(--muted);
  white-space:nowrap;
}
.gd-sum-tag.all{ color:var(--green) }
.gd-sum-line{
  display:flex; align-items:center; justify-content:space-between; gap:var(--s3);
  font-size:var(--t-label); color:var(--muted);
}
.gd-sum-line > span{ min-width:0 }
/* Green, not ink. Every other primary in the app commits you to something you
   have already assembled; this one is the moment a shaded square becomes a
   plan, and the colour it turns is the colour the whole calendar uses for
   "these people are free". */
.gd-plan{
  margin-top:var(--s2); min-height:48px;
  background:var(--green); border-color:var(--green);
}
.gd-plan:active{ background:#275c42; border-color:#275c42 }

/* The plan's name, which is also how you change it. A wrong answer used to be
   unfixable without throwing the whole conversation away. */
.as-title-btn{
  display:inline-flex; align-items:center; gap:5px;
  max-width:100%; min-height:34px; padding:4px 10px;
  border:1px solid var(--line); border-radius:var(--r-pill);
  background:var(--paper); cursor:pointer; text-align:left;
}
.as-title-btn i{ font-style:normal; color:var(--muted2); font-size:11px; flex:0 0 auto }
.as-title-btn:hover{ border-color:#b9b2a6 }

/* The widget step in setup. Three cards saying what to look for, because iOS
   gives no way to add a widget for somebody and no deep link to the gallery —
   all a screen can do is make the search worth making. */
.ob-widgets{ display:flex; flex-direction:column; gap:var(--s2); margin:var(--s3) 0 }
.ob-widget{ display:flex; align-items:flex-start; gap:var(--s3); padding:var(--s3) }
.ob-widget-art{
  flex:0 0 auto; width:44px; height:44px; border-radius:var(--r-control);
  background:var(--warm); display:grid; place-items:center; font-size:22px;
}
.ob-widget strong{ display:block; font-size:var(--t-body); font-weight:var(--w-med); color:var(--ink) }
.ob-widget span{ display:block; font-size:var(--t-label); color:var(--muted); line-height:1.45; margin-top:2px }

/* ==========================================================================
   The Pro matrix

   A list of what you would GET tells you nothing about what you have. Both
   columns at once, with the free one deliberately long, because the free tier
   is a whole product and the paid one is the same product with the expensive
   parts switched on.
   ========================================================================== */
.pro-matrix{
  border:1px solid var(--line); border-radius:var(--r-card);
  overflow:hidden; margin:var(--s3) 0;
  background:var(--card);
}
.pm-head,.pm-row{
  display:grid; grid-template-columns:1fr 62px 62px;
  align-items:center; gap:var(--s2);
  padding:10px var(--s3);
}
.pm-head{
  background:var(--warm);
  font-size:var(--t-micro); font-weight:700; letter-spacing:.07em;
  text-transform:uppercase; color:var(--muted);
}
.pm-head span:not(:first-child),.pm-row > span:not(:first-child){ text-align:center }
.pm-head .pm-pro{ color:var(--green) }
.pm-row{ border-top:1px solid var(--line); font-size:var(--t-label); color:var(--ink) }
/* The rows Pro alone has, marked once on the left rather than by colouring the
   whole row — which would read as "unavailable" for the things you already
   have in the free column beside it. */
.pm-row.pm-only > span:first-child{ font-weight:var(--w-med) }
.pm-yes{ color:var(--green); font-weight:700 }
.pm-no{ color:var(--muted2) }
.pm-val{ font-size:var(--t-micro); font-weight:var(--w-med); color:var(--muted) }
.pm-row > span:last-child .pm-val,.pm-row > span:last-child{ color:var(--ink) }

/* The free tier's compose row: a button that says what it costs, not a text
   field you can put a cursor in and never send. The tappable answers above it
   are the free way through, and they finish the job. */
.as-upsell{
  display:flex; align-items:center; justify-content:space-between;
  gap:var(--s3); width:100%; text-align:left; cursor:pointer;
  border:0; border-top:1px solid var(--line); background:var(--paper);
}
.as-upsell span{ display:flex; flex-direction:column; gap:1px; min-width:0 }
.as-upsell b{ font-size:var(--t-body); font-weight:var(--w-med); color:var(--ink) }
.as-upsell small{ font-size:var(--t-label); color:var(--green); font-weight:var(--w-med) }
.as-upsell i{ font-style:normal; color:var(--muted2); font-size:var(--t-body); flex:0 0 auto }

/* ==========================================================================
   Your day, on the Me tab — a card with a shape, not a grey square

   It was one flat bordered box: a date, a sentence, a rule, a list. Everything
   in it the same weight, so the thing you came to read — how much of the day is
   actually yours — sat in the middle looking like a caption.

   The status leads now, in the colour that means it, with the hours under it.
   The blocks become a real list rather than rows separated by hairlines.
   ========================================================================== */
.day-answer{
  margin-top:var(--s3);
  padding:0;
  border:1px solid var(--line);
  border-radius:var(--r-card);
  background:var(--card);
  overflow:hidden;
}
.day-answer .da-head{
  padding:var(--s3) var(--s3) 0;
  align-items:baseline;
}
.day-answer .da-head b{ font-size:var(--t-card); letter-spacing:-.02em }
.da-state{
  font-size:var(--t-micro); font-weight:700; letter-spacing:.06em;
  text-transform:uppercase; padding:3px 8px; border-radius:var(--r-pill);
  background:var(--warm); color:var(--muted);
}

/* The hours, given the weight the question deserves. "Free 5:00 PM–11:00 PM"
   is the answer somebody opened this to get. */
.da-free{
  margin:6px var(--s3) 0;
  padding-bottom:var(--s3);
  font-size:var(--t-body); line-height:1.4; color:var(--ink);
  font-weight:var(--w-med);
}
.da-past{ margin:6px var(--s3) var(--s3) }
.da-snoozed{ margin:6px var(--s3) 0 }

/* The list sits in its own band, so the card reads as two things: what the day
   is, and what is on it. */
.da-blocks{
  margin:0; border-top:1px solid var(--line);
  padding:var(--s2) var(--s3) var(--s3);
  background:var(--paper);
}
.da-block{
  border-bottom:0; border-radius:var(--r-control);
  padding:9px var(--s2); margin:0 calc(var(--s2) * -1);
  gap:var(--s3); align-items:center;
}
.da-block:hover{ background:var(--warm) }
.da-block-when{
  flex:0 0 auto; min-width:104px;
  font-size:var(--t-label); font-weight:var(--w-med); color:var(--ink);
  font-variant-numeric:tabular-nums;
}
.da-block-what{ flex:1; min-width:0; font-size:var(--t-label); color:var(--muted);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
.da-empty{
  margin:var(--s2) 0 0; padding:var(--s3); text-align:center;
  font-size:var(--t-label); color:var(--muted);
  background:var(--bg); border-radius:var(--r-control);
  border:1px dashed var(--line2);
}

/* The purchase button carries what 3.1.2 asks for: what it is, then what it
   costs and for how long. A price with no name beside it is a button that does
   not say what you are buying. */
.pro-buys button{
  display:flex; flex-direction:column; align-items:center; gap:2px;
  padding:12px var(--s3); min-height:56px;
}
.pro-buys button b{ font-size:var(--t-body); font-weight:var(--w-med) }
.pro-buys button span{ font-size:var(--t-label); opacity:.85 }
.pro-terms{
  margin:var(--s3) 0 0; font-size:var(--t-micro);
  line-height:1.5; color:var(--muted);
}
.pro-terms a{ color:var(--muted); text-decoration:underline }

/* ==========================================================================
   The assistant in a plan's chat

   Marked as software, always. A group deciding where to go should never have to
   wonder whether a friend said it.
   ========================================================================== */
.pp-ai{
  display:flex; align-items:center; justify-content:center; gap:6px;
  /* 44, not 40. It is a real target on both versions of this button - the live
     one opens a conversation, the locked one opens the paywall - and 40 is
     under the 44pt floor the rest of the app holds to. */
  width:100%; min-height:44px; margin-bottom:var(--s2);
  border:1px solid var(--green-mid); border-radius:var(--r-pill);
  background:var(--green-soft); color:var(--green);
  font-size:var(--t-label); font-weight:var(--w-med); cursor:pointer;
}
.pp-ai:disabled{ opacity:.7; cursor:default }
/* Locked, not disabled. It is the same button in grey with a badge on it, and
   it is still a target - a disabled control cannot say why it is disabled, and
   why is the entire message. */
.pp-ai.locked{ border-color:var(--line); background:var(--gray-soft); color:var(--muted) }

/* --- the PRO badge -----------------------------------------------------------
   On the thing itself rather than in a tooltip or a footnote, because somebody
   deciding whether to pay is looking at the feature, not at a pricing page.
   Small, quiet and the same everywhere it appears. */
.pp-pro,.disc-pro{
  margin-left:5px; padding:1px 5px; border-radius:var(--r-pill);
  background:var(--green); color:#fff;
  font-size:var(--t-dense); font-weight:var(--w-bold); letter-spacing:.06em;
  /* --t-dense is below the 12px floor, and this one is allowed to be: it is a
     three-letter mark, not something anybody reads a sentence of. White on
     --green is 5.6:1, so it stays legible at the size. */
  line-height:1.6; vertical-align:1px;
}
.disc-mode.locked{ color:var(--muted) }
.disc-mode.locked .disc-pro{ background:var(--muted); }
.disc-mode.on.locked .disc-pro{ background:var(--green) }
.ai-avatar{
  display:grid; place-items:center;
  background:var(--green-soft); color:var(--green);
  border:1px solid var(--green-mid); font-size:14px;
}
.message.ai .bubble{
  background:var(--green-soft);
  border:1px solid var(--green-mid);
}
.message.ai .bubble strong{ color:var(--green) }

/* ==========================================================================
   The home tab — scope, row two, rails.

   Everything here is a horizontal rail rather than a stack. That is the whole
   density argument: twelve friends stacked is a 1,727px column with half of it
   under the tab bar, and the same twelve as a rail is one swipe and 110px. The
   page is about two and a half screens instead of eight.
   ========================================================================== */
.hm{ padding-top:2px }
.hm-ctx{ display:flex; align-items:center; justify-content:space-between; padding:2px 0 10px }
.hm-group{
  display:inline-flex; align-items:center; gap:7px; border:0; background:transparent;
  font:inherit; color:var(--ink); font-size:var(--t-card); font-weight:var(--w-bold);
  letter-spacing:-.03em; cursor:pointer; min-height:44px; padding:0;
}
.hm-group i{ font-style:normal; font-size:var(--t-label); opacity:.45 }

/* The search bar that replaced the floating +. Full width and labelled, so the
   most important control in the app finally says what it is for. */
.hm-search{
  display:flex; align-items:center; gap:10px; width:100%; min-height:46px;
  padding:12px var(--s4); border:1px solid var(--line); border-radius:var(--r-pill);
  background:var(--paper); color:var(--muted); font:inherit; font-size:var(--t-meta);
  text-align:left; cursor:pointer; box-shadow:0 1px 2px rgba(40,35,28,.05);
}
.hm-search:active{ background:var(--warm) }

/* --- row one: the scope --------------------------------------------------- */
.hm-scopes{ display:flex; gap:var(--s2); padding:var(--s3) 0 0;
  border-bottom:1px solid var(--line); margin-bottom:2px }
.hm-sc{
  flex:1 1 0; min-width:0; display:flex; flex-direction:column; align-items:center;
  gap:6px; padding:0 2px 10px; border:0; background:transparent; font:inherit;
  cursor:pointer; position:relative;
}
.hm-sc i{
  font-style:normal; width:48px; height:48px; border-radius:50%; flex:0 0 auto;
  background:var(--warm); border:1px solid var(--line);
  display:grid; place-items:center; font-size:21px;
}
.hm-sc span{ font-size:var(--t-dense); font-weight:var(--w-med); line-height:1.2;
  color:var(--muted); text-align:center }
.hm-sc.on i{ background:var(--ink); border-color:var(--ink) }
.hm-sc.on span{ color:var(--ink); font-weight:var(--w-bold) }
/* The underline sits ON the row's border, which is what makes it read as a tab
   rather than as a selected button floating above a line. */
.hm-sc.on::after{
  content:''; position:absolute; left:12%; right:12%; bottom:-1px; height:3px;
  border-radius:3px 3px 0 0; background:var(--ink);
}
.hm-sc em{
  font-style:normal; position:absolute; top:-4px; right:0;
  font-size:var(--t-dense); font-weight:var(--w-bold); letter-spacing:.06em;
  color:#fff; background:var(--green); padding:2px 6px; border-radius:var(--r-pill);
}

/* --- row two: what, or where ---------------------------------------------- */
.hm-row{
  display:flex; gap:var(--s3); overflow-x:auto; padding:var(--s3) 2px var(--s2);
  scrollbar-width:none; -webkit-overflow-scrolling:touch;
}
.hm-row::-webkit-scrollbar{ display:none }
.hm-dot{
  flex:0 0 auto; width:56px; display:flex; flex-direction:column; align-items:center;
  gap:5px; border:0; background:transparent; font:inherit; cursor:pointer; padding:0;
}
.hm-dot i{
  font-style:normal; width:48px; height:48px; border-radius:50%; flex:0 0 auto;
  background:var(--paper); border:1px solid var(--line);
  display:grid; place-items:center; font-size:21px; position:relative;
}
.hm-dot span{ font-size:var(--t-dense); font-weight:var(--w-med); line-height:1.2;
  text-align:center; color:var(--ink) }
.hm-dot small{ font-size:var(--t-dense); color:var(--muted); line-height:1.1 }
.hm-dot.on i{ background:var(--green); border-color:var(--green) }
/* A tick, not just a fill. Colour alone is not an answer to "is this on" for
   anybody who cannot see the difference between the two greens. */
.hm-dot.on i::after{
  content:'\2713'; position:absolute; right:-3px; bottom:-3px;
  width:18px; height:18px; border-radius:50%; background:var(--ink); color:#fff;
  font-size:10px; font-weight:var(--w-bold); display:grid; place-items:center;
  border:2px solid var(--bg);
}
.hm-dot.on span{ color:var(--green); font-weight:var(--w-bold) }
.hm-dot.place i{ color:#fff; border:0;
  background:linear-gradient(145deg,#8fa89a,#5d7568) }
.hm-dot.place.on i{ box-shadow:0 0 0 2px var(--ink) }
/* Where the picked ones stop and the rest of the list starts. */
.hm-split{ flex:0 0 auto; width:1px; align-self:stretch; margin:2px 4px 20px;
  background:var(--line2) }

.hm-chips{ display:flex; gap:6px; overflow-x:auto; padding:2px 2px var(--s2);
  scrollbar-width:none }
.hm-chips::-webkit-scrollbar{ display:none }
.hm-chip{
  flex:0 0 auto; min-height:36px; padding:8px 14px; border-radius:var(--r-pill);
  border:1px solid var(--line); background:var(--paper); font:inherit;
  font-size:var(--t-label); font-weight:var(--w-med); color:var(--ink);
  white-space:nowrap; cursor:pointer;
}
.hm-chip.on{ background:var(--ink); color:#fff; border-color:var(--ink) }

/* --- sections and rails ---------------------------------------------------- */
.hm-sec{ display:flex; align-items:center; gap:var(--s2); padding:var(--s5) 2px var(--s3) }
.hm-sec b{ font-size:var(--t-card); font-weight:var(--w-bold); letter-spacing:-.032em }
.hm-sec i{ font-style:normal; margin-left:auto; font-size:var(--t-meta); color:var(--muted2) }
.hm-pro{
  font-size:var(--t-dense); font-weight:var(--w-bold); letter-spacing:.09em;
  color:#fff; background:var(--green); padding:3px 8px; border-radius:var(--r-pill);
}
/* The allowance, said out loud. A limit somebody can see is one they plan
   around; a silent one just feels like the app broke on the sixth try. */
.hm-left{ font-size:var(--t-dense); font-weight:var(--w-med); letter-spacing:.06em;
  color:var(--muted) }

.hm-rail{
  display:flex; gap:var(--s2); overflow-x:auto; padding:2px 2px var(--s2);
  scrollbar-width:none; -webkit-overflow-scrolling:touch;
}
.hm-rail::-webkit-scrollbar{ display:none }
.hm-rail .disc-card{ flex:0 0 168px; margin:0 }
.hm-rail .plan-card{ flex:0 0 232px }

/* --- days ------------------------------------------------------------------ */
.hm-day{
  display:flex; align-items:center; gap:var(--s3); width:100%; margin-top:var(--s2);
  padding:var(--s3); border:1px solid var(--line); border-radius:var(--r-card);
  background:var(--paper); font:inherit; text-align:left; cursor:pointer; min-height:56px;
}
.hm-day.all{ border-color:var(--green-mid);
  background:linear-gradient(180deg,var(--green-soft),var(--paper) 78%) }
.hm-dd{ flex:0 0 36px; text-align:center; line-height:1.05 }
.hm-dd b{ display:block; font-size:var(--t-card); font-weight:var(--w-bold); letter-spacing:-.03em }
.hm-dd small{ font-size:var(--t-dense); font-weight:var(--w-bold); color:var(--muted);
  letter-spacing:.06em }
.hm-dtx{ flex:1; min-width:0 }
.hm-dtx b{ display:block; font-size:var(--t-meta); font-weight:var(--w-med) }
.hm-dtx small{ display:block; font-size:var(--t-label); color:var(--muted); margin-top:1px }
.hm-go{ flex:0 0 auto; color:var(--muted2); font-size:var(--t-body) }

/* --- the locked Made for you ----------------------------------------------- */
.hm-locked{
  display:flex; flex-direction:column; gap:5px; width:100%; text-align:left;
  padding:var(--s4); border:1px solid var(--green-mid); border-radius:var(--r-card);
  background:linear-gradient(180deg,var(--green-soft),var(--paper) 80%);
  font:inherit; cursor:pointer;
}
.hm-locked b{ font-size:var(--t-body); font-weight:var(--w-bold); letter-spacing:-.02em }
.hm-locked small{ font-size:var(--t-label); color:var(--muted); line-height:1.45 }

/* --- the itinerary card ----------------------------------------------------- */
.plan-card .dc-photo{ position:relative }
.pc-badge{
  position:absolute; left:8px; top:8px; background:var(--green); color:#fff;
  font-size:var(--t-dense); font-weight:var(--w-bold); padding:4px 9px;
  border-radius:var(--r-pill);
}
.pc-stops{
  position:absolute; left:8px; bottom:8px; background:rgba(18,19,15,.82); color:#fff;
  font-size:var(--t-dense); font-weight:var(--w-med); padding:3px 9px;
  border-radius:var(--r-pill);
}
.pc-heart{ position:absolute; right:8px; top:8px; width:24px; height:24px;
  border-radius:50%; background:var(--ink); color:#fff; display:grid;
  place-items:center; font-size:var(--t-label) }

/* ==========================================================================
   The itinerary screen — the thing Pro sells.
   ========================================================================== */
.itin{ padding-top:2px }
.itin-top{ display:flex; align-items:center; gap:var(--s3); padding:2px 0 var(--s3) }
.itin-top b{ flex:1; font-size:var(--t-body); font-weight:var(--w-bold); letter-spacing:-.02em }
.itin-back,.itin-heart{
  width:44px; height:44px; flex:0 0 auto; border-radius:50%; border:1px solid var(--line);
  background:var(--paper); font-size:var(--t-card); color:var(--ink); cursor:pointer;
  display:grid; place-items:center;
}
.itin-heart.on{ background:var(--ink); border-color:var(--ink); color:#fff }
.itin-hero{
  height:132px; border-radius:var(--r-card); display:grid; place-items:center;
  font-size:46px; background:linear-gradient(140deg,#c9d9c2,#7d9c78);
}
.itin-head{ padding:var(--s3) 2px 0 }
.itin-head b{ display:block; font-size:var(--t-title); font-weight:var(--w-bold);
  letter-spacing:-.035em; line-height:1.1 }
.itin-head small{ display:block; font-size:var(--t-label); color:var(--muted); margin-top:4px }

.itin-tots{ display:flex; gap:var(--s2); margin-top:var(--s3) }
.itin-tot{ flex:1; padding:var(--s3) var(--s2); text-align:center;
  border:1px solid var(--line); border-radius:var(--r-control); background:var(--paper) }
.itin-tot b{ display:block; font-size:var(--t-body); font-weight:var(--w-bold);
  letter-spacing:-.02em }
.itin-tot small{ display:block; font-size:var(--t-dense); color:var(--muted);
  letter-spacing:.05em; text-transform:uppercase; margin-top:2px }
.itin-tot.won{ border-color:var(--green); background:var(--green-soft) }

/* One stop. The time is in the gutter so the column of times reads as a
   timeline without a rule down the middle pretending to be one. */
.itin-stop{ display:grid; grid-template-columns:46px 1fr; gap:var(--s2);
  padding-top:var(--s3) }
.itin-when{ font-size:var(--t-label); font-weight:var(--w-med); color:var(--muted);
  padding-top:14px; text-align:right; font-variant-numeric:tabular-nums }
.itin-box{ position:relative; padding:var(--s3); border:1px solid var(--line);
  border-radius:var(--r-control); background:var(--paper) }
.itin-box.swapped{ border-color:var(--green); background:var(--green-soft) }
.itin-box b{ display:block; font-size:var(--t-meta); font-weight:var(--w-med);
  padding-right:56px }
.itin-box small{ display:block; font-size:var(--t-label); color:var(--muted); margin-top:2px }
.itin-cost{ position:absolute; right:var(--s3); top:var(--s3); font-size:var(--t-label);
  font-weight:var(--w-bold); font-variant-numeric:tabular-nums }
.itin-swapt{ display:inline-block; margin-top:6px; font-size:var(--t-dense);
  font-weight:var(--w-bold); letter-spacing:.07em; text-transform:uppercase;
  color:var(--green) }
.itin-acts{ display:flex; gap:var(--s2); margin-top:4px }
.itin-acts .text-btn{ padding:var(--s2) 0; min-height:44px }

.itin-edits{ display:flex; gap:var(--s2); margin-top:var(--s4) }
.itin-edit{
  flex:1; min-height:48px; padding:var(--s3) var(--s2); border-radius:var(--r-control);
  border:1px solid var(--line); background:var(--paper); font:inherit;
  font-size:var(--t-label); font-weight:var(--w-bold); color:var(--ink); cursor:pointer;
}
.itin-edit.on{ background:var(--green-soft); border-color:var(--green); color:var(--green) }

.itin-send{
  display:flex; align-items:center; gap:var(--s3); margin-top:var(--s4);
  padding:var(--s4); border-radius:var(--r-card); background:var(--ink); color:#fff;
}
.itin-sendtx{ flex:1; min-width:0 }
.itin-sendtx b{ display:block; font-size:var(--t-meta); font-weight:var(--w-bold) }
.itin-sendtx small{ display:block; font-size:var(--t-label); color:#c9c6be; margin-top:2px }
.itin-send .primary{ flex:0 0 auto; background:#fff; color:var(--ink); border:0 }

@media (max-width:380px){
  .hm-sc i{ width:44px; height:44px; font-size:19px }
  .hm-dot{ width:52px }
  .hm-dot i{ width:44px; height:44px; font-size:19px }
}

/* One person, in the Free now rail. A card rather than a row because the rail
   is horizontal - twelve friends stacked was a 1,727px column with half of it
   under the tab bar, and the same twelve as a rail is one swipe. */
.fr-now{
  flex:0 0 84px; display:flex; flex-direction:column; align-items:center; gap:5px;
  padding:var(--s3) var(--s2); border:1px solid var(--line);
  border-radius:var(--r-card); background:var(--paper); font:inherit;
  text-align:center; cursor:pointer; min-height:44px;
}
.fr-now .avatar{ margin:0 }
.fr-now b{ font-size:var(--t-label); font-weight:var(--w-med); letter-spacing:-.01em }
.fr-now small{ font-size:var(--t-dense); color:var(--muted); line-height:1.25 }
/* A saved plan whose day has filled up. Grey rather than green, because the
   badge is reporting a fact that has changed since it was saved. */
.pc-badge.cold{ background:var(--gray-mid); color:var(--ink) }

/* Free / Busy on a day row. Both 44 tall: declining is the tap somebody makes
   while walking, and a small target is how you answer wrong by accident. */
.you-ans{ display:flex; gap:6px; flex:0 0 auto }
.you-ans .pill{ min-height:44px; display:inline-flex; align-items:center; border:0 }
.you-ans .pill.ghost{ background:transparent; color:var(--muted);
  border:1px solid var(--line) }
