/* CoHours V1 prototype -- shell + five destinations.
   Mobile first at 375px and 390px; desktop only caps the width.
   No raw hex here: every colour is a token from tokens.css.
   No url(): nothing external can enter through CSS. */

/* ===================================================================
   Shell
   =================================================================== */

.app-body {
  --app-frame-max: 430px;
  /* The phone status and home-indicator areas belong to the same surface as
     the app. A darker page backdrop made those safe areas look like empty
     bands on iPhone even though the content itself was correctly inset. */
  background: var(--paper);
  overflow-x: hidden;
}

@media (min-width: 640.01px) {
  .app-body { background: var(--paper-deep); }
}

@media (max-height: 600px) {
  .app-body { background: var(--paper); }
}

.app {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: var(--app-frame-max);
  margin: 0 auto;
  min-height: 100dvh;
  height: 100dvh;
  background: var(--paper);
  overflow: hidden;
}

@media (min-width: 460px) {
  .app { border-left: var(--rule) solid var(--line); border-right: var(--rule) solid var(--line); }
}

/* Modern Android phones can expose a CSS viewport wider than the old 430px
   reference frame. They are still phones, so fill that viewport instead of
   leaving decorative gutters. Tablet and desktop layouts keep their caps. */
@media (max-width: 640px), (max-height: 600px) {
  .app {
    max-width: none;
    border-left: 0;
    border-right: 0;
  }
}

/* A tablet is not a 430px phone floating in unused space. Keep the shell
   continuous across 640px, then meet the 728px desktop content column at the
   960px rail breakpoint without a shrink-then-jump discontinuity. */
@media (min-width: 640.01px) and (max-width: 959.99px) and (min-height: 601px) {
  .app-body:not(.room-body) {
    --app-frame-max: 720px;
    --gutter: clamp(18px, 3vw, 24px);
  }
}

.app-header {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  height: calc(var(--header-h) + var(--safe-top));
  padding: var(--safe-top) var(--gutter) 0;
  background: var(--paper);
  border-bottom: var(--rule) solid var(--line);
  z-index: 20;
}

.wordmark {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  min-height: var(--tap);
  font-family: var(--serif);
  font-size: 21px;
  letter-spacing: 0.01em;
  color: var(--ink);
  line-height: 1;
  padding: 0;
}
.brand-icon {
  display: block;
  width: 28px;
  height: 28px;
  flex: 0 0 28px;
  object-fit: cover;
  border-radius: 8px;
}
.brand-name { display: inline-block; }
.header-lead { display: flex; align-items: center; gap: 2px; min-width: 0; }

.header-title {
  font-family: var(--serif);
  font-size: 18px;
  color: var(--ink);
}

.header-actions {
  display: flex;
  align-items: center;
  gap: 2px;
}

.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap);
  height: var(--tap);
  border-radius: 999px;
  color: var(--ink);
  transition: background-color var(--press-duration) var(--motion-ease),
              transform var(--press-duration) var(--motion-ease);
}
.icon-btn:active { background: var(--ink-faint); transform: scale(0.94); }
.icon-btn[aria-pressed="true"] { background: var(--green-soft); color: var(--green); }

.inbox-button { position: relative; }
.inbox-badge {
  position: absolute;
  top: 2px;
  right: 1px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 2px solid var(--paper);
  border-radius: 999px;
  /* The count is tiny by necessity; use the AA-safe shu rather than the
     brighter decorative pigment so its white numerals remain crisp. */
  background: var(--cta-deep);
  color: white;
  font-size: 9px;
  font-weight: 700;
  line-height: 1;
}

.icon { width: 22px; height: 22px; }
.icon-sm { width: 16px; height: 16px; }
.icon-lg { width: 28px; height: 28px; }

.app-banner {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px var(--gutter);
  background: var(--green-soft);
  color: var(--ink-soft);
  font-size: 14px;
  border-bottom: var(--rule) solid var(--line);
}
.app-banner[data-tone="warn"] { background: var(--cta-soft); color: var(--cta-deep); }

.active-session-capsule {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  min-height: 56px;
  padding: 6px var(--gutter);
  background: var(--paper-raise);
  border-bottom: var(--rule) solid var(--line);
  animation: motion-paper-in var(--motion-duration) var(--motion-ease);
}
.active-session-capsule[hidden] { display: none; }

.active-session-capsule-state {
  display: flex;
  align-items: center;
  gap: 9px;
  min-width: 0;
}

.active-session-capsule-dot {
  width: 8px;
  height: 8px;
  flex: 0 0 8px;
  border-radius: 999px;
  background: var(--cta);
  box-shadow: 0 0 0 3px var(--cta-soft);
}
.active-session-capsule[data-session-state="break"] .active-session-capsule-dot {
  background: var(--green);
  box-shadow: 0 0 0 3px var(--green-soft);
}

.active-session-capsule-copy {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: 1.25;
}
.active-session-capsule-label {
  overflow: hidden;
  color: var(--ink);
  font-size: 13px;
  font-weight: 650;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.active-session-capsule-clock {
  color: var(--muted);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
}

.active-session-capsule-return {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--tap);
  flex: 0 0 auto;
  padding: 0 12px;
  border: var(--rule) solid var(--green-line);
  border-radius: 999px;
  background: var(--green-soft);
  color: var(--green);
  font-size: 13px;
  font-weight: 650;
  line-height: 1.2;
  transition: background-color var(--press-duration) var(--motion-ease),
              transform var(--press-duration) var(--motion-ease);
}
.active-session-capsule-return:active {
  background: var(--paper-deep);
  transform: scale(0.97);
}

.sessions-switch {
  flex: 0 0 auto;
  padding: 10px var(--gutter);
  border-bottom: var(--rule) solid var(--line);
  background: var(--paper);
}

.app-main {
  flex: 1 1 auto;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  padding-bottom: calc(var(--dock-h) + var(--safe-bottom) + 24px);
  /* Deliberately NOT smooth: returning to a tab must restore its position
     instantly. An animated glide back to where you were reads as the page
     moving under you, and it makes every restore feel slower than it is. */
  scroll-behavior: auto;
}

.panel {
  animation: motion-paper-in var(--motion-duration) var(--motion-ease);
}

.app-dock {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: stretch;
  height: calc(var(--dock-h) + var(--safe-bottom));
  padding-bottom: var(--safe-bottom);
  background: var(--paper);
  border-top: var(--rule) solid var(--line);
  z-index: 25;
}

