/* =============================================================================
   EYES OF INFINITY  ::  system.css
   The shared stylesheet for every v7 page. Written 2026-08-01 against
   mockups/v7/BUILD-NOTES.md, research/interior-gallery-design.md,
   research/award-animation-language.md, research/animation-implementation-arsenal.md,
   research/premium-direction-brief.md and CONTEXT.md.

   THE LAWS THIS FILE OBEYS
     True black ground. Cold indigo and silver blue only. The single source of
     warmth on any page lives inside a photograph.
     Serif type only. Cinzel for ceremony, Cormorant Garamond for prose.
     Curves and circles. Zero chamfers, zero corner brackets, zero graduations.
     Entrances ride cubic-bezier(.16,1,.3,1) over 0.8s to 1.4s.
     Passages ride cubic-bezier(.87,0,.13,1) over 0.9s to 1.6s.
     Hover arrives in 0.3s to 0.6s on a cubic out and leaves gentler and slower.
     Masks over fades. Photographs are pristine at rest.

   THE MOTION GATE
     Every hidden state sits behind html.js.motion. That class is written by
     system.js from one resolved value, and the review hook (?motion=, #motion=,
     window.EOI_MOTION, localStorage eoi-motion) can pin it either way. There is
     no bare @media (prefers-reduced-motion) block anywhere in this file, because
     a bare media block cannot be overridden by the hook and a reviewer could
     never inspect both designed variants on one machine.

   ORDER
     0   tokens
     1   reset and ground
     2   the type system
     3   hairlines and ornament
     4   masthead and the return control
     5   section headers and ghost type
     6   the wall label
     7   the collection card
     8   the gallery figure and the rhythm grid
     9   controls: pill, quiet link, glyph
     10  the lightbox shell
     11  sky, grain, cursor
     12  the arrival plate and the load veil
     13  reveal classes
     14  the designed reduced-motion variant
     15  narrow viewports, down to 430px
   ============================================================================= */


/* =============================================================================
   0. TOKENS
   ============================================================================= */
:root{

  /* --- ground and ink ------------------------------------------------------ */
  --void:#000;                       /* the v4 ruling. True black, no exceptions */
  --text:#A6B3D2;                    /* prose */
  --head:#C2CDE8;                    /* controls and secondary headings */
  --bright:#DCE6FF;                  /* wordmark, hovered label, the brightest pixel */
  --muted:#67738F;                   /* metadata, sample copy markers */
  --ghost:rgba(166,179,210,.10);     /* type set as texture, 10 percent above the field */
  --ghost-lit:rgba(196,212,255,.16);

  /* --- lines --------------------------------------------------------------- */
  --line:rgba(150,172,220,.20);
  --line-soft:rgba(150,172,220,.11);
  --line-faint:rgba(150,172,220,.06);
  --line-lit:rgba(220,230,255,.55);
  --rule-stroke:#96ACDC;
  --edge:rgba(210,224,255,.10);      /* the perimeter a photograph needs on true black */
  --edge-lit:rgba(220,232,255,.30);

  /* --- light --------------------------------------------------------------- */
  --bloom:rgba(220,230,255,.18);
  --bloom-soft:rgba(220,230,255,.09);
  --select-bg:rgba(150,172,220,.24);

  /* --- the sky -------------------------------------------------------------
     Six star temperatures, cold the whole way, running silver at rung 0 to
     indigo at rung 5. Each rung is a core, a mid and a halo. The halo is the
     deepest indigo of the three, so a star carries its colour in the dim wings
     and keeps its bright pixel close to silver.

     The field hands the silver rungs to its bright stars and the indigo rungs
     to its faint ones. That is the whole reason this reads as a sky and not as
     a tint: the loudest pixels stay near white, and the colour sits in the
     quiet ones, where it varies point to point. Tinting every star the same
     amount produces a blue wash, which this ladder exists to prevent.

     The rungs are spaced on hue, not on tint. Rung 0 sits at 208 degrees and
     rung 5 at 241, with saturation climbing from five percent to thirty three
     across the cores. Spacing on tint alone gives six shades of one colour,
     which the eye reads as a single wash; spacing on hue is what makes two
     neighbouring stars look like two different stars. The whole ladder stays
     in the cold half of the wheel, and blue leads red in every value here, so
     nothing in this block can put warmth on the ground. */
  --star-0-core:#F2F9FF; --star-0-mid:#E9F4FF; --star-0-halo:#B7D0F2;
  --star-1-core:#E4F0FF; --star-1-mid:#D2E2FF; --star-1-halo:#96B2F2;
  --star-2-core:#D6E3FF; --star-2-mid:#BACBFF; --star-2-halo:#748DF2;
  --star-3-core:#C8D3FF; --star-3-mid:#A2AFFF; --star-3-halo:#6D79F2;
  --star-4-core:#BAC1FF; --star-4-mid:#9498FF; --star-4-halo:#706DF2;
  --star-5-core:#AEACFF; --star-5-mid:#9C94FF; --star-5-halo:#766DF2;
  --star-gain:.62;                   /* the ceiling on the brightest star in the field */
  --star-edge:150,180,255;           /* published bare, because the chamber halo reads it */
  --star-flat:#D6E3FF;               /* rung two, the centre of the field, for the swatch board */
  --meteor:#DCE6FF;
  --neb-a:rgba(40,54,116,.055);
  --neb-b:rgba(52,44,110,.040);

  /* --- the material void ---------------------------------------------------
     What sits between the stars. A field of points on flat black reads as
     points on flat black; the real sky has veiling and dust in it, and this is
     that material.

     Two colours, both cold. lo is the veiling at its thinnest and it is the
     colour the void carries away from centre. hi is a filament at its densest,
     and it is the only place in this block that reaches violet. Blue leads red
     in both, so nothing here can put warmth on the ground.

     gain is the ceiling on the whole material, applied to an alpha map that
     spends its full byte range on shape. At .17 the densest filament in the
     field composites to about rgb(10,10,22) over black, a lightness of two and
     a half, and two thirds of the field sits at rgb(1,1,3), a lightness under
     a half. That is presence rather than light. Raise this token to make the
     void more material and lower it to take the material away; nothing else in
     the block needs touching to move it.

     floor is the smooth indigo that rides the vignette, and it is what puts
     material in the void itself rather than only where the noise is. It holds
     a dead centre, so the middle of the screen, where the photographs hang, is
     a literal zero. */
  --dust-lo:#1E2660;
  --dust-hi:#5E68C8;
  --dust-gain:.17;
  --dust-floor:.20;
  --skyfall-opacity:.26;
  --grain-opacity:.025;

  /* --- the cursor ----------------------------------------------------------
     hot is the luminous point, mid is the hairline, cool is held for a mark
     that wants a second weight. The halo is a tenth of an alpha at the centre
     and gone by three quarters of the radius: enough to lift a hairline off
     black, too little to be seen as light. */
  --cursor-hot:#FFFFFF;
  --cursor-mid:#E4EEFF;
  --cursor-cool:#A8C0FF;
  --cursor-halo-0:rgba(190,212,255,.10);
  --cursor-halo-1:rgba(158,186,255,.035);
  --cursor-halo-2:rgba(140,175,255,0);

  /* --- the aperture -------------------------------------------------------- */
  --orb:min(70vh,48vw);
  --eye-rim:rgba(150,172,220,.14);
  --eye-bloom:rgba(84,112,190,.07);
  /* 2026-08-09 morning. This used to reach .70 of black at the rim, which is
     section 8's own warning applied to the one plate on the site that was
     exempt from it: a vignette on black dissolves the edge instead of framing
     it. It now holds the print's material to the boundary and spends twelve
     hundredths on shape. The seat, the last three device pixels of contact
     shadow, is on .eye__rim.

     2026-08-09 evening. THE GEOMETRY WAS WRONG AND EVERY REDUCED-MOTION
     VISITOR SAW IT. The stops were claimed to be the shader's own curve
     sampled and they could not have been, for two reasons that have nothing to
     do with the numbers. A radial-gradient with no size keyword is
     farthest-corner, so on a square element the 100 percent stop lands at the
     corner, r*sqrt(2) out, and the rim of the disc sits at 70.7 percent along
     the ramp instead of 100. Measured on the live page the applied alpha at the
     rim was .031 to .058 against the .12 it was written for, a third of the
     shade in the wrong place. And it was centred at 50% 46%, so it varied top
     to bottom around a boundary the shader treats as perfectly radial.

     closest-side on a square element is exactly half its width, which is
     exactly the disc's radius, so a stop at N percent now sits at rn = N/100
     and the sampling below is true by construction. Centred. The stops are
     0.12 * smoothstep(0.50,1.0,rn) evaluated at 60, 70, 80, 90 and 100. */
  --vignette:radial-gradient(circle closest-side at 50% 50%,
     rgba(0,0,0,0) 50%, rgba(0,0,0,.012) 60%, rgba(0,0,0,.042) 70%,
     rgba(0,0,0,.078) 80%, rgba(0,0,0,.108) 90%, rgba(0,0,0,.12) 100%);

  /* --- faces --------------------------------------------------------------- */
  --cin:"Cinzel","Cormorant Garamond",Georgia,serif;
  --gar:"Cormorant Garamond","EB Garamond",Georgia,serif;

  /* --- the type scale ------------------------------------------------------
     A modular scale with a deliberate spread. The smallest register on a page
     is 9px and the largest display register reaches 72px, a ratio above 7 to 1.
     The interior research names timid ratios as the most reliable template
     tell, so the scale is built wide on purpose. */
  --t-micro:9px;                              /* catalogue number, credit line */
  --t-fine:clamp(9.5px,.72vw,11px);           /* letterspaced small caps */
  --t-small:clamp(10.5px,.82vw,12px);         /* rail labels */
  --t-meta:clamp(12px,.95vw,13.5px);          /* place and date, serif */
  --t-body:clamp(15px,1.05vw,17px);
  --t-lede:clamp(17px,1.35vw,21px);
  --t-title:clamp(20px,2.05vw,30px);          /* card names, masthead wordmark */
  --t-head:clamp(25px,3.1vw,44px);            /* section titles */
  --t-display:clamp(34px,5.2vw,72px);         /* the wall-label title */
  --t-colossus:clamp(56px,12vw,190px);        /* ghost type */

  --lh-tight:1.06;
  --lh-title:1.22;
  --lh-prose:1.62;
  --measure:54ch;                             /* 48 to 55 characters, held at every width */

  /* --- tracking -----------------------------------------------------------
     Every letterspaced run carries a matching text-indent when it is centred,
     which cancels the trailing space that letter-spacing adds after the last
     glyph. Dropping it puts every centred label a few pixels left of centre. */
  --ls-wide:.34em;
  --ls-caps:.30em;
  --ls-lab:.26em;
  --ls-rail:.22em;
  --ls-num:.18em;

  /* --- the spacing scale ---------------------------------------------------
     One scale, used everywhere. Nothing on any page is spaced off it. */
  --s-1:4px;
  --s-2:8px;
  --s-3:12px;
  --s-4:18px;
  --s-5:26px;
  --s-6:38px;
  --s-7:56px;
  --s-8:82px;
  --s-9:120px;
  --s-10:176px;
  --gutter:clamp(20px,4.2vw,76px);
  --mast-h:64px;                              /* ornament, px locked */

  /* --- easing -------------------------------------------------------------- */
  --enter:cubic-bezier(.16,1,.3,1);           /* every entrance */
  --pass:cubic-bezier(.87,0,.13,1);           /* every passage */
  --quart:cubic-bezier(.25,1,.5,1);           /* cursor and scale */
  --out:cubic-bezier(.33,1,.68,1);            /* hover in, cubic out */
  --out-soft:cubic-bezier(.25,.72,.4,1);      /* hover out, gentler */

  /* --- duration ------------------------------------------------------------ */
  --d-hover-in:.42s;
  --d-hover-out:.72s;
  --d-enter:1.15s;
  --d-enter-slow:1.4s;
  --d-char:1.05s;
  --d-draw:1.6s;
  --d-pass:1.05s;
  --d-swallow:760ms;
  --d-dissolve:520ms;
  --d-veil:.76s;
  --d-lbx-open:820ms;
  --d-lbx-close:660ms;                        /* close runs at eighty percent of open */

  /* --- stagger ------------------------------------------------------------- */
  --stg-line:90ms;
  --stg-char:30ms;
  --stg-grid:60ms;
  --stg-body:60ms;

  /* --- the layer ladder ---------------------------------------------------- */
  --z-neb:0;
  --z-sky:1;
  --z-page:2;
  --z-mast:20;
  --z-rail:24;
  --z-portal:40;
  --z-lightbox:50;
  --z-grain:60;
  --z-plate:9000;
  --z-cursor:9999;                            /* the plate sits at 9000 and the cursor rides above it */

  /* --- textures ------------------------------------------------------------ */
  --grain:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='3' seed='9' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23g)'/%3E%3C/svg%3E");
  --starfall:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='400'%3E%3Cfilter id='s'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='1' seed='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0.80 0 0 0 0 0.86 0 0 0 0 1 11 0 0 0 -10.15'/%3E%3C/filter%3E%3Crect width='400' height='400' filter='url(%23s)'/%3E%3C/svg%3E");
}


