.snr-widget {
  --snr-bg: #14141a;
  --snr-panel: #1c1c24;
  --snr-border: #33333e;
  --snr-text: #e8e6ef;
  --snr-muted: #9491a3;
  --snr-accent: #b6853f;
  width: 100%;
  margin: 2rem 0;
  color: var(--snr-text);
  font-size: 15px;
  line-height: 1.5;
  /* The stacked-layout breakpoint below reacts to how much room this
     widget itself actually has, not the browser window's width — a plain
     @media query would stay in wide/row mode (squeezing the photo area
     down to fit a still-360px-wide sidebar) whenever the widget sits in a
     column narrower than the viewport, e.g. a sidebar widget area or a
     narrow page template, even on a wide desktop browser. */
  container-type: inline-size;
  container-name: snr-widget;
}

.snr-widget * { box-sizing: border-box; }

/* Defensive reset: some host themes force [hidden] elements to display anyway
   (a broad ".foo, [hidden] { display:block }" rule is a common culprit), which
   would show the placeholder and the canvas at the same time. */
.snr-widget [hidden] {
  display: none !important;
}

/* Layout: Lightroom-style. Image takes whatever width is left over and
   grows with the page; the control panel is a fixed-width sidebar next to
   it, so its sliders and labels stay a comfortable width instead of
   stretching out on a wide site. The panel's height is synced to the
   image's rendered height in JS (see recolor-ui.js) and scrolls
   internally past that, so the image never has to be scrolled out of view
   just to reach a control further down the panel. */
.snr-layout {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  gap: 1.5rem;
}
@container snr-widget (max-width: 760px) {
  /* Not enough room for a fixed sidebar without squeezing the image too
     small to judge — stack instead, same as before. */
  .snr-layout {
    flex-direction: column;
  }
  /* .snr-layout's align-items: flex-start (needed for the side-by-side
     desktop layout above, so the sidebar does not stretch to the photo's
     full height) otherwise leaves each stacked panel shrink-wrapped to its
     own content width once the axes swap here — which is why the sidebar
     was landing at a different width/edge than the photo frame instead of
     lining up with it. Stretching both to the same 100% here is what
     actually keeps their edges, corners, and margins matched. */
  .snr-photo-area,
  .snr-controls {
    width: 100%;
  }
  .snr-controls {
    max-height: none !important;
    flex-basis: auto !important;
  }
}

/* Maximized: the widget lifts out of the page flow into a fixed overlay
   covering the browser viewport, so the surrounding site chrome (header,
   sidebar, footer) stops competing with the photo for space. Layout
   inside stays exactly the same flex row/sidebar arrangement — only the
   available room changes. */
.snr-widget-maximized {
  position: fixed;
  inset: 0;
  /* .snr-widget's own base rule sets width:100% for the normal in-flow
     layout -- left in place here, that explicit width fights the inset:0
     above. Per spec, a fixed element with left/width/right all non-auto is
     over-constrained, and the browser keeps left+width and drops "right"
     to satisfy it, sizing this off width:100% of a containing-block metric
     that does not quite match the true viewport instead of off inset:0
     directly -- which is what was pushing this box's right edge out past
     the actual viewport edge. Auto here lets inset:0 define the box. */
  width: auto;
  z-index: 100000;
  margin: 0;
  padding: 1rem;
  background: var(--snr-bg);
  /* Scrolling lives on .snr-layout below, not here -- a non-overlay
     scrollbar (Windows' default) renders inside whichever box has
     overflow:auto, eating into that box's own padding on the scrollbar
     side only. On this element that meant the right 1rem padding was
     quietly losing its own scrollbar's width while the left 1rem stayed
     full-size, throwing the two side margins out of sync. Moved one level
     in, the scrollbar eats into .snr-layout's content width instead (which
     .snr-photo-area, the flexible side, absorbs) and this element's own
     padding box -- what actually sets the left/right inset from the real
     viewport edge -- is never touched by it. */
  overflow: hidden;
}
.snr-widget-maximized .snr-layout {
  height: 100%;
  overflow-y: auto;
  overflow-x: hidden;
}
/* .snr-layout's align-items: flex-start (needed above so the sidebar
   does not stretch to the photo's own height) otherwise leaves
   .snr-photo-area sized to whatever its content naturally needs too --
   fine in the normal in-page layout (a zoomed-out photo should make the
   widget shorter there), but while maximized that meant zooming out far
   enough shrunk the whole container down around the now-small photo
   instead of it just floating, centered, inside a frame that stays the
   full viewport height. align-self here overrides flex-start for this
   one item without affecting the sidebar. */
