/* React (moments 9 and 10; react.js): the app's menu strip (AppContextMenu.css,
   FloatingMenu.css, header-button.css), the React face (react-face.css,
   react-lists.css, outline.css, connections.css, react-card.css, the
   kit's carousel.css and sources-widget.css), and the Research run's read
   stage in the peek. Scoped to .p-drawer [data-face='react'], the peek row
   and the lane's own .app-menu-root. The react lane owns this file. */

/* -------------------------------------------------- tokens
   tokens/canvas.css (--o-legible, --o-secondary), tokens/size.css
   (--grid-cell, --container-col), the menus' shadow (tokens/elevation.css),
   and the read stage's orb (solid-materials.css:417-419, 534-537). */
.p-drawer,
.app-menu-root {
  --o-legible: 0.5;
  --o-secondary: 0.3;
  --grid-cell: 119px;
  --container-col: 70px;
  --stage-read-metal-1: #2a0c00;
  --stage-read-metal-2: #a83a0a;
  --stage-read-metal-3: #ff7539;
  --stage-read-metal-4: #ffe6d9;
  --stage-read-glow: #ff75394d;
  --shadow-menu: drop-shadow(0 14px 30px rgba(0, 0, 0, 0.18)) drop-shadow(0 2px 6px rgba(0, 0, 0, 0.1));
}
@media (prefers-color-scheme: dark) {
  .p-drawer,
  .app-menu-root { --stage-read-glow: #f3591633; }
}
.p-drawer .moreinfo-peek-orb[data-stage='read'] { box-shadow: 0 1px 6px var(--stage-read-glow); }

/* -------------------------------------------------- the extra lines
   page.js marks the extra lines' letters .more, a class lab.css already
   gives the introduction's key cap (absolute, 24 square, opacity 0): on a
   letter in the text every one of those goes back to the line's own. They
   are inline, wrapping only after a space, as the app's text wraps: as
   inline blocks (the line's .l) a line may break inside a word, which the
   extra lines, running to three lines, would show. */
.p-text .l.more {
  position: static;
  z-index: auto;
  display: inline;
  white-space: pre-wrap;
  box-sizing: content-box;
  width: auto;
  height: auto;
  margin: 0;
  border-radius: 0;
  background: none;
  color: inherit;
  font: inherit;
  font-variation-settings: normal;
  font-feature-settings: normal;
  user-select: auto;
  -webkit-user-select: auto;
  opacity: 1;
  transition: none;
}
.p-text br.l.more { display: inline; }

/* -------------------------------------------------- the menu strip
   The root hangs under the pointer and takes none of it; the drop shadow on
   the root follows the strip and its tail as one shape. */
.app-menu-root {
  position: absolute;
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  pointer-events: none;
  filter: var(--shadow-menu);
  font-family: var(--font-sans);
}
/* The tail: the tooltip's triangle, 14.485 x 12.544, apex rounded 2, in the
   strip's own colour (AppContextMenu.css:27-29). */
.app-menu-root .floating-menu-tail {
  flex-shrink: 0;
  width: 14.485px;
  height: 12.544px;
  background: var(--color-surface);
  clip-path: path('M 14.485 12.544 L 0 12.544 L 5.5104 3 A 2 2 0 0 1 8.9746 3 Z');
}
/* The strip: 48 high on color-surface, fully round, padded 6, its capsules
   8 apart (AppContextMenu.css:14-24). */
.app-menu-root .floating-menu.app-context-menu {
  box-sizing: border-box;
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 8px;
  width: max-content;
  height: 48px;
  padding: 0 6px;
  border-radius: 24px;
  background: var(--color-surface);
  color: var(--color-ink);
  user-select: none;
  -webkit-user-select: none;
}
.app-menu-root .app-context-menu-icons { display: contents; }
.app-menu-root [data-component='tooltip-trigger'] { display: inline-flex; }
.react-tip-anchor { position: absolute; pointer-events: none; }

/* The button kit (header-button.css), for the strip outside the drawer: a
   36 control, flat at rest; under the pointer the hover fill, the top rim
   and the raise shade; pressed the ink with the bowl; disabled at 0.4. In a
   capsule a 30 disc with no fill, its glyph muted; React's circle the 36
   one-button capsule, its glyph muted at rest (AppContextMenu.css:42-44).
   data-hover and data-press are the film's pointer. */
.app-menu-root .hb {
  --hb-fill: var(--color-active);
  --hb-label: var(--color-ink);
  --hb-glyph: var(--color-ink);
  --hb-hover-motion: var(--motion-hover);
  position: relative;
  isolation: isolate;
  box-sizing: border-box;
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 36px;
  min-width: 36px;
  padding: 0 12px 0 10px;
  border: 0;
  border-radius: 100px;
  background-color: var(--hb-fill);
  background-image: none;
  color: var(--hb-label);
  font: 500 14px / 18px var(--font-sans);
  white-space: nowrap;
  cursor: default;
  outline: none;
  transition:
    background-color var(--hb-hover-motion) ease-out,
    box-shadow var(--hb-hover-motion) ease-out,
    color var(--hb-hover-motion) ease-out,
    --hb-glyph var(--hb-hover-motion) ease-out;
}
.app-menu-root .hb > .material-symbols-rounded {
  flex: 0 0 auto;
  font-size: 18px;
  color: var(--hb-glyph);
  opacity: var(--hb-dim, 1);
}
.app-menu-root .hb[data-shape='circle'] { width: 36px; padding: 0; }
.app-menu-root .hb-capsule {
  box-sizing: border-box;
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 2px;
  height: 36px;
  padding: 3px;
  border-radius: 100px;
  background: var(--color-active);
}
.app-menu-root .hb-capsule .hb {
  --hb-fill: transparent;
  --hb-glyph: var(--color-muted);
  width: 30px;
  height: 30px;
  min-width: 30px;
  padding: 0;
}
.app-menu-root .hb[data-shape='circle']:not([data-hover], [data-press]) { --hb-glyph: var(--color-muted); }
.app-menu-root .hb::before,
.app-menu-root .hb::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--hb-hover-motion) ease-out;
}
.app-menu-root .hb::before { background-image: var(--raise-shade); }
.app-menu-root .hb::after { background-image: var(--bowl-shade); }
.app-menu-root .hb[data-hover]:not(:disabled, [data-press]) {
  --hb-glyph: var(--color-ink);
  --hb-hover-motion: var(--motion-hover-in);
  background-color: var(--color-hover);
  box-shadow: inset 0 1px 0 var(--raise-rim);
}
.app-menu-root .hb[data-hover]:not(:disabled, [data-press])::before { opacity: 1; }
.app-menu-root .hb[data-press]:not(:disabled) {
  --hb-label: var(--color-surface);
  --hb-glyph: var(--color-surface);
  background-color: var(--color-ink);
  box-shadow: none;
  transition: none;
}
.app-menu-root .hb[data-press]:not(:disabled)::before { opacity: 0; transition: none; }
.app-menu-root .hb[data-press]:not(:disabled)::after { opacity: 1; transition: none; }
.app-menu-root .hb:disabled {
  --hb-dim: 0.4;
  color: color-mix(in srgb, var(--hb-label) 40%, transparent);
}

