/* =====================================================================
   Core / wall.css — THE WALL
   ---------------------------------------------------------------------
   A twelve-column wall that a project page hangs its canvases on:
   pictures, clips, embeds and text, each one placed by column and width
   rather than stacked into a fixed template.

   Loaded by works/*.html only, on top of core/site.css + core/resn.css.
   It adds no colours of its own — every value here is one of the .resn
   tokens, so a bespoke project page is still unmistakably the same site.

   ── THE ONE IDEA ─────────────────────────────────────────────────────
   "Canvases by the Wall" is an installation where a body in front of a
   camera changes what the work does. The page says the same thing with
   the reader's pointer: the wall is dim at rest, and what the pointer
   is near comes up — a warm hairline wipes across the top edge of a
   canvas and the scrim over it lifts. Nothing else on the page moves.

   Text never dims. Only media answers presence, so the page stays fully
   readable with the pointer parked in a corner.

   The numbers driving it (--wall-dim, --wall-reach and the rest) are
   pushed in from the CONFIG block in content/<slug>.js by core/wall.js,
   so no number is stated both here and there.
   ------------------------------------------------------------------ */

.wall-page{
  /* Filled from CONFIG by core/wall.js. What is written here is only
     what the page falls back to if that module never runs. */
  --wall-cols: 12;
  --wall-gap: 24px;
  --wall-unit: 8px;
  --wall-max: 1440px;
  --wall-dim: .42;
  --wall-guide: .05;
  --wall-reach: 520px;

  /* THE GLASS UNDER A CANVAS. Filled from the `glass` object in
     content/<slug>.js by core/wall.js, over CONFIG.pane's defaults; a
     block overrides any of them on itself. --pane-x/--pane-y are the
     SIZE: how far the sheet reaches past the canvas it is under. */
  --pane-x: 28px;
  --pane-y: 24px;
  --pane-radius: 4px;
  --pane-blur: 16px;
  --pane-tint: .05;
  --pane-line: .08;

  /* THE MEASURE — how far a line of each tone may run. Filled from the
     `measure` object in content/<slug>.js by core/wall.js; what is here
     is what the page falls back to if that module never runs, and it is
     the same set of numbers CONFIG.measure states there.

     A block's own `width` still wins over all of them: core/wall.js
     writes it as --t-width on the block, and every rule below reads
     that FIRST. */
  --m-title: none;
  --m-heading: none;
  --m-lede: 46ch;
  --m-body: 62ch;
  --m-note: 52ch;
  --m-caption: 52ch;

  /* THE CEILING no paragraph tone may cross, however wide the canvas
     under it is asked to be — see PARA_MEASURE_CAP in core/wall.js for
     the 7-of-12-column arithmetic. Title and heading are not wrapped in
     it below: they are the two tones `measure` means to leave uncapped. */
  --wall-text-cap: 830px;

  /* pointer position over the wall, in px from the wall's top-left */
  --wall-px: -9999px;
  --wall-py: -9999px;
  --wall-pool: 0;      /* 0 when the pointer is off the wall, 1 when on it */
}

/* ---------- the wall itself ------------------------------------------- */
.wall{
  position:relative;
  max-width:var(--wall-max);
  margin-inline:auto;
  display:grid;
  grid-template-columns:repeat(var(--wall-cols),minmax(0,1fr));
  column-gap:var(--wall-gap);
  row-gap:0;                    /* rows are spaced by each block's own lift */
  align-items:start;
}

/* The column guides: the wall's own grid, drawn once and left faint.
   They are the reason placement reads as deliberate rather than random,
   and in edit mode they come up bright and become the ruler.

   One <i> per column laid over the same grid, so a hairline sits on a
   real column edge instead of on a gradient's guess at one. */
.wall-guides{
  position:absolute;inset:0;z-index:0;
  display:grid;
  grid-template-columns:repeat(var(--wall-cols),minmax(0,1fr));
  column-gap:var(--wall-gap);
  pointer-events:none;
}
.wall-guides i{
  border-left:1px solid var(--r-line);
  opacity:var(--wall-guide);
  transition:opacity var(--r-mid) var(--r-ease);
}
.wall-guides i:last-child{border-right:1px solid var(--r-line)}

