/* =========================================================
   item-chrome.css — what is drawn over a card rather than in it.
   LAST OF SIX — must stay below the five sheets above it.
   Three ties that document order settles (see items.css banner).
   Pairs with canvas/items.ts and canvas/viewport.ts.
   ========================================================= */

.item.is-editing { cursor: text; }
.item.is-editing .item-body { pointer-events: auto; }
.item.is-editing .card-text,
.item.is-editing .card-name,
.item.is-editing .item-label,
.item.is-editing .note-rich { outline: none; overflow: auto; cursor: text; }
/* While renaming, the plate takes the caret and scrolls instead of truncating. */
.item.is-editing .item-label { pointer-events: auto; text-overflow: clip; }
/* Empty lines keep a line's worth of height so they stay clickable. */
.item.is-editing .note-line:empty { min-height: 1em; }

/* Caption plate: a flex row at the card's foot. Solid background — the old
   backdrop-filter blur was re-sampled every frame of a zoom. */
.item-bar {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  display: flex;
  align-items: stretch;
  background: var(--paper-card);
  border-top: 1px solid var(--rule);
  border-radius: 0 0 var(--radius) var(--radius);
  overflow: hidden;
}
.item-label {
  flex: 1;
  min-width: 0;
  padding: 6px 11px 7px;
  /* World-space type — zooms with the board, not on the chrome scale.
     Sorted deliberately off-scale by visual-audit item 2. */
  font-size: 12px;
  font-style: var(--display-italic);
  line-height: 1.25;
  color: var(--ink-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  pointer-events: none;
}
.item-label[hidden] { display: none; }

/* Bar hidden on unnamed cards and on types that draw the name inside the card.
   .item-bar-always exempts fences — an unnamed fence with no plate has no hit
   area (its interior refuses the pointer). */
.item-bar:not(.item-bar-always):not(:has(> .item-label:not([hidden]))),
.item[data-type="audio"] .item-bar,
.item[data-type="text"] .item-bar,
.item[data-type="note"] .item-bar,
.item[data-type="link"] .item-bar,
.item[data-type="model"] .item-bar,
.item[data-type="generic"] .item-bar { display: none; }
/* :not() avoids unhiding plates that types hide on purpose. */
#world.zoom-far .item:not(.is-editing) .item-label { display: none; }
/* Fence labels survive the far-zoom cull — they are set at the region's own
   size, still legible, and finding your way is exactly when you need them.
   Below the rule it exempts so document order wins the tie. */
#world.zoom-far .item[data-type="fence"] .item-label:not([hidden]) { display: block; }

/* ---------------------------------------------------------------------------
   The index rung: what a card is between 40% and 10%.

   Three rungs, each a different kind of answer:
     detail   >= 40%    the card, as built
     index    40 - 10%  what it is, and what it is called
     swatch   < 10%     picture, tint or paper — no text at all

   Nothing to look at (text, link, model, ...) — a specimen label: kicker,
   rule, name. A picture — edge to edge, no caption. A note — its tint and
   its opening line. Where the name sits tells you whether there is anything
   to look at.

   One element (.far-head) draws all of it. Size is in screen units (--iz),
   capped against the card so a small card does not wear type taller than
   itself.
   --------------------------------------------------------------------------- */
#world.zoom-far .item {
  /* 14 screen px, capped at 18% of the card's height. Everything else is
     derived from this so the head cannot drift out of proportion. */
  --ix-name: min(calc(14px * var(--iz, 1)), calc(var(--half-h, 60px) * 0.36));
  --ix-kind: calc(var(--ix-name) * 0.62);
  --ix-pad:  calc(var(--ix-name) * 0.72);
  /* The head of an audio card without a sleeve, which keeps its play button
     underneath: the top pad and one line of name. */
  --ix-head: calc(var(--ix-pad) + var(--ix-name) * 1.1);
}
/* Swatch rung — no label at all. */
#world.zoom-tiny .far-head { display: none; }

