/* Theme tokens. Dark is the default/base palette (this app was
   dark-only before the light/dark/system toggle - see js/theme.js) so
   :root carries the original dark values unchanged; :root[data-theme="light"]
   overrides them for light mode. js/theme.js always resolves the user's
   preference (light/dark/system, the latter following the OS
   prefers-color-scheme live) to a concrete 'light' or 'dark' and sets
   data-theme on <html> itself, so this file never needs its own
   prefers-color-scheme query.

   Not every color here is a token - a few are deliberately fixed across
   themes because they represent a concept rather than UI chrome: the
   canvas-transparency checkerboard and its surrounding backdrop, literal
   black/white paint swatches, the Matrix easter-egg green, and the
   destructive/success semantic colors (kept close to their dark values
   so they still read as "danger"/"success" in both themes). Search this
   file for the raw hex codes if you need to find those. */
:root {
  /* Tells the browser which native form-control/scrollbar rendering
     ("dark" = light-on-dark checkboxes, scrollbars, etc.) to use, so
     unstyled native chrome doesn't stay dark-styled after switching to
     the light theme (or vice versa). Kept next to the tokens it tracks. */
  color-scheme: dark;
  --color-bg: #1c1c1e;
  --color-surface: #2c2c2e;
  --color-surface-alt: #232325;
  --color-track: #3a3a3d;
  --color-border: #48484a;
  --color-text: #f2f2f2;
  --color-text-secondary: #a0a0a5;
  --color-text-tertiary: #9a9a9e;
  --color-text-muted: #6e6e73;
  --color-text-subtle: #d0d0d5;
  --color-text-subtle-2: #c0c0c5;
  --color-accent: #0a84ff;
  --color-accent-strong: #0a5cad;
  --color-accent-soft: rgba(10, 132, 255, 0.15);
  --color-danger: #ff453a;
  --color-success: #34c759;
  --color-overlay: rgba(28, 28, 30, 0.92);
  --color-overlay-soft: rgba(28, 28, 30, 0.75);

  /* Icon-button size scale (icon-button-size-system) - every icon-only
     button's width/height is meant to read from one of these five steps
     instead of a one-off literal. Values are the five distinct sizes
     already in ad hoc use across the app before this change (Layers
     panel row buttons, color picker popover header, Color Library
     header actions [now reassigned to XS - see .color-library-header-
     actions .icon-button], zoom controls, and the base .icon-button/tool
     rail/bottom-bar undo-redo), so adopting them as named steps changes
     no existing rendered size except where a selector below explicitly
     switches steps. XS is also this scale's accessibility floor - see
     that selector's own comment for why it doesn't go smaller. */
  --icon-size-xs: 1.4rem;
  --icon-size-s: 1.6rem;
  --icon-size-m: 1.8rem;
  --icon-size-l: 2.2rem;
  --icon-size-xl: 2.6rem;
}

:root[data-theme="light"] {
  color-scheme: light;
  --color-bg: #ececf0;
  --color-surface: #ffffff;
  --color-surface-alt: #e4e4e9;
  --color-track: #c6c6cc;
  --color-border: #a3a3a9;
  --color-text: #1c1c1e;
  --color-text-secondary: #6c6c70;
  --color-text-tertiary: #707074;
  --color-text-muted: #75757a;
  --color-text-subtle: #3c3c43;
  --color-text-subtle-2: #48484a;
  --color-accent: #007aff;
  --color-accent-strong: #0a5cad;
  --color-accent-soft: rgba(0, 122, 255, 0.12);
  --color-danger: #ff3b30;
  --color-success: #0f7a30;
  --color-overlay: rgba(255, 255, 255, 0.94);
  --color-overlay-soft: rgba(255, 255, 255, 0.8);
}

* {
  box-sizing: border-box;
}

html, body {
  height: 100%;
  margin: 0;
  /* Disables native pinch-zoom of the whole page (e.g. pinching over the
     right sidebar on iPad zoomed the entire app UI, not the canvas) -
     the viewport meta's user-scalable=no (index.html) is ignored by iOS
     Safari 10+ for accessibility, so this touch-action is the actual
     fix there; kept as pan-x pan-y (not none) so normal scrolling still
     works everywhere except the canvas, which sets its own stricter
     touch-action: none and handles pan/zoom entirely itself (see
     js/canvas-view.js). */
  touch-action: pan-x pan-y;
}

body {
  font-family: system-ui, sans-serif;
  background: var(--color-bg);
  color: var(--color-text);
}

.hidden {
  display: none !important;
}

.screen {
  height: 100%;
  display: flex;
  flex-direction: column;
}

/* Gallery screen */

#screen-gallery {
  position: relative;
  padding: 1rem;
  gap: 1rem;
  overflow-y: auto;
}

/* Bottom-right corner of the Gallery screen only - see index.html's
   comment for why. Deliberately tiny/low-contrast: a diagnostic, not UI
   chrome anyone's meant to notice unless they're looking for it. */
.version-badge {
  position: absolute;
  right: 0.6rem;
  bottom: 0.4rem;
  margin: 0;
  font-family: ui-monospace, monospace;
  font-size: 0.65rem;
  color: var(--color-border);
  pointer-events: none;
}

.gallery-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

.gallery-header h1 {
  margin: 0;
  font-size: 1.4rem;
}

.gallery-header .primary-button {
  margin-top: 0;
  width: auto;
  padding: 0.6rem 1rem;
  font-size: 1rem;
}

.gallery-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(7rem, 1fr));
  gap: 0.75rem;
}

.gallery-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 0.5rem;
  overflow: hidden;
  cursor: pointer;
}

.gallery-thumbnail {
  width: 100%;
  aspect-ratio: 1;
  object-fit: contain;
  background: #e2e2e2;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
  display: block;
}

.gallery-tile-name {
  padding: 0.4rem;
  font-size: 0.8rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.gallery-tile-delete {
  position: absolute;
  top: 0.3rem;
  right: 0.3rem;
  width: 1.8rem;
  height: 1.8rem;
  border: none;
  border-radius: 0.3rem;
  background: var(--color-overlay-soft);
  color: inherit;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  cursor: pointer;
  /* AUD-5: contain the icon-font-failure fallback text (see .icon-button). */
  overflow: hidden;
}

.gallery-tile-delete .material-symbols-outlined {
  font-size: 1.1rem;
}

.gallery-empty-state {
  color: var(--color-text-secondary);
  text-align: center;
  margin-top: 2rem;
}

/* New Canvas screen */

#screen-new-canvas {
  padding: 1rem;
  gap: 1rem;
  overflow-y: auto;
}

.field-group h2 {
  font-size: 0.9rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-secondary);
  margin-bottom: 0.5rem;
}

.preset-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.preset-button,
.tool-button {
  padding: 0.6rem 1rem;
  border: 1px solid var(--color-border);
  border-radius: 0.5rem;
  background: var(--color-surface);
  color: inherit;
  font-size: 1rem;
  /* Real <button>s default to cursor: default (not pointer) in the UA
     stylesheet - explicit pointer both signals clickability and is one of
     the heuristics WebKit uses to decide whether an element qualifies for
     the Apple Pencil hover effect on supporting iPads (confirmed via
     Playwright: computed cursor on a bare <button> is "default"). */
  cursor: pointer;
}

.preset-button.active,
.tool-button.active {
  background: var(--color-accent);
  border-color: var(--color-accent);
}

/* Hover feedback (button-hover-states) - every boxed button in the app
   shares this one base (.preset-button, .tool-button), so one rule pair
   covers all of them: topbar/tool-rail icons, Save/Cancel/Resize/Rotate
   text buttons, Color Library/Brushes action buttons, etc. :not(:disabled)
   - a disabled button darkening further on hover would read as if
   hovering had some effect. Active buttons get their own step (toward
   --color-accent-strong, the same token .active already darkens to
   elsewhere) instead of the plain surface shift, so the accent fill
   doesn't just look like it turned back to unselected. */
.preset-button:not(:disabled):hover,
.tool-button:not(:disabled):hover {
  background: var(--color-surface-alt);
}

.preset-button.active:not(:disabled):hover,
.tool-button.active:not(:disabled):hover {
  background: var(--color-accent-strong);
  border-color: var(--color-accent-strong);
}

.custom-size-row {
  display: flex;
  gap: 1rem;
  margin-top: 0.75rem;
}

.custom-size-row input {
  width: 5rem;
  padding: 0.4rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  color: inherit;
  border-radius: 0.4rem;
}

.primary-button {
  margin-top: auto;
  padding: 0.9rem;
  font-size: 1.1rem;
  border: none;
  border-radius: 0.6rem;
  background: var(--color-accent);
  color: white;
  cursor: pointer;
}

/* Workspace screen */

.material-symbols-outlined {
  font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
  font-size: 1.4rem;
  line-height: 1;
}

