
/* ==========================================================================
   April Kam · site.css · the whole site
   Lives at /assets/css/site.css. Every page links to this one file. There is
   no second stylesheet anywhere and there should not be one.

   HOW THIS FILE IS ORGANISED
   Everything above the WORKS banner at the bottom is page.css, copied here
   without moving a single line. Byte-for-byte identical CSS is the only proof
   that the Tracing… page did not change when the stylesheet moved out of it.
   Tidying it into shared-then-specific would have made that a hope instead of
   a fact, so it was not done.

   Site-wide and Tracing-only rules are therefore interleaved. They are still
   cleanly separable, because every Tracing-only rule hangs off a class that
   exists only in the markup build_page.py writes. That list, which is also
   the cut list if this file is ever split in two:

     .pile  .sheet  .sh  .apart  .group  .year  .cbar  .cbtn  .pilebar
     .detail  .detail__*  .chev  .tick  .slot  .track  .slide  .tcap
     .section-mark  .tracking  .ethics

   .pager belongs to the three project pages, not to Tracing… alone.
   #trace and .above belong to every page: the visitor trace now runs
   site-wide, from /assets/js/trace.js.

   Everything else, the tokens and the type scale and .frame and .masthead
   and .nav and .foot and the two breakpoints, belongs to every page.
   Changing one of those changes the whole site. That is the point of it.

   EVERY PAGE ALSO CARRIES A BODY CLASS · page-tracing, page-works, and so on.
   Nothing needs them yet. They are the insurance: if a future page ever needs
   a rule that must not touch another page, write it as
   .page-about .note{ … } and it provably cannot reach anything else.

   Written against real markup. If a selector here does not appear in any
   page, it is dead and should be deleted, not left in place.

   TYPE · READ THIS BEFORE PUBLISHING
   The <link> in the head loads Cormorant Garamond and IBM Plex Mono from
   Google's servers. Fine on your own machine, not fine once the site is
   public: it passes every visitor's IP to Google, which a Munich court
   awarded damages over in 2022. Before launch, download both families, put
   the .woff2 files in /assets/fonts/, delete the <link> and paste in
   @font-face rules.

   Type settings are April's, from the tuner: Cormorant Garamond, weight 300,
   18px, leading 1.45, ink #313036, no tracking.

   THE GRID
   One grid, two columns, used by the page and the footer alike. A rail on
   the left carries the years. The column on the right carries the work, up
   to 880px, but prose inside it stays at 560px so the measure is readable.
   Anything that should cross both columns gets class="span".
   ========================================================================== */

:root{
  --paper:      #ece9ef;   /* the wall in the Home photograph, lifted.
                              Never white, and never the wall as measured:
                              at #ccc9cf the level 5 labels fall to a contrast
                              of 2.1 and stop being readable. Changed
                              11 September 2026 from #f3f2ef. */
  --sheet:      #ffffff;
  --ink:        #1f1e24;   /* headings, work titles */
  --ink-body:   #313036;   /* prose */
  --ink-70:     #5c5a63;   /* captions */
  --ink-45:     #8a8891;   /* labels, years */
  --rule:       #d7d4dc;   /* follows --paper. Was #d8d6d1, a warm grey,
                              which reads yellow against a cool ground. */
  --violet:     #5a45c9;   /* the camera rectangle. Used sparingly. */

  --font-body:  "Cormorant Garamond", Garamond, Georgia, serif;
  --font-mono:  "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  --body-wt:    300;

/* ------------------------------------------------------------------ TYPE
   Five levels, and nothing outside them. If something new needs a size,
   it takes one of these rather than inventing a sixth.

   1  page title      Cormorant 300, clamp 2.3-3.4rem, --ink
   2  chapter mark    Plex Mono 500, .82rem, uppercase, .18em, --ink
                      Drawings, Tracking. The two halves of the page.
   3  prose           Cormorant 300, 18px / 1.45, --ink-body
                      20px / 1.55 at weight 400 below 900px, because
                      Cormorant has a very small x-height and 18px reads
                      cramped on a phone. Research floor is 16px; a
                      small-x-height serif needs 17-18px to feel like a
                      16px sans, and this face needs more again.
   4  caption         Plex Mono 400, .73rem, --ink-70. Title line 500.
   5  label           Plex Mono 400, .68rem, uppercase, .14em, --ink-45
                      years, dateline, counters, footer meta

   Weight, colour and case carry the hierarchy. Size alone does not, which
   is why levels 2, 4 and 5 sit within .15rem of each other and still read
   as three different things.
   ------------------------------------------------------------------------ */

  --sheet-max:  560px;     /* prose, and one sheet opened at full size */
  --pile-max:   880px;     /* piles run wider than the prose */
  --rail:       5.5rem;
  --gutter:     1.75rem;
  --sheet-w:    200px;     /* one width for every sheet. JS overwrites this. */
}

*,*::before,*::after{ box-sizing:border-box; }
html{
  -webkit-text-size-adjust:100%;
  scroll-behavior:smooth;
  scroll-padding-top:5.5rem;   /* so #tracking does not land under the header */
}

body{
  margin:0;
  background:var(--paper);
  color:var(--ink-body);
  font-family:var(--font-body);
  font-weight:var(--body-wt);
  font-size:18px;
  line-height:1.45;
  -webkit-font-smoothing:antialiased;
}

img{ max-width:100%; }
a{ color:inherit; }
:focus-visible{ outline:2px solid var(--violet); outline-offset:3px; }

.skip{
  position:absolute; left:-9999px; z-index:80;
  background:var(--ink); color:var(--paper); padding:.6rem 1rem;
  font-family:var(--font-mono); font-size:.75rem;
}
.skip:focus{ left:1rem; top:1rem; }

/* The visitor's own movement, drawn underneath everything. The sheets are
   opaque, so it only ever shows in the empty ground. */
#trace{ position:fixed; inset:0; width:100%; height:100%; z-index:0; pointer-events:none; }
.above{ position:relative; z-index:1; }

.label{
  font-family:var(--font-mono); font-weight:400; font-size:.68rem;
  letter-spacing:.14em; line-height:1.5; text-transform:uppercase;
  color:var(--ink-45);
}

/* ------------------------------------------------------------------ nav */
/* Stays at the top of the screen. Opaque, so content passes underneath. */
.masthead{
  position:sticky; top:0; z-index:40;
  background:var(--paper);
  padding:1.25rem 0 1rem;
  border-bottom:1px solid var(--rule);
}
.masthead__in{
  display:flex; flex-wrap:wrap; align-items:baseline;
  column-gap:2rem; row-gap:.7rem;
  padding:0 1.25rem;
}
.wordmark{
  font-family:var(--font-mono); font-size:.78rem; letter-spacing:.22em;
  text-transform:uppercase; text-decoration:none; color:var(--ink);
  justify-self:start;
}
/* margin-left:auto as well as the name's margin-right:auto, so the three
   items sit at the right edge whether they share the line with the name or
   wrap onto their own. */