.far-head {
  position: absolute;
  inset: 0;
  /* Off at every ordinary zoom; the rung rule below makes it visible. */
  display: none;
  flex-direction: column;
  align-items: stretch;
  padding: var(--ix-pad);
  gap: calc(var(--ix-pad) * 0.42);
  overflow: hidden;
  pointer-events: none;
  z-index: 1;
}
/* :not(.is-editing) so a card being typed into keeps showing its content. */
#world.zoom-far .item:not(.is-editing) > .far-head:not([hidden]) { display: flex; }
/* No head over a picture — a photograph is its own name at any size. As
   specific as the rule above, which it has to beat: one class short, it lost,
   and an audio sleeve wore its kind and name across the art.

   Two classes answer "is there a picture here", and the second is a model's.
   image and video never reach these rules at all (wantsHead in
   canvas/item-dom.ts builds them no head), but a model card is built like a
   card that has nothing to look at and then, most of the time, has a
   photograph of the part in it - the WebP still canvas/model.ts takes the
   moment you stop turning it. That is a picture on the same terms the sleeve
   is, so it takes the head off on the same terms too. A model with no still -
   still reading, being turned, or unreadable - has nothing to look at out here
   and keeps its label. */
#world.zoom-far .item:not(.is-editing):has(.card-cover, .model-still) > .far-head:not([hidden]) { display: none; }

/* The kind and its underline. The rule runs the card's measure, not the full
   width, because a specimen label is ruled inside its margins. */
.fh-kind {
  flex: none;
  font-family: var(--font-display);
  font-style: var(--display-italic);
  font-weight: calc(var(--display-weight) + 100);
  font-size: var(--ix-kind);
  line-height: 1;
  letter-spacing: calc(0.16em * var(--ls-scale));
  text-transform: uppercase;
  color: var(--accent-text);
  border-bottom: var(--board-hairline) solid var(--rule);
  padding-bottom: calc(var(--ix-pad) * 0.42);
  white-space: nowrap;
  overflow: hidden;
}
/* Three lines of name — the third is often where the distinguishing word lives.
   overflow-wrap:anywhere handles long filenames with no spaces. */
.fh-name {
  font-family: var(--font-display);
  font-weight: calc(var(--display-weight) + 200);
  font-size: var(--ix-name);
  line-height: 1.1;
  letter-spacing: 0.005em;
  color: var(--ink);
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
  overflow-wrap: anywhere;
}

/* A note: no kind, no rule, centred. Four lines because there is no kicker
   above and a note's name is a sentence, not a filename. */
#world.zoom-far .item[data-type="note"] > .far-head { justify-content: center; }
#world.zoom-far .item[data-type="note"] .fh-kind { display: none; }
#world.zoom-far .item[data-type="note"] .fh-name { -webkit-line-clamp: 4; }

/* Audio without a sleeve keeps its play button — the one control worth hitting
   from across a board. The head takes one line of name at the top and the
   button centres below (media.css). No kind above the name: the button says
   what the card is, and the kind and its rule took the room the button needed,
   which pushed it off the card's foot. */
#world.zoom-far .item[data-type="audio"]:not(:has(.card-cover)) > .far-head {
  bottom: auto;
  height: var(--ix-head);
  padding-bottom: 0;
}
#world.zoom-far .item[data-type="audio"] .fh-kind { display: none; }
#world.zoom-far .item[data-type="audio"] .fh-name { -webkit-line-clamp: 1; text-align: center; }

/* The inner bookplate rule retires once two hairlines would be too close to
   read as two lines. Not the fence — fences.css puts the cork on this same
   ::after, and this rule outranks the cork by an id. */
#world.zoom-far .card:not(.fence-card)::after { content: none; }
/* The card's edge moves to .item while the body is down. ::after rather than
   box-shadow so it paints over children. Ghosts, fences and title cards are
   excluded — each draws its own edge already. */
#world.zoom-far .item:not([data-type="ghost"]) .card { box-shadow: none; }
#world.zoom-far .item:not([data-type="ghost"]):not([data-type="fence"]):not([data-type="title"])::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: inset 0 0 0 var(--board-hairline) var(--item-border);
  pointer-events: none;
  z-index: 1;
}
/* Body hidden for every card that grew a head — it is the smear the head
   replaced. Two cards keep theirs. Audio, because its play button survives the
   rung. And any card the rule above took the head *off*: the head is the only
   thing the body was hidden for, so a card still showing a picture out here
   must not be emptied behind a label that is not drawn. That second exemption
   is a model's - it is why a part photographed onto its card stays a picture
   all the way out, the same as a photograph beside it, rather than becoming a
   blank card wearing the word "obj". Same test as the rule above, and they have
   to stay the same test. */