/* AUD-5: if js/icon-font-fallback.js detects (via the CSS Font Loading
   API) that the Material Symbols font never actually loaded, it flags
   <html class="icon-font-failed">. Without the font, every
   .material-symbols-outlined span renders its literal ligature text
   ("home", "undo", "expand_more", ...) - the fixed-size/overflow:hidden
   containment added to each icon-only button class above keeps that text
   from bleeding into neighbors, but a clipped fragment of garbled text is
   still worse than no icon at all. Hide it outright once confirmed
   failed; every icon button already has an aria-label and/or
   data-tooltip (js/workspace.js's tool-tooltip), so the control stays
   identifiable and accessible with a blank glyph slot. */
.icon-font-failed .material-symbols-outlined {
  visibility: hidden;
}

.workspace-screen {
  /* .screen's default column direction now holds: the topbar sits above
     everything, and .workspace-body (below) is its own row - tools
     sidebar beside the canvas/panels column, Photoshop-style. */
  height: 100%;
}

.workspace-topbar {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.4rem 0.5rem;
  background: var(--color-bg);
  border-bottom: 1px solid var(--color-surface);
  flex: none;
}

.workspace-topbar-spacer {
  flex: 1;
}

/* Symmetry toggle (#symmetry-toggle): a 4-state cycling control (off /
   horizontal / vertical / both), styled with the same .active accent
   treatment as every other topbar toggle plus a small letter badge (H/V/4)
   so the three "on" states stay visually distinguishable from each other,
   not just from off - see js/symmetry-ui.js. */
#symmetry-toggle {
  position: relative;
}

#symmetry-toggle::after {
  content: '';
  position: absolute;
  bottom: 0.15rem;
  right: 0.15rem;
  width: 0.85rem;
  height: 0.85rem;
  border-radius: 50%;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  color: var(--color-accent);
  font-size: 0.55rem;
  font-weight: 600;
  line-height: 0.8rem;
  text-align: center;
  opacity: 0;
  transition: opacity 150ms ease-out;
}

#symmetry-toggle[data-symmetry-mode='horizontal']::after {
  content: 'H';
  opacity: 1;
}

#symmetry-toggle[data-symmetry-mode='vertical']::after {
  content: 'V';
  opacity: 1;
}

#symmetry-toggle[data-symmetry-mode='both']::after {
  content: '4';
  opacity: 1;
}

.workspace-body {
  display: flex;
  flex-direction: row;
  flex: 1;
  min-height: 0;
}

.tools-sidebar {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: 0.5rem;
  background: var(--color-bg);
  border-right: 1px solid var(--color-surface);
  overflow-y: auto;
  flex: none;
}

/* Rectangle's Filled toggle - tool-scoped, bottom of the tools sidebar. */
.rectangle-options {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-top: 0.4rem;
  padding-top: 0.6rem;
  border-top: 1px solid var(--color-surface);
}

/* "1:1" proportion toggle - a plain text label (like the zoom preset
   buttons), not an icon - clearer than any icon at communicating "1:1"
   specifically, per feedback. */
.square-constraint-button {
  font-size: 0.8rem;
  font-weight: 600;
}

/* Rectangle Filled toggle's icon - raw SVG, not a font glyph (see
   index.html for why). currentColor so it follows the button's text
   color exactly like the font icons do (including the active/blue
   state via .tool-button.active). */
.rectangle-fill-icon {
  width: 1.3rem;
  height: 1.3rem;
  fill: currentColor;
}

/* Pencil/Eraser Size + Opacity - floats over the canvas's top-left
   corner (js/workspace.js positions its visibility, not its location -
   this is a fixed overlay, not part of the tools sidebar's flow), shown
   only while one of those two tools is active. */
.pencil-options {
  position: absolute;
  top: 0.6rem;
  left: 0.6rem;
  z-index: 10;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.6rem;
  padding: 0.6rem 0.5rem;
  background: var(--color-overlay);
  border: 1px solid var(--color-border);
  border-radius: 0.6rem;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
}

.pencil-options-row {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
}

/* Vertical range input via writing-mode, the modern standards-based
   approach (supported in current Firefox/Chrome/Safari) - not the older,
   WebKit-only -webkit-appearance: slider-vertical. Fully custom track +
   thumb (appearance: none) instead of the OS-native widget, which looks
   dated against the rest of this dark, flat UI. */
.vertical-slider {
  writing-mode: vertical-lr;
  direction: rtl;
  width: 1.1rem;
  height: 5.5rem;
  -webkit-appearance: none;
  appearance: none;
  background: transparent;
  cursor: pointer;
}

.vertical-slider::-webkit-slider-runnable-track {
  width: 3px;
  background: var(--color-track);
  border-radius: 3px;
}

.vertical-slider::-moz-range-track {
  width: 3px;
  background: var(--color-track);
  border-radius: 3px;
}

.vertical-slider::-moz-range-progress {
  width: 3px;
  background: var(--color-accent);
  border-radius: 3px;
}

.vertical-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 0.9rem;
  height: 0.9rem;
  border-radius: 50%;
  background: var(--color-accent);
  border: 2px solid var(--color-text);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
  margin-left: -0.35rem;
}

.vertical-slider::-moz-range-thumb {
  width: 0.9rem;
  height: 0.9rem;
  border-radius: 50%;
  background: var(--color-accent);
  border: 2px solid var(--color-text);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
}

.pencil-options-readout {
  font-size: 0.65rem;
  color: var(--color-text-secondary);
  white-space: nowrap;
}

.tools-sidebar .tool-button {
  width: var(--icon-size-xl);
  height: var(--icon-size-xl);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  position: relative;
  /* AUD-5: see .icon-button's identical comment - same icon-font-
     failure containment for the tool rail, which uses bare .tool-button
     (not .icon-button) for its icon-only buttons. */
  overflow: hidden;
}

/* Custom tooltip, from data-tooltip/data-shortcut - not the native title
   attribute (which is slow to appear and can't be styled to match the
   app's dark theme). A single shared element positioned via JS
   (js/workspace.js), not a per-button ::after: .tools-sidebar's own
   overflow-y: auto implicitly clips overflow-x too (a CSS
   overflow-pairing rule), which would clip a ::after poking out to the
   right of the sidebar. Styled Figma-style: a dark pill, bold name, the
   keyboard shortcut in dimmer gray, with a small triangle pointing back
   at the button (left edge, since our toolbar is vertical - Figma's own
   horizontal toolbar points down instead). */
.tool-tooltip {
  position: fixed;
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  background: var(--color-surface);
  color: var(--color-text);
  font-weight: 600;
  border-radius: 0.4rem;
  padding: 0.45rem 0.7rem;
  font-size: 0.9rem;
  /* Most tooltip text is a short label ("Undo", "Move layer up") that
     should hug its content, not stretch to some fixed width - shrink-
     to-fit (this element's default sizing) plus nowrap handles that.
     A few (the reference-image layer's disabled-state explanations,
     e.g. "Upload a new reference image to enable Original resolution
     mode") are full sentences though, so this also caps width and lets
     those specific ones wrap instead of producing a bubble wide enough
     to run off-screen. */
  max-width: 16rem;
  white-space: normal;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.1s ease;
  z-index: 50;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
}

.tool-tooltip.visible {
  opacity: 1;
}

.tool-tooltip::after {
  content: '';
  position: absolute;
  right: 100%;
  top: 50%;
  transform: translateY(-50%);
  border: 0.3rem solid transparent;
  border-right-color: var(--color-surface);
}

/* Top-bar buttons sit close together horizontally - a tooltip to the
   right would cover the next button. Shown below instead, arrow
   pointing up at the button. */
.tool-tooltip.below::after {
  right: auto;
  left: 50%;
  top: auto;
  bottom: 100%;
  transform: translateX(-50%);
  border-right-color: transparent;
  border-bottom-color: var(--color-surface);
}

/* .right-sidebar buttons sit near the viewport's right edge - a tooltip
   to the right (the default, tuned for the left .tools-sidebar) would
   render partly or fully off-screen. Shown to the left instead, arrow
   mirrored to point at the button from the tooltip's right edge. */
.tool-tooltip.left-side::after {
  right: auto;
  left: 100%;
  border-right-color: transparent;
  border-left-color: var(--color-surface);
}

.tool-tooltip-shortcut {
  color: var(--color-text-tertiary);
  font-weight: 500;
  font-size: 0.8rem;
}

.icon-button {
  display: flex;
  align-items: center;
  justify-content: center;
  /* XL by default (icon-button-size-system) - the un-overridden size for
     an icon-only button. Context selectors below (color-picker-popover-
     header, color-library-header-actions, zoom-controls, layer row
     buttons) override this to a smaller scale step where the surrounding
     UI is more compact. */
  width: var(--icon-size-xl);
  height: var(--icon-size-xl);
  padding: 0;
  cursor: pointer;
  /* AUD-5: if the Material Symbols icon font fails to load (blocked
     network, offline, ad-blocker), the .material-symbols-outlined span
     renders its literal ligature text ("home", "undo", etc.) instead of
     a glyph. Without this, that text is wider than the fixed box above
     and bleeds into neighboring buttons. This clips it to "blank-ish
     button" instead - see .icon-font-failed below for hiding the text
     outright once detected. Every .icon-button-classed element inherits
     this from here, including the width/height overrides further down
     this file (color-picker-popover-header, color-library-header-
     actions, zoom-controls, bottom-bar-group) since none of them unset
     overflow. */
  overflow: hidden;
}