/* The presence pool — the warm patch the pointer carries across the
   wall. Very low alpha: it should be felt at the edge of vision rather
   than looked at. */
.wall-pool{
  position:absolute;inset:0;z-index:0;pointer-events:none;
  opacity:var(--wall-pool);
  transition:opacity var(--r-slow) var(--r-ease);
  background:radial-gradient(
    circle var(--wall-reach) at var(--wall-px) var(--wall-py),
    rgba(255,218,147,.055), rgba(255,218,147,0) 70%);
}

/* ---------- a canvas on the wall --------------------------------------- */
.wall-b{
  --near:1;                     /* 0 = at rest, 1 = the pointer is on it */
  /* WHICH CANVAS IS IN FRONT — a block's own `layer`, when it states
     one. Depth only: the order the blocks are READ in stays the order
     they are written in, which is what the phone wall falls back to. */
  position:relative;z-index:var(--layer,1);
  min-width:0;                  /* so a wide image cannot blow its column out */
  grid-column:var(--col) / span var(--span);
  margin-top:var(--lift,0px);
}

/* THE GLASS UNDER A CANVAS — a frosted sheet behind a block, so a
   paragraph or a picture sits ON something instead of floating over the
   shader running behind the page.

   It is a pseudo-element on the block rather than a wrapper, so nothing
   about the wall's grid changes: the sheet is laid out from the
   canvas's own box and reaches PAST it by --pane-x and --pane-y, which
   is what gives it a size of its own to be dialled.

   z-index:-1 keeps it behind the block's own content and, because
   .wall-b makes its own stacking context, still in front of the column
   guides and the presence pool.

   backdrop-filter is not free — it is a full composited pass per sheet.
   Glass on every canvas of a long wall is a real cost; glass on the two
   that need to be lifted off the ground is not. */
.wall-b[data-glass]::before{
  content:'';position:absolute;z-index:-1;pointer-events:none;
  inset:calc(var(--pane-y) * -1) calc(var(--pane-x) * -1);
  border-radius:var(--pane-radius);
  background:rgba(255,255,255,var(--pane-tint));
  border:1px solid rgba(255,255,255,var(--pane-line));
  backdrop-filter:blur(var(--pane-blur)) saturate(1.08);
  -webkit-backdrop-filter:blur(var(--pane-blur)) saturate(1.08);
}

/* the frame: everything that can be lit lives inside one.

   --frame-radius and --frame-fade are a block's own `radius` and `fade`
   (core/wall.js applyFraming); unset, a frame is square and opaque
   exactly as it always was. overflow:hidden is what makes the corner
   cut the picture, the scrim and the waterline together. */
.wall-b-frame{
  position:relative;overflow:hidden;
  background:var(--r-bg-2);
  display:block;
  border-radius:var(--frame-radius,0);
  opacity:var(--frame-fade,1);
}
.wall-b-frame[data-ratio]{aspect-ratio:var(--ratio)}

.wall-b-frame img,
.wall-b-frame video,
.wall-b-frame iframe{
  display:block;width:100%;border:0;
  -webkit-user-drag:none;      /* belt-and-suspenders for draggable=false */
}
.wall-b-frame img,
.wall-b-frame video{
  opacity:0;transition:opacity var(--r-slow) ease-out;
}
.wall-b-frame[data-ratio] img,
.wall-b-frame[data-ratio] video{
  position:absolute;inset:0;height:100%;object-fit:var(--fit,cover);
}

/* WHERE THE CROP IS ANCHORED, and how far in. --focus is the block's
   `focus` and --zoom its `zoom` (core/wall.js applyFraming).

   `scale` rather than `transform`, because core/wall.js already writes
   `translate` on the block for nudge and the two properties must not be
   fighting over one declaration. transform-origin follows the focus
   point, so zooming in pushes the picture out around the part that was
   asked to stay. */
.wall-b-frame img,
.wall-b-frame video{
  object-position:var(--focus,50% 50%);
  scale:var(--zoom,1);
  transform-origin:var(--focus,50% 50%);
}
.wall-b-frame:not([data-ratio]) img,
.wall-b-frame:not([data-ratio]) video{height:auto}
.wall-b-frame iframe{position:absolute;inset:0;height:100%}
.wall-b-frame img.is-loaded,
.wall-b-frame video.is-loaded{opacity:1}

