/* ═══════════════════════════════════════════════════════════════════════════
 *  site.css — the two pages the SITE owns, rather than a tool.
 *
 *  AI NOTES
 *  ────────
 *  WHO LOADS THIS: the front page and Settings, by passing
 *  'css' => ['/assets/site.css'] to shell_configure(). It is the same slot a
 *  tool's own assets/app.css occupies, used by the two pages that are not in a
 *  tool folder and so have no assets/app.css of their own.
 *
 *  KEEP IT THIS SHORT. Cards, buttons, inputs, notes and the page column all
 *  come from assets/base.css and are shared with every tool. Anything added
 *  here that another tool could want belongs there instead.
 * ═══════════════════════════════════════════════════════════════════════════ */

/* ══════════════════════════ THE FRONT PAGE SETTLING, ONE CARD AT A TIME
 *
 * Each heading and each card fades up from slightly below, a little after the
 * one before it. index.php numbers them in document order as --i; the delay
 * below is the only thing that turns that number into time.
 *
 * ═══ THE from STATE IS IN THE KEYFRAME AND NOWHERE ELSE ═══
 * This is the whole safety of it, and it is worth saying plainly: the cards'
 * ordinary state is visible. There is no `opacity: 0` sitting in a rule
 * waiting for JavaScript or an animation to undo it — the usual way an
 * animated list ends up as a permanently blank page on the one browser that
 * did not run it. animation-fill-mode: backwards holds the from-state during
 * the delay; if the animation never runs at all, nothing was ever hidden.
 *
 * ═══ RESPONSIVE WITHOUT A MEDIA QUERY ═══
 * A grid lays out in document order, so one number is right at every width:
 * three across on a desktop reads left-to-right along each row, one column on
 * a phone reads straight down. The same --i describes both.
 *
 * ═══ AND IT IS CAPPED, WHICH MATTERS MORE ON A PHONE ═══
 * min(…, 420ms) is not tidiness. Narrow, the cards stack into one column, so
 * a site with twenty tools would have twenty rows and the last card would be
 * over a second late — an animation long enough to be a wait. Past the cap
 * everything remaining arrives together, which is the right failure: the top
 * of the page still cascades, the bottom stops making anybody watch.
 *
 * transform AND opacity ONLY. Both are compositor properties, so this costs no
 * layout and no repaint — on a phone that is the difference between a settle
 * and a stutter.
 */
@keyframes tools-settle {
  from { opacity: 0; transform: translateY(.7rem) scale(.99); }
  /* NO `to`. The animation ends at whatever the element's own styles say,
     which means this keyframe never has to know what a card looks like —
     including the hover shadow and the background picture it has not been
     told about. */
}

.cards > .card,
.group {
  animation: tools-settle .45s cubic-bezier(.22, .68, .28, 1) backwards;
  animation-delay: min(calc(var(--i, 0) * 55ms), 420ms);
}

/* STILL, FOR ANYBODY WHO ASKED FOR STILL. The page arrives in its finished
   state with nothing moving — which is exactly what removing the animation
   leaves behind, because the finished state is the element's own. */
@media (prefers-reduced-motion: reduce) {
  .cards > .card,
  .group { animation: none; }
}

/* ─────────────────────────────────────────────────────── the Settings form */

/* A row of category names. Narrower than the page, because a sixty-character
   name in a 62rem field is a field pretending it needs the room. */
body[data-page="settings"] .row { display: block; margin-bottom: .5rem; max-width: 40rem; }
body[data-page="settings"] .row input[type=text] { font-size: .9rem; }

/* One tool's settings: the name and category side by side, the context under
   both, and the whole thing in a card so the list reads as a list of tools. */
.tool-row { margin-bottom: 1rem; }
.tool-row .path { margin: 0 0 .9rem; font-family: var(--mono); font-size: .74rem; color: var(--ink-faint); }
.tool-row .pair { display: grid; gap: 1rem; grid-template-columns: 1fr minmax(10rem, 16rem); }
.tool-row textarea { min-height: 3.6rem; }
.tool-row .why { margin: .35rem 0 0; font-size: .73rem; color: var(--ink-faint); }

