/* ─────────────────────────────────────────────────────────────────────────
   ShapeClip web app — mobile-first responsive styles.

   Build philosophy:
     - Default styles target a phone (320–640px).
     - @media (min-width: 768px) layers in tablet+desktop affordances.
     - Touch targets are 44×44 px minimum (Apple HIG floor).
     - Color palette mirrors the Chrome extension and shapeclip.com landing.
   ───────────────────────────────────────────────────────────────────────── */

:root {
  --bg:           #0d1117;
  --panel:        #151b23;
  --panel-2:      #1d2631;
  --text:         #eef4f8;
  --muted:        #9aa8b5;
  --line:         #2b3642;
  --accent:       #0ea5a4;
  --accent-hover: #14b8a6;
  --accent-glow:  rgba(14, 165, 164, 0.18);
  --warn:         #d7ad6f;
  --radius:       12px;
  --tap:          44px;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Inter, sans-serif;
  font-size: 16px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

body {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

/* ── Header ──────────────────────────────────────────────────────────── */

.app-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 18px;
  border-bottom: 1px solid var(--line);
  background: var(--panel);
}

.brand {
  display: flex;
  align-items: center;
  gap: 10px;
}

.logo {
  width: 30px; height: 30px;
  background: var(--accent);
  border-radius: 7px;
  display: flex; align-items: center; justify-content: center;
  font-size: 14px; color: #fff;
  flex-shrink: 0;
}

.brand-name { font-size: 17px; font-weight: 700; letter-spacing: -0.2px; }
.brand-name b { color: var(--accent); }

.back-link {
  font-size: 13px;
  color: var(--muted);
  text-decoration: none;
}

.back-link:hover { color: var(--text); }

/* ── Main ────────────────────────────────────────────────────────────── */

.app-main {
  flex: 1;
  padding: 18px;
}

.step { animation: fadeIn 0.2s ease; }

@keyframes fadeIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

/* ── Upload step ─────────────────────────────────────────────────────── */

.upload-card {
  max-width: 480px;
  margin: 8vh auto 24px;
  text-align: center;
  padding: 28px 22px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.upload-card h1 {
  font-size: 26px;
  line-height: 1.18;
  margin: 0 0 10px;
  letter-spacing: -0.5px;
}

.upload-card .lead {
  color: var(--muted);
  font-size: 15px;
  margin: 0 0 22px;
}

.upload-btn {
  display: inline-block;
  background: var(--accent);
  color: #001416;
  font-size: 17px;
  font-weight: 700;
  padding: 14px 26px;
  border-radius: 10px;
  cursor: pointer;
  min-height: var(--tap);
  user-select: none;
  transition: background 0.15s ease, transform 0.05s ease;
}

.upload-btn:hover  { background: var(--accent-hover); }
.upload-btn:active { transform: translateY(1px); }
.upload-btn span   { pointer-events: none; }

/* iOS-safe upload pattern: the file input is an invisible overlay on top
   of the visible button. User taps appear to hit the button visually but
   actually land on the <input>, which avoids the iOS WebKit quirk where
   change events fired through .click() forwarding sometimes never fire.
   !important is needed because iOS Safari/Chrome can apply UA styles to
   `<input type="file">` that override regular CSS specificity. */
.upload-btn-wrap {
  position: relative !important;
  display: inline-block !important;
  vertical-align: middle;
}
.upload-btn-wrap input[type="file"] {
  position: absolute !important;
  top: 0 !important;
  left: 0 !important;
  right: 0 !important;
  bottom: 0 !important;
  width: 100% !important;
  height: 100% !important;
  opacity: 0 !important;
  cursor: pointer !important;
  font-size: 0 !important;
  margin: 0 !important;
  padding: 0 !important;
  border: 0 !important;
  z-index: 2;
}
.upload-btn-wrap input[type="file"]::-webkit-file-upload-button {
  visibility: hidden;
  display: none;
}

.muted { color: var(--muted); font-size: 13px; margin: 14px 0 0; }

/* Visible upload-status: replaces alert() because iOS Safari often
   silently drops alerts that fire inside a file-input change handler.
   Empty by default; populated from app.js when a file is picked. */
.upload-status {
  margin: 12px 0 0;
  padding: 0;
  font-size: 13px;
  line-height: 1.4;
  color: var(--muted);
  min-height: 18px;
}
.upload-status:empty { display: none; }
.upload-status.error { color: #f87171; }

/* ── Edit step ───────────────────────────────────────────────────────── */

/* Sticky-top: canvas + tab bar stay visible while the controls beneath
   them scroll. On mobile this is the difference between "I have to keep
   scrolling up to see the preview" and "I see my changes live as I move
   sliders below." Negative margin pulls it flush with the page edges so
   the sticky background doesn't leave a side gap. */
.sticky-top {
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--bg);
  margin: 0 -18px 16px;
  padding: 0 18px 8px;
}

.canvas-wrap {
  background: repeating-conic-gradient(#1d2631 0 25%, #151b23 0 50%) 50% / 16px 16px;
  border-radius: var(--radius);
  padding: 10px;
  margin: 0 auto 8px;
  max-width: 100%;
  display: flex; justify-content: center; align-items: center;
  overflow: hidden;
}

#canvas {
  width: 100%;
  height: auto;
  /* Canvas drawing surface stays 600×600 (set on the element itself) for
     export quality. CSS clamps the *displayed* size — ~20% smaller than
     the previous 70vh so font picker results are visible on phone. */
  max-width: 480px;
  max-height: 52vh;
  background: transparent;
  /* touch-action:none is REQUIRED for pinch/pan/rotate gestures to work.
     Without this, mobile browsers steal one or two-finger touches for
     page scroll/zoom and our pointer events never fire. */
  touch-action: none;
  cursor: grab;
  user-select: none;
  -webkit-user-select: none;
}
#canvas:active { cursor: grabbing; }

.controls {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}

/* Top tab bar — each tab is its OWN distinct pill button so inactive tabs
   are clearly clickable, not "just text." Outer container has no background;
   each .tab-btn carries its own background + border + shadow. */
.tab-bar {
  display: flex;
  gap: 8px;
  background: transparent;
  border: none;
  border-radius: 0;
  overflow: visible;
  padding: 0;
}

.tab-btn {
  flex: 1;
  background: var(--panel-2);
  border: 1.5px solid #0a7574;
  color: var(--text);
  font: inherit;
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.3px;
  padding: 6px 10px;
  min-height: 36px;
  cursor: pointer;
  border-radius: 999px;
  box-shadow:
    0 2px 4px rgba(0,0,0,0.25),
    inset 0 1px 0 rgba(255,255,255,0.05);
  transition: color 0.12s ease, background 0.12s ease, border-color 0.12s ease, box-shadow 0.12s ease, transform 0.05s ease;
}

.tab-btn:hover {
  color: var(--text);
  background: var(--line);
  border-color: rgba(255,255,255,0.18);
  box-shadow:
    0 3px 8px rgba(0,0,0,0.35),
    inset 0 1px 0 rgba(255,255,255,0.10);
}
.tab-btn:active { transform: translateY(1px); }
.tab-btn.active {
  color: #001416;
  background: var(--accent);
  border-color: var(--accent);
  box-shadow:
    0 4px 12px rgba(14,165,164,0.50),
    inset 0 1px 0 rgba(255,255,255,0.30);
}

.tab-panel { display: none; padding: 16px; }
.tab-panel.active { display: block; }

.field {
  display: block;
  margin-bottom: 14px;
  font-size: 13px;
  color: var(--muted);
}

.field span {
  display: flex;
  justify-content: space-between;
  margin-bottom: 8px;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  font-size: 17px;
  font-weight: 700;
  color: var(--text);
}

.field input[type="text"],
.field input[type="range"] {
  width: 100%;
  background: var(--bg);
  color: var(--text);
  border: 1.5px solid #0a7574;
  border-radius: 8px;
  padding: 12px;
  font: inherit;
  min-height: var(--tap);
}

/* Word input renders uppercase visually because the canvas always
   uppercases internally — without this, users type lowercase and are
   surprised when their export comes out all caps. JS also forces
   .value to uppercase as a belt-and-suspenders safety net. */
#word-input {
  text-transform: uppercase;
  font-size: 18px;
  font-weight: 700;
  letter-spacing: 1px;
}

.field input[type="range"] {
  padding: 0;
  -webkit-appearance: none; appearance: none;
  height: var(--tap);
  background: transparent;
}

.field input[type="range"]::-webkit-slider-runnable-track {
  height: 4px; background: var(--line); border-radius: 999px;
}

.field input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 22px; height: 22px;
  background: var(--accent);
  border: none; border-radius: 50%;
  margin-top: -9px;
}