/* =============================================================================
   1. RESET AND GROUND
   ============================================================================= */
*{box-sizing:border-box;}

html{
  background:var(--void);
  color-scheme:dark;
  -webkit-text-size-adjust:100%;
}
html.is-locked{overflow:hidden;}

body{
  margin:0;
  min-height:100%;
  background:var(--void);
  color:var(--text);
  font-family:var(--gar);
  font-weight:400;
  font-size:var(--t-body);
  line-height:var(--lh-prose);
  font-optical-sizing:auto;
  font-variant-numeric:oldstyle-nums;
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
  text-rendering:optimizeLegibility;
}

img,svg,canvas,video{display:block;max-width:100%;}
img{height:auto;border:0;}
figure{margin:0;}
button{font:inherit;color:inherit;background:none;border:0;padding:0;}
a{color:inherit;}
::selection{background:var(--select-bg); color:var(--bright);}

/* The page column. overflow-x is clipped here so a photograph can bleed past a
   viewport edge without the document ever gaining a horizontal scrollbar. */
.page{
  position:relative;
  z-index:var(--z-page);
  overflow-x:clip;
  overflow-y:visible;
  padding:0 var(--gutter);
}
.page--flush{padding:0;}
.wrap{max-width:1560px; margin:0 auto;}
.prose{max-width:var(--measure);}

.skip{
  position:absolute; left:-9999px; top:0;
  padding:var(--s-3) var(--s-5);
  background:var(--void);
  border:1px solid var(--line);
  color:var(--bright);
  z-index:calc(var(--z-plate) + 1);
}
.skip:focus{left:var(--gutter); top:var(--s-3);}

/* tabindex="-1" is a script-focus target (wing titles, the blast), never a
   control; a ring around it draws a box around the composition itself
   (operator report, 2026-08-14). Excluding it from the house ring was not
   enough: with no author rule, the browser painted its own default ring at
   the end of every passage (measured live, 2026-08-15). The suppression
   must be explicit. Real controls keep their ring. */
[tabindex="-1"]:focus,
[tabindex="-1"]:focus-visible{outline:none;}
:where(a,button,[tabindex]:not([tabindex="-1"])):focus-visible{
  outline:1px solid var(--line-lit);
  outline-offset:3px;
}


/* =============================================================================
   2. THE TYPE SYSTEM
   Two faces. Cinzel carries ceremony and every letterspaced small-cap run.
   Cormorant Garamond carries prose, metadata and italics.
   ============================================================================= */

/* --- ceremony ------------------------------------------------------------- */
.cin{font-family:var(--cin); font-weight:500;}
.gar{font-family:var(--gar); font-weight:400;}

/* --- the letterspaced small-cap run --------------------------------------
   --ls carries the tracking so the matching indent is written once. Centred
   runs cancel the trailing space with text-indent. Right-aligned runs cancel
   it with a negative margin, because an indent would push the first line. */
.caps{
  --ls:var(--ls-lab);
  display:inline-block;
  font-family:var(--cin);
  font-weight:500;
  text-transform:uppercase;
  letter-spacing:var(--ls);
  line-height:1.4;
  font-variant-numeric:lining-nums tabular-nums;
}
.caps.is-centred{text-indent:var(--ls);}
.caps.is-right{margin-right:calc(var(--ls) * -1);}
.caps--micro{--ls:var(--ls-caps); font-size:var(--t-micro); font-weight:400;}
.caps--fine{--ls:var(--ls-lab); font-size:var(--t-fine);}
.caps--rail{--ls:var(--ls-rail); font-size:var(--t-small);}
.caps--wide{--ls:var(--ls-wide); font-size:var(--t-title);}

/* --- headings ------------------------------------------------------------- */
.h-display{
  margin:0;
  font-family:var(--cin); font-weight:500;
  font-size:var(--t-display);
  line-height:var(--lh-tight);
  letter-spacing:.03em;
  color:var(--bright);
}
.h-head{
  margin:0;
  font-family:var(--cin); font-weight:500;
  font-size:var(--t-head);
  line-height:var(--lh-title);
  letter-spacing:.06em;
  color:var(--bright);
}
.h-title{
  margin:0;
  font-family:var(--cin); font-weight:500;
  font-size:var(--t-title);
  line-height:var(--lh-title);
  letter-spacing:.12em;
  color:var(--head);
}

/* --- prose ---------------------------------------------------------------- */
.p{margin:0 0 var(--s-4); max-width:var(--measure);}
.p:last-child{margin-bottom:0;}
.p--lede{font-size:var(--t-lede); line-height:1.5; color:var(--head);}
.p--italic{font-style:italic;}
.p--dim{color:var(--muted);}
.meta{
  font-family:var(--gar);
  font-size:var(--t-meta);
  line-height:1.5;
  color:var(--text);
  opacity:.72;
}
.num{
  font-family:var(--cin); font-weight:400;
  font-size:var(--t-micro);
  letter-spacing:var(--ls-num);
  font-variant-numeric:lining-nums tabular-nums;
  color:var(--text);
  opacity:.42;
}