#world.zoom-far .item:not(.is-editing):not([data-type="audio"]):not(:has(.card-cover, .model-still)):has(> .far-head:not([hidden])) .item-body { display: none; }
/* ---------------------------------------------------------------------------
   Cheap mode: what the board stops paying for while it is being moved.
   Set by canvas/viewport.ts for the duration of a gesture plus 140ms.
   backdrop-filter is the main cost — it re-samples every frame of a zoom. */
#world.is-viewing .card-audio.has-cover .play {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

/* Resize handles. --iz keeps them a constant size on screen at every zoom.
   The grip is an invisible hit area; the drawn mark is a heavier stretch of
   the selection ring at each corner (see .item::before). */
#world.zoom-far .item-bar { display: none; }
/* Fence plate survives — it is the region's only hit area and its label is
   still legible at this zoom. */
#world.zoom-far .item[data-type="fence"] .item-bar { display: flex; }

/* Shadow twins off at swatch scale — each is a multi-layer box-shadow element
   the compositor still has to paint, one per item. */
#world.zoom-far .item-shadow { display: none; }

.grip {
  position: absolute;
  /* Capped at half the ring so opposite marks meet without crossing. */
  width:  min(var(--mark-size), calc(50% + var(--sel-edge)));
  height: min(var(--mark-size), calc(50% + var(--sel-edge)));
  display: none;
  z-index: 2;
}
.item.is-selected .grip { display: block; }
/* A locked card has no grips — drawing handles that refuse to drag would
   be promising something the app then declines. */
.item[data-locked] .grip { display: none; }

/* The two corner badges: the anchor (.item-locked) and the pin (.item-pin).
   Each sits where the NE grip would have been - neither card has grips - and
   shows while the card is hovered or selected. Both are buttons: a press
   takes the anchor off, or lets the pin go for ten seconds (canvas/items.ts).
   A small disc so the glyph reads on any picture and the press has a target. */
