/* Fractalize Core: styles for the WebGL fractal viewer, the "visualize to
   music" noise-warp view, and the live-audio settings UI they share.
   Extracted from styles.css so this file can move into its own repo
   (fractalize-core) as a self-contained unit -- see fractalize-core.js for
   the matching extraction and the window.FractalizeCore API both files
   expose to a host page. */

/* Applied to <html> while either overlay below is open (see lockScroll/
   unlockScrollIfNeeded in fractalize-core.js) -- the engine owns its own
   scroll lock rather than depending on a host page's own "modal open"
   class, since it needs to work standalone (no lightbox, no host page
   awareness required). */
.fractalize-scroll-lock {
  overflow: hidden;
}

/* Fullscreen, cover-fit "visualize to music" view -- sits above a host
   page's own modals (see the z-index below) so it's reachable from
   within one. See the file-level comment in fractalize-core.js above
   openVisualizer() for what the warp is (and isn't) actually driven by. */
.image-visualizer {
  position: fixed;
  inset: 0;
  z-index: 3100;
  background: #000;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.3s ease, visibility 0s linear 0.3s;
}

.image-visualizer.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity 0.3s ease, visibility 0s linear 0s;
}

.image-visualizer-canvas {
  display: block;
  width: 100%;
  height: 100%;
  /* Fills the viewport edge-to-edge, cropping as needed rather than
     stretching (the "not stretching the image" requirement). */
  object-fit: cover;
}

.image-visualizer-close {
  position: absolute;
  top: 20px;
  right: 20px;
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  width: 44px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.1);
  color: #fff;
  font-size: 1.6rem;
  line-height: 1;
  cursor: pointer;
  transition: background-color 0.2s ease;
}

.image-visualizer-close:hover {
  background: rgba(255, 255, 255, 0.2);
}

/* Sits directly left of the close button (44px + 12px gap, matching the
   settings-gear/camera-roll-toggle gap convention elsewhere in this
   file) -- switches to the fractal view on the same photo without
   leaving fullscreen. */
.image-visualizer-switch-fractal {
  position: absolute;
  top: 20px;
  right: 76px;
  z-index: 1;
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  width: 44px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.1);
  cursor: pointer;
  transition: background-color 0.2s ease;
}

.image-visualizer-switch-fractal:hover {
  background: rgba(255, 255, 255, 0.2);
}

/* Same 44x44 circle-button + slide-up-glass-sheet language as the
   fractal's own settings gear/.fractal-controls (see below) -- copied
   rather than shared, matching this file's existing convention of
   keeping each surface's own classes separate even where the visual
   pattern is identical. Holds just the "Live audio input" row (see
   wireLiveAudioControls in fractalize-core.js), reused verbatim from the
   fractal's markup since it's the exact same control, just living in a
   different overlay. */
.image-visualizer-settings-toggle {
  position: absolute;
  top: 20px;
  left: 20px;
  z-index: 1;
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  width: 44px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.1);
  cursor: pointer;
  transition: background-color 0.2s ease;
}

.image-visualizer-settings-toggle:hover {
  background: rgba(255, 255, 255, 0.2);
}

.visualizer-controls {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  max-height: 75vh;
  overflow-y: auto;
  background: rgba(20, 20, 20, 0.78);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-top: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 20px 20px 0 0;
  padding: 20px 24px calc(20px + env(safe-area-inset-bottom, 0px));
  transform: translateY(100%);
  transition: transform 0.3s ease;
}

.visualizer-controls.is-open {
  transform: translateY(0);
}

/* Fullscreen WebGL "Mandelbrot zoom" view -- same shell pattern as
   .image-visualizer above (fixed, fades in/out, sits at the same
   z-index tier), and its content is a <canvas> too. See the file-level
   comment in fractalize-core.js above openFractal() for what's actually
   being rendered. */
.image-fractal {
  position: fixed;
  inset: 0;
  z-index: 3100;
  background: #000;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.3s ease, visibility 0s linear 0.3s;
}

