/* náhľad workspace styles.
 *
 * Loaded by index.html only. The tools menu and the feedr gallery still carry
 * their own inline styles, so this is the workspace's stylesheet rather than
 * the shared one it was originally meant to become. Worth knowing before
 * changing a colour here and expecting the other two pages to follow.
 *
 * The interface is achromatic on purpose. This tool exists to be judged by
 * eye, and a saturated control beside a warm interior shot changes how that
 * shot reads. So the chrome is grey, the photograph is the only colour on
 * screen, and the one saturated thing left marks what costs money.
 */

:root {
  /* Dark elevation: the canvas is the void and every surface above it is
     LIGHTER. Inverting this is what makes a dark panel read as a hole. */
  --void: #08080a;
  --surface: #121215;
  --raised: #1b1b20;
  --hover: #232329;

  --line: rgba(255, 255, 255, .07);
  --line-2: rgba(255, 255, 255, .13);

  --text: #fafafa;
  --muted: #8e8e97;
  --dim: #63636c;

  --ink: #fff;              /* the primary action, and nothing else */
  --signal: #f2b45c;        /* this will cost a credit */
  --free: #82d3a6;
  --err: #e8796a;

  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
          "Helvetica Neue", Arial, sans-serif;

  --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px; --s5: 24px; --s6: 32px;
  --r-sm: 7px; --r: 10px; --r-lg: 14px;

  --rail: 84px; --panel: 328px; --top: 56px;
  --shadow: 0 24px 70px rgba(0, 0, 0, .62);

  /* Motion. Three durations and three curves, used everywhere, because the
     alternative is what this file had: .12s, .14s, .15s and .16s all doing
     the same job with no reason to differ.

     Decelerating for things arriving, accelerating for things leaving, and a
     symmetric curve only for something that loops. */
  --t-fast: .16s;    /* hover, press, colour */
  --t-mid: .28s;     /* things appearing: popovers, toasts */
  --t-slow: .52s;    /* the result of a generation */
  --out: cubic-bezier(.16, .84, .44, 1);      /* arriving */
  --in: cubic-bezier(.5, 0, .84, .16);        /* leaving */
  --inout: cubic-bezier(.65, 0, .35, 1);      /* looping */
}

* { box-sizing: border-box; margin: 0; padding: 0; }

html, body { height: 100%; }

body {
  background: var(--void);
  color: var(--text);
  font-family: var(--font);
  font-size: 14px;
  line-height: 1.5;
  letter-spacing: -.006em;
  -webkit-font-smoothing: antialiased;
}

/* Three type roles. A fourth is usually a decision nobody made. */
.eyebrow {
  font-size: 10.5px; color: var(--dim); font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase;
}
.hint { font-size: 12px; color: var(--dim); }
.err { color: var(--err); font-size: 12.5px; }

:where(button, a, input, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--text); outline-offset: 2px; border-radius: var(--r-sm);
}

/* --- buttons ------------------------------------------------------------
 * One base, three variants. The primary is white because on this ground
 * nothing needs to be louder, and it leaves the accent free to mean one
 * specific thing rather than "clickable". */
.btn {
  font: inherit; font-size: 13.5px; font-weight: 600; letter-spacing: -.01em;
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
  color: var(--text); background: transparent;
  border: 1px solid var(--line-2); border-radius: var(--r);
  padding: 10px 16px; cursor: pointer; white-space: nowrap;
  transition: background var(--t-fast) var(--out),
              border-color var(--t-fast) var(--out),
              color var(--t-fast) var(--out), opacity var(--t-fast) var(--out);
}
.btn:hover:not(:disabled) { background: var(--hover); }
.btn:active:not(:disabled) { transform: translateY(.5px); }
.btn:disabled { opacity: .38; cursor: not-allowed; }