/* THE SCRIM — how far a canvas falls back when nothing is near it.
   Deliberately no CSS transition: core/wall.js eases --near itself,
   frame by frame, so the light trails the pointer rather than chasing
   it in steps. */
.wall-b-frame::after{
  content:'';position:absolute;inset:0;z-index:1;pointer-events:none;
  background:var(--r-bg);
  opacity:calc((1 - var(--near)) * var(--wall-dim));
  will-change:opacity;
}

/* THE WATERLINE — the site's one hover gesture (DESIGN.md), promoted to
   be this page's whole behaviour: warmth arrives as a hairline wiping in
   from the left along the top edge of whatever the reader is near. */
.wall-b-frame::before{
  content:'';position:absolute;left:0;right:0;top:0;height:1px;z-index:2;
  background:var(--r-accent);
  transform:scaleX(var(--near));transform-origin:left center;
  opacity:calc(var(--near) * .8);
  pointer-events:none;will-change:transform,opacity;
}

/* THE CLOSER LOOK — the opened picture and its controls — is in
   core/zoom.css, with core/zoom.js, shared with the Fragments page. What
   stays here is how a WALL canvas behaves around it. */

/* the canvas the picture came out of keeps its place in the wall while
   it is away — the page behind must not reflow, or there would be
   nowhere to put it back */
.wall-b-frame.is-zoomed img{visibility:hidden}

/* A picture worth opening says so under the pointer — but only where
   there is a pointer, and only where the page is not already drawing a
   cursor of its own. The dot-and-ring in core/site.js is the site's
   pointer; a second one arriving next to it would be one too many. */
@media (hover:hover){
  html:not(.has-cursor) .wall-b-frame:not(.is-empty) img{cursor:zoom-in}
}

/* An empty slot — a block with no file behind it yet. Deliberate rather
   than broken: it names its own type and holds its place on the wall, so
   a half-filled page still reads as a design. */
.wall-b-frame.is-empty{
  display:grid;place-items:center;
  min-height:140px;
  background:
    repeating-linear-gradient(-45deg,
      rgba(255,255,255,.014) 0 10px, transparent 10px 20px),
    var(--r-bg-1);
  outline:1px dashed var(--r-line);outline-offset:-1px;
}
.wall-b-frame.is-empty span{
  color:var(--r-gray-4);text-align:center;padding:1em;
  font-size:var(--r-fs-label);letter-spacing:var(--r-track-label);text-transform:uppercase;
}

/* the caption under a canvas */
.wall-cap{
  margin-top:12px;
  font-size:12px;font-weight:300;line-height:1.55;
  color:var(--r-gray-3);max-width:min(var(--t-width,var(--m-caption,52ch)),var(--wall-text-cap));
}

/* ---------- text on the wall ------------------------------------------ */
.wall-b-text > * + *{margin-top:1.1em}

/* --t-scale/--t-width/--t-color are set inline, per block, only when
   that block's own scale/width/color is used — see textBlock() in
   wall.js. Every tone's font-size and max-width is written so the
   override multiplies or replaces the tone's own default rather than
   needing one of its own; unset, var() falls through to that default. */
/* Each tone reads its own measure, and reads the BLOCK's word first:
   --t-width is what that one block asked for, --m-* is what the page
   asked for, and the literal at the end is what the site has always
   done. Widening a text block is therefore two independent moves —
   `span` gives it more of the wall, `width` (or the page's `measure`)
   lets the lines actually use it. */
