/* =========================================================
   dialog.css — question, credits, darkroom, inventory, link and export
   dialogs. Shared sheet dressing for all but the darkroom.
   The note composer (#compose) is the exception: no card, no sheet.
   ========================================================= */

/* Shared sheet dressing. The two selectors tie on specificity, so width
   overrides below rely on document order. */
#ask, #credits, #inventory, #link-props, #export-opts {
  max-width: min(420px, calc(100vw - 32px));
  padding: 22px 24px 18px;
  border: var(--hairline) solid var(--rule-2);
  border-radius: var(--radius);
  background: var(--paper-card);
  box-shadow: var(--shadow-2);
  color: var(--ink);
  /* Restores the centering the reset's margin:0 removes from <dialog>. */
  margin: auto;
  animation: ask-in var(--dur-base) var(--ease-back);
}
#ask::backdrop, #credits::backdrop, #inventory::backdrop, #link-props::backdrop,
#export-opts::backdrop {
  /* Ink-tinted so the scrim dims into the palette instead of going grey. */
  background: color-mix(in srgb, var(--ink) 42%, transparent);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}
#ask h2, #credits h2, #inventory h2, #link-props h2, #export-opts h2 {
  margin: 0 0 8px;
  font-family: var(--font-display);
  font-size: var(--t-title);
  font-style: var(--display-italic);
  font-weight: 400;
  color: var(--ink);
}
/* text-wrap: pretty avoids orphans in short question text. */
#ask p {
  margin: 0 0 18px;
  font-size: var(--t-body);
  color: var(--ink-2);
  text-wrap: pretty;
}
#ask p[hidden] { display: none; }
/* pre-line makes \n breaks from consent.ts visible. */
#ask-body { white-space: pre-line; }
/* Input field for questions that want a value. Full width because there is
   only ever one, and a short box beside a long sentence reads as an afterthought. */
#ask-field {
  display: block;
  width: 100%;
  min-height: calc(35px * var(--density));
  margin: 0 0 14px;
  padding: 0 10px;
  border: var(--hairline) solid var(--rule-2);
  border-radius: var(--leaf);
  background: var(--paper);
  color: var(--ink);
  font: inherit;
}
#ask-field:focus { outline: none; border-color: var(--accent-text); }
#ask-field[hidden] { display: none; }
/* Colour picker variant. Undressed to match .swatch-well in items.css.
   Two vendor spellings of the same two parts. */
#ask-field[type="color"] {
  min-height: calc(52px * var(--density));
  padding: 4px;
  cursor: pointer;
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
}
#ask-field[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
#ask-field[type="color"]::-webkit-color-swatch {
  border: 0;
  border-radius: max(0px, calc(var(--leaf) - 3px));
}
#ask-field[type="color"]::-moz-color-swatch {
  border: 0;
  border-radius: max(0px, calc(var(--leaf) - 3px));
}


/* ── Link properties ───────────────────────────────────────────────────────
   Two labelled fields (name, address) stacked, the address paired with a Copy
   button. Dressed as #ask above; the field look follows #ask-field. */
.lp-label {
  display: block;
  margin: 0 0 4px;
  font-family: var(--font-display);
  font-size: var(--t-small);
  font-style: var(--display-italic);
  color: var(--ink-3);
}
/* The name field carries the gap down to the next label. */
.lp-label + .lp-input { margin-bottom: 14px; }
.lp-input {
  display: block;
  width: 100%;
  min-height: calc(35px * var(--density));
  padding: 0 10px;
  border: var(--hairline) solid var(--rule-2);
  border-radius: var(--leaf);
  background: var(--paper);
  color: var(--ink);
  font: inherit;
}
.lp-input:focus { outline: none; border-color: var(--accent-text); }
/* The address field: a textarea that grows with its content and soft-wraps a
   long URL, up to four lines and then scrolls. line-height is fixed so the
   four-line cap is exact; ui/link-props.ts sets the live height on input, and
   max-height caps it. overflow-wrap:anywhere breaks a URL with no spaces in it. */
textarea.lp-url {
  min-height: calc(35px * var(--density));
  max-height: calc(1.5em * 4 + 12px + var(--hairline) * 2);
  padding: 6px 10px;
  line-height: 1.5;
  resize: none;
  overflow-y: auto;
  overflow-wrap: anywhere;
}
/* The address and its Copy button on one line; the field takes the slack and
   the button stays at the top as the field grows below it. */