/* The marker every invented string on a mockup carries. It is visible on
   purpose, so nothing placeholder can be mistaken for written copy. */
.mark{
  display:inline-block;
  font-family:var(--cin); font-weight:400;
  font-size:var(--t-micro);
  letter-spacing:var(--ls-caps);
  text-transform:uppercase;
  color:var(--muted);
  opacity:.7;
}


/* =============================================================================
   3. HAIRLINES AND ORNAMENT
   One pixel at every device pixel ratio. Nothing is graduated and nothing is
   numbered. Curves only.
   ============================================================================= */
.rule{
  display:block; width:100%; height:1px;
  border:0; margin:0;
  background:var(--line-soft);
}
.rule--faint{background:var(--line-faint);}
.rule--lit{background:var(--line);}
.rule--short{width:min(280px,52vw);}
.rule--centred{margin-left:auto; margin-right:auto;}

/* the drawn rule: a curve rather than a straight edge, on pathLength 1 so no
   script has to measure it */
.rule-draw{display:block; width:min(280px,52vw); height:12px; overflow:visible;}
.rule-draw path,.rule-draw circle,.rule-draw line{
  fill:none;
  stroke:var(--rule-stroke);
  stroke-width:1;
  vector-effect:non-scaling-stroke;
}
.draw{stroke-dasharray:1; stroke-dashoffset:1;}

/* the sigil, drawn small for the return control */
.sigil-mini{width:22px; height:22px; flex:0 0 auto; overflow:visible;}
.sigil-mini circle,.sigil-mini path{
  fill:none; stroke:var(--rule-stroke); stroke-width:1;
  vector-effect:non-scaling-stroke;
  transition:stroke var(--d-hover-in) var(--out);
}
.sigil-mini .pupil{fill:var(--bright); stroke:none; opacity:.9;}


/* =============================================================================
   4. MASTHEAD AND THE RETURN CONTROL
   Three items and nothing else: the way back, the wordmark, the wing.
   ============================================================================= */
.mast{
  position:fixed; top:0; left:0; right:0;
  z-index:var(--z-mast);
  height:var(--mast-h);
  display:grid;
  grid-template-columns:1fr auto 1fr;
  align-items:center;
  gap:var(--s-4);
  padding:0 var(--gutter);
  /* a black scrim so the wordmark survives over a photograph that bleeds up */
  background:linear-gradient(to bottom, rgba(0,0,0,.86) 0%, rgba(0,0,0,.52) 62%, rgba(0,0,0,0) 100%);
  pointer-events:none;
}
.mast > *{pointer-events:auto;}
.mast::after{
  content:""; position:absolute; left:var(--gutter); right:var(--gutter); bottom:0;
  height:1px; background:var(--line-faint);
}
.mast__mark{
  justify-self:center;
  font-family:var(--cin); font-weight:500;
  font-size:var(--t-fine);
  letter-spacing:var(--ls-wide); text-indent:var(--ls-wide);
  text-transform:uppercase;
  color:var(--bright);
  text-decoration:none;
  white-space:nowrap;
  transition:color var(--d-hover-in) var(--out), text-shadow var(--d-hover-in) var(--out);
}
.mast__mark:hover,.mast__mark:focus-visible{text-shadow:0 0 18px var(--bloom-soft);}
.mast__wing{
  justify-self:end;
  color:var(--muted);
  white-space:nowrap;
}

/* the way back to the portal */
.ret{
  justify-self:start;
  display:inline-flex; align-items:center; gap:var(--s-3);
  text-decoration:none;
  color:var(--text);
  white-space:nowrap;
}
.ret .caps{
  --ls:var(--ls-lab);
  font-size:var(--t-fine);
  color:var(--text);
  transition:color var(--d-hover-in) var(--out), text-shadow var(--d-hover-in) var(--out);
}
.ret:hover .caps,.ret:focus-visible .caps{color:var(--bright); text-shadow:0 0 16px var(--bloom-soft);}
.ret:hover .sigil-mini circle,.ret:hover .sigil-mini path,
.ret:focus-visible .sigil-mini circle,.ret:focus-visible .sigil-mini path{stroke:var(--line-lit);}
.ret:focus-visible{outline:none;}
.ret:focus-visible .caps{text-decoration:underline; text-underline-offset:5px;}
/* leaving is gentler and slower than arriving */
.ret .caps,.mast__mark,.sigil-mini circle,.sigil-mini path{
  transition-duration:var(--d-hover-out);
  transition-timing-function:var(--out-soft);
}
.ret:hover .caps,.ret:hover .sigil-mini circle,.ret:hover .sigil-mini path,
.mast__mark:hover{
  transition-duration:var(--d-hover-in);
  transition-timing-function:var(--out);
}

/* the page body clears the fixed masthead */
.page--inset{padding-top:calc(var(--mast-h) + var(--s-7));}


/* =============================================================================
   5. SECTION HEADERS AND GHOST TYPE
   ============================================================================= */
.sect{padding:var(--s-9) 0;}
.sect--tight{padding:var(--s-7) 0;}
.sect + .sect{border-top:1px solid var(--line-faint);}

.sect-head{
  display:flex; flex-direction:column; gap:var(--s-3);
  margin:0 0 var(--s-7);
}
.sect-head__eyebrow{
  --ls:var(--ls-caps);
  font-size:var(--t-micro);
  color:var(--muted);
}
.sect-head__rule{margin-top:var(--s-4); width:min(240px,34vw);}
.sect-head__note{color:var(--muted); font-size:var(--t-meta); max-width:var(--measure);}

/* type set as texture. The cheapest depth available on true black. */
.ghost{
  font-family:var(--cin); font-weight:500;
  font-size:var(--t-colossus);
  line-height:.86;
  letter-spacing:.02em;
  text-transform:uppercase;
  color:var(--ghost);
  pointer-events:none;
  user-select:none;
  white-space:nowrap;
}
.ghost--behind{
  position:absolute; left:0; top:0;
  z-index:-1;
}


/* =============================================================================
   6. THE WALL LABEL
   Four registers with a spread above six to one: number, place and date,
   medium, title. The title is bottom anchored, the way a museum sets a label.
   ============================================================================= */
.label{
  display:flex; flex-direction:column;
  gap:var(--s-2);
  color:var(--text);
}
.label__num{
  --ls:var(--ls-num);
  font-family:var(--cin); font-weight:400;
  font-size:var(--t-micro);
  letter-spacing:var(--ls-num);
  font-variant-numeric:lining-nums tabular-nums;
  color:var(--text);
  opacity:.42;
}
.label__meta{
  font-family:var(--gar);
  font-size:var(--t-meta);
  color:var(--text);
  opacity:.6;
}
.label__medium{
  --ls:.18em;
  font-size:var(--t-micro);
  color:var(--text);
  opacity:.4;
}
.label__title{
  margin:var(--s-4) 0 0;
  font-family:var(--cin); font-weight:500;
  font-size:var(--t-display);
  line-height:var(--lh-tight);
  letter-spacing:.02em;
  color:var(--bright);
  opacity:.92;
}
.label--anchored{height:100%;}
.label--anchored .label__title{margin-top:auto; padding-top:var(--s-7);}

/* the short caption that sits under a plate in a grid */
.cap{
  display:flex; flex-direction:column; gap:var(--s-1);
  margin-top:var(--s-3);
}
.cap__title{
  font-family:var(--gar);
  font-size:var(--t-meta);
  color:var(--head);
  line-height:1.35;
}
.cap__meta{
  --ls:.16em;
  font-size:var(--t-micro);
  color:var(--muted);
}


/* =============================================================================
   7. THE COLLECTION CARD
   Four registers and nothing more: count, name, place and period, one line.
   Anything past that is a product card.
   ============================================================================= */
.card{
  position:relative;
  display:flex; flex-direction:column;
  text-decoration:none;
  color:inherit;
}
.card__well{
  /* same reason as .fig__well: authored as a span, so it must be blockified
     for overflow, aspect-ratio and the unmask clip-path to apply at all. */
  display:block;
  position:relative;
  overflow:hidden;
  background:var(--void);
  aspect-ratio:2/3;
}
.card__well::after{
  content:""; position:absolute; inset:0;
  border:1px solid var(--edge);
  pointer-events:none;
  transition:border-color var(--d-hover-out) var(--out-soft);
}
.card:hover .card__well::after,.card:focus-visible .card__well::after{
  border-color:var(--edge-lit);
  transition-duration:var(--d-hover-in);
  transition-timing-function:var(--out);
}
.card__img{
  width:100%; height:100%;
  object-fit:cover;
  transform:scale(1);
  transition:transform var(--d-hover-out) var(--out-soft);
}
.card:hover .card__img,.card:focus-visible .card__img{
  transform:scale(1.035);
  transition-duration:.55s;
  transition-timing-function:var(--out);
}
.card__body{
  display:flex; flex-direction:column; gap:var(--s-2);
  padding-top:var(--s-4);
}
.card__count{--ls:var(--ls-num); font-size:var(--t-micro); color:var(--muted);}
.card__name{
  font-family:var(--cin); font-weight:500;
  font-size:var(--t-title);
  line-height:var(--lh-title);
  letter-spacing:.09em;
  color:var(--bright);
  transition:text-shadow var(--d-hover-out) var(--out-soft);
}
.card:hover .card__name,.card:focus-visible .card__name{
  text-shadow:0 0 20px var(--bloom-soft);
  transition-duration:var(--d-hover-in);
  transition-timing-function:var(--out);
}
.card__where{font-size:var(--t-meta); color:var(--text); opacity:.62;}
.card__line{font-size:var(--t-meta); font-style:italic; color:var(--muted); max-width:34ch;}
.card:focus-visible{outline:none;}
.card:focus-visible .card__well::after{border-color:var(--line-lit);}


