/* Shared styling for the standalone WebAssembly tool pages.
   These pages are intentionally self-contained (no Jekyll layout / Bootstrap)
   so their JavaScript is never touched by Liquid templating. The palette echoes
   the main site's dark Bootswatch theme (navbar #375a7f). */

:root {
  --bg: #272b30;
  --panel: #2e3338;
  --panel-2: #3a4047;
  --border: #1c1f22;
  --text: #e9ecef;
  --muted: #aab2bd;
  --accent: #375a7f;
  --accent-hover: #2b4866;
  --accent-text: #ffffff;
  --good: #00bc8c;
  --danger: #e74c3c;
  --radius: 10px;
  --maxw: 880px;
  /* Wider cap for the two-column editor on big screens so the recovered
     horizontal space is actually used instead of left as dead margin. */
  --maxw-editor: 1240px;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
    Arial, sans-serif;
  font-size: 17px;
  line-height: 1.5;
}

a { color: #8fb4dd; text-decoration: none; }
a:hover { text-decoration: underline; }

.topbar {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 14px;
  padding: 12px 20px;
  background: var(--accent);
  color: var(--accent-text);
}
.topbar a { color: #dbe7f3; font-weight: 600; }
.topbar > :first-child { justify-self: start; }
.topbar > :last-child { justify-self: end; }
.topbar .title { justify-self: center; font-weight: 700; font-size: 1.2rem; color: #fff; }
.topbar .brand { font-weight: 700; color: #fff; }

.wrap {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 28px 20px 80px;
}

h1 { font-size: 2rem; margin: 0 0 4px; }
h2 { font-size: 1.2rem; margin: 28px 0 12px; font-weight: 600; }
.lede { color: var(--muted); margin: 0 0 24px; }

.panel {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 20px;
  margin-bottom: 20px;
}

/* ---- Two-column editor shell (wide screens) ----
   By default (narrow / single column) the editor is a plain block: the preview
   stage stacks above the active step controls, exactly as before. At a min-width
   breakpoint it becomes a CSS grid — preview stage in column 1, the active
   step-pane in column 2 — so each step fits the 720px-tall viewport without the
   controls dropping below the fold, and the wasted left/right space is reclaimed. */
.editor { display: block; }
/* A hidden step-pane must ALWAYS stay hidden. Several wide-screen rules set
   display:flex/grid on specific panes (e.g. trim, export) to lay them out; those
   would otherwise beat the [hidden] attribute's default display:none and leave the
   inactive pane stacked on top of the active one (e.g. the Trim pane covering the
   Source dropzone so a video can't be added). [hidden] wins, always. */
.step-pane[hidden] { display: none !important; }

/* Step navigation (non-linear: every step is clickable any time) */
.steps {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 18px;
}
.stepbtn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex: 1 1 auto;
  justify-content: center;
  padding: 10px 14px;
  background: var(--panel);
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: .95rem;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}
.stepbtn:hover { color: var(--text); border-color: var(--accent); }
.stepbtn b {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; border-radius: 50%;
  background: var(--panel-2); color: var(--text);
  font-size: .8rem;
}
.stepbtn.active { background: var(--accent); color: #fff; border-color: var(--accent); }
.stepbtn.active b { background: rgba(255,255,255,.25); color: #fff; }

/* Tabs */
.tabs { display: flex; gap: 8px; margin-bottom: 18px; }
.tab {
  flex: 1;
  padding: 12px;
  text-align: center;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
  font-weight: 600;
  color: var(--muted);
  user-select: none;
}
.tab.active { background: var(--accent); color: #fff; }

/* Privacy tagline above the source dropzone */
.tagline { text-align: center; color: var(--muted); font-size: .95rem; margin: 0 0 14px; }
.tagline a { font-weight: 600; }

/* Drop zone */
.drop {
  border: 2px dashed #566;
  border-radius: var(--radius);
  padding: 38px 20px;
  text-align: center;
  color: var(--muted);
  cursor: pointer;
  transition: border-color .15s, background .15s;
}
.drop:hover, .drop.over { border-color: var(--accent); background: var(--panel-2); }
.drop strong { color: var(--text); }
.drop input { display: none; }
#logo-preview {
  max-height: 200px; max-width: 100%; object-fit: contain; border-radius: 6px;
  background: repeating-conic-gradient(#3a4047 0% 25%, #2e3338 0% 50%) 50% / 20px 20px;
}
#drop-logo.has-logo { border-style: solid; border-color: var(--good); padding: 14px; }

/* Controls */
.controls {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 16px;
}
.field { display: flex; flex-direction: column; gap: 6px; }
/* `.field { display:flex }` would otherwise override the [hidden] attribute's
   default display:none, leaving hidden fields (e.g. Speed vs Total duration) on
   screen. Keep hidden fields hidden. */
.field[hidden] { display: none; }
.field label { font-size: .85rem; color: var(--muted); font-weight: 600; }
.field label .lu { font-weight: 400; color: #7a8590; font-size: .78rem; }
.field .hint { font-size: .75rem; color: #7a8590; }
/* A field that holds only an action button: push it to the bottom so it lines
   up with neighbouring inputs (replaces an empty <label> spacer). */
.field.action { justify-content: flex-end; }
.field.action .btn { width: 100%; }

/* Grouped number fields (e.g. Crop region X/Y/W/H + Output size W/H): a titled
   block with compact single-letter inputs in a 2-up grid. Two groups sit side
   by side on desktop, stack on phones. */
.field-groups { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: 14px; align-items: start; }
.field-group { background: var(--panel-2); border: 1px solid var(--border); border-radius: var(--radius); padding: 12px 14px; }
.fg-title { font-size: .85rem; font-weight: 600; color: var(--muted); margin-bottom: 10px; }
.fg-title .lu { font-weight: 400; color: #7a8590; font-size: .78rem; }
.fg-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 14px; }
.fg-item { display: flex; align-items: center; gap: 8px; margin: 0; }
.fg-item .fg-l { width: 1.1em; flex: none; text-align: center; color: var(--muted); font-weight: 600; font-size: .9rem; }
.fg-item input { width: 100%; min-width: 0; }
/* 3-digit crop/output values (480, 320) were clipped to "4:" / "3:" in the
   narrow ~50px grid cells because the native number spinner + generous padding
   ate the right edge. Drop the spinner (the d-pad / typing covers stepping) and
   tighten the horizontal padding so the full digits show in the same cell. */
.fg-item input[type="number"] {
  -moz-appearance: textfield;
  appearance: textfield;
  padding-left: 6px;
  padding-right: 6px;
  text-align: center;
}
.fg-item input[type="number"]::-webkit-outer-spin-button,
.fg-item input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
input[type="number"], input[type="text"], select {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 7px;
  color: var(--text);
  padding: 9px 11px;
  font-size: 1rem;
}
input[type="range"] { width: 100%; }
.row { display: flex; align-items: center; gap: 10px; }
.checkbox { display: flex; align-items: center; gap: 8px; font-size: .9rem; color: var(--muted); }
.checkbox input { width: 18px; height: 18px; }

/* Buttons */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  background: var(--accent);
  color: #fff;
  border: none;
  border-radius: 8px;
  padding: 12px 22px;
  font-size: 1rem;
  font-weight: 600;
  cursor: pointer;
}
.btn:hover:not(:disabled) { background: var(--accent-hover); }
.btn:disabled { opacity: .45; cursor: not-allowed; }
.btn.secondary { background: var(--panel-2); }
.btn.good { background: var(--good); color: #06231b; }

/* Progress + status */
.progress { height: 10px; background: var(--bg); border-radius: 6px; overflow: hidden; margin-top: 14px; display: none; }
.progress.show { display: block; }
.progress > div { height: 100%; width: 0; background: var(--good); transition: width .15s; }
.status { margin-top: 12px; color: var(--muted); font-size: .9rem; min-height: 1.2em; }
.status.error { color: var(--danger); }

/* Result */
.result { display: none; }
.result.show { display: block; }
.result .meta { color: var(--muted); margin: 12px 0; }

/* Four size/quality variants from a single render. */
.results-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 14px;
}
.result-card {
  display: flex; flex-direction: column; gap: 8px;
  padding: 12px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  text-align: center;
}
.result-card img {
  max-width: 100%; border-radius: var(--radius); border: 1px solid var(--border); background:
  repeating-conic-gradient(#3a4047 0% 25%, #2e3338 0% 50%) 50% / 22px 22px; }
.result-card .rc-label { font-weight: 600; }
.result-card .rc-meta { color: var(--muted); font-size: .8rem; line-height: 1.4; }
.result-card .small-btn { padding: 8px 12px; font-size: .9rem; margin-top: auto; }
/* Result thumbnails are clickable to open the fullscreen viewer. */
.result-card img { cursor: zoom-in; }

/* ---- Fullscreen result viewer (lightbox) ---- */
.lightbox { position: fixed; inset: 0; z-index: 100; background: rgba(0,0,0,.92); display: flex; flex-direction: column; }
.lightbox[hidden] { display: none; }
.lb-bar { display: flex; gap: 10px; align-items: center; justify-content: center; flex-wrap: wrap; padding: 12px; background: var(--bg); border-bottom: 1px solid var(--border); }
.lb-bar .lb-label { color: var(--text); font-weight: 600; margin-right: auto; padding-left: 6px; }
.lb-bar .small-btn.active { background: var(--accent); color: #fff; border-color: var(--accent); }
.lb-stage {
  flex: 1; overflow: auto; display: flex; align-items: center; justify-content: center;
  padding: 8px;
  /* checkerboard so transparent (oval-masked) GIFs read as transparent */
  background: repeating-conic-gradient(#3a4047 0% 25%, #2e3338 0% 50%) 50% / 24px 24px;
}
.lb-stage img { display: block; image-rendering: auto; }
/* Full screen: scale to fill the viewport (up or down), preserving aspect.
   True size: native pixels (1:1), scroll if larger than the viewport. */
.lightbox.fit  .lb-stage img { width: 100%; height: 100%; object-fit: contain; }
.lightbox.true .lb-stage img { max-width: none; max-height: none; margin: auto; }

/* ---- Video timeline editor ---- */
/* The decode source <video>: rendered (so iOS decodes frames for the canvas)
   but effectively invisible. Must NOT be display:none / visibility:hidden. */
.decode-src {
  position: fixed; left: 0; bottom: 0;
  width: 2px; height: 2px; opacity: 0.01;
  pointer-events: none; z-index: 0;
}
/* Generate step: collapse the stage to nothing visible, but keep it display:block
   so the decode-source <video> inside stays rendered for frame extraction. */
#stage.collapsed { padding: 0; margin: 0; border: 0; background: none; }
#stage.collapsed #preview-wrap,
#stage.collapsed .transport,
#stage.collapsed .timeline { display: none; }

#preview-wrap {
  position: relative;
  display: block;
  width: fit-content;
  max-width: 100%;
  margin: 0 auto;
  line-height: 0;
  overflow: hidden;            /* clip the crop box's dimming shadow to the preview */
  border-radius: var(--radius);
}
#preview-canvas {
  display: block;
  max-width: 100%;
  max-height: 440px;
  background: #000;
  border-radius: var(--radius);
}
/* With the oval mask on, drop the black backing so the masked-out corners are
   genuinely transparent — you see the page/panel behind the preview. */
#preview-canvas.masked { background: transparent; }

/* Crop box overlaid on the preview (positioned in % of the displayed frame) */
#crop-rect {
  position: absolute;
  box-sizing: border-box;
  border: 1px solid rgba(255,255,255,.95);
  box-shadow: 0 0 0 9999px rgba(0,0,0,.5);   /* dims everything outside the crop */
  cursor: move;
  touch-action: none;
}
/* rule-of-thirds guides */
#crop-rect::before, #crop-rect::after {
  content: ''; position: absolute; pointer-events: none;
  border: 0 dashed rgba(255,255,255,.35);
}
#crop-rect::before { inset: 0; border-left-width: 1px; border-right-width: 1px; left: 33.33%; right: 33.33%; }
#crop-rect::after  { inset: 0; border-top-width: 1px; border-bottom-width: 1px; top: 33.33%; bottom: 33.33%; }
/* Oval-mask guide: a dashed inscribed ellipse on the crop box (border-radius:50%
   on a rectangle is an oval). Shown only when the oval mask is enabled. */
.crop-oval { position: absolute; inset: 0; border: 1px dashed rgba(255,255,255,.9); border-radius: 50%; pointer-events: none; }
#crop-rect .ch {
  position: absolute; width: 12px; height: 12px;
  background: #fff; border: 1px solid #375a7f; border-radius: 2px;
  touch-action: none;
}
.ch.nw { left: -6px;  top: -6px;  cursor: nwse-resize; }
.ch.n  { left: 50%;   top: -6px;  margin-left: -6px; cursor: ns-resize; }
.ch.ne { right: -6px; top: -6px;  cursor: nesw-resize; }
.ch.e  { right: -6px; top: 50%;   margin-top: -6px;  cursor: ew-resize; }
.ch.se { right: -6px; bottom: -6px; cursor: nwse-resize; }
.ch.s  { left: 50%;   bottom: -6px; margin-left: -6px; cursor: ns-resize; }
.ch.sw { left: -6px;  bottom: -6px; cursor: nesw-resize; }
.ch.w  { left: -6px;  top: 50%;   margin-top: -6px;  cursor: ew-resize; }
/* with a locked aspect ratio, only the corner handles make sense */
#crop-rect.aspect-locked .ch.n,
#crop-rect.aspect-locked .ch.e,
#crop-rect.aspect-locked .ch.s,
#crop-rect.aspect-locked .ch.w { display: none; }

/* Nudge d-pad for moving the crop pixel-by-pixel */
.nudge { display: flex; flex-direction: column; align-items: center; gap: 3px; }
.nudge-row { display: flex; gap: 3px; }
.ndg {
  width: 34px; height: 30px;
  background: var(--panel-2); color: var(--text);
  border: 1px solid var(--border); border-radius: 6px;
  cursor: pointer; font-size: .8rem; line-height: 1; padding: 0;
}
.ndg:hover { background: var(--accent); color: #fff; }
.transport {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 14px 0 10px;
  flex-wrap: wrap;
}
.tbtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--panel-2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 7px;
  width: 44px;
  height: 40px;
  padding: 0;
  cursor: pointer;
}
.tbtn svg { width: 20px; height: 20px; fill: currentColor; display: block; }
.tbtn:hover { background: var(--accent); color: #fff; }
.tbtn.play { background: var(--accent); color: #fff; width: 56px; }
.timecode { margin-left: auto; font-variant-numeric: tabular-nums; color: var(--muted); font-size: .9rem; }

.timeline { padding: 6px 0; }
.tl-track {
  position: relative;
  height: 46px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  cursor: pointer;
  overflow: hidden;
  touch-action: none;
}
.tl-regions { position: absolute; inset: 0; }
.tl-region { position: absolute; top: 0; bottom: 0; }
.tl-region.keep { background: rgba(0,188,140,.30); border-left: 1px solid rgba(0,188,140,.6); border-right: 1px solid rgba(0,188,140,.6); }
.tl-region.cut  {
  background: repeating-linear-gradient(45deg, rgba(231,76,60,.40) 0 8px, rgba(231,76,60,.18) 8px 16px);
  /* a red ✕ cursor to signal "click to delete this cut" */
  cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Ccircle cx='12' cy='12' r='11' fill='%23e74c3c'/%3E%3Cpath d='M8 8L16 16M16 8L8 16' stroke='white' stroke-width='2.5' stroke-linecap='round'/%3E%3C/svg%3E") 12 12, pointer;
}
.tl-region.cut:hover {
  background: repeating-linear-gradient(45deg, rgba(231,76,60,.85) 0 8px, rgba(231,76,60,.55) 8px 16px);
  box-shadow: inset 0 0 0 2px #e74c3c;
}
/* a "✕" badge centred on the cut while hovering, so deletion is unmistakable */
.tl-region.cut::after {
  content: '✕';
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  color: #fff; font-weight: 700; font-size: 14px; opacity: 0;
  text-shadow: 0 1px 2px rgba(0,0,0,.8); pointer-events: none;
}
.tl-region.cut:hover::after { opacity: 1; }

/* yellow band showing the cut currently being staged (start marked, not yet removed) */
.tl-pending {
  position: absolute; top: 0; bottom: 0; z-index: 3; pointer-events: none;
  background: rgba(241,196,15,.40);
  border-left: 2px solid #f1c40f;
  border-right: 2px solid #f1c40f;
  display: none;
}
.tl-region.outside { background: rgba(0,0,0,.55); }
.tl-handle {
  position: absolute; top: 0; bottom: 0; width: 12px;
  background: var(--good);
  cursor: ew-resize; z-index: 4;
  border-radius: 3px;
}
.tl-handle::after {
  content: ''; position: absolute; top: 50%; left: 50%;
  width: 2px; height: 18px; margin: -9px 0 0 -1px; background: #06231b;
}
.tl-handle.in  { transform: translateX(-50%); }
.tl-handle.out { transform: translateX(-50%); }
.tl-playhead {
  position: absolute; top: -3px; bottom: -3px; width: 2px;
  background: #fff; z-index: 5; pointer-events: none;
  box-shadow: 0 0 4px rgba(0,0,0,.8);
}
.tl-playhead::before {
  content: ''; position: absolute; top: 0; left: 50%;
  transform: translateX(-50%);
  border-left: 5px solid transparent; border-right: 5px solid transparent;
  border-top: 6px solid #fff;
}
.tl-actions { display: flex; align-items: center; gap: 8px; margin-top: 12px; flex-wrap: wrap; }
.tl-actions .small-btn { padding: 7px 12px; font-size: .85rem; }
.tl-actions .small-btn.armed { background: #f1c40f; color: #3a2f00; border-color: #f1c40f; }
.tl-actions .small-btn.armed:hover { background: #f4d03f; }
.tl-sep { width: 1px; align-self: stretch; background: var(--border); margin: 0 4px; }

/* Collapsible About panel — tasteful, small when closed */
details.about { padding: 14px 20px; }
details.about > summary {
  cursor: pointer; list-style: none;
  font-size: 1rem; font-weight: 600; color: var(--text);
}
details.about > summary::-webkit-details-marker { display: none; }
details.about > summary::before { content: '▸'; color: var(--muted); margin-right: 8px; }
details.about[open] > summary::before { content: '▾'; }
details.about[open] > summary { margin-bottom: 10px; }
details.about h3 { font-size: .95rem; }

/* "Vibe" callout */
.vibe {
  background: linear-gradient(135deg, rgba(55,90,127,.35), rgba(0,188,140,.18));
  border: 1px solid var(--accent);
  text-align: center;
}
.vibe h2 { margin-top: 0; }
.vibe .btn { margin-top: 6px; }
.refs { columns: 2; column-gap: 28px; font-size: .85rem; }
.refs li { margin: 0 0 6px; break-inside: avoid; }
@media (max-width: 560px) { .refs { columns: 1; } }

/* Searchable combobox (colormap picker) */
.combo { position: relative; }
.combo input { width: 100%; }
.combo-list {
  position: absolute; z-index: 20; left: 0; right: 0; top: 100%;
  margin-top: 4px; max-height: 260px; overflow-y: auto;
  background: var(--panel-2); border: 1px solid var(--border);
  border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,.45);
}
.combo-opt {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 10px; cursor: pointer; font-size: .92rem;
}
.combo-opt:hover, .combo-opt.active { background: var(--accent); color: #fff; }
.combo-opt .swatch {
  width: 64px; height: 48px; border-radius: 4px; flex: none;
  border: 1px solid rgba(0,0,0,.4);
  /* contain (not cover) so the frame thumbnail keeps its aspect ratio */
  background-size: contain; background-repeat: no-repeat; background-position: center;
  background-color: #26292d;
}
.combo-empty { padding: 9px 10px; color: var(--muted); font-size: .88rem; }

/* Tone curve editor: [input + curve]  →  [output] */
.curve-head { display: flex; align-items: center; justify-content: space-between; gap: 10px 14px; flex-wrap: wrap; }
.curve-toggles { display: flex; align-items: center; gap: 10px 14px; flex-wrap: wrap; }
.curve-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 8px; }
.curve-col { display: flex; flex-direction: column; gap: 4px; flex: 1 1 220px; min-width: 150px; max-width: 300px; }
.curve-col canvas {
  width: 100%; height: auto; display: block;
  border: 1px solid var(--border); border-radius: 8px; background: #16181b;
}
#curve-canvas { cursor: crosshair; touch-action: none; }
.curve-arrow { flex: 0 0 auto; color: var(--muted); font-size: 1.7rem; line-height: 1; }
.curve-cap { font-size: .72rem; color: var(--muted); text-align: center; }
.toggle { display: inline-flex; align-items: center; gap: 6px; font-size: .8rem; color: var(--muted); cursor: pointer; }
.toggle input { width: 16px; height: 16px; }

/* Contrast levels (triple slider) */
.levels { display: flex; flex-direction: column; gap: 7px; margin: 8px 0 4px; }
.lv-row { display: flex; align-items: center; gap: 10px; }
.lv-row > span:first-child { width: 42px; color: var(--muted); font-size: .82rem; }
.lv-row input[type="range"] { flex: 1; }
.lv-row .lv-val { width: 42px; text-align: right; font-variant-numeric: tabular-nums; color: var(--muted); font-size: .82rem; }

.muted { color: var(--muted); }
.small { font-size: .85rem; }
footer.legal { color: #7a8590; font-size: .8rem; margin-top: 40px; border-top: 1px solid var(--border); padding-top: 16px; }

/* ---- Wide-screen two-column editor (desktops / 1280×720 target) ----
   Optimised for a 1080p Windows screen at 150% scaling (≈1280×720 CSS px) and
   plain 1920×1080. The preview stage sits beside the active step controls so the
   short steps fit one screen, and the heaviest step (Style) only needs minimal
   scroll. Phones keep the single-column layout (the ≤640px rules below). */
@media (min-width: 820px) {
  /* Widen the page only here so the editor can use the recovered horizontal
     space; the step bar / standalone panels stay centred at the old width.
     Use min(--maxw-editor, 96vw) so at the 1280px target the wrap grows to fill
     all but a thin symmetric gutter (was capped at 1240 → a 40px right gutter)
     while still capping on truly wide screens. */
  .wrap { max-width: min(var(--maxw-editor), 96vw); }
  .steps,
  .vibe,
  details.about,
  footer.legal { max-width: var(--maxw); margin-inline: auto; }

  .editor {
    display: grid;
    /* col 1 = preview (flexes, but bounded), col 2 = controls (fixed-ish). */
    grid-template-columns: minmax(0, 1fr) minmax(360px, 440px);
    gap: 24px;
    align-items: start;
  }
  /* Preview stage lives in column 1 and sticks so it stays in view while the
     column-2 controls (which may be taller on Style) scroll. */
  #stage {
    grid-column: 1;
    grid-row: 1;
    position: sticky;
    top: 12px;
    margin-bottom: 0;
  }
  /* All step-panes share column 2 / row 1 (only one is ever un-hidden), so they
     stack in the same cell beside the preview rather than below it. */
  .step-pane {
    grid-column: 2;
    grid-row: 1;
    margin-bottom: 0;
    /* NB: deliberately NOT overflow:auto here — the colormap combobox dropdown
       (.combo-list) is absolutely positioned and would be clipped. The preview
       cap + density reductions keep the pane within the viewport; the heaviest
       step (Style) may scroll the page slightly, which is acceptable. */
  }
  /* Let the preview grow into its (wide) column instead of rendering small and
     left-aligned with ~220px of dead space to its right. The canvas fills the
     column width but stays height-capped so the stage + transport + timeline fit
     the 720px viewport; height:auto keeps its aspect ratio (a tall/portrait video
     binds on max-height instead and shrinks its width). The wrap hugs the canvas
     (width:fit-content) and is centred, so the absolutely-positioned crop box —
     which is sized in % of the wrap — stays aligned to the canvas exactly. */
  .video-only { text-align: center; }
  /* Drive the preview by HEIGHT, not width, so it upscales past the source's
     native pixel width into the wide column instead of rendering small and
     left-aligned with ~220px dead space. The canvas takes a fixed display height
     (capped so the stage + transport + timeline still fit 720px); width:auto
     follows the aspect ratio (e.g. a 3:2 video → ~605px wide at 403px tall,
     filling the column). max-width:100% reins in a tall/portrait video that would
     otherwise overflow the column (reducing its height). The wrap hugs the canvas
     (width:fit-content) and is centred, so the absolutely-positioned crop box —
     sized in % of the wrap — stays exactly aligned to the canvas. */
  #preview-wrap {
    width: fit-content;
    max-width: 100%;
    margin-inline: auto;
  }
  #preview-canvas {
    height: min(56vh, 460px);
    width: auto;
    max-width: 100%;
    max-height: none;
  }

  /* Before a video is loaded the stage is [hidden]; and when the Export step is
     focused (results shown) the stage collapses. In both cases drop to a single
     column so the controls / output GIFs span the full width instead of being
     stranded in the narrow column 2 beside an empty/zero column 1. */
  #editor:has(#stage[hidden]),
  #editor:has(#stage.collapsed) { grid-template-columns: 1fr; }
  #stage.collapsed { grid-column: 1 / -1; }
  #editor:has(#stage[hidden]) .step-pane,
  #editor:has(#stage.collapsed) .step-pane { grid-column: 1 / -1; }
  /* Pre-video the Source pane is the only single-column state; constrain it so
     the drop card isn't stranded as a very wide/tall panel beside dead space. */
  #editor:has(#stage[hidden]) .step-pane[data-pane="source"] {
    max-width: 880px; margin-inline: auto;
  }
  /* P2 (round 4) — pre-load the dashed dropzone occupied only the top third of a
     tall panel. Make it the hero: give it real vertical presence and centre the
     call-to-action so the empty Source screen looks intentional, not unbalanced. */
  #editor:has(#stage[hidden]) .step-pane[data-pane="source"] .drop {
    min-height: 300px;
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding: 28px 20px;
  }

  /* Export: keep the primary CTA (Create GIF / Start over) pinned to the bottom
     of the pane so it's reachable without hunting below the controls. */
  .step-pane[data-pane="export"] .action-row {
    position: sticky;
    bottom: 0;
    margin: 16px -20px -20px;
    padding: 14px 20px;
    background: var(--panel);
    border-top: 1px solid var(--border);
    border-radius: 0 0 var(--radius) var(--radius);
  }

  /* ---- Crop step: pack the controls dense so the whole pane clears 720px ----
     The default auto-fit grid wraps the two nudge d-pads + Reset + aspect onto
     several rows; force a tight 2-up grid, shrink the d-pad keys, drop the
     per-field hints (the labels are self-explanatory at this step) and tighten
     the field gaps. This brings the Crop pane from ~782px down under the fold. */
  /* First controls row (Rotate / Flip) stays 2-up; trim its inline 16px bottom
     margin so the Crop pane (which sat exactly at the 720 fold) clears it. */
  .step-pane[data-pane="crop"] .controls { grid-template-columns: 1fr 1fr; gap: 12px; }
  .step-pane[data-pane="crop"] > .controls[style] { margin-bottom: 10px !important; }
  /* The SECOND controls block (aspect + the two nudge d-pads + Reset) is the
     tallest part of the pane when it wraps to two rows of ~110px. Re-flow it:
     Aspect spans the full top row, then the two compact d-pads + the Reset button
     share the row below. Collapses ~227px → ~140px while keeping every control
     visible inside the 440px column (the earlier single-row variant pushed Reset
     past the right edge). */
  .step-pane[data-pane="crop"] .video-only .controls {
    grid-template-columns: auto auto 1fr;
    align-items: end;
    gap: 10px 14px;
  }
  .step-pane[data-pane="crop"] .video-only .controls > .field:first-child { grid-column: 1 / -1; }
  .step-pane[data-pane="crop"] .video-only .controls .field.action { align-self: center; justify-self: start; min-width: 0; }
  .step-pane[data-pane="crop"] .video-only .controls .field.action .btn { width: auto; white-space: nowrap; }
  .step-pane[data-pane="crop"] .field { gap: 4px; }
  .step-pane[data-pane="crop"] .hint { display: none; }
  .step-pane[data-pane="crop"] .ndg { width: 26px; height: 26px; }
  .step-pane[data-pane="crop"] .nudge { gap: 2px; }
  .step-pane[data-pane="crop"] .nudge-row { gap: 2px; }
  .step-pane[data-pane="crop"] .checkbox { font-size: .82rem; }
  /* The oval-mask checkbox carries an inline margin-bottom:14px; trim it (and the
     other gaps below) so the Crop pane clears the 720px fold with headroom. */
  .step-pane[data-pane="crop"] .oval-check { margin-bottom: 8px !important; }
  .step-pane[data-pane="crop"] .field-groups { margin-top: 8px; gap: 12px; }
  /* The "Keeping WxH → output …" summary is non-essential while fitting; shrink it. */
  .step-pane[data-pane="crop"] #crop-info { margin-top: 4px; font-size: .78rem; }
  .step-pane[data-pane="crop"] h2 { margin-bottom: 8px; }

  /* ---- Style step: keep the two 256² tone-curve / histogram canvases side by
     side (no wrap) and shorter, so the pane fits one screen instead of stacking
     them into a ~1040px tower in the narrow control column. ---- */
  .step-pane[data-pane="style"] .curve-row { flex-wrap: nowrap; }
  .step-pane[data-pane="style"] .curve-col { flex: 1 1 0; min-width: 0; max-width: none; }
  .step-pane[data-pane="style"] #curve-canvas,
  .step-pane[data-pane="style"] #hist-out { max-height: 132px; }
  /* The "How the tone curve & colormaps work" disclosure sat right at the fold;
     shave its top margin so the Style pane clears 700px. */
  .step-pane[data-pane="style"] .help-details { margin-top: 8px; }
  /* Tighten the tone-curve header (label + invert/reverse/log toggles + Reset)
     so it wraps to fewer rows in the narrow control column. */
  .step-pane[data-pane="style"] .curve-head { gap: 6px 10px; }
  .step-pane[data-pane="style"] .curve-toggles { gap: 6px 10px; }
  /* Input / Colormap / Colour-filter: 2-up grid (a 3-up row squeezes the
     searchable colormap combobox too narrow, forcing its hint to wrap tall). The
     per-field hints are dropped here to save the rows' height — the labels are
     self-explanatory and the workflow is described in the intro line. */
  .step-pane[data-pane="style"] .controls { grid-template-columns: 1fr 1fr; gap: 10px 12px; }
  .step-pane[data-pane="style"] .controls .hint { display: none; }
  .step-pane[data-pane="style"] h2 { margin-bottom: 6px; }
  /* The "Applied left → right …" intro paragraph + the tone-curve field's top
     margin are generous; tighten both to recover vertical room. */
  .step-pane[data-pane="style"] > p.muted { margin: 0 0 8px; }
  .step-pane[data-pane="style"] .curve-row { margin-top: 6px; }
  .step-pane[data-pane="style"] .curve-head { margin-top: 4px; }

  /* P2 (round 4) — the Trim controls panel is short (Set start/end + cut buttons
     + one paragraph), leaving ~270px of dead height beside the 567px preview.
     Vertically centre the pane in its grid cell so the controls sit level with
     the middle of the preview instead of floating at the top of an empty column. */
  .step-pane[data-pane="trim"] { align-self: stretch; display: flex; flex-direction: column; justify-content: center; }
}

/* Very wide monitors (e.g. 1920×1080 @100%): raise the editor cap so the
   recovered horizontal space grows the preview column instead of becoming dead
   outer margin. The step bar / vibe / about / footer stay centred at --maxw.
   The controls column is allowed to grow a little too (440 → 520px) so the
   recovered width feeds BOTH columns rather than only the preview. */
@media (min-width: 1500px) {
  .wrap { max-width: min(1760px, 95vw); }
  .editor { grid-template-columns: minmax(0, 1fr) minmax(380px, 520px); }
}

/* After encoding, the stage collapses and the page goes single-column; three
   full-width ~370px result GIFs then sit below the controls and need scrolling.
   On the short 720px target, cap the result thumbnails and force 3-up so all
   three variants + their Download buttons review within roughly one screen. */
@media (min-width: 820px) and (max-height: 760px) {
  .results-grid { grid-template-columns: repeat(3, 1fr); }
  .result-card img,
  .result-card canvas { max-height: 230px; object-fit: contain; }

  /* P0 (round 4) — post-encode Export must fit 720px without scrolling to the
     Download buttons. The generic `#editor:has(#stage.collapsed){1fr}` rule drops
     the page to one column, then the export controls row + "Your GIFs" heading +
     three cards + the #vibe panel stack to ~1125px (a ~405px overflow). Instead,
     on this short-screen target re-lay the EXPORT pane itself as a two-column
     grid: the FPS/Quality/Timing controls (+ the sticky Create/Start-over row) in
     a narrow right column, and the results (the three GIF cards + their Download
     buttons) filling the wide left column beside them — so everything reviews in
     one screen height. The collapsed #stage stays display:block (decode <video>
     rendered) but takes no space. */
  #editor:has(#stage.collapsed) {
    grid-template-columns: minmax(0, 1fr) minmax(340px, 420px);
    column-gap: 24px;
  }
  #editor:has(#stage.collapsed) .step-pane[data-pane="export"] {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(320px, 400px);
    grid-template-areas:
      "head    head"
      "result  controls"
      "result  action"
      "result  status";
    column-gap: 24px;
    row-gap: 8px;
    align-items: start;
  }
  #editor:has(#stage.collapsed) .step-pane[data-pane="export"] > h2 { grid-area: head; }
  #editor:has(#stage.collapsed) .step-pane[data-pane="export"] > .controls { grid-area: controls; grid-template-columns: 1fr 1fr; gap: 10px; }
  /* Its own row under the controls — NOT the same grid cell, which overlapped the
     Create/Start buttons on top of the FPS field. */
  #editor:has(#stage.collapsed) .step-pane[data-pane="export"] > .action-row { grid-area: action; }
  #editor:has(#stage.collapsed) .step-pane[data-pane="export"] > .progress,
  #editor:has(#stage.collapsed) .step-pane[data-pane="export"] > .status { grid-area: status; }
  #editor:has(#stage.collapsed) .step-pane[data-pane="export"] > .result { grid-area: result; }
  /* The action row was sticky-pinned to the pane bottom for the tall single-column
     layout; in this side-by-side layout let it sit under the controls so the two
     columns balance and nothing floats below the fold. */
  #editor:has(#stage.collapsed) .step-pane[data-pane="export"] .action-row {
    position: static; margin: 12px 0 0; padding: 0; background: none; border: 0;
  }
  /* Results now fill the wide left column: keep 3-up but let the cards use the
     available width with shorter thumbnails so the block stays under ~560px. */
  #editor:has(#stage.collapsed) #results-grid { gap: 10px; }
  #editor:has(#stage.collapsed) .result-card img,
  #editor:has(#stage.collapsed) .result-card canvas { max-height: 200px; }
  #editor:has(#stage.collapsed) .result > h2 { margin: 0 0 6px; }
}

/* Reclaim vertical budget on the 720px target: the topbar + step bar otherwise
   eat ~145px before any control. Tighten the header band on short screens. */
@media (max-height: 760px) {
  /* Tighten the header band so the editor itself fits the short viewport. The
     About & license details (collapsed) and the legal footer stay visible just
     below the editor — reachable with a small scroll — rather than being hidden,
     so the license is always accessible. */
  .wrap { padding-top: 16px; padding-bottom: 16px; }
  .steps { margin-bottom: 10px; }
  .stepbtn { padding: 7px 12px; }
  /* P1 (round 4) — the "100% vibed / Star on GitHub" promo (#vibe) renders below
     the editor only after a GIF is generated, and was the tallest contributor
     pushing the Download buttons past the 720px fold. It's promotional, not part
     of the flow, so slim it hard on the short target: tight padding, drop the
     secondary blurb line, and shrink the heading so it adds ~70px instead of
     ~180px below the results. */
  /* NB: a bare `display:flex` would override the [hidden] attribute's
     display:none and make #vibe occupy space on every step (it's only un-hidden
     after a GIF is generated). Scope the flex layout to the un-hidden state. */
  .vibe:not([hidden]) { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 8px 16px; }
  .vibe { padding: 8px 16px; margin-top: 8px; }
  .vibe h2 { font-size: 1rem; margin: 0; }
  /* Keep only the heading + the star button on the short export screen; the two
     promo paragraphs are what pushed it past the fold. The full blurb still shows
     on taller screens. */
  .vibe p { display: none; }
  .vibe .btn { margin: 0; }
}

/* The Style help text is collapsed by default into a small disclosure so the
   heavy Style pane fits; it reads the same as the old always-on paragraphs. */
.help-details { margin-top: 12px; }
.help-details > summary {
  cursor: pointer; list-style: none;
  font-size: .8rem; font-weight: 600; color: var(--muted);
}
.help-details > summary::-webkit-details-marker { display: none; }
.help-details > summary::before { content: '▸'; margin-right: 6px; }
.help-details[open] > summary::before { content: '▾'; }
.help-details[open] > summary { margin-bottom: 8px; }

/* Constrain the tone-curve / output-histogram canvases so the Style pane is
   shorter on every layout. */
#curve-canvas, #hist-out { max-height: 240px; }

/* ---- Responsive (phones / tablets) ---- */
@media (max-width: 640px) {
  html, body { font-size: 16px; }
  .topbar { padding: 10px 12px; gap: 8px; }
  .topbar .title { font-size: 1rem; }
  .topbar a, .topbar .brand { font-size: .82rem; }
  .wrap { padding: 14px 12px 56px; }
  .panel { padding: 14px; }
  h1 { font-size: 1.5rem; }
  /* Even 3×2 grid of step buttons instead of an uneven flex-wrap; compact so the
     bar eats less of the short screen. */
  .steps { display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; margin-bottom: 10px; }
  .stepbtn { flex: none; padding: 6px 4px; font-size: .72rem; gap: 4px; }
  .stepbtn b { width: 18px; height: 18px; font-size: .7rem; }
  /* One control per row — verbose labels/inputs no longer overflow on phones. */
  .controls { grid-template-columns: 1fr; gap: 12px; }
  /* But pack the densest step-panes two-up so they don't tower under the preview. */
  .step-pane[data-pane="crop"] .controls,
  .step-pane[data-pane="style"] .controls { grid-template-columns: 1fr 1fr; gap: 10px; }
  /* Crop region / Output size groups keep their 2-up inner inputs side by side. */
  .field-groups { grid-template-columns: 1fr 1fr; gap: 12px; }
  /* Smaller preview so controls get above the fold on the 720–844px-tall screens. */
  #preview-canvas { max-height: 42vh; }
  .timecode { font-size: .8rem; }
  /* Cap the curve + output histogram height so the Style pane stays short. They
     stack vertically at this width (arrow points down); the cap is the main
     height saver. */
  .curve-col { flex-basis: 100%; max-width: none; }
  .curve-arrow { transform: rotate(90deg); }
  #curve-canvas, #hist-out { max-height: 180px; }
  /* Two result cards per row with smaller previews — comparable at a glance. */
  .results-grid { grid-template-columns: 1fr 1fr; gap: 10px; }
  .result-card { padding: 8px; }
  .result-card img { max-height: 130px; object-fit: contain; }
  .tl-actions { gap: 6px; }
  .tl-actions .small-btn { flex: 1 1 auto; }
  .tl-sep { display: none; }   /* dividers add nothing once the buttons wrap */

  /* Export step (before encoding): on the single-column phone layout the full
     preview + transport + timeline stays expanded above the controls, pushing
     FPS / Quality / Timing / "Create GIF" below the fold (the preview is least
     useful at the moment you're about to encode). Collapse the stage's visible
     UI while the Export pane is active so the controls sit near the top. The
     <video> decode-source is a sibling of #preview-wrap (not inside it) so it
     stays rendered for iOS extraction; #stage itself is never display:none. */
  #editor:has(.step-pane[data-pane="export"]:not([hidden])) #stage {
    padding: 0; margin: 0; border: 0; background: none;
  }
  #editor:has(.step-pane[data-pane="export"]:not([hidden])) #preview-wrap,
  #editor:has(.step-pane[data-pane="export"]:not([hidden])) .transport,
  #editor:has(.step-pane[data-pane="export"]:not([hidden])) .timeline {
    display: none;
  }
  /* Logo dropzone is over-sized for an optional feature; shrink it so the
     Position / size / opacity controls sit closer to the fold. */
  .step-pane[data-pane="logo"] .drop { padding: 16px; }
  .step-pane[data-pane="logo"] .drop p { margin: 4px 0; }
  /* Tighten the phone preview cap so controls start higher on every step. */
  #preview-canvas { max-height: 38vh; }
  /* Lay the two tone-curve canvases SIDE BY SIDE on phone too (they stacked into
     a ~360px tower); rotate the arrow back to horizontal and cap shorter. */
  .step-pane[data-pane="style"] .curve-row { flex-wrap: nowrap; }
  .step-pane[data-pane="style"] .curve-col { flex: 1 1 0; min-width: 0; max-width: none; }
  .step-pane[data-pane="style"] .curve-arrow { transform: none; }
  .step-pane[data-pane="style"] #curve-canvas,
  .step-pane[data-pane="style"] #hist-out { max-height: 120px; }
  /* Crop step: two separate nudge d-pads (source-px + GIF-px) are redundant on a
     phone; keep the source-pixel pad and hide the GIF-pixel one to shorten the
     pane. Exact numeric entry is still available via the Output-size fields. */
  .step-pane[data-pane="crop"] .nudge[data-unit="output"] { display: none; }
  .step-pane[data-pane="crop"] .field:has(.nudge[data-unit="output"]) { display: none; }
  /* P0 (round 4, iPhone) — the crop pane is the worst overflow; pack it like the
     desktop reflow so it loses ~80px. The second controls block (Aspect + the
     kept source-px d-pad + Reset) goes Aspect-full-width-then-row-below; per-field
     hints drop, the d-pad keys shrink, and the oval-mask / field-group / crop-info
     gaps tighten. The crop box stays % of #preview-wrap, so alignment is intact. */
  .step-pane[data-pane="crop"] .video-only .controls {
    grid-template-columns: auto 1fr; align-items: end; gap: 8px 12px;
  }
  .step-pane[data-pane="crop"] .video-only .controls > .field:first-child { grid-column: 1 / -1; }
  .step-pane[data-pane="crop"] .video-only .controls .field.action { align-self: center; justify-self: start; }
  .step-pane[data-pane="crop"] .video-only .controls .field.action .btn { width: auto; }
  .step-pane[data-pane="crop"] .field { gap: 4px; }
  .step-pane[data-pane="crop"] .hint { display: none; }
  .step-pane[data-pane="crop"] .ndg { width: 30px; height: 28px; }
  .step-pane[data-pane="crop"] .nudge { gap: 2px; }
  .step-pane[data-pane="crop"] .nudge-row { gap: 2px; }
  .step-pane[data-pane="crop"] .oval-check { margin-bottom: 8px !important; }
  .step-pane[data-pane="crop"] .field-groups { margin-top: 8px; gap: 10px; }
  .step-pane[data-pane="crop"] #crop-info { margin-top: 4px; font-size: .76rem; }
  .step-pane[data-pane="crop"] > .controls[style] { margin-bottom: 10px !important; }
  /* About & license + footer remain visible below the editor (collapsed details
     is small; a short scroll reaches them) so the license is always accessible. */

  /* P0 (round 4, iPhone) — root cause of every editing step overflowing 844px is
     the #stage block (preview + transport + timeline) eating ~48% of the viewport
     ABOVE the controls. Slim the stage chrome so ~150px of controls move above the
     fold on every step: tighter panel padding, a shorter timeline track, and a
     smaller transport gap. (The decode <video> is a sibling, untouched.) */
  #stage { padding: 10px; margin-bottom: 12px; }
  .transport { margin: 10px 0 8px; }
  .timeline { padding: 4px 0; }
  .tl-track { height: 40px; }

  /* Cap the preview HARDER on the heavy editing steps (Crop/Style/Logo) where the
     controls are dense and the preview is secondary — 32vh frees ~60px over the
     38vh default. TRIM keeps a larger preview (40vh): there the timeline IS the
     primary control, so the frame must stay readable while scrubbing. The crop box
     stays % of #preview-wrap (which hugs the canvas), so alignment is unaffected. */
  #editor:has(.step-pane[data-pane="crop"]:not([hidden])) #preview-canvas,
  #editor:has(.step-pane[data-pane="style"]:not([hidden])) #preview-canvas,
  #editor:has(.step-pane[data-pane="logo"]:not([hidden])) #preview-canvas { max-height: 32vh; }
  #editor:has(.step-pane[data-pane="trim"]:not([hidden])) #preview-canvas { max-height: 40vh; }

  /* Trim: the 5-line help paragraph + the cut buttons wrap past the fold. Collapse
     the verbose hint (the buttons + timeline are self-explanatory) and let the
     action buttons flow tight so they fit one or two short rows. */
  .step-pane[data-pane="trim"] .video-only > p.muted { display: none; }
  .tl-actions .small-btn { padding: 7px 10px; font-size: .82rem; }

  /* Logo: place Position + Size side-by-side and tighten field gaps so the Size
     slider clears the fold with the preview capped at 32vh. */
  .step-pane[data-pane="logo"] .controls { grid-template-columns: 1fr 1fr; gap: 10px; }
  .step-pane[data-pane="logo"] .field { gap: 4px; }
  /* Style: drop the per-field hints on phone too (desktop already does) to shave
     the rows that pushed the curve canvases below the fold. */
  .step-pane[data-pane="style"] .controls .hint { display: none; }

  /* Very short phones (landscape / small devices): shrink the step bar a touch
     more to reclaim ~20px for content. */
  .stepbtn { padding: 6px 4px; }
}