.lp-url-row {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin-bottom: 18px;
}
.lp-url-row .lp-input { flex: 1 1 auto; min-width: 0; margin-bottom: 0; }
.lp-copy {
  flex: none;
  min-height: calc(35px * var(--density));
  padding: 0 14px;
  color: var(--ink);
  border: var(--hairline) solid var(--rule-2);
  border-radius: var(--leaf);
  background: var(--paper);
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease);
}
@media (hover: hover) {
  .lp-copy:hover { background: var(--paper-2); border-color: var(--accent-text); color: var(--accent-deep); }
}
.lp-copy:active { background: var(--paper-3); }
.lp-copy:focus { outline: none; }
.lp-copy:focus-visible {
  outline: var(--sel-line) solid var(--select);
  outline-offset: var(--sel-gap);
}

/* ── Export options ────────────────────────────────────────────────────────
   Labels and fields borrow the link box's look (.lp-label, .lp-input); each
   control sits on one line with its unit, and the ppi line carries the
   readout of what that comes to. The quality slider's track is drawn in
   sidebar.css's :is() list — .xo-dial is an entry on it. */
/* Controls on the left, the export drawn small on the right; the preview
   goes on top instead once the two cannot sit side by side. */
#export-opts {
  width: min(720px, calc(100vw - 32px));
  max-width: min(720px, calc(100vw - 32px));
}
.xo-body {
  display: grid;
  grid-template-columns: minmax(260px, 1fr) minmax(0, 1.1fr);
  gap: 12px;
}
.xo-controls { min-width: 0; }
.xo-preview {
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 0;
  margin: 0 0 18px;
}
/* A well of the board's own ground, the picture sitting in it as a sheet. */
.xo-preview-mount {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 240px;
  padding: 16px;
  border-radius: var(--leaf);
  background: var(--paper-2);
  transition: opacity var(--dur-fast) var(--ease);
}
.xo-preview-mount.is-stale { opacity: 0.55; }
.xo-preview-mount canvas {
  display: block;
  max-width: 100%;
  max-height: 320px;
  width: auto;
  height: auto;
  box-shadow: var(--shadow-1);
}
.xo-preview .xo-note { text-align: center; }
@media (max-width: 640px) {
  .xo-body { grid-template-columns: minmax(0, 1fr); gap: 12px; }
  .xo-preview { order: -1; margin-bottom: 4px; }
  .xo-preview-mount { min-height: 140px; }
  .xo-preview-mount canvas { max-height: 180px; }
}
.xo-line {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
}
.xo-line .xo-num { width: 6.5em; flex: none; }
.xo-dial input[type="range"] { flex: 1 1 auto; min-width: 0; }
.xo-unit {
  flex: none;
  min-width: 2.5em;
  font-size: var(--t-small);
  color: var(--ink-2);
}
.xo-note {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  color: var(--ink-3);
  text-align: right;
}
.xo-include {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px 12px;
  margin: 0 0 18px;
  padding: 0;
  border: 0;
}
.xo-include[hidden] { display: none; }
/* A legend will not sit in a grid cell by itself; span it across. */
.xo-include legend { grid-column: 1 / -1; padding: 0; }
.xo-check {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: var(--t-small);
  color: var(--ink);
  cursor: pointer;
}
.xo-check[hidden] { display: none; }
.xo-check input { margin: 0; accent-color: var(--accent-text); }

.ask-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
/* Pushes paired answers to the far end; collapses when the row wraps. */
.ask-gap { flex: 1 1 0; min-width: 0; }
.ask-row button {
  min-height: calc(35px * var(--density));
  padding: 0 14px;
  color: var(--ink);
  border: var(--hairline) solid var(--rule-2);
  border-radius: var(--leaf);
  background: var(--paper);
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              transform var(--dur-base) var(--ease-back),
              color var(--dur-fast) var(--ease);
}
/* Guarded: the lift would stay raised on touch until the dialog closed. */
@media (hover: hover) {
  .ask-row button:hover {
    background: var(--paper-2);
    border-color: var(--accent-text);
    color: var(--accent-deep);
    transform: translateY(var(--btn-lift)) scale(var(--btn-grow));
  }
}
.ask-row button:active { transform: scale(var(--btn-press)); background: var(--paper-3); }
/* The dialog opens focused, so the default ring shows every time. Suppressed
   for mouse; drawn in the app's own ring for keyboard navigation. */