.btn.primary {
  background: var(--ink); color: #0a0a0c; border-color: var(--ink); font-weight: 650;
}
.btn.primary:hover:not(:disabled) { background: #ebebee; border-color: #ebebee; }

.btn.ghost { border-color: transparent; color: var(--muted); }
.btn.ghost:hover:not(:disabled) { color: var(--text); background: var(--hover); }

.btn.sm { padding: 7px 12px; font-size: 12.5px; border-radius: var(--r-sm); }
.btn.block { width: 100%; }

/* --- top bar ------------------------------------------------------------ */
.top {
  position: fixed; inset: 0 0 auto 0; height: var(--top); z-index: 40;
  display: flex; align-items: center; gap: var(--s5); padding: 0 var(--s4);
  background: var(--surface); border-bottom: 1px solid var(--line);
}
.brand {
  font-size: 15px; font-weight: 700; letter-spacing: -.045em;
  display: flex; align-items: center; gap: var(--s2); white-space: nowrap;
}
/* The mark, not a decoration: one rounded frame split corner to corner, the
   drawing and the photograph of it being the same picture. Masked rather than
   inlined so it is a single element taking its colour from the text — which is
   what lets admin.html recolour it to --signal with one existing rule. 12px:
   at 7px the diagonal survives but the shape is cramped beside 15px type. */
.brand i {
  width: 12px; height: 12px; display: block; flex: none;
  background: currentColor;
  -webkit-mask: url("/mark.svg") center / contain no-repeat;
  mask: url("/mark.svg") center / contain no-repeat;
}
/* What the tool does, where the product switch used to be. One product now,
   so this is a statement rather than a choice. */
.tag-line {
  font-size: 11.5px; color: var(--dim); letter-spacing: .04em;
  padding-left: var(--s3); margin-left: var(--s1);
  border-left: 1px solid var(--line); white-space: nowrap;
}

.top .grow { flex: 1; }

.credits { display: flex; align-items: baseline; gap: 6px;
           text-decoration: none; color: inherit; border-radius: var(--r-sm);
           padding: 3px 6px; margin: -3px -6px;
           transition: background var(--t-fast) var(--out); }
a.credits:hover { background: var(--hover); }
.credits b { font-size: 15px; font-weight: 650; font-variant-numeric: tabular-nums; }
.credits span { font-size: 11.5px; color: var(--dim); }
.credits.low b { color: var(--signal); }
.credits.empty b { color: var(--err); }

/* --- three attached columns --------------------------------------------- */
.app {
  position: fixed; inset: var(--top) 0 0 0;
  display: grid; grid-template-columns: var(--rail) 1fr var(--panel);
}

/* --- rail --------------------------------------------------------------- */
.rail {
  background: var(--surface); border-right: 1px solid var(--line);
  padding: var(--s3); display: flex; flex-direction: column; gap: var(--s2);
  overflow-y: auto; scrollbar-width: none;
}
.rail::-webkit-scrollbar { display: none; }
.drop {
  border: 1px dashed var(--line-2); border-radius: 9px; aspect-ratio: 4/3;
  display: grid; place-items: center; text-align: center; color: var(--dim);
  cursor: pointer; font-size: 11px; line-height: 1.3; flex: none;
  transition: border-color var(--t-fast) var(--out), color var(--t-fast) var(--out);
}
.drop:hover, .drop.hover { border-color: var(--muted); color: var(--text); }
.drop b { display: block; font-size: 17px; font-weight: 300; line-height: 1; }

.ph {
  position: relative; border-radius: 9px; overflow: hidden; cursor: pointer;
  flex: none; box-shadow: 0 0 0 1px var(--line); transition: box-shadow var(--t-fast) var(--out);
}
.ph:hover { box-shadow: 0 0 0 1px var(--line-2); }
.ph.on { box-shadow: 0 0 0 2px var(--ink); }
.ph img {
  display: block; width: 100%; aspect-ratio: 4/3; object-fit: cover;
  opacity: .55; transition: opacity var(--t-fast) var(--out);
}
.ph:hover img { opacity: .85; }
.ph.on img { opacity: 1; }
.ph .n {
  position: absolute; left: 0; right: 0; bottom: 0; font-size: 9.5px;
  padding: 10px 5px 3px; color: #fff;
  background: linear-gradient(to top, rgba(0, 0, 0, .78), transparent);
}
/* The only saturated pixel in the rail: this photo will cost a credit. */
.ph .due {
  position: absolute; right: 5px; top: 5px; width: 5px; height: 5px;
  border-radius: 50%; background: var(--signal);
  box-shadow: 0 0 0 2px rgba(8, 8, 10, .65);
}
.ph.skel { aspect-ratio: 4/3; background: var(--raised); }

/* --- canvas ------------------------------------------------------------- */
.canvas {
  position: relative; overflow: hidden; min-width: 0;
  display: flex; align-items: center; justify-content: center; padding: var(--s5);
}
/* A blurred copy of the result fills the surround, so the picture can be
   CONTAINED rather than cropped and still read as full bleed. Cropping the
   thing the customer is judging is not an option. */
.canvas .bg {
  position: absolute; inset: -80px; width: calc(100% + 160px);
  height: calc(100% + 160px); object-fit: cover;
  filter: blur(52px) brightness(.5) saturate(.9); transform: scale(1.06);
}
.canvas .veil {
  position: absolute; inset: 0;
  background: radial-gradient(62% 58% at 50% 46%,
              rgba(8, 8, 10, .18), rgba(8, 8, 10, .8));
}
.canvas .empty { position: relative; z-index: 2; text-align: center; max-width: 290px; }
.canvas .empty h2 { font-size: 17px; font-weight: 600; margin-bottom: var(--s2); }
.canvas .empty p { font-size: 12.5px; color: var(--muted); line-height: 1.6; }

/* The picture and everything naming it share one column, so the caption and
   the takes align to the image's own edges rather than to the viewport. */
.work {
  position: relative; z-index: 2; display: flex; flex-direction: column;
  gap: var(--s3); max-width: 100%; max-height: 100%; min-height: 0;
}

.compare {
  position: relative; line-height: 0; border-radius: var(--r);
  overflow: hidden; box-shadow: var(--shadow); min-height: 0; user-select: none;
}
.compare > img { display: block; max-width: 100%; max-height: 100%;
                 width: auto; height: auto; }
.compare .after { position: absolute; inset: 0; overflow: hidden; }
.compare .after img { position: absolute; inset: 0; width: 100%; height: 100%;
                      object-fit: cover; }
.compare input[type=range] {
  position: absolute; inset: 0; width: 100%; height: 100%; margin: 0;
  appearance: none; background: transparent; cursor: col-resize; z-index: 4;
}
/* The native thumb is invisible: the seam below is what you actually see, and
   it tracks the same value. Keeping the input means arrow keys still work. */
.compare input[type=range]::-webkit-slider-thumb {
  appearance: none; width: 34px; height: 100%; background: transparent;
}
.compare input[type=range]::-moz-range-thumb {
  width: 34px; height: 2400px; border: none; background: transparent;
}
.compare .seam {
  position: absolute; top: 0; bottom: 0; z-index: 3; width: 2px;
  background: rgba(255, 255, 255, .94); pointer-events: none;
  transform: translateX(-1px);
}
.compare .grip {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 34px; height: 34px; border-radius: 50%; display: grid;
  place-items: center; color: #0a0a0c; background: #fff;
  box-shadow: 0 2px 10px rgba(0, 0, 0, .45);
}
.compare input[type=range]:focus-visible ~ .seam .grip {
  outline: 2px solid var(--text); outline-offset: 3px;
}
.compare .tag {
  position: absolute; top: 10px; z-index: 3; font-size: 10.5px;
  padding: 4px 10px; border-radius: 999px; pointer-events: none;
  background: rgba(8, 8, 10, .55); backdrop-filter: blur(14px);
  color: rgba(255, 255, 255, .92);
}
.compare .tag.l { left: 10px; }
.compare .tag.r { right: 10px; background: rgba(255, 255, 255, .95);
                  color: #0a0a0c; font-weight: 600; }

/* --- the plain view -----------------------------------------------------
 * A photo with no result yet is just a photo. Comparing it against itself
 * put a slider on screen that did nothing and implied work had happened. */
.plain { position: relative; line-height: 0; border-radius: var(--r);
         overflow: hidden; box-shadow: var(--shadow); min-height: 0; }
.plain img { display: block; max-width: 100%; max-height: 100%;
             width: auto; height: auto; }
.plain .tag {
  position: absolute; top: 10px; left: 10px; z-index: 3; font-size: 10.5px;
  padding: 4px 10px; border-radius: 999px; background: rgba(8, 8, 10, .55);
  backdrop-filter: blur(14px); color: rgba(255, 255, 255, .92);
}

/* --- while the picture is being made ------------------------------------
 * The photograph stays on screen, dimmed and drained of colour, and a band
 * of light travels down it. It is meant to read as the picture being worked
 * on rather than as a page waiting for a server, which is what a spinner in
 * the middle of an empty box says. */
.working { position: relative; overflow: hidden; }
.working img { filter: brightness(.42) saturate(.25); }
.working::after {
  content: ''; position: absolute; inset: 0; z-index: 5; pointer-events: none;
  background: linear-gradient(to bottom,
    transparent 0%, rgba(255, 255, 255, .015) 34%, rgba(255, 255, 255, .11) 47%,
    rgba(255, 255, 255, .3) 50%, rgba(255, 255, 255, .11) 53%,
    rgba(255, 255, 255, .015) 66%, transparent 100%);
  transform: translateY(-100%);
  /* Travels at a near constant rate with soft ends, then waits off screen
     before coming round again. Running edge to edge and restarting on the
     next frame read as a stutter every 2.6 seconds. */
  animation: develop 3.2s var(--inout) infinite;
  will-change: transform;
}
@keyframes develop {
  0%   { transform: translateY(-105%); }
  72%  { transform: translateY(105%); }
  100% { transform: translateY(105%); }
}

.status {
  position: absolute; inset: auto 0 0 0; z-index: 6; padding: var(--s4);
  display: flex; align-items: center; gap: var(--s3);
  background: linear-gradient(to top, rgba(8, 8, 10, .82), transparent);
  font-size: 12.5px;
}
.status .pulse {
  width: 7px; height: 7px; border-radius: 50%; background: var(--text);
  flex: none; animation: pulse 1.6s var(--inout) infinite;
}
@keyframes pulse { 0%, 100% { opacity: .25; transform: scale(.8); }
                   50% { opacity: 1; transform: scale(1); } }
.status .soft { color: var(--dim); }
.status .t { margin-left: auto; color: var(--muted);
             font-variant-numeric: tabular-nums; }

/* --- the result arriving ------------------------------------------------
 * Swapping the image in on one frame reads as a glitch. A short dissolve
 * makes the change legible as a change: this is the new one. */
@keyframes reveal {
  from { opacity: 0; transform: scale(1.012); }
  to   { opacity: 1; transform: scale(1); }
}

/* Switching between takes crossfades the result in place. It used to rebuild
   the whole canvas, which remounted the picture, the blurred surround, the
   caption and the strip, so choosing a take flashed the entire screen. */
.compare .after img.swap { animation: swap var(--t-mid) var(--out); }
@keyframes swap { from { opacity: .25; } to { opacity: 1; } }
.canvas .bg { transition: opacity var(--t-slow) var(--out); }

/* Pictures fade up as they decode instead of appearing between two frames.
   Thumbnails carry their own resting opacity, so the class only removes the
   starting state and lets the existing rule take over. */
.ph img, .tk img {
  opacity: 0; transform: translateY(5px);
  transition: opacity var(--t-fast) var(--out), transform var(--t-mid) var(--out);
}
.ph img.ld, .tk img.ld { transform: none; }
.ph img.ld { opacity: .55; }
.ph:hover img.ld { opacity: .85; }
.ph.on img.ld { opacity: 1; }
.tk img.ld { opacity: .55; }
.tk:hover img.ld { opacity: .8; }
.tk.on img.ld { opacity: 1; }
.fresh .after img, .fresh > img { animation: reveal var(--t-slow) var(--out); }
.fresh .seam { animation: seam-in var(--t-mid) var(--out) .18s backwards; }
@keyframes seam-in { from { opacity: 0; } to { opacity: 1; } }

.skel { position: relative; overflow: hidden; background: var(--raised); }
.skel::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.06), transparent);
  animation: shimmer 1.6s var(--inout) infinite;
}
@keyframes shimmer { 100% { transform: translateX(100%); } }