.snr-widget-maximized .snr-photo-area {
  align-self: stretch;
}
@media (max-width: 760px) {
  .snr-widget-maximized {
    padding: 0.5rem;
  }
}

/* Photo area: dropzone before a photo is loaded, image preview + before/after
   compare after. Stays a drop target the whole time, so a new photo can be
   dropped in to replace the current one without any extra step. */
.snr-photo-area {
  position: relative;
  border: 2px dashed var(--snr-border);
  border-radius: 8px;
  background: var(--snr-panel);
  padding: 0.75rem;
  min-height: 260px;
  flex: 1 1 auto;
  min-width: 0; /* allow the flex item to shrink below the canvas's intrinsic width */
  display: flex;
  flex-direction: column;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}
/* Empty state only — once a photo is loaded its own aspect ratio (via
   fit-width) decides the height, and a tall floor here would just pad
   out a short/wide photo with empty space around it. Before that, a
   bigger dropzone reads as a more deliberate, roomier "real tool" empty
   state rather than a cramped placeholder. */
.snr-photo-area:not(.snr-photo-area-has-image) {
  min-height: 520px;
}
.snr-photo-area:hover {
  border-color: var(--snr-accent);
}
.snr-photo-area-active {
  border-color: var(--snr-accent);
  background: #22222c;
}
/* Once a photo is loaded the dashed "drop something here" border reads as a
   mistake rather than an invitation, so it settles into a plain panel edge. */
.snr-photo-area-has-image {
  border-style: solid;
  cursor: default;
}

.snr-dropzone-placeholder {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 3rem 1.5rem;
}
.snr-dropzone-text {
  margin: 0;
  color: var(--snr-muted);
}

.snr-photo-area-hint {
  margin: 0.6rem 0 0;
  font-size: 0.78rem;
  color: var(--snr-muted);
  text-align: center;
}

/* Zoom toolbar — only shown once a photo is loaded (see .snr-photo-area-has-image
   below), sits above the compare area. */
.snr-zoom-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  row-gap: 0.35rem;
  margin-bottom: 0.6rem;
  padding: 0.35rem 0.5rem;
  background: var(--snr-bg);
  border: 1px solid var(--snr-border);
  border-radius: 6px;
}
.snr-zoom-btn {
  flex: 0 0 auto;
  min-width: 28px;
  height: 28px;
  padding: 0 0.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  color: var(--snr-text);
  border: 1px solid var(--snr-border);
  border-radius: 4px;
  font-size: 0.85rem;
  line-height: 1;
  cursor: pointer;
}
.snr-zoom-btn:hover {
  border-color: var(--snr-accent);
}
.snr-zoom-btn-active {
  border-color: var(--snr-accent);
  color: var(--snr-accent);
}
.snr-zoom-btn-icon svg {
  display: block;
}
.snr-zoom-select {
  /* An explicit width, not just flex-basis:auto — a bare <select> as a flex
     item can otherwise claim far more than its content needs in some
     browsers, which was forcing the whole toolbar to wrap early. */
  flex: 0 0 auto;
  width: 92px;
  height: 28px;
  padding: 0 0.3rem;
  background: var(--snr-bg);
  color: var(--snr-text);
  border: 1px solid var(--snr-border);
  border-radius: 4px;
  font-size: 0.78rem;
  cursor: pointer;
}
.snr-zoom-select:hover {
  border-color: var(--snr-accent);
}
.snr-zoom-select-format {
  width: 62px; /* shorter option labels than the crop-ratio select, so it doesn't need the same 92px */
}
/* background:transparent on the closed box was letting the toolbar's own
   dark background show through for that part, but the opened option
   list is a separate, OS-rendered popup that doesn't inherit it — it
   needs its own explicit (non-transparent) color to not default to a
   plain white dropdown. Chromium and Firefox both respect this; Safari
   is more limited and may partially ignore it, native-popup styling
   being one of the least standardized corners of CSS. */