.ask-row button:focus { outline: none; }
.ask-row button:focus-visible {
  outline: var(--sel-line) solid var(--select);
  outline-offset: var(--sel-gap);
}
.ask-row button[hidden] { display: none; }
/* The irreversible choice — danger colour, and never the one with focus. */
.ask-row button.danger { border-color: var(--danger); color: var(--danger); }
@media (hover: hover) {
  .ask-row button.danger:hover {
    background: var(--danger);
    border-color: var(--danger);
    color: var(--paper-card);
  }
}
@keyframes ask-in { from { opacity: 0; transform: translateY(8px) scale(0.97); } }

/* Not a sheet: the note being composed is a card, and a card inside a card is
   two pieces of paper. Width follows .note-toolbar — the bar sets fit-content,
   and the only cap left is the window. */
#compose {
  max-width: calc(100vw - 32px);
  margin: auto;
  padding: 0;
  border: 0;
  background: none;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  overflow: visible;
  animation: ask-in var(--dur-base) var(--ease-back);
}
/* display:flex must be on [open], or it outranks the UA's display:none
   and a closed dialog stays on screen. */
#compose[open] { display: flex; }
#compose::backdrop {
  background: color-mix(in srgb, var(--ink) 42%, transparent);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}
#compose .note-toolbar { order: 1; }
#compose-mount { order: 2; }
#compose .ask-row { order: 3; align-self: stretch; }

/* The mount holds a live item outside the world layer. position:static takes
   the board coordinates away so the dialog can write its own sizes. --iz is 1
   because there is no zoom here. */
#compose-mount {
  position: relative;
  --iz: 1;
}
#compose-mount .item {
  position: static;
  /* Clears the inline transform placeBox() writes (snap + rotation). */
  transform: none;
  /* No board transition — the card is being written on, not moved. */
  transition: none;
}
/* The card is laid out at its board size and magnified by an inline scale()
   (canvas/notes.ts, openComposer), so it wraps exactly as it will on the
   board; the mount is sized to the scaled box for the dialog's layout. */
#compose-mount .item {
  transform-origin: 0 0;
  /* The card's own board lean (--item-tilt, items.css) stays on the board;
     the composer's one tilt is the tier's, below. */
  rotate: none;
}

/* At the soft end of the whimsy axis (tier 0) the sheet being written lies a
   little crooked, the way a pad does on a desk. Not at Middle or Harsh, where
   cards on the board straighten too and a tilted sheet would be the one thing
   out of line. On the mount, since the card's own transform is the scale. */
:root[data-whimsy="0"] #compose-mount { transform: rotate(-1.6deg); }

/* Only the sheet survives (the counter is on it); grips, ring and bar belong
   to a thing you can drag, which a note being composed is not. */
#compose-mount .item > :not(.item-body) { display: none; }

/* The toolbar in the dialog. Wraps only when the window is narrower than the
   controls — one row by default, two on a phone. */
.note-toolbar.is-inline {
  position: static;
  max-width: 100%;
  flex-wrap: wrap;
  row-gap: 6px;
  justify-content: center;
  transform: none;
}

/* Landing: the card settles onto the board from slightly above. */
.item.is-landing { animation: note-land var(--dur-base) var(--ease-back); }
@keyframes note-land {
  from { transform: translateY(-10px) scale(1.06); }
}

/* Credits sheet — wider than the question to fit a quote on two lines. */
#credits {
  max-width: min(480px, calc(100vw - 32px));
  overflow: hidden;
}
/* The sheet holds its shape and .credits-scroll inside it takes the overflow.
   A <dialog> left to scroll itself reserves the scrollbar's strip inside its
   own border box, and Chromium does not round that strip — at --radius 26px
   both right corners visibly square off the moment the fold is open.
   [open] is load-bearing: a closed <dialog> is hidden by the UA's
   `dialog:not([open]) { display: none }`, and any author `display` beats the
   UA sheet whatever its specificity — an unqualified `display: flex` here
   leaves the credits sitting on the board from boot. */
#credits[open] {
  display: flex;
  flex-direction: column;
}
#credits h2 { margin-bottom: 16px; }
.credits-scroll {
  overflow-y: auto;
  /* Without this a flex item refuses to shrink under its content and the
     scrollbar lands back on the sheet. */
  min-height: 0;
  /* The bar rides inside the sheet's own padding rather than on its edge. The
     -6px on the left pays for the summary's negative margin below, which would
     otherwise poke out of this box and raise a horizontal bar. The 8px on top
     is room for the first face to lift and grow under the pointer — an
     overflow box clips its own first row, and --btn-lift -3px with --btn-grow
     1.04 and a shadow puts the top of that circle outside it. Each negative
     margin pays its padding back, so nothing on the page moves. */
  margin: -8px -10px 0 -6px;
  padding: 8px 10px 0 6px;
}
.credit-list {
  margin: 0 0 18px;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 12px;
}
.credit {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: start;
  gap: 12px;
}
/* Always round — faces are people, not items, so the radius ignores the
   whimsy axis. object-fit covers because sources may not be square. */
