/* image_edit_panel.css — the admin product-image editor's styles, extracted
   VERBATIM from product_detail.css (2026-09-02) so /admin/image-queue can host
   the same panel without loading product_detail.css (whose body:has(.cardstock)
   paper-desk rule would repaint the whole admin shell).

   Load order on product_detail: AFTER product_detail.css (the cut was chosen so
   no rule moves past a later same-specificity rule — zero cascade movement).

   REQUIRES the .cardstock design tokens (--paper-hi --ink --ink-3 --rule
   --accent --accent-deep --font-stack), declared in product_detail.css's
   .cardstock block (line ~69). A page that does not load product_detail.css
   must mirror them at a scope <body> inherits — the crop modal mounts on
   document.body as "cardstock crop-modal", OUTSIDE any page wrapper (see
   static/css/admin_image_queue.css).

   The (pointer: coarse) block below is the CSS half of initCrop's
   coarse()/hitR() helpers in static/js/image_edit_panel.js — change one and
   change the other. */

/* ===================================================================
   IMAGE-EDIT PANEL (admin) — restyled to cardstock tone
   =================================================================== */
.cardstock .image-edit-panel {
  margin-top: 12px;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  padding: 0;
  background: none;
  border: none;
  font-size: 12px;
}
.cardstock .image-edit-panel form { margin: 0; display: flex; gap: 4px; align-items: center; }
.cardstock .image-edit-panel input[type=url] {
  flex: 1 1 200px;
  min-width: 0;
  padding: 4px 8px;
  font: 400 12px/1.3 var(--font-stack);
  color: var(--ink);
  background: var(--paper-hi);
  border: 1px solid var(--rule);
  border-radius: 3px;
}
.cardstock .image-edit-panel select {
  font: 500 11px/1 var(--font-stack);
  color: var(--ink);
  background: var(--paper-hi);
  border: 1px solid var(--rule);
  border-radius: 3px;
  padding: 4px 6px;
}
.cardstock .image-edit-panel button {
  font: 700 11px/1 var(--font-stack);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--paper-hi);
  background: var(--accent);
  border: 0;
  border-radius: 3px;
  padding: 5px 9px;
  cursor: pointer;
  transition: background 150ms ease;
}
.cardstock .image-edit-panel button:hover { background: var(--accent-deep); }
.cardstock .image-edit-panel button.image-btn-del,
.cardstock .image-edit-panel button.image-btn-restore {
  background: rgba(40, 40, 40, 0.55);
}
.cardstock .image-edit-panel button.image-btn-del:hover,
.cardstock .image-edit-panel button.image-btn-restore:hover {
  background: rgba(40, 40, 40, 0.85);
}
/* BG button — purple to match the dollar-price ink. */
.cardstock .image-edit-panel button.image-btn-bg {
  background: rgba(88, 28, 135, 0.95);
}
.cardstock .image-edit-panel button.image-btn-bg:hover {
  background: rgba(70, 18, 110, 1);
}

/* Row 2 of the panel — transparency (BG) + framing tools on their own line.
   flex-basis 100% forces the line break in the panel's wrap row. */
.cardstock .image-edit-panel .image-edit-line-tools {
  flex: 1 1 100%;
  display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
}
/* All of its children are gated on has-image — hide the row itself too so the
   no-image state doesn't render an empty gap line. */
.cardstock .image-edit-panel:not(.has-image) .image-edit-line-tools { display: none; }

/* Collapsible input widgets — icon toggles (🔗 🛒) expand the field inline;
   ⤴ proxy-clicks the hidden file input (no expansion needed). */
.cardstock .image-edit-panel form.is-collapsed > :not(.ie-toggle) { display: none; }
.cardstock .image-edit-panel button.ie-toggle {
  background: rgba(40, 40, 40, 0.55);          /* quiet, matches del/restore tone */
}
.cardstock .image-edit-panel button.ie-toggle:hover { background: rgba(40, 40, 40, 0.85); }
.cardstock .image-edit-panel button.ie-toggle[aria-expanded="true"] { background: var(--accent); }

/* ─────────────── Manual crop (Box + Polygon) ───────────────
   The two entry buttons live on the tools line; clicking one draws an SVG
   overlay on the hero image. Hidden when there's no image, and on touch
   (desktop-only). The buttonless trim form is a headless carrier for the
   BG→auto-trim chain. */