.snr-zoom-select option,
.snr-zoom-select optgroup {
  background: var(--snr-bg);
  color: var(--snr-text);
}
.snr-zoom-level {
  min-width: 3.2rem;
  text-align: center;
  font-size: 0.78rem;
  color: var(--snr-muted);
}
.snr-zoom-divider {
  width: 1px;
  height: 18px;
  background: var(--snr-border);
  margin: 0 0.2rem;
}

/* canvasAfter (the recolored result) sits directly on top of canvasBefore
   (the untouched original, kept around as the pixel source read into
   state.originalImageData — see setupCanvasFromImage in recolor-ui.js) at
   the same size, always fully opaque; only canvasAfter is ever visible.
   .snr-canvas-stack centers itself with margin:auto (see below) rather
   than this element using align-items/justify-content:center — centering
   a flex item that way looks identical while it still fits, but once it
   overflows a scrollable container, the browser splits that overflow
   evenly on both sides while only ever letting you scroll into the
   positive-direction half, permanently stranding whatever falls on the
   other side (the top/left of a heavily zoomed-in photo, in practice).
   margin:auto centers the same way when there is free space, but yields
   to 0 the moment the item is bigger than the container, so it pins to
   the scroll origin instead and every part of an overflowing photo stays
   reachable by scrolling. */
.snr-compare {
  position: relative;
  width: 100%;
  overflow: hidden;
  border-radius: 4px;
  line-height: 0;
  cursor: grab;
  touch-action: pan-x pan-y pinch-zoom;
  display: flex;
}
.snr-compare.snr-compare-panning {
  cursor: grabbing;
  user-select: none;
}
/* Zoomed in, or fit-to-height on a wide photo: the rendered image can exceed
   the container in either direction, so it needs to scroll instead of
   spilling out or silently clipping. Capped to a viewport-relative height so
   "fit height" (and zooming beyond it) stays within one screen rather than
   stretching the whole page layout tall. Toggled by recolor-ui.js, left off
   entirely in the default fit-width state so that behavior is unchanged. */
.snr-compare-scrollable {
  overflow: auto;
  max-height: calc(100vh - 160px);
}
/* While maximized, this cap applies unconditionally -- not just when a
   zoom/fit mode already opts into .snr-compare-scrollable above. Fit
   width's own default state deliberately has no cap (see the comment
   above), which is fine in the normal in-page layout where the page
   itself just scrolls, but inside the fixed-viewport maximized overlay
   there is no such fallback: a tall/portrait photo at fit-width, or a
   wide/landscape one at fit-height, would otherwise blow out past the
   viewport with nothing to contain it. This also keeps the sidebar
   (synced to the photo area's own rendered height) from inheriting that
   same runaway height, since it is this element growing unbounded that
   was dragging .snr-photo-area's height up with it. */
