/*
 * MYAI companion.
 *
 * The design system in docs/mockups/DESIGN.md, applied to a personal record rather than to a
 * catalogue. Its grammar carries over directly: near-monochrome chrome, one ink, one soft surface,
 * pill-shaped actions on an 8px grid, and a deliberately small set of semantic accents that are
 * spent only where a state genuinely differs. Colour is never decoration here — a green chip means
 * the person actually holds this thing, a red one means something has moved without them.
 *
 * Display type is set in a geometric sans and worn in the system's voice: uppercase, tight leading,
 * unapologetically large. Everything below it stays quiet so the one line that names the page can
 * be loud.
 */

@import url("https://fonts.googleapis.com/css2?family=Jost:wght@400;500;600&family=Inter:wght@400;500;600&display=swap");

:root {
  --ink: #111111;
  --on-ink: #ffffff;
  --canvas: #ffffff;
  --surface: #f5f5f5;
  --surface-sunk: #ebebeb;
  --charcoal: #39393b;
  --ash: #4b4b4d;
  --mute: #707072;
  --stone: #9e9ea0;
  --hairline: #cacacb;
  --hairline-soft: #e5e5e5;

  /* Spent only on meaning: held, moved on, MYAI's own hand. */
  --held: #007d48;
  --held-wash: #e6f3ec;
  --caution: #d30005;
  --caution-wash: #fdeaea;
  --myai: #1151ff;
  --myai-wash: #e8eeff;

  --display: "Jost", "Futura", "Century Gothic", "Helvetica Neue", Arial, sans-serif;
  --text: "Inter", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;

  --xxs: 2px; --xs: 4px; --sm: 8px; --md: 12px; --lg: 18px; --xl: 24px; --xxl: 30px; --section: 48px;
  --r-sm: 18px; --r-md: 24px; --r-lg: 30px; --r-full: 9999px;

  --column: 520px;
  color-scheme: light;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ink: #f5f5f5;
    --on-ink: #111111;
    --canvas: #0c0c0d;
    --surface: #171718;
    --surface-sunk: #202022;
    --charcoal: #d8d8d9;
    --ash: #bcbcbe;
    --mute: #9e9ea0;
    --stone: #808083;
    --hairline: #35353a;
    --hairline-soft: #232326;
    --held: #1eaa52;
    --held-wash: #10251a;
    --caution: #ff6b6f;
    --caution-wash: #2a1416;
    --myai: #7fa2ff;
    --myai-wash: #131b32;
    color-scheme: dark;
  }
}

* { box-sizing: border-box; }

/*
 * Several elements below set `display`, which beats the user-agent rule for `[hidden]` and leaves a
 * hidden element on screen. The conditional chrome depends on `hidden` meaning hidden.
 */
[hidden] { display: none !important; }

body {
  margin: 0;
  background: var(--canvas);
  color: var(--ink);
  font-family: var(--text);
  font-size: 16px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

a { color: inherit; }

/* ------------------------------------------------------------------ chrome */

.chrome {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--md);
  padding: var(--md) var(--lg);
  background: var(--canvas);
  border-bottom: 1px solid var(--hairline-soft);
}

.brand {
  font-family: var(--display);
  font-weight: 600;
  font-size: 14px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  text-decoration: none;
}

.chrome-spacer { flex: 1; }

/*
 * The way back sits over the content, not in the frame — a breadcrumb, the way the design evidence
 * has it. It leaves the chrome to the two things that are about the whole surface rather than about
 * this page: what needs attention, and whose MYAI this is.
 */
.page-back {
  display: inline-flex;
  align-items: center;
  gap: var(--xs);
  border: 0;
  background: none;
  color: var(--mute);
  font: inherit;
  font-size: 14px;
  padding: 0;
  margin: 0 0 var(--md);
  cursor: pointer;
}

.page-back:hover { color: var(--ink); }

/*
 * The open thing's own two controls: the thing it is part of, and the way out of it.
 *
 * They read as one line because they are one decision — go up a level, or leave entirely — and
 * stacking them turned a pane with a parent into a pane with two back buttons, which is the shape
 * this whole model exists to get rid of.
 */
.detail-nav {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--md);
  margin: 0 0 var(--md);
}

.detail-nav .page-back { margin: 0; }

/* The parent is the more likely move from here, so it carries the weight and Close stays quiet. */
.page-up { color: var(--ink); }

/* Whose MYAI this is. One initial, because the answer is rarely in doubt and rarely needed. */
.account {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--hairline);
  border-radius: var(--r-full);
  font-family: var(--display);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--ash);
}

.account:hover { border-color: var(--ink); color: var(--ink); }

.account[aria-current="page"] { background: var(--ink); border-color: var(--ink); color: var(--on-ink); }

/* Signed out is a real state and says so, rather than showing an initial nobody has. It keeps
   saying so on the account page itself, where being current must not fill it in as though it were
   somebody. */
.account.is-signed-out { border-style: dashed; color: var(--stone); }

.account.is-signed-out[aria-current="page"] {
  background: var(--surface-sunk);
  border-color: var(--hairline);
  color: var(--ash);
}

/* The From MYAI attention: conditional, never a permanent destination. */
.attention {
  display: inline-flex;
  align-items: center;
  gap: var(--sm);
  min-width: 0;
  padding: 6px var(--md);
  border-radius: var(--r-full);
  background: var(--myai-wash);
  color: var(--ink);
  font-size: 13px;
  font-weight: 500;
  text-decoration: none;
  /* The chrome has three tenants on a phone. This one yields first. */
  overflow: hidden;
}

/* The words, which are what actually shortens. The dot beside them never does. */
.attention-label {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.attention::before {
  content: "";
  flex: 0 0 auto;
  width: 7px;
  height: 7px;
  border-radius: var(--r-full);
  background: var(--myai);
}

/*
 * Two panes, and where there is room for both they are both always there.
 *
 * The layout used to appear when a record opened: one 520px centred column became a 1240px grid, so
 * tapping a title changed the width of the page under the person's hand and tapping Close changed
 * it back. The panes were right and their arrival was the problem — a surface that resizes itself
 * as you read it is one you have to re-find your place in every time.
 *
 * So on a wide screen the list keeps one measure whether or not something is open, and the second
 * pane is simply empty until it holds something. That is also what teaches the shape: the place
 * things open is visible before anything has been opened.
 *
 * On a narrow screen the record still takes the column, exactly as it always has. One thing at a
 * time is what a phone wants, and it needs no overlay, no positioning and no trapped scroll.
 */
/*
 * The rail, the list and the detail. Below two panes the rail is a dock and the shell is the column
 * it always was, so everything here is additive to a layout a phone already understood.
 */
.shell {
  display: block;
}

.panes {
  max-width: var(--column);
  margin: 0 auto;
  padding: var(--xl) var(--lg) 104px;
}

#detail:empty { display: none; }

/* Below two panes the record has the column to itself, and the list is one Close away. */
@media (max-width: 1023.98px) {
  body.has-detail #view { display: none; }
}

@media (min-width: 1024px) {
  .panes {
    max-width: 1240px;
    display: grid;
    /* The list keeps a readable measure; the record gets the room its bands need. */
    grid-template-columns: minmax(0, 1fr) minmax(0, 560px);
    gap: var(--section);
    align-items: start;
  }

  #view {
    /* The list stays put while the record scrolls, which is what makes clicking through cheap. */
    position: sticky;
    top: calc(var(--xl) + 32px);
    max-height: calc(100vh - 190px);
    overflow-y: auto;
    /* The scrollbar is the pane's own business and should not crowd the rows. */
    scrollbar-width: thin;
    padding-right: var(--sm);
  }

  /* The pane the record lives in needs no frame; the whitespace between them is the separation. */
  #detail { min-width: 0; }

  /*
   * The empty pane says what it is for, once, quietly.
   *
   * It holds its track either way — that is the point of it being permanent — so this is not there
   * to fill a hole. It is there because a pane that is blank until the first time you happen to tap
   * a title is a part of the surface nobody has been told about.
   */
  #detail:empty {
    display: block;
    /* Roughly level with the first row of the list, rather than floating at the top of the track. */
    padding-top: calc(var(--xl) + 32px);
  }

  #detail:empty::after {
    content: "Nothing open.";
    display: block;
    color: var(--mute);
    font-size: 14px;
  }

  /*
   * The record pane holds its width for a record, not for the sentence saying there isn't one.
   *
   * An area page opens with nothing selected, so 480 pixels of "Nothing open." sat beside every
   * one of them — on a 1359px window that is the difference between two columns of cards and one.
   * The pane comes back the moment something is open, which is a reflow the person just asked for
   * by clicking a row.
   */
  body:not(.has-detail) .panes { grid-template-columns: minmax(0, 1fr); }
  body:not(.has-detail) #detail { display: none; }

  /* Before sign-in there is nothing to open, so the surface keeps its one column and says so. */
  body.is-signed-out .panes {
    max-width: var(--column);
    display: block;
  }

  body.is-signed-out #detail { display: none; }
}