/* --- caption ------------------------------------------------------------ */
/* The take number belongs beside what it names, not floating in the void. */
.cap { display: flex; align-items: baseline; gap: var(--s3); flex: none;
       min-width: 0; }
.cap .no { font-size: 26px; font-weight: 700; letter-spacing: -.04em;
           font-variant-numeric: tabular-nums; line-height: 1; flex: none; }
.cap h2 { font-size: 15px; font-weight: 600; letter-spacing: -.02em;
           min-width: 0; overflow: hidden; text-overflow: ellipsis;
           white-space: nowrap; }
.cap .meta { font-size: 12px; color: var(--muted); overflow: hidden;
             text-overflow: ellipsis; white-space: nowrap; }
.cap .right { margin-left: auto; display: flex; gap: var(--s2); flex: none; }

/* --- takes -------------------------------------------------------------- */
.takes { display: flex; gap: var(--s2); flex: none; overflow-x: auto;
         scrollbar-width: none; padding-bottom: 2px; }
.takes::-webkit-scrollbar { display: none; }
.tk {
  position: relative; width: 86px; border-radius: var(--r-sm); overflow: hidden;
  cursor: pointer; box-shadow: 0 0 0 1px var(--line); flex: none;
  transition: box-shadow var(--t-fast) var(--out); background: var(--raised);
}
.tk:hover { box-shadow: 0 0 0 1px var(--line-2); }
/* An attempt that produced nothing: no picture, muted, not clickable. It
   exists so the visible strip and the "zostáva X pokusov" counter agree. */
