/* Builds — page-local styles.

   The palette, the type ladder, the radii, the rail, the page head and the two
   failure notices all come from site/tokens.css, shared/base.css and
   shared/chrome.css. Only what is specific to a library of screenshots is here.

   The layout question this page has and the others do not: a captured tooltip is
   a picture at whatever size the game rendered it, often 500px wide and 1000 tall,
   and there are a dozen of them. So the board is a masonry-ish flow of natural-width
   cards rather than a grid — forcing them into equal cells would either shrink the
   readable ones or blow up the short ones. */

/* ---- item rarity ----
   The game's own rarity colours, three of them the same values
   apps/whirlwind-dps/app.css paints its swatches with. Derived, not chosen:
   lib/rarity-colors.mjs reads them out of the snapshot's arFontColorsExt table and
   apps/loot-filter/data.json carries the result. A stylesheet cannot import that,
   so this is a transcription, and tests/game-data.test.mjs pins it — the game's
   palette moving fails by name here rather than leaving this page quietly painting
   the old colours.

   Four, because summary.js's rarityOf matches four; the reason is there rather
   than repeated here. --set is this page's alone: the talisman's Set charms are
   the only place the site draws that rarity, so the calculator does not carry it.
   `--rarity` is the per-card indirection the rules further down paint through,
   set from the data attribute board.js writes. */
:root {
  --legendary: #ff8000;
  --unique: #dca779;
  --mythic: #dc9bf2;
  --set: #00ff00;
}

.item-card[data-rarity="mythic"] {
  --rarity: var(--mythic);
}

.item-card[data-rarity="unique"] {
  --rarity: var(--unique);
}

.item-card[data-rarity="legendary"] {
  --rarity: var(--legendary);
}

.item-card[data-rarity="set"] {
  --rarity: var(--set);
}

/* ---- page shell ----
   `body` and `.wrap` are declared per app on purpose: shared/base.css leaves
   `body` alone because the pages legitimately differ on typography, and the page
   measure is a token (--page-width) rather than a shared rule. This page had
   neither, which is not a small difference — it rendered full-bleed, in the
   browser's default serif, on whatever ground `color-scheme: dark` picked. These
   are the 16px/1.5 body and the gutter Prism Crafting, the Atlas and the
   calculator all use. */
body {
  min-height: 100vh;
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font:
    16px/1.5 "Segoe UI",
    system-ui,
    sans-serif;
}

.wrap {
  width: min(var(--page-width), 100%);
  margin: 0 auto;
  /* .pagehead owns the title's top spacing, as on the other pages. */
  padding: 0 20px 28px;
}

/* ---- buttons ----
   The same shape the calculator draws: --panel on --edge, accent border on hover.
   This page was relying on the browser's default button, which on a dark page is
   the one control that still looks like it belongs to a different site. */
button {
  padding: 0.4rem 0.9rem;
  border: 1px solid var(--edge);
  border-radius: var(--radius-md);
  background: var(--panel);
  color: var(--ink);
  font-size: var(--fs-base);
  cursor: pointer;
}

button:hover:not(:disabled) {
  border-color: var(--accent);
}

button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

button:disabled {
  opacity: 0.45;
  cursor: default;
}

/* Spelled `button.primary` / `button.quiet` like the builder's, so the class on
   its own cannot restyle something that is not a button. */
button.primary {
  border-color: var(--accent);
  background: var(--accent);
  color: #07111f;
  font-weight: 600;
}

button.quiet {
  padding: 0.25rem 0.5rem;
  border-color: transparent;
  background: none;
  color: var(--muted);
}

button.quiet:hover:not(:disabled) {
  border-color: var(--edge);
  color: var(--ink);
}

/* The file extension, wherever it is named in prose. */
.library-empty code {
  padding: 1px 5px;
  border-radius: var(--radius-sm);
  background: var(--chip-bg);
  color: var(--ink);
  font-size: var(--fs-sm);
}

/* ---- the header's import/export controls ----
   Sharing the row with the title instead of a bordered box below it, freeing the
   vertical space that box used to take. chrome.css's .pagehead is nowrap (built
   for the single kofi button); overridden here so this row can drop to its own
   line under the title on a narrow viewport instead of overflowing it. */
.pagehead {
  flex-wrap: wrap;
}

.pagehead-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}

