@import url("./ui.css");

/* ── Prompt / Negative tabs ──────────────────────────────────── */
/* One block, two panes. The inactive pane keeps its DOM (values, highlight
   mirror, autocomplete bindings) and is only hidden, so switching is free
   and nothing has to be re-attached. */
.promptbox { gap: 0; }
.promptbox-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--sp-2);
  /* The tab strip sits on this line and the active tab bridges it. */
  border-bottom: 1px solid var(--line);
  padding-bottom: 0;
}
.promptbox .fld-actions { padding-bottom: var(--sp-1); }

.tabs { display: flex; align-items: flex-end; gap: 2px; min-width: 0; }
.tab {
  position: relative;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px var(--sp-3) 7px;
  color: var(--fg-3);
  font-size: var(--fz-xs); font-weight: var(--fw-medium);
  letter-spacing: -0.005em;
  border-radius: var(--r2) var(--r2) 0 0;
  white-space: nowrap;
  transition: color var(--t1) var(--e), background var(--t1) var(--e);
}
.tab:hover { color: var(--fg-2); background: var(--sunken); }
/* Underline drawn over the head's border so the active tab reads as attached
   to the pane below it rather than floating above it. */
.tab::after {
  content: "";
  position: absolute; left: 0; right: 0; bottom: -1px;
  height: 2px;
  background: var(--a);
  border-radius: 2px 2px 0 0;
  transform: scaleX(0);
  transition: transform var(--t2) var(--e-out);
}
.tab[aria-selected="true"] { color: var(--fg); font-weight: var(--fw-semibold); }
.tab[aria-selected="true"]::after { transform: scaleX(1); }

/* Reads as a count, not a badge — it must not compete with the label. */
.tab-count {
  font: var(--fw-regular) var(--fz-2xs) var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--fg-4);
  transition: color var(--t1) var(--e);
}
.tab[aria-selected="true"] .tab-count { color: var(--fg-3); }
/* A non-empty Negative is worth noticing while you are on the Prompt tab:
   it silently shapes every result. */
.tab[aria-selected="false"] .tab-count[data-filled="1"] { color: var(--a); }

.promptbox-body { position: relative; min-height: 0; display: grid; }
.pane { min-height: 0; display: grid; }
.pane[hidden] { display: none; }

/* Anchored to the field, outside the scrolling text content. */
.fld-foot {
  position: absolute;
  right: 0;
  bottom: 0;
  z-index: 2;
  margin: 0 10px 8px 0;
  padding: 1px 5px;
  font: var(--fw-regular) var(--fz-2xs) var(--mono);
  color: var(--fg-4);
  background: color-mix(in srgb, var(--sunken) 88%, transparent);
  border-radius: 4px;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--t2) var(--e);
}
.fld-hero:focus-within .fld-foot { opacity: 1; }

/* ── Weight highlighting ─────────────────────────────────────── */
/* A mirror sits behind a transparent textarea. Three things previously broke:
   1) The textarea showed an 11px scrollbar (from the global scrollbar rule)
      while the mirror used overflow:hidden. Different content widths mean
      different line-break points, so every wrapped line drifted. Both now
      reserve the same gutter via `scrollbar-gutter: stable`.
   2) The mirror drew its own border/background and fought the textarea's
      focus ring. The wrapper owns all chrome now; both children are bare.
   3) Sub-pixel font differences: `font: inherit` does not carry every metric,
      so the shared block below sets each one explicitly on both elements. */
/* THE WRAPPER IS THE ONLY SCROLLER.
   The mirror and the textarea live in this one scrolling box, so scrolling
   moves both at once, natively. Earlier versions gave each its own scrollport
   and synced with `mirror.scrollTop = textarea.scrollTop`, which is always a
   frame late (blocks lag behind the caret) and clamps differently at the
   ends (blocks visibly detach at the top). There is no sync code now. */
.hl-wrap {
  position: relative;
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
  background: var(--well);
  border: 1px solid var(--line);
  border-radius: var(--r3);
  transition: border-color var(--t1) var(--e), background var(--t1) var(--e),
    box-shadow var(--t1) var(--e);
  /* the handle now resizes the scroller itself */
  resize: vertical;
}
.hl-wrap:hover { border-color: var(--line-2); }
.hl-wrap:focus-within {
  border-color: var(--a-line);
  background: var(--well-focus);
  box-shadow: 0 0 0 3px var(--a-dim);
}

/* Every property here affects text layout and MUST be identical on both.
   Neither element scrolls; both grow to full content height inside the
   wrapper, so their glyphs cannot drift apart at any scroll offset. */
.hl-wrap > textarea,
.hl-wrap > .hl-mirror {
  display: block;
  width: 100%;
  min-width: 0;
  margin: 0;
  border: 0;
  padding: var(--sp-3);
  font-family: var(--sans);
  font-size: var(--fz-md);
  font-weight: var(--fw-regular);
  font-style: normal;
  font-variant: normal;
  line-height: 1.62;
  letter-spacing: -0.006em;
  word-spacing: normal;
  text-indent: 0;
  text-transform: none;
  tab-size: 4;
  white-space: pre-wrap;
  overflow-wrap: break-word;
  word-break: normal;
  overflow: hidden;
  background: none;
  box-sizing: border-box;
}
/* The textarea is in flow and auto-grown by JS to its full content height,
   so it defines the scroll length. It never scrolls internally. */