.tk.spent { cursor: default; opacity: .5; display: grid; place-items: center;
            background: var(--panel-2, rgba(255,255,255,.04)); }
.tk.spent .n span { font-size: 10px; letter-spacing: .04em; }
.tk.on { box-shadow: 0 0 0 2px var(--ink); }
.tk img { display: block; width: 100%; aspect-ratio: 4/3; object-fit: cover;
          opacity: .55; transition: opacity var(--t-fast) var(--out); }
.tk:hover img { opacity: .8; }
.tk.on img { opacity: 1; }
.tk .n {
  position: absolute; left: 0; right: 0; bottom: 0; font-size: 9.5px;
  padding: 10px 5px 3px; color: #fff;
  background: linear-gradient(to top, rgba(0, 0, 0, .78), transparent);
  display: flex; justify-content: space-between; gap: 4px;
}
.tk .n span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
              opacity: .8; }

/* --- action panel ------------------------------------------------------- */
.panel {
  background: var(--surface); border-left: 1px solid var(--line);
  display: flex; flex-direction: column; min-height: 0;
}
.panel .body {
  flex: 1; overflow-y: auto; padding: var(--s5) var(--s4);
  display: flex; flex-direction: column; justify-content: flex-end;
  gap: var(--s4); scrollbar-width: thin;
}
.panel .body::-webkit-scrollbar { width: 6px; }
.panel .body::-webkit-scrollbar-thumb { background: var(--line-2); border-radius: 3px; }