.dock-item {
  position: relative;
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-height: var(--tap);
  color: var(--muted);
  transition: color var(--press-duration) var(--motion-ease);
}
.dock-item .icon { width: 21px; height: 21px; stroke-width: 1.7; }
.dock-label { font-size: 11px; letter-spacing: 0.01em; }
.dock-item.is-selected { color: var(--ink); }
.dock-item.is-selected .icon { stroke-width: 2.3; }
.dock-mark {
  position: absolute;
  top: 0;
  width: 22px;
  height: 2px;
  border-radius: 2px;
  background: transparent;
}
.dock-item.is-selected .dock-mark { background: var(--ink); }
.dock-item:active { transform: scale(0.96); }

/* ===================================================================
   Sequential washi surfaces -- rules and space, not stacked cards
   =================================================================== */

/* Density pass 2026-07-26: the owner noticed how much of this could be
   smaller. Padding and type came down together; every tap target still
   floors at --tap, so what shrank is whitespace and label size, never the
   thing your thumb has to hit. */
.section {
  padding: 14px var(--gutter) 16px;
  border-bottom: var(--rule) solid var(--line);
}
.section:last-child { border-bottom: 0; }
.section-flush { padding-left: 0; padding-right: 0; }
.section-flush .section-head,
.section-flush .section-note { padding-left: var(--gutter); padding-right: var(--gutter); }

.section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 8px;
}

.section-title { font-size: 16px; color: var(--ink); }
.section-note { font-size: 16px; line-height: 1.6; color: var(--muted); margin-bottom: 10px; }

.eyebrow {
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 6px;
}

.headline { font-size: 23px; line-height: 1.22; color: var(--ink); }
.lede { font-size: 16px; line-height: 1.6; color: var(--ink-soft); margin-top: 6px; }
.muted { color: var(--muted); }
.small { font-size: 14px; }

.text-action {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  min-height: var(--tap);
  min-width: var(--tap);
  justify-content: center;
  padding: 4px 2px;
  font-size: 14px;
  color: var(--green);
}
.text-action:active { opacity: 0.6; }

.rule { height: var(--rule); background: var(--line); border: 0; margin: 0; }

/* ---- rows ---- */

.row {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: var(--tap);
  padding: 10px var(--gutter);
  text-align: left;
  color: var(--ink);
  border-bottom: var(--rule) solid var(--line);
  background: transparent;
}
.row:last-child { border-bottom: 0; }
.row-button { transition: background-color var(--press-duration) var(--motion-ease); }
.row-button:active { background: var(--ink-faint); }
.row-button[disabled] { color: var(--muted); cursor: default; }