/* The capture client's download. Drawn as a button but written as a link,
   because that is what it is — a static archive at a stable URL, which the
   browser must be free to open in a new tab or copy the address of. The rules
   above key on the `button` element, so the shape is restated here rather than
   shared: a class that restyles a non-button is exactly what their comment
   refuses. */
/* Drawn quiet, like `button.quiet` beside it: a returning visitor already has the
   client, so the page's heaviest control should not be the one they never press
   again. The border arrives on hover, which is where the shape is needed. */
.pagehead-actions .download {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  padding: 0.4rem 0.9rem;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  color: var(--muted);
  font-size: var(--fs-base);
  text-decoration: none;
}

.pagehead-actions .download:hover {
  border-color: var(--edge);
  color: var(--ink);
}

.pagehead-actions .download:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

/* The version, in the link's own text rather than a title attribute: which
   release you are getting is worth knowing before a 70 MB download, and a
   tooltip is invisible on touch and to a keyboard. */
.download-meta {
  color: var(--muted);
  font-size: var(--fs-sm);
}

/* The file input is driven by the button beside it; it stays in the DOM (and
   labelled) rather than being display:none, so it is still a real control. */
#file-input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ---- status and warnings ---- */

.status {
  margin: 8px 0 0;
  font-size: var(--fs-sm);
  color: var(--muted);
}

.status:empty {
  margin: 0;
}

.status[data-tone="error"] {
  color: var(--danger);
}

.status[data-tone="ok"] {
  color: var(--good);
}

/* What a file lost on the way in. Not a toast: an item dropped for an unreadable
   image changes what the board means, so it stays on screen next to it. */
.warnings {
  margin: 8px 0 0;
  padding: 10px 14px 10px 30px;
  list-style: disc;
  border: 1px solid var(--edge-soft);
  border-left: 3px solid var(--danger);
  border-radius: var(--radius-sm);
  background: var(--panel);
  color: var(--muted);
  font-size: var(--fs-sm);
}

/* ---- stacked layout: library strip above the board ---- */

/* A left sidebar used to hold the library and cost the board panel its width.
   Stacking the two sections instead means the board always gets the page's
   full horizontal space, and the library reads as a strip of chips above it
   rather than a column beside it. */
.builds-layout {
  display: flex;
  flex-direction: column;
  gap: 22px;
  margin-top: 12px;
}

.library-panel {
  padding: 6px 8px;
  border: 1px solid var(--edge-soft);
  border-radius: var(--radius-md);
  background: var(--panel);
}

/* The panel's <h2> is .sr-only in the markup: a page that is entirely builds
   doesn't need "Your builds" printed above them, but the section still wants
   an accessible name for the document outline. */

.board-head h2 {
  margin: 0 0 10px;
  font-size: var(--fs-3xl);
}

.library-empty {
  margin: 0;
  color: var(--faint);
  font-size: var(--fs-sm);
}

.library {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 5px;
}

/* Stacked, not "name on the left and three verbs on the right". Each build is a
   compact card in the wrapping strip: the outline and surface make neighboring
   builds legible as separate things, even before one has been selected.

   The basis is the widest thing a row actually draws — the meta line, ~135px at
   the longest ("12 items · 15-08-2026 00:14") — plus the padding and a little
   slack. It was 232px, which left 50px of empty card to the right of every row;
   type sizes are deliberately untouched, so what came off is only that gap. */
.library-row {
  display: flex;
  flex: 0 0 186px;
  flex-direction: column;
  gap: 3px;
  padding: 6px 8px;
  border: 1px solid var(--edge-soft);
  border-radius: var(--radius-sm);
  background: var(--panel2);
  box-shadow: inset 3px 0 0 transparent;
  transition:
    border-color 120ms ease,
    background-color 120ms ease,
    box-shadow 120ms ease;
}

.library-row:hover,
.library-row:focus-within {
  border-color: var(--edge);
  background: var(--panel);
}

.library-row[data-current="true"] {
  border-color: var(--accent);
  background: var(--panel);
  box-shadow: inset 3px 0 0 var(--accent);
}

.library-open {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
  padding: 2px;
  border: 0;
  background: none;
  color: inherit;
  text-align: left;
  cursor: pointer;
  font: inherit;
}

