/* THE PULSE ROOM (view-pulse): the page layout, the heat map and the exchange (the index of people and the panel beside it).
   Moved out of app.css in the rebuild. What Pulse shares with other rooms stays in app.css: the tiles (.pulse-tiles, .pulse-tile, .pt-*) and the rows (.pulse-wait, .pw-*) are also drawn by Home, Projects and Today. */

/* THE WORK, ABOVE THE MEASUREMENTS.  ("A Place", 26 August.)
 * Two queues side by side on a wide screen: people you owe an answer, and
 * things you promised yourself and missed. Stacked below 1040px, because two
 * columns of a working list on a laptop is two half-lists. */
.pulse-work{ display:grid; gap:var(--space-6); grid-template-columns:1fr;
  max-width:var(--page-measure); align-items:start; margin-bottom:var(--space-2); }
@media(min-width:1040px){ .pulse-work{ grid-template-columns:minmax(0,1fr) minmax(0,1fr); } }
/* renderPulse puts this away on an account with no mailbox, and `display:grid`
   above outranks the browser's own [hidden]{display:none} — so el.hidden was
   true and "Waiting on you / Past due" stayed on screen over two empty boxes.
   Found in a screenshot, which is the only place it could have been found. */
.pulse-work[hidden]{ display:none; }

/* THE HEAT MAP. One hue, light to dark — never a rainbow, because the thing
 * being shown is a magnitude and a rainbow implies categories. The scale is
 * relative to the busiest hour in the window, which is the only reading that
 * means anything for one person's own mail. */
.pulse-heat{ display:flex; flex-direction:column; gap:var(--space-2); }
.ph-grid{ display:grid; grid-template-columns:26px repeat(24, minmax(0,1fr)); gap:var(--space-hair); }
/* --text-dim, not --text-faint. Measured at 4.48:1 against a 4.5:1 floor —
   a fail by two hundredths, which is still a fail and is exactly the kind of
   near-miss that gets waved through. These are the row labels: without them
   the grid is twenty-four anonymous squares, so they are not decoration. */
.ph-dow{ font-family:var(--font-mono); font-size:var(--type-label); color:var(--text-dim);
  display:flex; align-items:center; }
.ph-cell{ aspect-ratio:1; border-radius:var(--radius-tight); background:rgba(120,120,140,.10); min-width:0; }
.ph-hours{ display:grid; grid-template-columns:26px repeat(24, minmax(0,1fr)); gap:var(--space-hair);
  font-family:var(--font-mono); font-size:var(--type-label); color:var(--text-faint); }
.ph-hours span{ text-align:center; }
.ph-key{ display:flex; align-items:center; gap:var(--space-2); font-size:var(--type-label); color:var(--text-dim); }
.ph-key i{ display:block; width:13px; height:13px; border-radius:var(--radius-tight); }

.pulse-empty{ text-align:center; padding:var(--space-8) var(--space-3); font-size:var(--type-lead); font-weight:700; color:var(--text-dim); }

/* =========================================================================
   THE EXCHANGE — PULSE'S INDEX AND THE PANEL BESIDE IT.

   "The cardsa re big and weird ... I can never see what our communication
    was or what I owe them."   (Matt, 22 September.)

   TWENTY-NINE PIXELS A ROW, AND THE NUMBER IS NOT A PREFERENCE. A waiting
   card is 66px tall and about ten fit on a 900-tall window. 29 is 20px of
   avatar plus 17px of type plus four pixels of air — the smallest a row can
   be and still be a comfortable target beside its neighbour — and the same
   window then holds about twenty-seven names.

   The index keeps its own scroll on a wide screen so it never leaves the
   screen while he works down it. On a phone it does not: two scrolling boxes
   inside one scrolling page is how a list becomes impossible to reach the
   bottom of with a thumb, so the columns stack and the page scrolls once.
   ========================================================================= */
.pulse-exchange{ display:grid; grid-template-columns:minmax(0,1fr); gap:var(--space-4);
  align-items:start; max-width:var(--page-measure); margin-bottom:var(--space-6); }
.pulse-exchange[hidden]{ display:none; }
@media(min-width:900px){
  .pulse-exchange{ grid-template-columns:minmax(0,320px) minmax(0,1fr); }
  .px-rows{ max-height:74vh; overflow-y:auto; }
}
.px-idx{ border:1px solid var(--glass-border); background:var(--glass-bg);
  border-radius:var(--radius-card); overflow:hidden; min-width:0; }