.credit-face {
  width: 56px;
  height: 56px;
  object-fit: cover;
  border-radius: 50%;
  border: var(--hairline) solid var(--rule-2);
  background: var(--paper-2);
  box-shadow: var(--shadow-1);
  transition: border-color var(--dur-fast) var(--ease),
              transform var(--dur-base) var(--ease-back);
}
/* A 60px circle round the 56px face, so the focus ring is round and stands off
   the picture. A grid makes the face a block in the middle of it: an inline
   image left a baseline gap under it, which made the ring an oval. */
.credit-face-link {
  display: grid;
  place-items: center;
  width: 60px;
  height: 60px;
  border-radius: 50%;
  cursor: pointer;
}
.credit-face-link:hover .credit-face {
  border-color: var(--accent-text);
  transform: translateY(var(--btn-lift)) scale(var(--btn-grow));
}
/* Hovering the picture lights the name — people aim at the picture but the
   name says where the click goes. ~ avoids firing over the quote. */
.credit-face-link:hover ~ .credit-who .credit-name {
  color: var(--accent-text);
  text-decoration: underline;
}
.credit-who { min-width: 0; }
.credit-name {
  display: inline-block;
  font-family: var(--font-display);
  font-size: var(--t-body);
  color: var(--ink);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease);
}
@media (hover: hover) {
  .credit-name:hover { color: var(--accent-text); text-decoration: underline; }
}
.credit-name:focus { outline: none; }
.credit-name:focus-visible {
  outline: var(--sel-line) solid var(--select);
  outline-offset: var(--sel-gap);
}
.credit-site {
  display: block;
  font-family: var(--font-display);
  font-size: var(--t-tiny);
  font-style: var(--display-italic);
  letter-spacing: calc(0.06em * var(--ls-scale));
  color: var(--ink-3);
}
/* Border-left instead of quote marks — avoids nested punctuation around the
   placeholder line. */
.credit-quote {
  margin: 8px 0 0;
  padding-left: 10px;
  border-left: var(--hairline) solid var(--rule-2);
  font-size: var(--t-small);
  font-style: var(--display-italic);
  color: var(--ink-2);
  /* Avoids orphans in a short pull quote. */
  text-wrap: pretty;
}

/* Third-party credits, folded under a <details>. The summary is dressed as
   the app's own button; the marker is a CSS chevron. */
.credits-more {
  margin: 4px 0 18px;
  border-top: var(--hairline) solid var(--rule);
  padding-top: 12px;
}
.credits-more > summary {
  display: flex;
  align-items: center;
  gap: 8px;
  list-style: none;
  cursor: pointer;
  font-family: var(--font-display);
  font-size: var(--t-small);
  color: var(--ink-2);
  border-radius: var(--leaf);
  padding: 4px 6px;
  margin: -4px -6px;
  -webkit-user-select: none;
  user-select: none;
  transition: color var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease);
}
.credits-more > summary::-webkit-details-marker { display: none; }
@media (hover: hover) {
  .credits-more > summary:hover { color: var(--ink); background: var(--paper-2); }
}
.credits-more > summary:focus { outline: none; }
.credits-more > summary:focus-visible {
  outline: var(--sel-line) solid var(--select);
  outline-offset: var(--sel-gap);
}
/* Chevron: two borders on a square. Right when shut, down when open. */
.credits-more > summary::before {
  content: "";
  flex: none;
  width: 6px;
  height: 6px;
  border-right: 1.6px solid currentColor;
  border-bottom: 1.6px solid currentColor;
  rotate: -45deg;
  translate: 1px 0;
  transition: rotate var(--dur-fast) var(--ease), translate var(--dur-fast) var(--ease);
}
.credits-more[open] > summary::before { rotate: 45deg; translate: 0 -1px; }

.credits-note {
  margin: 12px 0 0;
  font-size: var(--t-small);
  color: var(--ink-2);
  text-wrap: pretty;
}
.credits-note code {
  font-family: var(--font-mono);
  font-size: 0.92em;
  color: var(--ink);
}
.credits-note a { color: var(--accent-text); }
/* The words under the author's card: pulled up to it, since the list's own
   foot margin is the gap before the fold. */