.image-fractal.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity 0.3s ease, visibility 0s linear 0s;
}

.image-fractal-canvas {
  display: block;
  width: 100%;
  height: 100%;
}

/* Bottom-center bar holding the 3 mode/panel buttons (settings, camera
   roll, switch-to-visualizer) -- replaces their old scattered
   top-left/top-right corner placement so they live in one
   mobile-friendly, thumb-reachable spot. Close (below) deliberately
   stays out of this group -- see its own comment. Same slide-up
   glass-sheet language as .fractal-controls/.fractal-cameraroll below
   (this is really just a third, always-available bottom sheet), and
   sits after both of those in the DOM so it paints above them rather
   than getting covered by whichever one is open -- see buildFractal's
   own comment for why that's required, not just cosmetic. Idle-hide
   (below) slides it back down the same way rather than just fading it,
   matching that language. */
.image-fractal-toolbar {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  background: rgba(20, 20, 20, 0.78);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-top: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 20px 20px 0 0;
  padding: 14px 20px calc(14px + env(safe-area-inset-bottom, 0px));
  transform: translateY(0);
  transition: transform 0.3s ease, opacity 0.3s ease;
}

.image-fractal.low-performance .image-fractal-toolbar,
.image-fractal.low-performance .image-fractal-close {
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  background: rgba(20, 20, 20, 0.92);
}

.image-fractal-close,
.image-fractal-switch-visualizer,
.image-fractal-settings-toggle,
.image-fractal-cameraroll-toggle,
.image-fractal-windowed-toggle,
.image-fractal-help-toggle {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  width: 44px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 50%;
  cursor: pointer;
  transition: background-color 0.2s ease;
}

.image-fractal-switch-visualizer,
.image-fractal-settings-toggle,
.image-fractal-cameraroll-toggle,
.image-fractal-windowed-toggle,
.image-fractal-help-toggle {
  background: rgba(255, 255, 255, 0.1);
}

.image-fractal-switch-visualizer:hover,
.image-fractal-settings-toggle:hover,
.image-fractal-cameraroll-toggle:hover,
.image-fractal-windowed-toggle:hover,
.image-fractal-help-toggle:hover {
  background: rgba(255, 255, 255, 0.2);
}

/* Back in its original top-right corner (see buildFractal's own
   comment for why it's not grouped with the other 3) -- same glass
   background/blur as .image-fractal-toolbar/.fractal-controls though,
   rather than the plain translucent-white the corner buttons used
   before this bar existed, so it still reads as part of the same
   control family. */
.image-fractal-close {
  position: absolute;
  top: 20px;
  right: 20px;
  z-index: 2;
  background: rgba(20, 20, 20, 0.78);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  color: #fff;
  font-size: 1.6rem;
  line-height: 1;
}

.image-fractal-close:hover {
  background: rgba(20, 20, 20, 0.92);
}

/* A small, semi-transparent settings sheet -- deliberately minimal (one
   toggle for now) after the last version grew a slider for every
   tunable variable and made bad combinations easy to reach. Slides up
   from the bottom edge of .image-fractal rather than fading, so it
   reads as a physical sheet being pulled into view. */
.fractal-controls {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  max-height: 75vh;
  overflow-y: auto;
  /* Lower blur radius + slightly higher opacity than the original
     20px/0.65 -- backdrop-filter has to be recomputed every single
     frame while the fractal keeps animating underneath (it can't be
     cached the way a blur over static content can), so a smaller
     kernel meaningfully cuts a real, continuous GPU cost; the higher
     opacity keeps the glass reading just as legible with less blur
     doing the work. */
  background: rgba(20, 20, 20, 0.78);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-top: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 20px 20px 0 0;
  /* Extra bottom padding (vs. the visualizer's own equivalent panel)
     clears .image-fractal-toolbar, which stays visible on top of
     whichever sheet is open (see its own comment) -- without this its
     content would sit behind the toolbar's own 44px buttons. */
  padding: 20px 24px calc(78px + env(safe-area-inset-bottom, 0px));
  transform: translateY(100%);
  transition: transform 0.3s ease;
}

