/* Contract: contracts/web/rules.md
 * the grid, the cards, and the workspace they sit in
 *
 * Part of what was style.css — 1700 lines in one file. The parts load in a fixed
 * order and MUST stay in it: the cascade is decided by source order and
 * specificity exactly as it was, because this is a split and not a rewrite.
 *
 * There are deliberately no @layer rules. Layering is the tidier shape and it
 * would have changed behaviour — a later layer beats an earlier one regardless
 * of specificity, and this stylesheet has at least one pair, `body.app > .bar`
 * written above `.bar`, where the MORE specific rule comes first and wins on
 * specificity. Putting those two in different layers silently reverses them.
 * Splitting the file is worth doing on its own; changing the cascade is a
 * separate decision that wants somebody looking at the screen.
 */
/* ---------- grid ---------- */
.grid {
  display: grid; gap: 14px; padding: 18px 20px 60px;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
}
.card {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  overflow: hidden; cursor: pointer; transition: transform .12s ease, border-color .12s ease;
}
.card:hover { transform: translateY(-2px); border-color: var(--accent); }
.shot {
  aspect-ratio: 1; background: var(--panel-2);
  display: flex; align-items: center; justify-content: center; position: relative;
}
.shot img { width: 100%; height: 100%; object-fit: contain; display: block; }

/* Transparency, shown rather than implied.
   A cutout flattened onto white is indistinguishable from artwork that really
   does have a white background, which matters when the whole point of the file
   is that it drops onto any backdrop. The checkerboard is drawn behind the
   image rather than baked into it, so it stays crisp at any size and the
   thumbnail the classifier reads is untouched. */
