/* ============================================================
   lil friends create — the creator image tool.

   Same neutral theme as the admin console (../../admin/site/css/admin.css):
   slate greys, a single blue for primary and active states, system fonts. It
   does NOT use the app's brand palette — this is a tool, not a brand surface.
   The tokens below are copied from admin.css rather than imported: separate
   workers have separate asset directories and there is no build step to share
   through. Keep them in sync by hand if the console's palette ever moves.
   ============================================================ */

:root {
  /* Tells the browser to render its own chrome — the file picker button, the
     search field's clear affordance, scrollbars — in whichever theme is active.
     Without it the native bits stay stubbornly light on a dark page. */
  color-scheme: light dark;

  --bg:            #ffffff;
  --surface:       #f8fafc;  /* rail, subtle fills */
  --surface-hover: #f1f5f9;
  --border:        #e2e8f0;
  --border-strong: #cbd5e1;

  --ink:    #1e293b;
  --muted:  #64748b;
  --subtle: #94a3b8;

  /* The one accent: a neutral blue for primary buttons and selected states. */
  --accent:      #2563eb;
  --accent-hover:#1d4ed8;
  --accent-weak: #eff4ff;
  --accent-ring: rgba(37, 99, 235, 0.35);
  --on-accent:   #ffffff;  /* label on a filled accent button */

  /* Status only. */
  --ok:          #16a34a;
  --ok-weak:     #f0fdf4;
  --danger:      #dc2626;
  --danger-weak: #fef2f2;
  --danger-edge: #f0c2c2;

  --radius: 8px;
  --rail:   420px;
}

/* ---- dark mode ----
   Follows the OS setting; there is no in-page switch. Copied from admin.css
   along with the light tokens above — keep the two in sync by hand. Only the
   tokens move, so the layout and the light theme are untouched.

   The greys here are NEUTRAL, not the slate of the light theme. Inverting slate
   directly gives a navy page: slate carries a blue cast that is invisible at 98%
   lightness and unmistakable at 5%, and it fights the one place blue is supposed
   to mean something. So the surfaces run pure grey on near-black and blue
   appears only as the accent. Two other things a straight inversion gets wrong:
   the accent lifts to a lighter blue (a 600-weight blue on a dark ground is too
   dim to lead, and white on it is unreadable — hence --on-accent going dark),
   and each status colour lifts while its weak fill becomes a deep, desaturated
   tint of itself rather than a wash. */

@media (prefers-color-scheme: dark) {
  :root {
    --bg:            #0a0a0a;
    --surface:       #141414;  /* the rail sits *above* the page here, not below */
    --surface-hover: #1f1f1f;
    --border:        #272727;
    --border-strong: #3d3d3d;

    --ink:    #ededed;
    --muted:  #a3a3a3;
    --subtle: #7c7c7c;  /* lifted off the light theme's ratio: dim-on-dark reads worse than dim-on-light */

    --accent:      #60a5fa;
    --accent-hover:#93c5fd;
    --accent-weak: #16202e;  /* dark enough to read as a grey pill that happens to be blue */
    --accent-ring: rgba(96, 165, 250, 0.4);
    --on-accent:   #0a0a0a;

    --ok:          #4ade80;
    --ok-weak:     #13251a;
    --danger:      #f87171;
    --danger-weak: #2a1516;
    --danger-edge: #522626;
  }

  /* The paw is a single flat ink fill inside an <img>, so CSS can't reach the
     path to recolour it. Inverting the whole image is exact for a one-colour
     mark and turns that ink into the warm off-white the dark theme wants. */
  .brand-mark { filter: invert(1); }
}

* { box-sizing: border-box; }

/* The `hidden` attribute is only a `display: none` default, so any element with
   an explicit display (the dropzone's two states, both flex) ignores it. The
   page toggles visibility with `hidden` throughout — make it actually win. */
[hidden] { display: none !important; }

body {
  margin: 0;
  font: 15px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
  color: var(--ink);
  background: var(--bg);
}

/* ---- sign-in gate ----
   A centred card in front of the whole tool. Deliberately plain: it is the
   first thing a creator arriving from a campaign sees, and there is nothing to
   decide on it beyond typing an email. */

.gate { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 24px; }
.gate-card {
  width: 100%;
  max-width: 400px;
  display: flex;
  flex-direction: column;
  gap: 18px;
  padding: 28px;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--bg);
}
.gate-step { display: flex; flex-direction: column; gap: 10px; }
.gate-step h1 { margin: 0; font-size: 21px; }
.gate-step .lede { margin: 0; color: var(--muted); font-size: 14px; line-height: 1.5; }
.gate-step .field { font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); }
.gate-step input {
  font: inherit;
  height: 44px;
  padding: 8px 12px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--bg);
  color: var(--ink);
}
.gate-step input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-ring);
}
/* The code is six digits read off a phone — wide tracking makes it far easier
   to check against the email while typing. */