/*
 * Wide enough for the rail as well as both panes.
 *
 * Three regions need more than two panes do, and the one that suffers when they are squeezed is the
 * list — which is the region doing the most work. So the rail waits for the room to exist rather
 * than taking it from the rows. Between here and the two-pane width the dock is still the dock, and
 * Home still carries the areas as doors.
 */
@media (min-width: 1180px) {
  .shell {
    display: grid;
    /* The rail is as wide as the longest area name needs and not one pixel wider. */
    grid-template-columns: 188px minmax(0, 1fr);
    gap: var(--lg);
    max-width: 1468px;
    margin: 0 auto;
    align-items: start;
  }

  .panes {
    /* The rail has taken its share, so the two panes divide what is left rather than the window. */
    grid-template-columns: minmax(0, 1fr) minmax(0, 480px);
    gap: var(--xl);
  }

  /*
   * The areas are in the rail here, so Home does not list them a second time.
   *
   * They are not removed from Home's markup, because below this width the rail has no room for them
   * and the doors are how the areas stay reachable. One set of doors, two placements.
   */
  .home-areas { display: none; }

  /* The rail is the way to an area and the way out of it, so the page needs no second one. */
  .page-back.is-rail-destination { display: none; }

  body.is-signed-out .shell { display: block; }
}

/*
 * The row you are reading, in the list you came from.
 *
 * Only meaningful when both panes are visible: on a narrow screen the list is not on screen to mark.
 */
@media (min-width: 1024px) {
  body.has-detail #view .row[aria-current="page"],
  body.has-detail #view .tie[aria-current="page"] {
    background: var(--surface-sunk);
    box-shadow: inset 3px 0 0 var(--ink);
  }
}

/*
 * A membership correction changes whether this record participates in future lists, not whether the
 * current list snapshot happened. Keep the row where the person was looking at it and make the
 * exclusion visible until that list is read again.
 */
#view .row.is-excluded-record,
#view .row-card.is-excluded-record {
  opacity: 0.5;
}

/* -------------------------------------------------------------------- type */

.display {
  font-family: var(--display);
  font-weight: 600;
  font-size: clamp(38px, 12vw, 56px);
  line-height: 0.9;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  margin: 0 0 var(--lg);
}

.page-kicker {
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--stone);
  margin: 0 0 var(--sm);
}

.section-label {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--stone);
  margin: var(--xxl) 0 var(--md);
}

.section-label:first-child { margin-top: 0; }

.lede { color: var(--ash); margin: 0 0 var(--lg); }

.note { color: var(--mute); font-size: 14px; margin: var(--sm) 0 0; }

.quiet { color: var(--stone); font-size: 14px; }

.dash { color: var(--stone); }

/* ------------------------------------------------------------------ search */

.find-field {
  display: flex;
  align-items: center;
  gap: var(--md);
  padding: var(--md) var(--lg);
  border: 1px solid var(--hairline);
  border-radius: var(--r-full);
  background: var(--canvas);
}

.find-field:focus-within { border-color: var(--ink); }

.find-field input {
  flex: 1;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  padding: 0;
  min-width: 0;
}

.find-field input:focus { outline: none; }

.find-field input::placeholder { color: var(--stone); }

/*
 * The browser's own clear affordance, removed.
 *
 * `type="search"` is the right semantics and comes with a WebKit cancel button that this surface
 * cannot style, cannot place, and does not need — there is one beside it that matches everything
 * else here and works the same way on every engine. Two of them side by side is the tell of a
 * control nobody decided about.
 */
.find-field input::-webkit-search-cancel-button,
.find-field input::-webkit-search-decoration { display: none; -webkit-appearance: none; }

/*
 * Clear, and Find, at the trailing edge of the field.
 *
 * Find is visible now. It was `sr-only`, so the only way to run a search was to already know that
 * Return would do it.
 */
.find-clear {
  flex: none;
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border: 0;
  border-radius: var(--r-full);
  background: var(--surface-sunk);
  color: var(--mute);
  font: inherit;
  font-size: 15px;
  line-height: 1;
  padding: 0;
  cursor: pointer;
}

.find-clear:hover { color: var(--ink); }

.find-submit {
  flex: none;
  border: 0;
  border-radius: var(--r-full);
  background: var(--ink);
  color: var(--on-ink);
  font: inherit;
  font-size: 14px;
  font-weight: 500;
  padding: 6px var(--lg);
  margin: calc(var(--md) * -1 + 4px) calc(var(--lg) * -1 + 6px) calc(var(--md) * -1 + 4px) 0;
  cursor: pointer;
}

.find-submit:hover { background: var(--charcoal); }

/*
 * Going out to the world, which is a move the person makes rather than something that happens to
 * them. Full width and quiet: it is an offer at the end of an answer, not a call to action.
 */
.world-search,
.world-more {
  display: block;
  width: 100%;
  margin: var(--lg) 0 var(--sm);
  border: 1px solid var(--hairline);
  border-radius: var(--r-full);
  background: var(--canvas);
  color: var(--ink);
  font: inherit;
  font-size: 14px;
  font-weight: 500;
  padding: 10px var(--lg);
  cursor: pointer;
}

.world-search:hover,
.world-more:hover { background: var(--surface); }

/* Running, said where the work is happening rather than by repainting the page to `Looking…`. */
.world-search.is-running {
  color: var(--mute);
  border-style: dashed;
  cursor: default;
}

.world-more {
  margin-top: var(--md);
  border-color: var(--hairline-soft);
  color: var(--ash);
}

/* What MYAI honestly knows to offer an empty box: the things this person actually searched for. */
.recent-searches {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sm);
  margin: 0 0 var(--xl);
  padding: 0;
  list-style: none;
}

.recent-search {
  border: 1px solid var(--hairline-soft);
  background: var(--surface);
  color: var(--ash);
  border-radius: var(--r-full);
  padding: 6px var(--md);
  font: inherit;
  font-size: 14px;
  cursor: pointer;
}

.recent-search:hover { color: var(--ink); }

.scopes {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sm);
  margin: var(--md) 0 var(--xl);
  padding: 0;
  list-style: none;
}

.scope {
  border: 1px solid var(--hairline-soft);
  background: var(--surface);
  color: var(--ash);
  border-radius: var(--r-full);
  padding: 6px var(--md);
  font: inherit;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
}

.scope[aria-pressed="true"] {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--on-ink);
}

/* ------------------------------------------------------------------ tiles */

.doors {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--md);
}

.door {
  display: block;
  padding: var(--lg);
  border-radius: var(--r-md);
  background: var(--surface);
  text-decoration: none;
  min-height: 104px;
}

.door:hover { background: var(--surface-sunk); }

.door h3 {
  font-family: var(--display);
  font-size: 20px;
  font-weight: 500;
  margin: 0 0 var(--xs);
  text-transform: uppercase;
  letter-spacing: 0.01em;
}

.door p { margin: 0; font-size: 14px; color: var(--ash); }

/* -------------------------------------------------------------------- rows */

.rows { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sm); }

/*
 * An area's bands, as a survey rather than a column.
 *
 * These lists answer "what should I look at", and a single column of tall cards answers it three
 * rows at a time: on a 1359px window the television area showed three of twelve, and the rest was
 * scrolling. `auto-fill` does the deciding, so nothing needs a breakpoint — a phone gets one
 * column because two of these will not fit, and a wide window gets as many as the measure allows.
 *
 * 340px is the width below which these cards stop working: the poster, an episode sentence and a
 * provider line need it, and narrower columns wrap the sentence to three lines and win nothing.
 */
.rows-grid { grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); }

/*
 * Cards beside each other should end together; ragged bottoms read as broken rather than airy.
 *
 * The card is the column and its body is what stretches. Giving the body `height: 100%` instead
 * pushed the controls past the bottom of the card, and with the card clipping its overflow the
 * dismiss button simply was not there on three cards out of four.
 */
.rows-grid > li { height: 100%; display: flex; flex-direction: column; }
.rows-grid > li > .row { flex: 1 1 auto; }

.row {
  display: flex;
  align-items: flex-start;
  gap: var(--md);
  padding: var(--md) var(--lg);
  border-radius: var(--r-md);
  background: var(--surface);
  text-decoration: none;
}

.row:hover { background: var(--surface-sunk); }

/* A world result: same row, same tap, visibly no history yet. */
.row.is-world {
  background: var(--canvas);
  border: 1px dashed var(--hairline);
}

.row-body { flex: 1; min-width: 0; }

.row-title { font-weight: 500; margin: 0; }

.row-sub { margin: var(--xxs) 0 0; font-size: 14px; color: var(--mute); }

/*
 * Why this row entered the person's attention.
 *
 * Blue belongs to MYAI-authored work in this system. The line is deliberately smaller than standing:
 * "Want to read" is the person's current relationship; "Suggested by MYAI today" is provenance for
 * why this particular row deserves another look.
 */
