/* ARTIFACT — prototype
 *
 * Governing constraints, from PRINCIPLES.md:
 *   · Monochrome is the world; colour is an event (applied here, not baked in).
 *   · Metadata is utilitarian — mono, small, left-aligned, never ornamental.
 *   · Materiality is subliminal. Grain sits at 0.03, not 0.3.
 *   · Nearest hazard is luxury cliché: black + white + expensive type becomes a
 *     fashion campaign. Countermeasures — no display face, no letterspaced caps
 *     as decoration, no centred elegance, no rounded corners, no shadows.
 *     Everything is left-aligned and slightly administrative.
 */

:root {
  --ground: #0b0b0c;
  --ground-lift: #17171a;   /* an empty frame still reads as a frame */
  --rule: #242428;
  --ink: #e8e6e2;      /* bone, not white */
  --ink-dim: #86858a;
  --ink-faint: #4a4a4f;

  --gap: 3px;          /* proof-sheet gutter, not a card grid */
  --pad: clamp(16px, 3vw, 34px);

  --sans: ui-sans-serif, -apple-system, "Helvetica Neue", Helvetica, Arial, sans-serif;
  --mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  --develop: 700ms cubic-bezier(.22, .61, .36, 1);
}

*, *::before, *::after { box-sizing: border-box; }

/* An explicit `display` on a component beats the hidden attribute's UA rule, which
   silently leaves "hidden" sections on the page. Restore the attribute's meaning. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 14px;
  line-height: 1.5;
  padding-bottom: 46px;      /* clears the status bar */
  overflow-x: hidden;
}

button { font: inherit; color: inherit; background: none; border: 0; padding: 0; }

:focus-visible {
  outline: 1px solid var(--ink);
  outline-offset: 2px;
}

.skip {
  position: absolute; left: -9999px; top: 0;
  background: var(--ink); color: var(--ground);
  padding: 8px 12px; z-index: 100;
}
.skip:focus { left: 0; }

/* ---------------------------------------------------------------- masthead */

.masthead {
  padding: var(--pad) var(--pad) calc(var(--pad) * .8);
  max-width: 62ch;
}

.masthead h1 {
  margin: 0 0 10px;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .14em;
}

/* The name is the route home, but it should not announce itself as a link — it reads
   as the masthead until you are on it. */
.masthead h1 a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color 300ms ease;
}
.masthead h1 a:hover,
.masthead h1 a:focus-visible { border-bottom-color: var(--ink-faint); }

.statement {
  margin: 0;
  color: var(--ink-dim);
  font-size: 13px;
  line-height: 1.65;
  text-wrap: pretty;
}

/* Links live inside the prose. Underline is the affordance — nothing else. */
.lnk {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
  cursor: pointer;
}
.lnk:hover { color: var(--ink); text-decoration-color: var(--ink-dim); }

/* ------------------------------------------------------------- the field */

.field {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
  gap: var(--gap);
  padding: 0 var(--pad) var(--pad);
}

/* Uniform cells. A real proof sheet does not vary frame size, and varying it
   here would reintroduce the hierarchy the archive rejects. */
.cell {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 3 / 2;
  background: var(--ground-lift);
  cursor: pointer;
  overflow: hidden;
  /* --e is exposure, 0 latent → 1 developed. Set per-frame from app.js. */
  --e: 0;
}

.cell img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;

  /* The development curve. Latent frames are present but unresolved — dim, low
     contrast, slightly soft. Attention brings them up. */
  filter:
    grayscale(1)
    brightness(calc(.24 + .76 * var(--e)))
    contrast(calc(.55 + .50 * var(--e)))
    blur(calc((1 - var(--e)) * 1.7px));
  opacity: calc(.36 + .64 * var(--e));

  /* No transition on the live pass — the loupe must feel immediate. The slow
     part is persistence, integrated frame by frame in app.js. */
  transform: scale(1.008);   /* hides blur bleed at the edges */
}

/* The frame number is printed on the sheet, so it is always faintly there. */
.cell::after {
  content: attr(data-id);
  position: absolute;
  left: 4px;
  bottom: 3px;
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: .04em;
  color: var(--ink);
  opacity: calc(.14 + .5 * var(--e));
  pointer-events: none;
  text-shadow: 0 0 4px rgba(0,0,0,.85);
}