.credits-about { margin: -6px 0 18px; }
/* The second card, inside the fold, sits clear of the summary. */
.credits-fold-list { margin: 14px 0 18px; }
/* One dependency per row: name, licence chip, creator. Licence inline with
   the name so it reads as a label, not a heading for the sentence below. */
.credits-deps {
  margin: 10px 0 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 4px;
}
.credits-deps > li {
  font-size: var(--t-small);
  line-height: 1.4;
}
.credits-deps a {
  font-family: var(--font-display);
  color: var(--ink);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease);
}
@media (hover: hover) {
  .credits-deps a:hover { color: var(--accent-text); text-decoration: underline; }
}
.credits-deps a:focus { outline: none; }
.credits-deps a:focus-visible {
  outline: var(--sel-line) solid var(--select);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}
/* A chip, not a word — reads as metadata rather than part of the sentence. */
.credits-lic {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  border: var(--hairline) solid var(--rule-2);
  border-radius: var(--radius-pill);
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: calc(0.04em * var(--ls-scale));
  color: var(--ink-3);
  white-space: nowrap;
}
.credits-by {
  color: var(--ink-3);
}
.credits-by::before { content: " · "; color: var(--ink-3); }
.credits-why {
  color: var(--ink-2);
  text-wrap: pretty;
}
.credits-why::before { content: " — "; color: var(--ink-3); }
.credits-why code {
  font-family: var(--font-mono);
  font-size: 0.92em;
}

/* ═════════════════════════════════════════════════════════════════════════
   The darkroom: one picture, cropped and graded.
   Shares .ask-row button styling with #ask above.
   ═════════════════════════════════════════════════════════════════════════ */

#darkroom {
  width: min(880px, calc(100vw - 32px));
  max-width: none;
  max-height: calc(100dvh - 32px);
  padding: 18px 20px 16px;
  border: var(--hairline) solid var(--rule-2);
  border-radius: var(--radius);
  background: var(--paper-card);
  box-shadow: var(--shadow-2);
  color: var(--ink);
  margin: auto;
  animation: ask-in var(--dur-base) var(--ease-back);
}
/* display:flex on [open] only — see #compose for why. */
#darkroom[open] { display: flex; flex-direction: column; gap: 12px; }
#darkroom::backdrop {
  background: color-mix(in srgb, var(--ink) 42%, transparent);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}

#darkroom-head { display: flex; align-items: baseline; gap: 12px; }
#darkroom-title {
  margin: 0;
  flex: 1 1 auto;
  font-family: var(--font-display);
  font-size: var(--t-title);
  font-weight: var(--display-weight);
}
#darkroom-close {
  flex: none;
  display: grid;
  place-items: center;
  /* Scales with --density, unlike bar buttons pinned to the glass. */
  width: calc(var(--icon-button) * var(--density));
  height: calc(var(--icon-button) * var(--density));
  color: var(--ink-2);
  border: none;
  border-radius: var(--leaf);
  background: none;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
@media (hover: hover) {
  #darkroom-close:hover { color: var(--accent-deep); background: var(--paper-2); }
}
#darkroom-close:active { background: var(--paper-3); }
#darkroom-close:focus-visible { color: var(--accent-deep); background: var(--paper-2); }

/* Stage centres the plate; the plate IS the picture. Nothing may size the
   plate independently — the crop frame's percentages depend on it. */
#darkroom-stage {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 0;
  background: var(--paper-2);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
#darkroom-plate {
  position: relative;
  display: inline-block;
  line-height: 0;
  /* Bounded against the window so a tall portrait cannot push dials off-screen. */
  --dk-cap: min(56dvh, 560px);
}
#darkroom-image {
  display: block;
  max-width: 100%;
  max-height: var(--dk-cap);
  width: auto;
  height: auto;
  /* Prevents drag-as-file from fighting the crop gesture. */
  -webkit-user-drag: none;
  -webkit-user-select: none;
  user-select: none;
}

/* ── Quarter turn: the plate cannot shrink-wrap a rotated picture ──
   The plate takes its size from the turned ratio (--dk-ar, written by
   ui/darkroom.ts as naturalHeight/naturalWidth). width is the definite
   dimension; aspect-ratio derives the height. The picture is then laid out
   at 100cqh x 100cqw via container-type:size, so the turn lands exactly. */
