/* Delivery Photo Poster - shared design system
   Plain CSS, no build step, no framework. Loaded by every page. */

:root {
  --color-bg: #f3f4f6;
  --color-surface: #ffffff;
  --color-border: #e5e7eb;
  --color-text: #1f2937;
  --color-text-muted: #6b7280;
  --color-accent: #2563eb;
  --color-accent-hover: #1d4ed8;
  --color-accent-contrast: #ffffff;
  --color-success-bg: #ecfdf5;
  --color-success-border: #a7f3d0;
  --color-success-text: #065f46;
  --color-error-bg: #fef2f2;
  --color-error-border: #fecaca;
  --color-error-text: #991b1b;
  /* "Deliberate state, not a fault" - the test-mode banner, and the indicative crop preview.
     Shared so those two never drift apart: both mean "this is working as intended, and it
     is not what you might assume". */
  --color-warn-bg: #fffbeb;
  --color-warn-border: #fcd34d;
  --color-warn-text: #78350f;
  --color-disabled-bg: #e5e7eb;
  /* Darker than a hair-thin #9ca3af: the primary CTA spends time in this disabled state
     (Send, before a caption exists), and it should read as "not yet ready", not "faded out". */
  --color-disabled-text: #6b7280;
  --radius: 10px;
  --radius-sm: 6px;
  --shadow-card: 0 1px 2px rgba(16, 24, 40, 0.04), 0 4px 12px rgba(16, 24, 40, 0.06);
  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial,
    sans-serif;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: var(--font-sans);
  background: var(--color-bg);
  color: var(--color-text);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

h1 {
  font-size: 1.375rem;
  font-weight: 600;
  margin: 0 0 1.25rem;
  color: var(--color-text);
}

a {
  color: var(--color-accent);
}

/* Layout shells */

.page {
  min-height: 100vh;
  padding: 2rem 1rem;
}

.page--centered {
  display: flex;
  align-items: center;
  justify-content: center;
}

.page-header {
  max-width: 960px;
  margin: 0 auto 1.5rem;
}

.page-body {
  max-width: 960px;
  margin: 0 auto;
}

/* Cards */

.card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  padding: 1.75rem;
}

.card--login {
  width: 100%;
  max-width: 360px;
}

.card--form {
  max-width: 480px;
  margin: 0 auto;
}

.card--entry {
  display: grid;
  grid-template-columns: minmax(0, 280px) 1fr;
  gap: 1.5rem;
  margin-bottom: 1.25rem;
}

@media (max-width: 720px) {
  .card--entry {
    grid-template-columns: 1fr;
  }
}

/* Notices */

.notice {
  border-radius: var(--radius-sm);
  padding: 0.75rem 1rem;
  margin-bottom: 1.25rem;
  font-size: 0.9375rem;
}

.notice--success {
  background: var(--color-success-bg);
  border: 1px solid var(--color-success-border);
  color: var(--color-success-text);
}

.notice--error {
  background: var(--color-error-bg);
  border: 1px solid var(--color-error-border);
  color: var(--color-error-text);
}

.notice--empty {
  text-align: center;
  color: var(--color-text-muted);
  background: var(--color-surface);
  border: 1px dashed var(--color-border);
}

/* Forms */

.field {
  margin-bottom: 1.1rem;
}

.field label,
.field legend {
  display: block;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--color-text);
  margin-bottom: 0.375rem;
  padding: 0;
  border: none;
}

fieldset.field {
  border: none;
  margin: 0 0 1.1rem;
  padding: 0;
}

.field-hint {
  font-size: 0.8125rem;
  color: var(--color-text-muted);
  margin-top: 0.25rem;
}

input[type="text"],
input[type="password"],
input[type="file"],
select,
textarea {
  width: 100%;
  font: inherit;
  font-size: 1rem;
  padding: 0.625rem 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text);
  min-height: 44px;
}

textarea {
  min-height: 88px;
  resize: vertical;
}

input:focus,
select:focus,
textarea:focus {
  outline: none;
  border-color: var(--color-accent);
  box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15);
}

/* Visually hidden but still in the layout and focusable - used for the file input once
   submission-form.js replaces it with the Take/Choose buttons. Not display:none, so the
   input still participates in the form and can be clicked programmatically. */