.fractal-controls.is-open {
  transform: translateY(0);
}

.fractal-controls-row {
  margin-bottom: 18px;
}

.fractal-controls-row label {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 0.85rem;
  color: rgba(255, 255, 255, 0.85);
  margin-bottom: 8px;
}

.fractal-controls-value {
  color: #fff;
  font-variant-numeric: tabular-nums;
}

.fractal-controls-row input[type="range"] {
  width: 100%;
  accent-color: #fff;
}

/* Wraps a randomizable slider's own <input> alongside its lock button
   (see .fractal-controls-lock below) -- the slider shrinks to make room
   rather than the button overlapping it. Only present on rows that
   actually have a lock button; every other .fractal-controls-row's
   <input> is still a direct child, still full-width via the rule above. */
.fractal-controls-slider-row {
  display: flex;
  align-items: center;
  gap: 10px;
}

.fractal-controls-slider-row input[type="range"] {
  flex: 1;
  min-width: 0;
  width: auto;
}

/* Randomizer lock: a small round icon button (vs. the pill shape used
   elsewhere in this panel, e.g. .fractal-controls-randomizer-pill) since
   it holds a square icon rather than text -- same border/background/
   is-active language as those pills otherwise, so it still reads as
   part of the same control family. Locked (.is-active) swaps to the
   closed-padlock icon on a solid white circle; LOCK_ICON's own two
   pre-built variants (see fractalize-core.js) already carry the
   matching light/dark icon fill for each state. */
.fractal-controls-lock {
  flex: none;
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.1);
  cursor: pointer;
  transition: background-color 0.2s ease;
}

.fractal-controls-lock:hover {
  background: rgba(255, 255, 255, 0.2);
}

.fractal-controls-lock.is-active {
  background: #fff;
  border-color: #fff;
}

.fractal-controls-toggle-row {
  margin-bottom: 18px;
}

.fractal-controls-toggle-row label {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 10px;
  font-size: 0.85rem;
  color: rgba(255, 255, 255, 0.85);
  cursor: pointer;
  margin-bottom: 0;
}

.fractal-controls-toggle-row input[type="checkbox"] {
  width: 18px;
  height: 18px;
  accent-color: #fff;
  cursor: pointer;
}

.fractal-controls-version {
  text-align: right;
  font-size: 0.7rem;
  color: rgba(255, 255, 255, 0.35);
  font-variant-numeric: tabular-nums;
  margin-top: 4px;
}

/* Two columns on desktop -- the device picker/status on the left, the
   live waveform meter (see fractal-audio-waveform below) to its right
   -- collapsing to stacked (meter below) once there isn't room for
   both side by side. flex-wrap alone would eventually do this on its
   own at some width, but this row lives inside contexts as different
   as a near-full-viewport bottom sheet and fractalize-studio's ~960px
   main column, so a plain viewport media query (matching this
   project's existing 600px breakpoint) gives a consistent, deliberate
   mobile behavior instead of an incidental one. */
.fractal-controls-audio-device {
  display: flex;
  align-items: flex-start;
  gap: 16px;
}

/* display:flex above otherwise beats the browser's own default
   [hidden]{display:none} UA rule at equal specificity -- this row is
   always rendered with the hidden attribute until the checkbox is
   actually checked (see wireLiveAudioControls in fractalize-core.js),
   so without this override it was visible from the start in all four
   places that use it: the fractal's own panel, the visualizer's,
   fractalize-studio's live-audio-help card, and its "Start" modal. */
.fractal-controls-audio-device[hidden] {
  display: none;
}

.fractal-audio-device-col {
  flex: 1 1 50%;
  min-width: 0;
}