/* Hover feedback (button-hover-states) - covers every icon-only button
   carrying this class, boxed or bare: bare ones (add-layer-button, the
   panel-header-actions row, the Layers panel's per-row buttons, close
   buttons) had no background at rest, so this is what makes them read
   as interactive at all; already-boxed ones (.tool-button.icon-button
   combos) already get a hover background from .tool-button:hover above
   - the border-radius here just applies the same corner treatment
   bare buttons get, in case it differs momentarily from .tool-button's
   own 0.5rem at rest. */
.icon-button:not(:disabled):hover {
  background: var(--color-surface-alt);
  border-radius: 0.3rem;
}

/* iOS 26 / Apple Pencil-style "magnetic hover" for the top bar
   (openspec/changes/adopt-magnetic-hover-topbar) - see
   js/magnetic-hover.js. Position/scale and transition live in CSS;
   magnetic-hover.js only ever sets the --pull-x/--pull-y/--pull-scale
   custom properties and toggles the .magnetic-active class (on at most
   one button at a time - see that change's "Exclusive activation"
   requirement). No glow/halo - real iOS 26 doesn't have one either, per
   feedback. */
.magnetic-hover {
  transition: transform 150ms ease-out;
}

.magnetic-hover.magnetic-active {
  transform: translate(var(--pull-x, 0), var(--pull-y, 0)) scale(var(--pull-scale, 1));
}

.workspace-main {
  display: flex;
  flex-direction: column;
  flex: 1;
  /* A real floor, not 0 - the canvas and palette row need enough room to
     stay usable even on a narrow window; .right-sidebar shrinks first
     (see its clamp() above), and only once it's already at its own
     minimum does workspace-main get this floor instead of continuing to
     shrink toward zero. */
  min-width: 12rem;
  height: 100%;
}

.canvas-container {
  position: relative;
  flex: 1;
  overflow: hidden;
  background: #e2e2e2;
  touch-action: none;
  /* (reference-image-original-resolution) An 'original'-mode reference
     layer's blend mode is applied via CSS mix-blend-mode on
     .reference-original-overlay (js/canvas-view.js's render()), unlike
     every other layer's blending, which stays contained inside a single
     canvas's own globalCompositeOperation. mix-blend-mode blends against
     everything painted earlier in the element's containing stacking
     context, not just its DOM siblings - without isolation: isolate here,
     a non-'normal' blend mode (multiply/screen/overlay, already settable
     via the layer blend-mode dropdown) could blend against content
     outside this container instead of just #workspace-canvas/
     #aboveCanvasEl. isolation: isolate scopes it back to this container.
  */
  isolation: isolate;
}

/* (6-add-tile-seamless-preview) The 3x3 tile-preview group's own
   positioned ancestor: #workspace-canvas and its overlay siblings
   (above-layer canvas, reference-image overlay, selection overlay - all
   still appended here by js/canvas-view.js, position:absolute;left:0;
   top:0 against this element exactly as they were against
   .canvas-container before) plus the 8 copy canvases are all direct
   children. transform-origin stays the default-matching '0 0' (js/
   canvas-view.js's constructor) so #applyTransform()'s translate/scale
   here reproduces the exact on-screen position #workspace-canvas used to
   compute for itself alone - the copies (positioned via left/top set in
   resetView(), offset by +-1 canvas CSS width/height) ride along with
   the same single transform, scaling/panning in lockstep with no zoom-
   math changes needed anywhere else. */
.workspace-tile-wrapper {
  position: absolute;
  left: 0;
  top: 0;
}

/* Read-only surrounding copies (js/canvas-view.js blits the same
   composited ImageData onto these whenever tile-preview is on); left/
   top/width/height are set in resetView() (one canvas-CSS-pixel-width/
   height away from #workspace-canvas in each direction, mirroring
   #aboveCanvasEl's own same-size-sibling sizing). Hidden (and, per
   CanvasView#render(), not even repainted) unless
   .workspace-tile-wrapper.tile-preview-active. No pointer events of
   their own - out-of-bounds input on a copy is simply ignored, same as
   anywhere else outside #workspace-canvas. */
.tile-preview-copy {
  position: absolute;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
  pointer-events: none;
  visibility: hidden;
  background-image: repeating-conic-gradient(#cfcfcf 0% 25%, #e8e8e8 0% 50%);
  background-position: 0 0;
}

.workspace-tile-wrapper.tile-preview-active .tile-preview-copy {
  visibility: visible;
}

#workspace-canvas {
  position: absolute;
  left: 0;
  top: 0;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
  touch-action: none;
  cursor: crosshair;
  /* Shows through the canvas's own transparent pixels, so transparency in
     the artwork itself reads as a checkerboard, distinct from the flat
     #e2e2e2 area surrounding the canvas. background-size is set in
     canvas-view.js to match the current per-pixel CSS scale, so each
     checker square is exactly one artwork pixel. */
  background-image: repeating-conic-gradient(#cfcfcf 0% 25%, #e8e8e8 0% 50%);
  background-position: 0 0;
}

/* Hand tool cursor states, toggled by CanvasView#setPanMode/#onPointerDown
   (js/canvas-view.js) - a custom paw cursor (not the OS-native open/closed
   hand) while idle, swapping to a "picked up" paw while an actual pan drag
   is in progress. Falls back to grab/grabbing if the SVG cursor image ever
   fails to load. */
#workspace-canvas.pan-mode {
  cursor: url('assets/cursors/pets.svg') 12 12, grab;
}

#workspace-canvas.pan-mode.panning {
  cursor: url('assets/cursors/pets-picked.svg') 12 12, grabbing;
}

/* Move tool: a standard browser cursor keyword is enough (no custom
   artwork needed the way Hand's paw cursors are) - toggled by
   CanvasView#setMoveMode. */
#workspace-canvas.move-mode {
  cursor: move;
}

/* (reference-image-original-resolution) A second same-size pixel-buffer
   canvas, sibling to #workspace-canvas, for the layer-stack segment above
   an 'original'-mode reference layer - see CanvasView#render(). Same
   position/crispness as #workspace-canvas; no cursor/background rules
   (those stay owned by #workspace-canvas, the one element pointer events
   and the transparency checkerboard are anchored to) and no pointer
   events of its own, so drawing/panning interaction is unaffected by it
   sitting visually on top. */
.workspace-canvas-above-layer {
  position: absolute;
  left: 0;
  top: 0;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
  pointer-events: none;
}

/* (reference-image-original-resolution) An 'original'-mode reference
   layer's on-screen rendering - deliberately NOT pixelated (that's the
   entire point of this mode: the browser's own smooth bitmap scaling
   from the source's native resolution, never round-tripped through the
   canvas's tiny fixed-size pixel buffer). object-fit: contain matches
   the pixelated mode's own "fit, centered, preserve aspect ratio,
   never crop" behavior (js/image-import.js's fitImageToCanvas), just
   expressed as a CSS box constraint instead of a downscaled buffer. */
.reference-original-overlay {
  position: absolute;
  left: 0;
  top: 0;
  object-fit: contain;
  pointer-events: none;
}

.selection-overlay {
  position: absolute;
  left: 0;
  top: 0;
  outline: 1px dashed var(--color-accent);
  outline-offset: -1px;
  background: var(--color-accent-soft);
  pointer-events: none;
}

/* Popover, not a docked bottom panel - position:fixed + JS (each user's
   own positionPanel - js/export.js's for the Export popover this class
   is shared with; js/canvas-settings.js for the Canvas Settings popover
   this class was originally built for), same clamped-to-viewport pattern
   as .color-picker-popover. Its DOM location no longer matters for layout
   since it's taken out of flow here. */
.canvas-settings-panel {
  position: fixed;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  width: 15rem;
  padding: 0.6rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 0.5rem;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
  z-index: 60;
}

.canvas-settings-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.canvas-settings-header-row {
  justify-content: space-between;
}

.canvas-settings-row .tool-button {
  display: flex;
  align-items: center;
  gap: 0.3rem;
}

.canvas-settings-row h2 {
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-secondary);
  margin: 0;
}

.canvas-settings-row label {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.85rem;
}

.canvas-settings-row input[type="number"] {
  width: 4.5rem;
  padding: 0.35rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  color: inherit;
  border-radius: 0.4rem;
}

/* Canvas Settings' Name field - its only user. */
.canvas-settings-name-label {
  flex: 1;
}

.canvas-settings-name-label input[type="text"] {
  flex: 1;
  min-width: 0;
  padding: 0.4rem 0.5rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  color: inherit;
  border-radius: 0.4rem;
  font-size: 0.9rem;
}

/* Export popover - reuses .canvas-settings-panel's base popover styling
   (position/size/background/border), just its own content rows below. */
.export-scale-row,
.export-format-row {
  gap: 0.4rem;
}

.export-scale-option,
.export-format-option {
  flex: 1;
  text-align: center;
}

.export-scale-option.active,
.export-format-option.active {
  border-color: var(--color-accent);
  background: var(--color-accent-soft);
}

#export-transparent-label.disabled {
  opacity: 0.5;
}

.export-download-button {
  flex: 1;
  justify-content: center;
  display: flex;
  align-items: center;
  gap: 0.3rem;
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: white;
}