@media (max-width: 40rem) {
  .tool-row .pair { grid-template-columns: 1fr; }
}

/* The save bar sits at the bottom of the form and stays reachable on a long
   page without covering the last field. */
.save-bar {
  position: sticky; bottom: 0; display: flex; gap: .6rem; align-items: center;
  margin-top: 1.4rem; padding: .8rem 0;
  background: linear-gradient(to top, var(--bg) 70%, transparent);
}

/* ──────────────────────────────────────────────────────── the front page */

footer {
  margin-top: 2.5rem; padding-top: 1.2rem;
  border-top: 1px solid var(--line);
  color: var(--ink-faint); font-size: .75rem;
}

/* THE MACHINE CHIP MOVED TO base.css. It was here when the front page was
   the only thing that named a device; the Recorder's settings page now
   names two of them, and a tool cannot see this file — site.css is loaded
   only by the pages the SITE owns. Copying it into a tool's sheet would
   have been the start of two chips. See .node there. */


/* ═══════════════════════════════════════ the picture behind a card, in Settings
 *
 * TWO STATES: a row with a thumbnail and some buttons, and — once there is a
 * picture to frame — the cropper underneath it.
 */
.card-pic { margin-top: 1.1rem; padding-top: .9rem; border-top: 1px solid var(--line); }
.card-pic > label {
  display: block; margin-bottom: .5rem;
  font-size: .72rem; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-faint); font-weight: 600;
}

.card-pic-row { display: flex; gap: .9rem; align-items: center; flex-wrap: wrap; }

/* THE THUMBNAIL IS THE CARD'S SHAPE, not a square. It is there to answer "what
   will this look like", and a square preview of a 3:1 crop answers a question
   nobody asked. --shot is set by the page and rewritten by the script. */
.card-pic-thumb {
  flex: none; width: 10.5rem; aspect-ratio: 3 / 1;
  border-radius: var(--r); border: 1px solid var(--line-firm);
  background-image: var(--shot); background-size: cover; background-position: center;
  background-color: var(--bg-sunk);
  display: grid; place-items: center;
  font-size: .7rem; color: var(--ink-faint);
}
.card-pic-thumb.empty { border-style: dashed; }

.card-pic-acts { display: flex; gap: .4rem; align-items: center; flex-wrap: wrap; }

/* ───────────────────────────────────────────────────────────── the cropper */

.cropper { margin-top: .9rem; }

/* THE VIEWFINDER. Fixed at the card's ratio and never resized by dragging —
   the picture moves behind it instead, which is the one arrangement that cannot
   produce a crop that does not fit. See the note in site.js.
   overflow:hidden IS THE CROP, visually: what is outside this box is exactly
   what the server will cut away. */
.crop-frame {
  position: relative; overflow: hidden;
  width: 100%; max-width: 34rem; aspect-ratio: 3 / 1;
  border-radius: var(--r); border: 1px solid var(--line-firm);
  background: var(--bg-sunk);
  cursor: grab; touch-action: none;   /* the browser must not pan the page instead */
  user-select: none;
}
.crop-frame.grabbing { cursor: grabbing; }
.crop-frame img {
  position: absolute; max-width: none; display: block;
  user-select: none; -webkit-user-drag: none;
}

/* WHAT A NARROW CARD KEEPS. A single-column card on a phone is nearer 2.2:1
   than 3:1 and the background is drawn `cover`, so the sides go first. These
   two lines are where that cut falls — roughly the central 73% of the width.
   pointer-events:none so they are a drawing, not something you can grab. */
.crop-safe {
  position: absolute; inset: 0; pointer-events: none;
  border-left: 1px dashed rgba(255, 255, 255, .45);
  border-right: 1px dashed rgba(255, 255, 255, .45);
  margin: 0 13.5%;
}