/* The panel's subject, so the action and the picture it acts on are in the
   same place. */
.pcard { display: flex; gap: var(--s3); align-items: center; }
.pcard img {
  width: 64px; aspect-ratio: 4/3; object-fit: cover; flex: none;
  border-radius: var(--r-sm); background: var(--raised);
}
.pmeta { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.pmeta b {
  font-size: 13px; font-weight: 600; letter-spacing: -.01em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pmeta span { font-size: 11.5px; color: var(--dim);
              font-variant-numeric: tabular-nums; }

/* One tick per attempt the credit buys. The budget used to appear only in
   the notice that says you have run out of it, which is one attempt too
   late to be worth reading. */
.meter { display: flex; gap: 4px; }
.meter i {
  flex: 1; height: 4px; border-radius: 2px;
  background: rgba(255, 255, 255, .09);
  transition: background var(--t-mid) var(--out);
}
.meter i.on { background: var(--muted); }


.panel .foot { border-top: 1px solid var(--line); padding: var(--s4);
               background: var(--surface); }
.note {
  display: flex; gap: 9px; align-items: flex-start; background: var(--raised);
  border-radius: var(--r); padding: 11px 12px; margin-bottom: var(--s3);
  font-size: 12px; color: var(--muted); line-height: 1.5;
}
.note b { color: var(--free); font-weight: 600; }
.note.costs b { color: var(--signal); }
.note .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--free);
             flex: none; margin-top: 5px; }