.cardstock .image-trim-form,
.cardstock .image-crop-rect-form,
.cardstock .image-crop-polygon-form { display: none; }   /* headless action carriers */
.cardstock .image-edit-panel button.image-btn-crop-rect,
.cardstock .image-edit-panel button.image-btn-crop-poly {
  background: rgba(30, 64, 110, 0.95);   /* steel blue — own pair, distinct from BG/trim */
}
.cardstock .image-edit-panel button.image-btn-crop-rect:hover,
.cardstock .image-edit-panel button.image-btn-crop-poly:hover {
  background: rgba(22, 48, 84, 1);
}
.cardstock .image-edit-panel button.image-btn-crop-rect.is-on,
.cardstock .image-edit-panel button.image-btn-crop-poly.is-on {
  background: var(--accent); box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.5);
}
.cardstock .image-edit-panel:not(.has-image) button.image-btn-crop-rect,
.cardstock .image-edit-panel:not(.has-image) button.image-btn-crop-poly { display: none; }

/* ─────────────── Tile crop (browse-grid thumbnail focal point) ───────────────
   Entry button matches the crop pair (same family of framing tools); the
   editor itself is a full-width flex child that wraps below the button row.
   The stage replicates the browse .card-image tile (16:9, same gradient,
   same corner radius) and renders the same contain+scale CSS the grid
   applies, so what the operator frames here is exactly what ships. */
.cardstock .image-edit-panel button.image-btn-tile {
  background: rgba(30, 64, 110, 0.95);
}
.cardstock .image-edit-panel button.image-btn-tile:hover {
  background: rgba(22, 48, 84, 1);
}
.cardstock .image-edit-panel button.image-btn-tile.is-on {
  background: var(--accent); box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.5);
}
.cardstock .image-edit-panel:not(.has-image) button.image-btn-tile,
.cardstock .image-edit-panel:not(.has-image) .thumb-crop-editor { display: none; }

.cardstock .thumb-crop-editor {
  flex: 1 1 100%;
  max-width: 360px;
  margin-top: 2px;
}
.cardstock .thumb-crop-editor[hidden] { display: none; }
.cardstock .thumb-crop-editor .tce-stage {
  /* MUST match .prod-card .card-image in browse.html — saved zooms are
     relative to this box's CONTAIN baseline, so a mismatched aspect ratio
     here would make the preview lie about the grid. */
  aspect-ratio: 5 / 4;
  width: 100%;
  background: linear-gradient(180deg, #f6f8fa 0%, #e1e4e8 100%);
  border: 1px solid var(--rule);
  border-radius: 8px;
  overflow: hidden;
  cursor: grab;
  touch-action: none;   /* the stage owns its gestures — no scroll-fighting */
  position: relative;
}
.cardstock .thumb-crop-editor .tce-stage.dragging { cursor: grabbing; }
.cardstock .thumb-crop-editor .tce-stage img {
  width: 100%; height: 100%;
  object-fit: contain;          /* zoom-1 baseline; JS drives position/scale */
  display: block;
  pointer-events: none;         /* pointer events land on the stage */
  user-select: none;
  -webkit-user-drag: none;
}
.cardstock .thumb-crop-editor .tce-controls {
  display: flex; align-items: center; gap: 8px;
  margin-top: 6px;
}
.cardstock .thumb-crop-editor .tce-zoom-label {
  display: flex; align-items: center; gap: 6px;
  flex: 1 1 auto; min-width: 0;
  font: 700 10px/1 var(--font-stack);
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink);
}
.cardstock .thumb-crop-editor input.tce-zoom {
  flex: 1 1 auto; min-width: 0;
  accent-color: var(--accent);
  cursor: pointer;
  margin: 0;
}
.cardstock .thumb-crop-editor .tce-zoom-val {
  font: 500 11px/1 var(--font-stack);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  min-width: 40px; text-align: right;
}
/* Total-degrees readout needs room for "-180.0°". */
.cardstock .thumb-crop-editor .tce-rot-val { min-width: 48px; }
/* Quarter-turn buttons — glyph-only, same steel blue as the framing pair. */
.cardstock .image-edit-panel .thumb-crop-editor button.tce-rot-ccw,
.cardstock .image-edit-panel .thumb-crop-editor button.tce-rot-cw {
  background: rgba(30, 64, 110, 0.95);
  font-size: 13px;
  padding: 3px 8px;
}
.cardstock .image-edit-panel .thumb-crop-editor button.tce-rot-ccw:hover,
.cardstock .image-edit-panel .thumb-crop-editor button.tce-rot-cw:hover {
  background: rgba(22, 48, 84, 1);
}
/* Actions row: hint flexes left, Reset/Save hang right. */
.cardstock .thumb-crop-editor .tce-actions .tce-hint {
  flex: 1 1 auto; min-width: 0; margin-top: 0;
}
/* Reset reads muted like Restore/Delete; Save tile is commit-green like the
   panel's publish Save (it persists immediately — metadata only). */