#darkroom-plate.dk-quarter {
  container-type: size;
  display: block;
  width: min(100%, calc(var(--dk-cap) * var(--dk-ar)));
  aspect-ratio: var(--dk-ar);
}
#darkroom-plate.dk-quarter > #darkroom-image {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100cqh;
  height: 100cqw;
  max-width: none;
  max-height: none;
}

/* The crop area. One enormous spread dims outside; the plate's overflow:hidden
   clips it to the picture. */
#darkroom-frame {
  position: absolute;
  box-shadow: 0 0 0 100vmax color-mix(in srgb, var(--ink) 55%, transparent);
  outline: var(--hairline) solid var(--paper);
  cursor: move;
  touch-action: none;
}
#darkroom-frame[hidden] { display: none; }

/* Background-removal preview inside the crop frame. Checkerboard makes
   transparency visible against the photograph beneath. No pixelated
   rendering — the smooth stretch better previews the soft-edged matte. */
#darkroom-cutout {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  background-color: var(--paper);
  background-image:
    linear-gradient(45deg, var(--paper-3) 25%, transparent 25%, transparent 75%, var(--paper-3) 75%),
    linear-gradient(45deg, var(--paper-3) 25%, transparent 25%, transparent 75%, var(--paper-3) 75%);
  background-size: 20px 20px;
  background-position: 0 0, 10px 10px;
  pointer-events: none;
}
#darkroom-cutout[hidden] { display: none; }

/* Pulse on the plate while the model runs. Steady dim for reduced-motion. */
@keyframes dk-working {
  50% { opacity: 0.62; }
}
#darkroom.dk-busy #darkroom-plate {
  animation: dk-working 1.1s var(--ease) infinite;
}
#darkroom.dk-busy #darkroom-stage { cursor: progress; }
@media (prefers-reduced-motion: reduce) {
  #darkroom.dk-busy #darkroom-plate { animation: none; opacity: 0.62; }
}

/* Thirds grid — decoration only, nothing snaps to them. */
.dk-grid {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.5;
  background:
    linear-gradient(to right,  transparent calc(33.333% - 0.5px), var(--paper) calc(33.333% - 0.5px), var(--paper) calc(33.333% + 0.5px), transparent calc(33.333% + 0.5px)),
    linear-gradient(to right,  transparent calc(66.666% - 0.5px), var(--paper) calc(66.666% - 0.5px), var(--paper) calc(66.666% + 0.5px), transparent calc(66.666% + 0.5px)),
    linear-gradient(to bottom, transparent calc(33.333% - 0.5px), var(--paper) calc(33.333% - 0.5px), var(--paper) calc(33.333% + 0.5px), transparent calc(33.333% + 0.5px)),
    linear-gradient(to bottom, transparent calc(66.666% - 0.5px), var(--paper) calc(66.666% - 0.5px), var(--paper) calc(66.666% + 0.5px), transparent calc(66.666% + 0.5px));
}

/* Crop handles. The drawn square is 10px; the grab area is 22px via ::before,
   because a 10px target misses on trackpads and touchscreens. */
.dk-handle {
  position: absolute;
  width: 10px;
  height: 10px;
  background: var(--paper);
  border: var(--hairline) solid var(--ink-3);
  border-radius: 2px;
}
.dk-handle::before {
  content: '';
  position: absolute;
  inset: -6px;
}
.dk-handle[data-h="nw"] { top: -5px;  left: -5px;  cursor: nwse-resize; }
.dk-handle[data-h="n"]  { top: -5px;  left: 50%; margin-left: -5px; cursor: ns-resize; }
.dk-handle[data-h="ne"] { top: -5px;  right: -5px; cursor: nesw-resize; }
.dk-handle[data-h="e"]  { top: 50%; margin-top: -5px; right: -5px; cursor: ew-resize; }
.dk-handle[data-h="se"] { bottom: -5px; right: -5px; cursor: nwse-resize; }
.dk-handle[data-h="s"]  { bottom: -5px; left: 50%; margin-left: -5px; cursor: ns-resize; }
.dk-handle[data-h="sw"] { bottom: -5px; left: -5px;  cursor: nesw-resize; }
.dk-handle[data-h="w"]  { top: 50%; margin-top: -5px; left: -5px; cursor: ew-resize; }

/* Tool buttons (flip, rotate). Pressed state keyed off aria-pressed so the
   attribute a screen reader reads is the only state to keep in step. */