/* -------------------------------------------------- the face's shell
   (react-face.css:182-190): the body under the header, 20 above, 28 below. */
.p-drawer [data-face='react'] .react-face-scroll { padding: 20px 0 28px; }
.p-drawer [data-face='react'] .react-face-body {
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-width: 0;
}

/* The top section (react-face.css:9-34): the reading column, then the 1x1
   "Say it your way", 16 apart, two tracks and a gutter high. */
.p-drawer [data-face='react'] .react-top {
  display: flex;
  gap: 16px;
  align-items: stretch;
  height: calc(2 * var(--grid-cell) + 16px);
}
.p-drawer [data-face='react'] .react-reading { display: flex; flex: 1 1 auto; flex-direction: column; min-width: 0; }
.p-drawer [data-face='react'] .react-reading-edges { position: relative; flex: 1 1 auto; min-height: 0; }
.p-drawer [data-face='react'] .react-reading-scroll { height: 100%; overflow: hidden; }
/* Meta: 12/16 Medium muted, 16 above the claim (react-face.css:36-50). */
.p-drawer [data-face='react'] .react-meta {
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 16px;
  margin: 0 0 16px;
  color: var(--color-muted);
  font: 500 12px / 16px var(--font-sans);
}
.p-drawer [data-face='react'] .react-meta-spinner { color: var(--color-muted); }
.p-drawer [data-face='react'] .react-swap { position: relative; }
.p-drawer [data-face='react'] .react-swap > [hidden] { display: none; }
/* The claim: Display 28/34 Medium ink, at most 640 wide, never truncated
   (react-face.css:58-66). */