.item-badge {
  position: absolute;
  top:   calc(var(--sel-edge) * -1 - 7px * var(--iz));
  right: calc(var(--sel-edge) * -1 - 7px * var(--iz));
  width:  calc(22px * var(--iz));
  height: calc(22px * var(--iz));
  box-sizing: border-box;
  display: none;
  place-items: center;
  padding: 0;
  /* --board-hairline, not --hairline: this box is inside the world transform. */
  border: var(--board-hairline) solid var(--rule-2);
  /* A disc on the soft tiers and a square at Harsh: a badge, not a circle with a
     meaning. Times --iz like its size, since --radius-pill is a real length now
     (tokens.css) and a board zoomed far out makes this box large in the world. */
  border-radius: calc(var(--radius-pill) * var(--iz));
  background: var(--paper-card);
  color: var(--ink-2);
  cursor: pointer;
  z-index: 3;
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.item-badge .ico { width: 62%; height: 62%; }
.item-pin { color: var(--select); }
.item[data-locked]:is(.is-selected, :hover) > .item-locked,
.item:is(.is-pinned, .is-pin-sel) > .item-pin { display: grid; }
@media (hover: hover) {
  .item-badge:hover { color: var(--accent-text); border-color: var(--accent-text); }
}
.item-badge:focus-visible { outline: var(--sel-line) solid var(--select); outline-offset: 1px; }
/* Off from the chrome rung (40%) down, with the labels and the item bar: that
   far out a badge is clutter on a card too small to press it on. */
#world.zoom-far .item-badge { display: none; }
/* Grips survive two rungs below the chrome drop and go at 15%
   (RESIZE_MIN_ZOOM in viewport.ts): that far out a card is carried, not
   sized. No grip on screen is no grip under the pointer, so this rule is
   the whole refusal. The corner marks go with them, and the ring turns
   dashed - a selection that moves and does not stretch. */
#world.zoom-no-resize .grip { display: none; }
#world.zoom-no-resize .item.is-selected::before { border-style: dashed; }

/* The corner mark — the selection ring drawn heavier at each corner. Only
   the two outward-facing borders are drawn; the inward pair would close it
   into a floating box.
   A border width floors to whole device pixels before the zoom transform, so
   zoomed in past about 160% the corner's 3.2 * --iz and the ring's 2 * --iz
   both floored to one pixel and the corners vanished into the ring. The floor
   is one device pixel past the ring as it is actually drawn. */
.grip:not(.grip-edge)::after {
  --px: var(--device-px, 1px);
  --sel-ring: max(var(--px), round(down, calc(var(--sel-line) * var(--iz)), var(--px)));
  content: '';
  position: absolute;
  inset: 0;
  border: calc(max(var(--sel-corner) * var(--iz), var(--sel-ring) + var(--px)) * var(--grip-swell, 1))
    solid var(--select);
  pointer-events: none;
  transition: border-width var(--dur-fast) var(--ease);
}
/* Thickens under the pointer / while dragging. Grows inward from the ring's
   outer edge so the line stays in place. */
.item.is-resizing .grip:not(.grip-edge) { --grip-swell: 1.6; }
@media (hover: hover) {
  .grip:not(.grip-edge):hover { --grip-swell: 1.6; }
}
/* Corner radius follows the item's own curve. R offset outward by d gives
   R + d, staying concentric at every zoom. */
.grip[data-g="nw"]::after {
  border-right-width: 0; border-bottom-width: 0;
  border-radius: var(--mark-radius) 0 0 0;
}
.grip[data-g="ne"]::after {
  border-left-width: 0; border-bottom-width: 0;
  border-radius: 0 var(--mark-radius) 0 0;
}
.grip[data-g="se"]::after {
  border-left-width: 0; border-top-width: 0;
  border-radius: 0 0 var(--mark-radius) 0;
}
.grip[data-g="sw"]::after {
  border-right-width: 0; border-top-width: 0;
  border-radius: 0 0 0 var(--mark-radius);
}

/* Corner boxes straddle the item's corner — outer edge on the ring. */
.grip[data-g="nw"] { left:  calc(-1 * var(--sel-edge)); top:    calc(-1 * var(--sel-edge)); cursor: nwse-resize; }
.grip[data-g="ne"] { right: calc(-1 * var(--sel-edge)); top:    calc(-1 * var(--sel-edge)); cursor: nesw-resize; }
.grip[data-g="sw"] { left:  calc(-1 * var(--sel-edge)); bottom: calc(-1 * var(--sel-edge)); cursor: nesw-resize; }
.grip[data-g="se"] { right: calc(-1 * var(--sel-edge)); bottom: calc(-1 * var(--sel-edge)); cursor: nwse-resize; }

/* The grab area — an invisible hit area larger than the drawn mark. In --iz
   units so it holds a constant size on screen. */
.grip {
  pointer-events: none;
  --grip-reach: var(--iz);
}
.grip::before {
  content: '';
  position: absolute;
  /* 4px of forgiveness on edges; corners replace this below. */
  inset: calc(-1 * var(--grip-slop, 4px) * var(--iz));
  pointer-events: auto;
}

/* Corner target: a constant square laid *outside* the item, so the face is
   always a move target. Lap per axis is capped at 1/8 of that side so four
   corners never cover more than 6.25% of the face. */
.grip:not(.grip-edge)::before {
  inset: auto;
  --grip-out: calc(var(--grip-corner, 24px) * var(--grip-reach));
  --grip-lap-x: min(var(--grip-out), calc(var(--half-w, 9999px) * 0.25));
  --grip-lap-y: min(var(--grip-out), calc(var(--half-h, 9999px) * 0.25));
  width:  calc(var(--grip-out) + var(--grip-lap-x));
  height: calc(var(--grip-out) + var(--grip-lap-y));
}
/* Offsets pin the outer corner; caps eat into the inner side only. */
.grip[data-g="nw"]::before,
.grip[data-g="sw"]::before { left:   calc(var(--sel-edge) - var(--grip-out)); }
.grip[data-g="ne"]::before,
.grip[data-g="se"]::before { right:  calc(var(--sel-edge) - var(--grip-out)); }
.grip[data-g="nw"]::before,
.grip[data-g="ne"]::before { top:    calc(var(--sel-edge) - var(--grip-out)); }
.grip[data-g="sw"]::before,
.grip[data-g="se"]::before { bottom: calc(var(--sel-edge) - var(--grip-out)); }

/* Below the grab rung (25%) the corner hitbox clips to its outside half, so
   the face takes every press. Pure geometry — no JS needed for the cursor
   or move behaviour. */
#world.zoom-grab .grip:not(.grip-edge)::before {
  width:  var(--grip-out);
  height: var(--grip-out);
}