/* =============================================================================
   8. THE GALLERY FIGURE AND THE RHYTHM GRID
   A print has a defined perimeter and square corners. On true black the edge
   is a one pixel hairline at ten percent white, inset by zero. There is no
   drop shadow anywhere in this file, and there is no vignette on a grid plate,
   because a vignette on black dissolves the edge instead of framing it.
   ============================================================================= */
.fig{position:relative; display:block; margin:0;}

.fig__well{
  /* display:block is load-bearing, not cosmetic. The wells are authored as
     <span>, and an inline box silently ignores overflow, aspect-ratio and
     clip-path, which kills the ratio crops, the hover scale containment and
     the unmask entrance. Fixed here once, centrally. */
  display:block;
  position:relative;
  overflow:hidden;
  background:var(--void);
}
.fig__well::after{
  content:""; position:absolute; inset:0;
  border:1px solid var(--edge);
  pointer-events:none;
  transition:border-color var(--d-hover-out) var(--out-soft);
}
.fig__img{
  width:100%; height:auto;
  transform:scale(1);
  transform-origin:50% 50%;
  transition:transform var(--d-hover-out) var(--out-soft);
}
.fig--ratio .fig__well{aspect-ratio:3/2;}
.fig--ratio .fig__img{height:100%; object-fit:cover;}
.fig--portrait .fig__well{aspect-ratio:2/3;}
.fig--portrait .fig__img{height:100%; object-fit:cover;}
.fig--square .fig__well{aspect-ratio:1/1;}
.fig--square .fig__img{height:100%; object-fit:cover;}

.fig__btn{
  display:block; width:100%; text-align:left; cursor:pointer;
}
.fig__btn:focus-visible{outline:none;}
.fig__btn:focus-visible + .cap .cap__title,
.fig__btn:focus-visible .cap__title{color:var(--bright);}
.fig__btn:focus-visible .fig__well::after{border-color:var(--line-lit);}

.fig:hover .fig__well::after{
  border-color:var(--edge-lit);
  transition-duration:var(--d-hover-in);
  transition-timing-function:var(--out);
}
.fig:hover .fig__img{
  transform:scale(1.04);
  transition-duration:.55s;
  transition-timing-function:var(--out);
}

/* the catalogue number, anchored outside the plate at the top right.
   At rest a registration mark holds the position. On pointer movement every
   mark in the collection resolves into its number. */
.fig__num{
  position:absolute; right:0; top:-16px;
  --ls:var(--ls-num);
  font-family:var(--cin); font-weight:400;
  font-size:var(--t-micro);
  letter-spacing:var(--ls-num);
  font-variant-numeric:lining-nums tabular-nums;
  color:var(--text);
  opacity:0;
  transition:opacity var(--d-hover-out) var(--out-soft);
  pointer-events:none;
}
.fig__reg{
  position:absolute; right:1px; top:-16px;
  width:7px; height:7px;
  opacity:.3;
  transition:opacity var(--d-hover-out) var(--out-soft);
  pointer-events:none;
}
.fig__reg::before,.fig__reg::after{
  content:""; position:absolute; background:var(--rule-stroke);
}
.fig__reg::before{left:3px; top:0; width:1px; height:7px;}
.fig__reg::after{left:0; top:3px; width:7px; height:1px;}
html.pointing .fig__num{opacity:.42; transition-duration:var(--d-hover-in); transition-timing-function:var(--out);}
html.pointing .fig__reg{opacity:0;}
.fig:hover .fig__num{opacity:.86;}

/* the rhythm grid. Twelve columns, three size tiers, empty cells on purpose,
   one plate per screenful cropped by the viewport edge. */
.grid{
  display:grid;
  grid-template-columns:repeat(12,minmax(0,1fr));
  column-gap:var(--s-5);
  row-gap:var(--s-9);
  align-items:start;
}
.grid > .fig{grid-column:span 3;}
.fig--sm{grid-column:span 2;}
.fig--md{grid-column:span 3;}
.fig--lg{grid-column:span 5;}
/* vertical offsets, quantised to the spacing scale so the ragged row edge
   reads as intent */
.fig--drop-1{margin-top:var(--s-6);}
.fig--drop-2{margin-top:var(--s-8);}
.fig--drop-3{margin-top:var(--s-10);}
/* the bleed. The plate is cropped by the page edge, which is what stops a
   grid from reading as a template. .page clips, so no scrollbar appears. */
.fig--bleed-l{margin-left:calc(var(--gutter) * -1 - 4vw);}
.fig--bleed-r{margin-right:calc(var(--gutter) * -1 - 4vw);}

/* column starts. Leaving twenty to thirty percent of the lattice empty is the
   mechanism most grids skip, and it is the one that stops a wall of
   photographs from reading as a contact sheet. These release at 820px, where
   the lattice collapses and every plate takes the full measure. */
.gc1{grid-column-start:1;} .gc2{grid-column-start:2;} .gc3{grid-column-start:3;}
.gc4{grid-column-start:4;} .gc5{grid-column-start:5;} .gc6{grid-column-start:6;}
.gc7{grid-column-start:7;} .gc8{grid-column-start:8;} .gc9{grid-column-start:9;}

/* the filmstrip index: a whole collection as one band on an empty field */
.strip{
  display:flex; align-items:center; gap:2px;
  overflow-x:auto;
  overscroll-behavior-x:contain;
  padding:var(--s-9) 0;
  scrollbar-width:thin;
  scrollbar-color:var(--line) transparent;
}
.strip__cell{
  flex:0 0 auto;
  width:55px; height:78px;
  overflow:hidden;
  background:var(--void);
  opacity:.42;
  transition:opacity var(--d-hover-out) var(--out-soft);
  cursor:pointer;
}
.strip__cell img{width:100%; height:100%; object-fit:cover;}
.strip__cell:hover,.strip__cell.is-current{
  opacity:1;
  transition-duration:var(--d-hover-in);
  transition-timing-function:var(--out);
}


/* =============================================================================
   9. CONTROLS
   ============================================================================= */
.pill{
  position:relative;
  display:inline-flex; align-items:center; justify-content:center;
  height:44px; padding:0 34px;
  border:1px solid var(--line); border-radius:999px;
  background:transparent;
  font-family:var(--cin); font-weight:500; font-size:11px;
  letter-spacing:.32em; text-indent:.32em;
  text-transform:uppercase;
  color:var(--head); text-decoration:none;
  cursor:pointer;
  transition:border-color var(--d-hover-out) var(--out-soft),
             color var(--d-hover-out) var(--out-soft),
             box-shadow var(--d-hover-out) var(--out-soft);
  will-change:transform;
}
.pill:hover,.pill:focus-visible{
  border-color:var(--line-lit); color:var(--bright);
  box-shadow:0 0 26px var(--bloom-soft);
  outline:none;
  transition-duration:.45s;
  transition-timing-function:var(--out);
}

/* the quiet link. The underline is drawn, never switched on. */
.quiet{
  position:relative;
  display:inline-block;
  text-decoration:none;
  color:var(--head);
  transition:color var(--d-hover-out) var(--out-soft);
}
.quiet::after{
  content:""; position:absolute; left:0; right:0; bottom:-3px;
  height:1px; background:var(--line-lit);
  transform:scaleX(0); transform-origin:0 50%;
  transition:transform var(--d-hover-out) var(--out-soft);
}
.quiet:hover,.quiet:focus-visible{color:var(--bright); outline:none;}
.quiet:hover::after,.quiet:focus-visible::after{
  transform:scaleX(1);
  transition-duration:.5s;
  transition-timing-function:var(--out);
}

/* the glyph control. Two circles and no words. */
.glyph{
  display:inline-flex; align-items:center; justify-content:center;
  width:34px; height:34px;
  border:1px solid var(--line); border-radius:50%;
  color:var(--head);
  font-family:var(--cin); font-size:12px; line-height:1;
  cursor:pointer;
  transition:border-color var(--d-hover-out) var(--out-soft),
             color var(--d-hover-out) var(--out-soft),
             box-shadow var(--d-hover-out) var(--out-soft);
}
.glyph:hover,.glyph:focus-visible{
  border-color:var(--line-lit); color:var(--bright);
  box-shadow:0 0 14px var(--bloom-soft);
  outline:none;
  transition-duration:var(--d-hover-in);
  transition-timing-function:var(--out);
}
.glyph[aria-pressed="true"]{border-color:var(--line-lit); color:var(--bright);}

.controls{display:flex; align-items:center; gap:var(--s-3); flex-wrap:wrap;}