.px-idxhd{ display:flex; justify-content:space-between; align-items:baseline; gap:var(--space-2);
  padding:var(--space-2) var(--space-3); font-size:var(--type-label); font-weight:700;
  color:var(--text-dim); border-bottom:1px solid var(--glass-border); }
.px-rows{ display:flex; flex-direction:column; }
.px-row{ display:flex; align-items:center; gap:var(--space-2); height:29px; flex:none;
  padding:0 var(--space-3); border:0; border-left:2px solid transparent;
  border-bottom:1px solid var(--glass-border); background:none; cursor:pointer;
  font-family:inherit; text-align:left; width:100%; min-width:0; }
.px-row:last-child{ border-bottom:0; }
.px-row:hover{ background:rgba(120,120,140,.10); }
.px-row.on{ background:color-mix(in srgb, var(--blue) 15%, transparent); border-left-color:var(--blue); }
.px-row:focus-visible{ outline:2px solid var(--blue); outline-offset:-2px; }
.px-av{ flex:none; width:20px; height:20px; border-radius:var(--radius-round);
  display:flex; align-items:center; justify-content:center; font-size:9px; font-weight:700;
  background:rgba(120,120,140,.20); color:var(--text); }
.px-av.px-av-l{ width:40px; height:40px; font-size:var(--type-body); }
.px-nm{ flex:1; min-width:0; font-size:var(--type-body); font-weight:650; color:var(--text);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
/* The state is a WORD and not a dot. A colour on its own says nothing to
   anybody reading it for the first time, and nothing at all to somebody who
   cannot tell the two colours apart. */
.px-tag{ flex:none; font-size:var(--type-label); font-weight:700; white-space:nowrap; }
.px-tag.owed{ color:var(--orange-text, var(--orange)); }
.px-tag.waiting{ color:var(--blue-text, var(--blue)); }
.px-tag.quiet{ color:var(--text-dim); }
.px-d{ flex:none; width:32px; text-align:right; font-family:'IBM Plex Mono',monospace;
  font-size:var(--type-label); color:var(--text-dim); font-variant-numeric:tabular-nums; }
.px-d.late, .px-d.hot{ color:var(--orange-text, var(--orange)); }
.px-d.hot{ font-weight:700; }
/* The fifteenth row. The same 29px as a name, because it stands where a name
   would and the list must not change rhythm at the bottom. */
.px-row.px-more .px-nm{ color:var(--blue-text, var(--blue)); font-weight:700; }
.px-row.px-more:hover .px-nm{ text-decoration:underline; }
/* The way back, under the list rather than in it — it is not a person. */
.px-gone{ display:block; width:100%; border:0; border-top:1px solid var(--glass-border);
  background:none; font-family:inherit; text-align:left; cursor:pointer;
  padding:var(--space-2) var(--space-3); font-size:var(--type-label); color:var(--text-dim); }
.px-gone:hover{ color:var(--text); background:rgba(120,120,140,.10); }
.px-gone:focus-visible{ outline:2px solid var(--blue); outline-offset:-2px; }
.px-gone[hidden]{ display:none; }
.px-note{ padding:var(--space-2) var(--space-3); font-size:var(--type-label);
  color:var(--orange-text, var(--orange)); border-bottom:1px solid var(--glass-border); }
.px-empty{ padding:var(--space-6) var(--space-3); text-align:center;
  font-size:var(--type-body); color:var(--text-dim); }

.px-pane{ border:1px solid var(--glass-border); background:var(--glass-bg);
  border-radius:var(--radius-card); padding:var(--space-4); min-width:0; }
.px-pane[hidden]{ display:none; }
/* WITH NOBODY ON THE INDEX there is no second column, so the exchange stops
   pretending to have one. Held to the prose measure because what is left is
   one sentence, and a sentence has its own width: the whole fault this fixes
   was a box of words 1398px across a 1920 monitor. */
.pulse-exchange.bare{ grid-template-columns:minmax(0,1fr);
  max-width:var(--prose-measure); }
.px-hd{ display:flex; gap:var(--space-3); align-items:center; flex-wrap:wrap;
  padding-bottom:var(--space-3); border-bottom:1px solid var(--glass-border); }
.px-who{ flex:1; min-width:0; display:flex; flex-direction:column; gap:var(--space-hair); }
.px-h{ font-size:var(--type-lead); font-weight:700; color:var(--text); }
.px-og{ font-size:var(--type-body); color:var(--text-dim); overflow-wrap:anywhere; }
.px-facts{ display:flex; gap:var(--space-5); flex-wrap:wrap;
  padding:var(--space-3) 0; border-bottom:1px solid var(--glass-border); }
.px-fact{ min-width:0; display:flex; flex-direction:column; gap:var(--space-hair); }
.px-fact b{ font-size:var(--type-label); font-weight:700; color:var(--text-dim); }
.px-fact span{ font-size:var(--type-body); color:var(--text); }

/* WHAT HE OWES, WHICH IS THE ONE THING THIS PAGE EXISTS FOR. The sentence
   inside is always verbatim out of a message — his own where he promised
   something, theirs where they asked — and never a summary. */
.px-owe{ margin:var(--space-3) 0 0; padding:var(--space-3);
  border:1px solid color-mix(in srgb, var(--orange) 30%, transparent);
  border-left:3px solid var(--orange);
  background:color-mix(in srgb, var(--orange) 7%, transparent);
  border-radius:var(--radius-control); display:flex; flex-direction:column; gap:var(--space-1); }
.px-owe.ask{ border-color:color-mix(in srgb, var(--blue) 30%, transparent);
  border-left-color:var(--blue); background:color-mix(in srgb, var(--blue) 7%, transparent); }
.px-owe.none{ border-color:var(--glass-border); border-left-color:var(--text-dim);
  background:rgba(120,120,140,.07); }
.px-owe b{ font-size:var(--type-label); font-weight:700; color:var(--text); }
.px-owe q{ font-size:var(--type-lead); color:var(--text); quotes:'\201C' '\201D'; overflow-wrap:anywhere; }
.px-src{ font-size:var(--type-label); color:var(--text-dim); overflow-wrap:anywhere; }
.px-src .pw-act{ margin-left:var(--space-2); }

.px-lab{ margin-top:var(--space-4); font-size:var(--type-label); font-weight:700;
  color:var(--text-dim); }
.px-thread{ margin-top:var(--space-2); display:flex; flex-direction:column; gap:var(--space-2); }
.px-msg{ display:grid; grid-template-columns:auto minmax(0,1fr); gap:var(--space-3);
  padding:var(--space-2) var(--space-3); border-radius:var(--radius-control);
  background:rgba(120,120,140,.10); border:1px solid var(--glass-border); }
.px-msg.mine{ background:color-mix(in srgb, var(--blue) 9%, transparent);
  border-color:color-mix(in srgb, var(--blue) 22%, transparent); }
.px-mt{ font-family:'IBM Plex Mono',monospace; font-size:var(--type-label);
  color:var(--text-dim); white-space:nowrap; }
.px-mb{ min-width:0; }
.px-mw{ font-size:var(--type-label); font-weight:700; color:var(--text-dim); }
.px-mx{ font-size:var(--type-body); color:var(--text); overflow-wrap:anywhere; }
.px-thin{ margin-top:var(--space-2); font-size:var(--type-label); color:var(--text-dim); }
.px-thin.px-bad{ color:var(--orange-text, var(--orange)); }
.px-thin .pw-act{ margin-left:var(--space-2); }

.px-acts{ display:flex; gap:var(--space-2); flex-wrap:wrap; margin-top:var(--space-4);
  padding-top:var(--space-3); border-top:1px solid var(--glass-border); }
.px-act, .px-go{ font-family:inherit; font-size:var(--type-body); font-weight:600;
  padding:var(--space-2) var(--space-4); border-radius:var(--radius-pill);
  border:1px solid var(--glass-border); background:rgba(120,120,140,.12);
  color:var(--text-dim); cursor:pointer; }
.px-act:hover{ color:var(--text); background:rgba(120,120,140,.22); }
/* Remove is not Snooze. It sits beside it and does not look like it. */
.px-act.px-rm{ color:var(--orange-text, var(--orange));
  border-color:color-mix(in srgb, var(--orange) 40%, transparent);
  background:color-mix(in srgb, var(--orange) 9%, transparent); }
.px-act.px-rm:hover{ color:var(--orange-text, var(--orange));
  background:color-mix(in srgb, var(--orange) 18%, transparent); }
.px-go{ color:var(--text); background:color-mix(in srgb, var(--blue) 26%, transparent);
  border-color:color-mix(in srgb, var(--blue) 50%, transparent); }
.px-go:hover{ background:color-mix(in srgb, var(--blue) 38%, transparent); }
.px-act:focus-visible, .px-go:focus-visible{ outline:2px solid var(--blue); outline-offset:2px; }