.row-origin { margin: var(--xs) 0 0; }
.row-origin-label {
  margin: 0;
  color: var(--myai);
  font-size: 12px;
  font-weight: 600;
}
.row-origin-detail {
  margin: 3px 0 0;
  color: var(--ash);
  font-size: 13px;
  line-height: 1.35;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.row-standing { margin: var(--xs) 0 0; font-size: 14px; color: var(--charcoal); }

.thumb {
  width: 44px;
  height: 62px;
  flex: 0 0 auto;
  border-radius: 6px;
  background: var(--surface-sunk);
  object-fit: cover;
}

.row.is-world .thumb { border: 1px dashed var(--hairline); background: transparent; }

/* ------------------------------------------------------------------- chips */

.chips { display: flex; flex-wrap: wrap; gap: var(--xs); margin: var(--sm) 0 0; padding: 0; list-style: none; }

.chip {
  border-radius: var(--r-full);
  padding: 3px 10px;
  font-size: 12px;
  font-weight: 500;
  background: var(--surface-sunk);
  color: var(--ash);
  white-space: nowrap;
}

.chip.is-held { background: var(--held-wash); color: var(--held); }
.chip.is-caution { background: var(--caution-wash); color: var(--caution); }
.chip.is-open { background: var(--surface-sunk); color: var(--ash); }
.chip.is-neutral { background: transparent; color: var(--mute); border: 1px solid var(--hairline-soft); }

/* ------------------------------------------------------------------ record */

.record-head { display: flex; gap: var(--lg); align-items: flex-start; margin-bottom: var(--xl); }

.record-head .thumb { width: 92px; height: 132px; border-radius: var(--sm); }

.record-title {
  font-family: var(--display);
  font-size: clamp(28px, 8vw, 38px);
  line-height: 1;
  font-weight: 600;
  margin: 0 0 var(--xs);
  text-transform: uppercase;
}

.record-sub { margin: 0; font-size: 14px; color: var(--mute); }

/* The banner's own row: art, identity, and the rating pinned to the far corner. */
.record-head .row-body { flex: 1 1 auto; min-width: 0; }

/*
 * Your rating, in the corner, as a control.
 *
 * Large enough to read at a glance across a room and still a tap target on a phone, which is the
 * whole reason it is a number and not a row of stars.
 */
.rating-badge {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  min-width: 58px;
  padding: var(--sm) var(--md);
  border: 1px solid var(--hairline);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  cursor: pointer;
}

.rating-badge:hover:not(:disabled) { border-color: var(--ink); }
.rating-badge:disabled { cursor: default; opacity: 0.6; }

.rating-badge-value { font-family: var(--display); font-size: 22px; font-weight: 600; line-height: 1; }
.rating-badge-label { font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--mute); }
.rating-badge.is-unrated .rating-badge-value { font-size: 14px; color: var(--mute); }

/*
 * Where the series is against where you are.
 *
 * The pair is the point, so they share a grid and the keys line up: a glance down the left column
 * reads `Latest / Last watched`, and the values beside them are the comparison.
 */
.progress {
  display: grid;
  gap: 2px;
  margin-top: var(--md);
}

.progress-line {
  display: grid;
  grid-template-columns: 68px minmax(0, 1fr) auto;
  align-items: baseline;
  gap: var(--sm);
  margin: 0;
  padding: var(--xs) 0;
  text-align: left;
  font: inherit;
  font-size: 14px;
  color: var(--ink);
}

.progress-key {
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--mute);
}

.progress-value { min-width: 0; }

/* The one that is yours is the one that does something, so it is the one that looks touchable. */
button.progress-line {
  width: 100%;
  border: 0;
  border-top: 1px solid var(--hairline-soft);
  background: none;
  cursor: pointer;
}

button.progress-line:disabled { cursor: default; }
button.progress-line:disabled .progress-edit { display: none; }

.progress-edit {
  font-size: 12px;
  color: var(--myai);
  white-space: nowrap;
}

button.progress-line:hover:not(:disabled) .progress-value { text-decoration: underline; }


/*
 * Checking the catalogue: available always, insistent never.
 *
 * It sits under the banner's own lines because that is where somebody notices the page is missing
 * something — and it is a maintenance action, not one of the two things they came to do.
 */
.record-refresh { margin-top: var(--sm); color: var(--mute); }
.record-refresh:hover { color: var(--held); }

/*
 * On a phone the banner is the screen, so the art gives up its width rather than the words.
 */
@media (max-width: 520px) {
  .record-head { gap: var(--md); }
  .record-head .thumb { width: 64px; height: 92px; }
  .progress-line { grid-template-columns: 60px minmax(0, 1fr) auto; }
}

.unseen-dot { color: var(--held); font-size: 10px; margin-right: var(--sm); vertical-align: 1px; }

/* Standing leads the page: what the person's relationship currently is, before any metadata. */
.standing {
  padding: var(--lg);
  border-radius: var(--r-md);
  background: var(--held-wash);
  margin-bottom: var(--lg);
}

.standing.is-unknown {
  background: var(--canvas);
  border: 1px dashed var(--hairline);
}

/*
 * Detail renders the same standing a list row does, through the same function, at a larger size.
 *
 * The blanket `margin: 0` this replaces was written when Detail composed its own two elements; it
 * would now collapse the six the shared renderer produces into one another. So the parts keep the
 * rhythm they were given and only the first loses its leading space.
 */
.standing > :first-child { margin-top: 0; }

.standing .row-standing {
  margin-top: 0;
  font-size: 17px;
  line-height: 1.5;
  color: var(--ink);
}

.standing .row-timing,
.standing .row-watch,
.standing .row-friends { font-size: 15px; }

/* Detail has room, so the service marks can be read rather than merely recognised. */
.standing .watch-mark { width: 26px; height: 26px; }

.record-meaning { margin: var(--xl) 0; }
.record-meaning .section-label { margin-top: 0; }
.meaning-card { padding: var(--lg) 0; border-bottom: 1px solid var(--hairline-soft); }
.meaning-card:first-of-type { padding-top: 0; }
.meaning-card blockquote { margin: 0; font-size: 18px; line-height: 1.55; }
.meaning-card p { margin: 0; font-size: 17px; line-height: 1.55; }
.myai-meaning { padding: var(--lg); border-radius: var(--r-md); background: var(--surface); }
.meaning-actions { display: flex; gap: var(--md); margin-top: var(--sm); }
/* MYAI's own writing about the thing: a title, then who wrote it and what from. */
.meaning-title { margin: 0 0 var(--xs); font-family: var(--display); font-size: 17px; }
.meaning-caption { display: block; margin-top: var(--sm); color: var(--mute); font-size: 13px; }
.meaning-card .meaning-sources { display: flex; flex-wrap: wrap; gap: var(--md); margin-top: var(--sm); }

/*
 * Personalized fit is model-authored meaning expressed through native Record Detail hierarchy.
 * It is intentionally not a card inside the MYAI band: the thesis leads, compact factors support
 * it, and caution recedes without becoming hidden. The model supplied none of this geometry.
 */