.p-drawer [data-face='react'] .react-claim {
  max-width: calc(8 * var(--container-col) + 80px);
  margin: 0;
  color: var(--color-ink);
  font: 500 28px / 34px var(--font-sans);
}
/* The thinking skeleton (react-face.css:86-96, 203-216): the claim's two
   24 bars, 640 and 420, 10 apart; the counters' pills, 104 and 96. */
.p-drawer [data-face='react'] .react-claim-skeleton { display: flex; flex-direction: column; gap: 10px; }
.p-drawer [data-face='react'] .react-bar { display: block; max-width: 100%; height: 24px; border-radius: 5px; }
.p-drawer [data-face='react'] .react-bar[data-bar='claim'] { width: calc(8 * var(--container-col) + 80px); }
.p-drawer [data-face='react'] .react-bar[data-bar='tail'] { width: calc(6 * var(--container-col)); }
/* The counters: the sources pill and the reroll circle, 8 apart, 36 high,
   16 under the column (react-face.css:98-126). */
.p-drawer [data-face='react'] .react-counters {
  display: flex;
  flex: 0 0 auto;
  gap: 8px;
  align-items: center;
  margin-top: 16px;
}
.p-drawer [data-face='react'] .react-counters-skeleton-row,
.p-drawer [data-face='react'] .react-counters-live { display: flex; gap: 8px; align-items: center; }
.p-drawer [data-face='react'] .react-counters > [hidden] { display: none; }
.p-drawer [data-face='react'] .react-pill-skeleton { display: block; height: 36px; border-radius: 100px; }
.p-drawer [data-face='react'] .react-pill-skeleton[data-bar='sources'] { width: 104px; }
.p-drawer [data-face='react'] .react-pill-skeleton[data-bar='reroll'] { width: 96px; }
.p-drawer [data-face='react'] .hb.react-pill {
  gap: 8px;
  height: 36px;
  padding: 0 12px;
  border-radius: 100px;
  font-size: 14px;
  line-height: 20px;
}
.p-drawer [data-face='react'] .hb.react-reroll { width: 36px; height: 36px; }

/* "Say it your way" (react-face.css:127-181): the 1x1, 254 square, at the
   trailing edge; the head 13/16 Medium muted with edit_note 16; the field
   16/22, its placeholder muted; Send at the trailing foot, 28, muted and
   flat at 0.6 while there is no text. */
.p-drawer [data-face='react'] .faces-widget.react-say {
  position: relative;
  flex: 0 0 calc(2 * var(--grid-cell) + 16px);
  width: calc(2 * var(--grid-cell) + 16px);
  height: calc(2 * var(--grid-cell) + 16px);
}
.p-drawer [data-face='react'] .react-say-head {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--color-muted);
  font: 500 13px / 16px var(--font-sans);
}
.p-drawer [data-face='react'] .react-say-text {
  flex: 1 1 auto;
  width: 100%;
  min-height: 0;
  margin: 0;
  padding: 0 0 36px;
  border: 0;
  outline: none;
  resize: none;
  background: none;
  color: var(--color-ink);
  font: 400 16px / 22px var(--font-sans);
  cursor: default;
}
.p-drawer [data-face='react'] .react-say-text::placeholder { color: var(--color-muted); opacity: 1; }
.p-drawer [data-face='react'] .hb.react-say-action {
  position: absolute;
  right: 16px;
  bottom: 16px;
  width: 28px;
  min-width: 28px;
  height: 28px;
  background-color: var(--color-surface);
}
.p-drawer [data-face='react'] .hb.react-say-send:disabled {
  --hb-dim: 1;
  background: none;
  box-shadow: none;
  color: var(--color-muted);
  opacity: 0.6;
}
.p-drawer [data-face='react'] .hb.react-say-send:disabled > .material-symbols-rounded { color: var(--color-muted); }

/* -------------------------------------------------- the widgets' looks
   The loading look (widget.css:117-120, 460-500): the content hidden under
   React's skeleton (react-card.css:44-54), which shows from 300; the
   badge's spinner from 1 s. */
.p-drawer [data-face='react'] .faces-widget-skeleton { display: flex; flex: 1 1 auto; flex-direction: column; min-height: 0; }
.p-drawer [data-face='react'] .faces-widget-skeleton[hidden],
.p-drawer [data-face='react'] .faces-widget-content[hidden] { display: none; }
.p-drawer [data-face='react'] .faces-widget-body { position: relative; }
.p-drawer [data-face='react'] .react-skeleton,
.p-drawer [data-face='react'] .react-skeleton-lines { display: flex; flex-direction: column; }
.p-drawer [data-face='react'] .react-skeleton { gap: 16px; }
.p-drawer [data-face='react'] .react-skeleton-lines { gap: 8px; }
.p-drawer [data-face='react'] .react-skeleton .faces-skeleton-title { margin-bottom: 0; }
.p-drawer [data-face='react'] .faces-widget-badge > * { grid-area: 1 / 1; }
.p-drawer [data-face='react'] .faces-spinner { display: inline-flex; flex: 0 0 auto; }