/* =============================================================================
   10. THE LIGHTBOX SHELL
   Three bands: the collection as a rail at the left edge, the photograph, the
   wall label at the right. Chrome is absent at rest and arrives on intent.
   ============================================================================= */
.lbx{
  position:fixed; inset:0;
  z-index:var(--z-lightbox);
  background:var(--void);
  display:none;
}
/* two columns since 2026-08-15: the label panel was withdrawn with the
   apparatus, and the reserved third column pushed every photograph off
   centre (operator report). The close glyph now anchors to the shell. */
.lbx.is-open{
  display:grid;
  grid-template-columns:clamp(56px,7vw,86px) minmax(0,1fr);
}
.lbx__rail{
  position:relative;
  height:100vh; height:100svh;
  overflow-y:auto;
  overscroll-behavior:contain;
  padding:var(--s-5) var(--s-3);
  display:flex; flex-direction:column; gap:var(--s-2); align-items:center;
  scrollbar-width:thin;
  scrollbar-color:var(--line) transparent;
}
.lbx__thumb{
  flex:0 0 auto;
  width:100%; max-width:56px; aspect-ratio:2/3;
  overflow:hidden;
  background:var(--void);
  opacity:.34;
  cursor:pointer;
  transition:opacity var(--d-hover-out) var(--out-soft);
}
.lbx__thumb img{width:100%; height:100%; object-fit:cover;}
.lbx__thumb:hover{opacity:.72; transition-duration:var(--d-hover-in); transition-timing-function:var(--out);}
.lbx__thumb.is-current{opacity:1;}
.lbx__thumb:focus-visible{outline:1px solid var(--line-lit); outline-offset:2px;}

.lbx__stage{
  position:relative;
  display:flex; align-items:center; justify-content:center;
  padding:var(--s-5) var(--s-4);
  min-width:0;
}
.lbx__img{
  max-width:100%;
  max-height:calc(100vh - var(--s-8));
  max-height:calc(100svh - var(--s-8));
  width:auto; height:auto;
  object-fit:contain;
}
.lbx__frame{
  position:relative;
  display:block;
  line-height:0;
}
.lbx__frame::after{
  content:""; position:absolute; inset:0;
  border:1px solid var(--edge);
  pointer-events:none;
}

.lbx__panel{
  /* an anchor for the close glyph, nothing more; the wall label it carried
     is withdrawn */
  position:absolute; top:0; right:0; z-index:3;
  padding:0;
}

/* chrome: gone at rest, present on intent. Gone means gone: visibility rides
   the fade, so a control the eye cannot see is not sitting in the tab order
   waiting for a keyboard to land on nothing. */
.lbx__rail,.lbx__panel,.lbx__close,.lbx__prev,.lbx__next{
  opacity:0;
  visibility:hidden;
  transition:opacity var(--d-hover-out) var(--out-soft),
             visibility 0s linear var(--d-hover-out);
}
.lbx.is-chrome .lbx__rail,
.lbx.is-chrome .lbx__panel,
.lbx.is-chrome .lbx__close,
.lbx.is-chrome .lbx__prev,
.lbx.is-chrome .lbx__next{
  opacity:1;
  visibility:visible;
  transition:opacity var(--d-hover-in) var(--out),
             visibility 0s;
}

.lbx__close{
  position:absolute; right:var(--s-5); top:var(--s-5);
  z-index:2;
}
.lbx__prev,.lbx__next{
  position:absolute; top:50%; transform:translateY(-50%);
  z-index:2;
}
.lbx__prev{left:var(--s-2);}
.lbx__next{right:var(--s-2);}

.lbx__count{
  --ls:var(--ls-num);
  margin-top:var(--s-5);
  font-size:var(--t-micro);
  color:var(--muted);
}

/* The shared element morph rides the house entrance curve at the museum
   duration. A title carried inside a scaled image goes blurry, so the label
   never takes a transition name and simply arrives with the panel. */
::view-transition-group(eoi-plate),
::view-transition-old(eoi-plate),
::view-transition-new(eoi-plate){
  animation-duration:820ms;
  animation-timing-function:cubic-bezier(.16,1,.3,1);
}
::view-transition-old(root),
::view-transition-new(root){
  animation-duration:320ms;
}

/* a plate that opens is a plate a pointer and a keyboard can both reach */
[data-lbx]{cursor:pointer;}
[data-lbx]:focus-visible{outline:1px solid var(--line-lit); outline-offset:6px;}


/* =============================================================================
   11. SKY, GRAIN, CURSOR
   The three ambient layers. Every page carries all three, identical.
   ============================================================================= */
.neb{
  position:fixed; inset:0; z-index:var(--z-neb); pointer-events:none;
  background-image:
    radial-gradient(ellipse 56% 42% at 20% 12%, var(--neb-a), transparent 68%),
    radial-gradient(ellipse 46% 40% at 84% 70%, var(--neb-b), transparent 70%);
}
/* The material between the stars. Its backing store is a few hundred pixels
   across and the compositor stretches it to the screen, which costs nothing and
   loses nothing, because nebulosity has no detail a stretch can take away. It
   sits above the two CSS blooms and below the star canvas. system.js creates it
   and paints it; a page with no scripts never gets one and keeps .skyfall. */
.dust{
  position:fixed; inset:0; z-index:var(--z-neb);
  pointer-events:none; display:block; width:100%; height:100%;
}
#sky{
  position:fixed; inset:0; z-index:var(--z-sky);
  pointer-events:none; display:block; width:100%; height:100%;
}
/* the floor for a visitor whose scripts never run */
.skyfall{
  position:fixed; inset:0; z-index:var(--z-sky); pointer-events:none;
  background-image:var(--starfall);
  background-size:400px 400px;
  opacity:0;
}
html:not(.js) .skyfall{opacity:var(--skyfall-opacity);}
/* with scripts running the canvas sky exists and this layer never shows;
   display:none keeps an invisible full-viewport texture off the compositor */
html.js .skyfall{display:none;}

.grain{
  position:fixed; inset:0; z-index:var(--z-grain); pointer-events:none;
  background-image:var(--grain);
  background-size:180px 180px;
  opacity:var(--grain-opacity);
}

/* -----------------------------------------------------------------------------
   THE CURSOR
   difference keeps the mark legible over a bright plate, which a grid page is
   full of. screen washed out over the brightest photographs.

   THE POSITION PATH CARRIES NO INTERPOLATION.
   system.js writes one transform onto the three layer boxes straight out of the
   pointer event. Nothing in this section is allowed to put a transition, an
   animation or a will-change delay on the transform of .cur or of .cur i, since
   any of the three would restore the drag by another route. Scale and opacity
   live on the children, which sit inside a box that they cannot move.
   -------------------------------------------------------------------------- */
.cur{
  position:fixed; left:0; top:0;
  z-index:var(--z-cursor);
  pointer-events:none;
  opacity:0;
  mix-blend-mode:difference;
  transition:none;
}
html.cursor-on .cur{opacity:1;}
/* the native pointer is suppressed only while the drawn mark can exist. The
   motion gate rides :where() so these selectors keep the EXACT specificity the
   cascade was tuned around: the PNG cursor rules further down this file win by
   source order, precisely as they did before the gate existed. Raising the
   specificity here once made the pointer vanish over every a/button/[tabindex]
   on the site; never gate this block with a bare class again. */
html.cursor-on:where(.motion),
html.cursor-on:where(.motion) a,
html.cursor-on:where(.motion) button,
html.cursor-on:where(.motion) input,
html.cursor-on:where(.motion) [tabindex]{cursor:none;}
.cur i{
  position:absolute; left:0; top:0; display:block;
  will-change:transform;
  transition:none;                /* the guard. see the note above. */
}

/* the halo. one faint breath of cold light under the mark, wide enough to lift
   it off a black field and far too faint to be the thing that is seen. */
.cur__glow{width:0; height:0;}
.cur__glow::after{
  content:""; position:absolute;
  left:-19px; top:-19px; width:38px; height:38px;
  border-radius:50%;
  background:radial-gradient(circle, var(--cursor-halo-0) 0%, var(--cursor-halo-1) 38%, var(--cursor-halo-2) 72%);
  transition:transform .55s var(--out-soft);
}
html.cur-grow .cur__glow::after{transform:scale(1.5); transition:transform .38s var(--quart);}

/* THE MARK
   A 32 pixel box centred on the pointer, holding a 64 unit view box, so two
   view units are one pixel and the centre of the box is the pointer itself.
   Every stroke is declared at width 1 with non-scaling-stroke, which holds the
   weight at one pixel on screen through every scale the hover state asks for.
   A hairline that thickens on hover stops being a hairline. */
.cur__mark{width:32px; height:32px; margin:-16px 0 0 -16px;}
.cur__mark svg{display:block; width:100%; height:100%; overflow:visible;}
.cur__ln{
  fill:none;
  stroke:var(--cursor-mid);
  stroke-width:1;
  vector-effect:non-scaling-stroke;
  opacity:.88;
}
.cur__pt{fill:var(--cursor-hot); opacity:.94;}
html.cur-grow .cur__pt{opacity:1;}