.personalized-analysis { padding: var(--xl); }
.analysis-title {
  margin: 0 0 var(--sm);
  font-family: var(--display);
  font-size: 22px;
  line-height: 1.2;
}
.personalized-analysis .analysis-thesis {
  margin: 0;
  font-size: 20px;
  line-height: 1.45;
  letter-spacing: -0.01em;
}
.analysis-points { margin-top: var(--xl); }
.analysis-points h3,
.analysis-followups h3 {
  margin: 0 0 var(--md);
  font-family: var(--display);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.analysis-points dl { margin: 0; display: grid; gap: var(--md); }
.analysis-points dt { font-size: 15px; font-weight: 600; }
.analysis-points dd { margin: 2px 0 0; color: var(--ash); font-size: 15px; line-height: 1.5; }
.analysis-points.is-friction {
  padding-top: var(--lg);
  border-top: 1px solid var(--hairline);
}
.analysis-points.is-friction dt { color: var(--ash); }
.analysis-followups { margin-top: var(--xl); }
.analysis-followups ul { margin: 0; padding-left: 1.15em; color: var(--ash); font-size: 15px; }
.analysis-confidence { margin-top: var(--lg) !important; color: var(--mute); font-size: 13px !important; }
.analysis-prose { margin-top: var(--lg); border-top: 1px solid var(--hairline); padding-top: var(--md); }
.analysis-prose summary { cursor: pointer; color: var(--mute); font-size: 14px; }
.analysis-prose .meaning-body { margin-top: var(--md); }

@media (max-width: 520px) {
  .personalized-analysis { padding: var(--lg); }
  .personalized-analysis .analysis-thesis { font-size: 18px; }
}

/*
 * Structure inside a piece of MYAI's writing.
 *
 * A guide arrives as headings, numbered points and the occasional table, and it is read rather than
 * glanced at — so the spacing here is generous enough to separate points and quiet enough that the
 * card still reads as one piece of writing rather than as a document embedded in the page.
 */
.meaning-card p + p,
.meaning-card .rich-list + p,
.meaning-card .rich-quote + p,
.meaning-card .rich-code + p,
.meaning-card .rich-table + p { margin-top: var(--md); }
/* The height clamp counts in this element's own lines, so it carries the text metrics its children
   are set in rather than inheriting a different em from the page. */
.meaning-body { font-size: 17px; line-height: 1.55; }
.meaning-card .rich-heading {
  margin: var(--lg) 0 var(--sm);
  font-family: var(--display); font-size: 15px; font-weight: 600; letter-spacing: 0.01em;
}
.meaning-card .rich-heading:first-child { margin-top: 0; }
.meaning-card .rich-list { margin: var(--md) 0 0; padding-left: 1.25em; font-size: 17px; line-height: 1.55; }
.meaning-card .rich-list li + li { margin-top: var(--xs); }
.meaning-card .rich-list .rich-list { margin-top: var(--xs); font-size: 16px; }
.meaning-card .rich-quote {
  margin: var(--md) 0 0; padding-left: var(--md);
  border-left: 2px solid var(--hairline); color: var(--ash);
}
.meaning-card .rich-code {
  margin: var(--md) 0 0; padding: var(--md); overflow-x: auto;
  border-radius: var(--r-sm); background: var(--surface-sunk);
  font-size: 14px; line-height: 1.5; white-space: pre-wrap; word-break: break-word;
}
.meaning-card code { font-size: 0.95em; }
.meaning-card .rich-rule { margin: var(--lg) 0; border: 0; border-top: 1px solid var(--hairline-soft); }
.meaning-card .rich-table {
  width: 100%; margin-top: var(--md); border-collapse: collapse; font-size: 15px; line-height: 1.45;
}
.meaning-card .rich-table th,
.meaning-card .rich-table td {
  padding: var(--xs) var(--sm) var(--xs) 0; border-bottom: 1px solid var(--hairline-soft);
  text-align: left; vertical-align: top;
}
.meaning-card .rich-table th { color: var(--mute); font-weight: 500; }
.meaning-card .rich-link { color: var(--held); }

/*
 * Long content, open at a readable height.
 *
 * Two clamps rather than one, because the browser can count lines only where there are lines to
 * count: a synopsis or a note is one run of prose and gets `-webkit-line-clamp`, which ends it on
 * an ellipsis; a guide is headings and a list and gets a height bound with a fade, since a box of
 * blocks has no line count to clamp. Both are set by `clamp.js`, and only on regions it measured as
 * actually overrunning — the control never appears over content that already fits.
 */
.clampable.is-clamped { position: relative; overflow: hidden; cursor: pointer; }
.clampable.clamp-lines.is-clamped {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: var(--clamp-lines, 8);
}
.clampable.clamp-height.is-clamped { max-height: calc(var(--clamp-lines, 12) * 1.55em); }
/* The fade says the text continues past the cut. Only the height clamp needs it: a line clamp ends
   on an ellipsis, and a pseudo-element inside a -webkit-box is drawn as one of its lines. */
.clampable.clamp-height.is-clamped::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 2.4em;
  background: linear-gradient(to bottom, transparent, var(--clamp-fade, var(--canvas)));
  pointer-events: none;
}
.clamp-toggle { display: block; margin-top: var(--sm); }
/* The fade has to end in whatever the region is sitting on, or it fades to the wrong colour. */
.myai-meaning .clampable.is-clamped,
.candidate .clampable.is-clamped,
.ask-candidate .clampable.is-clamped { --clamp-fade: var(--surface); }
.text-action { border: 0; padding: 0; background: transparent; color: var(--held); font: inherit; font-size: 13px; cursor: pointer; }
.text-action.is-danger { color: var(--caution); }
/*
 * Where you stand, as a set of statements rather than a form field.
 *
 * Every option visible at once, because this is a choice between four or five things somebody is
 * deciding among — a select hides all but one behind a tap and makes stating a standing feel like
 * filling something in. One shape for every domain; only the words differ.
 */
.standing-options { display: grid; gap: var(--xs); margin-bottom: var(--md); }
.standing-option {
  display: flex; align-items: flex-start; gap: var(--md);
  padding: var(--md); border: 1px solid var(--hairline); border-radius: var(--r-sm);
  background: var(--canvas); cursor: pointer;
}
.standing-option:has(input:checked) { border-color: var(--ink); background: var(--surface); }
.standing-option:has(input:focus-visible) { outline: 2px solid var(--myai); outline-offset: 2px; }
.standing-option input { margin: 3px 0 0; accent-color: var(--ink); }
.standing-option-body { display: grid; gap: 2px; }
.standing-option-label { font-size: 15px; color: var(--ink); }
.standing-option-note { font-size: 13px; color: var(--mute); }

.panel select, .panel input[type="number"] {
  display: block; width: 100%; margin: 0 0 var(--md); padding: 10px var(--md);
  border: 1px solid var(--hairline); border-radius: var(--r-sm); background: var(--canvas); font: inherit;
}

.moves { display: flex; flex-wrap: wrap; gap: var(--sm); margin-bottom: var(--xl); }

.button {
  border: 1px solid var(--ink);
  background: var(--ink);
  color: var(--on-ink);
  border-radius: var(--r-full);
  padding: 10px var(--lg);
  font: inherit;
  font-size: 15px;
  font-weight: 500;
  cursor: pointer;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: var(--sm);
}

.button.secondary { background: var(--canvas); color: var(--ink); border-color: var(--hairline); }
.button.secondary:hover { border-color: var(--ink); }
.button.small { padding: 6px var(--md); font-size: 13px; }
.button[disabled] { opacity: 0.4; cursor: not-allowed; }

.ties { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--xs); }

/* One line that reads as a sentence: what the tie is, then what it says. Long details wrap under
   the label rather than squeezing it into a two-word column. */
.tie {
  display: block;
  padding: var(--md) var(--lg);
  border-radius: var(--r-sm);
  background: var(--surface);
  text-decoration: none;
  font-size: 15px;
}

.tie .tie-detail { color: var(--mute); font-size: 14px; }

/* Trace collapses to one line with three marks, and opens only if asked. */
details.trace {
  margin-top: var(--xl);
  border-top: 1px solid var(--hairline-soft);
  padding-top: var(--md);
}

details.trace summary {
  cursor: pointer;
  font-size: 14px;
  color: var(--mute);
  list-style: none;
  display: flex;
  justify-content: space-between;
  gap: var(--md);
}

details.trace summary::-webkit-details-marker { display: none; }
details.trace summary::after { content: "Open"; color: var(--ink); font-weight: 500; }
details.trace[open] summary::after { content: "Close"; }

.trace-body { padding-top: var(--md); }

.trace-line {
  display: flex;
  justify-content: space-between;
  gap: var(--md);
  padding: var(--sm) 0;
  border-bottom: 1px solid var(--hairline-soft);
  font-size: 14px;
}

.trace-line span:last-child { color: var(--mute); white-space: nowrap; }

.refs {
  margin: var(--md) 0 0;
  padding: var(--md);
  border-radius: var(--r-sm);
  background: var(--surface);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  color: var(--mute);
  overflow-x: auto;
  white-space: pre-wrap;
  word-break: break-all;
}

/* ---------------------------------------------------------------- timeline */

.day { margin-bottom: var(--xxl); }

.day-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sm) var(--md);
  margin-bottom: var(--md);
}

.day-head h2 {
  font-family: var(--display);
  font-size: 18px;
  font-weight: 600;
  letter-spacing: 0.06em;
  margin: 0;
  text-transform: uppercase;
  white-space: nowrap;
}

.day-head span { font-size: 13px; color: var(--mute); }

.moments { list-style: none; margin: 0; padding: 0; }

.moment {
  position: relative;
  padding: 0 0 var(--lg) var(--xl);
  border-left: 1px solid var(--hairline-soft);
}

.moment:last-child { border-left-color: transparent; }

/* Authorship is a gutter dot, so life and MYAI's own work interleave in true chronology. */
.moment::before {
  content: "";
  position: absolute;
  left: -5px;
  top: 6px;
  width: 9px;
  height: 9px;
  border-radius: var(--r-full);
  background: var(--canvas);
  border: 1.5px solid var(--stone);
}

.moment.is-record::before { border-color: var(--myai); background: var(--myai); }
.moment.is-cluster::before { border-color: var(--ink); background: var(--ink); }

.moment h3 { margin: 0; font-size: 16px; font-weight: 500; }
.moment p { margin: var(--xxs) 0 0; font-size: 14px; color: var(--mute); }

/* The clock, set back from the sentence so it reads as a margin note rather than part of it. */
.moment-time {
  margin-left: var(--sm);
  font-size: 12px;
  font-weight: 400;
  color: var(--stone);
  white-space: nowrap;
}

/* What a rolled-up member actually changed, under the sentence it changed it with. */
.member-detail { display: block; color: var(--stone); }