/* drawing-timelapse-recording: #record-toggle's active state. Shares
   .tool-button.active's accent-fill styling (set alongside .recording in
   js/workspace.js) so "recording is on" still reads correctly without the
   pulse below - color alone (a red dot) would be an insufficient signal,
   and prefers-reduced-motion users get no animation at all. */
.tool-button.recording {
  position: relative;
}

.tool-button.recording::after {
  content: '';
  position: absolute;
  top: 4px;
  right: 4px;
  width: 0.4rem;
  height: 0.4rem;
  border-radius: 50%;
  background: #e5484d;
  box-shadow: 0 0 0 1px var(--color-bg);
}

@media (prefers-reduced-motion: no-preference) {
  .tool-button.recording::after {
    animation: timelapse-record-pulse 1.4s ease-in-out infinite;
  }
}

@keyframes timelapse-record-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.35;
  }
}

/* Timelapse review popover - reuses .canvas-settings-panel's base popover
   styling (position/size/background/border), same as the Export popover
   above; just its own content rows here. */
.timelapse-frame-count {
  font-size: 0.85rem;
  color: var(--color-text-secondary);
}

#timelapse-speed-slider {
  flex: 1;
  accent-color: var(--color-accent);
}

.timelapse-speed-readout {
  font-size: 0.8rem;
  color: var(--color-text-secondary);
  white-space: nowrap;
}

/* Right-side sidebar (Layers + Brushes) — a fixed-width column, sibling of
   workspace-main. Layers is always relevant (own show/hide toggle in the
   bottom bar); Brushes is scoped to the Brush tool (js/workspace.js) —
   each panel manages its own visibility and its own vertical scrolling. */
.right-sidebar {
  /* Shrinks on narrower viewports instead of staying a fixed 13rem —
     on a narrow window a fixed-width sidebar could squeeze
     workspace-main (and the palette row inside it) down to almost
     nothing. See .workspace-main's min-width below for the other half
     of this fix. */
  width: clamp(8rem, 22vw, 13rem);
  height: 100%;
  flex: none;
  display: flex;
  flex-direction: column;
  background: var(--color-bg);
  border-left: 1px solid var(--color-surface);
  overflow: hidden;
}

/* AUD-11: #right-sidebar-toggle collapses this via width (not .hidden's
   display:none, which can't be animated and made workspace-main's
   reflow an instant snap) - shrinking to 0 lets workspace-main (a flex
   sibling) grow into the freed space smoothly across the same
   transition instead of jumping. border-left-color also fades so the
   1px seam doesn't linger as a visible sliver at width 0. JS
   (setRightSidebarVisible in js/workspace.js) also sets `inert` while
   collapsed, since width:0 alone doesn't remove the now-invisible
   content from tab order. */
.right-sidebar.right-sidebar-collapsed {
  width: 0;
  border-left-color: transparent;
}

@media (prefers-reduced-motion: no-preference) {
  .right-sidebar {
    transition: width 0.2s ease, border-left-color 0.2s ease;
  }
}

/* Layers panel (add/delete/reorder/rename/visibility/blend-mode/
   opacity/background/reference-image/merge, plus its shared
   .panel-header* base classes also used by Color Library below). */

/* Bottom-most section (see .right-sidebar's order comment in index.html)
   - fills whatever's left after Color Library's bounded height and
   Brushes' own cap, mirroring Color Library's old flex:1 role. */
.layers-panel {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  border-top: 1px solid var(--color-surface);
  display: flex;
  flex-direction: column;
}

/* Collapsed: only the header's name/chevron row stays visible (see
   .panel-header/.layers-panel-body below) - the panel itself shrinks to
   that row's content height instead of continuing to claim flex space.
   The action-icons row collapses away too (panel-header-design), same
   as .color-library-panel.collapsed below - a collapsed accordion
   header shows just the title, not its toolbar. */
.layers-panel.collapsed {
  flex: none;
}

.layers-panel.collapsed .panel-header-actions {
  display: none;
}

.layers-panel-header {
  /* Row layout (display/flex-direction/gap) comes from the shared
     .panel-header (panel-header-design) - this rule only owns what's
     specific to the Layers panel's header. */
  padding: 0.4rem 0.5rem;
  flex: none;
}

.layers-panel-body {
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow-y: auto;
}

.layers-panel.collapsed .layers-panel-body {
  display: none;
}

.layers-panel-header h2 {
  flex: 1;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-secondary);
  margin: 0;
}

/* XS (icon-button-size-system) and no button chrome - shared by every
   boxed action icon in a panel-header-actions row (the reference-image
   button here, the 5 Color Library buttons below). .tool-button in the
   selector deliberately excludes #add-layer-button, which isn't boxed
   to begin with (it has its own bare/S-size rule above) and shouldn't
   pick up this XS override. background/border reset matches
   .layer-visibility-toggle/.layer-delete-button's identical "glyph, not
   a button" pattern - .icon-button alone doesn't reset .tool-button's
   border/background, only removing .tool-button itself does. */
.panel-header-actions .tool-button.icon-button {
  width: var(--icon-size-xs);
  height: var(--icon-size-xs);
  background: transparent;
  border: none;
}

/* This rule's own background: transparent above and the shared
   .icon-button:not(:disabled):hover rule (button-hover-states) are
   equal specificity (0,3,0 each) - as the later rule in the file, this
   one would otherwise win even on hover and silently cancel the hover
   background. Repeating it here, scoped to :hover, restores it. */
.panel-header-actions .tool-button.icon-button:not(:disabled):hover {
  background: var(--color-surface-alt);
}

/* 1.1rem, matching .layer-visibility-toggle/.layer-delete-button's own
   glyph-in-XS-box ratio elsewhere in this sidebar (icon-button-size-
   system) - was 1rem on Color Library's buttons only, leaving a visibly
   bigger gap around the glyph than that established ratio (~25% more),
   and unset on the Layers panel's reference-image button, which fell
   back to .material-symbols-outlined's default 1.4rem and nearly filled
   its XS box edge-to-edge. One shared rule fixes both. */
.panel-header-actions .tool-button.icon-button .material-symbols-outlined {
  font-size: 1.1rem;
}

/* Add-layer button: S size, no surrounding button chrome - deliberately
   bare .icon-button (no .tool-button) so it reads as a plain glyph next
   to the reference-image button's boxed icon rather than a second
   button. background/border/color reset matches .layer-visibility-
   toggle/.layer-delete-button's identical "glyph, not a button" pattern
   below - .icon-button alone doesn't reset the browser's default button
   chrome, only .tool-button does. */
#add-layer-button.icon-button {
  width: var(--icon-size-s);
  height: var(--icon-size-s);
  background: transparent;
  border: none;
  color: inherit;
}

/* The ID selector above outranks the shared .icon-button:hover rule
   (button-hover-states) regardless of source order, so its background:
   transparent wins even while hovering unless repeated here at the
   same ID specificity. */
#add-layer-button.icon-button:not(:disabled):hover {
  background: var(--color-surface-alt);
}

/* Dropping .tool-button above also drops .tool-button:disabled's dimming
   (workspace.js disables this button at the 8-layer cap) - replaced
   here so hitting the cap still reads as disabled, not just inert. */
#add-layer-button.icon-button:disabled {
  opacity: 0.4;
}

/* Real image asset (assets/icons/layers_add.svg), not a font glyph or
   hand-copied inline markup - see index.html's comment on #add-layer-
   button. Rendered via CSS mask rather than <img> so it still recolors
   with currentColor like every other icon button here (light/dark
   theme, hover, disabled dimming) - an <img>'s pixels are opaque to
   CSS, fixed at whatever colors the source file happens to use.
   mask-mode: alpha (not the mask default, luminance) matters here
   specifically: the source file draws its glyph in near-black with a
   white accent for its own two-tone display, and under luminance
   masking dark pixels read as "mostly transparent" - exactly backwards
   from what a mask needs. Alpha mode goes off which pixels have ink at
   all, regardless of what color that ink is, which is what actually
   reproduces the glyph's shape. Sized like .rectangle-fill-icon
   (smaller than the S 1.6rem button box - a filled shape with no font
   side-bearing needs more headroom than a font glyph at the same
   nominal size would). */
.layers-add-icon {
  display: block;
  width: 1.1rem;
  height: 1.1rem;
  background-color: currentColor;
  mask-image: url('assets/icons/layers_add.svg');
  mask-mode: alpha;
  mask-repeat: no-repeat;
  mask-position: center;
  mask-size: contain;
  -webkit-mask-image: url('assets/icons/layers_add.svg');
  -webkit-mask-repeat: no-repeat;
  -webkit-mask-position: center;
  -webkit-mask-size: contain;
}

/* Shared by .layers-panel-header and .color-library-header - the whole
   block is a click target that collapses/expands the panel down to just
   this header (Photoshop-accordion style), not only the chevron icon.
   Interactive controls inside it (the header-actions icon buttons) stop
   propagation in JS so clicking them doesn't also toggle the collapse.
   Two stacked rows (panel-header-design) - the name/chevron row on top,
   the action icons on their own row below - instead of everything
   crammed into one row, which is what previously forced the action
   icons down to an XS/cramped size to keep from overflowing the
   sidebar (see .color-library-header-actions .icon-button's comment). */