#darkroom-tools { display: flex; flex-wrap: wrap; gap: 8px; }
.dk-tool {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px 6px 10px;
  font: inherit;
  font-size: var(--t-small);
  color: var(--ink-2);
  border: var(--hairline) solid var(--rule-2);
  border-radius: var(--radius-pill);
  background: var(--paper);
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
/* Guarded: unguarded hover left the last-tapped tool looking selected. */
@media (hover: hover) {
  .dk-tool:hover { color: var(--ink); background: var(--paper-2); }
}
.dk-tool:active { background: var(--paper-3); }
.dk-tool[aria-pressed="true"] {
  color: var(--accent-text);
  border-color: color-mix(in srgb, var(--accent-text) 40%, transparent);
  background: var(--paper-3);
}
.dk-tool .ico { width: 15px; height: 15px; }
/* Left turn wears the right turn's glyph mirrored — one symbol, so the pair
   cannot drift apart. */
.dk-tool .dk-mirror { transform: scaleX(-1); }

/* Adjustment dials. The slider track itself is drawn in sidebar.css's :is()
   list — .dk-dial is an entry on it. */
#darkroom-dials { display: flex; flex-direction: column; gap: 8px; }
.dk-dial {
  display: grid;
  grid-template-columns: minmax(72px, auto) 1fr 3.5em;
  align-items: center;
  gap: 12px;
  font-size: var(--t-small);
  color: var(--ink-2);
}
.dk-dial output {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  color: var(--ink-3);
  text-align: right;
}

/* ── The inventory sheet ───────────────────────────────────────────────────
   Dressed as #ask/#credits (shared block above). The list is deliberately
   without pictures — building the report must not decode an image on a
   board with thousands of cards. .inv-peek at the foot shows one on hover. */
#inventory {
  max-width: min(460px, calc(100vw - 32px));
}
#inventory-body { margin-bottom: 18px; }
.inv-total {
  margin: 0 0 2px;
  font-family: var(--font-display);
  font-size: var(--t-title);
  font-style: var(--display-italic);
  font-weight: 400;
  color: var(--ink);
}
.inv-kinds, .inv-binned, .inv-orphans, .inv-where {
  margin: 0 0 10px;
  font-size: var(--t-small);
  color: var(--ink-2);
}
/* The weight, as a second heading under the count: what the board is, then
   what it costs. Display face to answer .inv-total, a size down from it. */
.inv-files {
  margin: 16px 0 2px;
  font-family: var(--font-display);
  font-size: var(--t-body);
  font-style: var(--display-italic);
  color: var(--ink);
}
.inv-orphans { margin-top: 14px; color: var(--ink-3); }
.inv-head {
  margin: 16px 0 6px;
  font-family: var(--font-display);
  font-size: var(--t-small);
  font-weight: 400;
  font-style: var(--display-italic);
  color: var(--ink-3);
}
.inv-list {
  margin: 0;
  padding: 0;
  list-style: none;
}
/* Border on the row, everything else on the line (<button> or <div>), so
   the two measure identically and rows that link do not differ in height. */
.inv-row { border-bottom: var(--hairline) solid var(--rule); }
.inv-row:last-child { border-bottom: 0; }
/* A file and its copies: the twisty, then the line. Both on one flex row so
   the name column lands in the same place whether or not there is a twisty —
   which is what .inv-slot below is for. */
.inv-group { display: flex; align-items: baseline; }
.inv-group .inv-line { flex: 1; min-width: 0; }
.inv-twisty, .inv-slot { flex: none; width: 20px; }
/* Centred against the row, not sat on its baseline. Both rows this lives in
   are `align-items: baseline` — right for a name beside a size, wrong for a
   glyph, which a baseline hangs by its own bottom edge and so lifts a line
   above the text it belongs to. `align-self: stretch` takes it out of that
   alignment, and the grid centres it in the height it is then given. */
.inv-twisty {
  display: grid;
  place-items: center;
  align-self: stretch;
  padding: 0;
  border: 0;
  border-radius: var(--leaf);
  background: none;
  color: var(--ink-3);
  cursor: pointer;
}
/* Block, so the icon carries no descender space of its own inside the grid. */
.inv-twisty .ico {
  display: block;
  width: 14px;
  height: 14px;
  transition: transform var(--dur-fast) var(--ease);
}
.inv-twisty[aria-expanded="true"] .ico { transform: rotate(90deg); }
.inv-twisty:focus { outline: none; }
.inv-twisty:focus-visible {
  outline: var(--sel-line) solid var(--select);
  outline-offset: calc(var(--sel-gap) * -1);
}
@media (hover: hover) {
  .inv-twisty:hover { color: var(--ink); }
}
/* The copies, under the file they were made from. Indented past the twisty,
   so the eye reads them as belonging to the row above rather than as rows. */