/* Timeline drilldowns name what a ref points at. The identifier itself stays in the DOM title for
   inspection, while a known Companion destination reads and behaves like navigation. */
.timeline-member-link {
  color: inherit;
  text-decoration: none;
}
.timeline-member-link:hover { text-decoration: underline; }

.timeline-references li { align-items: baseline; }
.timeline-reference {
  min-width: 0;
  overflow-wrap: anywhere;
  text-decoration: none;
}
a.timeline-reference {
  color: var(--myai);
  font-weight: 500;
}
a.timeline-reference:hover { text-decoration: underline; }
.timeline-reference-kind {
  color: var(--stone);
  font-size: 12px;
  white-space: nowrap;
}

/* The compression, kept and demoted: true, checkable, and no longer standing in for the day. */
.day-accounting {
  margin: calc(-1 * var(--xs)) 0 var(--md);
  font-size: 12px;
  color: var(--stone);
}

.moment details { margin-top: var(--xs); }
.moment summary { cursor: pointer; font-size: 13px; color: var(--myai); font-weight: 500; }
.moment summary::-webkit-details-marker { display: none; }

.evidence { list-style: none; margin: var(--sm) 0 0; padding: 0; display: grid; gap: var(--xxs); }

.evidence li {
  padding: var(--sm) var(--md);
  border-radius: 10px;
  background: var(--surface);
  font-size: 13px;
  color: var(--ash);
  display: flex;
  justify-content: space-between;
  gap: var(--md);
}

/* What the area holds that is open but not actionable. One line, and a quiet one. */
.resting {
  margin: var(--sm) 0 var(--xl);
  font-size: 13px;
  color: var(--stone);
}

/* ----------------------------------------------------------------- archive */

/* The whole record as an index of its years. Recent years stand alone; older ones open. */
.archive-years { list-style: none; margin: 0 0 var(--md); padding: 0; display: grid; gap: var(--xs); }

.archive-open {
  display: block;
  width: 100%;
  text-align: left;
  padding: var(--sm) var(--md);
  border: none;
  border-radius: 10px;
  background: var(--surface);
  cursor: pointer;
  font: inherit;
  color: inherit;
}

.archive-open:disabled { cursor: default; opacity: 0.6; }
.archive-open:not(:disabled):hover { background: var(--hairline-soft); }

/* The count wraps under the year rather than pushing the row wider than the pane it sits in. */
.archive-line { display: flex; align-items: baseline; justify-content: space-between; gap: var(--md); flex-wrap: wrap; }
.archive-line strong { font-size: 16px; font-weight: 600; }
.archive-line span { font-size: 13px; color: var(--mute); }

/* A band's own list inherits the page's width rather than the width of its longest title. */
.band .archive-years { min-width: 0; }
.band .archive-open { max-width: 100%; }

/* The same count the row already states, at a glance. Never a second, different number. */
.archive-bar {
  margin-top: var(--xs);
  height: 3px;
  border-radius: var(--r-full);
  background: var(--hairline-soft);
  overflow: hidden;
}

.archive-fill { height: 100%; background: var(--ink); border-radius: var(--r-full); }

.archive-names {
  margin: var(--xs) 0 0;
  font-size: 12px;
  color: var(--stone);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.band { margin-bottom: var(--sm); }

.band > summary {
  cursor: pointer;
  padding: var(--sm) var(--md);
  border-radius: 10px;
  font-size: 14px;
  font-weight: 500;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--md);
}

.band > summary::-webkit-details-marker { display: none; }
.band > summary:hover { background: var(--surface); }
.band[open] > summary { margin-bottom: var(--xs); }
.band-detail { font-size: 13px; font-weight: 400; color: var(--stone); }

/* ---------------------------------------------------------------- artifact */

.artifact-head { margin-bottom: var(--xl); }

.maintained {
  display: inline-flex;
  align-items: center;
  gap: var(--sm);
  font-size: 13px;
  color: var(--mute);
  margin-bottom: var(--sm);
}

.maintained::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: var(--r-full);
  background: var(--myai);
}

.pick {
  padding: var(--lg);
  border-radius: var(--r-md);
  background: var(--held-wash);
  margin-bottom: var(--md);
}

.pick .page-kicker { color: var(--held); }
.pick h3 { margin: 0 0 var(--xs); font-size: 20px; font-weight: 500; }
.pick p { margin: 0; font-size: 14px; color: var(--charcoal); }

.candidate {
  padding: var(--md) var(--lg);
  border-radius: var(--r-md);
  background: var(--surface);
  display: grid;
  gap: var(--xs);
}

.candidate-head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--md); }
.candidate-head h3 { margin: 0; font-size: 17px; font-weight: 500; }
.candidate p { margin: 0; font-size: 14px; color: var(--mute); }

.candidate.is-ruled-out { opacity: 0.55; }
.candidate.is-ruled-out h3 { text-decoration: line-through; }

.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(96px, 1fr)); gap: var(--md); margin-bottom: var(--xl); }

.stat { padding: var(--md); border-radius: var(--r-sm); background: var(--surface); }
.stat strong { display: block; font-family: var(--display); font-size: 28px; font-weight: 600; line-height: 1; }
.stat span { font-size: 12px; color: var(--mute); }

.months { display: flex; align-items: flex-end; gap: 3px; height: 88px; margin-bottom: var(--sm); }
.month { flex: 1; background: var(--ink); border-radius: 3px 3px 0 0; min-height: 2px; }
.month.is-empty { background: var(--hairline-soft); }

.month-axis { display: flex; gap: 3px; font-size: 10px; color: var(--stone); margin-bottom: var(--xl); }
.month-axis span { flex: 1; text-align: center; }

/* ------------------------------------------------------------------ panels */

.panel {
  padding: var(--lg);
  border-radius: var(--r-md);
  background: var(--surface);
  margin-top: var(--lg);
}

.panel h3 { margin: 0 0 var(--sm); font-size: 15px; font-weight: 600; }
.panel p { margin: 0 0 var(--sm); font-size: 14px; color: var(--ash); }
.panel p:last-child { margin-bottom: 0; }

.panel textarea {
  width: 100%;
  min-height: 148px;
  border: 1px solid var(--hairline);
  border-radius: var(--r-sm);
  background: var(--canvas);
  color: inherit;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px;
  line-height: 1.5;
  padding: var(--md);
  resize: vertical;
}

.panel textarea:focus { outline: none; border-color: var(--ink); }

.disclosure { list-style: none; margin: 0 0 var(--md); padding: 0; display: grid; gap: var(--xs); }

.disclosure li { font-size: 13px; color: var(--mute); display: grid; gap: 1px; }
.disclosure code { font-size: 12px; color: var(--ash); word-break: break-all; }

/* Honest absences. Quiet, present, never hidden. */
details.gaps { margin-top: var(--section); border-top: 1px solid var(--hairline-soft); padding-top: var(--md); }
details.gaps summary { cursor: pointer; font-size: 13px; color: var(--stone); }
details.gaps summary::-webkit-details-marker { display: none; }
details.gaps ul { margin: var(--md) 0 0; padding-left: var(--lg); }
details.gaps li { font-size: 13px; color: var(--mute); margin-bottom: var(--sm); }

/* The account surface: who MYAI thinks you are, and the only way out. */
.identity {
  display: flex;
  align-items: center;
  gap: var(--lg);
  padding: var(--lg);
  border-radius: var(--r-md);
  background: var(--surface);
  margin-bottom: var(--lg);
}

.identity .avatar {
  width: 52px;
  height: 52px;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-full);
  background: var(--ink);
  color: var(--on-ink);
  font-family: var(--display);
  font-size: 20px;
  font-weight: 600;
  text-transform: uppercase;
}

.identity.is-signed-out .avatar { background: transparent; border: 1px dashed var(--hairline); color: var(--stone); }

.identity h2 { margin: 0; font-size: 18px; font-weight: 500; }
.identity p { margin: var(--xxs) 0 0; font-size: 14px; color: var(--mute); }

.empty {
  padding: var(--xl) var(--lg);
  border-radius: var(--r-md);
  border: 1px dashed var(--hairline);
  color: var(--mute);
  font-size: 15px;
  text-align: center;
}

.banner {
  padding: var(--md) var(--lg);
  border-radius: var(--r-sm);
  background: var(--caution-wash);
  color: var(--caution);
  font-size: 14px;
  margin-bottom: var(--lg);
}

.banner.is-done { background: var(--held-wash); color: var(--held); }

/* -------------------------------------------------------------------- rail */

/*
 * Below two panes this is the bottom dock it has always been: three destinations, centred, thumb
 * height. The areas do not appear here — there is no room for a variable number of them at this
 * width, and Home carries them as doors instead.
 */
.rail {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 20;
  display: flex;
  justify-content: center;
  gap: var(--xs);
  padding: var(--sm) var(--lg) calc(var(--sm) + env(safe-area-inset-bottom, 0px));
  background: var(--canvas);
  border-top: 1px solid var(--hairline-soft);
}