.panel-header {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  cursor: pointer;
}

/* Row 1: panel name left, collapse chevron pinned to the right edge (the
   name's flex: 1 - set on .layers-panel-header h2/.color-library-header
   h2 - absorbs the row's slack so the chevron doesn't have to). */
.panel-header-top {
  display: flex;
  align-items: center;
  gap: 0.3rem;
}

/* Row 2: the header's action icon buttons, wrapping onto more lines
   instead of overflowing if the sidebar is narrower than the full set. */
.panel-header-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.panel-collapse-chevron {
  flex: none;
  font-size: 1.1rem;
  color: var(--color-text-secondary);
  transition: transform 0.15s ease;
  /* AUD-5: unlike this file's other icon-only elements, this one had no
     fixed footprint at all - just font-size - so icon-font-failure
     fallback text ("expand_more") would grow the box and push the rest
     of the panel-header row. Pin it to a square matching the font-size
     and clip anything wider. */
  width: 1.1rem;
  height: 1.1rem;
  overflow: hidden;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* Collapsed state rotates the chevron from pointing down (expanded) to
   pointing right, the common accordion convention. */
.panel-header[aria-expanded="false"] .panel-collapse-chevron {
  transform: rotate(-90deg);
}

/* Photoshop-style toolbar: Blend mode + Opacity, editing whichever
   layer is active - not per-row (see syncLayersPanelToolbar in
   js/workspace.js). One row (see index.html's toolbar comment for why
   the prior two-row layout no longer applies): the Blend select is
   sized to its own text instead of the full row width, freeing room for
   the Opacity button on the same line. */
.layers-panel-toolbar {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.4rem 0.5rem;
  border-top: 1px solid var(--color-surface);
  border-bottom: 1px solid var(--color-surface);
  background: var(--color-surface-alt);
}

/* Takes the row's remaining width (Opacity next to it is sized to its
   own compact content instead - see .layers-panel-opacity-toggle).
   appearance: none + a custom background-image chevron, not the native
   OS control - Safari otherwise renders <select> with its own puffy
   chrome that ignores the dark background/border set here. */
.layers-panel-blend-select {
  width: auto;
  flex: 1 1 auto;
  min-width: 0;
  appearance: none;
  -webkit-appearance: none;
  background: var(--color-surface) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23a0a0a5' d='M7 10l5 5 5-5z'/%3E%3C/svg%3E") no-repeat right 0.3rem center / 1rem;
  background-color: var(--color-surface);
  color: inherit;
  border: 1px solid var(--color-border);
  border-radius: 0.3rem;
  font-size: 0.75rem;
  padding: 0.25rem 1.4rem 0.25rem 0.4rem;
}

/* Same control, light theme: the chevron above is baked into a data-URI
   (can't be parameterized with a CSS var inside the URL string), so the
   whole background shorthand is restated here with a darker chevron fill
   for contrast against the light surface. */
:root[data-theme="light"] .layers-panel-blend-select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%236c6c70' d='M7 10l5 5 5-5z'/%3E%3C/svg%3E");
}

/* Opens #layers-panel-opacity-popover on click - see openLayersOpacityPopover
   in js/workspace.js. Icon, not the word "Opacity" - reclaims space at
   the sidebar's narrowest clamped width (see .right-sidebar); a tooltip
   (data-tooltip) still names it, same icon-only convention the Pencil
   Size/Opacity sliders use. Sized to its own content (flex: none), not
   flex-grown to fill the row - Blend mode above takes the rest instead.
   appearance: none - Safari otherwise renders <button> with its own
   native chrome (visibly lighter background, rounder corners) that
   overrides the dark background/border set here. */
.layers-panel-opacity-toggle {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  flex: none;
  appearance: none;
  -webkit-appearance: none;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 0.3rem;
  color: inherit;
  font: inherit;
  padding: 0.25rem 0.4rem;
  cursor: pointer;
}

/* Hover feedback (button-hover-states). */
.layers-panel-opacity-toggle:hover {
  background: var(--color-surface-alt);
}

.layers-panel-opacity-icon {
  flex: none;
  font-size: 1rem;
  color: var(--color-text-secondary);
}

.layers-panel-opacity-readout {
  flex: none;
  font-size: 0.75rem;
  white-space: nowrap;
}

/* Popover holding the actual slider + a number field for direct entry -
   position via fixed + JS, clamped to the viewport, same pattern as
   #color-picker-popover (see openColorPicker/openLayersOpacityPopover in
   js/workspace.js). Lives outside .right-sidebar (index.html) so that
   panel's own overflow-y:auto can't clip it. */
.opacity-popover {
  position: fixed;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 0.4rem;
  padding: 0.5rem 0.6rem;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
  z-index: 50;
}

.layers-panel-opacity-slider {
  width: 8rem;
}

.layers-panel-opacity-number {
  width: 3rem;
  background: var(--color-bg);
  color: inherit;
  border: 1px solid var(--color-border);
  border-radius: 0.3rem;
  padding: 0.2rem 0.3rem;
  font-size: 0.8rem;
}

.layers-panel-list {
  display: flex;
  flex-direction: column;
}

/* One compact row per layer - thumbnail, visibility, name, small
   reorder/delete icons. Blend mode/Opacity moved to the shared toolbar
   above (see .layers-panel-toolbar), matching Photoshop's own panel,
   where those controls apply to the selected layer rather than living
   inline in every row. */
.layer-row {
  display: flex;
  align-items: center;
  /* flex-wrap (icon-button-size-system) - a normal layer row (visibility,
     thumbnail, name, reorder x2, delete) fits on one line even at the
     sidebar's narrowest clamped width, but the reference image layer row
     adds a lock icon and a smoothing toggle on top of that (see
     buildLayerRow in js/workspace.js) and no realistic spacing budget fits
     all 8 controls on one line at the sidebar's normal clamped width
     without shrinking icons below this scale's XS floor (see :root's
     --icon-size-xs comment). Wrapping - not overflow-x:auto's implicit
     scrollbar (the overflow-pairing rule, same bug this container already
     had) - keeps every control visible and reachable; only rows with more
     controls than fit end up two lines tall. */
  flex-wrap: wrap;
  gap: 0.4rem;
  padding: 0.3rem 0.4rem;
  border-top: 1px solid var(--color-surface);
  cursor: pointer;
}

/* Photoshop's own selected-row highlight - a solid accent bar, not just
   a subtly darker background. */
.layer-row.active {
  background: var(--color-accent-strong);
}

.layer-row.active .layer-name-input,
.layer-row.active .layer-reorder-button,
.layer-row.active .layer-delete-button,
.layer-row.active .layer-visibility-toggle,
.layer-row.active .layer-lock-icon {
  color: #fff;
}

/* merge-layers: marked (multi-selected) layers, distinct from the single
   active-layer highlight above - a soft fill plus an inset accent border,
   so a row can show both active (solid fill) and marked (border) at once
   without one obscuring the other. */
.layer-row.marked {
  background: var(--color-accent-soft);
  box-shadow: inset 0 0 0 2px var(--color-accent);
}

/* A row that's both active and marked: keep the active row's solid fill
   (and white text, from the .active rules above) rather than letting
   .marked's lighter background win on equal specificity - only the
   marked border needs to show through. Must repeat `background` here
   (not just `box-shadow`), or cascade order alone decides which
   background wins and the white active text ends up on the light
   marked background instead. */
.layer-row.active.marked {
  background: var(--color-accent-strong);
  box-shadow: inset 0 0 0 2px #fff;
}

.layer-visibility-toggle {
  flex: none;
  width: var(--icon-size-xs);
  height: var(--icon-size-xs);
  background: transparent;
  border: none;
  color: inherit;
  font-size: 1rem;
  padding: 0;
  cursor: pointer;
  /* AUD-5: contain the icon-font-failure fallback text (see .icon-button). */
  overflow: hidden;
}

.layer-visibility-toggle .material-symbols-outlined,
.layer-delete-button .material-symbols-outlined {
  font-size: 1.1rem;
}

/* Real per-layer pixel-content preview, Photoshop-style, not a generic
   placeholder (see buildLayerThumbnailCanvas). Flat light background
   behind it stands in for transparency, same convention the Gallery's
   own project thumbnails already use. */
.layer-thumbnail {
  flex: none;
  width: 2rem;
  height: 2rem;
  background: #e2e2e2;
  border: 1px solid var(--color-border);
  border-radius: 0.2rem;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
  object-fit: contain;
}