.snr-widget-maximized .snr-compare {
  max-height: calc(100vh - 160px);
  overflow: auto;
  /* Grows to fill whatever height .snr-photo-area's own align-self:stretch
     (above) leaves over once the toolbar and hint text take their share,
     so a small/zoomed-out photo floats centered (via .snr-canvas-stack's
     margin:auto) inside a frame that stays the full viewport height,
     instead of this shrinking down around the photo the way it does in
     the normal, non-maximized in-page layout. min-height:0 is the usual
     flex fix needed for a flex-grow item to actually be able to scroll/
     cap via max-height above rather than just overflowing its parent. */
  flex: 1 1 auto;
  min-height: 0;
}
/* The positioning context for canvasAfter and the crop overlay below —
   see the comment above .snr-compare. Sized in JS alongside the canvases
   themselves (100% in fit-width, an explicit pixel width once zoomed), so
   it's never bigger than they are and centering actually shows. flex:0 0
   auto so .snr-compare's own sizing can't stretch or shrink it — zoomed
   in past the container, it stays its true size and simply overflows into
   the scrollbar, exactly as before. margin:auto is what actually centers
   it (see the comment on .snr-compare above for why that lives here
   instead of align-items/justify-content on the container). */
.snr-canvas-stack {
  position: relative;
  width: 100%;
  flex: 0 0 auto;
  margin: auto;
}
.snr-compare canvas {
  display: block;
  width: 100%;
  height: auto;
}
.snr-compare canvas[data-snr-canvas-after] {
  position: absolute;
  top: 0;
  left: 0;
}
/* Crop overlay — a single div positioned over the kept rectangle (in
   recolor-ui.js, computed from the crop ratio and dragged to reposition).
   The surrounding "dimmed" area isn't a separate element: it's this div's
   own box-shadow spread out past the edges of the viewport and clipped by
   .snr-compare's overflow, a standard crop-tool trick. Positioned in the
   same coordinate space as canvasAfter (top:0/left:0 within
   .snr-canvas-stack), so it scrolls together with the image when zoomed in
   (or moves with it when centered), no extra syncing
   needed on scroll. */
.snr-crop-overlay {
  position: absolute;
  box-shadow: 0 0 0 100vmax rgba(0, 0, 0, 0.65);
  border: 1px solid rgba(255, 255, 255, 0.9);
  cursor: move;
  touch-action: none;
}
/* Faint rule-of-thirds guide, purely decorative. */
.snr-crop-overlay::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(to right, transparent calc(33.33% - 1px), rgba(255, 255, 255, 0.35) calc(33.33% - 1px), rgba(255, 255, 255, 0.35) calc(33.33%), transparent calc(33.33%)),
    linear-gradient(to right, transparent calc(66.66% - 1px), rgba(255, 255, 255, 0.35) calc(66.66% - 1px), rgba(255, 255, 255, 0.35) calc(66.66%), transparent calc(66.66%)),
    linear-gradient(to bottom, transparent calc(33.33% - 1px), rgba(255, 255, 255, 0.35) calc(33.33% - 1px), rgba(255, 255, 255, 0.35) calc(33.33%), transparent calc(33.33%)),
    linear-gradient(to bottom, transparent calc(66.66% - 1px), rgba(255, 255, 255, 0.35) calc(66.66% - 1px), rgba(255, 255, 255, 0.35) calc(66.66%), transparent calc(66.66%));
  pointer-events: none;
}
/* Corner resize handles — dragging one keeps the locked ratio (see
   recolor-ui.js), growing/shrinking the box from the opposite, fixed
   corner rather than freely distorting it. */
.snr-crop-handle {
  position: absolute;
  width: 12px;
  height: 12px;
  background: #fff;
  border: 1px solid rgba(0, 0, 0, 0.6);
  border-radius: 2px;
}
.snr-crop-handle-nw {
  top: -6px;
  left: -6px;
  cursor: nwse-resize;
}
.snr-crop-handle-ne {
  top: -6px;
  right: -6px;
  cursor: nesw-resize;
}
.snr-crop-handle-sw {
  bottom: -6px;
  left: -6px;
  cursor: nesw-resize;
}
.snr-crop-handle-se {
  bottom: -6px;
  right: -6px;
  cursor: nwse-resize;
}

/* Controls panel — always fully shown; no collapse. Fixed sidebar width,
   flex-grow/shrink disabled so it holds this width regardless of how wide
   the image column gets. max-height is refined by JS to match the image's
   actual rendered height (a CSS fallback here just avoids a flash of the
   wrong size before that JS runs). */
