/* ═══════════════════════════════════════════════════════════════════════════
 *  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); }


/* ═══════════════════════════ the subdomains and the links, in Settings
 *
 * ONE ROW PER ENTRY, AND IT IS A CARD LIKE A TOOL'S ROW IS. The two sections
 * of that page are editing the same kind of thing — what a card says — so they
 * are built from the same .card and the same .pair grid, and .tool-row above
 * carries most of this. What is below is only what an entry has and a tool
 * does not: a kind, and an address.
 */
.entry-row .kind { display: grid; gap: 1rem; grid-template-columns: minmax(8rem, 10rem) 1fr; }
.entry-row .addr input[type=text] { font-family: var(--mono); font-size: .82rem; }
/* WHERE THE TYPED ADDRESS ACTUALLY POINTS, worked out by the server and shown
   back. "qr" on this site means qr.vha.tools, and the only way to be sure that
   is what somebody meant is to print it. Red when the address is not one. */
.entry-row .resolved { margin: .35rem 0 0; font-size: .73rem; color: var(--ink-faint); }
.entry-row .resolved code { font-size: .73rem; }
.entry-row .resolved.bad { color: var(--bad); }
/* The remove button sits with the address rather than in a corner of its own:
   an entry IS its address, so that is the field somebody is looking at when
   they decide it should go. */
.entry-row .entry-top { display: flex; align-items: flex-start; gap: .6rem; }
.entry-row .entry-top > :first-child { flex: 1; }
.entry-row.going { opacity: .4; }

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