.rail > a {
  flex: 1;
  max-width: 152px;
  text-align: center;
  padding: var(--sm) var(--md);
  border-radius: var(--r-full);
  text-decoration: none;
  font-size: 13px;
  font-weight: 500;
  color: var(--mute);
}

.rail a[aria-current="page"] { background: var(--surface); color: var(--ink); }

/* The areas belong to the rail, and the rail only exists as a rail where there is room for one. */
.rail-areas { display: none; }

@media (min-width: 1180px) {
  .rail {
    position: sticky;
    top: var(--xl);
    left: auto;
    right: auto;
    bottom: auto;
    z-index: 0;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: var(--xxs);
    padding: var(--xl) 0 0;
    background: none;
    border-top: 0;
  }

  .rail > a {
    flex: 0 0 auto;
    max-width: none;
    text-align: left;
    border-radius: var(--r-full);
    padding: var(--sm) var(--md);
    font-size: 14px;
  }

  .rail-areas {
    display: flex;
    flex-direction: column;
    gap: var(--xxs);
    /* The areas are a group inside the rail, not three more destinations in a flat list. */
    margin: var(--md) 0;
    padding: var(--md) 0;
    border-top: 1px solid var(--hairline-soft);
    border-bottom: 1px solid var(--hairline-soft);
  }

  .rail-areas a {
    display: block;
    padding: var(--sm) var(--md);
    border-radius: var(--r-full);
    text-decoration: none;
    color: var(--mute);
    font-size: 14px;
    font-weight: 500;
  }

  /*
   * The job the area currently holds, under its name.
   *
   * This is the label Home's doors carry, and it earns the same place here for the same reason: it
   * is what makes the rail worth reading rather than a list of nouns.
   */
  .rail-areas .rail-state {
    display: block;
    margin-top: 2px;
    font-size: 12px;
    font-weight: 400;
    color: var(--mute);
    opacity: 0.75;
  }
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ------------------------------------------------------------------ asks */

/*
 * A question MYAI is waiting on, which is not the same object as a row you might browse.
 *
 * It carries the same shape as a row so the page keeps one visual grammar, and one difference: the
 * MYAI mark down its edge, the same colour the attention badge uses. That is the whole signal —
 * this one is addressed to you, the others are just there.
 */
.ask {
  border-left: 2px solid var(--myai);
}

/* Why MYAI is asking, in its own words. Quieter than the ask, because it is the justification and
   not the question — but present, because an ask without a reason is a nag. */
.ask-because {
  margin: var(--xs) 0 0;
  font-size: 13px;
  line-height: 1.45;
  color: var(--stone);
}

/* An ask that can be settled here, settled here. */
.ask-answer {
  margin: var(--xs) 0 0 var(--lg);
  padding: 0 0 0 var(--md);
  border-left: 2px solid var(--hairline-soft);
}

.ask-candidates { list-style: none; margin: 0; padding: 0; }

.ask-candidate {
  display: flex;
  align-items: flex-start;
  gap: var(--md);
  padding: var(--xs) 0;
}

.ask-candidate + .ask-candidate { border-top: 1px solid var(--hairline-soft); }

.ask-candidate-body { flex: 1 1 auto; min-width: 0; }
.ask-candidate-label { margin: 0; font-size: 15px; color: var(--ink); }

/* Why MYAI put it forward — the thing that saves opening the decision to remember. */
.ask-candidate-why { margin: 2px 0 0; font-size: 13px; line-height: 1.45; color: var(--stone); }

/*
 * Quiet on purpose. Declining is reversible, low-stakes and one of several; a prominent control
 * would make elimination feel like the point of the screen rather than something available on it.
 */
.ask-decline {
  flex: 0 0 auto;
  padding: 4px var(--sm);
  font-size: 13px;
  color: var(--mute);
  background: none;
  border: 1px solid var(--hairline);
  border-radius: var(--r-full);
  cursor: pointer;
}

.ask-decline:hover { color: var(--ink); background: var(--surface-sunk); }
.ask-decline:disabled { opacity: 0.5; cursor: default; }

/* What ruling out does and does not mean, before the tap rather than after. */
.ask-scope { margin: var(--xs) 0 0; font-size: 12px; line-height: 1.4; color: var(--stone); }

.ask-failed { margin: var(--xs) 0 0; font-size: 13px; color: var(--caution); }

/* ------------------------------------------------------- world and friends */

/*
 * When the world next touches this. Above the person's own standing because an episode landing
 * tomorrow outranks what they did in March, and quiet unless it is close.
 */
.row-timing {
  margin: var(--xxs) 0 0;
  font-size: 14px;
  color: var(--mute);
}

.row-timing.is-soon {
  color: var(--myai);
  font-weight: 500;
}

/* What friends made of it. The mark is the only colour: the names carry the meaning. */
.row-friends {
  display: flex;
  align-items: baseline;
  gap: var(--xs);
  margin: var(--xs) 0 0;
  font-size: 14px;
  color: var(--charcoal);
}

.row-friends-mark {
  flex: 0 0 auto;
  font-size: 9px;
  line-height: 1.6;
  color: var(--myai);
}

/*
 * Which group a series is in today. Deliberately a small uppercase tag rather than a chip: the
 * chips on these rows are facts about the person (a rating, a run of episodes), and this is a
 * classification of the row itself, so it reads above them and in a different voice.
 *
 * Only the two actionable groups take colour. If `waiting` were also coloured — and it is the
 * majority of any real shelf — the colour would stop meaning anything.
 */
.row-availability {
  margin: 0 0 var(--xxs);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--stone);
}

.row-availability.is-available { color: var(--myai); }
.row-availability.is-upcoming { color: var(--charcoal); }

/*
 * Where to watch it. Quiet by default and only lit when the service is one the person already pays
 * for — an unlit line here is "you could subscribe to this", which is not worth anyone's attention.
 */
.row-watch {
  display: flex;
  align-items: center;
  gap: var(--xs);
  margin: var(--xs) 0 0;
  font-size: 14px;
  color: var(--mute);
}

.watch-marks {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 3px;
}

/*
 * Small, square and rounded, which is how every one of these services draws its own mark. A fixed
 * box rather than an intrinsic one: the catalog's art is not uniformly sized, and a row of logos
 * that each set their own height reads as a mistake.
 */
.watch-mark {
  width: 22px;
  height: 22px;
  object-fit: cover;
  border-radius: 5px;
  background: var(--surface-sunk);
}

.row-watch.is-mine { color: var(--charcoal); font-weight: 500; }

/* ------------------------------------------------------- the work itself */

/*
 * How much of a thing there is. Small caps and quiet: it is orientation, not news, and it sits
 * above the standing block so the page reads thing-then-you.
 */
.record-scale {
  margin: 0 0 var(--md);
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--stone);
}

/* What everyone else made of it, shown only beside what this person made of it. */
.record-crowd {
  display: flex;
  align-items: baseline;
  gap: var(--xs);
  margin: 0 0 var(--md);
  font-size: 14px;
  color: var(--mute);
}

.crowd-number { font-size: 17px; font-weight: 600; color: var(--charcoal); }
.crowd-gap { color: var(--stone); }
.crowd-gap.is-higher { color: var(--myai); }

.record-about {
  /* Catalogue copy arrives as text with blank lines where its paragraphs were, never as markup —
     see plainTextFromCatalogueHtml. This is what makes those breaks visible. */
  white-space: pre-line;
  margin: 0 0 var(--md);
  font-size: 15px;
  line-height: 1.6;
  color: var(--charcoal);
}

/* ------------------------------------------------------------- ratings plot */

/*
 * Everybody's score for one thing, on one axis. Ported from the MYAI app's ratings dot plot.
 *
 * Absolute positioning over a percentage scale rather than a flex row, because the whole claim the
 * drawing makes is horizontal: a dot's position *is* its value, so every mark on every line has to
 * be measured against the same track. The Flutter version computes those pixels and measures its
 * own text; here the percentages come from the same `ratingToPosition` the rating bar uses, and the
 * labels anchor to whichever side of the dot has room, which lets the browser do the measuring.
 */

:root {
  /* One set of three, used by the axis segments and by the dots that sit over them, so a mark can
     never be a different colour from the band it is standing in. */
  --plot-low: #a44143;
  --plot-mid: #d78e4e;
  --plot-high: #3b7f28;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --plot-low: #c2585a;
    --plot-mid: #e0a468;
    --plot-high: #4fa337;
  }
}

.plot {
  --plot-line: 20px;
  --plot-mark: 11px;
  margin: var(--sm) 0 0;
  /* The marks are placed at percentages of this box, so it has to be the box the axis spans. */
  position: relative;
}

.plot.is-detail { --plot-line: 22px; --plot-mark: 13px; margin: var(--md) 0 var(--lg); }

.plot-rows { position: relative; }

.plot-line {
  position: relative;
  height: var(--plot-line);
}