.wall-t-title{
  font-family:var(--font-title);font-weight:var(--wt-title);
  font-size:calc(clamp(30px,5vw,68px) * var(--t-scale,1));line-height:1.04;
  letter-spacing:.055em;text-transform:uppercase;color:var(--t-color,var(--r-white));
  max-width:var(--t-width,var(--m-title,none));
}
.wall-t-heading{
  font-family:var(--font-title);font-weight:var(--wt-title);
  font-size:calc(clamp(19px,2.2vw,30px) * var(--t-scale,1));line-height:1.2;
  letter-spacing:.05em;text-transform:uppercase;color:var(--t-color,var(--r-text));
  max-width:var(--t-width,var(--m-heading,none));
}
.wall-t-lede{
  font-size:calc(var(--r-fs-lede) * var(--t-scale,1));font-weight:300;line-height:1.6;
  color:var(--t-color,var(--r-text-2));max-width:min(var(--t-width,var(--m-lede,46ch)),var(--wall-text-cap));
}
.wall-t-body{
  font-size:calc(var(--r-fs-body) * var(--t-scale,1));font-weight:300;line-height:1.78;
  color:var(--t-color,var(--r-gray-2));max-width:min(var(--t-width,var(--m-body,62ch)),var(--wall-text-cap));
}
.wall-t-note{
  font-size:calc(13px * var(--t-scale,1));font-weight:300;line-height:1.7;
  color:var(--t-color,var(--r-gray-3));max-width:min(var(--t-width,var(--m-note,52ch)),var(--wall-text-cap));
}
.wall-b-text[data-bold] .wall-t-title,
.wall-b-text[data-bold] .wall-t-heading,
.wall-b-text[data-bold] .wall-t-lede,
.wall-b-text[data-bold] .wall-t-body,
.wall-b-text[data-bold] .wall-t-note{font-weight:700}
.wall-b-text[data-italic] .wall-t-title,
.wall-b-text[data-italic] .wall-t-heading,
.wall-b-text[data-italic] .wall-t-lede,
.wall-b-text[data-italic] .wall-t-body,
.wall-b-text[data-italic] .wall-t-note{font-style:italic}
.wall-b-text[data-align="center"]{text-align:center}
.wall-b-text[data-align="center"] > *{margin-inline:auto}
.wall-b-text[data-align="right"]{text-align:right}
.wall-b-text[data-align="right"] > *{margin-inline-start:auto}

/* ---------- the small furniture ---------------------------------------- */
.wall-b-rule{height:1px;background:var(--r-line);width:100%}
.wall-b-space{width:100%}

.wall-specs{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));
  gap:clamp(18px,2.6vw,34px);
}
.wall-specs dd{margin-top:8px;font-size:15px;font-weight:300;color:var(--r-text);line-height:1.5}
/* The specs that END the wall — every page's own Tools / Year / … list
   now sits last (content/<slug>.js) — open under a hairline, the way the
   project template's specs do (.r-specs in core/resn.css): the colophon
   of the page, over the way out and the pager. A specs block anywhere
   else on the wall stays as it was. */
.wall-b[data-type="specs"]:last-child .wall-specs{
  padding-top:clamp(24px,3vw,36px);border-top:1px solid var(--r-line);
}

.wall-link{
  display:inline-flex;align-items:center;gap:.9em;
  padding-bottom:6px;border-bottom:1px solid var(--r-gray-4);
  color:var(--r-white);
  transition:border-color var(--r-fast) ease-in-out,color var(--r-fast) var(--r-ease);
}
.wall-link:hover,.wall-link:focus-visible{color:var(--r-accent);border-color:var(--r-accent)}
.wall-link i{width:20px;height:1px;background:currentColor;transition:width var(--r-fast) var(--r-ease)}
.wall-link:hover i{width:30px}

/* ---------- the page around the wall ------------------------------------
   The way back is not here any more: on a single work it is in the mark's
   corner (#back, core/site.css).

   THE WAY OUT (wayOut() in core/wall.js): the project's Patreon and
   browser-piece links, at the END of the page — after the wall, whose
   last canvas is the project's specs, and over the pager's hairline. The
   same vocabulary as the project template's (.r-external, core/resn.css)
   and a link on the wall: white on a hairline that turns to the accent,
   a rule after the words that grows. Each is a 44px touch target; the
   padding is what buys that, not the type. Two classes deep, so it
   outranks the .resn .r-label grey the links also carry. */