.library-name {
  color: var(--ink);
  font-size: var(--fs-base);
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.library-meta {
  color: var(--faint);
  font-size: var(--fs-2xs);
}

/* Three verbs under the name they act on. Each still carries an aria-label
   naming its build, because "Delete" repeated down a list says nothing on its
   own to a screen reader reading the buttons out of context. */
.library-actions {
  display: flex;
  gap: 1px;
  margin-left: -2px;
}

.library-actions button {
  padding: 3px 5px;
  color: var(--faint);
  font-size: var(--fs-2xs);
}

/* ---- the board ---- */

/* Always visible, unlike .board-head — it is how a build-less visitor reaches
   the slot view at all. */
.board-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
  font-size: var(--fs-sm);
  color: var(--muted);
}

.board-toolbar select {
  padding: 0.3rem 0.5rem;
  border: 1px solid var(--edge);
  border-radius: var(--radius-sm);
  background: var(--panel);
  color: var(--ink);
  font-size: var(--fs-sm);
}

.board-toolbar select:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.board-toolbar-check {
  display: flex;
  align-items: center;
  gap: 5px;
  cursor: pointer;
}

.board-head {
  margin-bottom: 14px;
}

.board-subtitle {
  margin: 0;
  color: var(--faint);
  font-size: var(--fs-sm);
}

/* Empty for an open build — the library row just clicked already gave the
   count and date, so there is nothing to print above the board. Collapsed
   rather than left as a blank line. */
.board-subtitle:empty {
  display: none;
}

.board-section {
  margin-bottom: 20px;
  padding: 14px 16px;
  border: 1px solid var(--edge-soft);
  border-radius: var(--radius-md);
  background: var(--panel);
}

/* The label, the count, and a rule running out to the frame's edge — which is
   what makes a frame boundary readable now that the cards inside carry their own
   borders and the heading is no longer the only line on the panel. */
.board-section-title {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 0 14px;
  color: var(--ink);
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.board-section-title::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--edge-soft);
}

.board-section-count {
  padding: 1px 8px;
  border-radius: var(--radius-pill);
  background: var(--chip-bg);
  color: var(--muted);
  font-size: var(--fs-3xs);
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: none;
}

/* Still used by renderSlotBoard, which has one thing to say and no frame to say
   it in. The per-section version is .board-uncaptured below. */
.board-empty {
  margin: 0;
  color: var(--faint);
  font-size: var(--fs-sm);
  font-style: italic;
}

/* The sections nothing captured, in one line rather than a panel each. Dashed
   rather than solid: this is the shape of something absent, not a frame with
   nothing in it. */
.board-uncaptured {
  margin: 0;
  padding: 12px 18px;
  border: 1px dashed var(--edge-soft);
  border-radius: var(--radius-md);
  color: var(--faint);
  font-size: var(--fs-sm);
}

/* The card states the column width, and the image fills it.

   It used to be the other way round — the card sized itself to its content and
   the image carried `max-width: min(100%, <column-width>)`. In a max-content
   pass the `100%` there has no container to resolve against, so the card took
   the screenshot's *natural* width (460–480px) while the image inside it drew
   at the column width: the leftover was the button's black background, showing
   beside every tooltip on the board as a black band.

   Equal widths are not the grid the module header rules out. That grid would
   have equalized *heights* too, which is what would shrink a tall tooltip;
   heights stay natural here, and every card was already capped at the same
   column width anyway. */
.item-card {
  display: flex;
  flex-direction: column;
  gap: 7px;
  /* The column gap is horizontal only; the vertical one is this margin. */
  margin: 0 0 14px;
  padding: 7px 7px 9px;
  border: 1px solid var(--edge-soft);
  border-radius: var(--radius-md);
  background: var(--panel2);
  break-inside: avoid;
  width: 100%;
  transition: border-color 120ms ease;
}

/* Tinted rather than painted: the game's rarity colours are foreground colours,
   picked to be read as text on the tooltip's own dark art, and a border drawn at
   that saturation would out-shout the picture it frames. Mixed toward the card's
   own edge instead, so the card is recognisably that rarity without competing
   with it. A browser without color-mix keeps the plain --edge-soft above, which
   is the pre-rarity card and not a broken one. */
.item-card[data-rarity] {
  border-color: color-mix(in srgb, var(--rarity) 30%, var(--edge-soft));
}

.item-card:hover {
  border-color: var(--edge);
}

.item-card[data-rarity]:hover {
  border-color: color-mix(in srgb, var(--rarity) 55%, var(--edge-soft));
}