.plot-mark {
  position: absolute;
  top: calc((var(--plot-line) - var(--plot-mark)) / 2);
  width: var(--plot-mark);
  height: var(--plot-mark);
  margin-left: calc(var(--plot-mark) / -2);
  border-radius: 50%;
  background: var(--plot-mid);
}

.plot-mark.is-low { background: var(--plot-low); }
.plot-mark.is-mid { background: var(--plot-mid); }
.plot-mark.is-high { background: var(--plot-high); }

/*
 * The average, drawn as a triangle the way the app draws it.
 *
 * A different shape and not merely a different colour: it is the only mark on the plot that is not
 * somebody's rating, and a reader scanning a row of dots should not have to check a label to find
 * out which of them nobody actually gave. It points down at the axis, so it reads as an indicator
 * over the scale rather than as one more score sitting on it.
 */
.plot-mark.is-avg {
  width: 0;
  height: 0;
  border-radius: 0;
  background: none;
  border-left: calc(var(--plot-mark) / 2) solid transparent;
  border-right: calc(var(--plot-mark) / 2) solid transparent;
  border-top: var(--plot-mark) solid var(--plot-mid);
  border-bottom: none;
}

.plot-mark.is-avg.is-low { background: none; border-top-color: var(--plot-low); }
.plot-mark.is-avg.is-mid { background: none; border-top-color: var(--plot-mid); }
.plot-mark.is-avg.is-high { background: none; border-top-color: var(--plot-high); }

/* The span the friends cover, behind their dots. Grey on purpose: it is a shape, not a score. */
.plot-span {
  position: absolute;
  top: calc((var(--plot-line) - var(--plot-mark)) / 2 + 2px);
  height: calc(var(--plot-mark) - 4px);
  border-radius: var(--r-full);
  background: color-mix(in srgb, var(--mute) 22%, transparent);
}

/*
 * The label, anchored to the side of the dot that has room. Nothing wraps: a name that will not fit
 * is cut rather than pushed onto a second line, because a line is one rating and a two-line one
 * would slide every mark below it out of position.
 */
.plot-label {
  position: absolute;
  top: 0;
  max-width: 70%;
  line-height: var(--plot-line);
  font-size: 13px;
  color: var(--mute);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  padding-left: calc(var(--plot-mark) / 2 + 6px);
}

.plot-label.is-before {
  padding-left: 0;
  padding-right: calc(var(--plot-mark) / 2 + 6px);
  text-align: right;
}

.plot-name { color: var(--charcoal); }
.plot-phrase { white-space: pre; }
.plot-value { font-weight: 600; color: var(--charcoal); }

/* This person's own line, which is the one the others are there to be read against. */
.plot-line.is-strong .plot-name,
.plot-line.is-strong .plot-value { color: var(--ink); font-weight: 600; }

.plot-aside {
  margin: 0;
  line-height: var(--plot-line);
  font-size: 13px;
  color: var(--stone);
}

/*
 * The scale. Three segments with gaps rather than one gradient: the plot's job is to say whether a
 * score was a good one, and a band with an edge answers that at a glance where a gradient asks the
 * reader to judge a shade.
 */
.plot-axis {
  position: relative;
  height: 6px;
  margin: var(--xxs) 0;
}

.plot-segment {
  position: absolute;
  top: 0;
  height: 100%;
  border-radius: var(--r-full);
}

.plot-segment.is-low { background: var(--plot-low); }
.plot-segment.is-mid { background: var(--plot-mid); }
.plot-segment.is-high { background: var(--plot-high); }

.plot-rows.is-external { margin-top: var(--xxs); }
.plot-rows.is-external .plot-name { color: var(--mute); }

/* A card is smaller than a page and the plot shrinks with it rather than being left out of one. */
.row-card .plot { --plot-line: 18px; --plot-mark: 9px; }
.row-card .plot-label,
.row-card .plot-aside { font-size: 12px; }

/* ------------------------------------------------------------------ rating */

/*
 * The bar is not a linear scale and is not drawn as one. One to six occupies the first third and
 * six to ten the rest, because that is where most of what anybody finishes actually lands — a
 * linear track spends half its width on scores nobody gives.
 *
 * Ported from the year-in-review slider, including its gradient stops: the colour turns at roughly
 * the same place the words do, so "Good" and green arrive together.
 */
.rating { margin: var(--md) 0 var(--lg); }

.rating-readout {
  display: flex;
  align-items: baseline;
  gap: var(--xs);
  margin: 0 0 var(--sm);
}

.rating-number { font-size: 28px; font-weight: 600; line-height: 1; color: var(--ink); }
.rating-zone { font-size: 14px; color: var(--mute); }
.rating:not(.is-rated) .rating-number { color: var(--stone); }

/*
 * The number and what it means, as one thing.
 *
 * They were a number with a word trailing off its baseline, which reads as an annotation rather
 * than as the answer. Stacked and centred over the track, the pair is the thing the dialog is for.
 */
.rating-readout {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0;
  margin-bottom: var(--sm);
}

.rating-readout .rating-number { line-height: 1; }

.rating-readout .rating-zone {
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--mute);
}

.rating-track {
  position: relative;
  /*
   * What `100cqw` on the gradient inside is measured against.
   *
   * Without this there is no container to query and the unit falls back to the viewport, so on a
   * wide screen the gradient was laid out over 1280px while the track was 400px — the fill could
   * only ever reveal its first quarter, and every rating up to about 9 rendered somewhere in the
   * reds. The scale was right and the thing drawing it was measuring the wrong box.
   */
  container-type: inline-size;
  height: 28px;
  display: flex;
  align-items: center;
  touch-action: none;
  cursor: pointer;
}

.rating-track::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 12px;
  border-radius: var(--r-full);
  background: var(--surface-sunk);
}

.rating-track:focus-visible { outline: 2px solid var(--myai); outline-offset: 6px; border-radius: var(--r-full); }

/* The fill clips a full-width gradient rather than scaling one, so a given score is always the
   same colour whatever the width of the bar. */
.rating-fill {
  position: absolute;
  left: 0;
  height: 12px;
  width: 0;
  overflow: hidden;
  border-radius: var(--r-full);
  transition: width 0.18s ease-out;
}

.rating.is-dragging .rating-fill { transition: none; }
.rating:not(.is-rated) .rating-fill { opacity: 0; }

.rating-gradient {
  position: absolute;
  inset: 0;
  width: 100cqw;
  min-width: 320px;
  background: linear-gradient(to right, #931917 14%, #e8610d 24%, #d28b4d 36%, #3fe915 79%, #1e8700 100%);
}

.rating-thumb {
  position: absolute;
  top: 50%;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--surface);
  border: 4px solid var(--stone);
  box-shadow: 0 2px 8px rgb(0 0 0 / 30%);
  transform: translate(-50%, -50%);
  transition: left 0.18s ease-out, transform 0.1s ease-out;
  pointer-events: none;
}

.rating.is-rated .rating-thumb { border-color: var(--surface); }
.rating.is-dragging .rating-thumb { transform: translate(-50%, -50%) scale(1.15); transition: transform 0.1s ease-out; }

.rating-ticks { position: relative; height: 18px; margin-top: var(--xxs); }

.rating-tick {
  position: absolute;
  transform: translateX(-50%);
  font-size: 11px;
  font-weight: 600;
  color: var(--stone);
  user-select: none;
}

/* Season and episode, side by side, because they are one answer given in two parts. */
.caught-up-pick {
  display: flex;
  align-items: center;
  gap: var(--sm);
  margin: var(--xs) 0 var(--md);
}

.caught-up-pick select,
.caught-up-pick input {
  padding: var(--xs) var(--sm);
  font-size: 15px;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-sm);
}

.caught-up-pick input { width: 5.5rem; }
.caught-up-x { font-size: 14px; color: var(--mute); }

/* ------------------------------------------------- since you were last here */

/*
 * An event, not a shelf entry. The left edge carries the whole signal so the row stays as quiet as
 * every other row until something has actually happened — and only the two states worth acting on
 * get colour, because a band where everything is highlighted highlights nothing.
 */
.row.changed { border-left: 3px solid var(--hairline); }
.row.changed.is-available { border-left-color: var(--myai); }
.row.changed.is-airing { border-left-color: var(--charcoal); }
/*
 * Somebody else moved, which is worth noticing and is not something to act on tonight. Quieter than
 * the two above for that reason: the comment above says only what is worth acting on gets colour,
 * and a friend having watched something is news rather than a task.
 */
.row.changed.is-friends { border-left-color: var(--stone); }

.changed-title {
  margin: 0;
  font-size: 16px;
  font-weight: 500;
  line-height: 1.35;
  color: var(--ink);
}

/* ----------------------------------------------------------------- similar */

/*
 * Things like this one, from the person's own record.
 *
 * A scrolling strip rather than a grid: it is a glance, not a page, and its whole value is that
 * every cover in it is something they already recognise. It scrolls horizontally on purpose — the
 * row must never grow tall enough to push what they came for off the screen.
 */