.note.costs .dot { background: var(--signal); }
.note.bad .dot { background: var(--err); }
.note.bad b { color: var(--err); }

/* --- sign in ------------------------------------------------------------ */
.gate { min-height: 100vh; display: grid; place-items: center; padding: var(--s5);
        background: radial-gradient(78% 52% at 50% 0%,
                    rgba(255, 255, 255, .045), transparent 70%), var(--void); }
.gate .box { width: 100%; max-width: 340px; }
.gate .brand { font-size: 20px; margin-bottom: var(--s5); }
.gate h1 { font-size: 21px; font-weight: 600; letter-spacing: -.03em;
           margin-bottom: var(--s1); }
.gate p.sub { color: var(--muted); font-size: 13px; margin-bottom: var(--s5); }
.gate label { display: block; font-size: 11.5px; color: var(--muted);
              margin: 0 0 var(--s2); }
.gate label + label { margin-top: var(--s3); }
.gate input {
  width: 100%; font: inherit; font-size: 14px; color: var(--text);
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r); padding: 11px 12px; margin-top: var(--s2);
}
.gate input:focus { outline: none; border-color: var(--line-2); }
.gate .btn { margin-top: var(--s5); padding: 12px; }
.gate .switch { margin-top: var(--s4); font-size: 12.5px; color: var(--dim);
                text-align: center; }
.gate .switch button { font: inherit; font-size: 12.5px; background: none;
                       border: none; color: var(--text); cursor: pointer;
                       text-decoration: underline; text-underline-offset: 3px; }
.gate .msg { min-height: 18px; margin-top: var(--s3); }

/* --- toasts ------------------------------------------------------------- */
.toast-wrap {
  position: fixed; left: 50%; transform: translateX(-50%);
  top: calc(var(--top) + 12px); z-index: 90;
  display: flex; flex-direction: column; gap: 6px; align-items: center;
  pointer-events: none;
}
.toast {
  font-size: 12.5px; padding: 8px 15px; border-radius: 999px;
  background: var(--raised); border: 1px solid var(--line-2);
  box-shadow: var(--shadow); animation: drop var(--t-mid) var(--out);
}
.toast.err { color: var(--err); }
.toast.ok { color: var(--free); }
@keyframes drop { from { opacity: 0; transform: translateY(-8px) scale(.97); } }

/* --- the primary action -------------------------------------------------
 * Generate is the only thing on the page worth lifting off the surface. */
.btn.primary {
  transition: background var(--t-fast) var(--out),
              border-color var(--t-fast) var(--out),
              transform var(--t-fast) var(--out),
              box-shadow var(--t-mid) var(--out), opacity var(--t-fast) var(--out);
}
.btn.primary:hover:not(:disabled) {
  transform: translateY(-1px); box-shadow: 0 8px 22px rgba(0, 0, 0, .5);
}
.btn.primary:active:not(:disabled) { transform: translateY(0); box-shadow: none; }

/* Every tile and control here is reachable by keyboard; only the compare
   slider ever said so. */
button:focus-visible, a:focus-visible, [tabindex]:focus-visible {
  outline: 2px solid var(--ink); outline-offset: 2px;
}