.fractal-audio-meter-col {
  flex: 1 1 50%;
}

.fractal-audio-waveform {
  display: block;
  width: 100%;
  height: 57.6px; /* 48px + 20% */
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.15);
}

@media (max-width: 600px) {
  .fractal-controls-audio-device {
    flex-wrap: wrap;
  }
  .fractal-audio-meter-col {
    flex-basis: 100%;
  }
}

.fractal-controls-audio-device label {
  display: block;
  font-size: 0.85rem;
  color: rgba(255, 255, 255, 0.85);
  margin-bottom: 8px;
}

.fractal-controls-select {
  width: 100%;
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  background: rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 8px;
  color: #fff;
  padding: 8px 10px;
  font-size: 0.85rem;
}

/* The <select>'s own closed-state text stays white (readable against
   its own translucent-dark background above), but its dropdown list of
   <option>s renders using the browser's native popup chrome -- almost
   always a plain white/light background regardless of the select's own
   styling -- so white option text was invisible until a hover state
   (the browser's own highlight color) happened to add enough contrast.
   Pinning both colors on the options themselves fixes this regardless
   of the native popup's actual background. */
.fractal-controls-select option {
  color: #1a1a1a;
  background: #fff;
}

.fractal-controls-audio-status {
  margin: 8px 0 0;
  font-size: 0.78rem;
  color: rgba(255, 255, 255, 0.6);
}

/* Corner X on each bottom sheet. Sticky so it stays reachable while the
   sheet's content scrolls; its own row (not overlaid) so it never covers
   the first control. */
.fractal-panel-close {
  appearance: none;
  -webkit-appearance: none;
  position: sticky;
  top: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  margin: -8px -12px 4px auto;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.12);
  cursor: pointer;
}

.fractal-panel-close:hover {
  background: rgba(255, 255, 255, 0.22);
}

.fractal-controls-divider {
  margin: 14px 0 10px;
  border: 0;
  border-top: 1px solid rgba(255, 255, 255, 0.25);
}

.fractal-controls-hint {
  margin: 6px 0 0;
  font-size: 0.75rem;
  line-height: 1.35;
  color: rgba(255, 255, 255, 0.55);
}

/* Same slide-up glass sheet as .fractal-controls -- a second, mutually
   exclusive bottom sheet (see buildFractal's toggle wiring) rather than
   a second visual language, so the two panels read as one family. */
.fractal-cameraroll {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  max-height: 75vh;
  overflow-y: auto;
  /* Lower blur radius + slightly higher opacity than the original
     20px/0.65 -- backdrop-filter has to be recomputed every single
     frame while the fractal keeps animating underneath (it can't be
     cached the way a blur over static content can), so a smaller
     kernel meaningfully cuts a real, continuous GPU cost; the higher
     opacity keeps the glass reading just as legible with less blur
     doing the work. */
  background: rgba(20, 20, 20, 0.78);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-top: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 20px 20px 0 0;
  /* Extra bottom padding (vs. the visualizer's own equivalent panel)
     clears .image-fractal-toolbar, which stays visible on top of
     whichever sheet is open (see its own comment) -- without this its
     content would sit behind the toolbar's own 44px buttons. */
  padding: 20px 24px calc(78px + env(safe-area-inset-bottom, 0px));
  transform: translateY(100%);
  transition: transform 0.3s ease;
}

.fractal-cameraroll.is-open {
  transform: translateY(0);
}

/* Low performance mode (see FRACTAL_DEFAULTS/buildFractal in
   fractalize-core.js): backdrop-filter blur has to be recomputed every
   single frame over the continuously-animating canvas beneath it (it
   can't be cached the way a blur over static content can) -- dropping
   it entirely while either panel is open is one of the cheapest real
   wins available, at the cost of the panel reading as a flat dark sheet
   instead of frosted glass. Slightly higher opacity than the normal
   0.78 keeps it just as legible without the blur doing any of the work. */