/* Tracking is loose enough to check the code against the email digit by digit,
   but tight enough that 8 of them still fit — the OTP length is a Supabase
   project setting, so don't assume 6. */
#code { font-size: 19px; letter-spacing: 0.16em; text-align: center; font-variant-numeric: tabular-nums; }
.gate-step button[type="submit"] { height: 44px; }
.gate-foot { margin: 0; font-size: 12px; line-height: 1.45; color: var(--subtle); }
.gate-foot a { color: var(--muted); }

/* ---- account ---- */

.account { margin: -8px 2px 0; font-size: 12px; color: var(--subtle); }

/* ---- shell: fixed rail, scrolling feed ----
   Each pane scrolls independently so a long feed never pushes the controls off
   screen — the whole point of the two-pane layout is that you can keep firing
   jobs while earlier ones are still running. */

.shell { display: flex; align-items: flex-start; min-height: 100vh; }

.rail {
  width: var(--rail);
  flex: 0 0 var(--rail);
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
  background: var(--surface);
  border-right: 1px solid var(--border);
  padding: 22px 20px 28px;
  display: flex;
  flex-direction: column;
  gap: 22px;
}

.feed {
  flex: 1;
  min-width: 0;
  padding: 24px 28px 48px;
  display: flex;
  flex-direction: column;
  gap: 18px;
}

/* ---- brand ---- */

.brand { display: flex; align-items: center; gap: 11px; padding: 2px 2px 0; }
.brand-mark { height: 34px; width: auto; flex: none; }
.brand-text { font-weight: 650; letter-spacing: -0.01em; line-height: 1.15; }
.brand-text span { display: block; font-weight: 400; font-size: 12px; color: var(--muted); }

/* ---- rail sections ---- */

.group { display: flex; flex-direction: column; gap: 8px; }
.group-row { display: flex; align-items: baseline; justify-content: space-between; }
.group-heading {
  margin: 0;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--subtle);
}

/* ---- photo ---- */

.dropzone {
  border: 1px dashed var(--border-strong);
  border-radius: 10px;
  background: var(--bg);
  padding: 14px;
  cursor: pointer;
  transition: border-color 0.12s ease, background 0.12s ease;
}
.dropzone:hover { border-color: var(--accent); }
.dropzone:focus-visible { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-ring); }
/* Dragging over: solid accent, so "let go here" is unambiguous. */
.dropzone.is-over { border-style: solid; border-color: var(--accent); background: var(--accent-weak); }

.dropzone-empty { text-align: center; padding: 18px 8px; }
.dropzone-title { margin: 0 0 3px; font-weight: 600; }
.dropzone-hint { margin: 0; font-size: 13px; color: var(--muted); }
kbd {
  font: inherit;
  font-size: 12px;
  padding: 1px 5px;
  border: 1px solid var(--border-strong);
  border-bottom-width: 2px;
  border-radius: 5px;
  background: var(--bg);
  color: var(--muted);
}

.dropzone-loaded { display: flex; gap: 12px; align-items: center; }
.preview { width: 76px; height: 76px; object-fit: cover; border-radius: var(--radius); flex: none; }
.photo-meta { min-width: 0; display: flex; flex-direction: column; gap: 3px; align-items: flex-start; }
.photo-name {
  margin: 0;
  font-weight: 500;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.photo-dims { margin: 0; font-size: 12px; color: var(--muted); }

/* ---- scene picker ---- */

.search, .prompt {
  font: inherit;
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--bg);
  color: var(--ink);
}
.search { height: 40px; }
.prompt { resize: vertical; line-height: 1.45; }
.search:focus, .prompt:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-ring);
}

/* 137 scenes: a scrolling chip field beats a select (you can see neighbours)
   and beats a long list (six fit per row). Order is the catalog's own, which is
   already grouped by theme. */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  max-height: 260px;
  overflow-y: auto;
  padding: 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
}
.chip {
  font: inherit;
  font-size: 13px;
  padding: 5px 10px;
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  background: var(--bg);
  color: var(--ink);
  cursor: pointer;
  white-space: nowrap;
}
.chip:hover { background: var(--surface-hover); }
.chip[aria-selected="true"] {
  background: var(--accent-weak);
  border-color: var(--accent);
  color: var(--accent);
  font-weight: 600;
}
.chips-empty { margin: 4px; font-size: 13px; color: var(--subtle); }

.count { margin: 0; font-size: 12px; color: var(--subtle); text-align: right; }

/* ---- buttons (copied from admin.css so the two tools match) ---- */

