/* 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;
}

.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;
}

/* 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;
}

#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; pixi-pro's canvas-settings-ui.js for the Pro-only
   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-label (Canvas Settings' Name field styling)
   moved to pixi-pro's style-pro.css (split-pixi-pro-repo) - its only
   user was the now Pro-only Canvas Settings panel. */

/* 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;
}

/* 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 (its own CSS + the shared .panel-header* base classes
   it and Color Library both build on) moved to pixi-pro's
   style-pro.css (split-pixi-pro-repo). */

.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 used to be, 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-*/.new-palette-row/.import-preview-* (Color Library
   panel + its import/ramp preview popovers) moved to pixi-pro's
   style-pro.css (split-pixi-pro-repo). */


.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 .brush-swatch:not(.no-buzz):hover,
  .ios-platform .palette-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);
  }
}