.image-fractal.low-performance .fractal-controls,
.image-fractal.low-performance .fractal-cameraroll {
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  background: rgba(20, 20, 20, 0.92);
}

.fractal-cameraroll-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px;
  margin-bottom: 18px;
  padding-bottom: 18px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.15);
}

.fractal-cameraroll-shuffle {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 0.85rem;
  color: rgba(255, 255, 255, 0.85);
  cursor: pointer;
}

.fractal-cameraroll-shuffle input[type="checkbox"] {
  width: 18px;
  height: 18px;
  accent-color: #fff;
  cursor: pointer;
}

/* "Modern, clean, but unique" -- a row of rounded pills rather than a
   plain range slider, so the shuffle timer reads as a distinct control
   from the sliders in the settings panel rather than one more of them. */
.fractal-shuffle-timer {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.fractal-shuffle-pill {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.1);
  color: rgba(255, 255, 255, 0.85);
  font-size: 0.78rem;
  padding: 6px 14px;
  cursor: pointer;
  transition: background-color 0.2s ease, color 0.2s ease;
}

.fractal-shuffle-pill:hover {
  background: rgba(255, 255, 255, 0.2);
}

.fractal-shuffle-pill.is-active {
  background: #fff;
  color: #111;
  border-color: #fff;
}

/* Randomizer (settings panel): exact same checkbox+timer-row style as
   the camera roll's "Enable shuffle" above, including the border-bottom
   separator -- copied rather than shared, matching this file's existing
   convention of keeping each panel's own classes separate even where
   the visual pattern is identical (see .fractal-controls-* vs.
   .fractal-cameraroll-* throughout). */
.fractal-controls-randomizer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px;
  margin-bottom: 18px;
  padding-bottom: 18px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.15);
}

/* Forces its own full-width line below the toggle/timer row above,
   rather than sitting beside them as a third flex item -- everything
   else in this flex-wrap row is a control (label/pills/button) with its
   own intrinsic width, not a paragraph meant to share a line. */
.fractal-controls-randomizer > .fractal-controls-hint {
  width: 100%;
  margin-top: 4px;
}

.fractal-controls-randomizer-toggle {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 0.85rem;
  color: rgba(255, 255, 255, 0.85);
  cursor: pointer;
}

.fractal-controls-randomizer-toggle input[type="checkbox"] {
  width: 18px;
  height: 18px;
  accent-color: #fff;
  cursor: pointer;
}

.fractal-controls-randomizer-timer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

.fractal-controls-randomizer-pill,
.fractal-controls-quality-pill {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.1);
  color: rgba(255, 255, 255, 0.85);
  font-size: 0.78rem;
  padding: 6px 14px;
  cursor: pointer;
  transition: background-color 0.2s ease, color 0.2s ease;
}

.fractal-controls-randomizer-pill:hover,
.fractal-controls-quality-pill:hover {
  background: rgba(255, 255, 255, 0.2);
}

.fractal-controls-randomizer-pill.is-active,
.fractal-controls-quality-pill.is-active {
  background: #fff;
  color: #111;
  border-color: #fff;
}

/* Deliberately NOT another pill -- always-filled and bold rather than
   outline-until-selected, so it reads as an action to take rather than
   one more interval choice among the pills it sits beside. */
.fractal-controls-randomize-now {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  border: 1px solid #fff;
  border-radius: 999px;
  background: #fff;
  color: #111;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 7px 16px;
  cursor: pointer;
  transition: background-color 0.2s ease, transform 0.1s ease;
}

.fractal-controls-randomize-now:hover {
  background: rgba(255, 255, 255, 0.85);
}

.fractal-controls-randomize-now:active {
  transform: scale(0.96);
}

.fractal-cameraroll-section {
  margin-bottom: 20px;
}

.fractal-cameraroll-section-label {
  margin: 0 0 10px;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.5);
}

.fractal-cameraroll-row {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
  gap: 10px;
}