.cardstock .image-edit-panel .thumb-crop-editor button.tce-reset {
  background: rgba(40, 40, 40, 0.55);
}
.cardstock .image-edit-panel .thumb-crop-editor button.tce-reset:hover {
  background: rgba(40, 40, 40, 0.85);
}
.cardstock .image-edit-panel .thumb-crop-editor button.tce-save {
  background: rgba(21, 115, 71, 0.95);
}
.cardstock .image-edit-panel .thumb-crop-editor button.tce-save:hover {
  background: rgba(16, 92, 57, 1);
}
.cardstock .thumb-crop-editor .tce-hint {
  margin-top: 5px;
  font: 500 10.5px/1.45 var(--font-stack);
  color: #6a737d;
}

/* The overlay fills the shrink-wrapped .crop-modal-imgwrap (== the image box
   inside the crop modal), so inset:0 aligns it pixel-exact with the artwork.
   Hidden until activated. */
.cardstock .crop-overlay {
  position: absolute; inset: 0; z-index: 4;
  display: none; touch-action: none; cursor: crosshair;
}
.cardstock .crop-overlay.is-active { display: block; }
.cardstock .crop-overlay .crop-dim { pointer-events: none; }
.cardstock .crop-overlay .crop-sel,
.cardstock .crop-overlay .crop-line {
  fill: none; stroke: #fff; stroke-width: 1.5; stroke-dasharray: 5 4;
  pointer-events: none;
  filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.85));
}
.cardstock .crop-overlay .crop-h,
.cardstock .crop-overlay .crop-v {
  fill: #fff; stroke: var(--accent); stroke-width: 2;
}
.cardstock .crop-overlay .crop-v-first { fill: var(--accent); }
/* The transparent grab target sits on top of each dot; it carries the cursor. */
.cardstock .crop-overlay .crop-pt-hit { cursor: grab; }
.cardstock .crop-overlay .crop-pt-hit:active { cursor: grabbing; }
.cardstock .crop-toolbar {
  /* Docked in the modal's bottom bar — never covers the drawing surface, so
     the operator can place points right to the image's bottom edge. */
  display: flex; align-items: center; gap: 10px;
  width: min(860px, 100%);
  padding: 7px 10px; border-radius: 5px;
  background: rgba(11, 37, 69, 0.88); color: #fff;
  font: 600 11px/1.3 var(--font-stack);
  box-shadow: 0 4px 10px -3px rgba(0, 0, 0, 0.5);
}
.cardstock .crop-hint { flex: 1 1 auto; letter-spacing: 0.01em; }
.cardstock .crop-tb-btns { display: flex; gap: 6px; flex: 0 0 auto; }
.cardstock .crop-tb-btn {
  font: 700 11px/1 var(--font-stack); text-transform: uppercase; letter-spacing: 0.06em;
  padding: 5px 9px; border: 1px solid rgba(255, 255, 255, 0.4); border-radius: 4px;
  background: transparent; color: #fff; cursor: pointer;
}
.cardstock .crop-tb-btn:hover { background: rgba(255, 255, 255, 0.12); }
.cardstock .crop-tb-btn.crop-apply { background: var(--accent); border-color: var(--accent); }
.cardstock .crop-tb-btn.crop-apply:hover { background: var(--accent-deep); }
.cardstock .crop-tb-btn:disabled { opacity: 0.5; cursor: default; }