/* `input.sr-only` as well as `.sr-only`, and this is load-bearing rather than belt-and-
   braces. The file input above matches `input[type="file"]`, specificity (0,1,1), while a
   bare `.sr-only` is only (0,1,0) - so `width: 100%` won and the "hidden" input stayed a
   full-width element. Absolutely positioned and 375px wide, it pushed the submission form
   44px past the edge of a phone screen, giving every salesperson a sideways-scrolling
   form. `input.sr-only` ties at (0,1,1) and wins on source order. */
.sr-only,
input.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
  min-height: 0;
}

.media-buttons {
  display: flex;
  flex-wrap: wrap;
  gap: 0.625rem;
}

.media-buttons .btn {
  flex: 1 1 auto;
}

.media-filename {
  overflow-wrap: anywhere;
}

.radio-row {
  display: flex;
  gap: 1.5rem;
}

.radio-row label {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.9375rem;
  font-weight: 400;
  min-height: 44px;
}

.radio-row input {
  width: 1.1rem;
  height: 1.1rem;
}

/* Buttons */

button {
  font: inherit;
  cursor: pointer;
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  border: none;
  border-radius: var(--radius-sm);
  padding: 0.625rem 1.25rem;
  font-size: 0.9375rem;
  font-weight: 600;
  min-height: 44px;
  text-decoration: none;
  transition: background-color 120ms ease, opacity 120ms ease;
}

.btn-primary {
  background: var(--color-accent);
  color: var(--color-accent-contrast);
  width: 100%;
}

.btn-primary:hover:not(:disabled) {
  background: var(--color-accent-hover);
}

.btn-secondary {
  background: var(--color-surface);
  color: var(--color-text);
  border: 1px solid var(--color-border);
}

.btn-secondary:hover:not(:disabled) {
  background: var(--color-bg);
}

/* Destructive, and there is no undo. Deliberately quiet - an outline rather than a filled
   red block - so it never competes with "Send to SocialPilot" for the eye, but unmistakably
   not the same kind of action as the neutral secondary buttons beside it. */
.btn-danger {
  background: var(--color-surface);
  color: var(--color-error-text);
  border: 1px solid var(--color-error-border);
}

.btn-danger:hover:not(:disabled) {
  background: var(--color-error-bg);
}

.btn:disabled {
  background: var(--color-disabled-bg);
  color: var(--color-disabled-text);
  cursor: not-allowed;
}

/* Inputs get a custom focus ring above; buttons/links relied on the UA default. Match them
   so keyboard focus is visible and consistent across every interactive control. */
.btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.35);
}

/* The browser's own [hidden] rule is display:none, which any author `display` beats - and
   .btn sets inline-flex. Without this, `el.hidden = true` on a button does nothing at all. */
.btn[hidden] {
  display: none;
}

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.625rem;
  margin-top: 0.75rem;
}

/* Review entry cards */

.entry-media img,
.entry-media video {
  display: block;
  width: 100%;
  max-height: 320px;
  /* contain, not cover: this is the gate where a reviewer confirms exactly what will be
     published, so the media must never be cropped. The letterbox sits on --color-bg. */
  object-fit: contain;
  border-radius: var(--radius-sm);
  background: var(--color-bg);
}

.entry-title {
  font-size: 1rem;
  font-weight: 600;
  margin: 0 0 0.375rem;
  overflow-wrap: anywhere;
}

.entry-vehicle {
  font-weight: 400;
}

.entry-store {
  font-size: 0.9375rem;
  color: var(--color-text-muted);
  font-weight: 400;
  margin: 0 0 0.75rem;
  overflow-wrap: anywhere;
}

.entry-date {
  font-size: 0.8125rem;
  color: var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  margin: 0 0 0.75rem;
}

.entry-notes {
  font-size: 0.875rem;
  color: var(--color-text-muted);
  margin: 0 0 0.75rem;
  overflow-wrap: anywhere;
}

.entry-caption {
  margin: 0;
  overflow-wrap: anywhere;
}

/* Pending count next to the heading - a quiet at-a-glance workload signal. */
.count {
  color: var(--color-text-muted);
  font-weight: 400;
}

.pager {
  display: flex;
  justify-content: space-between;
  gap: 0.625rem;
  margin-top: 0.5rem;
}

/* Home page: two doors, each with a line naming who it is for. */

.home-intro {
  margin: -0.75rem 0 1.5rem;
}

