:root {
  --bg:           #000000;  /* the room the window is cut into */
  --paper:        #fbefd8;  /* the plate's own paper -- inside the frame only */
  --ink:          #d8ccb4;  /* type on black: bone, dimmed so the plate stays brightest */
  --ink-soft:     #857b68;
  --frame:        #3a352b;
  --measure:      48ch;  /* set to the verse: the longest authored line, not to prose */
  --gutter:       clamp(1.25rem, 5vw, 3.5rem);
  --serif:        Georgia, "Iowan Old Style", "Palatino Linotype", Palatino, "Times New Roman", serif;
}

* { box-sizing: border-box; }

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--serif);
  line-height: 1.65;
}

.page {
  display: flex;
  flex-direction: column;
  gap: clamp(2rem, 7vw, 3.5rem);
  padding: var(--gutter);
  /* No max-width: the plate is the page, so it keeps growing with the screen
     rather than stopping at a column and leaving dead black either side. */
}

/* The notice: one line, centred in its own column, italic to answer the verse.
   Sized so it always sets on a single line -- nowrap makes that a guarantee
   rather than a hope, since a broken "Coming / soon" would read as a mistake. */
.herald { margin: 0; }

.herald p {
  margin: 0;
  text-align: center;
  white-space: nowrap;
  font-style: italic;
  font-size: clamp(1.5rem, 0.9rem + 2vw, 2.5rem);
  line-height: 1.2;
  letter-spacing: 0.01em;
  color: var(--ink);
}

/* ---- the plate, as a window ----------------------------------------------
   The frame never moves. The view inside it does, so the art reads as a world
   seen through an opening rather than a picture hung on a wall.

   The window shrink-wraps the image, which keeps the plate sized from its own
   intrinsic dimensions -- still no aspect-ratio anywhere, so new art drops in
   with no CSS change. The overscan is a transform, which does not affect
   layout, and it is applied ONLY once the script marks the window live: with
   no pointer, no JS, or reduced motion, the plate shows whole and uncropped
   exactly as before. ------------------------------------------------------ */
.plate { margin: 0; }

.window {
  display: inline-block;   /* shrink-wraps the image, so the frame is the art's own edge */
  vertical-align: bottom;  /* kills the baseline gap under an inline-block, which would
                              otherwise sit 8px of nothing below the frame and throw the
                              bottom-aligned verse out by exactly that much */
  overflow: hidden;
  line-height: 0;
  border: 1px solid var(--frame);
  max-width: 100%;
}

.window img {
  display: block;
  width: auto;
  max-width: 100%;
  /* Full height, with no ceiling at the file's own pixel height: the plate is the
     page, so it fills the screen and is allowed to upscale. */
  max-height: calc(100vh - 5rem);
}

/* The art and its dust are one layer, so the dust pans with the world rather than
   hanging in front of the glass. */
.world { position: relative; display: block; }

.dust {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.window.is-live .world {
  transform: scale(1.06) translate(var(--px, 0%), var(--py, 0%));
  will-change: transform;
}

.narrator { max-width: var(--measure); }

.narrator p {
  margin: 0 0 1.15em;
  font-style: italic;
  font-size: clamp(0.85rem, 0.8rem + 0.25vw, 0.95rem);
}

.narrator p:last-child { margin-bottom: 0; }

/* Verse, not prose: one clause per line. Each line is its own block so that a line
   too long for the column hangs indented -- a wrap then reads as continuation
   rather than as a new line, which is the failure mode that makes set verse look
   broken. Lines are authored short enough that this rarely fires. */
.narrator .stanza span {
  display: block;
  padding-left: 1.15em;
  text-indent: -1.15em;
}

/* ---- tablet: 640px+ — wordmark above, then plate | narrator ---- */
@media (min-width: 40em) {
  .page {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: clamp(1.75rem, 4vw, 3rem);
  }

  .herald { flex: 0 0 100%; }

  .plate    { flex: 1 1 46%; min-width: 0; display: flex; justify-content: center; }

  .narrator { flex: 1 1 44%; min-width: 18rem; align-self: flex-end; }

  /* Here the wordmark sits in a row above, so the plate cannot have the whole
     viewport height -- leave room for it and the padding, or a short laptop
     scrolls. */
  .window img { max-height: calc(100vh - 12rem); }
}

/* ---- desktop: 1248px+ — wordmark | plate | narrator ----
   Equal side columns with the plate auto-sized between them, so the window sits
   dead centre of the viewport whatever the wordmark and the verse are doing.

   The side columns carry a floor of 22rem because that is what the longest verse
   line needs to set unbroken; the plate column takes what is left, so a narrower
   desktop yields a smaller window rather than a broken poem. Below 1248px there
   is not enough width for three columns and the two-column layout is better --
   which is why this breakpoint sits higher than the usual 1024. */
@media (min-width: 78em) {
  .page {
    display: grid;
    grid-template-columns: minmax(22rem, 1fr) auto minmax(22rem, 1fr);
    align-items: center;
    /* Centre the row rather than stretching it, so the row's own height IS the
       plate's height -- that is what lets the side columns align to the picture's
       edges instead of to the viewport's. */
    align-content: center;
    gap: clamp(1.5rem, 3vw, 4rem);
    min-height: 100vh;
    padding-block: 2rem;
  }

  /* The notice is centred on the plate; the verse sits on its bottom edge. */
  .herald    { align-self: center; }
  .plate     { display: block; }
  .narrator  { align-self: end; }

  /* Three columns: the wordmark is beside the plate, not above it, so the window
     gets the full height back. */
  .window img { max-height: calc(100vh - 5rem); }
}

/* ---- the one permitted motion on load: a single fade-up ---- */
@keyframes rise {
  from { opacity: 0; transform: translateY(0.5rem); }
  to   { opacity: 1; transform: none; }
}

.page { animation: rise 900ms ease-out both; }

@media (prefers-reduced-motion: reduce) {
  .page { animation: none; }
  .window.is-live .world { transform: none; }
  .dust { display: none; }
}

::selection { background: var(--paper); color: var(--bg); }