.fractal-cameraroll-thumb {
  position: relative;
  aspect-ratio: 1;
  border-radius: 10px;
  overflow: hidden;
  border: 2px solid transparent;
  background: rgba(255, 255, 255, 0.06);
}

.fractal-cameraroll-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.fractal-cameraroll-thumb.is-queued {
  border-color: rgba(255, 255, 255, 0.6);
}

.fractal-cameraroll-thumb.is-playing {
  border-color: #fff;
  box-shadow: 0 0 0 1px #fff;
}

.fractal-cameraroll-thumb-actions {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  justify-content: space-between;
  padding: 4px;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0));
}

.fractal-cameraroll-action {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  width: 26px;
  height: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.85);
  color: #111;
  font-size: 0.85rem;
  line-height: 1;
  cursor: pointer;
  transition: background-color 0.2s ease;
}

.fractal-cameraroll-action:hover {
  background: #fff;
}

/* .fractal-cameraroll-thumb above gives this its base size/shape --
   these override its plain fill with a dashed "empty dropzone" look,
   only ever created (see buildAddPhotoTile in fractalize-core.js) when
   a host page opts in via setUploadHandler. */
.fractal-cameraroll-add-thumb {
  display: flex;
  align-items: center;
  justify-content: center;
  border: 2px dashed rgba(255, 255, 255, 0.3);
  background: rgba(255, 255, 255, 0.03);
  cursor: pointer;
  transition: border-color 0.2s ease, background-color 0.2s ease;
}

.fractal-cameraroll-add-thumb:hover {
  border-color: rgba(255, 255, 255, 0.7);
  background: rgba(255, 255, 255, 0.08);
}

.fractal-cameraroll-add-thumb svg {
  opacity: 0.85;
}

/* Idle-hide: after IDLE_HIDE_MS of no mouse/keyboard activity (see
   fractalize-core.js), whichever overlay is open gets this class --
   fades out its own top-level chrome buttons (never an open settings/
   camera-roll panel, which pauses the hide entirely) and hides the OS
   cursor, so a photo left running fullscreen on an idle second monitor
   shows nothing but itself. Any activity removes the class immediately;
   only the hide itself fades. */
.image-fractal.chrome-hidden,
.image-visualizer.chrome-hidden {
  cursor: none;
}

.image-visualizer-close,
.image-visualizer-switch-fractal,
.image-visualizer-settings-toggle {
  transition: background-color 0.2s ease, opacity 0.4s ease;
}

.image-visualizer.chrome-hidden .image-visualizer-close,
.image-visualizer.chrome-hidden .image-visualizer-switch-fractal,
.image-visualizer.chrome-hidden .image-visualizer-settings-toggle {
  opacity: 0;
  pointer-events: none;
}

/* The fractal's own bottom-sheet chrome is one unit (.image-fractal-
   toolbar, see above) -- slides back down the same way the settings/
   camera-roll sheets themselves slide up, rather than just fading each
   button out, so idle-hide reads as the same physical sheet retreating
   rather than a different effect for a third thing that looks just
   like them. Close lives back in its own corner (see its own comment
   above) -- unlike the settings/camera-roll toggles it's the ONE
   button not gated behind a "reveal chrome first" step in most photo
   viewers, but this file already treats every chrome button the same
   idle-hide way pre-dating this bar's own existence, so it keeps
   fading with the rest here rather than carving out a new exception.
   Same 0.4s opacity fade as the visualizer's own equivalent buttons
   above, not the bar's 0.3s slide, since fading in place (not sliding
   off toward the bottom) is still the right motion for a corner
   button. */
.image-fractal-close {
  transition: background-color 0.2s ease, opacity 0.4s ease;
}

.image-fractal.chrome-hidden .image-fractal-close {
  opacity: 0;
  pointer-events: none;
}

.image-fractal.chrome-hidden .image-fractal-toolbar {
  transform: translateY(100%);
  opacity: 0;
  pointer-events: none;
}