/* One row-major flex row per BOARD_ROW cards — both boards use it, the single
   build's and the slot-across-builds one. Row-major, not the multi-column
   masonry this page used to carry, because the slots that pair (both rings,
   both dual-wield hands) have to read left-to-right and a column-major fill
   lands them stacked. See memory/equipment-groups-are-horizontal.md. */
.board-items-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 10px;
  margin-bottom: 10px;
}

.board-items-row:last-child {
  margin-bottom: 0;
}

/* A row is six card-widths, and every child claims `--span` of them: one for a
   card, two for a `.board-pair` box holding a ring pair. board.js sets it.

   Capping at that share — rather than letting flex-grow divide the row's
   leftover — is what keeps a card the same width no matter what its row holds.
   Grow alone resolves per wrapped *line*, so a trailing row of one drew that
   card at the full panel width, and a build that had captured only one of its
   two rings drew it at double every properly paired card. Both were the same
   bug: width read from row occupancy instead of from the slot.

   The five 10px gaps come out before the split, and a span reclaims the gaps it
   now covers internally, so six cards, three pairs, or any mix of the two all
   measure exactly 100%. `flex-basis` still lets a row wrap early on a narrow
   panel instead of forcing its cards below a readable width. */
.board-items-row > .item-card,
.board-items-row > .board-group {
  --span: 1;
  flex: var(--span) 1 calc(var(--span) * 190px);
  max-width: calc((100% - 50px) * var(--span) / 6 + (var(--span) - 1) * 10px);
  margin: 0;
}

/* One build's box in the cross-build slot view (see renderSlotBoard): its title,
   then its cards. The title is here rather than on each card because the build is
   what the whole box has in common — repeating it per card spent the caption on a
   string every neighbour also carried, and an untitled capture's fallback title is
   long enough that it truncated the slot name beside it.

   Its floor scales with the span because a lone ring gets a one-card box: two
   cards need their own 150px each, the 10px between them and this box's 10px
   padding on each side — 330px exactly, at which flex layout's own subpixel
   rounding wrapped the pair one card at a time, so the 175px per card here
   carries slack rather than the bare minimum. See
   memory/equipment-groups-are-horizontal.md. */
.board-group {
  display: flex;
  flex-direction: column;
  gap: 9px;
  min-width: calc(var(--span, 1) * 175px + 22px);
  padding: 10px;
  border: 1px solid var(--edge-soft);
  border-radius: var(--radius-md);
  background: var(--panel);
}

/* Wraps rather than truncates: this is a build's name, and which build a set of
   cards came from is the one thing this view exists to tell you. The card
   captions inside are the short, aligned ones. */
.board-group-title {
  margin: 0;
  color: var(--ink);
  font-size: var(--fs-sm);
  font-weight: 600;
  overflow-wrap: anywhere;
}

.board-group-items {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

/* Each card splits the box evenly. The box already carries the span, so a lone
   ring's card fills a one-card box and comes out the same width as every other
   card on the board. `min-width` is what lets `flex-wrap` above drop the second
   card to its own line on a narrow panel rather than squeezing both below a
   readable width. */
.board-group .item-card {
  flex: 1 1 0;
  min-width: 150px;
  width: auto;
  max-width: 100%;
  margin: 0;
}

.item-image {
  padding: 0;
  border: 1px solid var(--edge-soft);
  border-radius: var(--radius-sm);
  background: #000;
  cursor: zoom-in;
  line-height: 0;
  transition: border-color 0.15s ease;
}

.item-image:hover,
.item-image:focus-visible {
  border-color: var(--accent);
}

/* Roughly half the captured size. The client crops at the game's own
   resolution, so a 2560×1440 capture yields tooltips that would otherwise
   dominate the page; the lightbox is where full size lives. The width/height
   attributes board.js sets keep the aspect ratio reserved before the blob
   decodes, so the board does not reflow as the images arrive. */
.item-image img {
  display: block;
  width: 100%;
  height: auto;
}

/* A skill tooltip is landscape where an item's is portrait — a skill capture
   measures 524×320, against ~490×1200 for a gear tooltip. It reads as a
   thumbnail, not as a document: the six of them are a skill bar, and which skills
   are on it is what the row says. The modifier text is the lightbox's job.

   Every skill capture is that exact size: the panels differ by a few pixels on
   screen and build-generator's SkillCropNormalizer squares them off, so a row of
   cards is uniform without this file equalising anything. Older build files
   predate it and still carry ragged crops — hence min(natural, column) below,
   which is right for both.

   So the skill row is its own grid rather than the six-column split above, at one
   fixed card width. The flex row shares its leftover per wrapped *line*, so six
   double-span cards that wrapped four-and-two drew the last two half again as
   wide as the first four — the same "width read from row occupancy" bug the span
   cap fixed for equipment, reappearing one level down. A fixed track cannot do
   that: every skill card is --skill-thumb wide on every line, at every panel
   width, and the row fits as many as it fits.

   The image is still capped rather than stretched: filling the column would
   upscale past the resolution it was captured at, which is blurred pixels
   presented as a sharper picture than the capture is. min(natural, column) is
   right at every panel width. */
.board-section[data-section="skills"] .board-items-row {
  --skill-thumb: 250px;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, var(--skill-thumb)), 1fr));
}