.layer-name-input {
  /* flex-basis (the "4rem") lowered from 4rem to 2rem, and min-width
     from 3rem to 2rem (icon-button-size-system). Two separate reasons:
     min-width - the reference image layer row adds a lock icon +
     smoothing toggle alongside the usual visibility/thumbnail/reorder/
     delete controls, and a 3rem floor left no room to shrink under that
     extra load at the sidebar's normal clamped width, forcing .layer-row
     wider than its container (the same overflow-pairing problem - see
     .color-library-header-actions .icon-button's comment). flex-basis -
     .layer-row's flex-wrap (added alongside this) decides whether a row
     needs a second line using each item's flex-basis, not its post-
     shrink size, so a 4rem basis was pushing ordinary (non-reference)
     rows onto two lines even though they fit on one once name-input
     actually shrinks. 2rem for both keeps ordinary rows on one line
     while still leaving enough width to read a few characters of a
     layer name while editing - it grows via flex-grow to fill any
     leftover space on its line either way. */
  flex: 1 1 2rem;
  min-width: 2rem;
  background: transparent;
  border: 1px solid transparent;
  color: inherit;
  font-size: 0.85rem;
  padding: 0.15rem 0.3rem;
  border-radius: 0.3rem;
}

.layer-name-input:focus-visible {
  border-color: var(--color-accent);
  outline: none;
}

/* Background layer (2g-background-layer) - locked in stacking position,
   see buildLayerRow in js/workspace.js. `span.layer-lock-icon` (not just
   `.layer-lock-icon`) (icon-button-size-system) - this element also
   carries the bare `.material-symbols-outlined` class, and the Google
   Fonts icon stylesheet (index.html's link, loaded after this file) ships
   its own `.material-symbols-outlined { font-size: 24px }` default at the
   same specificity; being later in the cascade, it silently won over this
   rule's 0.9rem, rendering the lock icon at 24px instead of ~14px - found
   while fixing the reference image layer row's overflow, where the extra
   ~10px mattered. The added element-type qualifier raises this selector's
   specificity just enough to win instead. */
span.layer-lock-icon {
  flex: none;
  font-size: 0.9rem;
  color: var(--color-text-secondary);
}

/* Reference image layer only (reference-image-original-resolution) -
   toggles Pixelated vs. Original resolution mode, see buildLayerRow in
   js/workspace.js. Sized identically to .layer-reference-smoothing-
   toggle, which it sits next to (visible only in Pixelated mode). */
.layer-reference-mode-toggle {
  flex: none;
  width: var(--icon-size-xs);
  height: var(--icon-size-xs);
  background: transparent;
  border: none;
  color: inherit;
  font-size: 1rem;
  padding: 0;
  cursor: pointer;
  overflow: hidden;
}

.layer-reference-mode-toggle .material-symbols-outlined {
  font-size: 1.1rem;
}

.layer-reference-mode-toggle:disabled {
  opacity: 0.3;
}

/* Reference image layer only - toggles smoothed vs. blocky downscale on
   the last upload, see buildLayerRow in js/workspace.js. Sized like
   .layer-visibility-toggle, which it sits next to. */
.layer-reference-smoothing-toggle {
  flex: none;
  width: var(--icon-size-xs);
  height: var(--icon-size-xs);
  background: transparent;
  border: none;
  color: inherit;
  font-size: 1rem;
  padding: 0;
  cursor: pointer;
  overflow: hidden;
}

.layer-reference-smoothing-toggle .material-symbols-outlined {
  font-size: 1.1rem;
}

.layer-reference-smoothing-toggle:disabled {
  opacity: 0.3;
}

.layer-row-actions {
  display: flex;
  align-items: center;
  /* Tightened from 0.3rem (icon-button-size-system) to reclaim a little
     more width for the reference image layer row's extra controls - see
     .layer-name-input's min-width comment above for the full picture. */
  gap: 0.2rem;
  flex: none;
}

.layer-reorder-button,
.layer-delete-button {
  flex: none;
  width: var(--icon-size-xs);
  height: var(--icon-size-xs);
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  color: inherit;
  font-size: 0.9rem;
  padding: 0;
  cursor: pointer;
  /* AUD-5: contain the icon-font-failure fallback text (see .icon-button). */
  overflow: hidden;
}

.layer-reorder-button .material-symbols-outlined {
  font-size: 0.95rem;
}

.layer-reorder-button:disabled,
.layer-delete-button:disabled,
.layer-visibility-toggle:disabled {
  opacity: 0.3;
}

/* Hover feedback (button-hover-states) - the one icon-only class in this
   file that doesn't carry .icon-button (see that class's own hover rule,
   which already covers .layer-delete-button/.layer-visibility-toggle),
   so it needs its own copy of the same treatment. */
.layer-reorder-button:not(:disabled):hover {
  background: var(--color-surface-alt);
  border-radius: 0.3rem;
}

.palette-row {
  display: flex;
  gap: 0.35rem;
  padding: 0.5rem;
  overflow-x: auto;
  background: var(--color-bg);
}

.palette-swatch {
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 0.3rem;
  border: 2px solid var(--color-border);
  flex: none;
  padding: 0;
}

.palette-swatch.active {
  border-color: var(--color-accent);
}

/* Hover feedback (button-hover-states) - a border-color shift, not a
   background fill like the icon/tool buttons above, since a swatch's
   background IS the color it represents and can't be repurposed for
   hover without misrepresenting that color. :not(.active) so hovering
   a swatch never reads as "this one's no longer selected" by
   overwriting the accent border the .active rule above sets. */
.palette-swatch:not(.active):hover {
  border-color: var(--color-text-secondary);
}

.palette-swatch.rainbow-swatch {
  background: conic-gradient(red, yellow, lime, cyan, blue, magenta, red);
}

/* Foreground/Background (Photoshop-style) - lives in the tools sidebar,
   below the tool buttons, always visible (unlike the tool-scoped Pencil
   options below it). Stacked vertically to fit the narrow sidebar. */
.fg-bg-swatches {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  margin-top: 0.4rem;
  padding-top: 0.6rem;
  border-top: 1px solid var(--color-surface);
  flex: none;
}

.fg-bg-swatch-stack {
  position: relative;
  width: 2.6rem;
  height: 2.6rem;
  flex: none;
  margin: 0.15rem 0.15rem 0.3rem 0;
}

/* Buttons, not divs (clicking either opens the color-picker-popover
   below, targeting whichever one was clicked) - reset default button
   chrome back to a plain swatch. */
.fg-bg-swatch {
  position: absolute;
  width: 1.7rem;
  height: 1.7rem;
  border-radius: 0.25rem;
  border: 1px solid var(--color-border);
  padding: 0;
  cursor: pointer;
}

.bg-swatch {
  right: 0;
  bottom: 0;
  background: #ffffff;
}

.fg-swatch {
  left: 0;
  top: 0;
  background: #000000;
}

/* Hover feedback (button-hover-states) - border-color shift, same
   reasoning as .palette-swatch:hover above (background is the actual
   foreground/background color, can't be repurposed for hover). */
.fg-bg-swatch:hover {
  border-color: var(--color-text-secondary);
}

/* Small corner icons on the swatch stack (Photoshop-style), not
   full-size buttons stacked above/below it. */
.fg-bg-corner-button {
  position: absolute;
  width: 1.05rem;
  height: 1.05rem;
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: 50%;
  background: var(--color-bg);
  color: inherit;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  z-index: 1;
  /* AUD-5: contain the icon-font-failure fallback text (see .icon-button). */
  overflow: hidden;
}

.fg-bg-corner-button .material-symbols-outlined {
  font-size: 0.7rem;
}

.swap-corner {
  top: -0.2rem;
  right: -0.2rem;
}

.reset-corner {
  bottom: -0.2rem;
  left: -0.2rem;
}

/* Hover feedback (button-hover-states). */
.fg-bg-corner-button:hover {
  background: var(--color-surface-alt);
}

/* Color picker popover - opened by clicking the Foreground or Background
   swatch, not an always-visible inline row. Positioned via JS
   (js/workspace.js), same fixed-position/getBoundingClientRect pattern
   the tool tooltip already uses. */
.color-picker-popover {
  position: fixed;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 0.5rem;
  padding: 0.6rem;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
  z-index: 60;
  width: 13.5rem;
}

.color-picker-popover-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--color-text-subtle);
}

.color-picker-popover-header .icon-button {
  width: var(--icon-size-s);
  height: var(--icon-size-s);
}

.color-picker-popover input[type='color'] {
  width: 100%;
  height: 1.9rem;
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: 0.3rem;
  background: none;
}

.color-picker-hex-row {
  position: relative;
  display: flex;
  align-items: center;
}

.color-picker-hex {
  width: 100%;
  padding: 0.35rem 0.5rem;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  color: inherit;
  border-radius: 0.4rem;
  font-size: 0.85rem;
}

.color-picker-copied {
  position: absolute;
  right: 0.4rem;
  font-size: 0.7rem;
  color: var(--color-success);
  background: var(--color-bg);
  padding: 0 0.3rem;
  pointer-events: none;
}

.color-picker-rgb-row {
  display: flex;
  gap: 0.35rem;
}

.color-picker-rgb {
  width: 0;
  flex: 1;
  padding: 0.35rem 0.4rem;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  color: inherit;
  border-radius: 0.4rem;
  font-size: 0.85rem;
}

/* Width/height explicitly reset here - #color-picker-popover lives inside
   #tools-sidebar (see index.html), so the fixed 2.6rem square from
   `.tools-sidebar .tool-button` (meant for the icon-only tool rail) would
   otherwise win the cascade on width/height (its selector doesn't set
   padding, so that part of #color-picker-add's rule already wins - but
   width/height need an explicit override here, not just higher specificity,
   since a property only cascades against rules that set that property).
   #color-picker-generate-ramp (7-add-palette-color-ramp-generator) shares
   the same override, same reasoning. */