.nav{
  margin-left:auto;
  font-family:var(--font-mono); font-weight:400; font-size:.72rem;
  letter-spacing:.13em; text-transform:uppercase;
}
.nav ul{ list-style:none; margin:0; padding:0; }
.nav > ul{ display:flex; gap:1.15rem; }
.nav a{
  display:inline-block; text-decoration:none; color:var(--ink-70);
  padding-bottom:.15rem; border-bottom:1px solid transparent;
}
.nav a:hover{ color:var(--ink); border-bottom-color:var(--rule); }
.nav a[aria-current]{ color:var(--ink); border-bottom-color:var(--ink); }

/* The three project titles under Works. Pointer devices only: on a phone
   there is no hover, and rendering them as a permanent list under Works
   left About and Contact stranded on the line above. */
.nav__sub{ display:none; }

/* ---------------------------------------------------------------- frame */
.frame{ padding:0 1.25rem; }

.pagehead{ padding:5rem 0 3.5rem; }
.pagehead h1{
  font-weight:var(--body-wt);
  font-size:clamp(2.3rem, 9vw, 3.4rem);
  letter-spacing:.005em; line-height:1.05;
  margin:0 0 .8rem; color:var(--ink);
}
.dateline{ display:block; }

.section-mark{
  margin:0 0 3rem; padding-bottom:.9rem;
  border-bottom:1px solid var(--ink-45);
  font-family:var(--font-mono); font-weight:500; font-size:.82rem;
  letter-spacing:.18em; text-transform:uppercase; color:var(--ink);
}
.tracking{ margin-top:6rem; }

/* Prose. One style, one position, always below the images it belongs to. */
.note{ max-width:var(--sheet-max); margin:2.5rem 0 0; }
.note p{ margin:0 0 1.15rem; }
.note p:last-child{ margin-bottom:0; }

figure{ margin:0; }

.caption{
  max-width:var(--sheet-max); margin-top:1.15rem; padding-top:.7rem;
  border-top:1px solid var(--rule);
  font-family:var(--font-mono); font-weight:400; font-size:.73rem;
  line-height:1.65; color:var(--ink-70);
}
.caption .work{ color:var(--ink); font-weight:500; }

/* The rule that cuts the hairline under 000. The only one of its kind on
   the page, and that is what makes it mean something. */
.apart{ margin:4rem 0 3.25rem; border:0; border-top:1px solid var(--rule); }

/* ================================================== group + year rail ==
   A group is one year label and one block of work. The label sits in the
   rail and stays in view while that group is on screen. The hairline in
   the gutter runs the length of the sequence.                            */
.group{ position:relative; padding-bottom:4.5rem; }
.group:last-of-type{ padding-bottom:0; }

.year{
  margin:0 0 1.25rem;
  font-family:var(--font-mono); font-weight:400; font-size:.68rem;
  letter-spacing:.12em; color:var(--ink-45);
}

/* ------------------------------------------------------------------ 000
   Upright, at sheet size, caption beneath. The one sheet presented rather
   than handled, so it keeps the full gallery caption on the page.        */
.plain{
  display:block; padding:0; border:0; background:none; cursor:pointer;
  width:var(--sheet-w); max-width:100%;
}
.plain img{ -webkit-user-drag:none; }
.plate{
  display:block; width:100%; height:auto; background:var(--sheet);
  box-shadow:0 1px 2px rgba(23,22,26,.12), 0 10px 22px rgba(23,22,26,.10);
}
.solo .caption{ max-width:24rem; }

/* ---------------------------------------------------------------- piles */
.pilebar{
  display:flex; flex-wrap:wrap; align-items:baseline; gap:.75rem 2rem;
  margin-bottom:2.25rem;
}
.hint{
  margin-right:auto;
  font-family:var(--font-mono); font-size:.72rem; letter-spacing:.08em;
  color:var(--ink-70); transition:opacity .5s;
}
.hint.spent{ opacity:0; }
.modes{ display:flex; gap:1.25rem; }
.modes button{
  background:none; border:0; padding:.2rem 0; cursor:pointer;
  font-family:var(--font-mono); font-size:.68rem; letter-spacing:.14em;
  text-transform:uppercase; color:var(--ink-45);
  border-bottom:1px solid transparent;
}
.modes button[aria-pressed="true"]{ color:var(--ink); border-bottom-color:var(--ink); }

/* ---------------------------------------------------------------- sheets
   The default is a swipeable row, one sheet at a time with the next one
   showing at the edge. That is what a phone gets, and what anyone with
   JavaScript switched off gets on any device.

   .live turns a row into a pile. It is only added above 900px, where there
   is a pointer to drag with and room to scatter into. On a phone a pile was
   hard to read and blocked the scroll, so it is not offered there. */
.pile{
  position:relative;
  display:flex; gap:1.25rem;
  overflow-x:auto;
  /* proximity, not mandatory. Mandatory snapping makes the row hang on to a
     gesture that started inside it, which is what stopped the page scrolling
     while a finger was resting on a drawing. */
  scroll-snap-type:x proximity;
  overscroll-behavior-x:contain;
  touch-action:pan-x pan-y;
  -webkit-overflow-scrolling:touch;
  scrollbar-width:none;
  padding-bottom:.5rem;
}
.pile::-webkit-scrollbar{ display:none; }

.sheet{
  margin:0;
  flex:0 0 86%;            /* the next sheet peeks in. This is the signifier. */
  scroll-snap-align:start;
}
.sh{
  display:block; width:100%;
  padding:0; border:0; border-radius:0; background:none; cursor:pointer;
  -webkit-user-select:none; user-select:none; -webkit-touch-callout:none;
}
/* The browser's own image dragging must not start. It steals the mouse from
   this page and hands the movement back only on release, which is what made
   the first drag feel delayed and then jump. */
.sh img{
  display:block; width:100%; height:auto; background:var(--sheet);
  -webkit-user-drag:none; pointer-events:none;
  box-shadow:0 1px 2px rgba(23,22,26,.12), 0 10px 22px rgba(23,22,26,.10);
  transition:box-shadow .2s;
}
.sheet b{ display:none; }
.sheet .caption{ max-width:none; }


/* ------------------------------------------------------------- pile mode */
.pile.live{
  display:block; overflow:visible; padding-bottom:0;
  touch-action:pan-y;
}
.pile.live .sheet{
  position:absolute; top:0; left:0;
  width:var(--sheet-w); flex:none;
}
.pile.live .sh{ cursor:grab; }
.pile.live .sheet.inhand .sh{ cursor:grabbing; }
.pile.live .sheet.inhand .sh img{
  transition:none;
  box-shadow:0 2px 5px rgba(23,22,26,.16), 0 20px 38px rgba(23,22,26,.15);
}
.pile.live .sh:hover img{ box-shadow:0 2px 5px rgba(23,22,26,.16), 0 20px 38px rgba(23,22,26,.15); }

/* Layers are built while a pile is on screen rather than at the moment of
   the press, because building one costs a repaint. */
.pile.armed .sheet{ will-change:transform; }
.pile.settling .sheet{ transition:transform .55s cubic-bezier(.2,.7,.2,1); }