/* The max-width overrides the six-column formula above, whose 100% would resolve
   against one track here and shrink the card to a third of it. */
.board-section[data-section="skills"] .item-card {
  max-width: var(--skill-thumb);
  align-items: flex-start;
}

.board-section[data-section="skills"] .item-image img {
  width: auto;
  max-width: 100%;
}

/* The paragon row: five columns, one per board, exactly as the game's own panel
   lays them out — starting node, then the four boards. A column that needed two
   screenshots stacks them, so its second picture is under its first rather than
   in the next column's cell (see appendParagonColumns in board.js).

   Five fixed tracks rather than the six-card formula above, and `auto-fit` so a
   narrow panel drops to fewer columns instead of squeezing five unreadable ones.
   `align-items: start` keeps a one-picture column at its own height next to a
   two-picture neighbour instead of stretching it. */
.board-paragon-row {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  align-items: start;
}

/* Below five readable columns the row wraps to fewer rather than shrinking them
   past the point the tooltip text is legible; the column order is preserved
   either way, so a wrapped board still reads left to right. */
@media (max-width: 1100px) {
  .board-paragon-row {
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr));
  }
}

.board-section[data-section="paragon"] .board-column {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 0;
}

/* A column that took two screenshots is one board, so it is drawn as one panel:
   the two cards lose the border edge between them and share a single frame,
   rather than sitting as two separately-framed cards that happen to be stacked.

   Done by fusing the cards rather than boxing them — a box around cards that
   already carry their own border nests two frames, and the outer one then has to
   out-weigh the inner to read as the grouping, which makes the whole column
   heavier than its four single-card neighbours. Here the pair ends up exactly as
   heavy as a single card, which is the point: the row still reads as five equal
   columns. `gap: 0` so the halves meet, and the radii are flattened only where
   they touch. */
.board-section[data-section="paragon"] .board-column[data-continued] {
  gap: 0;
}

.board-section[data-section="paragon"] .board-column[data-continued] .item-card:not(:last-child) {
  border-bottom-color: transparent;
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
  padding-bottom: 0;
}

.board-section[data-section="paragon"] .board-column[data-continued] .item-card:not(:first-child) {
  border-top: 0;
  border-top-left-radius: 0;
  border-top-right-radius: 0;
  padding-top: 0;
}

/* The continuation's caption is taken off the screen but left in the document.
   The panel already carries the slot name once, at its top, and the two halves
   are now one panel — so a second copy of it in the seam is the same title twice.
   Hidden the way #file-input above is, rather than display:none, because the
   caption is what a screen reader has instead of the seam: it reads the cards one
   after another, and slotLabel still gives that one the "(continued)" suffix. */