.row-lead {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  color: var(--ink-soft);
}
.row-body { flex: 1 1 auto; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.row-title { font-size: 16px; line-height: 1.35; }
.row-meta { font-size: 13px; color: var(--muted); }
.row-trail { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 6px; color: var(--muted); }
.row-value { font-size: 14px; color: var(--ink-soft); }
.row-chevron { color: var(--muted); }
/* The chosen mark on a pick-one list. Present on every row and only ever
   hidden, so marking a different row cannot resize the one under the thumb. */
.row-tick { display: inline-flex; align-items: center; color: var(--green); }
.row.is-selected { background: var(--green-soft); }
.row-option { min-height: var(--sheet-row-h); }
.row-quiet .row-title { color: var(--ink-soft); }
.row-done .row-title { color: var(--muted); text-decoration: line-through; }

.option-list { display: flex; flex-direction: column; }

/* ---- buttons ---- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: var(--tap);
  padding: 11px 18px;
  border-radius: 12px;
  font-size: 16px;
  line-height: 1.2;
  border: var(--rule) solid transparent;
  transition: transform var(--press-duration) var(--motion-ease),
              background-color var(--press-duration) var(--motion-ease),
              opacity var(--press-duration) var(--motion-ease);
}
.btn:active { transform: scale(0.975); }
.btn[disabled] { opacity: 0.45; cursor: default; transform: none; }

/* --cta on white measures 3.75:1 -- below the 4.5:1 floor for 16px text.
   --cta-deep is the same shu family and measures 5.26:1, so the commit
   button uses it and presses to a token mix rather than a new colour. */
.btn-primary { background: var(--cta-deep); color: var(--white); }
.btn-primary:active { background: color-mix(in srgb, var(--cta-deep) 86%, var(--ink)); }
.btn-md { min-height: var(--tap); font-size: 16px; }
.btn-quiet { background: var(--white); color: var(--ink); border-color: var(--line); }
.btn-quiet:active { background: var(--green-soft); }
.btn-ghost { color: var(--ink-soft); }
.btn-ghost:active { background: var(--ink-faint); }
.btn-green { background: var(--green); color: var(--white); }
.btn-danger { color: var(--danger); border-color: var(--line); background: var(--white); }
/* Second-rank but still a real move: outlined in ai so it is visibly not
   the primary and visibly not a link either. Filling it would give the
   screen two things claiming to be the next step. */
.btn-secondary { background: var(--ai-wash); color: var(--ai-ink); border-color: var(--ai-line); }
.btn-secondary:active { background: color-mix(in srgb, var(--ai-wash) 84%, var(--ai)); }
.btn-lg { min-height: 52px; font-size: 17px; padding: 14px 22px; }
.btn-sm { min-height: var(--tap); font-size: 14px; padding: 8px 14px; }
.btn-block { width: 100%; }
.btn.is-busy { opacity: 0.7; }

/* Icon-only, and round, so it cannot be mistaken for one of the labelled
   rectangles above. Only ever used where the glyph is unambiguous AND the
   action is reversible; aria-label carries the name for screen readers and
   the long-press tooltip carries it for everyone else. */
.btn-icon {
  width: var(--tap);
  height: var(--tap);
  min-height: var(--tap);
  padding: 0;
  border-radius: 999px;
  flex: 0 0 auto !important;
  background: var(--white);
  border-color: var(--line);
  color: var(--ink-soft);
}
.btn-icon:active { background: var(--ink-faint); }
.btn-icon .icon { width: 20px; height: 20px; }

.button-row { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
.button-row .btn { flex: 1 1 auto; }

.primary-bar {
  position: sticky;
  /* Resting on the very bottom is correct for every page WITHOUT a dock
     (room.html, and sheets). The three shell destinations that do have one
     raise their own bar by --dock-h where they build it, because a selector
     cannot tell the two pages apart: room.html uses the same .app-main and
     .panel class names the shell does. */
  bottom: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px var(--gutter) calc(12px + var(--safe-bottom));
  background: var(--paper);
  border-top: var(--rule) solid var(--line);
  z-index: 10;
}

/* ---- chips + segmented ---- */

.chip-row {
  display: flex;
  gap: 8px;
  padding: 2px var(--gutter) 2px;
  overflow-x: auto;
  scrollbar-width: none;
}
.chip-row::-webkit-scrollbar { display: none; }

.chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  flex: 0 0 auto;
  min-height: var(--tap);
  padding: 7px 12px;
  border-radius: 999px;
  border: var(--rule) solid var(--line);
  background: var(--white);
  color: var(--ink-soft);
  font-size: 13px;
  transition: background-color var(--press-duration) var(--motion-ease);
}
.chip:active { transform: scale(0.97); }
.chip.is-selected { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.chip[disabled] { opacity: 0.45; }

.segmented {
  display: flex;
  gap: 4px;
  padding: 3px;
  border-radius: 12px;
  background: var(--paper-deep);
}
.segment {
  flex: 1 1 0;
  min-height: var(--tap);
  border-radius: 9px;
  font-size: 15px;
  color: var(--ink-soft);
  transition: background-color var(--press-duration) var(--motion-ease);
}
.segment.is-selected { background: var(--paper); color: var(--ink); box-shadow: var(--shadow-soft); }
.segment:active { opacity: 0.75; }

/* ---- toggles ---- */

.toggle {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: var(--tap);
  padding: 10px var(--gutter);
  border-bottom: var(--rule) solid var(--line);
  cursor: pointer;
}
.toggle:last-child { border-bottom: 0; }
.toggle-body { flex: 1 1 auto; display: flex; flex-direction: column; gap: 3px; }
.toggle-label { font-size: 16px; }
.toggle-hint { font-size: 13px; color: var(--muted); }
.toggle-input {
  position: absolute;
  width: 1px; height: 1px;
  opacity: 0;
  pointer-events: none;
}
.toggle-track {
  flex: 0 0 auto;
  position: relative;
  width: 46px;
  height: 28px;
  border-radius: 999px;
  background: var(--paper-deep);
  border: var(--rule) solid var(--line);
  transition: background-color var(--motion-duration) var(--motion-ease);
}
.toggle-knob {
  position: absolute;
  top: 2px; left: 2px;
  width: 22px; height: 22px;
  border-radius: 999px;
  background: var(--white);
  box-shadow: var(--shadow-soft);
  transition: transform var(--motion-duration) var(--motion-ease);
}
.toggle-input:checked ~ .toggle-track { background: var(--green); border-color: var(--green); }
.toggle-input:checked ~ .toggle-track .toggle-knob { transform: translateX(18px); }
.toggle-input:focus-visible ~ .toggle-track { outline: 2px solid var(--green); outline-offset: 3px; }

.lab-tag {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  align-self: flex-start;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--lab-wash);
  color: var(--green);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.lab-banner {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 var(--gutter) 12px;
  padding: 8px 12px;
  border-radius: 10px;
  background: var(--lab-wash);
  color: var(--ink-soft);
  font-size: 13px;
}

/* ---- notes ---- */

.note {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  font-size: 16px;
  color: var(--muted);
  line-height: 1.5;
}
.note .icon-sm { flex: 0 0 auto; margin-top: 3px; }
.note-warn { color: var(--cta-deep); }
.note-quiet { color: var(--muted); }
.note-strong { color: var(--ink-soft); }

/* ---- stats + charts ---- */

.stat-row {
  display: flex;
  gap: 14px;
  align-items: flex-end;
}
.stat { flex: 1 1 0; min-width: 0; }
.stat-value { font-family: var(--serif); font-size: 27px; line-height: 1.05; color: var(--ink); }
.stat-unit { font-size: 14px; font-family: var(--sans); color: var(--muted); margin-left: 3px; }
.stat-label { font-size: 13px; color: var(--muted); margin-top: 2px; }

.bars {
  width: 100%;
  /* Eight weekly columns cannot be 44px wide inside a 343px content box, so
     the chart scrolls sideways instead of shrinking its targets below the
     floor. The scroll lives here, not on the plot, so the aim line still
     spans the whole series. */
  overflow-x: auto;
  scrollbar-width: none;
}
.bars::-webkit-scrollbar { display: none; }
.bars-plot {
  position: relative;
  display: flex;
  align-items: flex-end;
  gap: 8px;
  height: 132px;
  padding-top: 8px;
  min-width: min-content;
}
.bar-col {
  position: relative;
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: center;
  height: 100%;
  /* Must come last, and must not be reset to 0: this is what stops eight
     weekly columns from shrinking their tap targets below the 44px floor.
     The chart scrolls instead. */
  min-width: var(--tap);
}
.bar-fill {
  width: 100%;
  max-width: 26px;
  border-radius: 3px 3px 0 0;
  background: var(--green);
  transition: height var(--motion-duration) var(--motion-ease);
}
.bar-col.is-current .bar-fill { background: var(--ink); }
.bar-col.is-future .bar-fill { background: var(--line); }
/* A day with no study is drawn as a neutral washi mark -- not a green
   sliver pretending to be progress, and never red. */
.bar-col.is-zero .bar-fill { background: var(--line); }
.bar-label {
  margin-top: 6px;
  font-size: 11px;
  color: var(--muted);
  white-space: nowrap;
}
.bar-hit {
  position: absolute;
  inset: 0;
  min-height: var(--tap);
  border-radius: 6px;
}
.bar-hit:active { background: var(--ink-faint); }
/* The aim line used to be explained by a sentence under the chart ("the
   dotted line is the aim you set yourself"). With the sentence gone the
   line has to say it on its own, so it takes the yamabuki that means "aim"
   everywhere else in the app and its label became a tag sitting on the
   line rather than 11px of green floating near it. */
.bars-aim {
  position: absolute;
  left: 0; right: 0;
  height: 0;
  border-top: 1.5px dashed var(--yamabuki-mark);
  pointer-events: none;
  z-index: 1;
}
.bars-aim-label {
  position: absolute;
  right: 0;
  top: -10px;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--yamabuki-wash);
  color: var(--yamabuki-ink);
  font-size: 12px;
  line-height: 1.5;
  white-space: nowrap;
}

.heat {
  display: grid;
  grid-template-rows: repeat(7, 1fr);
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: 3px;
}
.heat-cell {
  aspect-ratio: 1;
  border-radius: 2px;
  background: var(--paper-deep);
}
.heat-cell[data-level="1"] { background: color-mix(in srgb, var(--green) 22%, var(--paper)); }
.heat-cell[data-level="2"] { background: color-mix(in srgb, var(--green) 42%, var(--paper)); }
.heat-cell[data-level="3"] { background: color-mix(in srgb, var(--green) 66%, var(--paper)); }
.heat-cell[data-level="4"] { background: var(--green); }

.heat-legend {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 10px;
  font-size: 12px;
  color: var(--muted);
}

/* .meter-track / .meter-fill live with the other state visuals further down. */
.meter-caption { margin-top: 6px; font-size: 13px; color: var(--muted); }

/* ---- forms ---- */

.field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 16px; }
.field-label { display: flex; justify-content: space-between; gap: 8px; font-size: 14px; color: var(--ink-soft); }
.field-optional { color: var(--muted); font-size: 13px; }
.field-hint { font-size: 13px; color: var(--muted); }