.snr-controls {
  background: var(--snr-panel);
  /* Matches .snr-photo-area's own frame exactly, including its dashed-until-
     an-image-is-loaded state below, so the two side-by-side panels read as
     a matched pair in every state, not just once a photo is loaded. */
  border: 2px dashed var(--snr-border);
  border-radius: 8px;
  display: flex;
  flex-direction: column;
  flex: 0 0 360px;
  max-height: 640px;
  overflow: hidden; /* histogram stays put; .snr-controls-body scrolls internally */
}
.snr-widget-has-image .snr-controls {
  border-style: solid;
}

/* Histogram — a fixed sibling above .snr-controls-body, not inside it, so
   it can't scroll out of view or be collapsed away; genuinely always
   visible, the way a real tool's histogram is. */
.snr-histogram-panel {
  flex: 0 0 auto;
  padding: 0.75rem 1.25rem;
  border-bottom: 1px solid var(--snr-border);
}
/* A saved preset spans both Basic and Color Grading, not just Basic, so
   it lives here as its own pinned panel (same reasoning and layout as
   .snr-histogram-panel above) rather than inside either accordion
   section. */
.snr-preset-panel {
  flex: 0 0 auto;
  padding: 0.75rem 1.25rem;
  border-bottom: 1px solid var(--snr-border);
}
.snr-histogram-title {
  display: block;
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--snr-muted);
  margin-bottom: 0.5rem;
}
.snr-histogram-chart {
  position: relative;
  height: 96px;
}
.snr-histogram-canvas {
  display: block;
  width: 100%;
  height: 96px;
  background: var(--snr-bg);
  border: 1px solid var(--snr-border);
  border-radius: 4px;
}
.snr-histogram-hint {
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 96px;
  background: var(--snr-bg);
  border: 1px solid var(--snr-border);
  border-radius: 4px;
  text-align: center;
}

/* Tone Curve editor — the canvas paints its own background transparent
   (see renderCurve in recolor-ui.js) so var(--snr-bg) below shows through
   exactly like .snr-histogram-canvas above; crosshair cursor and no
   default touch gestures match the Color Grading wheel's own canvas-like
   control for the same reason (dragging on a touch device shouldn't also
   try to scroll/zoom the page). */
.snr-curve-wrap {
  margin-bottom: 0.5rem;
}
.snr-curve-canvas {
  display: block;
  width: 100%;
  height: 200px;
  background: var(--snr-bg);
  border: 1px solid var(--snr-border);
  border-radius: 4px;
  cursor: crosshair;
  touch-action: none;
}
.snr-curve-canvas:focus-visible {
  outline: 2px solid var(--snr-accent);
  outline-offset: 2px;
}

.snr-controls-body {
  flex: 1 1 auto;
  min-height: 0; /* required for a flex child to actually scroll instead of stretching */
  overflow-y: auto;
  padding: 1rem 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
}

/* Settings accordion — each item opens/closes independently; which ones
   start open is set in the markup (see studio-notes-recolor.php). */