.field input[type="range"]::-moz-range-track {
  height: 4px; background: var(--line); border-radius: 999px;
}

.field input[type="range"]::-moz-range-thumb {
  width: 22px; height: 22px;
  background: var(--accent);
  border: none; border-radius: 50%;
}

/* ── Shape grid ──────────────────────────────────────────────────────── */

.shape-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
}

.shape-tile {
  aspect-ratio: 1;
  background: var(--bg);
  border: 1.5px solid #0a7574;
  border-radius: 12px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  cursor: pointer;
  color: var(--text);
  text-align: center;
  padding: 6px 4px;
  transition: border-color 0.12s ease, background 0.12s ease, transform 0.05s ease, box-shadow 0.12s ease;
  min-height: var(--tap);
  /* Grid items default to min-width:auto which prevents them shrinking
     below their content size — labels like "ROUNDED SQUARE" then push
     the row wider than the parent and the rightmost tiles get clipped
     off the controls column on desktop. min-width:0 lets the cells
     actually shrink to their fr unit. */
  min-width: 0;
  overflow: hidden;
}
/* Inline SVG inside the tile icon (Car, Tree, etc.) — sized in em so
   the icon scales together with whatever font-size the tile is using
   on the current breakpoint. fill: currentColor keeps the silhouette
   the same color as the label text (white on dark by default). */
.shape-tile-icon .shape-svg {
  width: 1em;
  height: 1em;
  fill: currentColor;
  display: block;
}
.shape-tile-icon {
  font-size: 36px;
  line-height: 1;
  color: var(--text);
  display: block;
}
.shape-tile-label {
  font-size: 10px;
  line-height: 1.2;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--text);
  display: block;
}

.shape-tile {
  box-shadow:
    0 2px 4px rgba(0,0,0,0.20),
    inset 0 1px 0 rgba(255,255,255,0.04);
}
.shape-tile:hover {
  border-color: var(--accent);
  transform: translateY(-1px);
  box-shadow:
    0 4px 8px rgba(0,0,0,0.30),
    inset 0 1px 0 rgba(255,255,255,0.08);
}
.shape-tile:active { transform: translateY(0); }
.shape-tile.active {
  border-color: var(--accent);
  background: var(--accent-glow);
  color: var(--text);
  box-shadow:
    0 0 0 2px var(--accent),
    0 4px 12px rgba(14,165,164,0.35),
    inset 0 1px 0 rgba(255,255,255,0.10);
}