#color-picker-add,
#color-picker-generate-ramp {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.3rem;
  width: 100%;
  height: auto;
  padding: 0.5rem 0.4rem;
  white-space: nowrap;
  font-size: 0.85rem;
}

/* Brushes panel — Photoshop-Brushes-panel-style: title, spacing control,
   thumbnail grid, bottom toolbar with add/delete icon buttons. */

/* Second section of the right-sidebar (see .right-sidebar above), shown
   only while the Brush tool is active — see js/workspace.js. Capped at
   35% like Layers, so it doesn't crowd out Color Library above or Layers
   below when shown. */
.brushes-panel {
  flex: none;
  max-height: 35%;
  overflow-y: auto;
}

/* Color Library panel + its import/ramp preview popovers. */
/* Top-most section (see .right-sidebar's order comment in index.html) -
   bounded to a fixed height range regardless of window height, rather
   than growing to fill remaining space (Layers does that now instead -
   see .layers-panel). */
.color-library-panel {
  flex: none;
  max-height: clamp(200px, 30vh, 250px);
  overflow-y: auto;
  border-bottom: 1px solid var(--color-surface);
  display: flex;
  flex-direction: column;
}

.color-library-header {
  /* Row layout (display/flex-direction/gap) comes from the shared
     .panel-header (panel-header-design) - this rule only owns what's
     specific to the Color Library header. */
  padding: 0.4rem 0.5rem;
  flex: none;
}

.color-library-header h2 {
  flex: 1;
  /* min-width: 0 lets this shrink below its text's natural width instead
     of forcing the header row (and its 5 icon buttons) wider than the
     sidebar at the clamp's narrower end (icon-button-size-system) - a
     flex item's default min-width is auto (its content size), which
     would otherwise win over flex:1's shrink. Truncates with an ellipsis
     rather than wrapping/clipping raw, so the label stays legible. */
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-secondary);
  margin: 0;
}

.color-library-body {
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow-y: auto;
}

/* Collapsed: only the header's name/chevron row stays visible (mirrors
   .layers-panel.collapsed). */
.color-library-panel.collapsed .color-library-body {
  display: none;
}

.color-library-panel.collapsed .panel-header-actions {
  display: none;
}

/* Row layout (display/gap/flex-wrap), sizing, chrome (XS, no border/
   background), and glyph size all come from the shared
   .panel-header-actions / .panel-header-actions .tool-button.icon-
   button rules above - these 5 buttons don't need their own override. */

.color-library-select {
  margin: 0 0.5rem 0.4rem;
  padding: 0.3rem 0.4rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  color: inherit;
  border-radius: 0.4rem;
  font-size: 0.8rem;
  flex: none;
}

/* Name input + Save/Cancel on one line - at the sidebar's narrowest
   clamped width (see .right-sidebar) the two buttons alone eat most of
   the row, leaving the flex:1 input too little space for its own
   placeholder text. flex-wrap lets the input drop to its own line above
   the buttons instead of clipping, same technique as
   .brush-editor-row:last-child's button-wrap comment above. */
.new-palette-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin: 0 0.5rem 0.4rem;
  flex: none;
}

.new-palette-row input {
  flex: 1;
  min-width: 6rem;
  padding: 0.3rem 0.4rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  color: inherit;
  border-radius: 0.4rem;
  font-size: 0.8rem;
}

.new-palette-row .tool-button {
  padding: 0.3rem 0.5rem;
  font-size: 0.75rem;
}

/* Import-palette popover (2o-image-import-refinements) - built on
   .canvas-settings-panel's base popover styling (position/size/
   background/border), same as .export-panel; position:fixed + JS
   (js/color-library-ui.js's positionPanelBelow), clamped to the
   viewport. Its DOM location no longer matters for layout since it's
   taken out of flow, same reasoning as .canvas-settings-panel's own
   comment. Rows inside reuse .canvas-settings-row's generic
   label/input[type=number] styling - only the swatch grid and the text
   input need their own rules here. */
.import-preview-panel {
  width: 16rem;
}

.import-preview-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(19px, 1fr));
  grid-auto-rows: 19px;
  align-content: start;
  gap: 0.25rem;
  /* Fixed, not max - a shorter preview (fewer rows) must reserve the
     same space as a full one, or the controls below still shift as the
     count changes (the bug this popover conversion was supposed to
     fix). Scrolls internally past that height instead of growing. */
  height: 9rem;
  overflow-y: auto;
}

.import-preview-panel input[type='text'] {
  flex: 1;
  min-width: 0;
  padding: 0.4rem 0.5rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  color: inherit;
  border-radius: 0.4rem;
  font-size: 0.9rem;
}

/* Smaller swatches (~19px, was ~26px) so more rows fit within the
   panel's now-bounded height (see .color-library-panel). */
.color-library-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(19px, 1fr));
  gap: 0.25rem;
  padding: 0 0.5rem 0.5rem;
  overflow-y: auto;
}

.color-library-swatch {
  width: 100%;
  aspect-ratio: 1;
  border-radius: 0.25rem;
  border: 2px solid var(--color-border);
  padding: 0;
  cursor: pointer;
}

.color-library-swatch.active {
  border-color: var(--color-accent);
}

/* Hover feedback (button-hover-states) - same reasoning as
   .palette-swatch:hover. */
.color-library-swatch:not(.active):hover {
  border-color: var(--color-text-secondary);
}

.color-library-empty {
  grid-column: 1 / -1;
  padding: 0 0.5rem 0.5rem;
  font-size: 0.75rem;
  color: var(--color-text-muted);
  margin: 0;
}

.brushes-panel-header {
  display: flex;
  align-items: center;
  padding: 0.4rem 0.5rem;
}

.brushes-panel-header h2 {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-secondary);
  margin: 0;
}

.brushes-panel-row {
  padding: 0 0.5rem 0.4rem;
}

.brushes-spacing-label {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.85rem;
}

.brushes-spacing-label input[type="number"] {
  width: 3.5rem;
  padding: 0.3rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  color: inherit;
  border-radius: 0.4rem;
}

.brushes-panel-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(2.75rem, 1fr));
  gap: 0.4rem;
  padding: 0 0.5rem 0.5rem;
}

.brush-swatch {
  width: 100%;
  aspect-ratio: 1;
  border-radius: 0.3rem;
  border: 2px solid var(--color-border);
  background: #ffffff;
  color: inherit;
  padding: 0.2rem;
  overflow: hidden;
  cursor: pointer;
}

.brush-swatch.active {
  border-color: var(--color-accent);
}

/* Hover feedback (button-hover-states) - same reasoning as
   .palette-swatch:hover. */
.brush-swatch:not(.active):hover {
  border-color: var(--color-text-secondary);
}

/* Preview of the brush pattern itself (black on white), not its name -
   drawn from brush.pixels at brush.width x brush.height and scaled up
   crisply, same idea as the canvas checkerboard's per-pixel rendering. */
.brush-swatch-preview {
  width: 100%;
  height: 100%;
  image-rendering: pixelated;
  display: block;
}

.brushes-panel-toolbar {
  display: flex;
  gap: 0.4rem;
  padding: 0.4rem 0.5rem;
  border-top: 1px solid var(--color-surface);
}

/* Brush editor — fixed 9x9 grid, click/drag to toggle cells on. */

.brush-editor-panel {
  padding: 0.6rem;
  background: var(--color-bg);
  border-top: 1px solid var(--color-surface);
  flex: none;
}

.brush-editor-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.5rem;
}

.brush-editor-row:last-child {
  margin-bottom: 0;
}

/* Title + Import icon button on one line (2o-image-import-refinements) -
   same space-between pattern as .canvas-settings-header-row. */
.brush-editor-header-row {
  justify-content: space-between;
}

.brush-editor-header-row h2 {
  margin: 0;
}

/* Clear/Cancel/Save row - the 13rem sidebar is too narrow to fit three
   default-padded tool-buttons on one line, so they wrap and shrink. */
.brush-editor-row:last-child {
  flex-wrap: wrap;
}

.brush-editor-row:last-child .tool-button {
  flex: 1 1 auto;
  padding: 0.5rem 0.4rem;
  font-size: 0.85rem;
}

.brush-editor-row input[type="text"] {
  flex: 1;
  padding: 0.4rem 0.5rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  color: inherit;
  border-radius: 0.4rem;
}

.brush-editor-size-label {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.85rem;
}

.brush-editor-size-label input[type="number"] {
  width: 3.5rem;
  padding: 0.3rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  color: inherit;
  border-radius: 0.4rem;
}

/* Cell size is set inline per grid (--brush-editor-cell-size) so a large
   custom brush (up to canvas size) still fits the panel; 1.6rem is just
   the fallback for the fixed-size default. */