.crop-bar {
  display: flex; gap: .5rem; align-items: center; flex-wrap: wrap;
  margin-top: .6rem; max-width: 34rem;
}
.crop-spacer { flex: 1 1 auto; }
.crop-zoom {
  display: flex; gap: .45rem; align-items: center;
  font-size: .72rem; color: var(--ink-dim); margin: 0;
  letter-spacing: 0; text-transform: none;
}
.crop-zoom input[type=range] { width: 8rem; accent-color: var(--go); }

/* The one line of feedback under the row. .bad is the same red the notes use. */
.card-pic .why.bad { color: var(--bad); }

/* ─────────────────────────────────────────── the category list in Settings
 *
 * A ROW IS A GRIP, A NAME, A COUNT AND A REMOVE. The grip is a real control
 * rather than "drag anywhere", because the row is mostly a text field and a
 * drag that starts on a text field is a drag that fights selecting a word.
 * See assets/site.js — the DOM order of these rows is the saved order. */

.cats { max-width: 40rem; }
.cat {
  display: flex; align-items: center; gap: .6rem;
  margin-bottom: .45rem;
  /* Positioned so a row being dragged can be lifted above its neighbours. */
  position: relative; background: var(--bg);
}
.cat input[type=text] { flex: 1; font-size: .9rem; }

.grip {
  flex: none; cursor: grab; user-select: none;
  width: 1.5rem; height: 1.9rem; display: flex; align-items: center; justify-content: center;
  color: var(--ink-faint); font-size: .95rem; line-height: 1;
  border-radius: var(--r); border: 1px solid transparent;
  /* The pointer must not be stolen by the browser's own scroll gesture while a
     drag is in progress — without this a touch drag scrolls the page instead. */
  touch-action: none;
}
.grip:hover { color: var(--ink); background: rgba(255, 255, 255, .04); }
.grip:focus-visible { outline: 2px solid var(--go-line); outline-offset: 1px; color: var(--ink); }
/* The spare row at the bottom has nothing to reorder yet: the grip is drawn so
   the row lines up with the others, and is inert. */
.grip.ghost { opacity: .18; cursor: default; }
.grip.ghost:hover { background: none; color: var(--ink-faint); }

.cat.dragging {
  z-index: 2; cursor: grabbing;
  box-shadow: 0 .6rem 1.4rem rgba(0, 0, 0, .45);
}
.cat.dragging .grip { cursor: grabbing; color: var(--go-ink); }
.cat.dragging input[type=text] { border-color: var(--go-line); }

.cat-n { flex: none; font-size: .72rem; white-space: nowrap; min-width: 3.4rem; text-align: right; }
.cat.new .cat-n { visibility: hidden; }

.unsaved { font-size: .74rem; }

/* THE CONFIRMATION POPS OUT OF THE ROW RATHER THAN WIDENING IT. .confirm is a
   flex item in flow by default, which is right in the YouTube library's wide
   actions cell and wrong here: this row is mostly a text field, so an inline
   confirmation squeezed the name box and wrapped its own sentence. Anchored to
   the ✕ it came from, it costs the row no width at all. */
.cat .confirm {
  position: absolute; right: 0; top: calc(100% + .4rem);
  z-index: 3; white-space: nowrap;
  padding: .45rem .55rem; border-radius: var(--r);
  background: var(--bg-raise); border: 1px solid var(--bad-line);
  box-shadow: 0 .5rem 1.3rem rgba(0, 0, 0, .55);
}

/* THE AUTOSAVE'S ONE LINE OF FEEDBACK. Quiet when it is working, loud only
   when it is not: a page that announces every success trains you to ignore it,
   and then you ignore the failure too. */
.autosave { font-size: .74rem; color: var(--ink-faint); transition: color .15s; }
.autosave.ok  { color: var(--ink-dim); }
.autosave.bad { color: var(--bad); }