/* SHIPPED. THE INFINITE EYE.
   The sigil at the zenith of the portal, taken off the door and put in the
   hand. Same figure, same order, 0.846 of the size: a lens, an iris ring, a
   pupil of two tangent circles that read as the infinity mark, and the
   brightest point of the mark sitting exactly where the two pupils touch. On
   the door that point is the brightest pixel on the page. Here it is the
   pointer itself, so the hotspot and the infinity node are one place.

   The one structural change from the sigil is that the lens is two open arcs
   rather than one closed path, upper lid and lower lid meeting at the canthi.
   That is what lets it behave like an eye instead of a widget.

   Twenty two pixels wide, nine tall at rest. Every stroke is width 1 with
   non-scaling-stroke, so the hairline holds one pixel through the lid stretch
   and the iris dilation alike. Mostly negative space: eleven curves and lines
   inside a 22 by 9 pixel almond, and six of the eleven are near invisible
   until the eye opens.

   THE THREE STATES. No rotation, no pulse, nothing moving at idle.
     rest   lids at their drawn height, iris at 1, rays at .10, halo dark
     hover  the lids part and the iris dilates. scaleY on the lids pins the
            canthi and lifts the apex, which is how a lid actually opens, and
            the iris grows out of the same centre. The rays come up and a small
            halo lights inside the iris, the same halo the sigil lights when a
            pointer comes near it. This is an eye opening, not a UI element
            scaling.
     press  it blinks. The lids close to a seventh of their height, the iris
            falls back and fades out behind them, the rays go dark and the core
            drops to a third. Nothing translates, so the eye closes over the
            spot it was already looking at. */
.cur__lids{
  transform:scaleY(1);
  transform-origin:32px 32px;
  transition:transform .55s var(--out-soft);
}
.cur__lid{opacity:.86;}
.cur__iris{
  transform:scale(1);
  transform-origin:32px 32px;
  opacity:1;
  transition:transform .55s var(--out-soft), opacity .55s var(--out-soft);
}
.cur__ring{opacity:.74;}
.cur__pup{opacity:.60;}
/* the radiance. Six hairlines out of the iris into the sclera, held at a tenth
   so the resting mark stays almost all negative space. */
.cur__ray{
  fill:none;
  stroke:var(--cursor-cool);
  stroke-width:1;
  vector-effect:non-scaling-stroke;
  stroke-linecap:round;
  opacity:.10;
  transition:opacity .55s var(--out-soft);
}
/* the sigil's proximity halo, driven by hover rather than by distance */
.cur__eye-halo{
  fill:var(--cursor-hot); stroke:none;
  opacity:0;
  transition:opacity .55s var(--out-soft);
}
.cur__pt{transition:opacity .55s var(--out-soft);}

html.cur-grow .cur__lids{transform:scaleY(1.30); transition:transform .38s var(--quart);}
html.cur-grow .cur__iris{transform:scale(1.16); transition:transform .38s var(--quart);}
html.cur-grow .cur__ray{opacity:.28; transition:opacity .38s var(--quart);}
html.cur-grow .cur__eye-halo{opacity:.10; transition:opacity .38s var(--quart);}

.cur.cur__down .cur__lids{transform:scaleY(.14); transition:transform .3s var(--quart);}
.cur.cur__down .cur__iris{
  transform:scale(.68); opacity:.12;
  transition:transform .3s var(--quart), opacity .3s var(--quart);
}
.cur.cur__down .cur__ray{opacity:0; transition:opacity .3s var(--quart);}
.cur.cur__down .cur__eye-halo{opacity:0; transition:opacity .3s var(--quart);}
/* the core dims under the blink. It is the one rule in this block that an
   alternate would inherit if one were opened, and a point that dims under a
   press suits the diamond and the bare point as well, so it is left shared. */
.cur.cur__down .cur__pt{opacity:.34; transition:opacity .3s var(--quart);}

.cur__ret,
.cur__dia{display:none;}

/* -----------------------------------------------------------------------------
   THE THREE ALTERNATES
   All four marks are already in the markup that system.js writes, and the
   second alternate is the point that all four share. Changing the cursor is an
   edit here and nowhere else: delete the two marker lines that fence one block,
   the one reading DELETE THIS LINE and the one reading DELETE THIS LINE TOO.
   Each block sits after the shipped rules, overrides them, and states
   everything it needs, including switching the eye off. Putting the two lines
   back returns the eye. Never open two at once.

   diag.cursorMark in system.js still reads infinite-eye after a swap here. It
   names what the code writes, not what the CSS shows.
   -------------------------------------------------------------------------- */

/* ALTERNATE A. THE FINE DIAMOND.
   One hairline lozenge, thirteen pixels tall and seven wide, standing around
   the same luminous point. It is the quieter of the two. A closed figure reads
   as one object where the reticle reads as an instrument, and it carries the
   lozenge that the rules and the registration marks use elsewhere on the site.
   It opens to 1.42 rather than 1.62, since a closed figure covers more ground
   at the same scale. */
/* --- ALTERNATE A: DELETE THIS LINE TO SHIP THE DIAMOND ---------------------
.cur__eye,
.cur__ret{display:none;}
.cur__dia{
  display:block;
  transform:scale(1);
  transform-origin:32px 32px;
  transition:transform .55s var(--out-soft);
}
html.cur-grow .cur__dia{transform:scale(1.42); transition:transform .38s var(--quart);}
.cur.cur__down .cur__dia{transform:scale(.88); transition:transform .3s var(--quart);}
   --- ALTERNATE A: DELETE THIS LINE TOO -------------------------------- */

/* ALTERNATE B. THE SINGLE POINT.
   No lines at all. One luminous point held at a little over two pixels, and the
   halo widened from thirty eight to forty eight so the point has something to
   sit in. The whole cursor becomes light and position and nothing else. The
   hover state has only the halo to work with, which makes this the most
   restrained of the three and the one that says least about what is under it. */
/* --- ALTERNATE B: DELETE THIS LINE TO SHIP THE POINT -----------------------
.cur__eye,
.cur__ret,
.cur__dia{display:none;}
.cur__pt{transform:scale(1.35); transform-origin:32px 32px;}
.cur__glow::after{left:-24px; top:-24px; width:48px; height:48px;}
   --- ALTERNATE B: DELETE THIS LINE TOO -------------------------------- */

/* ALTERNATE C. THE SIGHTING RETICLE. RETIRED.
   Four hairlines standing three and a half pixels clear of the luminous point,
   five and a half pixels long, nine pixels from the point at the far end. The
   gap was the mark: it opened on anything that answered a click and closed
   under a press. It shipped as the site default until the third of August,
   when the operator ruled it out in one line: a crosshair makes no sense on a
   site whose subject is vision, and it reads as a weapon sight. It is kept
   because the geometry is sound and the objection was to the meaning.

   The half unit offset is deliberate: it lands each hairline on a half pixel,
   where a one pixel stroke fills one pixel instead of half filling two. The
   point stays at 32 32, on the pointer, unmoved. */
/* --- ALTERNATE C: DELETE THIS LINE TO SHIP THE RETICLE ---------------------
.cur__eye,
.cur__dia{display:none;}
.cur__ret{
  display:block;
  transform:translate(1px,1px) scale(1);
  transform-origin:33px 33px;
  transition:transform .55s var(--out-soft);
}
html.cur-grow .cur__ret{transform:translate(1px,1px) scale(1.62); transition:transform .38s var(--quart);}
.cur.cur__down .cur__ret{transform:translate(1px,1px) scale(.86); transition:transform .3s var(--quart);}
   --- ALTERNATE C: DELETE THIS LINE TOO -------------------------------- */

/* -----------------------------------------------------------------------------
   CURSOR SKINS
   A skin replaces the drawn star while the pointer is over an element carrying
   data-cursor-skin. This is the only route for art a CSS cursor cannot express:
   cursor: cannot animate, and engines drop cursor images much above 128px.
   As an element both restrictions vanish and the damping is inherited.

   Skins render normally rather than through the star's difference blend. The
   blend inverts whatever sits beneath it, which is right for an abstract mark
   and wrong for a painted bird, a band sigil or a dove: those carry their own
   colour and must keep it. The halo goes with it, for the same reason.
   -------------------------------------------------------------------------- */
.cur[data-skin]{mix-blend-mode:normal;}
.cur[data-skin] .cur__mark,
.cur[data-skin] .cur__glow{opacity:0;}
.cur__skin{
  position:absolute; left:0; top:0;
  width:0; height:0;
  opacity:0;
  pointer-events:none;
}
.cur[data-skin] .cur__skin{opacity:1;}
.cur__skin::after{
  content:""; position:absolute; display:block;
  background-repeat:no-repeat;
  background-size:contain;
  image-rendering:pixelated;      /* every skin is pixel art. Keep it crisp. */
  transition:transform .55s var(--quart);
  transform-origin:0 0;
}
html.cur-grow .cur__skin::after{transform:scale(1.12);}
.cur__down .cur__skin::after{transform:scale(.92);}

/* Each skin sets its own box and its own hotspot. The negative offsets ARE the
   hotspot: they place the art so the declared hot pixel sits on the pointer. */