/* On short *single-column* screens (phones / narrow laptops, where the preview
   stacks ABOVE the controls) trim the preview so controls clear the fold. On the
   wide two-column editor the preview is sticky in its own column and shrinking it
   doesn't help the control column — there it should fill its column width
   instead (see the min(56vh,460px) cap in the ≥820px block), so scope this to
   <820px only. */
@media (max-height: 760px) and (max-width: 819px) {
  #preview-canvas { max-height: 44vh; }
}

/* ---- Bigger hit targets on touch devices ---- */
@media (pointer: coarse) {
  /* 16px form text stops iOS Safari from zooming in when a field is focused;
     44px min height/width meets the Apple touch-target guideline. */
  input[type="number"], input[type="text"], select { font-size: 16px; min-height: 44px; }
  .btn, .small-btn { min-height: 44px; }
  .checkbox input, .toggle input { width: 22px; height: 22px; }
  .tbtn { width: 50px; height: 46px; }
  .tbtn svg { width: 22px; height: 22px; }
  .tbtn.play { width: 60px; }
  .ndg { width: 44px; height: 40px; }
  .tl-track { height: 56px; }
  .tl-handle { width: 18px; }
  /* enlarge crop handles and re-center them on the corners/edges */
  #crop-rect .ch { width: 22px; height: 22px; }
  .ch.nw { left: -11px; top: -11px; }
  .ch.n  { top: -11px; margin-left: -11px; }
  .ch.ne { right: -11px; top: -11px; }
  .ch.e  { right: -11px; margin-top: -11px; }
  .ch.se { right: -11px; bottom: -11px; }
  .ch.s  { bottom: -11px; margin-left: -11px; }
  .ch.sw { left: -11px; bottom: -11px; }
  .ch.w  { left: -11px; margin-top: -11px; }
}