.home-choices {
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
}

/* The explanatory line belongs to the button above it, so pull it close and leave a
   clear gap before the next choice - otherwise the two pairs read as one list of four. */
.home-choices .field-hint {
  margin: 0 0 1rem;
}

.home-choices .field-hint:last-child {
  margin-bottom: 0;
}

.home-choices .btn {
  width: 100%;
}

/* Screen header: title on the left, log out on the right. Wraps rather than squashing
   the title on a narrow phone. */

.screen-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}

.screen-header h1 {
  margin-bottom: 0.75rem;
}

.logout-form {
  margin: 0;
}

/* Quiet by design. Logging out is destructive to someone mid-task, so it should be
   findable but never compete with the action the user actually came to do.
   The padding keeps a full 44px tap target on a phone while the text stays small - the
   hit area grows, the visual weight does not, because there is no background or border. */
.btn-link {
  background: none;
  border: none;
  color: var(--color-text-muted);
  font-size: 0.875rem;
  font-weight: 400;
  text-decoration: underline;
  padding: 0.75rem 0.5rem;
  min-height: 44px;
}

.btn-link:hover {
  color: var(--color-text);
}

/* Test mode: the app is fully working but nothing reaches SocialPilot. Amber rather than
   red - this is a deliberate state, not a fault - but distinct enough from the green and
   red flash notices that it is not mistaken for one of them. It sits above the flashes and
   persists, so it must not look like a message that can be dismissed. */
.notice--test-mode {
  background: var(--color-warn-bg);
  border: 1px solid var(--color-warn-border);
  color: var(--color-warn-text);
}

/* A delivery the app cannot post for you (currently: video, which SocialPilot's automation
   does not support). Blue rather than amber or red - this is an instruction, not a warning
   and not a fault, and the reviewer has a clear job to do. */
.notice--manual {
  background: #eff6ff;
  border: 1px solid #bfdbfe;
  color: #1e3a8a;
}

/* A statement of fact that asks nothing of the reviewer - deliberately quieter than
   notice--manual, which is blue because it hands them a job to go and do. Used when a photo
   cannot be cropped: the delivery still posts normally, just uncropped. */
.notice--quiet {
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
}

/* Facebook framing (crop.js) ------------------------------------------------------------
   Two states, and they must never be mistaken for one another:

   AMBER + DASHED  = indicative. A Facebook-shaped preview of a photo that is still stored,
                     and still posted, whole. Nothing has been committed.
   GREEN + SOLID   = applied. The stored file IS this framing. Rendered by the template from
                     server state, so it does not depend on any of this script having run.

   Amber is already this app's "deliberate state, not a fault" colour (the test-mode
   banner); green is already "this worked". Reusing them costs the reviewer no new
   vocabulary. */

.crop-tools {
  margin-top: 0.75rem;
}

/* The preview frame. Fixed shape, overflow hidden; the photo inside is what moves. */
.crop-frame {
  position: relative;
  width: 100%;
  margin: 0 auto;
  overflow: hidden;
  /* Transparent by default so the frame never changes size when the border turns amber. */
  border: 2px solid transparent;
  border-radius: var(--radius-sm);
  background: var(--color-bg);
  /* Without this the browser claims the gesture for panning and the drag never starts. */
  touch-action: none;
}

.crop-frame:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.35);
}

/* Dashed, not solid: a dashed edge reads as provisional everywhere else in software too,
   and it is the cue that survives a greyscale screenshot or a colour-blind reviewer. */
.crop-frame--indicative {
  border-color: var(--color-warn-border);
  border-style: dashed;
}

.crop-frame--pannable {
  cursor: grab;
}

.crop-frame--dragging {
  cursor: grabbing;
}

/* Overrides .entry-media img, which is the plain full-width preview this photo used to be.
   Position and size are set in percentages by crop.js on every render. */
.crop-frame img {
  position: absolute;
  max-width: none;
  max-height: none;
  border-radius: 0;
  background: none;
  object-fit: fill;
  user-select: none;
  -webkit-user-drag: none;
}

/* Sits ON the photo, because the photo is the thing being misread. Someone scanning a queue
   of twenty cards has to be able to tell preview from committed without reading a word of
   body copy - and a committed crop carries no chip at all. */