.alpha-bg {
  --sq: 10px;
  background-color: #fff;
  background-image:
    linear-gradient(45deg, #d8d8dc 25%, transparent 25%, transparent 75%, #d8d8dc 75%),
    linear-gradient(45deg, #d8d8dc 25%, transparent 25%, transparent 75%, #d8d8dc 75%);
  background-size: calc(var(--sq) * 2) calc(var(--sq) * 2);
  background-position: 0 0, var(--sq) var(--sq);
}
.inspector .big.alpha-bg { --sq: 14px; }

/* Hover playback: the clip fades in over the still, so there is no jump. */
.shot .preview {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: contain; opacity: 0; transition: opacity .18s ease; pointer-events: none;
}
.shot.playing .preview { opacity: 1; }
.shot.playable::after {
  content: '▶'; position: absolute; right: 7px; bottom: 7px;
  font-size: 10px; line-height: 1; padding: 4px 6px; border-radius: 5px;
  background: color-mix(in srgb, var(--panel) 78%, transparent);
  color: var(--ink-dim); backdrop-filter: blur(6px); transition: opacity .18s ease;
}
.shot.playing::after { opacity: 0; }

/* Right-click menu */
.ctx {
  position: fixed; z-index: 60; min-width: 190px; padding: 5px;
  background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
  box-shadow: 0 12px 34px rgba(0,0,0,.32);
}
.ctx button {
  display: block; width: 100%; text-align: left; padding: 7px 10px;
  background: none; border: 0; border-radius: 6px;
  color: var(--ink); font: inherit; font-size: 13px; cursor: pointer;
}
.ctx button:hover { background: var(--panel-2); }
.ctx button.danger { color: var(--accent); }
.ctx hr { border: 0; border-top: 1px solid var(--line); margin: 5px 2px; }

.btn.danger { background: var(--accent); }
.shot .none { color: var(--ink-dim); font-size: 26px; }
.badge {
  position: absolute; top: 7px; left: 7px;
  padding: 2px 7px; border-radius: 6px; font-size: 10px; font-weight: 700;
  letter-spacing: .04em; text-transform: uppercase;
  background: color-mix(in srgb, var(--panel) 82%, transparent); backdrop-filter: blur(6px);
}
.badge.canonical { background: var(--ok); color: #08120c; }
.badge.deck { background: var(--warn); color: #14141a; }
/* Muted rather than alarming. A superseded file is not a problem, it is history
   — the badge is there so nobody sends it to a printer by mistake, not to
   suggest anything is wrong with it. */
.badge.old {
  background: color-mix(in srgb, var(--panel) 70%, transparent);
  color: var(--ink-dim); border: 1px solid var(--line);
}
.badge.current { background: var(--accent); color: #fff; }

/* The three marks — keep it, put it away, no. BOTTOM LEFT, which is the only
   corner of a card that is free: badges are top left, the type and size chips
   top right, and the ▶ marker on a playable video bottom right. Put at top
   right first, the star sat on top of the size chip and cropped it to "1.2".

   Two visibilities on purpose. An unset mark appears on hover, because a grid
   of sixty hollow stars is noise on top of the pictures the grid exists to
   show. A set one is always drawn: the question it answers is "which of these
   did I already deal with?", asked while scrolling past, and a mark you have to
   point at to see cannot answer it.

   Kept visible on :focus-visible as well, or the keyboard route to it would be
   a control that is focused and invisible. */
.marks {
  position: absolute; bottom: 5px; left: 5px; z-index: 2;
  display: flex; gap: 3px;
}
.mark {
  width: 26px; height: 26px; padding: 0; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: 7px; cursor: pointer;
  font-size: 15px; color: var(--ink-dim);
  background: color-mix(in srgb, var(--panel) 78%, transparent);
  backdrop-filter: blur(6px);
  opacity: 0; transition: opacity .12s ease, color .12s ease, transform .12s ease;
}
.mark svg { width: 15px; height: 15px; }
.card:hover .mark, .mark:focus-visible { opacity: 1; }
.mark:hover { transform: scale(1.12); color: var(--ink); }
.mark.on { opacity: 1; }
/* Amber rather than the accent: the accent is this interface's "act on this"
   colour and is already on the hover border of every card, so a starred card
   would read as a selected one at a glance across a full screen. */
.fav.on { color: #f0b429; }
/* The two clearings-away are deliberately quiet, and the stronger one is the
   quieter. A red thumbs-down on every card he has ever said no to would make
   the Hidden shelf look like a wall of errors, which is the opposite of what it
   is: a shelf of decisions, all of them undoable. */
.putaway.on { color: var(--ink); }
.nope.on { color: var(--ink-dim); }

/* A phone has no hover, so every mark would be invisible until tapped —
   which is the one state that makes the control unusable rather than quiet. */
@media (hover: none) {
  .mark { opacity: 1; }
}

/* …but not while choosing. That mode promises every tap picks a picture, and
   controls sitting in the corner of each card invite exactly the tap it would
   have to ignore. selection.js takes the gesture back; this stops it being
   offered. */
body.choosing .marks { display: none; }

/* Cleared away, and on its way off the screen. The card is taken out in place
   rather than by re-running the query — see dropCard in grid.js — and this is
   the only thing that tells the eye it went somewhere rather than glitching.
   Short enough not to be a wait: the gesture is made forty times in a row. */
.card.leaving {
  opacity: 0; transform: scale(.94);
  transition: opacity .15s ease, transform .15s ease;
  pointer-events: none;
}
/* A picture that arrived while somebody was looking at the grid. It fades up
   where a placeholder was standing a moment ago, so the swap reads as the same
   thing finishing rather than as the grid jumping. Set once by grid.js and
   never removed — the animation runs to its end and the class costs nothing
   afterwards. */
.card.arriving { animation: arrive .32s ease-out; }
@keyframes arrive { from { opacity: 0; transform: scale(.96); } }

/* ---------- a picture that does not exist yet ----------
   A request the archive is getting on with, drawn at the head of an unfiltered
   grid. See the last section of web/grid.js for when it is allowed to be here
   at all, and web/requests-view.js for where the rows come from.

   NOT `.card`, and that is a rule rather than a preference: selection.js,
   choose.js and grid.js itself all reach `.card` expecting a `data-sha` and a
   `_row` on every one they find. So the shape is repeated here rather than
   shared. It is a dozen lines, and the alternative is a node with no asset
   behind it inside a shift-range selection. */
.pendcard {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  overflow: hidden; cursor: pointer;
  /* Dashed, and it is the whole visual argument: every solid-edged card on this
     screen is a file that exists. This one is a promise, and it should not be
     possible to mistake the two at arm's length. */
  border-style: dashed;
  transition: transform .12s ease, border-color .12s ease;
}
.pendcard:hover { transform: translateY(-2px); border-color: var(--accent); }
.pendshot {
  aspect-ratio: 1; background: var(--panel-2); position: relative; overflow: hidden;
}
/* A light crossing an empty frame. Deliberately not a spinner: a spinner in the
   corner of a card is a control that failed, and this is a frame that is being
   filled. Deliberately not a thumbnail either — see underwayCard in grid.js. */
.pendglow {
  position: absolute; inset: 0;
  background: linear-gradient(100deg,
    transparent 20%,
    color-mix(in srgb, var(--ink) 7%, transparent) 50%,
    transparent 80%);
  background-size: 220% 100%;
  animation: pendsweep 1.9s ease-in-out infinite;
}
@keyframes pendsweep { from { background-position: 120% 0; } to { background-position: -80% 0; } }
/* The quietest badge on the screen. It reads as chrome rather than as news for
   the same reason the "under way" pill does: it is the archive saying it is
   busy, which is worth knowing and not worth interrupting anybody for. */
.badge.making {
  background: color-mix(in srgb, var(--panel) 70%, transparent);
  color: var(--ink-dim); border: 1px solid var(--line);
}
/* What was asked for, over two lines rather than one. A filename is a label and
   truncates without losing much; a request is a sentence, and "Garamond's
   apartment — the one with…" is the half that says which idea this is. */
.pendcard .meta .name {
  white-space: normal; overflow: hidden; font-weight: 560;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}

@media (prefers-reduced-motion: reduce) {
  .pendglow { animation: none; }
  .card.arriving { animation: none; }
}
.meta { padding: 9px 11px 11px; }
.meta .name {
  font-size: 12.5px; font-weight: 560; margin-bottom: 3px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* A card name mid-rename. The ellipsis and the nowrap have to go while it is
   open — a name is typed from the left and clipped at the right, so keeping
   them would hide the half being written — and the text has to become
   selectable again, since card labels are deliberately not. */
.meta .name.renaming {
  white-space: normal; overflow: visible; text-overflow: clip;
  user-select: text; -webkit-user-select: text;
  background: var(--panel-2); border-radius: 6px;
  margin: -2px -4px 1px; padding: 2px 4px;
  outline: 2px solid var(--accent); outline-offset: -1px;
}
.meta .name.saving { opacity: .6; }
.meta .sub { font-size: 11.5px; color: var(--ink-dim); line-height: 1.4;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.lowconf { color: var(--warn); font-weight: 600; }

/* ---------- dense ----------
   `body.dense`, set from Preferences. Measured at 1512x900: comfortable gets
   four cards of 204x277 across a 896px column and eight of them fully visible;
   these numbers get six of 139x167 and eighteen. That is the whole point of the
   setting — an archive of four thousand things looked at through a window
   holding eight of them.

   The guard is the inverse of the phone-chrome query in 07-narrow.css, written
   out rather than undone there: restating the comfortable values inside that
   block would be a second copy of every default, and a default changed in one
   place and not the other is silent. On a phone the grid is already as tight as
   it can be and the captions are load-bearing, so dense has nothing to offer
   and this simply never applies. */
@media (min-width: 721px) and (min-height: 521px) and (pointer: fine) {
  body.dense .grid {
    gap: 8px; padding: 10px 12px 60px;
    grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
  }
  body.dense .meta { padding: 5px 7px 6px; }
  body.dense .meta .name { font-size: 11.5px; margin-bottom: 0; }
  /* The description is the first thing to go. It is two lines of the card's
     height and the thumbnail is already the answer to "which one is this". */
  body.dense .meta .sub { display: none; }

  /* Without these two the superseded badge is covered by the type and size chips
     by 43px — measured, not estimated. The badge outranks the chips, and this is
     the one real design decision in the dense layout: type and size are a "which
     of these two files" question you ask about one card at a time, and
     superseded is a warning you need across the whole screen at once. Shrinking
     everything proportionally buries the single fact a thumbnail cannot show,
     and it is the fact you most need before sending a file to a printer. */
  body.dense .card .chips { opacity: 0; transition: opacity .12s; }
  body.dense .card:hover .chips { opacity: 1; }
  body.dense .badge { font-size: 9px; padding: 2px 5px; }
}

/* There was a rule here reserving room for a bar that floated over the last row
 * of the grid — `max(60px, 18px + var(--selfloor) + 12px)`, where --selfloor
 * was the bar's own measured height. The bar is gone and nothing floats over
 * the grid any more, so the grid needs nothing reserved. The phone still has a
 * flat reserve in 07-narrow.css and 08-sharing.css, for the camera button,
 * which does. */

.empty { padding: 60px 20px; text-align: center; color: var(--ink-dim); }

/* ---------- workspace: grid beside a permanent inspector ----------
   The inspector used to be a drawer over the grid with a scrim behind it, so
   checking six files meant six open-read-close cycles with the results hidden
   for all of them. It is a column now: clicking a card changes what it shows. */
.workspace {
  display: grid; grid-template-columns: 1fr var(--inspector-w);
  /* minmax(0, 1fr) rather than 1fr: a grid row sized `1fr` still refuses to go
     below the height of its contents, so an overflowing column would push the
     row past the frame instead of scrolling inside it. */
  grid-template-rows: minmax(0, 1fr);
  flex: 1; min-height: 0;
}
/* overscroll-behavior stops iOS handing an over-pull at the top of the grid to
   the page behind it, which is what lets pull-to-refresh own that gesture
   instead of competing with a rubber-band. See web/pull-refresh.js. */
.results {
  min-width: 0; min-height: 0; position: relative;
  overflow-y: auto; overscroll-behavior-y: contain;
}

.inspector {
  background: var(--panel); border-left: 1px solid var(--line);
  overflow-y: auto; padding: 20px;
}
.inspector-empty {
  color: var(--ink-dim); font-size: 13px; line-height: 1.6;
  padding: 28px 4px; text-align: center;
}
/* Only ever the escape hatch for the narrow overlay layout. */
.inspector-close { display: none; }

.inspector .big {
  width: 100%; border-radius: var(--radius); background: var(--panel-2);
  margin-bottom: 16px; display: block;
}
/* The 256px stand-in, blown up to panel width while the real one loads. A touch
   of blur is the honest rendering of it: without it the enlarged pixels read as
   a bad export rather than as a picture still arriving. */
.inspector .big.upgrading { filter: blur(1.2px); }
.inspector .big { transition: filter .18s ease; }
@media (prefers-reduced-motion: reduce) {
  .inspector .big { transition: none; }
}

/* A video plays in the panel rather than standing in for itself. Deliberately
   not wrapped in .bigwrap: that is a button that opens the lightbox, and its
   zoom-in cursor and hover brightening belong to a picture rather than to a set
   of controls somebody is trying to press. Black behind it, because letterbox
   bars in the panel colour read as part of the picture. */
.inspector .videobox { margin-bottom: 16px; }
.inspector .videobox .big { margin-bottom: 0; background: #000; transition: none; }
.inspector .videonote {
  margin: 8px 0 0; font-size: 12px; line-height: 1.45; color: var(--ink-dim);
}
/* The icon row under the preview: download, the three marks, and the ID.
 *
 * The marks are the SAME buttons the grid draws, from web/marks.js, so two
 * things they rely on out there have to be undone in here. `.marks` positions
 * itself absolutely into the bottom-left corner of a card — there is no card to
 * sit in — and every `.mark` starts at opacity 0, revealed by `.card:hover`,
 * which never fires in a panel and would leave the row invisible until pressed.
 *
 * Overridden here rather than made conditional in marks.js: the card corner is
 * that module's whole subject, and a second caller should not turn its layout
 * into a parameter. */
.previewbar {
  display: flex; align-items: center; gap: 6px;
  margin: -6px 0 14px;
}
.previewbar .marks {
  position: static; display: contents;
}
.previewbar .mark {
  opacity: 1;
  background: var(--panel-2);
}
.previewbar .mark:hover { background: color-mix(in srgb, var(--ink) 10%, var(--panel-2)); }
/* The download sits apart from the three judgements: it does something to the
   file, they say something about it. A gap rather than a rule, which at this
   size would be a third of the height of what it separates. */
.previewbar > a[download] { margin-right: 6px; }
/* …and the ID is the other kind again — it copies, changing nothing — so it
   goes to the far end rather than crowding the marks it does not belong with.
   Selected by its own class rather than by position: `.marks` renders as
   `display: contents`, so its three buttons are siblings of this one and a
   positional selector would be describing an accident. */
.previewbar > .copyid { margin-left: auto; }

.inspector h2 { font-size: 16px; margin: 0 0 4px; word-break: break-word; }

/* The filename, edited where it sits. Nothing here may move the text: the
   affordance has to be visible without shifting the line when it appears, or
   every card click would nudge the panel. So the padding is always there and
   only the background and the outline arrive on hover and focus. */
.namebox { display: flex; align-items: baseline; gap: 4px; margin: -4px -6px 4px; }
.inspector .name-edit {
  flex: 1 1 auto; min-width: 0;
  margin: 0; padding: 4px 6px; border-radius: 7px;
  cursor: text; outline: 2px solid transparent; outline-offset: -1px;
}
.name-edit:hover { background: var(--panel-2); }
.name-edit:focus { background: var(--panel-2); outline-color: var(--accent); }
/* Deleting the whole name mid-edit would otherwise collapse the box to nothing
   and leave no target to click back into. Blurring restores the old name. */
.name-edit:empty::before { content: 'Name this file'; color: var(--ink-dim); }
.inspector .name-edit.saving { opacity: .6; }
.namebox .saved { flex: none; margin: 0; }
.inspector .caption { color: var(--ink-dim); font-size: 13.5px; margin: 0 0 16px; }
.rows { display: grid; grid-template-columns: auto 1fr; gap: 6px 14px; font-size: 13px; margin-bottom: 18px; }
.rows dt { color: var(--ink-dim); }
.rows dd { margin: 0; word-break: break-word; }
.rows code { font: 12px ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--ink-dim); }
.tags { display: flex; flex-wrap: wrap; gap: 5px; }
.tag { background: var(--panel-2); border-radius: 6px; padding: 2px 7px; font-size: 11.5px; }
.transcript { margin: 4px 0 18px; font-size: 13px; }
.transcript summary { cursor: pointer; color: var(--ink-dim); font-size: 12px; font-weight: 600; }
.transcript p { background: var(--panel-2); border-radius: 8px; padding: 10px 12px; margin: 10px 0 0; line-height: 1.6; }
.transcript .translated { color: var(--ink-dim); }

.actions { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 18px; }
.btn {
  padding: 8px 14px; border: 0; border-radius: 8px; cursor: pointer;
  background: var(--accent); color: #fff; font: inherit; font-size: 13px; font-weight: 560;
  text-decoration: none; display: inline-block;
}
.btn.sec { background: var(--panel-2); color: var(--ink); }
.btn[disabled] { opacity: .45; cursor: not-allowed; }

.edit { border-top: 1px solid var(--line); padding-top: 16px; }
.edit h3 { font-size: 13px; margin: 0 0 10px; color: var(--ink-dim); font-weight: 600; }
.edit label { display: block; font-size: 12px; color: var(--ink-dim); margin-bottom: 10px; }
.edit select, .edit input {
  width: 100%; margin-top: 4px; padding: 7px 10px;
  background-color: var(--panel-2); color: var(--ink);
  border: 1px solid var(--line); border-radius: 8px; font: inherit; font-size: 13px;
}
/* Keep the drawn chevron clear of the text — the shared select rule owns the
   right-hand padding, and a width-setting rule must not take it back. */
.edit select { height: 34px; padding: 0 32px 0 10px; }
.saved { color: var(--ok); font-size: 12.5px; margin-left: 10px; }