.brush-editor-grid {
  display: grid;
  grid-template-columns: repeat(var(--brush-editor-cols, 9), var(--brush-editor-cell-size, 1.6rem));
  grid-template-rows: repeat(var(--brush-editor-rows, 9), var(--brush-editor-cell-size, 1.6rem));
  gap: 1px;
  background: var(--color-border);
  border: 1px solid var(--color-border);
  width: fit-content;
  margin-bottom: 0.5rem;
  touch-action: none;
}

.brush-editor-cell {
  width: var(--brush-editor-cell-size, 1.6rem);
  height: var(--brush-editor-cell-size, 1.6rem);
  background: var(--color-surface);
  border: none;
  padding: 0;
}

.brush-editor-cell.on {
  background: var(--color-accent);
}

.selection-controls {
  display: flex;
  gap: 0.4rem;
  padding: 0.5rem;
  background: var(--color-bg);
  border-top: 1px solid var(--color-surface);
}

.selection-controls .tool-button {
  flex: 1;
}

.bottom-bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.75rem;
  padding: 0.5rem;
  background: var(--color-bg);
  border-top: 1px solid var(--color-surface);
  overflow-x: auto;
}

.bottom-bar-group {
  display: flex;
  flex-wrap: nowrap;
  gap: 0.4rem;
  flex: none;
}

.bottom-bar-group .tool-button {
  flex: none;
  padding: 0.6rem 0.8rem;
  font-size: 0.85rem;
  white-space: nowrap;
}

/* Zoom controls - bottom-left group: -/+ icon buttons flanking a live
   percentage readout, plus the three preset buttons. */
.zoom-controls {
  align-items: center;
}

.zoom-controls .icon-button {
  width: var(--icon-size-l);
  height: var(--icon-size-l);
}

.zoom-controls .tool-button:not(.icon-button) {
  /* Explicit height (not just padding) to match .zoom-controls
     .icon-button's fixed 2.2rem - the -/+ icon buttons and these text
     presets (100%/Fit/Fill) sit in the same row and previously rendered
     at slightly different heights (padding + line-height doesn't equal
     a fixed height), a visible mismatch in the bottom bar. */
  display: flex;
  align-items: center;
  height: 2.2rem;
  padding: 0 0.6rem;
  font-size: 0.8rem;
}

.zoom-readout {
  min-width: 3.4rem;
  text-align: center;
  font-variant-numeric: tabular-nums;
  font-size: 0.85rem;
  color: var(--color-text-subtle);
}

/* Same specificity as the rule above, later in source order, so it wins:
   undo/redo are icon-only, unlike the rest of the bottom bar's text buttons. */
.bottom-bar-group .icon-button {
  padding: 0;
  width: var(--icon-size-xl);
  height: var(--icon-size-xl);
}

.tool-button:disabled {
  opacity: 0.4;
}

/* A little celebration when exporting a finished piece - see
   celebrateExport() in js/workspace.js. */
.confetti-burst {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 200;
  overflow: hidden;
}

.confetti-piece {
  position: absolute;
  width: 7px;
  height: 7px;
  margin: -3.5px 0 0 -3.5px;
  border-radius: 2px;
  animation: confetti-burst-fly 1.1s cubic-bezier(0.25, 0.75, 0.4, 1) forwards;
}

@keyframes confetti-burst-fly {
  0% {
    transform: translate(0, 0) rotate(0deg);
    opacity: 1;
  }
  100% {
    transform: translate(var(--dx), calc(var(--dy) + 160px)) rotate(var(--rot));
    opacity: 0;
  }
}

/* The "matrix" magic palette's own effect (see MAGIC_PALETTES/matrixRain
   in js/workspace.js) - falling green characters instead of confetti. */
.matrix-rain {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 200;
  overflow: hidden;
}

.matrix-rain-column {
  position: absolute;
  top: 0;
  white-space: pre;
  font-family: ui-monospace, monospace;
  font-size: 1rem;
  line-height: 1.1;
  color: #00ff41;
  text-shadow: 0 0 6px #00ff41;
  animation-name: matrix-rain-fall;
  animation-timing-function: linear;
  animation-fill-mode: forwards;
}

/* Viewport-relative units (not the column's own height, e.g. -100%/200%
   of a ~300px-tall text block) - otherwise the whole fall stays off the
   top edge, however far the animation runs (a real bug caught while
   verifying: -100%/200% of the element's own height never actually
   crossed into the visible viewport). */
@keyframes matrix-rain-fall {
  from {
    transform: translateY(-100vh);
    opacity: 1;
  }
  to {
    transform: translateY(100vh);
    opacity: 0;
  }
}

/* Shared "Are you sure?" confirm modal (js/confirm-dialog.js) - built
   once, reused for every delete action in the app (project, custom
   brush, color palette, layer), replacing window.confirm(). */
.confirm-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
}

.confirm-dialog {
  width: 20rem;
  max-width: calc(100vw - 2rem);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 0.7rem;
  padding: 1.2rem;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);
}

.confirm-dialog-title {
  margin: 0 0 0.5rem;
  font-size: 1rem;
  font-weight: 600;
}

.confirm-dialog-message {
  margin: 0 0 1.2rem;
  font-size: 0.85rem;
  color: var(--color-text-subtle-2);
  line-height: 1.4;
}

.confirm-dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.6rem;
}

.confirm-dialog-confirm {
  background: var(--color-danger);
  border-color: var(--color-danger);
  color: #fff;
}

/* Paw Parade easter egg (js/gallery.js) - 7 rapid clicks on the Gallery's
   "Pixi" title. Purely decorative overlay, removes itself. */
.paw-parade {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 200;
  overflow: hidden;
}

.paw-print {
  position: absolute;
  top: 50%;
  font-size: 1.8rem;
  opacity: 0;
  animation: paw-print-step 0.6s ease-out forwards;
}

@keyframes paw-print-step {
  0% {
    opacity: 0;
    transform: translateY(calc(-50% + var(--paw-offset, 0px))) scale(0.4) rotate(-15deg);
  }
  30% {
    opacity: 1;
    transform: translateY(calc(-50% + var(--paw-offset, 0px))) scale(1) rotate(-5deg);
  }
  100% {
    opacity: 0;
    transform: translateY(calc(-50% + var(--paw-offset, 0px))) scale(1) rotate(-5deg);
  }
}

/* "Buzz" hover feedback - a small, fast shake, reimplemented directly
   (a few lines of CSS) rather than pulling in ianlunn.github.io/Hover/
   as an external dependency, since this app has no CDN/build step for
   third-party CSS libraries (see CLAUDE.md's stack). Applied to most
   clickable controls (buttons and swatches) - explicitly not the
   drawing canvas, text/number/range inputs, the brush-editor's
   pixel-toggle grid, the Foreground/Background swatches and their
   swap/reset corner buttons, the bottom bar's zoom toolset, any
   delete-labeled button, "new artwork" actions (+ New Canvas, Create),
   or Clear selection/Delete - buttons used often/in quick succession,
   fine click targets, or destructive/creation actions, where a
   jitter-on-hover reads as noise (or feels wrong for something
   consequential) rather than a nice touch - per feedback.
   Scoped to iPad/iPhone only, per feedback that it shouldn't fire on
   ordinary desktop mouse hover. Originally tried as a pure CSS media
   query, `(hover: hover) and (pointer: coarse)` (meant to catch a
   hover-capable touchscreen) - but Apple Pencil is a *precise* stylus,
   reporting `pointer: fine` exactly like a mouse, so that query
   excluded Pencil hover right along with the desktop mouse hover it
   was meant to exclude, and buzz disappeared on iPad entirely. CSS
   alone can't distinguish "a mouse is hovering" from "a Pencil is
   hovering" - platform detection is used instead: js/app.js adds
   `.ios-platform` to <html> via a UA check. Also respects
   prefers-reduced-motion.
   Exclusions (Clear selection/Delete/zoom toolset/"new artwork"/etc.)
   are marked with a shared `.no-buzz` class instead of separate
   higher-specificity override rules - simpler and less fragile than
   fighting a specificity/source-order battle against this rule for
   every excluded element that happens to also carry .tool-button/
   .icon-button (a real bug this app hit once already: adding
   `.ios-platform` to this rule's selectors upgraded its specificity
   past the old override rules, silently un-excluding the zoom toolset
   and the confirm-dialog's "Delete" button on iOS). */
@media (prefers-reduced-motion: no-preference) {
  .ios-platform .tool-button:not(:disabled):not(.no-buzz):hover,
  .ios-platform .preset-button:not(:disabled):not(.no-buzz):hover,
  .ios-platform .icon-button:not(:disabled):not(.no-buzz):hover,
  .ios-platform .layer-visibility-toggle:not(.no-buzz):hover,
  .ios-platform .layer-reorder-button:not(:disabled):not(.no-buzz):hover,
  .ios-platform .brush-swatch:not(.no-buzz):hover,
  .ios-platform .palette-swatch:not(.no-buzz):hover,
  .ios-platform .color-library-swatch:not(.no-buzz):hover {
    animation: hover-buzz 0.25s linear infinite; /* slowed further per feedback (was 0.2s) */
  }
}

@keyframes hover-buzz {
  50% {
    transform: translateX(3px) rotate(2deg);
  }
  100% {
    transform: translateX(-3px) rotate(-2deg);
  }
}