.pile.live .sheet b{
  display:block;
  position:absolute; left:0; top:calc(100% + .4rem);
  font-family:var(--font-mono); font-weight:400; font-size:.62rem;
  letter-spacing:.06em; color:var(--ink-45); opacity:0; transition:opacity .18s;
}
.pile.live .sheet:hover b,.pile.live .sh:focus-visible + b{ opacity:1; }

/* In a pile there is no room for the caption, so it goes off screen. It is
   still in the markup, so screen readers and search engines still get it,
   and it comes back the moment a sheet is opened. */
.pile.live .caption{
  position:absolute; width:1px; height:1px; margin:-1px; padding:0;
  overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0;
}

.vh{
  position:absolute; width:1px; height:1px; margin:-1px; padding:0;
  overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0;
}

/* --------------------------------------------------------- opened sheet */
.detail{
  position:fixed; inset:0; z-index:60;
  /* Was rgba(243,242,239,.975) — the OLD --paper, #f3f2ef. Missed when the
     ground changed to #ece9ef on 11 September 2026, so an opened work went on
     sitting on the previous warm colour while the page behind it had already
     moved to the cool one. Affected both Tracing… and Secret Life of Mailbox.
     Fixed 13 September 2026.

     Written against the variable now, so it cannot drift again. Two
     declarations on purpose: the first is the fallback for any engine without
     color-mix and gives the right colour with no ghost of the page behind;
     the second restores the 2.5% translucency where it is supported. */
  background:var(--paper);
  background:color-mix(in srgb, var(--paper) 97.5%, transparent);
  display:none; overflow:auto; padding:4.5rem 1.5rem;
}
.detail[open]{ display:block; }
.detail__in{ position:relative; max-width:var(--sheet-max); margin:0 auto; }
.detail__plate img{ display:block; width:100%; background:var(--sheet); }
.detail__side .caption{ max-width:none; }

.detail__close{
  position:fixed; top:1.25rem; right:1.5rem; background:none; border:0;
  cursor:pointer; font-family:var(--font-mono); font-size:.7rem;
  letter-spacing:.14em; text-transform:uppercase; color:var(--ink-45);
}
.detail__close:hover{ color:var(--ink); }

/* Sixteen hairline ticks with the current one standing full height: the rail
   that runs down the drawings, laid on its side. It says where you are and
   how many there are without a number, and each tick jumps. */
.detail__ruler{
  display:flex; align-items:flex-end; gap:2px;
  margin-top:1.4rem; padding-top:.7rem;
  border-top:1px solid var(--rule);
}
.tick{
  position:relative; width:11px; height:1.1rem;
  background:none; border:0; padding:0; cursor:pointer;
}
.tick::before{
  content:""; position:absolute; left:50%; bottom:0;
  width:1px; height:.5rem; background:var(--rule);
  transition:height .18s, background .18s;
}
.tick:hover::before{ background:var(--ink-45); height:.75rem; }
.tick[aria-current]::before{ background:var(--ink); height:1.1rem; }

.detail__hint{
  margin:1rem 0 0;
  font-family:var(--font-mono); font-size:.66rem; letter-spacing:.08em;
  color:var(--ink-45);
}

/* A chevron drawn from two hairlines, so it matches every other line on the
   page instead of arriving as a typographic arrow with a stem. */
.detail__nav{ display:flex; justify-content:space-between; margin-top:1.5rem; }
.chev{
  display:flex; align-items:center; justify-content:center;
  width:2.75rem; height:2.75rem;
  background:none; border:0; padding:0; cursor:pointer;
  color:var(--ink-45); transition:color .15s;
}
.chev::before{
  content:""; display:block; width:.62rem; height:.62rem;
  border-top:1px solid currentColor; border-right:1px solid currentColor;
}
.chev--next::before{ transform:rotate(45deg); margin-left:-.2rem; }
.chev--prev::before{ transform:rotate(-135deg); margin-right:-.2rem; }
.chev:hover:not([disabled]){ color:var(--ink); }
.chev[disabled]{ opacity:.22; cursor:default; }

/* ------------------------------------------------------- tracking stills
   Kept as a carousel. Three images arguing a method are a sequence, not a
   heap, so this section is deliberately ordered where the drawings are not. */
.carousel{ max-width:var(--sheet-max); }
.track{
  display:flex; gap:1.25rem; overflow-x:auto;
  scroll-snap-type:x mandatory; -webkit-overflow-scrolling:touch;
  scrollbar-width:none;
  padding-bottom:1rem;   /* keeps a macOS overlay scrollbar off the caption */
}
.track::-webkit-scrollbar{ display:none; }
.slide{ flex:0 0 100%; scroll-snap-align:start; }
.slide img{ display:block; width:100%; height:auto; background:var(--sheet); }

.slot{
  display:flex; flex-direction:column; justify-content:center;
  aspect-ratio:3/4; padding:1.5rem;
  border:1px dashed var(--violet);
  font-size:.95rem; line-height:1.45; color:var(--ink-70);
}
.slot b{
  display:block; margin-bottom:.6rem;
  font-family:var(--font-mono); font-weight:400; font-size:.66rem;
  letter-spacing:.14em; text-transform:uppercase; color:var(--violet);
}

/* Tracking captions use their own shorter form, not the artwork caption
   block. These are not works with dimensions. Wording still provisional. */
.tcap{
  max-width:var(--sheet-max); margin:.9rem 0 0; padding-top:.6rem;
  border-top:1px solid var(--rule);
  font-family:var(--font-mono); font-weight:400; font-size:.7rem;
  letter-spacing:.05em; line-height:1.55; color:var(--ink-70);
}

/* Both sections page the same way and so look the same way: the counter
   stays at the left edge where it was, the two arrows sit together at the
   right edge, and the space between them is the width of the column. */
.cbar{
  display:flex; align-items:center;
  gap:.9rem; margin-top:1.25rem;
  max-width:var(--sheet-max);
}
.cbar [data-prev]{ margin-left:auto; }
.cbtn{
  background:none; border:1px solid var(--rule); border-radius:0;
  color:var(--ink); width:2.25rem; height:2.25rem;
  font-size:.9rem; line-height:1; cursor:pointer;
}
.cbtn:hover:not([disabled]){ border-color:var(--ink); }
.cbtn[disabled]{ color:var(--ink-45); opacity:.45; cursor:default; }

.videoblock{ max-width:var(--sheet-max); }
.videoblock video{ display:block; width:100%; height:auto; background:var(--ink); }

.ethics{
  max-width:var(--sheet-max); margin:3.5rem 0 0; padding-top:1.1rem;
  border-top:1px solid var(--rule);
  font-size:.95rem; line-height:1.5; color:var(--ink-70);
}

/* ================================================================ pager ==
   Deliberately unlike the carousel buttons. No box, pinned to the outer
   edges, because this leaves the page rather than moving inside it.       */
.pager{
  display:flex; justify-content:space-between; align-items:center;
  margin-top:6rem; padding-top:1.75rem;
  border-top:1px solid var(--rule);
}
.pager a,.pager .none{
  display:block; padding:.35rem .1rem;
  font-family:var(--font-mono); font-size:1.45rem; line-height:1;
  text-decoration:none; color:var(--ink-45);
  transition:color .15s, transform .15s;
}
.pager a:hover{ color:var(--ink); }
.pager a[rel="next"]:hover{ transform:translateX(.3rem); }
.pager a[rel="prev"]:hover{ transform:translateX(-.3rem); }
.pager .none{ visibility:hidden; }

