@layer components {
  .finalist-intro {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--r-md);
    padding: var(--s5) var(--s6);
    margin-bottom: var(--s6);
    color: var(--color-text-muted);
  }

  .finalist-intro p {
    margin: 0 0 var(--s3);
    font-size: 14px;
    line-height: 1.6;
  }

  .finalist-intro-rules {
    margin: 0;
    padding-left: var(--s5);
    font-size: 13px;
    color: var(--color-text-subtle);
  }

  .finalist-intro-rules li {
    margin-bottom: var(--s1);
  }

  .finalist-grid {
    display: flex;
    flex-direction: column;
    gap: var(--s4);
  }

  .finalist-panel {
    display: flex;
    flex-direction: column;
    gap: var(--s2);
  }

  .finalist-panel-errors {
    display: flex;
    flex-direction: column;
    gap: var(--s2);
  }

  .finalist-panel-errors:empty {
    display: none;
  }

  .finalist-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 280px;
    gap: var(--s6);
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--r-lg);
    padding: var(--s5);
  }

  @media (max-width: 768px) {
    .finalist-row {
      grid-template-columns: 1fr;
    }
  }

  /* The main column: category header, attribute pills, the large photo preview,
     then the title/caption fields stacked beneath it. Capped so the preview and
     fields stay a comfortable reading width on a wide screen. */
  .finalist-row-main {
    min-width: 0;
    max-width: 480px;
    display: flex;
    flex-direction: column;
    gap: var(--s4);
  }

  .finalist-row-preview {
    width: 100%;
    aspect-ratio: 4/3;
    background: var(--ink-100);
    border-radius: var(--r-md);
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
  }

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

  .finalist-row-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
  }

  .finalist-row-category {
    font-size: 12px;
    font-weight: 500;
    color: var(--color-accent);
    letter-spacing: 0.4px;
  }

  .finalist-row-id {
    font-size: 11px;
    font-family: var(--font-mono);
    color: var(--color-text-subtle);
  }

  /* Read-only pills for the image attributes (HDR, Panorama, …) the participant
     checked at submission. */
  .finalist-row-attrs {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s2);
  }

  .finalist-attr-pill {
    display: inline-flex;
    align-items: center;
    padding: 2px var(--s2);
    border-radius: var(--r-full);
    border: 1px solid var(--color-border);
    background: var(--color-surface-alt);
    font-size: 11px;
    font-weight: 500;
    letter-spacing: 0.2px;
    color: var(--color-text-muted);
  }

  /* Title/caption fields stacked under the preview. Unlike the autosave category
     cards (borderless), this form saves on an explicit button, so the fields
     carry a very subtle resting border to read clearly as inputs, deepening to
     the focus ring + halo on focus (design_system.md → Form elements). */
  .finalist-field {
    margin-bottom: var(--s4);
  }

  .finalist-field-label {
    display: block;
    margin-bottom: var(--s2);
    font-size: 11px;
    font-weight: 500;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--color-text-subtle);
  }

  .finalist-field-input {
    width: 100%;
    font-family: var(--font-ui);
    font-size: 14px;
    color: var(--color-text);
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--r-md);
    padding: 9px 13px;
    transition: border-color var(--transition), box-shadow var(--transition);
    outline: none;
  }

  .finalist-field-hint {
    display: block;
    margin-top: var(--s2);
    font-size: 12px;
    line-height: 1.5;
    color: var(--color-text-subtle);
  }

  .finalist-field-input::placeholder { color: var(--color-text-subtle); }
  .finalist-field-input:hover { border-color: var(--color-border-mid); }

  .finalist-field-input:focus {
    border-color: var(--forest-500);
    box-shadow: 0 0 0 3px var(--forest-100);
  }

  textarea.finalist-field-input {
    resize: vertical;
    min-height: 64px;
  }

  .finalist-row-save {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--s2);
  }

  /* Per-photo submit state: a Submit button once the photo is complete, a
     green-check "Submitted" badge + Reopen after submitting, a muted hint
     otherwise. Sits below the title/caption form on the card. */
  .finalist-submit {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s3);
    margin-top: var(--s4);
    padding-top: var(--s3);
    border-top: 1px solid var(--color-border);
  }

  .finalist-submit:empty {
    display: none;
  }

  .finalist-submit-form {
    margin: 0;
  }

  .finalist-submit-badge {
    display: inline-flex;
    align-items: center;
    gap: var(--s2);
    font-size: 13px;
    font-weight: 500;
    color: var(--success-600);
  }

  .finalist-submit-check {
    display: inline-flex;
    color: var(--success-500);
  }

  .finalist-submit-check svg {
    width: 18px;
    height: 18px;
  }

  /* Hints sit on their own line under the action buttons rather than cramped
     beside them in the flex row. */
  .finalist-submit-hint {
    flex-basis: 100%;
    font-size: 12px;
    color: var(--color-text-subtle);
  }

  .finalist-submitted-count {
    margin-top: var(--s1);
    font-size: 13px;
    font-weight: 500;
    color: var(--success-600);
  }

  .finalist-submitted-count:empty {
    display: none;
  }

  /* Collapsed summary for a submitted photo: a compact one-line button (check,
     category, photo id, title) shown in place of the full panel. The
     finalist-panel controller toggles --collapsed; the full row and the summary
     swap visibility. */
  .finalist-collapsed {
    display: none;
    width: 100%;
    align-items: center;
    gap: var(--s3);
    padding: var(--s4) var(--s5);
    background: var(--success-50, #ebf7f1);
    border: 1px solid var(--success-300, #82c4a3);
    border-radius: var(--r-lg);
    text-align: left;
    font: inherit;
    color: var(--color-text);
    cursor: pointer;
    transition: border-color var(--transition);
  }

  .finalist-collapsed:hover {
    border-color: var(--success-500);
  }

  .finalist-panel--collapsed .finalist-collapsed {
    display: flex;
  }

  .finalist-panel--collapsed .finalist-row {
    display: none;
  }

  .finalist-collapsed .finalist-submit-check {
    display: inline-flex;
    color: var(--success-500);
  }

  .finalist-collapsed .finalist-submit-check svg {
    width: 18px;
    height: 18px;
  }

  .finalist-collapsed-title {
    flex: 1;
    min-width: 0;
    font-weight: 500;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .finalist-collapsed-chevron {
    display: inline-flex;
    color: var(--color-text-subtle);
  }

  .finalist-collapsed-chevron svg {
    width: 12px;
    height: 12px;
  }

  .finalist-row-raw {
    display: flex;
    flex-direction: column;
    gap: var(--s3);
  }

  .finalist-slot {
    border: 1px solid var(--color-border);
    border-radius: var(--r-md);
    padding: var(--s3);
    background: var(--color-surface);
  }

  /* The slot tints green when it holds a live (non-failed) card. Driven off the
     DOM with :has() rather than a server-computed class, so the tint stays
     correct under per-card Turbo updates without a whole-row re-render. */
  .finalist-slot:has(.raw-upload-state:not(.raw-upload-state--failed)) {
    border-color: var(--success-300, #82c4a3);
    background: var(--success-50, #ebf7f1);
  }

  .finalist-slot-header {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--s2);
    margin-bottom: var(--s2);
  }

  .finalist-slot-label {
    font-weight: 600;
    font-size: 14px;
    color: var(--color-text);
  }

  .finalist-slot-hint {
    font-size: 12px;
    color: var(--color-text-muted);
  }

  /* Multiple RAW files (panorama / HDR / focus stack) stack here; the gap keeps
     each "Received" card visually separate instead of borders touching. */
  .raw-upload-state-stack {
    display: flex;
    flex-direction: column;
    gap: var(--s3);
  }

  /* "I don't have a RAW file" declaration, shown under the RAW stack. */
  .finalist-raw-waiver {
    margin-top: var(--s3);
  }

  .finalist-raw-waiver-label {
    display: flex;
    align-items: center;
    gap: var(--s2);
    font-size: 13px;
    color: var(--color-text-muted);
    cursor: pointer;
  }

  .finalist-raw-waiver-checkbox {
    flex-shrink: 0;
    cursor: pointer;
  }

  .raw-upload-zone {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: var(--s5);
    border: 2px dashed var(--color-border-mid);
    border-radius: var(--r-md);
    background: var(--color-surface-alt);
    cursor: pointer;
    transition: var(--transition);
    text-align: center;
  }

  .raw-upload-zone:hover,
  .raw-upload-zone.drag-over {
    border-color: var(--color-cta);
    background: var(--amber-50);
  }

  .raw-upload-zone-icon {
    width: 36px;
    height: 36px;
    color: var(--color-text-muted);
    margin-bottom: var(--s2);
  }

  .raw-upload-zone-icon svg {
    width: 100%;
    height: 100%;
  }

  .raw-upload-zone-label {
    font-size: 13px;
    font-weight: 500;
    color: var(--color-text);
    margin-bottom: var(--s1);
  }

  .raw-upload-zone-hint {
    font-size: 11px;
    font-family: var(--font-mono);
    color: var(--color-text-subtle);
  }

  .raw-upload-state {
    position: relative;
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: var(--s5);
    border-radius: var(--r-md);
    border: 1px solid var(--color-border);
    background: var(--color-surface-alt);
    text-align: center;
    gap: var(--s2);
  }

  /* Per-file delete: a subtle trash button in the card's top-right corner,
     mirroring the preview card's delete affordance. The button_to wrapper form
     is a static, zero-flow element; the button itself is absolutely positioned
     within the (now relative) card. */
  .raw-upload-state-delete-form { margin: 0; }

  .raw-upload-state-delete {
    position: absolute;
    top: var(--s2);
    right: var(--s2);
    width: 24px;
    height: 24px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: none;
    border-radius: var(--r-full);
    background: transparent;
    color: var(--color-text-subtle);
    cursor: pointer;
    transition: color 0.12s ease, background 0.12s ease;
  }

  .raw-upload-state-delete:hover {
    color: var(--danger-600);
    background: var(--danger-50);
  }

  .raw-upload-state-delete svg { width: 13px; height: 13px; }

  .raw-upload-state--processing {
    border-color: var(--color-border-mid);
  }

  .raw-upload-state--processed {
    border-color: var(--success-500);
    background: var(--success-50);
  }

  .raw-upload-state--failed {
    border-color: var(--danger-500);
    background: var(--danger-50);
  }

  /* A small thumbnail of the uploaded file (RAW embedded preview or downscaled
     high-res image) so the participant can confirm at a glance it's the right
     photo. Sits above the badge in the processed state card. */
  .raw-upload-state-thumb {
    width: 100%;
    max-width: 96px;
    aspect-ratio: 4 / 3;
    object-fit: cover;
    border-radius: var(--r-sm);
    background: var(--color-surface);
  }

  .raw-upload-state-filename {
    font-size: 12px;
    font-family: var(--font-mono);
    color: var(--color-text-muted);
    word-break: break-all;
  }

  .raw-upload-state-meta {
    font-size: 11px;
    color: var(--color-text-subtle);
  }

  .raw-upload-state-error {
    font-size: 12px;
    color: var(--danger-600);
  }

  /* Mirrors .raw-upload-state chrome so the in-upload card reads as an in-slot
     state card while the file uploads, then gets swapped for the real state
     card on confirmation. */
  .finalist-upload-progress {
    /* Positioning context + clip so .image-upload-bar (position: absolute) stays
       inside this card instead of escaping to the viewport as a full-width bar. */
    position: relative;
    overflow: hidden;
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding: var(--s5);
    background: var(--color-surface-alt);
    border: 1px solid var(--color-border);
    border-radius: var(--r-md);
    gap: var(--s2);
  }

  /* Anchor the upload bar to the top of the finalist card (the preview card
     keeps it at the bottom via .image-card). */
  .finalist-upload-progress .image-upload-bar {
    top: 0;
    bottom: auto;
  }

  .finalist-upload-progress-meta {
    display: flex;
    justify-content: space-between;
    font-size: 11px;
    font-family: var(--font-mono);
    color: var(--color-text-subtle);
  }

  .alert-finalist {
    display: flex;
    align-items: flex-start;
    gap: var(--s3);
    padding: var(--s4) var(--s5);
    border-radius: var(--r-md);
    border: 1px solid var(--color-accent);
    background: var(--forest-50);
    color: var(--color-accent-dark);
    text-decoration: none;
    margin-bottom: var(--s4);
    transition: var(--transition);
  }

  .alert-finalist:hover {
    background: var(--forest-100);
  }

  .alert-finalist .alert-icon {
    flex-shrink: 0;
    width: 22px;
    height: 22px;
    color: var(--color-accent);
  }

  .alert-finalist .alert-content {
    flex: 1;
  }

  .alert-finalist .alert-title {
    font-weight: 600;
    margin-bottom: var(--s1);
  }

  .alert-finalist-complete {
    display: flex;
    align-items: flex-start;
    gap: var(--s3);
    padding: var(--s4) var(--s5);
    border-radius: var(--r-md);
    border: 1px solid var(--success-500);
    background: var(--success-50);
    color: var(--success-600);
    text-decoration: none;
    margin-bottom: var(--s4);
    transition: var(--transition);
  }

  .alert-finalist-complete:hover {
    border-color: var(--success-600);
  }

  .alert-finalist-complete .alert-icon {
    flex-shrink: 0;
    width: 22px;
    height: 22px;
    color: var(--success-500);
  }

  .alert-finalist-complete .alert-content {
    flex: 1;
  }

  .alert-finalist-complete .alert-title {
    font-weight: 600;
    margin-bottom: var(--s1);
  }

  .finalist-deadline {
    display: inline-flex;
    align-items: center;
    gap: var(--s2);
    padding: var(--s2) var(--s3);
    border-radius: 999px;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-text-subtle);
    font-family: var(--font-mono);
    font-size: 13px;
    white-space: nowrap;
  }

  .finalist-deadline-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--color-accent);
    flex-shrink: 0;
  }
}