.input {
  width: 100%;
  min-height: var(--tap);
  padding: 11px 12px;
  font-size: 16px;
  color: var(--ink);
  background: var(--white);
  border: var(--rule) solid var(--line);
  border-radius: 10px;
  appearance: none;
}
.input:focus-visible { outline: 2px solid var(--green); outline-offset: 1px; }
textarea.input { line-height: 1.5; resize: vertical; }

.field-inline { display: flex; gap: 10px; }
.field-inline .field { flex: 1 1 0; }

.scale { margin-bottom: 16px; }
.scale-label { font-size: 14px; color: var(--ink-soft); margin-bottom: 8px; }
.scale-track { display: flex; gap: 8px; }
.scale-dot {
  flex: 1 1 0;
  min-height: var(--tap);
  border-radius: 10px;
  border: var(--rule) solid var(--line);
  background: var(--white);
  color: var(--ink-soft);
  font-size: 16px;
}
.scale-dot.is-selected { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.scale-dot:active { transform: scale(0.96); }
.scale-ends { display: flex; justify-content: space-between; margin-top: 6px; font-size: 12px; color: var(--muted); }

.weekday-picker {
  display: grid;
  grid-template-columns: repeat(7, minmax(var(--tap), 1fr));
  gap: 3px;
  overflow-x: auto;
  scrollbar-width: none;
}
.weekday-picker::-webkit-scrollbar { display: none; }
.weekday {
  min-width: var(--tap);
  min-height: var(--tap);
  border-radius: 10px;
  border: var(--rule) solid var(--line);
  background: var(--white);
  color: var(--ink-soft);
  font-size: 14px;
}
.weekday.is-selected { background: var(--green); color: var(--white); border-color: var(--green); }

/* ---- avatars + seats ---- */

.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 999px;
  border: var(--rule) solid var(--line);
  background: var(--white);
  color: var(--ink-soft);
  flex: 0 0 auto;
}
.seat-icon { width: 22px; height: 22px; }
.avatar .seat-icon { width: 22px; height: 22px; }
.avatar-lg { width: 60px; height: 60px; }
.avatar-lg .seat-icon { width: 34px; height: 34px; }
.avatar-sm { width: 28px; height: 28px; }
.avatar-sm .seat-icon { width: 18px; height: 18px; }
.avatar-green { background: var(--green-soft); color: var(--green); border-color: var(--green-soft); }

/* .seat-strip lives with the other state visuals further down. */

/* ---- shortcut tray (Focus only) ---- */

.shortcut-tray {
  display: flex;
  gap: 14px;
  padding: 10px var(--gutter) 12px;
  overflow-x: auto;
  scrollbar-width: none;
  border-bottom: var(--rule) solid var(--line);
}
.shortcut-tray::-webkit-scrollbar { display: none; }

.shortcut {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  width: 64px;
  color: var(--ink-soft);
}
.shortcut-ring {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--shortcut-size);
  height: var(--shortcut-size);
  border-radius: 999px;
  border: 1.5px solid var(--line);
  background: var(--white);
  transition: transform var(--press-duration) var(--motion-ease);
}
.shortcut:active .shortcut-ring { transform: scale(0.94); }
.shortcut-ring .icon { width: 22px; height: 22px; }
.shortcut-live .shortcut-ring { border-color: var(--cta); color: var(--cta); }
.shortcut-buddy .shortcut-ring { border-color: var(--green); color: var(--green); }
.shortcut-aim .shortcut-ring { border-color: transparent; background: transparent; padding: 0; }
/* The aim tile IS the ring, so the tile's own border would double it. */
.shortcut-aim .ring { width: var(--shortcut-size); height: var(--shortcut-size); }
.shortcut-aim .ring-label { font-size: 13px; }
/* A material's subject colours its ring, so the tray is scannable by hue
   before any of the labels underneath are read. */
.shortcut-subject .shortcut-ring { border-color: var(--subject-1); color: var(--subject-1); }

/* A room that is running right now gets a shu edge on the row. The pill and
   its beat still say so too -- colour is never the only signal. */
.row[data-phase="live"] { box-shadow: inset 3px 0 0 var(--cta); }

/* In a timetable the clock is the thing being scanned, so it gets the
   serif and the size and the room name drops to meta. Reading down the
   left edge should be reading times, not room names. */
.row-room .row-title {
  font-family: var(--serif);
  font-size: 19px;
  line-height: 1.15;
  font-variant-numeric: tabular-nums;
}
.row-room .row-lead { display: none; }

.shortcut-label {
  font-size: 12px;
  line-height: 1.25;
  text-align: center;
  color: var(--ink-soft);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ---- feature blocks used by the destinations ---- */

.hero {
  padding: 16px var(--gutter) 18px;
  border-bottom: var(--rule) solid var(--line);
}
.hero-time { font-family: var(--serif); font-size: 30px; line-height: 1.05; color: var(--ink); }
.hero-meta { margin-top: 7px; display: flex; flex-wrap: wrap; align-items: center; gap: 5px 10px; font-size: 13px; color: var(--muted); }
.hero-actions { margin-top: 13px; display: flex; flex-direction: column; gap: 8px; }

.fact {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 13px;
  color: var(--ink-soft);
}
.fact-green { color: var(--green); }
/* --cta-deep, not --cta: 4.66:1 on paper instead of 3.75:1. */
.fact-live { color: var(--cta-deep); }

.dot-live {
  width: 7px; height: 7px;
  border-radius: 999px;
  background: var(--cta);
  flex: 0 0 auto;
}
.dot-quiet { width: 7px; height: 7px; border-radius: 999px; background: var(--green); flex: 0 0 auto; }

/* ===================================================================
   State made visible -- the parts that used to be sentences
   =================================================================== */

/* ---- live: a beat, not the word "進行中" -----------------------------
   The ring is a separate ::after so the dot itself never changes size;
   a dot that grows and shrinks inside a text line drags the baseline. */
.dot-live { position: relative; }
.dot-live::after {
  content: '';
  position: absolute;
  inset: -3px;
  border-radius: 999px;
  border: 1.5px solid var(--cta);
  opacity: 0;
  animation: live-beat 2.4s var(--motion-ease) infinite;
}
@keyframes live-beat {
  0%   { transform: scale(0.7); opacity: 0.75; }
  70%  { transform: scale(1.5); opacity: 0; }
  100% { transform: scale(1.5); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .dot-live::after { animation: none; opacity: 0.55; transform: none; }
}
body.reduced-motion .dot-live::after { animation: none !important; opacity: 0.55; transform: none; }

/* ---- state pills: the shape and colour say it before the word does ---- */
.pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 12px;
  line-height: 1.5;
  white-space: nowrap;
  flex: 0 0 auto;
}
.pill .icon-sm { width: 13px; height: 13px; }
.pill-live { background: var(--cta-soft); color: var(--cta-deep); }
.pill-mine { background: var(--green-soft); color: var(--green); }
.pill-plan { background: var(--ai-wash); color: var(--ai-ink); }
.pill-aim { background: var(--yamabuki-wash); color: var(--yamabuki-ink); }
.pill-past { background: var(--paper-deep); color: var(--ink-soft); }

/* ---- seats: the people, not the count ------------------------------
   Each reserved seat is one glyph. No empty chairs are drawn, because the
   fixture has no capacity and a half-full room would be invented. */
.seat-strip { display: flex; align-items: center; flex: 0 0 auto; }
.seat-strip .avatar + .avatar { margin-left: -8px; }
.seat-strip .avatar {
  box-shadow: 0 0 0 2px var(--paper);
  transition: transform var(--press-duration) var(--motion-ease);
}
.seat-strip-mine { border-color: var(--green) !important; }
.seat-strip-more {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 28px; height: 28px;
  margin-left: -8px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--paper-deep);
  box-shadow: 0 0 0 2px var(--paper);
  color: var(--ink-soft);
  font-size: 12px;
  flex: 0 0 auto;
}
.seat-strip-empty {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  color: var(--muted);
  font-size: 13px;
}
.seat-strip-empty .seat-open {
  width: 22px; height: 22px;
  border-radius: 999px;
  border: 1px dashed var(--line);
  flex: 0 0 auto;
}

