/* px_ui / file_dropzone_html — the form's stylesheet, an asset because the
   server strips a template's style text.
   Head-linked from anvil.yaml native_deps.head_html, so it also styles the
   designer canvas.

   Scoping: the root rule `.px-dropzone` stays bare; every other rule takes
   `:where(.px-dropzone)`, which adds ZERO specificity, so no rule changes
   which declaration wins. No second class is needed anywhere in this file —
   the `.px-dropzone*` stem appears in no host theme, in PX_TOKENS_CSS /
   PX_CHROME_CSS or in PXM_SHARED_CSS.

   No designer gate: every hiding rule here (`:empty`) hides the same node on
   the designer canvas. */

/* =====================================================================
   px_ui / file_dropzone_html — the shared file dropzone (6C-50).

   A real drop target with ARIA, a live region and a status map with
   transition rules, so an 'uploaded' row can never be re-marked
   'uploading' by a late callback.

   THE BOUNDARY THIS COMPONENT DOES NOT CROSS (sol2-#15,
   NON-NEGOTIABLE RULE 1). The accept attribute and the byte cap below
   are UX preflight. The browser is free to ignore accept and the
   operator is free to rename a file, so the SERVER re-validates the
   real bytes before storing anything. That is the estate's standing
   position, stated the same way in
   ``px_shared/training/material_contract.py``.

   COLOUR POLICY (3C-07 / 3T-07 / OD-02 / 3C-03b, hard):
     * ZERO colour literals live in this file — every colour is a
       --px-* token from the unified set.
     * NO var() fallbacks: an unresolved token must fail visibly rather
       than silently paint a stand-in.
   ===================================================================== */

.px-dropzone {
  font-family: var(--px-font);
  color: var(--px-text-primary);
  display: flex;
  flex-direction: column;
  gap: var(--px-space-sm);
  min-width: 0;
}

/* The zone is a <label>: clicking or tapping anywhere on it opens the
   native picker with no JS at all, and the input it labels supplies
   its accessible name. */
:where(.px-dropzone) .px-dropzone-zone {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--px-space-xs);
  text-align: center;
  padding: var(--px-space-xl) var(--px-space-lg);
  border: 2px dashed var(--px-border-strong);
  border-radius: var(--px-radius);
  background: var(--px-bg-card);
  cursor: pointer;
  transition:
    border-color var(--px-transition-fast),
    background var(--px-transition-fast);
}

:where(.px-dropzone) .px-dropzone-zone:hover {
  background: var(--px-bg-elevated);
}

/* The ONE tab stop in this component is the file input, and the input
   is visually hidden — so the visible focus ring is painted by the zone
   it labels. :focus-within is what keeps "one control, one ring" true;
   a second tabindex="0" wrapper would make the same control two tab
   stops. */
:where(.px-dropzone) .px-dropzone-zone:focus-within {
  outline: 2px solid var(--px-accent);
  outline-offset: 2px;
  border-color: var(--px-accent);
}

/* Drag feedback is STYLING ONLY. A dragenter must never move focus:
   the pointer and the keyboard are two independent cursors, and
   stealing focus mid-drag drops a keyboard user somewhere they did not
   navigate to. */
:where(.px-dropzone) .px-dropzone-zone[data-px-dropzone-over="yes"] {
  border-color: var(--px-accent);
  background: var(--px-accent-soft);
}

/* Visually hidden, NOT display:none — a display:none input is not
   focusable, and the keyboard/AT path through this component is the
   input itself. */
:where(.px-dropzone) .px-dropzone-input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

:where(.px-dropzone) .px-dropzone-zone-title {
  font-weight: 600;
  color: var(--px-text-primary);
}

:where(.px-dropzone) .px-dropzone-hint {
  font-size: 0.85rem;
  color: var(--px-text-secondary);
}

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

:where(.px-dropzone) .px-dropzone-list:empty {
  display: none;
}

:where(.px-dropzone) .px-dropzone-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--px-space-xs) var(--px-space-sm);
  padding: var(--px-space-xs) var(--px-space-sm);
  border: 1px solid var(--px-border);
  border-radius: var(--px-radius-sm);
  background: var(--px-bg-card);
  font-size: 0.9rem;
  min-width: 0;
}

:where(.px-dropzone) .px-dropzone-row-name {
  font-weight: 600;
  overflow-wrap: anywhere;
  min-width: 0;
}

:where(.px-dropzone) .px-dropzone-row-size {
  color: var(--px-text-muted);
  white-space: nowrap;
}

:where(.px-dropzone) .px-dropzone-row-state {
  margin-left: auto;
  font-weight: 600;
  white-space: nowrap;
  color: var(--px-text-secondary);
}

:where(.px-dropzone) .px-dropzone-row-message {
  flex-basis: 100%;
  color: var(--px-text-secondary);
  overflow-wrap: anywhere;
}

:where(.px-dropzone) .px-dropzone-row-message:empty {
  display: none;
}

/* The painted state is keyed off the SAME attribute the announced state
   is written from (see pxDropzoneSetState), so the eye and the screen
   reader cannot disagree. The five values are the closed vocabulary the
   Python side enforces. */
:where(.px-dropzone) .px-dropzone-row[data-px-dropzone-state="uploading"] .px-dropzone-row-state {
  color: var(--px-accent);
}

:where(.px-dropzone) .px-dropzone-row[data-px-dropzone-state="uploaded"] {
  border-color: var(--px-success-border);
}

:where(.px-dropzone) .px-dropzone-row[data-px-dropzone-state="uploaded"] .px-dropzone-row-state {
  color: var(--px-success-text);
}

:where(.px-dropzone) .px-dropzone-row[data-px-dropzone-state="failed"] {
  border-color: var(--px-danger-border);
}

:where(.px-dropzone) .px-dropzone-row[data-px-dropzone-state="failed"] .px-dropzone-row-state,
:where(.px-dropzone) .px-dropzone-row[data-px-dropzone-state="failed"] .px-dropzone-row-message {
  color: var(--px-danger-text);
}