.snr-accordion {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.snr-accordion-item {
  border: 1px solid var(--snr-border);
  border-radius: 6px;
  overflow: hidden;
}
/* The header row holds two independent buttons side by side: the trigger
   (expands/collapses, takes the remaining space) and the reset button
   (fixed-width, its own click target) — siblings, not nested, since a
   <button> can't contain another. */
.snr-accordion-header {
  margin: 0;
  display: flex;
  align-items: stretch;
  background: var(--snr-bg);
}
.snr-accordion-trigger {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.6rem 0.75rem;
  background: transparent;
  color: var(--snr-text);
  border: none;
  cursor: pointer;
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  text-align: left;
}
.snr-accordion-trigger:hover {
  color: var(--snr-accent);
}
.snr-accordion-chevron {
  flex: 0 0 auto;
  color: var(--snr-muted);
  transition: transform 0.15s ease;
}
/* Chevron rotates to point down once its panel is open, rather than
   swapping in a different glyph. */
.snr-accordion-trigger[aria-expanded="true"] .snr-accordion-chevron {
  transform: rotate(90deg);
}
.snr-accordion-reset {
  flex: 0 0 auto;
  width: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  color: var(--snr-muted);
  border: none;
  border-left: 1px solid var(--snr-border);
  cursor: pointer;
}
.snr-accordion-reset:hover {
  color: var(--snr-accent);
}
/* Solo/mute preview toggle — same shape as the reset button next to it,
   but a persistent on/off state (not a one-shot action), so it also gets
   an "active" look for when it's currently hiding the section. */
.snr-accordion-bypass {
  flex: 0 0 auto;
  width: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  color: var(--snr-muted);
  border: none;
  border-left: 1px solid var(--snr-border);
  cursor: pointer;
}
.snr-accordion-bypass:hover {
  color: var(--snr-accent);
}
.snr-accordion-bypass-active {
  color: var(--snr-accent);
}
.snr-accordion-panel-inner {
  padding: 0.9rem 0.75rem 1rem;
}

.snr-hint {
  font-size: 0.78rem;
  color: var(--snr-muted);
  margin: 0.5rem 0 0;
}

/* Photo info — read-only facts about the loaded file, filled in by
   recolor-ui.js. Label stacked above value (rather than a label/value
   row side by side) since EXIF strings like a lens name can run long
   enough in this sidebar's width to wrap awkwardly otherwise. */
.snr-photo-info {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}
.snr-photo-info-row {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}
.snr-photo-info-label {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--snr-muted);
}
.snr-photo-info-value {
  font-size: 0.85rem;
  color: var(--snr-text);
  word-break: break-word;
}

/* The strength number/range classes below (.snr-stage-strength(-number))
   are a holdover name from a since-removed Effects section — Color
   Grading's own per-range Luminance control is their only remaining user,
   and gets its layout from .snr-colorgrade-control instead, not from
   these. */
/* Hide native spinner arrows so the box stays compact next to the slider —
   shared by Color Grading's per-range Luminance number input and the Tone
   sliders' number inputs. */
.snr-stage-strength-number::-webkit-outer-spin-button,
.snr-stage-strength-number::-webkit-inner-spin-button,
.snr-tone-number::-webkit-outer-spin-button,
.snr-tone-number::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.snr-stage-strength-number[type="number"],
.snr-tone-number[type="number"] {
  -moz-appearance: textfield;
}

/* Color grading: shadows/midtones/highlights, each its own card. The
   Luminance row reuses the .snr-stage-strength(-number) classes for the
   same look as every other slider in the panel, but those rules key off a
   ".snr-stage" ancestor this row doesn't have, hence the repeat here. */
.snr-colorgrade {
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
}
.snr-colorgrade-range {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 0.7rem 0.75rem;
  background: var(--snr-bg);
  border: 1px solid var(--snr-border);
  border-radius: 6px;
}
.snr-colorgrade-range-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
}
.snr-colorgrade-range-title {
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--snr-muted);
}
/* Live H/S/L readout, Lightroom's own on-wheel style, updated by
   recolor-ui.js on every drag/keyboard/reset/preset change -- lets a
   value be compared or matched against Lightroom's numbers directly
   instead of eyeballing puck position. Dropped onto its own line
   (flex-basis: 100%) under the title+wheel row, right-aligned so it
   sits under the wheel rather than spanning the row's full width. */
.snr-colorwheel-readout {
  flex-basis: 100%;
  text-align: right;
  margin-top: 0.3rem;
  font-size: 0.72rem;
  font-variant-numeric: tabular-nums;
  color: var(--snr-muted);
}
/* Color wheel: hue by angle (clockwise from 12 o'clock, matching
   conic-gradient's own default), saturation by distance from center —
   plain HSL at a fixed 50% lightness, picked and read by recolor-ui.js.
   The radial-gradient white fade is layered *over* the conic hue ring
   (first background listed paints on top), so the center reads as white
   and fades out by 60% of the radius, leaving a solid, fully saturated
   ring for the outer 40% rather than washing out all the way to the
   edge — the standard HSL wheel look, achieved with no canvas or
   per-pixel JS at all. (The puck's own saturation math is still exactly
   linear with radius — see recolor-ui.js — this only compresses where
   the fade visually finishes, not what a given position picks.) */