/* ── Action bar ──────────────────────────────────────────────────────── */

.action-bar {
  display: flex;
  gap: 8px;
  padding: 12px 16px 16px;
  border-top: 1px solid var(--line);
}

.btn {
  flex: 1;
  background: var(--panel-2);
  color: var(--text);
  border: 1.5px solid #0a7574;
  border-radius: 999px;
  padding: 13px 18px;
  font: inherit;
  font-weight: 700;
  cursor: pointer;
  min-height: var(--tap);
  box-shadow:
    0 2px 4px rgba(0,0,0,0.25),
    inset 0 1px 0 rgba(255,255,255,0.06);
  transition: background 0.12s ease, transform 0.05s ease, box-shadow 0.12s ease;
}

.btn:hover  {
  background: var(--line);
  box-shadow:
    0 3px 8px rgba(0,0,0,0.35),
    inset 0 1px 0 rgba(255,255,255,0.10);
}
.btn:active {
  transform: translateY(1px);
  box-shadow:
    0 1px 2px rgba(0,0,0,0.3),
    inset 0 1px 2px rgba(0,0,0,0.2);
}

.btn.primary {
  background: var(--accent);
  color: #001416;
  border-color: var(--accent);
  box-shadow:
    0 4px 12px rgba(14,165,164,0.45),
    inset 0 1px 0 rgba(255,255,255,0.25);
  border-color: var(--accent);
}

.btn.primary:hover {
  background: var(--accent-hover);
  box-shadow:
    0 6px 16px rgba(14,165,164,0.55),
    inset 0 1px 0 rgba(255,255,255,0.3);
}
.btn.primary:active {
  box-shadow:
    0 2px 6px rgba(14,165,164,0.4),
    inset 0 1px 2px rgba(0,0,0,0.15);
}

/* ── Footer ──────────────────────────────────────────────────────────── */

.app-footer {
  text-align: center;
  padding: 18px;
  color: var(--muted);
  font-size: 13px;
  border-top: 1px solid var(--line);
}

.app-footer a { color: var(--accent); text-decoration: none; }
.app-footer a:hover { color: var(--accent-hover); }
.app-footer p { margin: 0; }
.app-footer-links {
  margin-top: 6px !important;
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  font-size: 13px;
}
.app-footer-links span { color: var(--muted); }

/* ── Hint text + small buttons ───────────────────────────────────────── */

.hint {
  margin: 0 0 14px;
  padding: 14px 16px;
  background: var(--panel-2);
  border: 1.5px solid #0a7574;
  border-radius: 12px;
  color: var(--text);
  font-size: 16px;
  line-height: 1.5;
}
.hint b { color: var(--text); }

.btn.small {
  flex: none;
  width: 100%;
  font-size: 13px;
  padding: 10px;
  font-weight: 500;
  margin-top: 4px;
}

/* ── Toggle row (label + checkbox) ───────────────────────────────────── */

.toggle-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 14px;
  text-transform: none;
  letter-spacing: 0;
  color: var(--text);
  margin-bottom: 10px;
  background: var(--bg);
  border: 1.5px solid #0a7574;
  border-radius: 999px;
  padding: 10px 16px;
}

.toggle-row span {
  display: inline-block;
  margin: 0;
  text-transform: none;
  letter-spacing: 0;
  font-size: 14px;
  color: var(--text);
}

/* Toggle-row checkbox styled as an iOS-style switch — much more obviously
   "tap to toggle" than a tiny checkbox on phones. The slider knob slides
   right when checked and the track fills with the accent color, so the
   ON/OFF state reads at a glance. The ::after element renders OFF/ON text
   inside the track for extra clarity. */
.toggle-row input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  position: relative;
  width: 64px;
  height: 32px;
  background: var(--panel-2);
  border: 1.5px solid #0a7574;
  border-radius: 999px;
  cursor: pointer;
  flex-shrink: 0;
  margin: 0;
  padding: 0;
  transition: background 0.2s ease, border-color 0.2s ease;
}
.toggle-row input[type="checkbox"]::before {
  /* The sliding knob. Centered vertically, parked at the left when off. */
  content: '';
  position: absolute;
  top: 50%;
  left: 3px;
  width: 24px;
  height: 24px;
  background: #fff;
  border-radius: 50%;
  transform: translateY(-50%);
  box-shadow: 0 1px 3px rgba(0,0,0,0.4);
  transition: left 0.18s ease;
}
.toggle-row input[type="checkbox"]::after {
  /* OFF / ON text label inside the track, opposite the knob so it's
     always readable. Switches text + side when the box is checked. */
  content: 'OFF';
  position: absolute;
  top: 50%;
  right: 8px;
  transform: translateY(-50%);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.6px;
  color: var(--muted);
}
.toggle-row input[type="checkbox"]:checked {
  background: var(--accent);
  border-color: var(--accent);
}
.toggle-row input[type="checkbox"]:checked::before {
  left: 35px;
}
.toggle-row input[type="checkbox"]:checked::after {
  content: 'ON';
  right: auto;
  left: 9px;
  color: #001416;
}

/* ── Divider between style sections ──────────────────────────────────── */