.inv-kept {
  margin: 0 0 4px 20px;
  padding: 0;
  list-style: none;
}
.inv-kept[hidden] { display: none; }
.inv-kid .inv-name, .inv-kid .inv-size { color: var(--ink-3); }
.inv-kid .inv-line { font-size: var(--t-tiny); }

/* A folded holder: twisty, heading, what it weighs, and — for the past — a
   way to let it go. The heading keeps its face and loses its top margin to
   the bar, which carries the spacing for all four. */
.inv-fold {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin: 16px 0 0;
}
.inv-fold .inv-head { flex: 1; min-width: 0; margin: 0; }
.inv-fold .inv-size { font-size: var(--t-small); color: var(--ink-2); }
/* The list a fold opens is its sibling, not its child - the bar is a flex row
   and a list inside it would lay out as a fourth column. */
.inv-list[hidden] { display: none; }
.inv-free {
  flex: none;
  padding: 2px 8px;
  border: var(--hairline) solid var(--rule-2);
  border-radius: var(--leaf);
  background: none;
  color: var(--ink-3);
  font: inherit;
  font-size: var(--t-tiny);
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
@media (hover: hover) {
  .inv-free:hover { border-color: var(--danger); color: var(--danger); }
}
.inv-free:focus { outline: none; }
.inv-free:focus-visible {
  outline: var(--sel-line) solid var(--select);
  outline-offset: 2px;
}
/* What the cap left out. Counted rather than listed — see TOP in inventory.ts. */
.inv-more {
  padding: 4px 6px;
  font-size: var(--t-tiny);
  font-style: italic;
  color: var(--ink-3);
}
.inv-line {
  display: flex;
  align-items: baseline;
  gap: 12px;
  width: 100%;
  margin: 0;
  padding: 4px 6px;
  border: 0;
  border-radius: var(--leaf);
  background: none;
  color: inherit;
  font: inherit;
  font-size: var(--t-small);
  text-align: left;
}
button.inv-line { cursor: pointer; transition: background var(--dur-fast) var(--ease); }
@media (hover: hover) {
  button.inv-line:hover { background: var(--paper-2); }
}
button.inv-line:focus { outline: none; }
button.inv-line:focus-visible {
  outline: var(--sel-line) solid var(--select);
  outline-offset: calc(var(--sel-gap) * -1);
}
/* Shown only when a file is under several cards — the case this report
   exists to make visible, since deleting a card then frees nothing. */
.inv-used {
  flex: none;
  font-family: var(--font-display);
  font-size: var(--t-tiny);
  font-style: var(--display-italic);
  color: var(--ink-3);
}
/* Name truncates; size never does — the size column is what is scanned. */
.inv-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ink-2);
}
.inv-size {
  flex: none;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
/* Orphans: stored but claimed by no card. Marked inline so the heaviest-first
   order is kept in one list. */
.inv-row[data-orphan] .inv-name { color: var(--ink-3); font-style: italic; }

/* The peek: one row's picture beside the sheet. Fixed size (168px = PEEK in
   ui/inventory.ts) so placement arithmetic works before the image loads.
   Inside the <dialog> so it paints over the backdrop. */
.inv-peek {
  position: fixed;
  z-index: 1;
  display: grid;
  place-items: center;
  width: 168px;
  height: 168px;
  padding: 6px;
  border: var(--hairline) solid var(--rule-2);
  border-radius: var(--leaf);
  background: var(--paper-card);
  box-shadow: var(--shadow-2);
  pointer-events: none;
}
.inv-peek[hidden] { display: none; }
.inv-peek img,
.inv-peek video {
  max-width: 100%;
  max-height: 100%;
  border-radius: calc(var(--leaf) - 4px);
  object-fit: contain;
}

/* Fallback without color-mix() (WebKit < 16.2). Scrims matter most here:
   without them the backdrop is transparent and the dialog has no dimming. */
@supports not (color: color-mix(in srgb, red, blue)) {
  #ask::backdrop,
  #credits::backdrop,
  #inventory::backdrop,
  #compose::backdrop,
  #link-props::backdrop,
  #export-opts::backdrop,
  #darkroom::backdrop { background: rgb(var(--ink-c) / 42%); }
  #darkroom-frame { box-shadow: 0 0 0 100vmax rgb(var(--ink-c) / 55%); }
  .dk-tool[aria-pressed="true"] { border-color: rgb(var(--accent-c) / 40%); }
}