.board-section[data-section="paragon"]
  .board-column[data-continued]
  .item-card:not(:first-child)
  .item-meta {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* The grid track is the width here, so the six-column max-width formula above —
   whose 100% would resolve against one track — must not apply. The card's own
   bottom margin goes with it: the column is a flex container and spaces its
   children with `gap`, so the margin would add a second, unequal space — and in
   a fused column it would hold the two halves apart at exactly the seam the
   `gap: 0` above closes. */
.board-section[data-section="paragon"] .item-card {
  max-width: 100%;
  margin: 0;
}

/* Natural width, capped — filling the column would upscale past the resolution
   the shot was taken at, which is blurred pixels presented as a sharper picture
   (the same call the skills section makes above).

   Centred, because the columns are not all the same width: the starting node's
   panel is narrower than a board's, so its picture left it short of the button's
   right edge and the button's black background showed there as a band. `margin:
   auto` rather than `text-align`, since the image is display:block. */
.board-section[data-section="paragon"] .item-image img {
  width: auto;
  max-width: 100%;
  margin-inline: auto;
}

/* A label bar across the top of the card: the slot on the left, the rarity on
   the right. board.js puts the figcaption first in the figure, so this is the
   document's order too — nothing here reorders it. */
.item-meta {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 0 2px;
  font-size: var(--fs-xs);
}

/* Ellipsised rather than wrapped: a two-line slot name on one card and one line
   on its neighbour puts the two pictures beside it at different heights, which
   is the misalignment the label bar exists to remove. "Two-Handed Bludgeoning"
   is the one that needs it. */
.item-slot {
  color: var(--muted);
  font-size: var(--fs-2xs);
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.item-rarity {
  flex: none;
  padding: 1px 7px;
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--rarity) 16%, var(--panel));
  color: var(--rarity);
  font-size: var(--fs-3xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* ---- dialogs ---- */

dialog::backdrop {
  background: rgb(9 13 19 / 88%);
}

#lightbox {
  max-width: 96vw;
  max-height: 96vh;
  padding: 0;
  border: 1px solid var(--edge);
  border-radius: var(--radius-lg);
  background: var(--ground);
  /* The bar is a row of controls, so the frame above it is the only part that
     reads as "click to dismiss" — the cursor says so there and nowhere else. */
  overflow: hidden;
}

/* The picture takes what the caption bar does not. `min-height: 0` is what lets
   it shrink inside the column instead of forcing the dialog past 96vh. */
.lightbox-frame {
  display: flex;
  justify-content: center;
  min-height: 0;
  overflow: auto;
  cursor: zoom-out;
}

#lightbox img {
  display: block;
  max-width: 100%;
  /* The bar is ~44px; leaving it out here is what pushed the dialog off-screen
     on a short viewport. */
  max-height: calc(96vh - 46px);
  object-fit: contain;
}

.lightbox-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  border-top: 1px solid var(--edge-soft);
  background: var(--panel);
}

.lightbox-caption {
  flex: 1 1 auto;
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 8px;
  margin: 0;
  min-width: 0;
  font-size: var(--fs-sm);
  color: var(--ink);
}

.lightbox-position {
  flex: none;
  color: var(--faint);
  font-size: var(--fs-2xs);
  font-variant-numeric: tabular-nums;
}

/* The three bar controls are square, and larger than a .quiet button elsewhere:
   a modal needs a comfortable target even though clicking its backdrop also
   closes it. */
.lightbox-bar button {
  flex: none;
  width: 30px;
  height: 30px;
  padding: 0;
  font-size: var(--fs-lg);
  line-height: 1;
}

.lightbox-bar button[hidden] {
  display: none;
}

/* Set apart from "next", which it otherwise sits flush against — a near miss on
   the arrow you are pressing repeatedly should not be the one control that ends
   the gallery. */
#lightbox-close {
  margin-left: 10px;
  border-left-color: var(--edge);
}

/* Rename. The builder asks through its own dialog rather than through
   prompt(); that is the only control on the site that would otherwise arrive
   in the browser's chrome instead of the page's. */
.sheet {
  width: min(420px, calc(100vw - 40px));
  padding: 18px 20px;
  border: 1px solid var(--edge);
  /* --radius-lg is the dialog step in tokens.css, which the lightbox above also
     takes. */
  border-radius: var(--radius-lg);
  background: var(--panel);
  color: var(--ink);
}

.sheet h2 {
  margin: 0 0 10px;
  font-size: var(--fs-2xl);
}

.sheet p {
  margin: 0;
  color: var(--muted);
  font-size: var(--fs-sm);
}

.sheet-field {
  display: block;
  margin-bottom: 4px;
  color: var(--muted);
  font-size: var(--fs-sm);
}

.sheet input[type="text"] {
  width: 100%;
  padding: 0.4rem 0.5rem;
  border: 1px solid var(--edge);
  border-radius: var(--radius-sm);
  background: var(--chip-bg);
  color: var(--ink);
}

.sheet input[type="text"]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.sheet-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 16px;
}

@media (prefers-reduced-motion: reduce) {
  .item-card,
  .item-image {
    transition: none;
  }
}