/* ─────────────── Crop modal (full-viewport workspace) ───────────────
   Entering crop mode opens a body-level modal so the operator frames the crop
   against a near-fullscreen render of the master instead of the small rail
   image. The root carries .cardstock (it mounts on <body>, outside the page
   wrapper) so the overlay/toolbar selectors above and the CSS vars apply.
   z-index matches the edit-match modal — above the navbar dropdowns and the
   prod-admin banner. */
.cardstock.crop-modal {
  position: fixed; inset: 0; z-index: 10000;
  display: flex; flex-direction: column; align-items: center;
  padding: 20px 24px 14px;
}
.cardstock.crop-modal[hidden] { display: none; }
.crop-modal .crop-modal-backdrop {
  /* Near-opaque: at 0.9 the header/banner's white text still ghosted through
     legibly — a focused work surface wants the page gone, not dimmed. */
  position: absolute; inset: 0;
  background: rgba(7, 22, 41, 0.97);
}
.crop-modal .crop-modal-stage {
  position: relative; z-index: 1;
  flex: 1 1 auto; min-height: 0; width: 100%;
  display: flex; align-items: center; justify-content: center;
  /* Empty-stage clicks fall through to the backdrop (= cancel). */
  pointer-events: none;
}
.crop-modal .crop-modal-imgwrap {
  /* Shrink-wraps the artwork (mirrors .image-frame-inner) so the overlay's
     inset:0 stays pixel-exact with the image box. Entry animates OPACITY
     ONLY — a scale() here once skewed the overlay's viewBox: with a cached
     master the overlay mounts synchronously, mid-scale, and locks in the
     shrunken measurement (ancestor transforms don't fire ResizeObserver),
     leaving every marker ~1.5% off the pointer. */
  position: relative; display: inline-flex;
  pointer-events: auto;
  background: #f6f8fa;             /* transparent PNGs read against paper */
  box-shadow: 0 18px 60px rgba(0, 0, 0, 0.5);
  opacity: 0;
  transition: opacity 0.14s ease;
}
.crop-modal.is-visible .crop-modal-imgwrap { opacity: 1; }
.crop-modal img.crop-modal-img {
  /* Sized by sizeModalImg() in initCrop — JS fit-scales the master into the
     stage, UP as well as down (max-* can't upscale, and object-fit would
     break the overlay's border-box == painted-box contract). */
  display: block;
  width: auto; height: auto;
}
.crop-modal .crop-modal-bar {
  position: relative; z-index: 1;
  flex: 0 0 auto; width: 100%;
  display: flex; justify-content: center;
  margin-top: 10px;
  pointer-events: none;            /* bar gutters fall through to backdrop */
}
.crop-modal .crop-modal-bar .crop-toolbar { pointer-events: auto; }
.crop-modal.is-loading .crop-modal-stage::after {
  content: "Loading full-resolution image…";
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  color: rgba(255, 255, 255, 0.75);
  font: 600 12px/1 var(--font-stack);
  letter-spacing: 0.04em; text-transform: uppercase;
}
.crop-modal.is-busy,
.crop-modal.is-busy .crop-overlay { cursor: progress; }
.crop-modal .crop-modal-close {
  position: absolute; top: 14px; right: 18px; z-index: 2;
  width: 34px; height: 34px; border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.35);
  background: rgba(255, 255, 255, 0.1); color: #fff;
  font-size: 22px; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  transition: background 0.12s;
}
.crop-modal .crop-modal-close:hover { background: rgba(255, 255, 255, 0.22); }
@media (prefers-reduced-motion: reduce) {
  .crop-modal .crop-modal-imgwrap { transition: none; }
}

/* Touch (coarse pointer). Both crop tools used to be HIDDEN here — "precise
   framing on a phone-width image isn't worth the UX cost" — but that reasoning
   predates the full-viewport modal: the crop surface is no longer the rail-sized
   hero, it's a near-fullscreen render that sizeModalImg() upscales the master
   into. What genuinely differs on touch is ergonomics, not viability:
   a fingertip can't hit a 26px handle and OCCLUDES the point it is placing, so
   initCrop grows the SVG hit circles behind the same matchMedia('(pointer:
   coarse)') query — keep the two in step, they are one decision in two
   languages. Here: the toolbar wraps its hint onto its own line instead of
   squeezing the Apply/Cancel buttons toward zero width, and the buttons take
   full-size tap targets. */