.divider {
  border: none;
  border-top: 1px solid var(--line);
  margin: 16px 0;
}

/* ── Color picker + Select styling ───────────────────────────────────── */

.field input[type="color"] {
  width: 100%;
  height: var(--tap);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 4px;
  cursor: pointer;
}

.field select {
  width: 100%;
  background: var(--bg);
  color: var(--text);
  border: 1.5px solid #0a7574;
  border-radius: 8px;
  padding: 12px 14px;
  font: inherit;
  font-size: 17px;
  font-weight: 400;
  min-height: var(--tap);
  cursor: pointer;
}

/* ── Segmented toggle (Shape vs Word inside Mask tab) ────────────────── */

/* ── Segmented toggle (Shape vs Word inside Mask tab) ────────────────── */

/* Same pill-button treatment as the top tabs — each option is its OWN
   distinct pill so it reads as clickable, not just text in a container. */
.seg-toggle {
  display: grid;
  grid-template-columns: 1fr 1fr;
  background: transparent;
  border: none;
  border-radius: 0;
  padding: 0;
  gap: 8px;
  margin-bottom: 14px;
}
.seg-btn {
  height: 48px;
  border: 1.5px solid #0a7574;
  background: var(--panel-2);
  color: var(--text);
  font-weight: 700;
  font-size: 17px;
  letter-spacing: 0.3px;
  border-radius: 999px;
  cursor: pointer;
  box-shadow:
    0 2px 4px rgba(0,0,0,0.25),
    inset 0 1px 0 rgba(255,255,255,0.05);
  transition: color 0.12s ease, background 0.12s ease, border-color 0.12s ease, box-shadow 0.12s ease, transform 0.05s ease;
}
.seg-btn:hover {
  color: var(--text);
  background: var(--line);
  border-color: rgba(255,255,255,0.18);
}
.seg-btn:active { transform: translateY(1px); }
.seg-btn.active {
  background: var(--accent);
  color: #001416;
  border-color: var(--accent);
  box-shadow:
    0 4px 12px rgba(14,165,164,0.50),
    inset 0 1px 0 rgba(255,255,255,0.30);
}
.mode-panel { display: none; }
.mode-panel.active { display: block; }

/* ── Adjust panel: touch help vs sliders ──────────────────────────────────
   On touch-primary devices (phones, tablets), gestures replace sliders.
   We hide sliders entirely and show a friendly instruction block so the
   user knows fingers ARE the control, not a missing UI. On pointer-fine
   devices (desktop with mouse), gestures don't include pinch/rotate, so
   we keep sliders and hide the touch help.
   `pointer: coarse` is the standard test for touch-primary input. */

.adjust-touch-help { display: none; }
@media (pointer: coarse) {
  .adjust-touch-help { display: block; }
  .adjust-sliders    { display: none; }
}
.adjust-touch-help {
  background: #131a23;
  border: 1px solid #1f2a36;
  border-radius: var(--radius);
  padding: 14px 16px;
  margin: 4px 0 10px;
}
.adjust-touch-help .touch-help-title {
  margin: 0 0 8px;
  font-size: 14px;
  color: var(--text);
  font-weight: 600;
}
.adjust-touch-help .touch-help-list {
  margin: 0;
  padding-left: 18px;
  color: var(--muted);
  font-size: 14px;
  line-height: 1.55;
}
.adjust-touch-help .touch-help-list b { color: var(--text); }

/* ── Color swatch button ──────────────────────────────────────────────────
   Replaces the native color input trigger. The swatch shows the current
   color; tapping it opens our custom bottom-sheet picker. */

.color-pair { display: inline-flex; align-items: center; }
.color-swatch {
  width: 44px;
  height: 32px;
  border-radius: 8px;
  border: 1.5px solid #0a7574;
  cursor: pointer;
  background: #000;
  box-shadow: inset 0 0 0 2px rgba(255,255,255,0.12);
  padding: 0;
}
.color-swatch:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ── Bottom-sheet color picker ──
   Sized so the canvas above remains visible — drag on the SV square or
   hue strip and watch the masked photo update live. */

