/* Photo Studio - page-specific layout. base.css holds the shared design
   system; this file holds layout that is specific to one page (the shoot
   grid, for now). */

/* --- Shoots list page (static/shoots.html) --- */

/* Shared with the shoot grid page's own title/meta row below - a generic
   "page title + one action button" header layout, not anything specific to
   either page. */
.shoot-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-md);
  flex-wrap: wrap;
  margin-bottom: var(--space-lg);
}

.shoot-header h1 {
  margin: 0;
}

/* Opening a shoot is the constant action here (creating one is rare and
   sits behind its own "New shoot" toggle) - a visible hover/focus cue is
   what makes an otherwise plain info card read as a real, comfortable
   click target rather than just a stat tile. */
.shoot-card {
  display: block;
  transition: border-color var(--transition-fast), transform var(--transition-fast);
}

.shoot-card:hover,
.shoot-card:focus-visible {
  border-color: var(--accent-blue);
  transform: translateY(-2px);
}

/* --- Shoot grid page (static/shoot.html + static/js/grid.js) --- */

.dropzone {
  border: 2px dashed var(--border-card);
  border-radius: var(--card-radius);
  padding: var(--space-xl);
  text-align: center;
  color: var(--text-secondary);
  cursor: pointer;
  transition: border-color var(--transition-fast), background var(--transition-fast),
    color var(--transition-fast);
  margin-bottom: var(--space-lg);
}

.dropzone:hover,
.dropzone:focus-visible,
.dropzone.drag-over {
  border-color: var(--accent-blue);
  background: color-mix(in srgb, var(--accent-blue) 8%, transparent);
  color: var(--text-primary);
}

.upload-progress {
  margin-bottom: var(--space-lg);
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.upload-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  padding: var(--space-xs) var(--space-sm);
  background: var(--bg-card);
  border: 1px solid var(--border-card);
  border-radius: var(--input-radius);
  font-size: 0.85em;
}

.upload-row-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.upload-row-status {
  flex-shrink: 0;
  color: var(--text-secondary);
}

.upload-row-status.status-imported {
  color: var(--color-success);
}

.upload-row-status.status-duplicate {
  color: var(--text-secondary);
}

.upload-row-status.status-error {
  color: var(--color-danger);
}

.shoot-toolbar {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  flex-wrap: wrap;
  margin-bottom: var(--space-md);
}

.shoot-toolbar .form-input,
.shoot-toolbar .form-select {
  width: auto;
  min-width: 200px;
}

.selection-count {
  color: var(--text-secondary);
  font-size: 0.9em;
}

/* .bulk-bar, .upload-progress, and #run-queued-btn are all toggled via the
   "hidden" attribute (grid.js sets el.hidden = true/false), never inline
   style. .bulk-bar/.upload-progress/.btn (run-queued-btn's own class) each
   set their own "display" as an author-stylesheet rule, which beats the
   browser's default UA-stylesheet "[hidden] { display: none }" regardless
   of selector specificity or source order (UA rules always lose to an
   author rule of equal specificity) - so without this override, "hidden"
   would have no visible effect on any of them. */
.bulk-bar[hidden],
.upload-progress[hidden],
#run-queued-btn[hidden] {
  display: none;
}

.bulk-bar {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  background: var(--bg-card);
  border: 1px solid var(--border-card);
  border-radius: var(--input-radius);
  margin-bottom: var(--space-lg);
  flex-wrap: wrap;
}

.bulk-bar .form-select {
  width: auto;
  min-width: 160px;
}

.photo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: var(--space-md);
}

/* Grid item: holds the selectable .photo-tile plus the .photo-edit-link as
   siblings (see grid.js's buildTile) so the link is never a DOM descendant
   of an element carrying an interactive ARIA role (axe-core:
   nested-interactive). position: relative anchors the edit link's absolute
   placement over the tile's own bottom-right corner - visually identical
   to when it lived inside the tile's own footer. */
.photo-tile-wrap {
  position: relative;
}

.photo-tile {
  position: relative;
  border: 2px solid transparent;
  border-radius: var(--input-radius);
  background: var(--bg-card);
  overflow: hidden;
  cursor: pointer;
  transition: border-color var(--transition-fast);
}

.photo-tile:hover,
.photo-tile:focus-visible {
  border-color: var(--border-input);
}

.photo-tile.selected {
  border-color: var(--accent-blue);
}

.photo-thumb-wrap {
  aspect-ratio: 1 / 1;
  overflow: hidden;
  background: var(--bg-body);
}