/* ---- meters: a position you can see at arm's length ----------------- */
/* The track is darker than --paper-deep on purpose. At --paper-deep the
   empty part of the bar is nearly the page colour, so the bar reads as a
   line of unknown length instead of a fraction of a known whole. */
.meter-track {
  position: relative;
  height: 8px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--paper) 78%, var(--ink) 22%);
  overflow: hidden;
}
.meter-fill { display: block; height: 100%; background: var(--green); border-radius: 999px; }
.meter-sm { height: 5px; }
.meter-ai .meter-fill { background: var(--ai); }
.meter-aim .meter-fill { background: var(--yamabuki-mark); }
.meter-done .meter-fill { background: var(--muted); }

/* The number sits ON the same line as the bar, not in a sentence above it. */
.meter-line { display: flex; align-items: center; gap: 10px; }
.meter-line .meter-track { flex: 1 1 auto; min-width: 60px; }
.meter-line-value {
  flex: 0 0 auto;
  font-family: var(--serif);
  font-size: 15px;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.meter-line-value span { font-family: var(--sans); font-size: 12px; color: var(--muted); margin-left: 1px; }

/* ---- subject colour: a material is known before its title is read ---- */
.subject-bar {
  width: 3px;
  align-self: stretch;
  min-height: 24px;
  border-radius: 999px;
  background: var(--subject-1);
  flex: 0 0 auto;
}
.subject-dot { width: 8px; height: 8px; border-radius: 999px; background: var(--subject-1); flex: 0 0 auto; }
[data-subject="2"] { --subject-1: var(--subject-2); }
[data-subject="3"] { --subject-1: var(--subject-3); }
[data-subject="4"] { --subject-1: var(--subject-4); }

/* ---- ring: the week's aim as an arc, not "10 h のうち 6.2 h" --------- */
.ring { position: relative; flex: 0 0 auto; width: 56px; height: 56px; }
.ring svg { width: 100%; height: 100%; transform: rotate(-90deg); overflow: visible; }
.ring circle { fill: none; stroke-width: 5; stroke-linecap: round; }
.ring-track { stroke: color-mix(in srgb, var(--paper) 78%, var(--ink) 22%); }
.ring-fill { stroke: var(--yamabuki-mark); transition: stroke-dashoffset var(--motion-duration) var(--motion-ease); }
.ring-label {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--serif);
  font-size: 15px;
  color: var(--ink);
}

.kv { display: flex; justify-content: space-between; gap: 12px; padding: 9px 0; font-size: 15px; }
.kv + .kv { border-top: var(--rule) solid var(--line); }
.kv-key { color: var(--muted); }
.kv-value { color: var(--ink); text-align: right; }

.timeline { display: flex; flex-direction: column; }
.timeline-item {
  display: flex;
  gap: 12px;
  padding: 10px var(--gutter);
  border-bottom: var(--rule) solid var(--line);
}
.timeline-time { flex: 0 0 56px; font-size: 13px; color: var(--muted); }
.timeline-body { flex: 1 1 auto; min-width: 0; }
.timeline-title { font-size: 15px; }
.timeline-meta { font-size: 13px; color: var(--muted); margin-top: 2px; }

.progress-line {
  display: flex;
  align-items: baseline;
  gap: 6px;
  font-size: 15px;
  color: var(--ink-soft);
}
.progress-line strong { font-family: var(--serif); font-size: 20px; color: var(--ink); }

.milestone {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px var(--gutter);
  background: var(--green-soft);
  border-radius: 12px;
  margin: 0 var(--gutter);
  color: var(--ink-soft);
  font-size: 14px;
}

.share-card {
  border: var(--rule) solid var(--line);
  border-radius: 14px;
  background: var(--white);
  padding: 22px 20px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.share-card-mark { font-family: var(--serif); font-size: 13px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--muted); }
.share-card-value { font-family: var(--serif); font-size: 40px; line-height: 1; color: var(--ink); }
.share-card-line { font-size: 16px; color: var(--ink-soft); }

.pill-list { display: flex; flex-wrap: wrap; gap: 8px; }

/* The quiet feed: pale cards in time order. No counters, no controls on the
   card itself -- a card is something to glance at, not something to work. */
.feed-list { display: flex; flex-direction: column; gap: 8px; padding: 0 var(--gutter); }
.feed-card {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  padding: 10px 12px;
  background: var(--paper-raise);
  border: var(--rule) solid var(--line);
  border-radius: 12px;
}
.feed-card-mine { border-color: var(--green-line); }
.feed-body { flex: 1 1 auto; min-width: 0; }
.feed-head { display: flex; align-items: baseline; gap: 8px; }
.feed-name { font-size: 14px; font-weight: 600; color: var(--ink); }
.feed-tag { font-size: 12px; color: var(--muted); }
.feed-time { font-size: 12px; color: var(--muted); margin-left: auto; white-space: nowrap; }
.feed-text { font-size: 16px; color: var(--ink-soft); margin-top: 2px; overflow-wrap: anywhere; }
.feed-scores { font-size: 13px; color: var(--muted); margin-top: 2px; }