.wall-out{
  max-width:var(--wall-max);
  margin:clamp(48px,7vw,96px) auto 0;
  display:flex;flex-wrap:wrap;align-items:center;
  gap:4px clamp(24px,3.4vw,44px);
}
.wall-out .wall-out-link{
  display:inline-flex;align-items:center;gap:.9em;min-height:44px;
  color:var(--r-white);
  transition:color var(--r-fast) var(--r-ease);
}
.wall-out .wall-out-link:hover,.wall-out .wall-out-link:focus-visible{color:var(--r-accent)}
.wall-out-link i{width:20px;height:1px;background:currentColor;opacity:.6;
  transition:width var(--r-fast) var(--r-ease),opacity var(--r-fast) var(--r-ease)}
.wall-out-link:hover i,.wall-out-link:focus-visible i{width:32px;opacity:1}
@media (prefers-reduced-motion:reduce){ .wall-out-link i{transition:none} }

/* hidden until core/wall.js knows the neighbours (the display:flex below
   would otherwise outrank the hidden attribute) */
.wall-pager[hidden]{display:none}
/* the pager closes up under the row, which already opened the gap */
.wall-out + .wall-pager{margin-top:clamp(20px,2.4vw,32px)}

.wall-pager{
  max-width:var(--wall-max);margin:clamp(56px,9vw,120px) auto 0;
  padding-top:clamp(24px,3vw,36px);border-top:1px solid var(--r-line);
  display:flex;justify-content:space-between;gap:var(--r-fs-body);
}
.wall-pager a{color:var(--r-gray);transition:color var(--r-fast) var(--r-ease)}
.wall-pager a:hover,.wall-pager a:focus-visible{color:var(--r-white)}

/* ---------- the two smaller walls ---------------------------------------
   Twelve-column placement is a desk-sized idea. Rather than ask the page
   to be authored twice, core/wall.js halves the same numbers for a
   tablet; on a phone the wall collapses to one canvas per row in the
   order they were authored, which is the only honest reading of a wall
   that no longer fits on the screen.                                   */
@media (max-width:760px){
  .wall{
    /* named, because the run below has to be able to subtract it */
    --wall-row-gap:clamp(28px,7vw,48px);
    grid-template-columns:1fr;row-gap:var(--wall-row-gap);
  }
  .wall-b{grid-column:1 / -1 !important;margin-top:0 !important}

  /* A RUN STAYS A RUN ON A PHONE. One column means there is no
     horizontal gutter left for the vertical one to agree with, but the
     pictures still have to read as one set rather than as the page's
     ordinary rhythm — so a run closes back up to the wall's own gap
     while everything around it keeps breathing.

     A negative margin against the row-gap rather than a second
     row-gap: gap is a property of the container and every row of the
     wall shares one, so the only place left to say "these two, closer"
     is on the item. `start` is the first canvas of a run and keeps the
     page's spacing — it is where the run begins, not part of its
     internal gutter. */
  .wall-b[data-run="in"]{
    margin-top:calc(var(--wall-gap) - var(--wall-row-gap)) !important;
  }
  .wall-guides{display:none}
  .wall-t-title,.wall-t-heading,
  .wall-t-lede,.wall-t-body,.wall-t-note,.wall-cap{max-width:none}
  /* nothing overlaps on a one-column wall, so depth has nothing to say */
  .wall-b{z-index:1 !important}
  .wall-t-title{font-size:clamp(28px,9vw,44px)}

  /* A phone canvas already runs the full width of the page, so there is
     no margin beside it for the glass to reach into — a sheet dialled
     for a desk would hang off both edges. Sideways reach is capped to
     the padding the page has; the vertical reach is left alone, because
     that is what still separates one plate from the next. */
  .wall-b[data-glass]::before{
    left:calc(-1 * min(var(--pane-x), 5vw));
    right:calc(-1 * min(var(--pane-x), 5vw));
  }
}

/* Touch has no pointer to be near, so the light is driven by what sits
   in the middle of the screen instead (core/wall.js). The pool goes:
   there is nothing carrying it. */
@media (hover:none){
  .wall-pool{display:none}
}

@media (prefers-reduced-motion:reduce){
  .wall-b{--near:1 !important}
  .wall-pool{display:none}
  .wall-b-frame img,.wall-b-frame video{opacity:1 !important;transition:none}
}