/* --- small screens ------------------------------------------------------ */
@media (max-width: 900px) {
  .app { grid-template-columns: 1fr; grid-template-rows: auto 1fr auto; }
  .top { gap: var(--s3); padding: 0 var(--s3); }
  .rail {
    grid-row: 1; flex-direction: row; border-right: none;
    border-bottom: 1px solid var(--line); padding: var(--s2) var(--s3);
    overflow-x: auto; overflow-y: hidden;
  }
  .rail .drop, .rail .ph { flex: 0 0 74px; }
  .canvas { grid-row: 2; padding: var(--s3); }
  .panel {
    grid-row: 3; border-left: none; border-top: 1px solid var(--line);
    max-height: 46vh;
  }
  /* A grid item defaults to min-width:auto, so a row that refuses to shrink
     widens the column rather than wrapping inside it. The caption now carries
     the whole filename, which is as long as the customer's file names get. */
  .rail, .canvas, .panel { min-width: 0; }
  .cap { flex-wrap: wrap; row-gap: var(--s2); }
}

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* Comparing against the model is a flag, not a mode. */
.btn.flag { border-color: var(--line); }
.btn.flag.on { border-color: var(--ink); color: var(--ink); }
.btn.flag:disabled { opacity: .4; cursor: not-allowed; }

/* One box for the picture, whichever frame is showing it.
 *
 * The two frames derived their size differently: the comparison shrink-wrapped
 * the ORIGINAL, the plain view the TAKE. So switching the flag resized the
 * picture, sometimes by half, and object masks no longer matched what was
 * under them.
 *
 * Both now take the column's full width and the TAKE's own aspect ratio, so
 * they are the same box by construction. Because the box IS the take's shape,
 * cover neither crops nor distorts it, and a mask at inset:0 lines up exactly.
 * max-height keeps a tall picture inside the column; aspect-ratio then narrows
 * the width to match rather than squashing the image. */
.compare.fit, .plain.fit {
  /* The box takes the TAKE's own proportions, so it hugs the picture: no
     letterbox bars, no squashing, and both frames are the same shape because
     both read the same ratio.

     Driven from the height. Width is then height x ratio, and max-width pulls
     both back proportionally on a wide picture, so a tall photo becomes narrow
     and a wide one becomes short — instead of every photo being forced to the
     column's full width and cropped by overflow:hidden, which is what hid the
     top and bottom of a portrait upload. */
  aspect-ratio: var(--ar, 3 / 2);
  height: 74vh;
  /* fit-content, not auto: these frames are block boxes, and a block with
     width:auto fills its parent — which is what kept a portrait picture as
     wide as a landscape one and then made it enormously tall. */
  width: fit-content;
  max-width: 100%;
  margin-inline: auto;
  /* .work is a column flex container, so a child stretches to its full width
     by default — which overrode the computed width and kept a portrait
     picture as wide as a landscape one. */
  align-self: center;
}


/* A photo with no take yet is shown by the plain frame without .fit, so it
   needs the same ceiling or a tall upload runs off the screen — and it has
   no .fit box to sit in the middle of, so it centres itself. */
.plain img { max-height: 74vh; }
.plain:not(.fit) { display: flex; justify-content: center; }


/* The column, not the frame, was the thing that moved.
 *
 * .work had no width of its own, so it shrink-wrapped its widest child: the
 * comparison holds the ORIGINAL (larger) and the plain view the TAKE, so the
 * whole column resized when the flag was switched and the picture jumped with
 * it. Giving the column the canvas width makes both frames the same box. */
.canvas .work { width: 100%; }

/* Comparison off: the picture sizes itself.
 *
 * The container was forcing an aspect ratio while the image inside sized
 * independently, so the two disagreed and the photo came out disproportionate.
 * With comparison off there is only one image and no second pane to line it up
 * with, so the simplest correct thing is to let the image decide and cap its
 * height. contain guarantees it is never stretched, whatever the box does. */
.plain.fit {
  aspect-ratio: auto;
  height: auto;
  width: fit-content;
  max-width: 100%;
}
.plain.fit img {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 74vh;
  object-fit: contain;
}

/* Soft delete on a rail tile: hidden until the tile is hovered or focused,
   so the rail stays a wall of pictures rather than a wall of close boxes. */
.ph .pdel {
  position: absolute; top: 4px; right: 4px;
  width: 20px; height: 20px; line-height: 18px;
  border: 0; border-radius: 50%;
  background: rgba(0, 0, 0, .55); color: #fff;
  font-size: 14px; text-align: center; cursor: pointer;
  opacity: 0; transition: opacity .15s;
}
.ph:hover .pdel, .ph:focus-within .pdel { opacity: 1; }
.ph .pdel:hover { background: rgba(200, 40, 60, .85); }