.plan-tabs, .progress-tabs { padding: 12px var(--gutter) 4px; }

.detail-head {
  padding: 18px var(--gutter);
  border-bottom: var(--rule) solid var(--line);
}

/* ---- whole-screen states ---- */

.state-block {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding: 44px var(--gutter) 40px;
  text-align: center;
}
.state-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 52px; height: 52px;
  border-radius: 999px;
  border: var(--rule) solid var(--line);
  color: var(--muted);
  margin-bottom: 4px;
}
.state-title { font-size: 20px; }
.state-body { font-size: 16px; line-height: 1.6; color: var(--muted); max-width: 30ch; }
.state-block .btn { margin-top: 8px; min-width: 190px; }
.state-error .state-mark { color: var(--cta-deep); border-color: var(--cta-line); }

.state-loading { align-items: stretch; text-align: left; }
.state-loading-label { font-size: 15px; color: var(--muted); margin-bottom: 6px; }
.skeleton {
  display: block;
  height: 16px;
  border-radius: 6px;
  background: var(--paper-deep);
  margin-bottom: 12px;
  animation: skeleton-pulse 1.4s ease-in-out infinite;
}
.skeleton:nth-child(3) { width: 72%; }
.skeleton:nth-child(4) { width: 88%; }
.skeleton:nth-child(5) { width: 60%; }
@keyframes skeleton-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.55; }
}

/* ---- live region + toast ---- */

.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.toast-host {
  position: fixed;
  left: 50%;
  bottom: calc(var(--dock-h) + var(--safe-bottom) + 14px);
  transform: translateX(-50%);
  width: min(100%, 398px);
  padding: 0 var(--gutter);
  z-index: 60;
  pointer-events: none;
}

/* On a screen that pins a main-button band, the band -- not the dock -- is
   what the toast has to clear: Focus and the lobby both stand --primary-bar-h
   tall directly above the dock, so a toast measured from the dock landed
   inside the band and covered the button that had just been pressed.
   A literal cannot answer it, because the five panels do not all carry the
   same band: Focus and the lobby pin exactly --primary-bar-h, while Plan,
   Progress and Me use a shorter sticky one that is not always at the bottom.
   Guessing high floats the toast over the band on those screens instead of
   under it. So app.js measures the band of the panel on screen and writes the
   distance from the bottom of the window to its top edge into
   --shell-bar-top, the same answer room.html has had since C (room.css:29).
   max() keeps the dock rule as the floor, so a screen with no band never
   floats for no reason -- and --dock-h is 0px from 960px up, so the one
   expression holds at 390x844 and at 1280x800 alike. */
.app-body:not(.room-body) .toast-host {
  bottom: max(
    calc(var(--dock-h) + var(--safe-bottom) + 14px),
    calc(var(--shell-bar-top, 0px) + 12px)
  );
}
.toast {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 11px 14px;
  border-radius: 12px;
  background: var(--ink);
  color: var(--paper);
  font-size: 14px;
  box-shadow: var(--shadow-soft);
  animation: motion-paper-in var(--motion-duration) var(--motion-ease);
}
.toast-warn { background: var(--danger); }

/* ---- bottom sheet ---- */

.sheet-layer {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: flex;
  align-items: flex-end;
  justify-content: center;
}
.sheet-scrim {
  position: absolute;
  inset: 0;
  background: var(--scrim);
  animation: motion-fade-in var(--motion-duration) var(--motion-ease);
}
.sheet {
  position: relative;
  width: min(100%, 430px);
  max-height: 88dvh;
  display: flex;
  flex-direction: column;
  background: var(--paper);
  border-radius: var(--sheet-radius) var(--sheet-radius) 0 0;
  padding-bottom: var(--safe-bottom);
  animation: motion-sheet-in var(--motion-duration) var(--motion-ease);
  will-change: transform;
}

@media (max-width: 640px), (max-height: 600px) {
  .sheet {
    width: 100%;
    max-width: none;
  }
}
.sheet-drag-handle {
  display: grid;
  place-items: center;
  min-height: var(--tap);
  flex: 0 0 auto;
  cursor: grab;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
}
.sheet.is-dragging .sheet-drag-handle { cursor: grabbing; }
.sheet-grip {
  width: 38px; height: 4px;
  border-radius: 999px;
  background: var(--line);
}
.sheet-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
  padding: 8px var(--gutter) 10px;
  border-bottom: var(--rule) solid var(--line);
  flex: 0 0 auto;
}
.sheet-heading { flex: 1 1 auto; min-width: 0; }
.sheet-eyebrow { font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--muted); }
.sheet-title { font-size: 20px; margin-top: 2px; }
.sheet-close { flex: 0 0 auto; margin-top: -4px; }
.sheet-body {
  flex: 1 1 auto;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 14px 0 18px;
}
.sheet-body > .field,
.sheet-body > .scale,
.sheet-body > .note,
.sheet-body > .button-row,
.sheet-body > .chip-row,
.sheet-body > .segmented,
.sheet-body > .share-card,
.sheet-body > p,
.sheet-body > h3 { margin-left: var(--gutter); margin-right: var(--gutter); }
.sheet-body > .chip-row { padding-left: 0; padding-right: 0; }
/* A repaintable wrapper inside a sheet still needs the page gutter; only
   direct children get it above. */
.sheet-block { padding: 0 var(--gutter); }
.sheet-block > .chip-row { padding-left: 0; padding-right: 0; }