button {
  font: inherit;
  font-weight: 600;
  padding: 9px 16px;
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  background: var(--accent);
  color: var(--on-accent);
  cursor: pointer;
  transition: background 0.12s ease, border-color 0.12s ease;
}
button:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
button:disabled { opacity: 0.5; cursor: default; }
button.ghost {
  background: var(--bg);
  color: var(--ink);
  border-color: var(--border-strong);
  font-weight: 500;
  padding: 5px 10px;
}
button.ghost:hover:not(:disabled) { background: var(--surface-hover); border-color: var(--border-strong); }
button.danger {
  background: var(--bg);
  color: var(--danger);
  border-color: var(--danger-edge);
  font-weight: 500;
  padding: 5px 10px;
}
button.danger:hover { background: var(--danger-weak); border-color: var(--danger-edge); }
/* A text-only affordance for "undo my edit" — a button here would compete with
   Generate for attention. */
button.linky {
  background: none;
  border: 0;
  padding: 0;
  color: var(--accent);
  font-size: 12px;
  font-weight: 500;
}
button.linky:hover { background: none; text-decoration: underline; }

.actions { display: flex; align-items: center; gap: 12px; }
.actions button { flex: 1; height: 42px; }
.shortcut { margin: 0; font-size: 12px; color: var(--subtle); }

/* ---- rail footer ----
   margin-top: auto pushes it to the base of the rail when the controls don't
   fill the height, and it simply follows them when they do. */

.rail-foot { margin-top: auto; padding-top: 18px; }
.rail-foot p { margin: 0 0 4px; font-size: 12px; line-height: 1.45; color: var(--subtle); }
.rail-foot p:last-child { margin-bottom: 0; }
.rail-foot a { color: var(--muted); }

/* ---- messages ---- */

.banner { margin: 0; padding: 10px 14px; border-radius: var(--radius); font-size: 14px; }
.banner.error { background: var(--danger-weak); color: var(--danger); }
.banner.ok { background: var(--ok-weak); color: var(--ok); }
.banner:empty { display: none; }

/* ---- feed ---- */

/* The measure is set so the longest item still lands on one line — a bulleted
   list whose items wrap loses the scannability that made it a list. Note that
   ch is the width of "0", which in this proportional font is 1.32x an average
   character, so the value is NOT the character count it looks like: 48ch minus
   the marker indent leaves room for ~61. */
.feed-empty {
  margin: 0;
  padding-left: 1.15em;  /* room for the markers, which sit outside the text */
  max-width: 48ch;
  color: var(--subtle);
}
.feed-empty li + li { margin-top: 5px; }

.job {
  display: flex;
  gap: 18px;
  padding: 16px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--bg);
}
.job-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 10px; }
.job-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.job-scene { font-weight: 600; }
.job-status {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--surface-hover);
  color: var(--muted);
}
.job-status.running { background: var(--accent-weak); color: var(--accent); }
.job-status.done    { background: var(--ok-weak); color: var(--ok); }
.job-status.error   { background: var(--danger-weak); color: var(--danger); }
.job-elapsed { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }

.job-prompt { margin: 0; font-size: 13px; line-height: 1.45; color: var(--muted); }
.job-meta { margin: 0; font-size: 12px; color: var(--subtle); }
.job-error { margin: 0; font-size: 13px; color: var(--danger); }
.job-source { width: 64px; height: 64px; object-fit: cover; border-radius: var(--radius); flex: none; }

.job-out { display: block; max-width: 320px; max-height: 560px; border-radius: 10px; }
/* The placeholder holds the output's own 1344x2912 shape, so a card doesn't
   jump when the image lands. */
.job-pending {
  width: 180px;
  aspect-ratio: 1344 / 2912;
  border-radius: 10px;
  background: linear-gradient(100deg, var(--surface) 30%, var(--surface-hover) 50%, var(--surface) 70%);
  background-size: 300% 100%;
  animation: shimmer 1.4s linear infinite;
}
@keyframes shimmer { from { background-position: 150% 0; } to { background-position: -50% 0; } }
@media (prefers-reduced-motion: reduce) { .job-pending { animation: none; } }

.job-actions { display: flex; gap: 8px; flex-wrap: wrap; }
/* Copy swaps its label to "Copied" / "Failed" on click. Without a floor the
   button resizes and drags every button after it sideways under the cursor. */
.job-actions button.copy { min-width: 76px; }

/* ---- responsive: desktop-first, but must survive a narrow window ---- */

@media (max-width: 900px) {
  .shell { flex-direction: column; }
  .rail {
    width: auto;
    flex: none;
    position: static;
    height: auto;
    overflow: visible;
    border-right: 0;
    border-bottom: 1px solid var(--border);
  }
  .feed { padding: 20px 18px 40px; width: 100%; }
  .job { flex-direction: column; }
}