.photo-thumb-wrap img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.photo-badges {
  position: absolute;
  top: var(--space-xs);
  left: var(--space-xs);
  display: flex;
  gap: var(--space-xs);
}

/* A badge sitting over a thumbnail (Flagged/Queued/AI) can land over any
   photo - including a pale one - so it needs a solid, opaque chip rather
   than the translucent 15%-tint background .badge-warning/.badge-info use
   elsewhere (that tint was contrast-checked against this app's own dark
   card background, not against an arbitrary photograph). Dark text on the
   token's own full-strength colour keeps this readable over anything
   underneath it. */
.photo-badges .badge-warning {
  background: var(--color-warning);
  color: var(--bg-body);
}

.photo-badges .badge-info {
  background: var(--color-info);
  color: var(--bg-body);
}

.photo-select-indicator {
  position: absolute;
  top: var(--space-xs);
  right: var(--space-xs);
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 2px solid var(--text-bright);
  background: color-mix(in srgb, var(--bg-body) 50%, transparent);
}

.photo-tile.selected .photo-select-indicator {
  background: var(--accent-blue-dark);
  border-color: var(--accent-blue-dark);
}

.photo-tile-footer {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
}

.photo-filename {
  /* Fills the footer and shrinks/ellipsizes within it (min-width: 0 is what
     lets a flex item actually shrink below its own text's min-content
     width - without it the text would just overflow instead of eliding). */
  flex: 1 1 auto;
  min-width: 0;
  padding: var(--space-xs) var(--space-sm);
  /* .photo-edit-link is a DOM sibling of the tile (not a flex child here -
     see grid.js's buildTile), absolutely positioned over this same
     bottom-right corner, so this is what actually keeps a long filename's
     ellipsis from running underneath "Edit" - reserve exactly its
     footprint (padding var(--space-xs) var(--space-sm) around ~"Edit" at
     0.8em, comfortably under --space-2xl) rather than letting them share
     the same pixels with nothing but luck between them. */
  padding-right: var(--space-2xl);
  font-size: 0.8em;
  color: var(--text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.photo-edit-link {
  position: absolute;
  right: 0;
  bottom: 0;
  padding: var(--space-xs) var(--space-sm);
  font-size: 0.8em;
  font-weight: 600;
  color: var(--accent-blue);
  /* Solid, not transparent: this sits on the same corner as the filename
     above, and a solid chip is what guarantees "Edit" itself stays legible
     rather than merely "usually clear of the text" if a future change ever
     narrows the reserved padding. */
  background: var(--bg-card);
  border-top-left-radius: var(--input-radius);
  text-decoration: none;
}

.photo-edit-link:hover,
.photo-edit-link:focus-visible {
  text-decoration: underline;
}

@media (max-width: 768px) {
  .shoot-toolbar {
    flex-direction: column;
    align-items: stretch;
  }

  .shoot-toolbar .form-input,
  .shoot-toolbar .form-select {
    width: 100%;
  }
}

/* --- Photo editor page (static/photo.html + static/js/editor.js) --- */

.container-wide {
  max-width: 1600px;
}

.editor {
  display: grid;
  /* minmax(0, ...) rather than a bare 1fr: a bare 1fr track's automatic
     minimum size is its content's min-content width, which lets a wide
     descendant (a slider row, a button row) force the whole track - and
     with it this grid's own box - wider than the viewport instead of
     shrinking. minmax(0, 1fr) removes that automatic minimum so the track
     (and everything in it) actually shrinks to fit, which is what keeps
     the page from scrolling horizontally at narrow widths. */
  grid-template-columns: minmax(0, 1fr) 320px;
  gap: var(--space-lg);
  align-items: start;
}

.editor-canvas,
.editor-panel {
  min-width: 0;
}

.editor-canvas {
  background: var(--bg-card);
  border: 1px solid var(--border-card);
  border-radius: var(--card-radius);
  padding: var(--space-md);
}

.editor-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  margin-bottom: var(--space-md);
}

.editor-filename {
  color: var(--text-secondary);
  font-size: 0.9em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.editor-image-wrap {
  position: relative;
  width: 100%;
  background: var(--bg-body);
  border-radius: var(--input-radius);
  overflow: hidden;
}

.editor-after-img {
  display: block;
  width: 100%;
  height: auto;
}

.editor-before-clip {
  position: absolute;
  inset: 0;
  overflow: hidden;
  clip-path: inset(0 calc(100% - var(--reveal, 50%)) 0 0);
}

.editor-before-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The overlay canvas's own pixel buffer is set (by brush.js) to the proxy
   image's natural resolution, then CSS-stretched to fill this box exactly
   like .editor-after-img - that stretch is what keeps a stroke's on-screen
   position aligned with the image underneath it at any zoom/window size.
   Inert (no pointer capture) until the AI-edits panel is open, so it never
   swallows clicks elsewhere on the page. */
.brush-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.brush-canvas.active {
  pointer-events: auto;
  cursor: crosshair;
}

.brush-toggle-row {
  display: flex;
  gap: var(--space-sm);
}

.mask-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.mask-list-item {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  padding: var(--space-xs) var(--space-sm);
  background: var(--bg-body);
  border: 1px solid var(--border-card);
  border-radius: var(--input-radius);
}

.mask-list-row {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  flex-wrap: wrap;
}

.mask-list-instruction {
  flex: 1;
  font-size: 0.85em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.mask-list-queued {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  font-size: 0.8em;
  color: var(--text-secondary);
  white-space: nowrap;
}

/* AI run row: model choice + Run/Re-run + spinner (while a run is in
   flight, ~9-20s for a real model call) + a status badge that shows either
   "No patch yet", the cached result's model/cost/latency, or a failed run's
   error message (see editor.js's statusBadgeFor). */
.mask-list-ai-row .mask-list-model {
  flex: 1;
  min-width: 10em;
  width: auto;
  padding: var(--space-xs) var(--space-sm);
}

.mask-list-status {
  white-space: normal;
}

.spinner-sm {
  width: 14px;
  height: 14px;
  border-width: 2px;
}

.compare-row {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  margin-top: var(--space-md);
}

.compare-row .form-label {
  margin: 0;
  white-space: nowrap;
}

.compare-row input[type="range"] {
  flex: 1;
}

.editor-panel {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.op-section {
  background: var(--bg-card);
  border: 1px solid var(--border-card);
  border-radius: var(--card-radius);
  overflow: hidden;
}

.op-section-header {
  display: block;
  width: 100%;
  padding: var(--space-sm) var(--space-md);
  background: transparent;
  border: none;
  color: var(--text-heading);
  font-weight: 600;
  font-size: 0.95em;
  text-align: left;
  cursor: pointer;
}

.op-section-header:hover {
  background: var(--bg-body);
}

.op-section-body {
  padding: 0 var(--space-md) var(--space-md);
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.op-section.collapsed .op-section-body {
  display: none;
}

.slider-row {
  display: grid;
  /* Same minmax(0, ...) reasoning as .editor above - without it, the range
     input's own automatic min-content width becomes this row's (and its
     grid-item ancestors') minimum, which is exactly what forced the 375px
     editor breakout this was fixed for. */
  grid-template-columns: 90px minmax(0, 1fr) 40px;
  align-items: center;
  gap: var(--space-sm);
}

.slider-row .form-label {
  margin: 0;
}

.slider-value {
  color: var(--text-secondary);
  font-size: 0.85em;
  text-align: right;
}

@media (max-width: 768px) {
  .editor {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* --- Jobs page (static/jobs.html + static/js/jobs.js) --- */

.jobs-table-wrap {
  overflow-x: auto;
}

.job-progress-bar {
  position: relative;
  width: 160px;
  height: 10px;
  background: var(--bg-body);
  border: 1px solid var(--border-card);
  border-radius: var(--input-radius);
  overflow: hidden;
}

/* --progress is set from jobs.js via style.setProperty, never a literal
   style="..." attribute in markup - same pattern as .editor-before-clip's
   --reveal (static/js/editor.js). */
.job-progress-bar-fill {
  position: absolute;
  inset: 0;
  width: var(--progress, 0%);
  background: var(--accent-blue);
  transition: width var(--transition-normal);
}

.job-progress-label {
  margin-top: var(--space-xs);
  font-size: 0.8em;
  color: var(--text-secondary);
  white-space: nowrap;
}

.job-error {
  margin-top: var(--space-xs);
  color: var(--color-danger);
  font-size: 0.85em;
}

.job-cost {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* --- Presets page (static/presets.html + static/js/presets.js) --- */

.preset-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  margin-top: var(--space-lg);
}

.preset-header {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-md);
}

.preset-preview {
  width: 56px;
  height: 56px;
  flex-shrink: 0;
  border-radius: var(--input-radius);
  overflow: hidden;
  background: var(--bg-body);
  border: 1px solid var(--border-card);
}

.preset-preview-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.preset-preview-placeholder {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-xs);
  text-align: center;
  font-size: 0.65em;
  line-height: 1.2;
  color: var(--text-secondary);
}

.preset-name {
  flex: 1;
  font-weight: 600;
  color: var(--text-heading);
}