.color-picker {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  pointer-events: auto;
}
.color-picker[hidden] { display: none; }
.color-picker-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0,0,0,0.35);
  opacity: 0;
  transition: opacity 0.2s ease;
}
.color-picker.open .color-picker-backdrop { opacity: 1; }
.color-picker-sheet {
  position: relative;
  width: 100%;
  max-width: 520px;
  background: var(--panel);
  border-top-left-radius: 18px;
  border-top-right-radius: 18px;
  padding: 8px 16px 18px;
  box-shadow: 0 -8px 30px rgba(0,0,0,0.45);
  transform: translateY(100%);
  transition: transform 0.25s ease;
}
.color-picker.open .color-picker-sheet { transform: translateY(0); }
.color-picker-grip {
  width: 44px; height: 4px;
  background: #2b3642;
  border-radius: 2px;
  margin: 4px auto 10px;
}
.color-picker-header {
  display: flex; align-items: center; gap: 10px; margin-bottom: 12px;
}
.color-preview {
  width: 32px; height: 32px;
  border-radius: 8px;
  border: 1px solid var(--line);
  flex-shrink: 0;
  box-shadow: inset 0 0 0 2px rgba(255,255,255,0.08);
}
.hex-input {
  flex: 1; height: 36px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 8px;
  color: var(--text);
  font: 600 14px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  padding: 0 10px;
  text-transform: uppercase;
}
.hex-input:focus { outline: none; border-color: var(--accent); }
.color-picker-done {
  height: 36px; padding: 0 14px;
  border-radius: 8px;
  border: none;
  background: var(--accent);
  color: #fff;
  font-weight: 700;
  cursor: pointer;
}
.color-picker-done:hover { background: var(--accent-hover); }
.sv-box {
  position: relative;
  width: 100%;
  height: 200px;
  border-radius: 10px;
  overflow: hidden;
  background: var(--sv-bg, #ff0000);
  cursor: crosshair;
  touch-action: none;
}
.sv-overlay-x {
  position: absolute; inset: 0;
  background: linear-gradient(to right, #ffffff, rgba(255,255,255,0));
  pointer-events: none;
}
.sv-overlay-y {
  position: absolute; inset: 0;
  background: linear-gradient(to bottom, rgba(0,0,0,0), #000000);
  pointer-events: none;
}
.sv-handle, .hue-handle {
  position: absolute;
  width: 18px; height: 18px;
  border: 2px solid #fff;
  border-radius: 50%;
  box-shadow: 0 0 0 1px rgba(0,0,0,0.5), 0 1px 4px rgba(0,0,0,0.5);
  transform: translate(-50%, -50%);
  pointer-events: none;
}
.hue-strip {
  position: relative;
  width: 100%;
  height: 28px;
  border-radius: 8px;
  margin-top: 14px;
  background: linear-gradient(to right,
    #ff0000 0%, #ffff00 16.66%, #00ff00 33.33%,
    #00ffff 50%, #0000ff 66.66%, #ff00ff 83.33%, #ff0000 100%);
  cursor: pointer;
  touch-action: none;
}
.hue-handle { top: 50%; }
.color-presets {
  display: grid;
  grid-template-columns: repeat(10, 1fr);
  gap: 6px;
  margin-top: 14px;
}
.preset {
  width: 100%; aspect-ratio: 1 / 1;
  border-radius: 6px;
  border: 1px solid var(--line);
  cursor: pointer;
  padding: 0;
  box-shadow: inset 0 0 0 2px rgba(255,255,255,0.06);
}
.preset:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ── Iconify search block (Shape mode) ────────────────────────────────────
   Lives below the built-in shape grid. The search hits the Iconify CDN at
   https://api.iconify.design — same as the Chrome extension. Results are
   shown as preview tiles; tapping a tile sets the active shape to that
   icon. Free-tier behavior: no save/persist (those land in Pro). */

.icon-search-block {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}
.icon-search-row {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: 8px;
  margin-bottom: 8px;
}
.icon-search-row input[type="text"] {
  height: var(--tap);
  background: var(--bg);
  color: var(--text);
  border: 1.5px solid #0a7574;
  border-radius: 8px;
  padding: 0 12px;
  font: inherit;
}
.icon-search-row input[type="text"]:focus {
  outline: none;
  border-color: var(--accent);
}
.icon-search-row .btn.small {
  width: auto;
  margin-top: 0;
  padding: 0 14px;
  height: var(--tap);
}
.btn.ghost {
  background: transparent;
  border: 1px solid var(--line);
  color: var(--muted);
}
.btn.ghost:hover { color: var(--text); border-color: var(--accent); }
.icon-status {
  font-size: 13px;
  color: var(--text);
  min-height: 18px;
  margin: 6px 0;
}
.icon-status.error { color: #f87171; }
.icon-results {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(60px, 1fr));
  gap: 6px;
  max-height: 360px;
  overflow-y: auto;
}
.icon-tile {
  position: relative;
  aspect-ratio: 1 / 1;
  background: var(--panel-2);
  border: 1.5px solid #0a7574;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  padding: 8px;
  transition: border-color 0.15s, transform 0.1s;
}
.icon-tile:hover { border-color: var(--accent); }
.icon-tile.active {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-glow);
}
.icon-tile.bad {
  opacity: 0.35;
  cursor: not-allowed;
}
.icon-tile img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  filter: invert(0.95);
  pointer-events: none;
}

/* ── Tablet+ ─────────────────────────────────────────────────────────── */

@media (min-width: 768px) {
  .app-main { padding: 28px; }
  .upload-card { margin-top: 14vh; padding: 40px 32px; }
  .upload-card h1 { font-size: 34px; }
  .shape-grid { grid-template-columns: repeat(6, 1fr); }
  .action-bar { padding: 16px 20px 20px; }
  .sv-box { height: 240px; }
}
/* ── Desktop ────────

/* Desktop layout — REQUIRES mouse-pointer input. `pointer: fine` excludes
   touchscreens (phones, tablets) regardless of how wide their CSS viewport
   reports. Some large Android phones and tablets-in-landscape exceed
   1024px CSS-pixels and were incorrectly picking up the desktop grid;
   pointer:fine is the canonical way to say "real desktop, not touch." */
@media (min-width: 1024px) and (pointer: fine) {
  /* [hidden] is display:none in the UA stylesheet, but `display: grid`
     below would override that — leaving the empty editor visible while
     the upload card is still up. Restore precedence explicitly. */
  #edit-step[hidden] { display: none; }

  /* Three-area layout: tab bar at TOP spanning both columns, controls on
     the LEFT, canvas on the RIGHT. .sticky-top wraps canvas+tab-bar in
     the HTML but we use display:contents to dissolve it on desktop so
     its children become direct grid items. */
  #edit-step {
    max-width: 1180px;
    margin: 0 auto;
    display: grid;
    grid-template-columns: minmax(340px, 400px) minmax(420px, 1fr);
    grid-template-rows: auto 1fr;
    grid-template-areas:
      "tabs     tabs"
      "controls canvas";
    gap: 18px 28px;
    align-items: start;
  }
  .sticky-top  { display: contents; }
  .tab-bar     { grid-area: tabs; }
  .controls    { grid-area: controls; align-self: start; }
  .canvas-stack {
    grid-area: canvas;
    max-width: 640px;
    margin: 0 auto;
    /* Sticky on desktop so the canvas stays visible while the user
       scrolls the (often tall) controls column on the left. align-self
       must be `start` for sticky to work inside a CSS grid item — without
       it the grid stretches the cell to full row height and sticky has
       nothing to stick to. */
    position: sticky;
    top: 18px;
    align-self: start;
  }
  .canvas-stack > .canvas-wrap { max-width: 100%; }
  #canvas { max-width: 600px; max-height: 70vh; }
}

/* Font preview — shows the currently selected font in BIG letters so iOS
   users (whose native dropdown can't render option fonts) still see what
   they picked. Pure visual; not interactive. */
.font-preview {
  margin: 8px 0 0;
  padding: 16px 12px;
  background: var(--bg);
  border: 1.5px solid #0a7574;
  border-radius: 12px;
  text-align: center;
  font-size: 32px;
  line-height: 1;
  color: var(--text);
  letter-spacing: 1px;
  min-height: 56px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ── Canvas controls (Show Photo / Lock) ────────────────────────────────
   Two persistent toggles that live BELOW the canvas — visible across
   all four tabs because they're part of .sticky-top.
     - Show Photo: toggles state.previewMode → ghost layer at low opacity
                   around the masked shape so user can see surroundings.
     - Lock:       toggles state.canvasLocked → blocks pan/pinch/rotate
                   gestures so the photo doesn't move when the user
                   accidentally touches the canvas while scrolling. */
.canvas-stack {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin: 0 auto 8px;
  max-width: 100%;
}
.canvas-stack > .canvas-wrap {
  margin: 0 auto;
  width: 100%;
  max-width: 480px;
}
.canvas-controls {
  display: flex;
  gap: 6px;
  width: 100%;
  max-width: 480px;
  margin: 8px auto 0;
}
.canvas-toggle {
  flex: 1;
  background: var(--panel-2);
  color: var(--text);
  border: 1.5px solid #0a7574;
  border-radius: 999px;
  padding: 4px 6px;
  font: inherit;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.3px;
  text-transform: uppercase;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  min-height: 32px;
  box-shadow:
    0 2px 4px rgba(0,0,0,0.25),
    inset 0 1px 0 rgba(255,255,255,0.05);
  transition: color 0.12s ease, background 0.12s ease, border-color 0.12s ease, box-shadow 0.12s ease, transform 0.05s ease;
}
.canvas-toggle:hover {
  background: var(--line);
}
.canvas-toggle:active { transform: translateY(1px); }
.canvas-toggle.active {
  background: var(--accent);
  color: #001416;
  border-color: var(--accent);
  box-shadow:
    0 4px 12px rgba(14,165,164,0.45),
    inset 0 1px 0 rgba(255,255,255,0.30);
}
.canvas-toggle-icon { font-size: 18px; line-height: 1; }

/* ── Color presets row ──────────────────────────────────────────────────
   Big tap-target color squares + custom-picker button. Higher-specificity
   selectors (.field .color-pair / .field .color-preset) are required so
   they win over `.field span` (which uppercases / spaces / flexes spans
   inside .field by default — that was making the presets render as thin
   colored dashes). !important on key layout/text properties belt-and-
   suspenders the override on iOS Safari which can be picky about
   specificity ties between equally-specific selectors. */
.field .color-pair {
  display: flex !important;
  gap: 6px;
  flex-wrap: nowrap;
  align-items: center;
  justify-content: flex-start !important;
  text-transform: none !important;
  font-size: 14px !important;
  font-weight: 400 !important;
  letter-spacing: 0 !important;
  margin: 0 !important;
}
.field .color-preset {
  width: 32px;
  height: 32px;
  flex: 0 0 auto;
  border-radius: 8px;
  border: 2px solid #0a7574;
  cursor: pointer;
  padding: 0;
  box-shadow:
    inset 0 0 0 2px rgba(255,255,255,0.12),
    0 2px 4px rgba(0,0,0,0.30);
  transition: transform 0.05s ease, box-shadow 0.12s ease, border-color 0.12s ease;
}
.field .color-preset:hover {
  transform: translateY(-1px);
  border-color: var(--accent);
  box-shadow:
    inset 0 0 0 2px rgba(255,255,255,0.20),
    0 4px 10px rgba(14,165,164,0.35);
}
.field .color-preset:active { transform: translateY(0); }
.field .color-preset:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* Custom-picker button — defaults to a rainbow conic-gradient so it
   reads as "any color you want." After the user picks any color (preset
   or custom-picker), JS overrides the background to that solid color so
   the button reflects the active selection. */
.field .color-swatch.color-custom {
  width: 32px;
  height: 32px;
  background: conic-gradient(
    from 0deg,
    #ef4444, #f97316, #eab308, #22c55e,
    #06b6d4, #3b82f6, #a855f7, #ec4899, #ef4444
  ) !important;
  border: 2px solid #0a7574;
  border-radius: 8px;
  flex: 0 0 auto;
  cursor: pointer;
  padding: 0;
  box-shadow:
    inset 0 0 0 2px rgba(255,255,255,0.18),
    0 2px 4px rgba(0,0,0,0.30);
}
.field .color-swatch.color-custom:hover {
  border-color: var(--accent);
}

/* Universal `hidden` attribute respect — author rules like `.field {
   display: block }` were winning specificity ties against the UA's
   `[hidden] { display: none }` rule and leaving "hidden" elements
   visible (e.g. the bg-color-row stayed open even when Transparent
   background was checked). Forcing it with !important guarantees every
   `<element hidden>` actually disappears. */
[hidden] { display: none !important; }

/* ── Scroll hint (mobile only) ──────────────────────────────────────────
   Sits between the tab bar and the controls panel. Tells the user that
   the area below scrolls — without it, the boundary between sticky-top
   and the tab-panel is invisible on phone and people don't realize they
   can scroll to find more controls. Hidden on desktop where the layout
   is side-by-side and all controls are visible. */
.scroll-hint {
  display: block;
  text-align: center;
  font-size: 18px;
  font-weight: 900;
  letter-spacing: 4px;
  color: var(--accent);
  padding: 8px 0 6px;
  margin: 6px 0 0;
  text-transform: uppercase;
  opacity: 1;
}

/* ── Desktop tweaks: bring tab + canvas-toggle heights back up
   (mobile shrunk them aggressively to claw back vertical real estate
   when scrolling), and hide the scroll hint since desktop has a
   side-by-side layout with no hidden content. */
@media (min-width: 1024px) and (pointer: fine) {
  .scroll-hint { display: none; }
  .tab-btn {
    font-size: 18px;
    padding: 14px 12px;
    min-height: 52px;
  }
  .canvas-toggle {
    font-size: 13px;
    padding: 10px 12px;
    min-height: 44px;
    gap: 6px;
  }
  /* Shape grid: shrink everything so all 12 tiles fit cleanly in 2 rows
     × 6 columns inside the narrower desktop controls column (~340–400px
     wide). Tablet uses the same 6-col grid but at full screen width, so
     it doesn't need this override. Mobile stays at 4 columns × 3 rows. */
  .shape-grid {
    gap: 5px;
    grid-template-columns: repeat(6, 1fr);
  }
  .shape-tile {
    padding: 4px 2px;
    border-radius: 9px;
    gap: 2px;
  }
  .shape-tile-icon {
    font-size: 22px;
  }
  .shape-tile-label {
    font-size: 8px;
    letter-spacing: 0.2px;
    line-height: 1.1;
  }
}

/* ── Upload-step button row (Pick a Photo + Take a Picture) ──────────────
   Two stacked buttons on phone, side-by-side on tablet+. The Take a
   Picture button uses capture="environment" on its <input> so phones
   open the camera UI directly. On desktop with a real mouse it gets
   hidden entirely (see pointer:fine block below) since the file picker
   would just open without launching a camera. */
.upload-btn-row {
  display: flex;
  flex-direction: column;
  gap: 10px;
  align-items: stretch;
  margin: 0 auto;
  max-width: 320px;
}
.upload-btn-row .upload-btn-wrap {
  display: block !important;
  width: 100%;
}
.upload-btn-row .upload-btn {
  width: 100%;
  display: block;
}
.upload-btn-camera {
  background: var(--panel-2) !important;
  color: var(--text) !important;
  border: 1.5px solid #0a7574;
}
.upload-btn-camera:hover {
  background: var(--line) !important;
}
@media (min-width: 768px) {
  .upload-btn-row { flex-direction: row; max-width: 480px; }
}
/* Hide Take a Picture on devices with a real mouse pointer (desktops).
   pointer:fine matches mouse + trackpad but NOT touchscreens, so phones
   and tablets keep the button. Tablets-with-mouse are an edge case
   we're OK losing the button on. */
@media (pointer: fine) {
  .upload-camera-wrap { display: none !important; }
  .replace-prompt-camera { display: none !important; }
}

/* ── Color preset selection indicator (Fix #13) ──
   Heavy white ring around the preset that matches the active color so
   the user can see which one is "current." Falls back to the
   custom-picker swatch when the active color isn't one of the presets. */
.field .color-preset.selected,
.field .color-swatch.color-custom.selected {
  border-color: #ffffff !important;
  box-shadow:
    inset 0 0 0 2px rgba(255,255,255,0.95),
    0 0 0 2px var(--accent),
    0 4px 10px rgba(14,165,164,0.45) !important;
  transform: translateY(-1px);
}

/* ── Export tab: Save + Copy buttons side by side ───────────────────────
   Wraps the two primary actions in a flex row on tablet+, stacks on
   phone for thumb-friendly tap targets. */
.export-row {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: 10px;
}
.export-row .btn { width: 100%; }
@media (min-width: 768px) {
  .export-row { flex-direction: row; }
  .export-row .btn { flex: 1; width: auto; }
}
/* ── Save / Copy toast ──────────────────────────────────────────────────
   Fixed at top of viewport so it's visible regardless of scroll position
   or which tab is active. Slides down on show, slides up on dismiss.
   Tap-to-dismiss is wired in app.js. Stays for 9s on success, until
   tapped on error. */
.export-toast {
  position: fixed;
  top: 12px;
  left: 50%;
  transform: translate(-50%, -160%);
  z-index: 200;
  width: calc(100% - 24px);
  max-width: 480px;
  padding: 14px 18px;
  background: var(--accent);
  color: #001416;
  border: 2px solid #0a7574;
  border-radius: 14px;
  font-size: 15px;
  font-weight: 700;
  line-height: 1.4;
  text-align: center;
  box-shadow:
    0 10px 30px rgba(0,0,0,0.45),
    0 0 0 4px rgba(14,165,164,0.25);
  cursor: pointer;
  opacity: 0;
  transition: transform 0.32s cubic-bezier(0.2, 0.7, 0.2, 1.05),
              opacity   0.25s ease;
  pointer-events: auto;
}
.export-toast.show {
  transform: translate(-50%, 0);
  opacity: 1;
}
.export-toast.error {
  background: #7f1d1d;
  color: #fff;
  border-color: #b91c1c;
  box-shadow:
    0 10px 30px rgba(0,0,0,0.55),
    0 0 0 4px rgba(220,38,38,0.25);
}
.export-toast[hidden] { display: none; }

/* The Replace Photo file input has to be visually-hidden (not display:none)
   because iOS Safari sometimes refuses to open the picker for inputs that
   are CSS-display:none. Pulled to a 0×0 box off-screen so it accepts a
   programmatic .click() but never shows up in layout. */
.visually-hidden-file {
  position: absolute !important;
  width: 1px !important;
  height: 1px !important;
  padding: 0 !important;
  margin: -1px !important;
  overflow: hidden;
  clip: rect(0,0,0,0);
  white-space: nowrap;
  border: 0;
  opacity: 0;
}

/* ── Adjust tab: Step 1 / Step 2 lock hints ─────────────────────────────
   The two .hint blocks at the top + bottom of the Adjust tab tell the
   user to unlock the photo before adjusting and re-lock it after. The
   accent left-border makes them stand out from the body text without
   being shouty. */
.adjust-lock-hint {
  border-left: 4px solid var(--accent);
  background: var(--panel-2);
}
.adjust-lock-hint b { color: var(--accent); }

/* ── Shape transforms (Flip / Upside Down / Twist) ──────────────────────
   Sits below the Iconify search block on the Shape tab. Two toggle
   buttons (flip H, flip V — same pill styling as canvas-toggle) plus a
   twist slider that rotates the shape mask in-place. */
.shape-transforms {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}
.shape-transforms .hint { margin-bottom: 10px; }
.shape-flip-row {
  display: flex;
  gap: 10px;
  margin-bottom: 14px;
}
.flip-toggle {
  flex: 1;
  background: var(--panel-2);
  color: var(--text);
  border: 1.5px solid #0a7574;
  border-radius: 999px;
  padding: 10px 12px;
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.3px;
  text-transform: uppercase;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 44px;
  box-shadow:
    0 2px 4px rgba(0,0,0,0.25),
    inset 0 1px 0 rgba(255,255,255,0.05);
  transition: color 0.12s ease, background 0.12s ease, border-color 0.12s ease, box-shadow 0.12s ease, transform 0.05s ease;
}
.flip-toggle:hover {
  background: var(--line);
}
.flip-toggle:active { transform: translateY(1px); }
.flip-toggle.active {
  background: var(--accent);
  color: #001416;
  border-color: var(--accent);
  box-shadow:
    0 4px 12px rgba(14,165,164,0.45),
    inset 0 1px 0 rgba(255,255,255,0.30);
}
.flip-icon {
  font-size: 18px;
  line-height: 1;
}

/* ── Replace Photo chooser sheet ────────────────────────────────────────
   Bottom sheet that asks "take a new picture or pick a photo?". Camera
   option auto-hides on desktop via the pointer:fine media query above.
   Mirrors the existing color-picker sheet visual style for consistency. */
.replace-prompt {
  position: fixed;
  inset: 0;
  z-index: 70;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  pointer-events: auto;
}
.replace-prompt[hidden] { display: none; }
.replace-prompt-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0,0,0,0.45);
  opacity: 0;
  transition: opacity 0.2s ease;
}
.replace-prompt.open .replace-prompt-backdrop { opacity: 1; }
.replace-prompt-sheet {
  position: relative;
  width: 100%;
  max-width: 460px;
  background: var(--panel);
  border-top-left-radius: 18px;
  border-top-right-radius: 18px;
  padding: 8px 22px 22px;
  box-shadow: 0 -8px 30px rgba(0,0,0,0.55);
  transform: translateY(100%);
  transition: transform 0.25s ease;
}
.replace-prompt.open .replace-prompt-sheet { transform: translateY(0); }
.replace-prompt-title {
  margin: 6px 0 6px;
  font-size: 22px;
  font-weight: 800;
  letter-spacing: -0.3px;
  text-align: center;
  color: var(--text);
}
.replace-prompt-sub {
  margin: 0 0 18px;
  font-size: 14px;
  color: var(--muted);
  text-align: center;
  line-height: 1.45;
}
.replace-prompt-actions {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.replace-prompt-actions .btn { width: 100%; flex: none; }
@media (min-width: 768px) {
  .replace-prompt {
    align-items: center;
  }
  .replace-prompt-sheet {
    border-radius: 18px;
    transform: translateY(20px) scale(0.96);
    opacity: 0;
    transition: transform 0.22s ease, opacity 0.22s ease;
  }
  .replace-prompt.open .replace-prompt-sheet {
    transform: translateY(0) scale(1);
    opacity: 1;
  }
}