/* Debug: paint every grab hitbox, corners and sides. Toggled by
   mbrd.debugGrips() or the #grips hash. The sides take a lighter wash, so
   where a corner laps onto a side strip the two still read apart. */
[data-debug-grips] .grip::before {
  background: color-mix(in srgb, var(--select) 28%, transparent);
  outline: max(1px, calc(1px * var(--iz))) solid var(--select);
  outline-offset: calc(-1 * max(1px, calc(1px * var(--iz))));
}
[data-debug-grips] .grip-edge::before {
  background: color-mix(in srgb, var(--select) 14%, transparent);
}

/* Interior controls (play button, link anchor, waveform) ride above the grips
   so they keep the eye and the cursor. */
.vbig,
.wave,
.card-link .card-name { position: relative; z-index: 3; }

/* Edge handles: invisible strips between the corners. Outside the item so the
   face is always a drag. One variable for thickness and offset so the touch
   block widens the strip without sliding it over the card. */
.grip-edge { background: none; }
.grip[data-g="n"], .grip[data-g="s"] {
  left:  min(var(--mark-size), 40%);
  right: min(var(--mark-size), 40%);
  width: auto;
  height: calc(var(--grip-edge, 14px) * var(--grip-reach));
  cursor: ns-resize;
}
.grip[data-g="n"] { top:    calc(-1 * var(--grip-edge, 14px) * var(--grip-reach)); }
.grip[data-g="s"] { bottom: calc(-1 * var(--grip-edge, 14px) * var(--grip-reach)); }
.grip[data-g="w"], .grip[data-g="e"] {
  top:    min(var(--mark-size), 40%);
  bottom: min(var(--mark-size), 40%);
  height: auto;
  width: calc(var(--grip-edge, 14px) * var(--grip-reach));
  cursor: ew-resize;
}
.grip[data-g="w"] { left:  calc(-1 * var(--grip-edge, 14px) * var(--grip-reach)); }
.grip[data-g="e"] { right: calc(-1 * var(--grip-edge, 14px) * var(--grip-reach)); }

/* ---------------------------------------------------------------------------
   The tour mark: which stop a card is, and the way to make it one.

   canvas/items.ts puts one on every card while the tour editor is open.
   .is-add = not on a stop (dashed "+"); without it = a stop number.
   A <button> so input.ts gives it the press and a keyboard can reach it.
   An element, not a pseudo — both of .item's pseudos are spoken for.
   Sized in --iz so it holds a constant on-screen size.
   --------------------------------------------------------------------------- */
.tour-mark {
  position: absolute;
  z-index: 4;
  top: calc(-10px * var(--iz, 1));
  right: calc(-10px * var(--iz, 1));
  width: calc(var(--icon-button) * var(--iz, 1));
  height: calc(var(--icon-button) * var(--iz, 1));
  box-sizing: border-box;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: calc(var(--leaf) * var(--iz, 1));
  background: var(--accent);
  color: var(--accent-fg);
  font-family: var(--font-mono);
  font-size: calc(var(--t-tiny) * var(--iz, 1));
  font-variant-numeric: tabular-nums;
  line-height: 1;
  box-shadow: var(--shadow-1);
  cursor: pointer;
  transition: opacity var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease);
}
/* Beside a corner badge rather than over it, when the card wears one. */
.item[data-locked] > .tour-mark,
.item:is(.is-pinned, .is-pin-sel) > .tour-mark {
  right: calc(var(--sel-edge) + 22px * var(--iz, 1));
}
/* Not on a stop — quiet, dashed outline. On a board of forty this is most of
   them, so the accent colour would make the board unreadable. */