.cur[data-skin="leaf"] .cur__skin::after{
  width:32px; height:32px; left:-1px; top:-1px;
  background-image:image-set(
    url('assets/cursors/leaf-arrow-32x32.png') 1x,
    url('assets/cursors/leaf-arrow-64x64.png') 2x);
}
.cur[data-skin="leaf-hand"] .cur__skin::after{
  width:32px; height:32px; left:-6px; top:-1px;
  background-image:image-set(
    url('assets/cursors/leaf-hand-32x32.png') 1x,
    url('assets/cursors/leaf-hand-64x64.png') 2x);
}
.cur[data-skin="heartagram"] .cur__skin::after{
  width:32px; height:32px; left:0; top:0;
  background-image:url('assets/cursors/heartagram-32x32.png');
}
/* The dove is 256x140 and could never have been a CSS cursor. Held at half
   size so it reads as a bird in flight rather than a banner. */
.cur[data-skin="dove"] .cur__skin::after{
  width:128px; height:70px; left:-1px; top:-16px;
  background-image:url('assets/cursors/dove-256x140.png');
  image-rendering:auto;           /* it has real antialiased edges. Let them be. */
}
/* Two frames, 100ms each, exactly as the source .ani declared. background-image
   switches discretely across keyframes, which is what a 2 frame loop wants. */
.cur[data-skin="hummingbird"] .cur__skin::after{
  width:48px; height:48px; left:-1px; top:-6px;
  background-image:url('assets/cursors/hummingbird-f0-48x48.png');
}
html.js.motion .cur[data-skin="hummingbird"] .cur__skin::after{
  animation:cur-hum 200ms steps(1) infinite;
}
@keyframes cur-hum{
  0%,49.9%{background-image:url('assets/cursors/hummingbird-f0-48x48.png');}
  50%,100%{background-image:url('assets/cursors/hummingbird-f1-48x48.png');}
}
/* Three frames, 333ms each, exactly as the source .ani declared, so the loop is
   999ms. Declared hotspot at 48px is 0 0, so the art sits square on the pointer
   with no offset to make. The chamber's wing cursor: an eye held in an open
   hand, which is the site's own subject handed to the visitor at the one page
   that is not announced anywhere. */
.cur[data-skin="eye-in-hand"] .cur__skin::after{
  width:48px; height:48px; left:0; top:0;
  background-image:url('assets/cursors/eye-in-hand-f0-48x48.png');
}
html.js.motion .cur[data-skin="eye-in-hand"] .cur__skin::after{
  animation:cur-eye-in-hand 999ms steps(1) infinite;
}
@keyframes cur-eye-in-hand{
  0%,33.32%{background-image:url('assets/cursors/eye-in-hand-f0-48x48.png');}
  33.33%,66.65%{background-image:url('assets/cursors/eye-in-hand-f1-48x48.png');}
  66.66%,100%{background-image:url('assets/cursors/eye-in-hand-f2-48x48.png');}
}


/* =============================================================================
   12. THE ARRIVAL PLATE AND THE LOAD VEIL

   THE LAW THIS SECTION OBEYS
   A page shows its content. Nothing here is allowed to be the thing that
   decides whether the content is visible, because everything here is driven by
   JavaScript and JavaScript can stall, fail to parse, or never arrive at all.

   Both layers are therefore inverted. Neither one exists in the document a
   server sends: the plate and the veil are display:none until a script writes
   a class, so the first painted frame is always the page. When a script does
   raise one, the raising is its whole responsibility and the lowering is not:
   each layer carries a CSS animation that ends hidden and stays hidden, and
   that animation runs whether or not another line of script ever executes.

   The failure this replaces was reported four times from a real machine.
   Forests, concerts and about opened on true black and needed a manual reload.
   ============================================================================= */
.plate{
  position:fixed; inset:0;
  z-index:var(--z-plate);
  display:none;
  pointer-events:none;
  background:var(--void);
}
html.arriving .plate,
html.leaving .plate{display:block;}

/* THE DEAD MAN'S SWITCH, IN CSS.
   No document ships html.arriving in its markup. The class is added by script
   after the page has painted, so the plate is atmosphere laid over content that
   is already on screen, and this animation is what takes the atmosphere away
   when the script that raised it never finishes.

   A JavaScript failsafe cannot answer a JavaScript failure. This one is pure
   CSS, so it holds even when no script runs at all. A genuine arrival finishes
   near 2.2s at its slowest and removes the class, which cancels the animation
   before it reaches its end; anything still standing at 3.6s has failed, and
   the visitor gets their page back. forwards keeps it down once it falls.

   visibility, not opacity. An animation outranks every author declaration
   including an inline style, so an opacity here would seize the one property a
   controller cross fades by hand during a genuine passage. */
html.arriving .plate{
  animation:plate-deadman 3.6s steps(1,end) forwards;
}
@keyframes plate-deadman{
  from{visibility:visible;}
  to  {visibility:hidden;}
}
.plate__gl{position:absolute; inset:0; width:100%; height:100%; display:block;}
.plate__flat{
  position:absolute; inset:0;
  background-size:cover;
  background-position:center;
  opacity:0;
}
html.plate-flat .plate__flat{opacity:1;}

/* html.arriving carries no scroll lock. It used to hold body at overflow
   hidden, which meant a stalled script left a page that was legible and would
   not move, so the class now does one thing and one thing only: it shows the
   plate. The plate is pointer-events:none and the body scrolls underneath it
   throughout, which costs the arrival nothing, since the arrival opens on the
   top of the page and lasts about a second and a half. */

/* the load veil: true black with one hairline that fills. It is the designed
   load state, and it is the reason no page ever shows a spinner. */
.veil{
  position:fixed; inset:0;
  z-index:calc(var(--z-plate) - 1);
  display:none;
  align-items:center; justify-content:center;
  background:var(--void);
}

/* THE VEIL IS ARMED BY SCRIPT AND DISARMED BY CSS.
   It used to raise itself off html.js.motion alone and then wait for
   JavaScript to take it down. boot() lowers it inside veil.run(), and it calls
   veil.run() on the branch with no arrival to play and never on the branch
   with one. So every page that played an arrival left an opaque veil standing
   under the plate: the plate came down on schedule and the visitor was looking
   at true black with one unfilled hairline, content intact underneath, until
   they reloaded. That is the four times reported symptom, and animals,
   landscapes, chamber and index only escaped it because each had bolted on a
   line of its own to retire the veil by hand.

   is-live is the arming, and veil.run() is the only thing that writes it, so a
   branch that intends no veil now gets no veil by construction rather than by
   remembering. veil-deadman is the disarming, and it needs no script at all. */
html.js.motion .veil.is-live{
  display:flex;
  animation:veil-deadman 3.6s steps(1,end) forwards;
}
@keyframes veil-deadman{
  from{visibility:visible;}
  to  {visibility:hidden;}
}
html.js.motion .veil.is-gone{
  opacity:0;
  pointer-events:none;
  transition:opacity var(--d-veil) var(--pass);
}
html.js.motion .veil.is-removed{display:none;}
.veil__line{
  position:relative;
  width:min(240px,34vw); height:1px;
  background:var(--line-faint);
}
.veil__line i{
  position:absolute; inset:0; display:block;
  background:linear-gradient(90deg,rgba(220,230,255,.18),rgba(220,230,255,.62),rgba(220,230,255,.18));
  transform:scaleX(0); transform-origin:0 50%;
  will-change:transform;
}


/* =============================================================================
   13. REVEAL CLASSES
   Masks over fades. Every hidden state sits behind html.js.motion, so without
   scripts and under reduced motion nothing is hidden and nothing has to be
   switched on.
   ============================================================================= */

/* a line rising from behind a mask */
.ln{display:block; overflow:hidden; padding:.06em 0 .22em; margin:0 0 -.22em;}
.lni{display:block; transform:none; transition:transform var(--d-enter) var(--enter);}
/* one character rising from behind a mask, for a wordmark and nothing else */
.ch{display:inline-block; overflow:hidden; vertical-align:bottom;}
.chi{display:inline-block; transform:none; transition:transform var(--d-char) var(--enter);}

html.js.motion .split{visibility:hidden;}
html.js.motion .split.is-split{visibility:visible;}
html.js.motion .lni{transform:translateY(118%);}
html.js.motion .split.in .lni{transform:none;}
html.js.motion .chi{transform:translateY(112%);}
html.js.motion .chars.in .chi{transform:none;}

/* a block arriving. Prose is allowed a fade and nothing more. */
html.js.motion [data-reveal]{
  opacity:0;
  transform:translateY(16px);
  transition:opacity var(--d-enter) var(--enter), transform var(--d-enter) var(--enter);
}
html.js.motion [data-reveal].in{opacity:1; transform:none;}
html.js.motion [data-reveal="fade"]{transform:none; transition:opacity var(--d-enter) var(--enter);}

/* a photograph unmasked by a curtain, with the image counter scaling inside
   the mask so the plate settles rather than slides */
html.js.motion .unmask .fig__well,
html.js.motion .unmask .card__well{
  clip-path:inset(0 0 100% 0);
  transition:clip-path var(--d-enter-slow) var(--pass);
}
html.js.motion .unmask.in .fig__well,
html.js.motion .unmask.in .card__well{clip-path:inset(0 0 0 0);}
html.js.motion .unmask .fig__img,
html.js.motion .unmask .card__img{
  transform:scale(1.12);
  transition:transform 1.5s var(--enter);
}
html.js.motion .unmask.in .fig__img,
html.js.motion .unmask.in .card__img{transform:scale(1);}

