/* List, grid, table, canvas and graph view presentation. */
  /* list view: an ordinary column */
  #board[data-view="list"] { display: flex; flex-direction: column; gap: 0.75rem; }
  .kids[data-view="list"] { display: flex; flex-direction: column; gap: 0.75rem; }
  .kids[data-view="grid"] { display: grid; grid-template-columns: 1fr 1fr; gap: 0.65rem; align-items: start; }
  /* Capture spans the grid; a field is never half a tile. */
  #board[data-view="grid"] .capture,
  #board[data-view="grid"] .sharefilter,
  #board[data-view="grid"] .grouphead { grid-column: 1 / -1; }
  /* Grid is two abreast, not a zoom: clamp the blurb so a half-width
     face still reads as a tile instead of a squeezed full card. */
  .kids[data-view="grid"] .cardopen p,
  #board[data-view="grid"] .cardopen p,
  .kids[data-view="grid"] .prop-quote,
  #board[data-view="grid"] .prop-quote,
  .kids[data-view="grid"] .prop-prose,
  #board[data-view="grid"] .prop-prose {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
  .kids[data-view="grid"] .prop-prose,
  #board[data-view="grid"] .prop-prose { -webkit-line-clamp: 3; }

  /* table view: rows of cards, properties as columns. The sheet
     scrolls inside itself, like the board's columns — so the header
     row and the title column can sit still. Collapse would stop
     sticky from taking; separate borders are the price. */
  /* The table board is a flex column now — the open-window rules near
     .page own its display; a later block rule here once collapsed it. */
  .sheetwrap {
    background: var(--card);
    border-radius: var(--radius);
    box-shadow: 0 0.5px 2px rgba(30, 32, 38, 0.06);
    height: 100%; min-height: 16rem;
    overflow: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
  }
  .sheet {
    width: max-content; min-width: 100%;
    border-collapse: separate;
    border-spacing: 0;
    font-size: 0.9375rem;
  }
  .sheet th {
    position: sticky; top: 0; z-index: 2;
    text-align: left;
    font-size: 0.6875rem; font-weight: 600; letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--ink2);
    padding: 0.55rem 0.85rem;
    white-space: nowrap;
    background: var(--card);
    border-bottom: 0.5px solid var(--line);
  }
  .sheet td {
    padding: 0.7rem 0.85rem;
    border-top: 0.5px solid var(--line);
    white-space: nowrap;
    vertical-align: middle;
    background: var(--card);
  }
  .sheet tbody tr:first-child td { border-top: none; }
  .sheet .prop + .prop { margin-left: 0.3rem; }
  .sheet td:has(.prop-quote) { white-space: normal; max-width: 14rem; }
  .sheet td:has(.prop-prose) { white-space: normal; max-width: 18rem; }
  .sheetrow { cursor: pointer; }
  .sheetrow:active td { background: rgba(118, 118, 128, 0.06); }
  .sheet-title {
    position: sticky; left: 0; z-index: 1;
    font-weight: 600; letter-spacing: -0.01em; color: var(--ink);
    min-width: 8rem; max-width: 10rem;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    box-shadow: 1px 0 0 var(--line);
  }
  .sheet th.sheet-title {
    z-index: 3;
    font-weight: 600; font-size: 0.75rem; color: var(--ink2);
    box-shadow: 1px 0 0 var(--line), 0 1px 0 var(--line);
  }
  .sheetrow:active .sheet-title { background: var(--card); }
  .sheet .prop { margin: 0; }

  /* loose and graph share the canvas: a table you pinch, pan and zoom.
     touch-action: none is what keeps the pinch on the table instead of
     the page. */
  /* Loose and graph boards are flex columns too — the day a stale
     block rule outlived the open window here, both views collapsed to
     a zero-height canvas and vanished. The cascade is a loaded gun. */
  .canvas {
    position: relative; overflow: hidden;
    height: 100%; min-height: 16rem;
    background: var(--card);
    border-radius: var(--radius);
    box-shadow: 0 0.5px 2px rgba(30, 32, 38, 0.06);
    touch-action: none;
    cursor: grab;
  }
  .canvas:active { cursor: grabbing; }
  .stage { position: absolute; left: 0; top: 0; transform-origin: 0 0; will-change: transform; }
  .stage .card {
    position: absolute; width: 300px;
    box-shadow: 0 6px 20px rgba(30, 32, 38, 0.12);
  }
  /* text adapts: far out, the small labels bow out and the room reads by
     its shapes. Obsidian fades every name past a zoom threshold. */
  .stage .gnode text { transition: opacity 200ms ease; }
  .stage.lowzoom .gnode text { opacity: 0; }

  /* graph view: a force-directed map in the Obsidian manner — filled
     discs, faint links, tags as hubs, the rest of the room dimming
     when a node is held. */
  #board[data-view="graph"] .canvas {
    background: #ececf1;
    box-shadow: none;
  }
  .graph { display: block; }
  .gedge {
    stroke: #6b6b76;
    stroke-opacity: 0.28;
    stroke-width: 0.9;
    pointer-events: none;
  }
  .gnode { cursor: pointer; }
  .gnode circle { fill: var(--gfill, #8E8E93); stroke: none; }
  .gnode circle.ghit { fill: transparent; }
  .gnode text {
    fill: var(--ink2); font-size: 9px; font-weight: 500;
    text-anchor: middle; pointer-events: none; opacity: 0.78;
  }
  .gnode.gtag { cursor: default; }
  .gnode.gtag text { fill: var(--ink); font-weight: 600; opacity: 0.92; }
  .graph.gfocus .gedge { stroke-opacity: 0.06; }
  .graph.gfocus .gedge.ghot {
    stroke: var(--tint); stroke-opacity: 0.88; stroke-width: 1.35;
  }
  .graph.gfocus .gnode { opacity: 0.14; }
  .graph.gfocus .gnode.ghot { opacity: 1; }
  .graph.gfocus .gnode.ghot text { opacity: 1; fill: var(--ink); }