.hl-wrap > textarea {
  position: relative;
  z-index: 1;
  height: auto;
  color: var(--fg);
  caret-color: var(--a);
  resize: none;
  border-radius: inherit;
}
.hl-wrap > textarea:focus { outline: none; box-shadow: none; background: none; }
.hl-wrap > textarea::placeholder { color: var(--fg-4); }

/* Painted underneath at the same origin. Because it is inside the same
   scrollport, it translates with the content automatically. */
.hl-wrap > .hl-mirror {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 0;
  min-height: 100%;
  color: transparent;
  pointer-events: none;
  user-select: none;
}

.w { border-radius: 2px; }
/* Weight punctuation stays readable but recedes. */
.w-mark { opacity: 0.85; }

/* number with unit suffix */
.fld-inline { position: relative; }
.fld-inline input { padding-right: 26px; }
.fld-inline span {
  position: absolute; right: 10px; top: 50%;
  transform: translateY(-50%);
  font: var(--fw-medium) var(--fz-2xs) var(--mono);
  color: var(--fg-4);
  pointer-events: none;
}


/* ── Danbooru Tag autocomplete ─────────────────────────────── */
.tag-suggest {
  position: fixed;
  z-index: var(--z-pop);
  max-height: min(360px, calc(100dvh - 16px));
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-gutter: stable;
  touch-action: pan-y;
  padding: 3px;
  background: color-mix(in srgb, var(--raised) 97%, transparent);
  border: 1px solid var(--line-2);
  border-radius: var(--r3);
  box-shadow: var(--sh-3), var(--sh-lip);
  backdrop-filter: blur(16px);
  animation: tagSuggestIn var(--t1) var(--e-out);
}
.tag-suggest[hidden] { display: none; }
.tag-suggest-list { display: grid; gap: 1px; }
.tag-suggest-option {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 9px;
  min-height: 48px;
  padding: 6px 8px;
  color: var(--fg-2);
  text-align: left;
  border-radius: var(--r2);
  transition: color var(--t1) var(--e), background var(--t1) var(--e);
}
.tag-suggest-option:hover,
.tag-suggest-option[aria-selected="true"] { color: var(--fg); background: var(--hover); }
.tag-suggest-names { display: grid; gap: 0; overflow: hidden; }
.tag-suggest-names strong,
.tag-suggest-names span { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.tag-suggest-names strong { font: var(--fw-medium) var(--fz-2xs)/1.3 var(--mono); color: inherit; }
.tag-suggest-names span { font-size: var(--fz-2xs); line-height: 1.3; color: var(--fg-3); }
.tag-suggest-meta {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font: var(--fw-regular) var(--fz-2xs)/1.3 var(--mono);
  color: var(--fg-4);
  white-space: nowrap;
}
.tag-category-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--fg-4); }
.tag-category-dot.artist { background: #e88787; }
.tag-category-dot.copyright { background: #b3a1ff; }
.tag-category-dot.character { background: #74cf9b; }
.tag-category-dot.meta { background: #e0ab68; }
.tag-suggest-note {
  display: flex; align-items: center; justify-content: center; gap: var(--sp-2);
  padding: 12px; font-size: var(--fz-xs); color: var(--fg-3); text-align: center;
}
.tag-suggest-note.err { color: var(--err); }
/* Inline spinner so the debounce + request window is not silent. */
.tag-suggest-note.load::before {
  content: "";
  flex: none; width: 11px; height: 11px;
  border: 1.5px solid var(--line-2);
  border-top-color: var(--a);
  border-radius: 50%;
  animation: tagSpin 620ms linear infinite;
}
@keyframes tagSpin { to { transform: rotate(1turn); } }
@keyframes tagSuggestIn {
  from { opacity: 0; transform: translateY(3px) scale(.99); }
}

/* ── Shell ───────────────────────────────────────────────────── */
.shell {
  display: grid;
  grid-template-columns: var(--rail) 0 minmax(0, 1fr) var(--hist);
  height: calc(100vh - var(--bar-h));
  transition: grid-template-columns var(--t3) var(--e);
}
body[data-hist="off"] { --hist: 0px; }

/* ── Splitter ────────────────────────────────────────────────── */
/* Zero-width in flow; the grab area is drawn outside its own box so
   it never steals layout space from either pane. */
.splitter {
  position: relative;
  z-index: 15;
  cursor: col-resize;
  touch-action: none;
}
.splitter::before {
  content: "";
  position: absolute;
  top: 0; bottom: 0; left: -4px; right: -4px;
}
.splitter::after {
  content: "";
  position: absolute;
  top: 0; bottom: 0; left: -1px;
  width: 2px;
  background: var(--a);
  opacity: 0;
  transition: opacity var(--t2) var(--e);
}
.splitter:hover::after { opacity: 0.5; }
body[data-drag="on"] .splitter::after { opacity: 1; }
body[data-drag="on"] { cursor: col-resize; user-select: none; }
body[data-drag="on"] .viewport { pointer-events: none; }

.composer {
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto;
  border-right: 1px solid var(--line);
  background: var(--chrome);
  min-height: 0;
}
/* Flex column, not grid: the prompt field needs to absorb leftover height
   so the primary input is the biggest thing in the panel. */
.composer-scroll {
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}
.composer-scroll > * { flex: none; }
.composer-heading {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  color: var(--fg-2);
  font-size: var(--fz-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.01em;
}
.section-ico {
  width: 16px;
  height: 16px;
  color: var(--fg-3);
  fill: currentColor;
  stroke: none;
  transition: color var(--t1) var(--e), transform var(--t2) var(--e-out);
}
.composer-heading .section-ico { color: var(--a); }
.composer-foot {
  padding: var(--sp-3) var(--sp-4) var(--sp-4);
  display: grid; gap: 6px;
  border-top: 1px solid var(--line);
  /* Sits inside the composer, so it fades into chrome, not the page. */
  background: linear-gradient(to top, var(--chrome) 60%, transparent);
}

/* ratio cards: the glyph is a true scale model of the output */
.ratios { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--sp-2); }
.ratio {
  display: grid; place-items: center; gap: 6px;
  padding: 8px 4px 7px;
  color: var(--fg-4);
  background: var(--raised);
  border: 1px solid var(--line);
  border-radius: var(--r2);
  transition: color var(--t1) var(--e), background var(--t1) var(--e),
    border-color var(--t1) var(--e), transform var(--t1) var(--e);
}
.ratio-box { display: grid; place-items: center; height: 20px; }
.ratio i { display: block; border: 1.5px solid currentColor; border-radius: 2.5px; transition: border-color var(--t1) var(--e); }
.ratio b { font: var(--fw-medium) var(--fz-2xs)/1 var(--mono); letter-spacing: 0; }
.ratio:hover { color: var(--fg-2); border-color: var(--line-2); transform: translateY(-1px); }
.ratio[aria-checked="true"] { color: var(--a); background: var(--a-dim); border-color: var(--a-line); }

/* drawer */
/* Containers that sit on --raised re-point the well pair so controls inside
   them recess by the same single notch they do out on the panel. Without this
   a select in the 参数 drawer sat 5.5 below its own background — the "internals
   look far too dark" report. */
.drawer,
.gate-card,
.picker-card,
.dropzone-card,
.toast {
  --well: #19191c;         /* ΔL* 2.6 below --raised */
  --well-focus: #17171a;
}

.drawer { border: 1px solid var(--line); border-radius: var(--r3); background: var(--raised); overflow: hidden; }
.drawer summary {
  display: flex; align-items: center; gap: var(--sp-2);
  min-height: 40px;
  padding: 8px var(--sp-3);
  font-size: var(--fz-xs); font-weight: var(--fw-medium); color: var(--fg-2);
  cursor: pointer; list-style: none;
  transition: background var(--t1) var(--e), color var(--t1) var(--e), box-shadow var(--t1) var(--e);
}
.drawer summary::-webkit-details-marker { display: none; }
.drawer summary:hover { background: var(--hover); color: var(--fg); }
.drawer summary:hover .section-ico { color: var(--fg-2); transform: scale(1.04); }
.drawer[open] > summary {
  color: var(--fg);
  background: color-mix(in srgb, var(--hover) 56%, transparent);
  box-shadow: inset 0 -1px var(--line);
}
.drawer[open] > summary .section-ico { color: var(--a); }
.caret {
  margin-left: auto; width: 7px; height: 7px; flex: none;
  border-right: 1.4px solid var(--fg-3); border-bottom: 1.4px solid var(--fg-3);
  transform: rotate(45deg) translate(-1px, -1px);
  transition: transform var(--t2) var(--e);
}
.drawer[open] .caret { transform: rotate(225deg) translate(-1px, -1px); }
.badge {
  padding: 2px 7px;
  font: var(--fw-medium) var(--fz-2xs) var(--mono);
  color: var(--a); background: var(--a-dim);
  border-radius: 20px;
}
.badge[hidden] { display: none; }
.drawer-body {
  display: grid; gap: var(--sp-3);
  padding: var(--sp-1) var(--sp-3) var(--sp-3);
  animation: drawerIn var(--t2) var(--e-out);
}
@keyframes drawerIn { from { opacity: 0; transform: translateY(-6px); } }

.settings-block { display: grid; gap: 7px; }
.settings-block + .settings-block {
  padding-top: var(--sp-3);
  border-top: 1px solid var(--line);
}
.setting-line { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); }
.setting-line > label { font-size: var(--fz-xs); font-weight: var(--fw-medium); color: var(--fg-3); }
.setting-count { width: 104px; flex: none; }

.seed { display: grid; grid-template-columns: 1fr auto; gap: var(--sp-2); }

/* ── Stage ───────────────────────────────────────────────────── */
.stage {
  position: relative;
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto;
  min-width: 0;
  min-height: 0;
  background: var(--chrome);
}

/* Generation state remains available to screen readers while visible status
   stays in the busy/error layers instead of consuming a permanent toolbar. */
/* Present to assistive tech, absent from layout and paint. */
.stage-status,
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
/* The mat. Slight vignette gives depth without drawing attention. */
.viewport {
  position: relative;
  display: grid;
  place-items: center;
  padding: var(--sp-5);
  min-height: 0;
  overflow: hidden;
  /* The darkest surface in the app. Everything else rose to make the
     panels legible; this went the other way so the print still reads
     as the one bright object on screen. */
  background:
    radial-gradient(120% 90% at 50% 0%, rgba(255,255,255,0.022), transparent 62%),
    var(--canvas);
}

/* Layers cross-fade. `visibility` (not display) so it can transition
   and still leave the a11y tree + hit-testing. */
.layer {
  grid-area: 1 / 1;
  width: 100%; height: 100%;
  min-height: 0;
  opacity: 0; visibility: hidden;
  transition: opacity var(--t2) var(--e), visibility var(--t2);
}
.layer.center { display: grid; place-items: center; }

/* Inactive layers are taken out of layout entirely. Leaving them in the
   grid let the hidden skeleton influence max-content sizing, which fed the
   scrollbar/resize feedback loop in 1:1 mode. */
.layer { content-visibility: hidden; }
body[data-state="empty"] .layer[data-layer="empty"],
body[data-state="busy"] .layer[data-layer="busy"],
body[data-state="error"] .layer[data-layer="error"],
body[data-state="done"] .layer[data-layer="image"] { content-visibility: visible; }

body[data-state="empty"] .layer[data-layer="empty"],
body[data-state="busy"] .layer[data-layer="busy"],
body[data-state="error"] .layer[data-layer="error"],
body[data-state="done"] .layer[data-layer="image"] {
  opacity: 1; visibility: visible;
}

/* blank / error copy block */
.blank { display: grid; justify-items: center; gap: var(--sp-3); text-align: center; max-width: 280px; color: var(--fg-4); }
.blank-s { font-size: var(--fz-sm); line-height: 1.6; color: var(--fg-3); }
.blank-s kbd { margin: 0 1px; }
.blank .btn { margin-top: var(--sp-1); }

/* ── History rail (right) ────────────────────────────────────── */
.gallery {
  display: flex;
  flex-direction: column;
  border-left: 1px solid var(--line);
  background: var(--chrome);
  overflow: hidden;
  min-width: 0;
}
body[data-hist="off"] .gallery { border-left-color: transparent; }
/* The rail keeps a compact heading for its count and clear action. */
.gallery-head {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2);
  height: var(--head-h); flex: none;
  padding: 0 var(--sp-2) 0 var(--sp-3);
  border-bottom: 1px solid var(--line);
  font-size: var(--fz-2xs); color: var(--fg-4);
  overflow: hidden;
}
.gallery-head > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.gallery-head .btn-link { flex: none; }
.gallery-head .btn-link[hidden] { display: none; }

/* vertical stack */
.tray {
  flex: 1 1 auto;
  display: flex; flex-direction: column; gap: 6px;
  overflow-y: auto; overscroll-behavior: contain;
  padding: var(--sp-2);
  min-height: 0;
}
.tray:empty { flex: none; padding: 0; }
.tray-empty {
  flex: none;
  padding: var(--sp-4) var(--sp-2);
  font-size: var(--fz-2xs); line-height: 1.55; color: var(--fg-4); text-align: center;
}
.tray-empty[hidden] { display: none; }

.tile {
  position: relative; flex: none;
  width: 100%;
  /* A uniform square grid. Letting each tile take the aspect of its image made
     a portrait thumbnail nearly three times the height of a landscape one, so
     the rail scanned as randomly sized blocks and the eye had no column to
     follow. The shape of a result is already legible from the crop. */
  aspect-ratio: 1 / 1;
  border-radius: var(--r1);
  overflow: hidden;
  /* Letterbox bars: the same mat the stage uses, so a thumbnail reads as a
     small print rather than as a control with a picture in it. */
  background: var(--canvas);
  box-shadow: 0 0 0 1px var(--line);
  transition: box-shadow var(--t1) var(--e), transform var(--t2) var(--e-out);
  /* skip paint work for tiles scrolled out of the rail */
  content-visibility: auto;
  contain-intrinsic-size: auto 140px;
}
/* The whole picture, letterboxed. `cover` filled the square but cropped the
   composition — on a portrait result that meant losing the top and bottom of
   the very thing being judged. `contain` keeps the grid uniform AND the frame
   intact; the unused area is the tile's own dark backing. */
.tile img { display: block; width: 100%; height: 100%; object-fit: contain; }
.tile:hover { transform: translateY(-1px); box-shadow: 0 0 0 1px var(--line-2), 0 6px 14px -4px rgba(0,0,0,.6); }
.tile[aria-current="true"] { box-shadow: 0 0 0 1.5px var(--a); }

/* Placeholder for the image still being generated. Carries the same rings as
   the stage frame so the rail and the viewport read as one pending operation. */
.tile-pending {
  background:
    linear-gradient(170deg, rgba(255, 255, 255, 0.026), transparent 62%),
    var(--canvas);
  box-shadow: 0 0 0 1px var(--a-line);
  content-visibility: visible;   /* never skip paint: it is the active element */
}
/* Currently on screen: matches how a selected thumbnail reads. */
.tile-pending[aria-current="true"] { box-shadow: 0 0 0 1.5px var(--a); }
/* Browsing elsewhere — invite the click back. */
.tile-pending[aria-current="false"]:hover {
  transform: translateY(-1px);
  box-shadow: 0 0 0 1.5px var(--a), 0 6px 14px -4px rgba(0, 0, 0, 0.6);
}
.tile-x {
  position: absolute; top: 3px; right: 3px;
  width: 17px; height: 17px;
  display: grid; place-items: center;
  font-size: var(--fz-2xs); line-height: 1; color: #fff;
  background: rgba(0,0,0,0.6);
  backdrop-filter: blur(3px);
  border-radius: 5px;
  opacity: 0;
  transition: opacity var(--t1) var(--e), background var(--t1) var(--e);
}
.tile:hover .tile-x, .tile-x:focus-visible { opacity: 1; }
.tile-x:hover { background: var(--err); color: var(--a-ink); }

/* ── Import sheet ────────────────────────────────────────────── */
/* Same figure as .gate but on its own attribute hook, because the gate's
   transitions are bound to body[data-gate] and that flag is spent by the
   time the app is usable. */
.sheet {
  position: fixed; inset: 0; z-index: var(--z-modal);
  display: grid; place-items: center; padding: var(--sp-5);
  background: rgba(8, 8, 10, 0.9);
  backdrop-filter: blur(10px) saturate(120%);
  opacity: 0;
  transition: opacity var(--t3) var(--e);
}
.sheet[hidden] { display: none; }
.sheet > .gate-card { width: min(468px, 100%); }
/* Scoped by id: an unscoped .sheet rule here also revealed the tag picker. */
body[data-import="open"] #importSheet { opacity: 1; }
body[data-import="open"] #importSheet > .gate-card { transform: none; }

.import-list { display: grid; gap: var(--sp-1); }
/* Each row is a checkbox: importing is opt-out per field, since a stray
   dimension or seed can quietly invalidate everything else. */
.import-row {
  display: grid;
  grid-template-columns: 16px minmax(0, 1fr) minmax(0, 1.35fr);
  align-items: center;
  gap: var(--sp-3);
  padding: 7px 9px;
  border: 1px solid transparent;
  border-radius: var(--r2);
  cursor: pointer;
  transition: background var(--t1) var(--e), border-color var(--t1) var(--e);
}
.import-row:hover { background: var(--sunken); }
.import-row:has(input:checked) { border-color: var(--a-line); background: var(--a-dim); }
.import-row input { position: absolute; opacity: 0; width: 0; height: 0; }
.import-row > .box {
  position: relative; flex: none;
  width: 16px; height: 16px;
  background: var(--well);
  border: 1px solid var(--line-2);
  border-radius: 5px;
  transition: background var(--t1) var(--e), border-color var(--t1) var(--e);
}
.import-row > .box::after {
  content: ""; position: absolute; inset: 0;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m5 12 4.5 4.5L19 7' fill='none' stroke='%2317132a' stroke-width='2.1' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center/11px no-repeat;
  opacity: 0; transform: scale(0.6);
  transition: opacity var(--t1) var(--e), transform var(--t2) var(--e-out);
}
.import-row input:checked + .box { background: var(--a); border-color: var(--a); }
.import-row input:checked + .box::after { opacity: 1; transform: none; }
.import-row input:focus-visible + .box { outline: 2px solid var(--a); outline-offset: 2px; }
.import-row > .k { font-size: var(--fz-xs); color: var(--fg-3); }
.import-row > .v {
  font: var(--fw-regular) var(--fz-2xs)/1.45 var(--mono);
  color: var(--fg);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Long prompts get room to breathe rather than a 200-char ellipsis. */
.import-row.wide { grid-template-columns: 16px minmax(0, 1fr); row-gap: 5px; }
.import-row.wide > .v {
  grid-column: 1 / -1;
  white-space: pre-wrap; overflow-wrap: anywhere;
  max-height: 58px; overflow-y: auto;
  padding: 6px 7px;
  background: var(--well);
  border-radius: var(--r1);
}
.import-foot { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-2); }

/* ── Tag picker ──────────────────────────────────────────────── */
body[data-picker="open"] #pickerSheet { opacity: 1; }
body[data-picker="open"] .picker-card { transform: none; }
.picker-card {
  display: grid;
  grid-template-rows: auto auto auto auto minmax(0, 1fr) auto;
  gap: var(--sp-3);
  width: min(520px, 100%);
  height: min(640px, calc(100dvh - 2 * var(--sp-5)));
  padding: var(--sp-4);
  background: var(--raised);
  border: 1px solid var(--line);
  border-radius: var(--r4);
  box-shadow: var(--sh-lip), var(--sh-4);
  transform: translateY(10px) scale(0.985);
  transition: transform var(--t3) var(--e-out);
}
.picker-head { display: flex; align-items: center; gap: var(--sp-3); }
.picker-title { display: flex; align-items: center; gap: var(--sp-2); margin-right: auto; }
.picker-title .ico { color: var(--a); }
.picker-title strong { font-size: var(--fz-xl); font-weight: var(--fw-bold); letter-spacing: -0.02em; }

.picker-search {
  display: flex; align-items: center;
  background: var(--well);
  border: 1px solid var(--line-2);
  border-radius: var(--r2);
  transition: border-color var(--t1) var(--e), box-shadow var(--t1) var(--e);
}
.picker-search:focus-within { border-color: var(--a-line); box-shadow: var(--ring); }
.picker-search > .ico { margin-left: 10px; color: var(--fg-4); }
.picker-search input {
  flex: 1; width: auto;
  min-height: 38px;
  background: transparent; border: 0; box-shadow: none;
  font-size: var(--fz-md);
}
.picker-search input:hover:not(:focus),
.picker-search input:focus { background: transparent; box-shadow: none; }

/* What this session put in, so it can be taken back out without hunting
   through the prompt text. */
.picker-chosen { display: grid; gap: var(--sp-2); }
.picker-chosen > span { font-size: var(--fz-2xs); color: var(--fg-4); }
.picker-chosen > div { display: flex; flex-wrap: wrap; gap: var(--sp-1); }
.picker-chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 8px;
  font: var(--fw-regular) var(--fz-2xs)/1.5 var(--mono);
  color: var(--a);
  background: var(--a-dim);
  border: 1px solid var(--a-line);
  border-radius: 20px;
  transition: color var(--t1) var(--e), background var(--t1) var(--e);
}
.picker-chip i {
  width: 7px; height: 7px; flex: none;
  background: currentColor;
  clip-path: polygon(14% 0, 50% 36%, 86% 0, 100% 14%, 64% 50%, 100% 86%, 86% 100%, 50% 64%, 14% 100%, 0 86%, 36% 50%, 0 14%);
}
.picker-chip:hover { color: var(--err); background: var(--err-dim); border-color: transparent; }

.picker-status { min-height: 15px; font-size: var(--fz-xs); color: var(--fg-3); }
.picker-status[hidden] { display: none; }
.picker-status.err { color: var(--err); }
.picker-status.load { display: flex; align-items: center; gap: var(--sp-2); }
.picker-status.load::before {
  content: "";
  flex: none; width: 11px; height: 11px;
  border: 1.5px solid var(--line-2);
  border-top-color: var(--a);
  border-radius: 50%;
  animation: tagSpin 620ms linear infinite;
}

.picker-results {
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-gutter: stable;
  display: grid; gap: 1px;
  align-content: start;
}
.picker-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-3);
  width: 100%;
  min-height: 44px;
  padding: 6px 9px;
  color: var(--fg-2);
  text-align: left;
  border: 1px solid transparent;
  border-radius: var(--r2);
  transition: color var(--t1) var(--e), background var(--t1) var(--e),
    border-color var(--t1) var(--e);
}
.picker-row:hover, .picker-row.on { color: var(--fg); background: var(--hover); }
/* Already in the prompt: clicking again removes it. */
.picker-row[aria-pressed="true"] {
  color: var(--fg);
  background: var(--a-dim);
  border-color: var(--a-line);
}
.picker-row[aria-pressed="true"] .picker-names strong::after {
  content: "";
  display: inline-block;
  width: 9px; height: 9px;
  margin-left: 6px;
  background: var(--a);
  clip-path: polygon(19% 52%, 36% 68%, 82% 22%, 92% 33%, 37% 87%, 8% 60%);
}
.picker-names { display: grid; gap: 1px; overflow: hidden; }
.picker-names strong,
.picker-names span { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.picker-names strong { font: var(--fw-medium) var(--fz-sm)/1.35 var(--mono); }
.picker-names span { font-size: var(--fz-xs); color: var(--fg-3); }
.picker-meta {
  flex: none;
  display: inline-flex; align-items: center; gap: 5px;
  font: var(--fw-regular) var(--fz-2xs)/1.3 var(--mono);
  color: var(--fg-4);
  white-space: nowrap;
}
.picker-foot {
  padding-top: var(--sp-2);
  border-top: 1px solid var(--line);
  font-size: var(--fz-2xs); color: var(--fg-4);
}

/* ── Dropzone ────────────────────────────────────────────────── */
.dropzone {
  position: fixed; inset: 0; z-index: var(--z-scrim);
  display: grid; place-items: center;
  padding: var(--sp-5);
  background: rgba(8, 8, 10, 0.82);
  backdrop-filter: blur(6px);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--t2) var(--e);
}
body[data-drop="on"] .dropzone { opacity: 1; }
.dropzone-card {
  display: grid; justify-items: center; gap: 9px;
  padding: var(--sp-6) var(--sp-7);
  color: var(--fg-2);
  text-align: center;
  background: color-mix(in srgb, var(--raised) 92%, transparent);
  border: 1.5px dashed var(--a-line);
  border-radius: var(--r4);
  transform: scale(0.98);
  transition: transform var(--t2) var(--e-out);
}
body[data-drop="on"] .dropzone-card { transform: none; }
.dropzone-card .ico { color: var(--a); }
.dropzone-card strong { font-size: var(--fz-md); font-weight: var(--fw-semibold); color: var(--fg); }
.dropzone-card span { font-size: var(--fz-xs); color: var(--fg-4); }