/* Once fixed, the frame leaves the live pass entirely and comes up on its own —
   the print rising in the tray. This is the only slow motion in the interface. */
.cell.is-fixed img {
  filter: grayscale(1) brightness(1) contrast(1.05) blur(0);
  opacity: 1;
  transition: filter var(--develop), opacity var(--develop);
}

.cell.is-fixed::after { opacity: .64; }

.cell.is-fixed::before {
  /* A grease-pencil mark on the sheet: evidence of what was attended to. */
  content: "";
  position: absolute;
  inset: 0;
  border: 1px solid rgba(232,230,226,.22);
  pointer-events: none;
  z-index: 1;
}

/* -------------------------------------------------------------- directory */

.directory {
  padding: 0 var(--pad) var(--pad);
  max-width: 980px;
}

.dir-row {
  display: grid;
  grid-template-columns: 9ch 11ch 1fr auto;
  gap: 16px;
  align-items: baseline;
  width: 100%;
  padding: 9px 0;
  border-bottom: 1px solid var(--rule);
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: .02em;
  color: var(--ink-dim);
  text-align: left;
  cursor: pointer;
}
.dir-row:first-child { border-top: 1px solid var(--rule); }
.dir-row:hover { color: var(--ink); background: var(--ground-lift); }
.dir-row .id { color: var(--ink); }
.dir-row .type { text-transform: lowercase; }
.dir-row .tail { color: var(--ink-faint); }

/* ----------------------------------------------------------- status bar */

.status {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  display: flex;
  gap: 20px;
  padding: 11px var(--pad);
  background: color-mix(in srgb, var(--ground) 88%, transparent);
  backdrop-filter: blur(6px);
  border-top: 1px solid var(--rule);
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .06em;
  color: var(--ink-faint);
  z-index: 20;
}
.status b { font-weight: 400; color: var(--ink-dim); }

/* ------------------------------------------------------------ inspection */

.inspect {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: var(--ground);
  display: grid;
  grid-template-rows: auto 1fr auto;
  padding: var(--pad);
  gap: 14px;
}

.inspect-close,
.inspect-nav button {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .06em;
  color: var(--ink-dim);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
  justify-self: start;
}
.inspect-close:hover,
.inspect-nav button:hover { color: var(--ink); }

.inspect-figure {
  margin: 0;
  min-height: 0;
  display: grid;
  grid-template-rows: 1fr auto;
  gap: 12px;
}

.inspect-figure img {
  min-height: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center;
  filter: grayscale(1) contrast(1.02);
}

/* Metadata is a row of facts, mono, lowercase-neutral. No captions, no prose,
   nothing the camera did not record. */
.meta {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 18px;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .05em;
  color: var(--ink-faint);
}
.meta .k { color: var(--ink); }

.inspect-nav { display: flex; gap: 20px; }

/* ---------------------------------------------------------------- grain */

/* Subliminal. If it is visible as texture, it is wrong. */
.grain {
  position: fixed;
  inset: -50%;
  z-index: 30;
  pointer-events: none;
  opacity: .032;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/></filter><rect width='140' height='140' filter='url(%23n)'/></svg>");
}

/* --------------------------------------------------------------- mobile */

/* Not a stacked desktop. Without a cursor, attention is what you scrolled to,
   so the develop band follows the viewport centre (see app.js). */
@media (max-width: 700px) {
  :root { --gap: 2px; }
  .field { grid-template-columns: repeat(2, 1fr); }
  .masthead { max-width: none; }
  .dir-row { grid-template-columns: 9ch 1fr; gap: 8px; }
  .dir-row .type, .dir-row .tail { display: none; }
}

@media (hover: none) {
  .cell::after { opacity: calc(.2 + .5 * var(--e)); }
}

/* ------------------------------------------------------- reduced motion */

/* The mechanic survives; the continuous falloff does not. Frames resolve
   discretely on hover or focus, with no travelling gradient to track. */
@media (prefers-reduced-motion: reduce) {
  .cell img { transition: none; }
  .grain { display: none; }
}