/* --- Tutorial: dark spotlight overlay + centered caption ---------------
   Sits above every other child of .image-fractal (toolbar included) so
   its shading always wins the paint order regardless of DOM order --
   see buildFractal's own comment on why the "hole" is four real,
   separately-positioned divs rather than one clever box-shadow/mask
   trick: with a real gap, the highlighted element underneath needs no
   pointer-events workaround to stay genuinely clickable. */
.fractal-tutorial {
  position: absolute;
  inset: 0;
  z-index: 10;
  /* Transparent to clicks by default. Without this, this container's
     OWN box -- which spans the full viewport regardless of what its
     children actually paint -- would win hit-testing everywhere it
     covers, including the spotlight hole: a real (coordinate-based)
     click there would land on this empty container instead of falling
     through to the highlighted element underneath, even though nothing
     is visually on top of it there. pointer-events is inherited, so
     every child that DOES need to catch clicks (the shades, the close
     button) turns it back on for itself below. */
  pointer-events: none;
}

.fractal-tutorial[hidden] {
  display: none;
}

.fractal-tutorial-shade {
  position: absolute;
  left: 0;
  width: 100%;
  background: rgba(0, 0, 0, 0.75);
  /* Only some steps let tapping the shade advance/close (see
     tutorialShadeAdvances in buildFractal) -- the pointer cursor only
     applies then, via the modifier below, so it never implies
     clickability on a step where it wouldn't do anything. */
  cursor: default;
  /* Re-enables hit-testing that .fractal-tutorial's own pointer-events:
     none turned off -- these are the actual dark, opaque area, so they
     should still block a click from reaching whatever's behind them
     (the canvas, other toolbar buttons), whether or not that click also
     advances the tutorial. */
  pointer-events: auto;
}

.fractal-tutorial.shade-advances .fractal-tutorial-shade {
  cursor: pointer;
}

/* A step's optional heavyTint (see TUTORIAL_STEPS) swaps the usual 75%
   for something much closer to opaque -- for de-emphasizing the whole
   screen, target included if it has one, in favor of its own caption,
   rather than the usual "still see roughly what's there" dimming. */
.fractal-tutorial-shade.is-heavy {
  background: rgba(0, 0, 0, 0.93);
}

/* Top/bottom span the full width; left/right (positioned/sized entirely
   via buildFractal's positionTutorialSpotlight, both dimensions) fill
   just the row alongside the spotlight hole, so together the four cover
   the screen exactly once each, with no overlap and no gap besides the
   hole itself. */
.fractal-tutorial-shade[data-tutorial-shade="top"] {
  top: 0;
}

.fractal-tutorial-shade[data-tutorial-shade="left"],
.fractal-tutorial-shade[data-tutorial-shade="right"] {
  width: auto;
}

/* An extra layer positioned/sized to exactly one element's own rect --
   either a step's blurTarget, or (with blockTargetClicks instead) the
   same rect as the spotlight hole itself. Transparent and invisible by
   default, its only job then is pointer-events: auto (overriding
   .fractal-tutorial's own none) to block clicks to whatever it's
   covering -- e.g. a hole shown off but deliberately kept inert. The
   .is-blurred modifier is what actually makes it visible: backdrop-
   filter blurs whatever's actually painted behind it in place (that
   element's real content, already dimmed by a shade underneath if one
   overlaps it too), enough on its own to make text there illegible with
   no extra tint of its own needed. Not used by any current step (see
   TUTORIAL_STEPS' own comment), but kept ready for one that wants it. */
.fractal-tutorial-blur {
  position: absolute;
  z-index: 1;
  cursor: default;
  pointer-events: auto;
}

.fractal-tutorial-blur.is-blurred {
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
}

.fractal-tutorial-blur[hidden] {
  display: none;
}