.snr-colorwheel {
  position: relative;
  flex: 0 0 auto;
  width: 96px;
  height: 96px;
  border-radius: 50%;
  background:
    radial-gradient(circle at center, #fff 0%, rgba(255, 255, 255, 0) 60%),
    conic-gradient(red, yellow, lime, cyan, blue, magenta, red);
  border: 1px solid var(--snr-border);
  cursor: crosshair;
  touch-action: none;
}
.snr-colorwheel:focus-visible {
  outline: 2px solid var(--snr-accent);
  outline-offset: 2px;
}
.snr-colorwheel-puck {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 16px;
  height: 16px;
  margin: -8px 0 0 -8px;
  border-radius: 50%;
  border: 2px solid #fff;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.55), 0 1px 2px rgba(0, 0, 0, 0.4);
  pointer-events: none;
}
.snr-colorgrade-control {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.snr-colorgrade-control label {
  flex: 0 0 auto;
  width: 4.2rem;
  font-size: 0.78rem;
  text-transform: none;
  letter-spacing: normal;
  color: var(--snr-muted);
  margin-bottom: 0;
}
.snr-colorgrade-control input[type="range"] {
  flex: 1 1 auto;
  width: auto;
  min-width: 0;
}
.snr-colorgrade-control input[type="number"] {
  width: 2.75rem !important;
  flex: 0 0 2.75rem !important;
  min-width: 0;
  background: var(--snr-panel);
  color: var(--snr-text);
  border: 1px solid var(--snr-border);
  border-radius: 4px;
  padding: 0.2rem 0.3rem;
  font-size: 0.85rem;
  text-align: right;
}
/* Shared across all three ranges (see the PHP template), set apart from
   them with a divider the same way Lightroom's own Blending/Balance row
   sits below its three wheels rather than being read as a fourth range. */
.snr-colorgrade-blending {
  margin-top: 0.75rem;
  padding-top: 0.6rem;
  border-top: 1px solid var(--snr-border);
}

/* Basic panel: Treatment, then Exposure/Brightness/Contrast/Highlights/
   Shadows/Whites/Blacks, then Presence's Texture/Clarity/Dehaze/Vibrance/
   Saturation, split by plain dividers (see .snr-tone-row-divider below) —
   a flat list of rows inside the "Basic" section, not their own accordion
   items, since there's nothing more to reveal per row, so there's nothing
   to expand. No preview-toggle/reset per row either — the whole section
   shares just one of each, in its own accordion header. */
.snr-basic-rows {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}
.snr-tone-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.snr-tone-row-label {
  flex: 0 0 auto;
  width: 5rem;
  /* Small enough that "Temperature" (the longest label here) fits without
     ellipsis-truncating, like every shorter one already did. */
  font-size: 0.68rem;
  color: var(--snr-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.snr-tone-row input[type="range"] {
  flex: 1 1 auto;
  width: auto;
  min-width: 0;
}
.snr-tone-row input[type="number"] {
  width: 3rem !important;
  flex: 0 0 3rem !important;
  min-width: 0;
  background: var(--snr-panel);
  color: var(--snr-text);
  border: 1px solid var(--snr-border);
  border-radius: 4px;
  padding: 0.2rem 0.3rem;
  font-size: 0.85rem;
  text-align: right;
}
/* Splits the row list into sub-groups without a heading for each — under
   Treatment (separating it from the tone sliders below) and under Blacks
   and Dehaze (splitting Basic's seven, Presence's Texture/Clarity/Dehaze
   trio, and its own Vibrance/Saturation pair). */
.snr-tone-row-divider {
  padding-bottom: 0.55rem;
  border-bottom: 1px solid var(--snr-border);
}
/* Treatment has no slider and no label (it's the only row that doesn't —
   "Color/B&W" already says what it is), so unlike every other row it's
   centered rather than filling the row edge to edge. */
.snr-treatment-row {
  justify-content: center;
  flex-wrap: wrap;
  row-gap: 0.5rem;
}
.snr-treatment-row .snr-segmented {
  flex: 0 0 auto;
}
.snr-segmented {
  display: flex;
  flex: 1 1 auto;
  border: 1px solid var(--snr-border);
  border-radius: 4px;
  overflow: hidden;
}
.snr-segmented-option {
  position: relative;
  flex: 1 1 auto;
  text-align: center;
  padding: 0.3rem 0.5rem;
  font-size: 0.78rem;
  color: var(--snr-muted);
  cursor: pointer;
}
.snr-segmented-option:not(:first-child) {
  border-left: 1px solid var(--snr-border);
}
/* The radio itself stays in the DOM (keyboard/screen-reader operable),
   just visually hidden — the label's own text is the visible control. */
.snr-segmented-option input {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
  pointer-events: none;
}
.snr-segmented-option input:checked + span {
  color: var(--snr-accent);
}
.snr-segmented-option input:focus-visible + span {
  outline: 2px solid var(--snr-accent);
  outline-offset: 2px;
}

/* Named presets bar -- a select of saved looks plus save/delete/import/
   export icon buttons, next to the Color/B&W segmented control. Reuses
   .snr-zoom-btn/.snr-zoom-select/.snr-zoom-divider (all bare class
   selectors, not scoped to the zoom toolbar) so it matches that toolbar
   without duplicating its styles. */
.snr-preset-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
  /* flex-end made sense sharing a row with the Treatment segmented control
     (pushed to the far side of it); now that this is its own standalone
     panel row, it reads better hugging the left like everything else here
     does, so this is left at the flex default (flex-start) instead. */
}
.snr-preset-select {
  width: 118px;
  flex: 1 1 90px;
  max-width: 140px;
}
.snr-preset-bar .snr-zoom-btn-icon:disabled {
  opacity: 0.4;
  cursor: default;
}
.snr-preset-bar .snr-zoom-btn-icon:disabled:hover {
  border-color: var(--snr-border);
}

.snr-wb-baseline {
  margin-top: 0.6rem;
}
.snr-wb-baseline label {
  display: inline-block;
  font-size: 0.78rem;
  color: var(--snr-muted);
  text-transform: none;
  letter-spacing: normal;
  margin-right: 0.5rem;
}
.snr-wb-baseline input[type="number"] {
  width: 4.5rem;
  background: var(--snr-panel);
  color: var(--snr-text);
  border: 1px solid var(--snr-border);
  border-radius: 4px;
  padding: 0.2rem 0.3rem;
  font-size: 0.85rem;
}
.snr-wb-baseline span[aria-hidden] {
  margin-left: 0.3rem;
  font-size: 0.78rem;
  color: var(--snr-muted);
}
.snr-wb-baseline .snr-hint {
  margin-top: 0.35rem;
}

.snr-checkbox-label {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.85rem;
  text-transform: none;
  letter-spacing: normal;
  color: var(--snr-text);
}

/* Buttons */
.snr-actions {
  display: flex;
  gap: 0.6rem;
}
.snr-button {
  flex: 1;
  padding: 0.55rem 0.8rem;
  border-radius: 5px;
  border: 1px solid var(--snr-border);
  font-size: 0.85rem;
  cursor: pointer;
}
.snr-button-primary {
  background: var(--snr-accent);
  border-color: var(--snr-accent);
  color: #1a1408;
  font-weight: 600;
}
.snr-button-secondary {
  background: transparent;
  color: var(--snr-text);
}

.snr-status {
  min-height: 1.2em;
  font-size: 0.8rem;
  color: #e08a8a;
  margin: 0;
}