/* --------------------------------------------------------------- footer */
.foot{ margin-top:5rem; border-top:1px solid var(--rule); padding:3rem 0 4rem; }
.foot__grid{ display:grid; gap:2.75rem; }

.signup label{
  display:block; margin-bottom:.7rem;
  font-family:var(--font-mono); font-size:.68rem; letter-spacing:.14em;
  text-transform:uppercase; color:var(--ink);
}
.signup__row{ display:flex; align-items:baseline; gap:.75rem; max-width:26rem; }
.signup input{
  flex:1 1 auto; min-width:0; background:none; border:0;
  border-bottom:1px solid var(--ink); padding:.35rem 0;
  font:inherit; font-size:1rem; color:var(--ink); border-radius:0;
}
.signup input::placeholder{ color:var(--ink-45); }
.signup button{
  flex:0 0 auto; background:none; border:0; border-bottom:1px solid var(--ink);
  padding:.35rem 0; font-family:var(--font-mono); font-size:.7rem;
  letter-spacing:.13em; text-transform:uppercase; color:var(--ink); cursor:pointer;
}
.signup .help{
  margin:.85rem 0 0; max-width:26rem;
  font-size:.95rem; line-height:1.4; color:var(--ink-70);
}

.foot__links{
  display:flex; flex-wrap:wrap; gap:.6rem 1.75rem; align-items:baseline;
  font-family:var(--font-mono); font-size:.72rem; letter-spacing:.08em;
}
.foot__links a,.foot__links button{
  color:var(--ink-70); text-decoration:none; background:none; cursor:pointer;
  border:0; border-bottom:1px solid var(--rule); padding:.1rem 0;
  font:inherit; letter-spacing:inherit;
}
.foot__links a:hover,.foot__links button:hover{ color:var(--ink); border-bottom-color:var(--ink); }