/* the circular variant, for an aperture */
html.js.motion .unmask--circle .fig__well{
  clip-path:circle(0% at 50% 50%);
  transition:clip-path var(--d-enter-slow) var(--pass);
}
html.js.motion .unmask--circle.in .fig__well{clip-path:circle(72% at 50% 50%);}

/* an SVG path drawing itself */
html.js.motion .draw{transition:stroke-dashoffset var(--d-draw) var(--enter);}
html.js.motion .draw.in{stroke-dashoffset:0;}

/* nothing enters while the plate is up */
html.arriving [data-reveal],
html.arriving .split,
html.arriving .unmask{transition:none !important;}

/* The composed state, landed in one frame. A page that boots into a hidden
   document has no entrance to perform, because nobody is watching it, and a
   hidden document does not run transitions. This class holds every duration at
   zero while the composed state is written, then leaves. */
html.compose-now *,
html.compose-now *::before,
html.compose-now *::after{
  transition-duration:0s !important;
  transition-delay:0s !important;
  animation-duration:.001ms !important;
}


/* =============================================================================
   14. THE DESIGNED REDUCED-MOTION VARIANT
   This is a composed page, not a disabled one. Everything is present, every
   hairline is drawn, every catalogue number is legible, the sky holds one
   static frame and the grain is unchanged. Only the movement is gone.

   It is reached three ways and all three land here: scripts that never run,
   a system that asks for less movement, and the review hook pinning motion
   off on a machine that has never asked for anything.
   ============================================================================= */
html.js:not(.motion) *,
html.js:not(.motion) *::before,
html.js:not(.motion) *::after{
  animation-duration:.001ms !important;
  animation-iteration-count:1 !important;
  transition-duration:.001ms !important;
  scroll-behavior:auto !important;
}
/* The kill above is scoped to html.js, so it fires when the resolved gate says
   no and never when scripts are simply absent. A page with no scripts keeps its
   sub-second hover transitions, because with no scripts there is no resolved
   gate to read and a bare prefers-reduced-motion media block would be a rule
   the review hook could never override. */
/* the reveals never hide, so nothing has to be switched on */
html:not(.motion) .draw{stroke-dashoffset:0;}
html:not(.motion) [data-reveal]{opacity:1; transform:none;}
html:not(.motion) .split{visibility:visible;}
html:not(.motion) .veil{display:none;}
html:not(.motion) .plate{display:none;}
/* the catalogue numbers are simply legible, since there is no pointer move to
   resolve them */
html:not(.motion) .fig__num{opacity:.42;}
html:not(.motion) .fig__reg{opacity:0;}
/* the lightbox keeps its chrome, since there is no pointer move to summon it */
html:not(.motion) .lbx__rail,
html:not(.motion) .lbx__panel,
html:not(.motion) .lbx__close,
html:not(.motion) .lbx__prev,
html:not(.motion) .lbx__next{opacity:1;}
/* hover states become light alone, with no scale */
html:not(.motion) .fig:hover .fig__img,
html:not(.motion) .card:hover .card__img{transform:none;}
/* the drawn cursor is never armed under reduced motion, so the native pointer
   stays exactly where the visitor left it */
html:not(.motion) .cur{display:none;}


/* =============================================================================
   15. NARROW VIEWPORTS
   Down to 430px. Ornament stays locked in pixels at every width: hairlines are
   one pixel, the registration mark is seven, the sigil is twenty two, the
   catalogue number is nine.
   ============================================================================= */
@media (max-width:1100px){
  .grid > .fig{grid-column:span 4;}
  .fig--sm{grid-column:span 3;}
  .fig--md{grid-column:span 4;}
  .fig--lg{grid-column:span 6;}
  .lbx.is-open{grid-template-columns:clamp(52px,8vw,72px) minmax(0,1fr);}
}

@media (max-width:820px){
  :root{
    --s-9:96px;
    --s-10:128px;
  }
  .grid{row-gap:var(--s-8);}
  .grid > .fig{grid-column:span 6;}
  .fig--sm{grid-column:span 6;}
  .fig--md{grid-column:span 6;}
  .fig--lg{grid-column:span 12;}
  .fig--drop-1,.fig--drop-2,.fig--drop-3{margin-top:0;}
  .fig--bleed-l{margin-left:calc(var(--gutter) * -1);}
  .fig--bleed-r{margin-right:calc(var(--gutter) * -1);}
  .gc1,.gc2,.gc3,.gc4,.gc5,.gc6,.gc7,.gc8,.gc9{grid-column-start:auto;}
  /* the lightbox stacks: the photograph, then the label, then the rail */
  .lbx.is-open{
    grid-template-columns:minmax(0,1fr);
    grid-template-rows:minmax(0,1fr) auto;
  }
  .lbx__rail{
    order:3;
    height:auto;
    flex-direction:row;
    overflow-x:auto; overflow-y:hidden;
    padding:var(--s-3) var(--s-4);
  }
  .lbx__thumb{width:44px; max-width:44px;}
  .lbx__stage{order:1;}
  .lbx__img{max-height:60svh;}
  .label--anchored .label__title{padding-top:var(--s-4);}
}

@media (max-width:600px){
  :root{
    --t-micro:9px;
    --s-7:44px;
    --s-8:64px;
  }
  .mast{grid-template-columns:auto 1fr; row-gap:0;}
  .mast__mark{justify-self:end;}
  .mast__wing{display:none;}
  .ret .caps{--ls:.18em; font-size:10px;}
  .mast__mark{letter-spacing:.2em; text-indent:.2em;}
  .grid > .fig,
  .fig--sm,.fig--md,.fig--lg{grid-column:span 12;}
  .card__well{aspect-ratio:3/4;}
  .ghost{font-size:clamp(40px,17vw,90px);}
}

@media (max-width:430px){
  :root{--gutter:20px;}
  body{font-size:16px;}
  .h-display{letter-spacing:.01em;}
  .caps{--ls:.18em;}
  .pill{height:42px; padding:0 24px; letter-spacing:.22em; text-indent:.22em;}
  .strip__cell{width:44px; height:62px;}
  .lbx__close{right:var(--s-3); top:var(--s-3);}
}

/* a coarse pointer never gets the drawn cursor and never gets a hover scale */
@media (hover:none),(pointer:coarse){
  .cur{display:none;}
  .fig:hover .fig__img,
  .card:hover .card__img{transform:none;}
  .fig__num{opacity:.42;}
  .fig__reg{opacity:0;}
}

/* =============================================================================
   16. THE APPARATUS WITHDRAWN
   Operator ruling 2026-08-14: the photographs are meant to be seen, not read.
   The visible catalogue apparatus leaves every surface: figure numbers, wall
   label lines, collection counts, range markers. The data stays in the markup
   and in data-lbx-*, because the machinery (lightbox order, aria names,
   internal work) still runs on it. Only the display goes. Reversal is
   deleting this section.
   ============================================================================= */
.fig__num{display:none;}
.label__num,.label__meta,.label__medium{display:none;}
.thr-label{display:none;}
.pl .cap{display:none;}
.card__count{display:none;}
.rail__n{display:none;}
/* the number's seat goes with the number: the register cross held the
   catalogue number's position, and the first pointer move faded it toward a
   number that no longer displays. One mark, withdrawn whole. */
.fig__reg{display:none;}
/* the last count on any surface. The lightbox rail still shows the visitor
   where they stand. */
.lbx__count{display:none;}
/* photographs never touch the page edge (operator ruling 2026-08-14); the
   bleed treatment retires at every width. Placed here so source order wins
   over the base rules and the narrow-viewport variants alike. */
.fig--bleed-l{margin-left:0;}
.fig--bleed-r{margin-right:0;}
/* the drawn arc goes with the rest of the ornament, and it goes by its OWN
   class, not its wrappers: killing .head__draw on 2026-08-14 left every arc
   that rode a different container standing, and the operator had to flag it
   again (2026-08-15, the album artwork section). The svg itself is the
   vehicle; no wrapper variant survives this rule. */
.head__draw{display:none;}
.rule-draw{display:none;}

/* =============================================================================
   THE PHOTOGRAPHS ARE NOT HANDED OVER (operator ruling 2026-08-29)
   =============================================================================
   No right click, no drag to the desktop, no long press save sheet on a
   phone. This is a latch and not a lock: anyone who opens devtools or reads
   the network tab still has the file, and no browser technique changes that.
   What actually protects the work is what is already true of it. Every
   derivative carries the operator's EXIF, XMP and IPTC ownership, and the
   wall serves the 1280 tier rather than the print master.
   ========================================================================== */
.fig__img,
.lbx__img,
.card__img,
.eye__img,
.pl__neg{
  -webkit-user-drag:none;
  -khtml-user-drag:none;
  -moz-user-drag:none;
  -o-user-drag:none;
  user-drag:none;
  -webkit-touch-callout:none;   /* the iOS long press save sheet */
  -webkit-user-select:none;
  -moz-user-select:none;
  -ms-user-select:none;
  user-select:none;
}