/* ── Responsive ──────────────────────────────────────────────── */
/* ── Tablet / narrow desktop ──────────────────────────────────── */
@media (max-width: 1080px) {
  :root { --rail: 280px; --hist: 112px; }
}

/* ── Mobile: single column, stage first, controls in a sheet ───── */
@media (max-width: 860px) {
  :root { --bar-h: 54px; --ico: 16px; }

  /* Infrequent tools move into an off-canvas rail, leaving the image width
     untouched while the point balance remains comfortably readable. */
  .topbar { gap: var(--sp-2); padding: 0 var(--sp-3); }
  .brand { gap: 7px; }
  .brand-mark { width: 25px; height: 25px; }
  .brand-title { font-size: 18px; }
  .brand-subtitle { font-size: 9px; letter-spacing: 0.1em; }
  .meter { min-width: 138px; padding: 5px 9px 6px; }
  .meter-icon { width: 18px; height: 18px; }
  /* Provider names are mixed case; the wide tracking here was tuned for the
     old all-caps FREE/POINTS and now just loosens a proper noun. */
  .meter-k { font-size: 9.5px; letter-spacing: 0.01em; }
  .meter-v b { font-size: var(--fz-xs); }

  .topbar-menu-toggle {
    display: inline-grid;
    width: 38px; height: 38px;
    --ico: 20px;
    color: var(--fg-2);
  }
  .topbar-menu-toggle .menu-close { display: none; }
  body[data-topbar-menu="open"] .topbar-menu-toggle .menu-open { display: none; }
  body[data-topbar-menu="open"] .topbar-menu-toggle .menu-close { display: block; }
  .topbar-menu-backdrop {
    display: block;
    position: fixed;
    inset: var(--bar-h) 0 0;
    z-index: 1;
    border: 0; border-radius: 0;
    background: rgba(8, 8, 10, 0.56);
    backdrop-filter: blur(3px);
    opacity: 0; visibility: hidden;
    pointer-events: none;
    transition: opacity var(--t2) var(--e), visibility 0s var(--t2);
  }
  .topbar-end {
    position: fixed;
    top: var(--bar-h); right: 0; bottom: 0;
    z-index: 2;
    width: min(224px, 72vw);
    padding: var(--sp-3);
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: var(--sp-1);
    overflow-x: hidden;
    overflow-y: auto;
    overscroll-behavior: contain;
    background: color-mix(in srgb, var(--raised) 97%, transparent);
    border-left: 1px solid var(--line-2);
    box-shadow: var(--sh-4);
    transform: translateX(100%);
    visibility: hidden;
    transition: transform var(--t3) var(--e-out), visibility 0s var(--t3);
  }
  .topbar-end .ibtn {
    width: 100%; height: 42px;
    display: flex;
    justify-content: flex-start;
    gap: var(--sp-3);
    padding: 0 var(--sp-3);
    --ico: 17px;
    color: var(--fg-2);
    background: transparent;
    box-shadow: none;
  }
  .topbar-end .ibtn:hover { background: var(--hover); box-shadow: none; }
  .topbar-end .ibtn[hidden] { display: none; }
  .topbar-action-label { display: inline; font-size: var(--fz-sm); font-weight: var(--fw-medium); }
  body[data-topbar-menu="open"] { overflow: hidden; }
  body[data-topbar-menu="open"] .topbar-menu-backdrop {
    opacity: 1; visibility: visible; pointer-events: auto; transition-delay: 0s;
  }
  body[data-topbar-menu="open"] .topbar-end {
    transform: none; visibility: visible; transition-delay: 0s;
  }

  .topbar-history-slot {
    display: flex;
    flex: 1 1 auto;
    min-width: 0;
    min-height: 0;
    margin-top: var(--sp-2);
    padding-top: var(--sp-2);
    border-top: 1px solid var(--line);
    overflow: hidden;
  }
  body[data-hist="off"] .topbar-history-slot { display: none; }

  /* dvh avoids the iOS/Android URL-bar height jump */
  .shell {
    grid-template-columns: 1fr;
    grid-template-rows: auto auto;
    height: auto;
    min-height: calc(100dvh - var(--bar-h));
    padding-bottom: var(--mobile-foot-h);
  }
  .splitter { display: none; }     /* no pointer to drag with */

  /* image first — it is the reason the page exists */
  .stage { order: 1; }
  .viewport {
    height: clamp(440px, calc(100svh - var(--bar-h)), 720px);
    padding: var(--sp-3);
  }
  .result-panel { padding: 10px var(--sp-3) var(--sp-3); gap: var(--sp-2); }

  /* History lives below the tools inside the manually opened side rail. */
  .topbar-history-slot .gallery {
    flex: 1 1 auto;
    width: 100%;
    min-width: 0;
    min-height: 0;
    border: 0;
    background: transparent;
    overflow: hidden;
  }
  body[data-hist="off"] .gallery { display: none; }
  .topbar-history-slot .gallery-head {
    height: 36px;
    flex: none;
    padding: 0 2px 0 var(--sp-1);
    border-bottom: 1px solid var(--line);
    flex-direction: row;
    gap: var(--sp-2);
  }
  .topbar-history-slot .gallery-head .ibtn {
    width: 32px;
    height: 32px;
    flex: none;
    justify-content: center;
    padding: 0;
  }
  .topbar-history-slot .tray {
    flex: 1 1 auto;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    align-content: start;
    padding: var(--sp-2) 2px;
    gap: var(--sp-2);
    overflow-x: hidden;
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  .topbar-history-slot .tray:empty { display: none; }
  .topbar-history-slot .tile {
    width: 100%;
    height: auto;
    aspect-ratio: 1;
  }
  .topbar-history-slot .tray-empty {
    flex: none;
    padding: var(--sp-4) var(--sp-2);
    text-align: center;
  }

  /* composer scrolls with the document; no nested scrollport on touch */
  .composer {
    order: 3;
    border-right: 0; border-top: 1px solid var(--line);
    display: block;
  }
  .composer-scroll { overflow: visible; padding: var(--sp-4) var(--sp-3); }
  .fld-hero { min-height: 0; }
  .fld-hero .hl-wrap { height: auto; min-height: 128px; max-height: 42dvh; }

  /* The primary action belongs to the viewport, not the scroll position. */
  .composer-foot {
    position: fixed;
    left: 0; right: 0; bottom: 0;
    z-index: var(--z-action);
    padding: var(--sp-2) var(--sp-3) calc(var(--sp-2) + env(safe-area-inset-bottom));
    background: color-mix(in srgb, var(--chrome) 94%, transparent);
    backdrop-filter: blur(12px);
    border-top: 1px solid var(--line);
    box-shadow: 0 -10px 28px -20px rgba(0, 0, 0, .9);
  }
  .btn-go { height: 44px; }        /* 44px = comfortable tap target */

  .ibtn { width: 34px; height: 34px; }
  .ibtn-xs { width: 28px; height: 28px; }
  .check { min-height: 32px; }
  input[type="range"] { height: 28px; }
  .ratio { padding: 10px 4px 8px; }
  
  .tag-suggest { padding: 4px; border-radius: 11px; }
  .tag-suggest-option { min-height: 44px; padding: 6px 8px; }
  .tag-suggest-names strong { font-size: var(--fz-sm); }
  .tag-suggest-names span { font-size: var(--fz-xs); }

  /* full-bleed sheet instead of a floating dialog */
  .gate { padding: 0; align-items: stretch; }
  .gate-card {
    width: 100%; max-height: none; min-height: 100dvh;
    border: 0; border-radius: 0;
    padding: var(--sp-5) var(--sp-4) calc(var(--sp-5) + env(safe-area-inset-bottom));
    align-content: start;
  }

  .toasts { bottom: calc(var(--mobile-foot-h) + var(--sp-3)); }
  .lightbox { padding: var(--sp-3); }
  .lightbox-x { top: calc(var(--sp-3) + env(safe-area-inset-top)); right: var(--sp-3); }

  /* The import list is short, so a bottom sheet beats a full-height page —
     but override the .gate-card rule above, which it otherwise inherits. */
  .sheet { padding: 0; align-items: end; }
  #importSheet > .gate-card {
    width: 100%; min-height: 0; max-height: 88dvh;
    border: 0; border-radius: var(--r4) var(--r4) 0 0;
    padding: var(--sp-5) var(--sp-4) calc(var(--sp-4) + env(safe-area-inset-bottom));
    align-content: start;
    transform: translateY(12px);
  }
  .import-row { padding: 9px; }
  .dropzone-card { padding: var(--sp-5) var(--sp-5); }

  /* Tall sheet: the list is the point, and the on-screen keyboard will take
     the lower half once the field is focused. */
  .picker-card {
    width: 100%;
    height: 88dvh;
    border: 0;
    border-radius: var(--r4) var(--r4) 0 0;
    padding: var(--sp-4) var(--sp-3) calc(var(--sp-3) + env(safe-area-inset-bottom));
    transform: translateY(12px);
  }
  .picker-row { min-height: 48px; }
}