@media (pointer:coarse){ #clearTrace,#stopTrace{ display:none; } }

.foot__legal{ margin-top:2.5rem; display:flex; flex-wrap:wrap; gap:.6rem 1.75rem; }
.foot__legal a{ color:var(--ink-45); text-decoration:none; }
.foot__legal a:hover{ color:var(--ink); }

/* ========================================================== breakpoints ==
   900px is where the rail appears and the grid takes over.                */
@media (max-width:899px){
  .pilebar{ display:none; }        /* no pile on a phone, so no pile controls */
  /* JS only measures a sheet width in pile mode, so on a phone 000 needs the
     same 86% a slide gets, or it sits smaller than everything after it. */
  :root{ --sheet-w:86%; }
  .cbar{ max-width:none; }
  /* Cormorant Garamond has a very small x-height, so the size that reads
     well on a laptop reads cramped on a phone. Up one step, and up one
     weight, because 300 loses its stroke at small sizes on a bright screen. */
  body{ font-size:20px; font-weight:400; line-height:1.55; }
  .caption{ font-size:.8rem; }
  .tcap{ font-size:.76rem; }
  .label,.year{ font-size:.72rem; }
  .section-mark{ font-size:.86rem; margin-bottom:2.25rem; }
  .tracking{ margin-top:4rem; }
  .note{ margin-top:2rem; }
  .ethics,.slot{ font-size:1rem; }
  .pagehead{ padding:3.5rem 0 2.5rem; }
  .group{ padding-bottom:3.5rem; }
  .foot{ margin-top:4rem; }
  .pager{ margin-top:4.5rem; }
}

@media (min-width:900px){
  /* Same width behaviour as .frame below, including the padding, so the
     name and the nav land on the page's two edges at every window size.
     A fixed width here only matched when the window was wide enough. */
  .masthead__in{
    display:flex; justify-content:space-between; align-items:baseline;
    width:100%;
    max-width:calc(var(--rail) + var(--gutter) + var(--pile-max) + 4rem);
    margin:0 auto; padding:0 2rem;
  }
  /* Right, not centre. Centred in a block narrower than the screen, the nav
     floated in the middle of nowhere with empty space to its right. Aligned
     right, the masthead has the same two edges as the page. */
  .nav{ position:relative; }
  .nav > ul > li{ position:relative; }
  .nav__sub{
    display:grid; gap:.45rem;
    position:absolute; top:100%; right:0;
    margin-top:.9rem; padding:.9rem 1.1rem;
    background:var(--paper); border:1px solid var(--rule);
    white-space:nowrap;
    opacity:0; visibility:hidden; transition:opacity .15s; z-index:15;
  }
  .nav__sub a{ color:var(--ink-45); text-transform:none; letter-spacing:.06em; }

  /* The .9rem gap above is dead ground: moving the pointer from Works down
     to Tracing… crossed it, hover was lost, and the menu closed before it
     could be reached. This bridges the gap. It only exists while the menu is
     open, because it is inside a box that is hidden until then. */
  .nav__sub::before{
    content:""; position:absolute; left:0; right:0;
    top:-.9rem; height:.9rem;
  }
  .has-sub:hover .nav__sub,
  .has-sub:focus-within .nav__sub{ opacity:1; visibility:visible; }

  .cbar--sheets{ display:none; }   /* a pile has no slides to page through */

  .frame{
    display:grid;
    grid-template-columns:var(--rail) minmax(0, var(--pile-max));
    column-gap:var(--gutter);
    justify-content:center;
    padding:0 2rem;
  }
  .frame > *{ grid-column:2; }
  .frame > .span{ grid-column:1 / -1; }

  /* a group carries its year in the rail and keeps it in view */
  .group{
    display:grid;
    grid-template-columns:var(--rail) minmax(0,1fr);
    column-gap:var(--gutter);
  }
  .group > .year{
    grid-column:1; margin:0; text-align:right;
    position:sticky; top:6rem; align-self:start;   /* clears the sticky header */
  }
  .group > *:not(.year){ grid-column:2; }

  /* the hairline down the gutter, running through the sequence */
  .group::before{
    content:""; position:absolute;
    left:calc(var(--rail) + (var(--gutter) / 2));
    top:0; bottom:0; width:1px; background:var(--rule);
  }

  /* The opened sheet, on a pointer device.

     Everything on one screen: the arrows move out to the sides of the sheet
     where they are in view the whole time, and the sheet scales down to what
     the window can hold so the caption never falls below the fold. It still
     never scales up past 560px. */
  /* The opened sheet on a pointer device.

     The work sits centred with its caption in the bottom right corner beside
     it, the way a label sits beside a framed piece rather than under a
     picture in a book. The chevrons bracket the whole composition instead of
     clinging to the window edges, and they stay level with the middle of the
     sheet. The sheet scales down to whatever the window can hold so nothing
     falls below the fold, and never scales up past 560px. */
  .detail{ padding:3rem clamp(4rem, 7vw, 7rem); }
  .detail[open]{ display:grid; place-items:center; }
  .detail__in{
    display:flex; align-items:flex-end;
    gap:clamp(1.75rem, 3vw, 2.75rem);
    width:auto; max-width:none;
  }
  .detail__plate img{
    max-width:var(--sheet-max);
    max-height:calc(100vh - 9rem);
    width:auto;
  }
  .detail__side{ width:clamp(11rem, 17vw, 14rem); padding-bottom:.2rem; }
  .detail__nav{ display:contents; }
  .chev{
    position:absolute; top:50%; transform:translateY(-50%);
    width:3.25rem; height:3.25rem;
  }
  .chev::before{ width:.85rem; height:.85rem; }
  .chev--prev{ left:calc(clamp(2.5rem, 4vw, 4rem) * -1); }
  .chev--next{ right:calc(clamp(2.5rem, 4vw, 4rem) * -1); }

  .foot__grid{ grid-template-columns:1fr auto; align-items:start; }
  .foot__links{ justify-content:flex-end; }
}

@media (prefers-reduced-motion:reduce){
  *{ animation:none !important; transition:none !important; }
  html{ scroll-behavior:auto; }
}

@media print{
  .masthead,.foot,.cbar,.pilebar,.pager,#trace{ display:none; }
  body{ background:#fff; }
  .pile,.pile.live{ display:flex; flex-wrap:wrap; gap:1rem; }
  .pile.live .sh{ position:static; transform:none !important; }
  .track{ display:block; overflow:visible; }
  .slide{ page-break-inside:avoid; margin-bottom:2rem; }
}

/* ==========================================================================
   PART 3 · WORKS INDEX                                        /works/index.html
   Added 31 August 2026, revised 9 September. Everything above this banner is
   page.css, unchanged apart from the one marked nav fix.

   Eight rules. The page is built almost entirely out of things that already
   existed: .frame for the two edges it shares with the nav and the footer,
   .span so the row runs the full width of the grid rather than sitting in
   the column meant for the year rail, .pagehead for the title, .label for
   the dates.

   THE GRID. Two across, two down, title and dates under each image. It was
   three across while there were three projects; Arbuscule made four, which
   left one alone on a second row, so on 20 September 2026 it became a square
   of four. At the full width of the grid each image is roughly 470px wide.
   Scrolling stays vertical.

   A FIFTH PROJECT would strand one again. Two across still holds at five and
   six; beyond that, think about the page rather than the number.

   Title and dates only. The one-line descriptions were removed on
   1 September: three sentences of the same shape, stacked, read as a caption
   block rather than as three works.

   Type, from the five levels and nothing outside them:
     title   level 3, Cormorant, in --ink instead of --ink-body
     dates   level 5, .label

   THE CROP. One shape for all of them, because the files are a portrait
   drawing, a wide photograph and a small nearly square scan, and left alone
   they would give the grid different heights. 3/2 is April's choice.
   4/3 is squarer and keeps more of the drawing, 1/1 is square.

   Below 900px they fold into one column, image above, words below.
   ========================================================================== */

.works{
  list-style:none; margin:0; padding:0;
  display:grid; grid-template-columns:repeat(2, 1fr); gap:2rem;
}

.works a{ display:block; text-decoration:none; color:inherit; }

.works img{
  display:block; width:100%; height:auto;
  aspect-ratio:3 / 2; object-fit:cover;
  background:var(--sheet);
}

.works .w{ display:block; margin-top:1.15rem; }
.works .t{ display:block; color:var(--ink); }
.works .d{ display:block; margin-top:.4rem; }

/* Quiet, and nothing moves. A hairline under the title, drawn by the
   browser's own underline so the line hugs the letters and the block does
   not shift by a pixel when it appears. Pointer devices only: on a phone
   :hover sticks after a tap and would leave a project underlined for good. */
@media (hover:hover){
  .works a:hover .t{
    text-decoration:underline;
    text-decoration-thickness:1px;
    text-underline-offset:.18em;
  }
}

@media (max-width:899px){
  .works{ grid-template-columns:1fr; gap:2.5rem; }
}

/* ==========================================================================
   PART 4 · HOME                                                     /index.html
   Added 9 September 2026, rebuilt three times the same evening.

   Nine rules. The front door is one photograph filling the window, with the
   name and the nav lying on top of it and the legal line lying along the
   bottom. Nothing else on the page, and nothing below the fold: the whole
   thing is exactly one screen and does not scroll.

   THE NAME IS THE HEADING. On this page the masthead name is wrapped in the
   h1 rather than repeated below it. That is not a detail: an h1 sitting in
   the page and hidden by CSS shows up as a second "April Kam" the moment a
   browser serves a stale stylesheet, which is exactly what happened. One
   element cannot be doubled.

   THE MASTHEAD AND THE FOOTER LIE ON THE IMAGE. Both taken out of the flow,
   no backgrounds, no rules, so the photograph runs corner to corner behind
   them. The nav and the legal line sit at the quietest ink on the site; the
   name stays at full strength, because it is the one thing that must read.

   THE WHOLE IMAGE IS A LINK to Works. The nav sits above it and still takes
   its own clicks, so pointing at Works or About does what it says.

   NO TRACE ON THIS PAGE. The canvas and the script are simply not on it.

   ONE FILE FOR EVERY SCREEN. A second, portrait crop for phones was built on
   9 September and removed the next day. Phones are far more uniform than they
   look, all between 0.45 and 0.46 wide by tall, so the window crops the one
   photograph the same way on nearly all of them. And a second file has to be
   chosen by a media query on WIDTH, while the thing that actually differs is
   SHAPE: at max-width 899px an iPad held upright, at 768 wide and almost
   square, would have been served the phone crop and had its top and bottom
   rows cut off, as would any narrowed window on a laptop. One file is both
   simpler and more correct.

   THIS ASSUMES A PALE IMAGE. Ink on a light wall is legible. If a dark
   photograph is ever used here, the name, the nav and the legal line will
   need to turn pale instead, which is three colour values in .page-home,
   nothing structural.
   ========================================================================== */

/* height, not min-height, and a COLUMN. Both matter and both have bitten.
   A flex box with no definite height takes its height from its content, and
   the content here is a photograph 1439px tall, so the image box became
   taller than the window and the picture was shown magnified with the page
   scrolling. height:100svh makes the height definite; flex-direction:column
   passes that definite height down to main; min-height:0 stops the image
   pushing the box open again. Do not loosen any of the three. */
.page-home .screen{
  position:relative; height:100svh; display:flex; flex-direction:column;
}
.page-home main{ flex:1; min-height:0; }

/* Plain heights, not flex sizing, and this matters. The <img> carries
   width="2560" height="1439", and those HTML attributes act as a CSS height:
   the image box was forced to 1439px tall whatever the flex rules said, so
   the photograph hung below the bottom of the screen and was shown magnified.
   height:100% here beats the attribute. Never take it out. */
.home__link{ display:block; height:100%; }
.home__img{ display:block; width:100%; height:100%; object-fit:cover; }

.page-home .masthead{
  position:absolute; top:0; left:0; right:0; z-index:20;
  background:none; border-bottom:0;
}

/* h1 in the masthead carries no size of its own. The wordmark styles it. */
.page-home .masthead h1{ margin:0; font:inherit; }

.page-home .nav a{ color:var(--ink-45); }
.page-home .nav a:hover{ color:var(--ink); }


/* The footer, reduced to the two lines the law requires and laid along the
   bottom edge of the photograph. */
.page-home .foot{
  position:absolute; left:0; right:0; bottom:0; z-index:20;
  margin:0; border-top:0; padding:0 0 1.5rem;
}
.page-home .foot__legal{ margin:0; }

/* ==========================================================================
   PART 5 · ALIEN NATION                        /works/alien-nation/index.html
   Added 11 September 2026, rearranged 12 September.

   EVERY RULE HERE STARTS WITH .page-alien-nation, the body class of that one
   page, so none of them can reach Home, Works or Tracing…. That is how this
   page is arranged as its own body of work while everything that makes the
   site one site stays shared: the nav, the footer, the ground, the ink, the
   five type levels, captions, the video block, the arrows.

   THE PAGE IS READ OVER THE MAP. On a computer the map is no longer one of
   the pictures on the page. It is the ground the page is printed on: full
   width, dimmed to a quarter, with the video at full strength over it. What
   is being shown here is the video; the map is the world it was made across.

   It scrolls with the page rather than staying fixed to the window, so a
   visitor passes over it once, sees the whole of it, and puts it together
   for themselves.

   ON A PHONE NOTHING CHANGED. The layout April approved is untouched: the map
   runs edge to edge at full strength, the video sits below it, and there is
   no dimmed ground at all. The map is landscape and a phone is not, so a
   background there would show a thin band of the middle and nothing else.
   That is why the two are built differently rather than scaled.

   THE MAP IS A CUT-OUT. Its white background has been taken out of the file
   itself, so the visitor trace runs between the circles. The trace is drawn
   on a fixed canvas above this layer, so the line passes across the map. The
   five QR discs are kept solid, because the white squares inside a QR code
   carry its meaning and a phone cannot read one with a line through it. The
   printed title is trimmed off, because the page title says Alien Nation.

   Two files hold the same picture. WebP is about a fifth of the weight; the
   PNG is there for anything that cannot read WebP. One file is fetched, and
   it serves as both the phone's picture and the computer's ground.

   WHAT IS LOST, so that it is lost on purpose. As a ground the map cannot be
   looked at properly, its QR codes cannot be read, and a screen reader is
   told nothing, because a background carries no description. The map is shown
   at full strength in the catalogue PDF, and on a phone.

   THE PHOTOGRAPH is small and to the right of the text on a computer, and
   takes the column above the text on a phone, never wider than the video.

   Type, from the five levels and nothing outside them:
     captions         level 4, .caption, unchanged
     catalogue link   level 3, a line of the prose
   ========================================================================== */

.page-alien-nation .map{ display:block; width:100%; height:auto; }
.page-alien-nation .field picture{ display:block; }
.page-alien-nation .stage{ margin-top:1rem; }

.page-alien-nation .story{ display:grid; row-gap:2rem; margin-top:3.5rem; }
.page-alien-nation .story .note{ margin-top:0; }
.page-alien-nation .photo{ max-width:var(--sheet-max); }
.page-alien-nation .photo img{ display:block; width:100%; height:auto; background:var(--sheet); }

@media (max-width:899px){
  /* The phone, exactly as approved on 11 September. */
  .page-alien-nation .field{ margin:0 -1.25rem; }
  .page-alien-nation .film{ margin-top:1.5rem; }
}

@media (min-width:900px){
  /* The map leaves the page and becomes its ground: one dimmed layer lying
     behind everything, at the width of the page's own content, so its edges
     line up with the video and the photograph rather than running out to the
     window.

     position:fixed, not absolute. The layer belongs to the window, not to the
     document, so it stays exactly where it is while the page scrolls over it.

     It is a background image on the body rather than a picture in the page,
     and that is the whole reason it can sit where it does. The page's content
     lives inside .above, which is a layer of its own; nothing inside that
     layer can be painted below the trace canvas. Written here, on the body,
     the ground sits beneath both the page and the trace, so the visitor's
     line is drawn across the map. A background also takes no part in the
     layout, which is what lets it be centred on the window while the page
     goes on being a page. The phone keeps the real picture, with its
     description, because there the map is a work on the page and not a
     ground.

     The width is the page's own: the rail, the gutter and the widest column,
     which is what the frame is built from. Never wider than the window less
     its margins. The height follows the picture.

     .26 is the strength April chose. Below about .3 the prose keeps a
     comfortable contrast everywhere the circles fall; if it is raised, the
     page wants checking again where dark circles sit behind text. */
  .page-alien-nation::before{
    content:""; position:fixed; inset:0; z-index:-1; pointer-events:none;
    background-image:url("../img/april-kam-alien-nation-2021-map-with-qr-code.webp");
    background-repeat:no-repeat;
    background-position:50% 50%;
    background-size:min(100% - 4rem, calc(var(--rail) + var(--gutter) + var(--pile-max))) auto;
    opacity:.26;
  }

  /* The picture itself is not shown twice. It stays in the markup for the
     phone, and for anything that cannot load the background. */
  .page-alien-nation .field{ display:none; }

  /* 18rem is the narrowest the photograph's column may get: enough for
     "April Kam and Mira Johanna Sattelberger" on one caption line. The text
     gives way instead. */
  .page-alien-nation .story{
    grid-template-columns:minmax(0, var(--sheet-max)) minmax(18rem, 1fr);
    column-gap:var(--gutter); align-items:start;
  }
  .page-alien-nation .story .note{ grid-column:1; grid-row:1; }
  .page-alien-nation .photo{ grid-column:2; grid-row:1; }
}


/* ==========================================================================
   PART 6 · SECRET LIFE OF MAILBOX     /works/secret-life-of-mailbox/index.html

   Added 13 September 2026. Every rule starts with .page-mailbox.

   WHY THE SCALE WENT WRONG, AND THE ONE RULE THAT FIXES IT.

   Earlier versions gave every photograph a common HEIGHT. That is the normal
   justified-gallery answer, and it is wrong for this work. Common height
   equalises one dimension and lets the other run free, so a 4:3 landscape
   piece drawn beside a 3:4 portrait piece covers exactly TWICE the area.
   Male Gaze contains both. Calendar, drawn at double height because each of
   its photographs holds two sides, covered 4.2 times the smallest picture on
   the page. Measured, not guessed.

   The fix is to equalise AREA rather than height, and the simplest way to do
   that is a square cell. A 4:3 picture inscribed in a 160px square is
   160×120. A 3:4 picture in the same square is 120×160. Same area. Every
   photograph on this page is inscribed in an identical square, so every
   piece carries the same visual weight whatever shape it is. Calendar is
   0.67 and comes out 11% under; everything else is within 1%.

   That also removes every exception. No per-set heights, no doubling for
   Calendar, no numbers to keep in step.

   AND IT REMOVES THE ARROWS. A set that wraps needs no paging control at
   all: all eleven Male Gaze pieces are on the page at once, which is what
   the set is about. Nothing to swipe, nothing to press, no boxed arrows.

   TWO EARLIER BUGS, BOTH MINE, BOTH WORTH NOT REPEATING:
   · .plate is already taken — it is the Tracing… sheet, white with a shadow.
   · No <span class="vh"> inside a scroller: it is absolutely positioned and
     drags the whole document sideways.
   ========================================================================== */

.page-mailbox .set{ margin:0 0 4.5rem; min-width:0; }
.page-mailbox .set:last-of-type{ margin-bottom:0; }

/* ONE NUMBER CONTROLS THE WHOLE PAGE. --piece is the smallest a square may
   be before the grid drops a column, so it sets how large the work is drawn.

   Sized against Tracing… so the two pages hold the work at the same scale. A
   sheet there is drawn at min(240, max(130, (880/3) × 0.72)) ≈ 211 × 300px on
   a laptop. Measured here at 1440, where the grid is 996px wide:

       --piece   columns   cell    a landscape piece   a portrait piece
       148px     6         153     153 × 114           114 × 153
       190px     4         237     237 × 178           178 × 237
       270px     3         321     321 × 241           241 × 321   ← a sheet
       330px     2         490     490 × 368           368 × 490

   270px is set. A portrait piece then draws 241 × 321 against a Tracing…
   sheet at 211 × 300, which is as near as a square grid gets. It costs rows:
   Male Gaze runs to four, Beautiful Shoes and Calendar to two. Everything is
   still on the page — the set is not hidden behind a control, only taller.

   190px is the fallback if the page feels too long. Nothing else changes.

   THE PHONE IS SET SEPARATELY, at the foot of this part, and is deliberately
   NOT sized against Tracing…: there is no room for it, and two columns at
   140px is what was approved. Changing the number above does not touch it.

   NOTHING IS EVER DRAWN WIDER THAN ITS OWN FILE. Checked at every value in
   the table. object-fit:scale-down is what enforces it — it takes the smaller
   of the fitted size and the real size, so it can shrink but never enlarge.
   The smallest files on the page are the 375px Male Gaze portraits, drawn at
   241px, so there is headroom even at three columns. */
.page-mailbox{ --piece:270px; --piecegap:1rem; }

.page-mailbox .spread{
  display:grid; gap:var(--piecegap);
  grid-template-columns:repeat(auto-fill, minmax(var(--piece), 1fr));
  min-width:0;
}

/* auto-fill, not auto-fit. auto-fill keeps the empty tracks, so a set of one
   piece gets a cell exactly the same size as a set of eleven. That is the
   whole point: consistent scale across the page, not within a set. */

.page-mailbox .piece{
  margin:0; padding:0; border:0; background:none;
  aspect-ratio:1; display:block; width:100%;
  cursor:zoom-in;
}
.page-mailbox .piece img{
  display:block; background:none;
  width:100%; height:100%;
  object-fit:scale-down;      /* contain, but never enlarges a small file */
  object-position:center;
}

/* The caption runs a little wider than the 560px prose column, because the
   Calendar medium line is 76 characters and was breaking with "artist's
   seal" alone on the last line. */
.page-mailbox .set .caption{ max-width:38rem; text-wrap:pretty; margin-top:1.6rem; }

/* THE PROSE, IN TWO COLUMNS.

   At the 560px measure the site uses everywhere else, this paragraph already
   runs about 73 characters to the line, which is the top of the comfortable
   range. So the block could not simply be made wider: past 560px the lines
   get long before they get handsome, and at the full 996px of the picture
   grid they run to well over a hundred characters.

   The problem was never the measure, it was the composition — a 560px slab
   of text sitting under a 996px field of pictures, with 436px of nothing
   beside it. Two columns fill that width at about 62 characters a line,
   which is easier to read than the single column was, and it is the form an
   exhibition catalogue would use anyway.

   No new type level. This is level 3 prose, unchanged, in two columns.

   One column again on a phone, at the foot of this part. */
.page-mailbox .closing{
  margin-top:5.5rem;
  max-width:none;
  column-count:2; column-gap:3rem; column-fill:balance;
  orphans:2; widows:2;
}

/* --------------------------------------------------- the opened picture ---
   Unchanged: the same .detail block as Tracing…, all twenty-seven in one
   sequence. Two overrides only — no white behind a photograph, and never
   drawn wider than its own file. */
.page-mailbox .detail__plate img{
  background:none;
  width:auto; height:auto; max-width:100%; max-height:70vh; margin-inline:auto;
}

@media (max-width:899px){
  .page-mailbox{ --piece:140px; --piecegap:.7rem; }
  .page-mailbox .set{ margin-bottom:3.25rem; }
  .page-mailbox .closing{ margin-top:4rem; }
  .page-mailbox .detail__plate img{ max-height:56vh; }

  /* One column on a phone: two would be about 30 characters a line. */
  .page-mailbox .closing{ column-count:1; max-width:var(--sheet-max); }
}


/* ==========================================================================
   PART 7 · ABOUT · CONTACT · ARBUSCULE          added 14 September 2026

   PASTE THIS AT THE VERY END of /assets/css/site.css. Do not replace the
   file: the copy in the Project panel is older than the one on your disk
   (it has no PART 6), so a full replacement would delete the Secret Life
   of Mailbox styles.

   Then raise the version number on EVERY page from ?v=14 to ?v=15,
   including inside build_page.py. Miss one and it silently keeps the old
   stylesheet.

   Every rule below starts with a body class, so none of it can reach any
   existing page. Class names checked against the whole file first: all
   twelve are new.
   ========================================================================== */

/* ------------------------------------------------------------------ ABOUT */

/* The statement is prose. It takes the prose measure and the prose size,
   with no font-size of its own, so it stays in step with the biography
   and the coda at every window width. An earlier version set it a step
   larger; on a phone, where the body goes from 18px to 20px, that left
   three different sizes on one page. The bold and the white space carry
   the emphasis instead. */
   .page-about .statement{ max-width:var(--sheet-max); }
   .page-about .statement p{ margin:0 0 1.15rem; }
   
   /* Bold inside the statement. Cormorant 600, and the darker ink, so the
      emphasis carries by weight AND colour rather than weight alone: at
      this size a 300 to 600 jump alone is easy to miss. */
   .page-about .statement strong{ font-weight:600; color:var(--ink); }
   
   /* The single line that turns the statement to its subject. Set on its own
      with air above it, so it reads as an arrival rather than a fourth
      paragraph. */
   .page-about .statement .turn{ margin-top:2.25rem; margin-bottom:0; }
   
   /* The coda. Air, and nothing else: no rule, no italic, no indent. The five
      lines are already doing the work. line-height is looser than prose
      because each line is a unit, not part of a paragraph. */
   .page-about .coda{
     max-width:var(--sheet-max);
     margin:4.5rem 0 5.5rem;
     line-height:1.9;
     color:var(--ink-body);
   }
   
   /* Portrait and biography. One column on a phone, portrait first. */
   .page-about .life{ display:grid; row-gap:2rem; }
   .page-about .life .note{ margin-top:0; }
   /* Small. 15rem is about 240px: a portrait, not a picture of a person.
      Same size on a phone, where it sits above the biography. */
   .page-about .portrait{ max-width:15rem; }
   .page-about .portrait img{
     display:block; width:100%; height:auto; background:var(--sheet);
   }
   
   /* ---------------------------------------------------------------- CONTACT */
   
   .page-contact .invite{ margin:3rem 0 0; max-width:var(--sheet-max); }
   .page-contact .invite dt{
     font-family:var(--font-mono); font-weight:500; font-size:.78rem;
     letter-spacing:.08em; color:var(--ink);
     margin:0 0 .5rem;
   }
   .page-contact .invite dd{ margin:0 0 2.25rem; }
   .page-contact .invite dd:last-child{ margin-bottom:0; }
   
   /* The email. The one large thing on the page, because it is the action the
      page exists for. It now sits directly under the opening line, so the
      space above it is a beat, not a gap. */
   .page-contact .mailto{
     margin:1.75rem 0 0;
     font-size:clamp(1.5rem, 5vw, 2.1rem); line-height:1.2;
   }
   .page-contact .mailto a{
     text-decoration:none;
     border-bottom:1px solid var(--rule);
     padding-bottom:.15rem;
     color:var(--ink);
   }
   .page-contact .mailto a:hover{ border-bottom-color:var(--ink); }
   
   .page-contact .elsewhere{ margin:1.75rem 0 0; }
   .page-contact .elsewhere a{
     text-decoration:none; border-bottom:1px solid transparent;
   }
   .page-contact .elsewhere a:hover{ border-bottom-color:var(--ink-45); }
   
   /* The second copy of the mailing list form, on the page rather than in the
      footer. Rule above it so it reads as its own invitation and not as a
      continuation of the Instagram line. */
   .page-contact .signup--page{
     margin-top:4rem; padding-top:2.5rem;
     border-top:1px solid var(--rule);
     max-width:var(--sheet-max);
   }
   
   /* -------------------------------------------------------------- ARBUSCULE */
   
   /* The paintings are square and small. Shown at the prose measure rather
      than the wider pile width, so the page keeps one column and the drawings
      are not enlarged past what a 29 cm sheet can carry. */
   .page-arbuscule .work-figure{ max-width:var(--sheet-max); margin-bottom:4rem; }
   .page-arbuscule .work-figure img{
     display:block; width:100%; height:auto; background:var(--sheet);
   }
   .page-arbuscule .note{ margin-top:1rem; }
   
   /* ------------------------------------------------------------ BREAKPOINT */
   
   @media (min-width:900px){
   
     /* Portrait to the right of the biography, as on Alien Nation. 16rem is
        the narrowest the portrait column may get before the face is too
        small to be a portrait; the text gives way instead. */
     .page-about .life{
       grid-template-columns:minmax(0, var(--sheet-max)) minmax(0, 1fr);
       column-gap:var(--gutter); align-items:start;
     }
     .page-about .life .note{ grid-column:1; grid-row:1; }
     /* justify-self:start keeps it beside the text rather than pushed out to
        the far right with a gap in between. */
     .page-about .portrait{ grid-column:2; grid-row:1; justify-self:start; }
   }
   

/* ==========================================================================
   PART 8 · IMPRESSUM · DATENSCHUTZ              added 15 September 2026

   PASTE AT THE VERY END of /assets/css/site.css, after PART 7.

   Only the two German pages use any of this. Nothing here can reach an
   existing page: every rule starts with .page-impressum or .page-datenschutz.
   Class names checked against the whole file first: legal and stand are
   both new.

   The two new pages link site.css?v=16. The other seven can stay at v=15
   until publishing day, because none of them uses a rule in this block.
   Level them all to 16 before the site goes public.
   ========================================================================== */

   .page-impressum .legal,
   .page-datenschutz .legal{
     max-width:var(--sheet-max);
   }
   
   /* Section headings. Level 2 of the type scale, the same chapter mark used
      for Drawings and Tracking, but without the rule underneath: a legal page
      has ten of them and ten rules would look like a form. */
   .page-impressum .legal h2,
   .page-datenschutz .legal h2{
     margin:3rem 0 .9rem;
     font-family:var(--font-mono); font-weight:500; font-size:.78rem;
     letter-spacing:.14em; text-transform:uppercase; color:var(--ink);
   }
   .page-impressum .legal h2:first-child,
   .page-datenschutz .legal h2:first-child{ margin-top:0; }
   
   .page-impressum .legal p,
   .page-datenschutz .legal p{ margin:0 0 1.15rem; }
   
   .page-impressum .legal a,
   .page-datenschutz .legal a{ color:var(--ink); }
   
   /* The date at the bottom, set apart. */
   .page-impressum .legal .stand,
   .page-datenschutz .legal .stand{
     margin-top:3.5rem; padding-top:.9rem;
     border-top:1px solid var(--rule);
   }
   
   /* A long German compound in the page title needs a soft hyphen to break
      on a phone; &shy; in the markup does the work, this just allows it. */
   .page-datenschutz .pagehead h1{ hyphens:manual; }
   

/* ======================================================================
   PART 9 · MAILING LIST, LIVE                    added 18 September 2026
   The consent line under the sign-up field, and Brevo's hidden spam
   trap. Every rule starts with .signup, so nothing else on the site moves.
   ====================================================================== */

/* the consent line: a small checkbox, then the sentence */
.signup__consent{
  display:flex; align-items:baseline; gap:.55rem;
  margin-top:.9rem; max-width:26rem;
}

/* undo the underlined-field style that .signup input gives the email box */
.signup .signup__consent input{
  flex:0 0 auto; width:auto; margin:0; padding:0;
  border:0; background:none; accent-color:var(--ink);
  position:relative; top:.12em;
}

/* undo the small-caps mono style that .signup label gives "Traces by post" */
.signup .signup__consent label{
  display:inline; margin:0;
  font-family:inherit; font-size:.95rem; line-height:1.4;
  letter-spacing:normal; text-transform:none; color:var(--ink-70);
}
.signup .signup__consent a{ color:var(--ink); }

/* Brevo's spam trap: off-screen, never seen, never tabbed into */
.signup__trap{
  position:absolute !important; left:-10000px;
  width:1px; height:1px; overflow:hidden;
}

/* Contact has no footer form (18 September 2026), so the links would slide
   into the empty left column. Keep them on the right, where they sit on
   every other page. */
@media (min-width:900px){
  .page-contact .foot__links{ grid-column:2; }
}


/* ==========================================================================
   PART 10 · THE TWO LETTER PAGES                added 20 September 2026

   /letters/check-your-inbox/ and /letters/welcome/, where Brevo sends
   somebody after they sign up and after they confirm. Every rule starts
   with .page-letter, so nothing here can reach an existing page.
   .letter is a new class; checked against the whole file first.
   ========================================================================== */

   .page-letter .letter{
     max-width:var(--sheet-max);
     font-size:1.05rem; line-height:1.65; color:var(--ink-body);
   }

   .page-letter .letter p{ margin:0 0 1.15rem; }

   /* The way back out, set apart from the two sentences above it. */
   .page-letter .letter__back{
     margin-top:2.5rem; padding-top:.9rem;
     border-top:1px solid var(--rule);
     font-family:var(--font-mono); font-size:.78rem;
     letter-spacing:.06em; color:var(--ink-45);
   }
   .page-letter .letter__back a{ color:var(--ink); }