.inbox-list { display: grid; gap: 10px; padding: 0 var(--gutter) 18px; }
.inbox-card {
  padding: 14px;
  border: var(--rule) solid var(--line);
  border-radius: 14px;
  background: var(--paper);
}
.inbox-card.is-unread { border-color: var(--cta); background: var(--cta-soft); }
.inbox-card-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.inbox-card-type,
.inbox-card-unread {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.inbox-card-type-official { color: var(--cta-deep); }
.inbox-card-type-friend { color: var(--green); }
.inbox-card-unread { color: var(--cta-deep); }
.inbox-card-title { margin-top: 8px; font-size: 17px; line-height: 1.3; }
.inbox-card-body { margin-top: 5px; color: var(--ink-soft); font-size: 16px; line-height: 1.6; }
.inbox-card .btn { margin-top: 10px; }
.pwa-guide-icon-wrap { display: flex; justify-content: center; padding: 4px var(--gutter) 8px; }
.pwa-guide-icon { display: block; width: 90px; height: 90px; border-radius: 20px; }

body.has-sheet { overflow: hidden; }

/* ===================================================================
   Short viewport (on-screen keyboard): keep the primary action reachable
   =================================================================== */

@media (max-height: 560px) {
  .app-header { height: calc(44px + var(--safe-top)); }
  .hero { padding-top: 14px; padding-bottom: 16px; }
  .hero-time { font-size: 26px; }
  .sheet { max-height: 94dvh; }
  .sheet-body { padding-top: 8px; }
  .shortcut-tray { padding-top: 10px; padding-bottom: 12px; }
  .state-block { padding-top: 26px; padding-bottom: 24px; }
}

@media (prefers-reduced-motion: reduce) {
  .skeleton { animation: none; }
  .active-session-capsule { animation: none; }
}

/* ===================================================================
   Desktop shell (960px and up) -- the dock becomes a left rail
   ===================================================================

   One shell, two shapes. Below 960px NOTHING in this section applies and
   the phone layout is untouched: the bottom dock, the main button pinned
   above it, the header +, the 430px column.

   At 960px and up (UX-IA handoff §9, §13 "PC"):
     - the five destinations move into a left rail, with Create above them
       opening the same central sheet the header + opens on a phone
     - Focus splits 65 / 35 -- doing on the left (NOW and the one main
       button), reading on the right (next focus time, this week)
     - Rooms splits the same way -- the quiet feed on the left, who is here
       and the next focus time on the right
     - Plan, Progress and Me stay ONE column: the panel keeps the full
       content width and caps its line length with padding instead, so the
       shell still fills the screen without forcing a second column

   The DOM is never reordered for any of this. Reading order and tab order
   stay "action, progress, detail" exactly as they are on the phone; only
   the grid placement differs, so a keyboard and a screen reader walk the
   same path on both shapes.

   room.html shares this stylesheet but not this shell -- it has no dock and
   its own immersive column -- so every rule below is scoped away from
   .room-body rather than applied to .app in general.
   =================================================================== */

/* The wrapper that holds Create + the dock. It generates no box of its own
   until the rail exists, so on a phone the dock is still a direct child of
   .app and still pins itself against it exactly as before. */
.app-rail { display: contents; }

/* On a phone the door to the create sheet is the header +. The rail button
   is the same door for the desktop shape, so only one is ever laid out. */
.rail-create { display: none; }

@media (min-width: 960px) {
  .app-body:not(.room-body) {
    /* Column widths. --pc-aside is the same 35% the panel grids below give
       to their second column, and the pinned main button subtracts it to
       land exactly on the left column's right edge. Change one, change both. */
    --rail-w: 232px;
    --pc-aside: 0.35;
    /* One column, capped for line length. Not a max-width on the panel: the
       panel keeps the whole content width (over 1000px at 1280) and only its
       text is inset, so the shell still fills the screen. */
    --pc-measure: 840px;
    /* There is no dock at the bottom any more. Everything that reserved room
       for it -- the scroller's padding, the pinned bars, the toast, the
       scroll margins in focus-os.css and lobby.css -- reads this one variable
       and collapses on its own. */
    --dock-h: 0px;
    /* 16px is a 390px-wide column's margin. At 680px and 1040px it reads as
       text glued to the edge, so the one gutter every screen already shares
       steps up rather than each screen inventing its own desktop padding. */
    --gutter: 24px;
  }

  .app-body:not(.room-body) .app {
    display: grid;
    grid-template-columns: var(--rail-w) minmax(0, 1fr);
    grid-template-rows: auto auto auto auto minmax(0, 1fr);
    max-width: 1440px;
    /* The 460px rule draws side rules on a phone-width column standing in a
       wide window. At this width the shell IS the window. */
    border-left: 0;
    border-right: 0;
  }

  .app-body:not(.room-body) .app-header { grid-column: 2; grid-row: 1; }
  .app-body:not(.room-body) .app-banner { grid-column: 2; grid-row: 2; }
  .app-body:not(.room-body) .active-session-capsule { grid-column: 2; grid-row: 3; }
  .app-body:not(.room-body) .sessions-switch { grid-column: 2; grid-row: 4; }
  /* No dock to clear, so no bottom padding on the scroller -- and it has to
     go to zero rather than shrink: padding on a scroll container holds the
     sticky bars that many screens use a padding's height off the bottom
     edge, and the rows still scrolling past show through the gap. Each
     panel carries its own end-of-content breathing room instead. */
  .app-body:not(.room-body) .app-main {
    grid-column: 2;
    grid-row: 5;
    padding-bottom: 0;
  }

  /* ---- the rail ---- */

  .app-body:not(.room-body) .app-rail {
    display: flex;
    flex-direction: column;
    grid-column: 1;
    grid-row: 1 / -1;
    min-width: 0;
    /* 4px, so Create's centre lands on the wordmark's centre in the header
       band next to it rather than a row below it. */
    padding: calc(var(--safe-top) + 4px) 12px calc(var(--safe-bottom) + 12px);
    background: var(--paper);
    border-right: var(--rule) solid var(--line);
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  .app-body:not(.room-body) .app-rail .rail-create {
    display: inline-flex;
    justify-content: flex-start;
    flex: 0 0 auto;
    gap: 10px;
    padding-left: 12px;
  }

  .app-body:not(.room-body) .app-dock {
    position: static;
    flex-direction: column;
    align-items: stretch;
    gap: 2px;
    height: auto;
    margin-top: 12px;
    padding-bottom: 0;
    background: transparent;
    border-top: 0;
    z-index: auto;
  }

  .app-body:not(.room-body) .dock-item {
    flex: 0 0 auto;
    flex-direction: row;
    justify-content: flex-start;
    gap: 12px;
    min-height: 46px;
    padding: 0 12px;
    border-radius: 10px;
  }
  .app-body:not(.room-body) .dock-label { font-size: 15px; }
  .app-body:not(.room-body) .dock-item.is-selected { background: var(--green-soft); }
  /* The selected mark turns from a rule over the icon into a bar down the
     leading edge -- same signal, rotated with the rail. */
  .app-body:not(.room-body) .dock-mark {
    top: 50%;
    left: 0;
    width: 3px;
    height: 22px;
    transform: translateY(-50%);
  }

  /* The phone has :active and nothing else. A pointer that hovers is new at
     this width, so the rail answers it -- quietly, and never with colour the
     selected state already owns. */
  @media (hover: hover) {
    .app-body:not(.room-body) .dock-item:hover { background: var(--ink-faint); }
    .app-body:not(.room-body) .dock-item.is-selected:hover { background: var(--green-soft); }
  }

  /* The rail already carries Create, so the header keeps only the wordmark
     (and, in the three-tab comparison, the face). */
  .app-body:not(.room-body) .app-header [data-action="quick-add"] { display: none; }

  /* ---- the two split screens ----
     Both panels place a single tall block in the left column and a stack of
     short ones on the right. The left block spans down to a flexible last
     row on purpose: a grid item that crosses a flexible track contributes
     its height to THAT track only, so a long feed can never stretch the
     rows the right-hand stack is sitting in and tear it apart. */

  .app-body:not(.room-body) #panel-focus,
  .app-body:not(.room-body) #panel-rooms {
    display: grid;
    /* 65 / 35. --pc-aside above must stay in step with the second number. */
    grid-template-columns: minmax(0, 65fr) minmax(0, 35fr);
    min-height: 100%;
    /* The rule between the columns. A background line rather than a border on
       the sections, so it runs the full height of the screen instead of
       stopping wherever the shorter column ends. */
    background-image: linear-gradient(to bottom, var(--line), var(--line));
    background-repeat: no-repeat;
    background-size: var(--rule) 100%;
    background-position: 65% 0;
  }

  /* Loading, error, empty. These replace the whole screen rather than filling
     one of its columns, so they take both -- otherwise a centred empty state
     would sit centred inside the left two thirds and read as misaligned. */
  .app-body:not(.room-body) #panel-focus > .state-block,
  .app-body:not(.room-body) #panel-rooms > .state-block,
  .app-body:not(.room-body) #panel-focus > .focus-flow > .state-block,
  .app-body:not(.room-body) #panel-rooms > .lobby-flow > .state-block {
    grid-column: 1 / -1;
    grid-row: 1;
  }
  /* ...and with nothing in the second column, the rule between the columns
     has nothing left to separate. */
  .app-body:not(.room-body) #panel-focus:has(> .state-block),
  .app-body:not(.room-body) #panel-focus:has(> .focus-flow > .state-block),
  .app-body:not(.room-body) #panel-rooms:has(> .state-block),
  .app-body:not(.room-body) #panel-rooms:has(> .lobby-flow > .state-block) {
    background-image: none;
  }

  /* Focus: NOW on the left, the next focus time and the week on the right. */
  .app-body:not(.room-body) #panel-focus {
    grid-template-rows: auto auto minmax(0, 1fr);
    padding-bottom: var(--primary-bar-h);
  }
  .app-body:not(.room-body) #panel-focus > .focus-flow { display: contents; }
  .app-body:not(.room-body) #panel-focus > .focus-flow > * { grid-column: 1; }
  /* The left column carries one block and one button across two thirds of a
     desk monitor, so the block takes the size the app already gives its
     biggest single fact (.hero-time is 30px) rather than staying at phone
     scale and leaving the column looking unfinished. Nothing is invented:
     same face, same colour, one step up a ladder that already exists. */
  .app-body:not(.room-body) #panel-focus > .focus-flow > .focus-now {
    grid-row: 1 / -1;
    align-self: start;
    padding-top: 28px;
    padding-bottom: 24px;
    /* Its rule would hang in mid-air across two thirds of the screen. */
    border-bottom: 0;
  }
  .app-body:not(.room-body) .focus-now-title { font-size: 30px; }
  .app-body:not(.room-body) #panel-focus > .focus-flow > .section:not(.focus-now) {
    grid-column: 2;
  }

  /* Rooms: the head across the top, then the next focus time and current
     presence in the wide left column; quiet history belongs in the narrow
     right column. The time decision owns the first scan position on both
     phone and desktop. Named blocks, rather than child positions, are vital:
     a displaced saved draft exists only in one state and must not reshuffle
     the desktop grid when it appears. */
  .app-body:not(.room-body) #panel-rooms {
    grid-template-rows: auto auto auto minmax(0, 1fr);
    padding-bottom: var(--primary-bar-h);
  }
  .app-body:not(.room-body) #panel-rooms > .lobby-flow { display: contents; }
  .app-body:not(.room-body) #panel-rooms > .lobby-flow > * { grid-column: 1; }
  .app-body:not(.room-body) #panel-rooms > .lobby-flow > .hero {
    grid-column: 1 / -1;
    grid-row: 1;
  }
  .app-body:not(.room-body) #panel-rooms > .lobby-flow > .lobby-next-section,
  .app-body:not(.room-body) #panel-rooms > .lobby-flow > .lobby-saved-draft,
  .app-body:not(.room-body) #panel-rooms > .lobby-flow > .lobby-presence {
    grid-column: 1;
    align-self: start;
  }
  .app-body:not(.room-body) #panel-rooms > .lobby-flow > .lobby-presence {
    border-bottom: 0;
  }
  .app-body:not(.room-body) #panel-rooms > .lobby-flow > .lobby-feed {
    grid-column: 2;
    grid-row: 2 / -1;
  }
  /* Latest / Today is a two-word choice. Stretched to 650px it reads as a
     pair of banners rather than a switch, so it keeps a thumb-sized measure
     even when its column does not. */
  .app-body:not(.room-body) #panel-rooms .segmented { max-width: 340px; }

  /* ---- the one main button, still pinned, now inside the left column ----
     Both bars are positioned against .app (the only positioned ancestor), so
     100% here is the shell's width and the right inset is exactly the aside
     column. --dock-h is 0px at this width, so `bottom` needs no override. */
  .app-body:not(.room-body) .focus-bar,
  .app-body:not(.room-body) #panel-rooms .primary-bar {
    left: var(--rail-w);
    right: calc((100% - var(--rail-w)) * var(--pc-aside));
  }

  /* ---- the three one-column screens ----
     Padding, not max-width: the panel still measures the full content width
     (over 1000px at 1280), while its rules and its text stop at a readable
     measure instead of running the whole way across. */
  .app-body:not(.room-body) #panel-plan,
  .app-body:not(.room-body) #panel-progress,
  .app-body:not(.room-body) #panel-me {
    padding-inline: max(0px, calc((100% - var(--pc-measure)) / 2));
    padding-bottom: 32px;
  }

  /* Sticky bars inside those panels are children of the padded panel, so
     they already sit inside the measure and need nothing here. */

  /* The toast is centred on the window; the content is centred on the window
     minus the rail. Half a rail to the right puts it back over the content. */
  .app-body:not(.room-body) .toast-host { margin-left: calc(var(--rail-w) / 2); }
}

/* ?reduced=1 forces the same behaviour so the owner can check it in a
   normal browser without changing an OS setting. Identical outcome to the
   media query: everything appears immediately. */
body.reduced-motion *,
body.reduced-motion *::before,
body.reduced-motion *::after {
  animation-duration: 0.01ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0.01ms !important;
  scroll-behavior: auto !important;
}
body.reduced-motion .skeleton { animation: none !important; }