@media (max-width: 480px) {
  /* Compact but complete now that utility actions live in the side rail. */
  .brand { gap: 6px; }
  .brand-mark { width: 24px; height: 24px; }
  .brand-wordmark { gap: 6px; }
  .topbar { padding-inline: 10px; gap: 6px; }
  .meters { margin-left: 14px; }
  .meter { min-width: 134px; padding-inline: 8px; }
  .topbar-menu-toggle { width: 36px; height: 36px; --ico: 19px; }

  .ratios { grid-template-columns: repeat(4, 1fr); gap: 5px; }
  .pair { grid-template-columns: 1fr 1fr; }
  .seg { grid-auto-flow: row; }

  .result-bottom { gap: var(--sp-2); }
  .result-param { padding-inline: 8px; }
  .result-actions { gap: 2px; }
}

@media (max-width: 380px) {
  .meter { min-width: 132px; }
}

@media (max-width: 340px) {
  .topbar { padding-inline: 6px; }
  .meters { margin-left: 12px; }
}

/* Tooltips are hover-only affordances; on touch they fire on tap and linger,
   so suppress them and rely on aria-label instead. */
@media (hover: none) {
  [data-tip]::after { content: none; }
  .tile-x { opacity: 1; }          /* no hover to reveal the delete button */

  /* WCAG 2.5.5: 44px minimum. The desktop sizes (32/26px) are fine for a
     mouse but not a fingertip. Grown via padding and a negative margin so
     the tap area expands without shifting any surrounding layout. */
  .ibtn, .ibtn-xs {
    width: 44px; height: 44px;
  }
  .topbar-end { gap: 0; }
  .result-actions { gap: 0; }
}