.crop-chip {
  position: absolute;
  top: 0.5rem;
  left: 0.5rem;
  z-index: 1;
  background: var(--color-warn-bg);
  color: var(--color-warn-text);
  border: 1px solid var(--color-warn-border);
  border-radius: 999px;
  padding: 0.125rem 0.5rem;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* Shape picker ---------------------------------------------------------------------------
   Two columns, because four buttons in a ~280px media column will not sit on one row and a
   2x2 block is easier to scan than a ragged wrap. */
.crop-ratios {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.5rem;
  margin-top: 0.75rem;
}

/* Name on the first line, ratio on the second: the shape is what the reviewer is choosing,
   the numbers are the confirmation. Stacking them keeps the button narrow enough for two
   per row without truncating "Landscape". */
.crop-ratio {
  flex-direction: column;
  gap: 0;
  padding: 0.5rem 0.5rem;
  font-size: 0.875rem;
  line-height: 1.25;
}

.crop-ratio-num {
  font-size: 0.75rem;
  font-weight: 400;
  color: var(--color-text-muted);
}

/* The chosen shape decides what the whole preview above is doing, so the selected button
   takes the accent fill rather than a subtle tint. */
.crop-ratio[aria-pressed="true"] {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-accent-contrast);
}

.crop-ratio[aria-pressed="true"] .crop-ratio-num {
  color: inherit;
  opacity: 0.85;
}

/* The pointer is still sitting on the button the reviewer just clicked, and
   .btn-secondary:hover is the more specific rule - without this it repaints the selected
   shape pale grey while its text stays white, which is unreadable. */
.crop-ratio[aria-pressed="true"]:hover:not(:disabled) {
  background: var(--color-accent-hover);
  border-color: var(--color-accent-hover);
}

/* Zoom ------------------------------------------------------------------------------------ */

.crop-adjust {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  gap: 0.625rem;
  margin-top: 0.625rem;
}

.crop-adjust label {
  font-size: 0.875rem;
  font-weight: 600;
}

.crop-adjust input[type="range"] {
  width: 100%;
  /* Keeps the 44px target used by every other control here; the slider itself stays thin. */
  min-height: 44px;
  accent-color: var(--color-accent);
}

.crop-adjust input[type="range"]:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.crop-adjust input[type="range"]:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.35);
  border-radius: var(--radius-sm);
}

/* The sentence that says what will actually be posted ------------------------------------- */

.crop-state {
  border-radius: var(--radius-sm);
  padding: 0.625rem 0.75rem;
  margin: 0.75rem 0 0;
  font-size: 0.8125rem;
  line-height: 1.45;
}

/* Indicative. Deliberately NOT quiet: this is the one message on the card that stops a
   reviewer believing a crop is applied when it is not. */
.crop-state--preview {
  background: var(--color-warn-bg);
  border: 1px solid var(--color-warn-border);
  color: var(--color-warn-text);
}

/* "Original" selected: the preview and the stored file agree, so there is nothing to warn
   about. Neutral rather than amber, or the warning colour would come to mean nothing. */
.crop-state--exact {
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
}

/* Committed. The crop IS the stored file now. */
.crop-state--applied {
  background: var(--color-success-bg);
  border: 1px solid var(--color-success-border);
  color: var(--color-success-text);
}

.crop-actions {
  /* Apply takes the room that is going; Reset stays exactly as wide as its label. */
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0.5rem;
}

.crop-actions .btn {
  width: auto;
  padding-left: 0.875rem;
  padding-right: 0.875rem;
}

.crop-status {
  margin-top: 0.5rem;
}

.crop-error,
.crop-unavailable {
  margin: 0.75rem 0 0;
  font-size: 0.8125rem;
}

/* An applied crop, with JS off as much as with it on. The green border is the counterpart
   of the dashed amber one above, and max-height goes because the stored file is now the
   exact shape that will be posted - letterboxing it inside a 320px box would put grey bars
   around the one image on this card that is not an approximation of anything. */
.entry-media--cropped img {
  border: 2px solid var(--color-success-border);
  max-height: none;
}

/* Wraps a committed crop so its chip has something to sit on. No JS involved - the chip is
   part of the server-rendered applied state. */
.crop-committed {
  position: relative;
}

.crop-chip--applied {
  background: var(--color-success-bg);
  color: var(--color-success-text);
  border-color: var(--color-success-border);
}