@media (pointer: coarse) {
  .cardstock.crop-modal { padding: 12px 10px 10px; }
  .cardstock .crop-toolbar { flex-wrap: wrap; gap: 8px; padding: 8px 10px; }
  .cardstock .crop-hint { flex: 1 1 100%; font-size: 11.5px; }
  .cardstock .crop-tb-btns { flex: 1 1 auto; justify-content: flex-end; gap: 8px; }
  .cardstock .crop-tb-btn { padding: 10px 14px; font-size: 12px; }
  .crop-modal .crop-modal-close { top: 8px; right: 10px; width: 40px; height: 40px; }
}
/* Upload (file → master) — the native file input is [hidden]; the ⤴ icon
   toggle proxy-clicks it and the change handler auto-submits. */
.cardstock .image-edit-panel:not(.has-image) .image-bg-form,
.cardstock .image-edit-panel:not(.has-image) .image-restore-form,
.cardstock .image-edit-panel:not(.has-image) .image-save-form,
.cardstock .image-edit-panel:not(.has-image) .image-delete-form { display: none; }
.cardstock .image-edit-panel:not(.has-backup) .image-restore-form { display: none; }
.cardstock .image-edit-panel.busy { opacity: 0.65; pointer-events: none; }
.cardstock .image-edit-panel button[disabled] { cursor: wait; opacity: 0.7; }
/* Save (publish) — green commit button. Edits stage to disk; Save is the one
   click that publishes to GCS + rebuilds the WebP ladder. Idle-disabled (no
   unsaved edits) reads as muted, not "busy"; armed (.has-unsaved) it pops. */
.cardstock .image-edit-panel button.image-btn-publish {
  background: rgba(21, 115, 71, 0.95);
}
.cardstock .image-edit-panel button.image-btn-publish:hover { background: rgba(16, 92, 57, 1); }
.cardstock .image-edit-panel button.image-btn-publish:disabled {
  cursor: default;
  opacity: 0.45;
  background: rgba(40, 40, 40, 0.45);
}
.cardstock .image-edit-panel.has-unsaved button.image-btn-publish {
  opacity: 1;
  box-shadow: 0 0 0 2px rgba(21, 115, 71, 0.35);
  animation: cs-save-pulse 1.6s ease-in-out infinite;
}
@keyframes cs-save-pulse {
  0%, 100% { box-shadow: 0 0 0 2px rgba(21, 115, 71, 0.25); }
  50%      { box-shadow: 0 0 0 3px rgba(21, 115, 71, 0.55); }
}
.cardstock .image-edit-panel .image-spinner {
  display: inline-block;
  width: 10px; height: 10px;
  border: 2px solid rgba(255,255,255,0.4);
  border-top-color: rgba(255,255,255,0.95);
  border-radius: 50%;
  animation: cs-spin 700ms linear infinite;
}
@keyframes cs-spin { to { transform: rotate(360deg); } }

/* Source-lookup links row (admin), beneath image buttons */
.cardstock .source-links {
  margin-top: 8px;
  display: flex; flex-wrap: wrap; gap: 6px;
}
.cardstock .source-links a {
  font-size: 9px; font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--ink-3);
  text-decoration: none;
  padding: 3px 8px;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  transition: color 150ms ease, border-color 150ms ease;
}
.cardstock .source-links a:hover {
  color: var(--accent-deep);
  border-color: var(--accent-deep);
}

/* Toast (used by image-edit panel AJAX). Position is fixed at the
   viewport bottom so it floats above whatever section the operator
   is currently in. */
#image-action-toast {
  position: fixed; left: 50%; bottom: 28px;
  transform: translate(-50%, 16px);
  background: #1a1a1a;
  color: #f5f5f3;
  padding: 9px 16px;
  border-radius: 4px;
  font: 600 12px/1.3 "Roboto", sans-serif;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  box-shadow: 0 8px 20px rgba(0,0,0,0.45);
  opacity: 0;
  transition: opacity 180ms ease, transform 180ms ease;
  pointer-events: none;
  z-index: 9000;
}
#image-action-toast.show { opacity: 1; transform: translate(-50%, 0); }
#image-action-toast.error { background: #7a2f17; }