.tour-mark.is-add {
  background: var(--paper-card);
  color: var(--ink-3);
  /* --board-hairline, not --hairline: this box is inside the world transform. */
  border: var(--board-hairline) dashed var(--rule-2);
  box-shadow: none;
}
/* Fades in on hover. On touch there is no hover, so it stays visible. */
@media (hover: hover) {
  .tour-mark.is-add { opacity: 0; }
  .item:hover > .tour-mark.is-add,
  .item.is-selected > .tour-mark.is-add,
  .tour-mark.is-add:focus-visible { opacity: 1; }
  .tour-mark.is-add:hover { border-color: var(--accent-text); color: var(--accent-text); }
}

/* Number at rest, cross on hover — a number reads as a label, so without the
   cross the remove action was invisible. Danger colour on removal. */
.tour-mark-x {
  display: none;
  width: calc(var(--icon-glyph) * var(--iz, 1));
  height: calc(var(--icon-glyph) * var(--iz, 1));
}
@media (hover: hover) {
  .tour-mark:not(.is-add):hover { background: var(--danger); color: var(--paper-card); }
  .tour-mark:not(.is-add):hover > .tour-mark-n { display: none; }
  .tour-mark:not(.is-add):hover > .tour-mark-x { display: block; }
}
.tour-mark:not(.is-add):focus-visible { background: var(--danger); color: var(--paper-card); }
.tour-mark:not(.is-add):focus-visible > .tour-mark-n { display: none; }
.tour-mark:not(.is-add):focus-visible > .tour-mark-x { display: block; }

/* ---------------------------------------------------------------------------
   The peek: pointer over the editor's bar shows the order over the board.

   .is-tour-peek on <html> (no selector can cross from bar to cards).
   The mark covers the card (inset:0), washed white so the numeral is legible
   over any picture. rem because this is a graphic numeral, not text. */
:root.is-tour-peek .tour-mark.is-add { display: none; }
:root.is-tour-peek .tour-mark:not(.is-add) {
  inset: 0;
  width: auto;
  height: auto;
  border: 0;
  border-radius: inherit;
  background: rgb(255 255 255 / 40%);
  color: var(--ink);
  box-shadow: none;
  font-size: 2.2rem;
  overflow: hidden;
  /* Instant — box and colour change together; transitioning one makes the
     other look late. */
  transition: none;
}
/* The mark must come back at swatch zoom — that is where the peek is most
   useful (whole board, reading order visible). */
:root.is-tour-peek #world.zoom-tiny .tour-mark:not(.is-add) { display: grid; }
/* Gone below the zoom where on-card controls are too small to aim at. */
#world.zoom-tiny .tour-mark { display: none; }

/* ---------------------------------------------------------------------------
   A board on tour, or having its tour built, is looked at rather than worked
   on: no card lifts under the pointer, and in the editor none is grabbed. */
:root:is(.is-touring, .is-tour-edit) :is(.item, .item-shadow).is-hover {
  --lift: 0px;
  --grow: 1;
  --sh-lift: 0px;
  --sh-grow: 1;
}
:root.is-tour-edit .item.is-selected { cursor: default; }
/* On tour, only the stop on show takes input, and in it only play: no links,
   and no viewer or move (canvas/input.ts). A press anywhere else lands on the
   board, which steps the tour on. `is-spot` is canvas/items.ts's setSpot(). */
:root.is-touring .item:not(.is-spot),
:root.is-touring .item:not(.is-spot) *,
:root.is-touring .item.is-spot a { pointer-events: none; }
/* In the editor, a stop chip or a card's number under the pointer leaves that
   stop lit and fades the rest. */
:root.is-stop-peek .item:not(.is-spot) { opacity: 0.25; }

/* Fallback without color-mix() (WebKit < 16.2). */
@supports not (color: color-mix(in srgb, red, blue)) {
  [data-debug-grips] .grip::before { background: rgb(var(--select-c) / 28%); }
  [data-debug-grips] .grip-edge::before { background: rgb(var(--select-c) / 14%); }
}