.similar-strip {
  display: flex;
  gap: var(--md);
  overflow-x: auto;
  scrollbar-width: thin;
  padding-bottom: var(--sm);
  margin-bottom: var(--lg);
}

.similar-item {
  flex: 0 0 auto;
  width: 84px;
  text-decoration: none;
  color: inherit;
}

.similar-item .thumb {
  width: 84px;
  height: 120px;
  border-radius: var(--sm);
  margin-bottom: var(--xs);
}

.similar-title {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: 12px;
  line-height: 1.3;
  color: var(--mute);
}

.similar-item:hover .similar-title { color: var(--ink); }

/* ---------------------------------------------------------------- row card */

/*
 * A list row that can be acted on.
 *
 * The whole card still opens the record, because that is what a row has always done and what people
 * aim at. The link keeps that job by stretching over the card rather than by wrapping the controls —
 * a button inside an anchor is neither a button nor a link, and nesting them makes the browser guess
 * which one was meant. Here the controls simply sit above the stretched link and win the click.
 */
/*
 * The card owns the surface, not the link inside it.
 *
 * Leaving the background on the anchor left the controls sitting on the page below a grey box they
 * plainly belonged to. The card is the thing now; the link is one region of it, stretched over the
 * rest so that everywhere except the controls still opens the record.
 */
.row-card {
  position: relative;
  border-radius: var(--r-md);
  background: var(--surface);
}

.row-card:hover { background: var(--surface-sunk); }

.row-card > .row {
  position: static;
  background: none;
}

.row-card > .row:hover { background: none; }

.row-card > .row::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
}

/*
 * A line of its own, never a corner overlay.
 *
 * The obvious thing is to float these into the card's top-right, and it is wrong here: this list
 * lives in a pane, and a pane is narrow while the window is wide, so a viewport media query cannot
 * tell when there is room. Floated controls landed on top of the sentence beside them. A row that
 * always takes its own line is right at every width without having to know what width it is.
 */
.row-controls {
  position: relative;
  z-index: 1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--sm);
  padding: 0 var(--md) var(--md);
  margin-top: calc(-1 * var(--sm));
}

/* Where somebody is, and what is out — the pair a card is worth showing at all. */
.row-progress {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 1px;
  padding: var(--xs) var(--sm);
  border: 1px solid var(--hairline);
  border-radius: var(--r-sm);
  background: var(--canvas);
  color: var(--ink);
  font: inherit;
  font-size: 12px;
  line-height: 1.25;
  text-align: right;
  cursor: pointer;
}

.row-progress:hover:not(:disabled) { border-color: var(--ink); }
.row-progress:disabled { cursor: default; opacity: 0.6; }

.row-progress-mine { font-weight: 500; }
.row-progress-latest { color: var(--mute); font-size: 11px; }

/* The same corner the record page puts it in, so the two surfaces agree on where a rating lives. */
.row-rating {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 38px;
  padding: var(--xs) var(--sm);
  border: 1px solid var(--hairline);
  border-radius: var(--r-sm);
  background: var(--canvas);
  color: var(--ink);
  font: inherit;
  cursor: pointer;
}

.row-rating:hover:not(:disabled) { border-color: var(--ink); }
.row-rating:disabled { cursor: default; opacity: 0.6; }
.row-rating-value { font-family: var(--display); font-size: 15px; font-weight: 600; line-height: 1; }

/*
 * Taking something off the list, from the list.
 *
 * Quieter than the controls beside it, and deliberately so: this is the move somebody makes while
 * skimming, and a button competing with the rating for attention would read as the point of the
 * card. It only sharpens on hover, when it is being aimed at.
 */
.row-dismiss {
  display: inline-flex;
  align-items: center;
  /* A thumb's worth of target. Below this it is a thing you aim at rather than press. */
  min-height: 36px;
  padding: var(--xs) var(--sm);
  border: 1px solid var(--hairline);
  border-radius: var(--r-sm);
  background: var(--canvas);
  color: var(--ink);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
  margin-right: auto;
}

.row-dismiss:hover:not(:disabled) { border-color: var(--ink); }

/* Where there is no hover to reveal anything, the control is worth a little more room. */
@media (pointer: coarse) {
  .row-dismiss { min-height: 44px; padding: var(--sm) var(--md); }
}
.row-dismiss:disabled { cursor: default; opacity: 0.6; }

/* Fading out is the card leaving, and it is the only part of the gesture worth keeping. */
.row-card.is-dismissing { opacity: 0; transition: opacity 220ms ease; }

/* What was just taken off, said where the card used to be. */
.rows-said {
  grid-column: 1 / -1;
  color: var(--mute);
  font-size: 13px;
  padding: var(--xs) var(--md);
}

/* The picker or the scale, opened on the card it was asked for. */
.row-panel:not(:empty) { padding: 0 var(--md) var(--md); }
.row-panel .panel { margin: 0; }

/* ------------------------------------------------------------------ sheet */

/*
 * The thing you are editing, in front of you.
 *
 * Centred where there is room and along the bottom edge where there is not, which is where a thumb
 * already is. It is a real `dialog`, so the backdrop, Escape and focus containment are the
 * platform's rather than three more things to get wrong.
 */
.sheet {
  width: min(440px, calc(100vw - 2 * var(--lg)));
  max-height: min(80vh, 640px);
  overflow-y: auto;
  padding: 0;
  border: 1px solid var(--hairline);
  border-radius: var(--r-md);
  background: var(--canvas);
  color: var(--ink);
}

.sheet::backdrop { background: rgb(0 0 0 / 0.45); }

/* The panels inside already carry their own padding and surface; here they are the whole sheet. */
.sheet .panel {
  margin: 0;
  border: 0;
  border-radius: var(--r-md);
}

@media (max-width: 560px) {
  .sheet {
    width: 100vw;
    max-width: 100vw;
    max-height: 85vh;
    margin: auto auto 0;
    border-radius: var(--r-md) var(--r-md) 0 0;
    border-bottom: 0;
    /* Clear of the home indicator, and of the dock behind it. */
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }
}

/* The sheet's own dismissal, in the corner every dialog puts one. */
.sheet { position: relative; }

.sheet-close {
  position: absolute;
  top: var(--md);
  right: var(--md);
  z-index: 1;
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: var(--r-full);
  background: none;
  color: var(--mute);
  font: inherit;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}

.sheet-close:hover { background: var(--surface-sunk); color: var(--ink); }

/* Room for it, so a heading never runs under the button. */
.sheet .panel > h3:first-child { padding-right: var(--xxl); }

/* Where this person normally lands, as a reference point rather than a sentence about the bar. */
.rating-typical { color: var(--mute); font-size: 14px; margin: 0 0 var(--md); }
.rating-typical-value { font-family: var(--display); font-weight: 600; color: var(--ink); }

/* A connection with art. The row grammar, with the cover the strip already gives its items. */
.row.tie .thumb { width: 44px; height: 64px; border-radius: var(--xs); }

/* ----------------------------------------------------------- neighbourhood */

/*
 * What this record sits next to.
 *
 * One scrolling row of tiles, all the same shape whatever kind of thing they are — a book, a list
 * MYAI keeps, a series that merely resembles this one. The shape is deliberately uniform: what
 * differs between them is the claim each makes, and that is written under each tile rather than
 * encoded in how big its box is.
 */
.neighbours {
  display: flex;
  gap: var(--md);
  overflow-x: auto;
  scrollbar-width: thin;
  padding-bottom: var(--sm);
  margin-bottom: var(--lg);
}

.neighbour {
  flex: 0 0 auto;
  width: 96px;
  text-decoration: none;
  color: inherit;
}

.neighbour .thumb {
  width: 96px;
  height: 138px;
  border-radius: var(--sm);
  margin-bottom: var(--xs);
}

.neighbour-title {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: 13px;
  font-weight: 500;
  line-height: 1.25;
}

/* The edge itself. Quieter than the name, and never absent. */
.neighbour-relation {
  display: block;
  margin-top: 1px;
  font-size: 11px;
  line-height: 1.3;
  color: var(--mute);
}

.neighbour-detail {
  display: block;
  font-size: 11px;
  line-height: 1.3;
  color: var(--stone);
}

/*
 * The one edge that is a fact about the story rather than a resemblance gets the only colour in the
 * row. Enough to be the first thing read, and not a second heading.
 */
/* An author has no jacket. Initials rather than an empty square waiting for one that never comes. */
.neighbour .thumb.is-plate {
  display: flex;
  align-items: center;
  justify-content: center;
}

.plate-initials {
  font-size: 22px;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--stone);
}

/* Their own claim, wherever it sits. A resemblance captioned `You gave it 8` is still their score. */
.neighbour-relation.is-yours { color: var(--held); font-weight: 500; }

.neighbour:hover .neighbour-title { text-decoration: underline; }

/* Provenance, under the row: known about this thing, not somewhere else to go. */
.connection-note { margin: 0 0 var(--xs); font-size: 13px; color: var(--mute); }
.connection-note-detail { color: var(--stone); }