.fractal-tutorial-text {
  position: absolute;
  /* top/bottom/transform are set inline per step by
     positionTutorialSpotlight -- dead-center of the screen by default,
     or pinned a fixed distance above a step's own textAnchor. */
  left: 0;
  right: 0;
  z-index: 1;
  margin: 0;
  padding: 0 15%;
  text-align: center;
  color: #fff;
  font-size: 1.15rem;
  font-weight: 600;
  line-height: 1.4;
  text-shadow: 0 2px 14px rgba(0, 0, 0, 0.7);
  /* Already the inherited default (.fractal-tutorial's own
     pointer-events: none above) -- explicit here so this stays correct
     even if that ever changes, and to record the reason: whatever's
     beneath (a shade, or the spotlight hole) needs the click, not this
     purely-visual caption. */
  pointer-events: none;
}

.fractal-tutorial-close {
  position: absolute;
  top: 20px;
  right: 20px;
  z-index: 2;
  /* Overrides the inherited none from .fractal-tutorial -- unlike the
     shades (real dark area, still meant to block clicks) or the hole
     (meant to pass them through), this is an actual control and needs
     to stay clickable. */
  pointer-events: auto;
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.1);
  cursor: pointer;
  transition: background-color 0.2s ease;
}

.fractal-tutorial-close:hover {
  background: rgba(255, 255, 255, 0.2);
}

/* Applied to whichever real element the current step points at (see
   showTutorialStep) -- pulses brighter to draw the eye to it, the
   highlighting itself coming from the shades' cutout above rather than
   any outline/ring here. */
@keyframes fractal-tutorial-flash-kf {
  0%,
  100% {
    filter: brightness(1);
  }
  50% {
    filter: brightness(2.1);
  }
}

.fractal-tutorial-flash {
  animation: fractal-tutorial-flash-kf 1s ease-in-out infinite;
}

/* --- Phone remote: settings-panel button + QR/approval modal ----------- */
.fractal-remote-open {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  display: block;
  margin: 4px 0 14px;
  border: 1px solid rgba(255, 255, 255, 0.4);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.1);
  color: #fff;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  padding: 9px 18px;
  cursor: pointer;
  transition: background-color 0.2s ease;
}

.fractal-remote-open:hover {
  background: rgba(255, 255, 255, 0.2);
}

.fractal-remote-open[hidden] {
  display: none;
}

.fractal-remote-modal {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(0, 0, 0, 0.6);
}

.fractal-remote-modal[hidden] {
  display: none;
}

.fractal-remote-card {
  position: relative;
  width: min(360px, 100%);
  max-height: 100%;
  overflow-y: auto;
  padding: 18px 22px 22px;
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 20px;
  background: rgba(20, 20, 20, 0.94);
  color: #fff;
  text-align: center;
}

.fractal-remote-title {
  margin: 4px 0 14px;
  font-size: 1rem;
  font-weight: 600;
}

.fractal-remote-qr {
  width: min(240px, 100%);
  margin: 0 auto 14px;
  padding: 8px;
  border-radius: 12px;
  background: #fff;
  line-height: 0;
}

.fractal-remote-qr[hidden] {
  display: none;
}

.fractal-remote-qr svg {
  width: 100%;
  height: auto;
}

.fractal-remote-text {
  margin: 0 0 14px;
  font-size: 0.85rem;
  line-height: 1.4;
  color: rgba(255, 255, 255, 0.75);
}

.fractal-remote-actions {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 10px;
}

.fractal-remote-action {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  border: 1px solid #fff;
  border-radius: 999px;
  background: #fff;
  color: #111;
  font-size: 0.8rem;
  font-weight: 700;
  padding: 9px 20px;
  cursor: pointer;
}

.fractal-remote-action.is-secondary {
  background: transparent;
  color: #fff;
  border-color: rgba(255, 255, 255, 0.4);
}

/* --- Quality row (settings panel): pill choices share the Randomizer
   timer pills' look above; this is just their wrapping row. ----------- */
.fractal-controls-quality-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