/* A list with its own scroller (FadeEdge toEdge, widget.css:336-375,
   880-895): it runs to the widget's sides and foot, and up under the head,
   its rows padded back; the body runs to the border. */
.p-drawer [data-face='react'] .faces-widget-body:has(.fade-edge[data-to-edge='end']) { margin-bottom: -16px; }
.p-drawer [data-face='react'] .fade-edge[data-to-edge='end'] {
  position: relative;
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  min-height: 0;
  margin: calc(-1 * var(--widget-above)) -16px 0;
}
.p-drawer [data-face='react'] .fade-edge[data-to-edge='end'] > :first-child {
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
  padding: var(--widget-above) 16px 0;
}
.p-drawer [data-face='react'] .fade-edge-fade[data-edge='end'] {
  top: 0;
  right: 0;
  bottom: 0;
  background: linear-gradient(to right, var(--fade-clear), var(--fade-color));
}

/* Pushback (react-lists.css:5-101): rows of 48, padding 4 6, 10 between
   the 28 disc and the words; the title 14/20 ink, the line 13/18 muted,
   each on one line. */
.p-drawer [data-face='react'] .react-push { display: flex; flex: 1 1 auto; flex-direction: column; min-height: 0; }
.p-drawer [data-face='react'] .react-push-list { display: flex; flex-direction: column; }
.p-drawer [data-face='react'] .react-push-row {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-width: 0;
  min-height: 48px;
  margin: 0;
  padding: 4px 6px;
  border: 0;
  border-radius: 10px;
  background: none;
  font: inherit;
  text-align: left;
  color: var(--color-ink);
  cursor: default;
  transition: background-color var(--motion-hover) ease-out;
}
.p-drawer [data-face='react'] .react-push-disc {
  display: grid;
  flex: 0 0 auto;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--color-active);
  color: var(--color-muted);
}
.p-drawer [data-face='react'] .react-push-words { display: flex; flex-direction: column; min-width: 0; }
.p-drawer [data-face='react'] .react-push-title,
.p-drawer [data-face='react'] .react-push-line { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.p-drawer [data-face='react'] .react-push-title { font: 400 14px / 20px var(--font-sans); color: var(--color-ink); }
.p-drawer [data-face='react'] .react-push-line { font: 400 13px / 18px var(--font-sans); color: var(--color-muted); }

/* Next moves (react-lists.css:102-176; carousel.css:4-36): the run bleeds
   to the widget's sides, padded 6 16, its columns 16 apart; a column padded
   6, what it draws on 13/18 muted with its 16 glyph, the headline Label
   18/24 12 under it, the reason 14/20 muted at the foot; the end fade 40 in
   the widget's fill. */
.p-drawer [data-face='react'] .react-moves { display: flex; flex: 1 1 auto; flex-direction: column; min-height: 0; }
.p-drawer [data-face='react'] .faces-carousel {
  position: relative;
  isolation: isolate;
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  margin: 0 -16px;
}
.p-drawer [data-face='react'] .faces-carousel-run {
  position: relative;
  z-index: 0;
  display: flex;
  flex: 1 1 auto;
  gap: 16px;
  margin: -6px 0;
  padding: 6px 16px;
  overflow: hidden;
}
.p-drawer [data-face='react'] .faces-carousel-run > * { flex-shrink: 0; }
.p-drawer [data-face='react'] .faces-carousel > .fade-edge-fade { z-index: 1; }
.p-drawer [data-face='react'] .react-move-tile {
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  width: var(--react-move-width);
  min-width: 0;
  margin: 0;
  padding: 6px;
  border: 0;
  border-radius: 10px;
  background: none;
  font: inherit;
  text-align: left;
  color: var(--color-ink);
  cursor: default;
}
.p-drawer [data-face='react'] .react-move-draws {
  display: flex;
  align-items: center;
  gap: 6px;
  max-width: 100%;
  font: 400 13px / 18px var(--font-sans);
  color: var(--color-muted);
}
.p-drawer [data-face='react'] .react-move-draws > span:last-child { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.p-drawer [data-face='react'] .react-move-headline { margin-top: 12px; font: 400 18px / 24px var(--font-sans); color: var(--color-ink); }
.p-drawer [data-face='react'] .react-move-reason { margin-top: auto; padding-top: 8px; font: 400 14px / 20px var(--font-sans); color: var(--color-muted); }

/* Outline (outline.css): rows of 48 on a 50 pitch, the words from 72, the
   says line 14/20 ink over the role and opening words 13/18 muted, the
   chip at the row's end; the spine behind the words, 16 in, from where the
   rows start; a missing step's words muted. */
.p-drawer [data-face='react'] .outline-list { position: relative; display: flex; flex-direction: column; gap: 2px; }
.p-drawer [data-face='react'] .outline-row {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  gap: 8px;
  box-sizing: border-box;
  width: 100%;
  height: 48px;
  margin: 0;
  padding: 0 6px 0 72px;
  border: 0;
  border-radius: 10px;
  background-color: transparent;
  text-align: left;
  font: inherit;
  cursor: default;
}
.p-drawer [data-face='react'] .outline-words { display: flex; flex: 1 1 auto; flex-direction: column; gap: 2px; min-width: 0; }
.p-drawer [data-face='react'] .outline-says,
.p-drawer [data-face='react'] .outline-line { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-family: var(--font-sans); }
.p-drawer [data-face='react'] .outline-says { font-size: 14px; line-height: 20px; color: var(--color-ink); }
.p-drawer [data-face='react'] .outline-line { font-size: 13px; line-height: 18px; color: var(--color-muted); }
.p-drawer [data-face='react'] .outline-row[data-missing='true'] .outline-says { color: var(--color-muted); }
.p-drawer [data-face='react'] .outline-chip-slot { display: inline-flex; flex-shrink: 0; }
.p-drawer [data-face='react'] .outline-spine {
  position: absolute;
  top: var(--widget-above);
  left: 16px;
  overflow: visible;
  pointer-events: none;
}
.p-drawer [data-face='react'] .outline-claim { stroke: var(--color-muted); stroke-width: 1; opacity: var(--o-legible); }
.p-drawer [data-face='react'] .outline-path,
.p-drawer [data-face='react'] .outline-path-missing {
  fill: none;
  stroke: var(--color-muted);
  stroke-width: 1.5;
  stroke-linejoin: round;
  stroke-linecap: round;
}
.p-drawer [data-face='react'] .outline-path-missing { stroke-width: 1; opacity: 0.6; }
.p-drawer [data-face='react'] .outline-dot { fill: var(--color-ink); }
.p-drawer [data-face='react'] .outline-dot[data-look='missing'] {
  fill: var(--side-menu-well);
  stroke: var(--color-muted);
  stroke-width: 1.25;
}

/* React's Sources (react-lists.css:183-205 over sources-widget.css): rows
   on a 30 pitch, the number in 16, the 20 disc, the host 16 further in in
   a track and a gap, at 13/16, the title 13/18. */
.p-drawer [data-face='react'] .react-sources { display: contents; }
.p-drawer [data-face='react'] .faces-widget:has(.sources-list) { gap: 8px; }
.p-drawer [data-face='react'] .sources-list { display: flex; flex-direction: column; gap: 0; container: react-sources / inline-size; }
.p-drawer [data-face='react'] .sources-row {
  display: grid;
  grid-template-columns: 16px 20px calc(var(--grid-cell) + 8px) minmax(0, 1fr) auto;
  align-items: center;
  flex-shrink: 0;
  width: 100%;
  height: 30px;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 10px;
  background: none;
  text-align: left;
  font: inherit;
  color: var(--color-ink);
  cursor: default;
}
.p-drawer [data-face='react'] .sources-row > .sources-disc { width: 20px; height: 20px; font: 500 12px / 16px var(--font-sans); }
.p-drawer [data-face='react'] .sources-number {
  font: 500 12px / 16px var(--font-sans);
  font-variant-numeric: tabular-nums;
  text-align: center;
  color: var(--color-muted);
}
.p-drawer [data-face='react'] .sources-detail,
.p-drawer [data-face='react'] .sources-name { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.p-drawer [data-face='react'] .sources-detail {
  margin-left: 16px;
  padding-right: 8px;
  font: 400 13px / 16px var(--font-sans);
  color: var(--color-muted);
}
.p-drawer [data-face='react'] .sources-name { font: 500 13px / 18px var(--font-sans); color: var(--color-ink); }
/* The phone's one column (the list 278 wide): the host's column narrows
   so the title keeps its room; the site's own fallback, as Research's. */
@container react-sources (width < 440px) {
  .p-drawer [data-face='react'] .sources-row { grid-template-columns: 16px 20px 96px minmax(0, 1fr) auto; }
  .p-drawer [data-face='react'] .sources-detail { margin-left: 8px; }
}

/* Connections (connections.css on lane/connections-3d): the graph fills
   the body, drawn in the well with no inner box (react.js GRAPH). Names
   13/18; the size pill alone at the trailing foot. */
.p-drawer [data-face='react'] .connections-graph {
  /* The graph's own motion tokens (tokens/motion.css): its names fade in
     and out on the reveal tokens, the view goes home over --motion-home on
     --ease-settle. */
  --motion-reveal-in: 160ms;
  --motion-home: 800ms;
  --heat-edge-mix: 28%;
  /* An edge's ink at the claim's depth: faint, the claim's own fainter. */
  --edge-ink: 0.42;
  --edge-ink-claim: 0.2;
  position: relative;
  isolation: isolate;
  flex: 1 1 0;
  min-height: 0;
  font: 400 13px / 18px var(--font-sans);
  /* The hand (Pat 2026-11-09 04:22, 04:23): a drag turns the graph, so the
     open hand, closed while it turns or carries a node (html[data-grabbing]
     below the reader's handle); with Shift or Option down a drag pans, so
     the move arrows. The site's own cursors, as the drawer's handle's. A
     touch screen gives the graph sideways drags only: up and down scroll
     the page. */
  cursor: url('/assets/cursors/openhand.svg') 15 16, grab;
  touch-action: pan-y;
  -webkit-user-select: none;
  user-select: none;
}
.p-drawer [data-face='react'] .connections-graph:is([data-hand='turn'], [data-hand='node']) { cursor: url('/assets/cursors/closedhand.svg') 15 16, grabbing; }
.p-drawer [data-face='react'] .connections-graph:is([data-pan-key='true'], [data-hand='pan']) { cursor: move; }
html[data-graph-pan],
html[data-graph-pan] * { cursor: move !important; }
.p-drawer [data-face='react'] .faces-widget-content:has(> .connections-graph) { overflow: visible; }
/* The graph's world: the space as the camera draws it. The ball keeps its
   proportions, so it spills past the box and fades to nothing over the last
   24 at each edge, clipped there with the hit areas. */
.p-drawer [data-face='react'] .connections-world {
  --graph-fade: 24px;
  position: absolute;
  inset: 0;
  overflow: hidden;
  -webkit-mask-image:
    linear-gradient(to right, transparent, #000 var(--graph-fade), #000 calc(100% - var(--graph-fade)), transparent),
    linear-gradient(to bottom, transparent, #000 var(--graph-fade), #000 calc(100% - var(--graph-fade)), transparent);
  -webkit-mask-composite: source-in;
  mask-image:
    linear-gradient(to right, transparent, #000 var(--graph-fade), #000 calc(100% - var(--graph-fade)), transparent),
    linear-gradient(to bottom, transparent, #000 var(--graph-fade), #000 calc(100% - var(--graph-fade)), transparent);
  mask-composite: intersect;
}
.p-drawer [data-face='react'] .connections-svg { position: absolute; inset: 0; overflow: visible; pointer-events: none; }
/* Depth (--far, 0 nearest to 1 farthest) fades a far node's ink toward
   --o-legible, on fill and stroke opacity (the hover's dim eases opacity).
   Heat (--heat): the node's colour by its 3D distance from the claim. */
.p-drawer [data-face='react'] .connections-edge {
  stroke: var(--heat, var(--color-line));
  stroke-opacity: calc(var(--edge-ink) * (1 - var(--far, 0) * (1 - var(--o-legible))));
  stroke-linecap: round;
  transition: opacity var(--motion-hover) var(--ease-out);
}
.p-drawer [data-face='react'] .connections-edge[data-claim='true'] {
  stroke-opacity: calc(var(--edge-ink-claim) * (1 - var(--far, 0) * (1 - var(--o-legible))));
}
/* Dots: filled in their heat for the claim and the writer's own, a 1.5
   ring of it on the well for outside work. */
.p-drawer [data-face='react'] .connections-dot {
  fill: var(--heat, var(--color-ink));
  stroke: color-mix(in oklab, var(--heat, var(--color-ink)), var(--color-ink) var(--heat-edge-mix));
  stroke-width: 1;
  fill-opacity: calc(1 - var(--far, 0) * (1 - var(--o-legible)));
  stroke-opacity: calc(1 - var(--far, 0) * (1 - var(--o-legible)));
  transition: opacity var(--motion-hover) var(--ease-out);
}
.p-drawer [data-face='react'] .connections-dot[data-side='outside'] {
  fill: var(--side-menu-well);
  stroke: color-mix(in oklab, var(--heat, var(--color-ink)), var(--color-ink) calc(var(--heat-edge-mix) / 2));
  stroke-width: 1.5;
}
/* A node's name: hidden until hovered or zoomed in until it has room,
   fading in over --motion-reveal-in and out over --motion-reveal-out,
   knocked out of the edges behind it by the well's own colour. */
.p-drawer [data-face='react'] .connections-label {
  position: absolute;
  height: 18px;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--color-muted);
  text-shadow: 0 0 2px var(--side-menu-well), 0 0 2px var(--side-menu-well), 0 0 3px var(--side-menu-well);
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--motion-reveal-out) var(--ease-out);
}
.p-drawer [data-face='react'] .connections-label[data-shown='true'] { opacity: 1; transition-duration: var(--motion-reveal-in); }
.p-drawer [data-face='react'] .connections-label[data-claim='true'] { color: var(--color-ink); }
/* Hover: the node and its neighbours stay, the rest at 30%. */
.p-drawer [data-face='react'] :is(.connections-edge, .connections-dot, .connections-label[data-shown='true'])[data-dim='true'] { opacity: var(--o-secondary); }
.p-drawer [data-face='react'] .connections-nodes { position: absolute; inset: 0; margin: 0; padding: 0; list-style: none; pointer-events: none; }
.p-drawer [data-face='react'] .connections-nodes > li { display: contents; }
/* Each node's 24 hit area over its dot, at every zoom: the plain arrow
   unless the hand is at work or Shift or Option is down. */
.p-drawer [data-face='react'] .connections-node {
  position: absolute;
  width: 24px;
  height: 24px;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: none;
  cursor: default;
  pointer-events: auto;
  outline: none;
}
.p-drawer [data-face='react'] .connections-graph:is([data-hand], [data-pan-key='true']) .connections-node { cursor: inherit; }
.p-drawer [data-face='react'] .connections-graph > [data-component='tooltip-trigger'] {
  position: absolute;
  right: 0;
  bottom: 0;
  z-index: 1;
  cursor: default;
}
@media (prefers-reduced-motion: reduce) {
  .p-drawer [data-face='react'] :is(.connections-edge, .connections-dot, .connections-label) { transition: none; }
}

/* -------------------------------------------------- the reader's handle
   In React part two the drawer's handle is the reader's (react.js): the open hand
   over it, the closed hand while it is held (the scene's own cursors,
   pointer.js; the app's grab and grabbing, Stage.css:429-447), the bar ink
   under the pointer and while held. The page never scrolls under the drag. */
.pscene.reader-drag .p-handle {
  pointer-events: auto;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
  cursor: url('/assets/cursors/openhand.svg') 15 16, grab;
}
.pscene.reader-drag .p-handle[data-held] .stage-handle-bar { background: var(--color-ink); }
@media (hover: hover) {
  .pscene.reader-drag .p-handle:hover .stage-handle-bar { background: var(--color-ink); }
}
html[data-grabbing],
html[data-grabbing] * { cursor: url('/assets/cursors/closedhand.svg') 15 16, grabbing !important; }

/* -------------------------------------------------- the phone
   The app has no window this narrow (its React boards are 1104 and 680):
   on the phone's 350 drawer the top section stacks, the reading column
   over "Say it your way" at the body's width. The site's own fallback, as
   the margin's and Research's sources are. */
.board.phone .p-drawer [data-face='react'] .react-top { flex-direction: column; height: auto; }
.board.phone .p-drawer [data-face='react'] .react-reading-edges { flex: 0 0 auto; }
.board.phone .p-drawer [data-face='react'] .faces-widget.react-say { flex: 0 0 auto; width: 100%; }
.board.phone .p-drawer [data-face='react'] .react-bar[data-bar='claim'] { width: 100%; }
.board.phone .p-drawer [data-face='react'] .react-bar[data-bar='tail'] { width: 66%; }

/* -------------------------------------------------- the writing's fold
   page.js wraps the block in .p-fold: the scene's height, and 160 wider
   than the column each side for the margin's marks. In React (react.js
   adds p-faded) it carries the app's two edge fades as a mask, held still
   while the block moves under them (Stage.css .stage-edge-fade): the
   page's top fade under the header, where a line going up dissolves
   rather than being cut, and the seam, 48 over the drawer's drawn edge,
   where the line the drawer slices dissolves. The seam follows the edge on
   the drawer's own curve, 1:1 while it is held, and rides up with React
   part two's way out, moment 10 on to 11 (--p-travel). */
@property --p-seam {
  syntax: '<length>';
  inherits: false;
  initial-value: 0px;
}
.p-fold {
  position: absolute;
  top: 0;
  left: -160px;
  right: -160px;
}
.p-fold > .p-block { left: 160px; right: 160px; }
/* The top fade starts under the header's title and is the app's 120 tall
   (Stage.css --stage-edge-fade-height): a line going up dims over a few
   lines' height, never in one line's (the review's phone crop). */
.pscene { --p-fade-a: 92px; --p-fade-b: 212px; }
.board.phone .pscene { --p-fade-a: 80px; --p-fade-b: 200px; }
.pscene.p-faded .p-fold {
  --p-seam: var(--p-h);
  -webkit-mask-image: linear-gradient(
    to bottom,
    transparent var(--p-fade-a),
    #000 var(--p-fade-b),
    #000 calc(100% - var(--p-seam) - 48px + var(--p-travel, 0px)),
    transparent calc(100% - var(--p-seam) + var(--p-travel, 0px))
  );
  mask-image: linear-gradient(
    to bottom,
    transparent var(--p-fade-a),
    #000 var(--p-fade-b),
    #000 calc(100% - var(--p-seam) - 48px + var(--p-travel, 0px)),
    transparent calc(100% - var(--p-seam) + var(--p-travel, 0px))
  );
  transition: --p-seam var(--motion-drawer) var(--ease-settle);
}
.pscene.p-faded.dragging .p-fold { transition: none; }
/* A rung's settle after the reader's drag: the writing's parallax moves on
   the drawer's curve and length, as the app's follows its drawer. */
.pscene .p-block.p-riding { transition: top var(--motion-drawer) var(--ease-settle); }
/* React part two's way out to the interlude (react.js): the block, the drawer, its shade and
   its handle ride up with the page, 1:1 with the scroll. */
.pscene.p-travel .p-block { translate: 0 calc(-50% + var(--p-travel)); }
.pscene.p-travel .p-drawer,
.pscene.p-travel .p-drawer-shade,
.pscene.p-travel .p-handle { translate: 0 var(--p-travel); }
/* On the way out the drawer, its shade and its handle pass under the same
   top fade as the writing, so all of React leaves as one (the review of
   round 3: the drawer stayed sharp over the header). A mask moves with its
   box, so each carries the fade in its own box: a scene line Y is at
   Y - (scene height) + (the box's bottom offset, --p-up) + 100% - travel
   down the box (each is anchored by its bottom). --p-scene-h is react.js
   setTravel's. Where the drawer and shade already fade at the foot, the
   two masks intersect. */
.pscene.p-travel .p-drawer,
.pscene.p-travel .p-drawer-shade,
.pscene.p-travel .p-handle {
  --p-up: 0px;
  --p-top-fade: linear-gradient(
    to bottom,
    transparent calc(var(--p-fade-a) - var(--p-scene-h) + var(--p-up) + 100% - var(--p-travel)),
    #000 calc(var(--p-fade-b) - var(--p-scene-h) + var(--p-up) + 100% - var(--p-travel))
  );
  -webkit-mask-image: var(--p-top-fade);
  mask-image: var(--p-top-fade);
}
.pscene.p-travel .p-handle { --p-up: calc(var(--p-h) + 8px); }
.pscene.p-travel .p-drawer[data-view='full'],
.pscene.p-travel:has(.p-drawer[data-view='full']) .p-drawer-shade {
  -webkit-mask-image:
    var(--p-top-fade),
    linear-gradient(to bottom, #000 calc(100% - var(--p-foot, 80px)), transparent);
  mask-image:
    var(--p-top-fade),
    linear-gradient(to bottom, #000 calc(100% - var(--p-foot, 80px)), transparent);
  -webkit-mask-composite: source-in;
  mask-composite: intersect;
}
/* Pat's lines grow the block down from its top (react.js startGrow): the
   top holds still, no easing, until the face rises. */
.pscene .p-block.p-growing { transition: none; }
