/* ─────────────────────────────────────────────────────────────────────────
   Our Family — the photo wall and the view that opens over it.

   Only the .fam-* classes live here. The page scaffolding — .nav, .wrap,
   .section, .back, .foot and the whole phone menu — comes from folio.css,
   which this page loads first. Nothing in this file may restyle those: the
   nav in particular is identical across 43 pages and a rule here would move
   one of them out of step with no error anywhere.

   The hero above the wall is NOT here - it is the .ip-hero-* block from
   people.css, shared with Content Creators and Interesting People so the
   three pages open identically. Do not reimplement it.

   The palette is the site's own tokens. No colour is invented below; if one
   is ever needed it goes in :root as a variable first, per CLAUDE.md.
   ───────────────────────────────────────────────────────────────────────── */


/* The wall.

   auto-fill rather than auto-fit, deliberately and for the same reason the
   projects grid uses it: auto-fit collapses the empty tracks, so a row with
   two photographs left in it would stretch them to half the page each. These
   are photographs of people and their scale should not lurch about. */
.fam-grid{
  display:grid;
  grid-template-columns:repeat(auto-fill,minmax(clamp(150px,20vw,230px),1fr));
  gap:clamp(12px,1.6vw,18px);
}

.fam-item{
  position:relative; display:block;
  border:1px solid var(--hairline); border-radius:8px;
  overflow:hidden; background:var(--surface);
  text-decoration:none; color:inherit;
  transition:transform var(--dur-base) var(--ease-ui), border-color var(--dur-base) var(--ease-ui);
}
.fam-item img{
  display:block; width:100%; height:100%;
  aspect-ratio:1/1; object-fit:cover;
}
.fam-item:hover{ transform:translateY(-3px); border-color:var(--gold); }
.fam-item:focus-visible{ outline:2px solid var(--gold); outline-offset:3px; }

/* THE SEARCH DOES NOT WORK WITHOUT THIS, and it fails silently - 24 Sep 2026.

   js/family.js filters by setting tiles[i].hidden, and that was doing nothing
   at all: the browser's own [hidden]{display:none} is a USER-AGENT rule, and
   any author declaration beats the user agent whatever the specificity. So
   .fam-item{display:block} four rules up won every time, all 41 photographs
   stayed on the wall, and the only sign anything had happened was the counter
   dropping to the right number beside a wall that had not moved.

   This is the third time this file has met the same trap and the second time
   it shipped. .fam-view carries the identical rule for the identical reason,
   and the comment beside .fam-none says "[hidden] on a <p> needs no help -
   unlike .fam-view, which has a display of its own to beat". .fam-item has a
   display of its own too; it was simply not on the list of things being
   thought about.

   The rule: ANY element in here that gets display and is toggled with
   [hidden] needs its own [hidden] rule. */
.fam-item[hidden]{ display:none; }

/* AND THE COUNT IS WHY THIS IS NOW A GENERAL RULE - 29 Sep 2026.

   The paragraph above used to end "There are three such elements now and all
   three have one". That was true when it was written on 24 September. `.fam-get`
   - the lightbox's download button, hidden at js/family.js:254 and :257 - sets
   `display` and has never had a guard, so the count was a fourth element out of
   date and the sentence read as a completed sweep.

   That is the fourth time this trap has been met in this repository and the
   third time a fix covered the instances in front of the person fixing it:
   .fam-view then .fam-item here, four unguarded classes in
   css/prompt-creator.css this evening, and now this. The prompt-creator fix
   shipped a live page that said "there is no request button here" and rendered
   one.

   So the guard is on the attribute, which cannot go out of date, and the
   sentence above no longer counts anything. `.fam-none` is deliberately still
   unguarded and needs nothing: it is a <p> that sets no display, which is what
   its own note says and which is correct - checked with comments stripped,
   because a first sweep matched the class name inside a comment and nearly
   reported it as a fault.

   LATENT, NOT LIVE, and worth the distinction. The hide branch is
   `if (p.download)`, and `data-download` holds a URL rather than a flag; all 458
   tiles carry one, so the branch never fires on the page as it stands. This is a
   broken-link bug waiting for a photograph with no full-size file, not a
   suppression control that has stopped working. Found by the Cowork session. */
[hidden]{ display:none !important; }

/* The caption sits over the foot of the picture rather than under it, so the
   tiles stay a uniform grid whatever length the caption is. A two-stop
   gradient rather than a flat panel: the words need a dark ground, the face
   above them does not. */
.fam-meta{
  position:absolute; left:0; right:0; bottom:0;
  padding:26px 10px 8px;
  display:block; font-family:var(--text); font-size:12px; line-height:1.35;
  color:var(--paper);
  background:linear-gradient(to top,rgba(10,10,11,.92) 0%,rgba(10,10,11,.72) 55%,rgba(10,10,11,0) 100%);
}
/* The year on a wall tile is the SAME COMPONENT as the year on the opened
   photograph - Ashley, 25 Sep 2026, asked for the two to match and chose the
   gold block as the one to keep. Solid gold, black digits, the same 4px corner
   and the same .01em tracking as .fam-year further down this file.

   The SIZE is not copied, and that is deliberate. .fam-year is 20px because it
   sits under a photograph 700px wide; a tile is between 150 and 230, so 20px
   would swamp it and the caption beside it. 13px holds the block and still
   reads as the same object - the treatment is what makes two things match, not
   the point size.

   width:fit-content is what keeps it a BLOCK the caption sits under, rather
   than a bar across the whole tile: display:block alone would stretch the gold
   the full width of the picture. Black on this gold measures 8.29:1, the same
   pairing already used by the filled pills and by .fam-year. */
.fam-meta b{
  display:block; width:fit-content;
  background:var(--gold); color:var(--canvas);
  font-size:13px; font-weight:700; letter-spacing:.01em;
  padding:3px 8px; border-radius:4px;
  margin:0 0 5px;
}

.fam-empty{ color:var(--muted); }
/* Shown when the search matches nothing. Toggled with the hidden attribute,
   and [hidden] on a <p> needs no help - unlike .fam-view, which has a display
   of its own to beat. */
.fam-none{
  color:var(--muted); font-family:var(--text); font-size:15px;
  margin:6px 0 0; padding:26px 0; text-align:center;
}

/* ---------- the photograph, opened ----------
   Built by js/family.js. With JavaScript off the tile is still a plain link
   to the larger image, so none of this is load-bearing. */
.fam-view{
  position:fixed; inset:0; z-index:60;
  display:flex; align-items:center; justify-content:center;
  /* Trimmed from clamp(16px,4vw,48px) on 25 Sep 2026. On a landscape iPad the
     old one spent 47px a side, and every pixel of that came off the
     photograph, which is height-limited there. See the note on the image. */
  --fam-pad:clamp(12px,2.2vw,40px);
  /* The bar under the picture: the 52px download button plus the 16px gap
     above it. The photograph may have everything else. */
  --fam-chrome:68px;
  padding:var(--fam-pad);
  background:rgba(10,10,11,.94);
  opacity:0;
  transition:opacity var(--dur-base) var(--ease-ui);
}
/* Hidden by the ATTRIBUTE, not by visibility, and the specificity is the
   whole point: [hidden] alone is (0,1,0) and loses to .fam-view's display:flex,
   so the panel would never hide. .fam-view[hidden] is (0,2,0) and wins.

   Why not visibility - it cost a real bug. A visibility:hidden element cannot
   take focus, and focus() on one fails silently. The dialog opened with focus
   still behind it on the page and nothing anywhere said so. An element at
   opacity:0 IS focusable, so this version can hand focus over the instant the
   panel exists, with no dependence on when a style recalculation happens. */
.fam-view[hidden]{ display:none; }
.fam-view.is-open{ opacity:1; }

.fam-view figure{ margin:0; max-width:min(100%,1200px); max-height:100%; display:flex;
  flex-direction:column; gap:16px; align-items:stretch; }

/* HOW BIG THE OPENED PHOTOGRAPH GETS - Ashley asked for larger on the iPad,
   25 Sep 2026, and the shape of these pictures is why the old rule was wrong.

   Every one of the 41 is SQUARE, 1254x1254, because that is what his
   enhancement software emits. A square is the shape most punished by a flat
   max-height, because on any screen wider than it is tall the height is what
   binds and the leftover width is simply wasted. Measured on the live page at
   1180x820, a landscape iPad: the photograph came out 607x607 with 479px of
   empty room beside it, because 74vh is 607 and nothing else was consulted.

   So the cap is now derived rather than guessed: the whole viewport, less the
   padding, less the bar. It cannot push the bar off the bottom because the
   bar's height is what it subtracts.

   THREE DECLARATIONS, AND THE ORDER IS THE POINT. The last one a browser
   understands wins, so this degrades rather than breaks:
     74vh    the old value, known safe, for anything that cannot do the calc
     100vh   every browser in use
     100dvh  iOS Safari, where 100vh means the viewport WITHOUT the toolbar
             and so overstates the room by the height of the toolbar

   Measured before and after, live, same instant, four sizes:
     iPad landscape  1180x820    607 -> 700    the one that prompted this
     iPad portrait    820x1180   754 -> 784
     iPad Pro        1024x1366   942 -> 979
     phone            375x667    343 -> 351
   Portrait gains little and that is not a fault: at 784 of 820 the picture is
   already 96% of the screen's width, and a square cannot grow past that
   without being cropped. The photographs are Ashley's; they do not get
   cropped to look bigger. */
.fam-view img{
  display:block; max-width:100%;
  max-height:74vh;
  max-height:calc(100vh - (var(--fam-pad) * 2) - var(--fam-chrome));
  max-height:calc(100dvh - (var(--fam-pad) * 2) - var(--fam-chrome));
  width:auto; height:auto; object-fit:contain;
  border:1px solid var(--hairline); border-radius:6px; background:var(--surface);
}

/* AND THE WIDTH HAS TO COME FROM THE VIEWPORT, NOT FROM THE BOX AROUND IT.
   25 Sep 2026, the second half of Ashley's double-tap report.

   Writing the photograph's dimensions into the page fixed the SHAPE and not
   the SIZE, and the measurement said why. A not-yet-loaded image contributes
   no width to its parent - width and height attributes give a ratio, not an
   intrinsic width - so this figure, which is a shrink-to-fit flex column, took
   its width from the only child that had one: the caption bar. Measured on the
   live page, first open, fresh load: figure 362, bar 362, photograph 362x362.
   align-items:stretch then stretched the image to match, and the 1:1 ratio
   made it square. Exactly the right shape at less than half the size.

   So the width is now derived from the viewport the same way the height cap
   is, times the photograph's own ratio, which js/family.js sets as --ar. It
   mentions the figure nowhere, so it cannot be led by the bar, and it is
   right on the FIRST frame with nothing downloaded: measured 700x700 with a
   natural size still 0x0, and it does not move when the picture arrives.

   The three declarations above stay as the fallback. Anything that cannot
   parse this falls back to width:auto and the max-height cap, which is what
   shipped yesterday - smaller on the first tap, never broken. */
.fam-view img{
  width:min(
    calc(100vw - (var(--fam-pad) * 2)),
    calc((100vh - (var(--fam-pad) * 2) - var(--fam-chrome)) * var(--ar, 1)),
    1200px);
  height:auto;
}
@supports (height:100dvh){
  /* Same expression against the viewport iOS Safari actually gives the page.
     100vh there is the viewport with the toolbar HIDDEN, so it overstates the
     room by the height of the toolbar - which is the one browser this whole
     thread was reported from. */
  .fam-view img{
    width:min(
      calc(100vw - (var(--fam-pad) * 2)),
      calc((100dvh - (var(--fam-pad) * 2) - var(--fam-chrome)) * var(--ar, 1)),
      1200px);
  }
}
/* The bar under the picture: year and caption to the left, download to the
   right. Ashley's layout, 24 Sep 2026.

   Every colour here is a token already in the palette - --gold for both the
   block and the outline, --canvas for the ink on the gold. Nothing new was
   invented, per the rule at the top of CLAUDE.md, and the pairing measures
   8.29:1 either way round because it is the same two values the filled and
   outlined pills already use. */
.fam-bar{
  width:100%; display:flex; align-items:center; justify-content:space-between;
  gap:14px; flex-wrap:wrap;
  font-family:var(--text);
}
.fam-tags{ display:flex; align-items:stretch; gap:8px; min-width:0; }

/* The year, solid gold with black writing. Larger and heavier than the
   caption beside it because it is the thing being scanned for - this wall is
   read as a timeline. */
.fam-year{
  display:inline-flex; align-items:center;
  background:var(--gold); color:var(--canvas);
  font-size:20px; font-weight:700; letter-spacing:.01em;
  padding:8px 14px; border-radius:4px; white-space:nowrap;
}
/* The caption, gold writing inside a gold outline. */
.fam-name{
  display:inline-flex; align-items:center;
  border:1px solid var(--gold); color:var(--gold);
  background:transparent;
  font-size:15px; font-weight:500;
  padding:8px 14px; border-radius:4px;
  min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}

/* Controls. Real buttons, so they are reachable by keyboard and announced. */
.fam-btn{
  position:absolute; background:transparent; border:1px solid var(--hairline);
  color:var(--paper); border-radius:999px; cursor:pointer;
  width:44px; height:44px; line-height:1; font-size:20px;
  display:flex; align-items:center; justify-content:center;
  transition:border-color var(--dur-fast) var(--ease-ui), background var(--dur-fast) var(--ease-ui);
}
.fam-btn:hover{ border-color:var(--gold); background:var(--gold-soft); }
.fam-btn:focus-visible{ outline:2px solid var(--gold); outline-offset:3px; }
.fam-close{ top:clamp(12px,2vw,22px); right:clamp(12px,2vw,22px); }

/* The download sits IN the bar, not among the floating controls, so it is
   not position:absolute and does not use .fam-btn. Square-ish rather than
   round, to sit with the two boxes across from it. */
.fam-get{
  flex:0 0 auto;
  display:inline-flex; align-items:center; justify-content:center;
  width:52px; height:52px;
  border:1px solid var(--hairline); border-radius:4px;
  color:var(--paper); background:transparent;
  text-decoration:none; cursor:pointer;
  transition:border-color var(--dur-fast) var(--ease-ui), color var(--dur-fast) var(--ease-ui), background var(--dur-fast) var(--ease-ui);
}
.fam-get:hover{ border-color:var(--gold); color:var(--gold); background:var(--gold-soft); }
.fam-get:focus-visible{ outline:2px solid var(--gold); outline-offset:3px; }
.fam-prev{ left:clamp(8px,2vw,22px);  top:50%; transform:translateY(-50%); }
.fam-next{ right:clamp(8px,2vw,22px); top:50%; transform:translateY(-50%); }

.fam-count{
  position:absolute; bottom:clamp(12px,2vw,22px); left:50%;
  transform:translateX(-50%);
  font-family:var(--text); font-size:12px; letter-spacing:.06em;
  color:var(--faint);
}

@media (max-width:560px){
  .fam-year{ font-size:17px; padding:6px 11px; }
  .fam-name{ font-size:13px; padding:6px 11px; }
  .fam-get{ width:44px; height:44px; }
  .fam-prev, .fam-next{ top:auto; bottom:clamp(12px,3vw,20px); transform:none; }
  .fam-count{ bottom:auto; top:clamp(14px,3vw,22px); left:clamp(14px,3vw,20px);
              transform:none; }
}

@media (prefers-reduced-motion:reduce){
  .fam-item, .fam-view{ transition:none }
}

/* ---------- the years under the title ----------
   Ashley, 26 Sep 2026: bigger, and "really pop". Shown four treatments and he
   chose the gold block, which is the answer that also makes the page cohere -
   the year is now ONE object used at three sizes. 13px on a wall tile, 20px
   under the opened photograph, 30px under the title. Same gold, same black,
   same corner, every time.

   SCOPED HERE, NOT CHANGED IN people.css. .ip-hero-pull belongs to that file
   and three other project pages use it; this page loads family.css after it,
   and .ip-hero-text .ip-hero-pull is (0,2,0) against its (0,1,0), so the
   override cannot be an accident in either direction.

   font-style:normal is doing real work - people.css sets the pull italic, and
   italic digits in a solid block read as a mistake rather than as a label.

   width:fit-content for the same reason as the tile year: without it the gold
   runs the full width of the hero column instead of hugging the numerals.

   Black on this gold is 8.29:1, the same pairing as .fam-year and the filled
   pills. Nothing new was invented. */
.ip-hero-text .ip-hero-pull{
  width:fit-content;
  background:var(--gold); color:var(--canvas);
  font-family:var(--text); font-style:normal;
  font-size:clamp(20px,2.3vw,26px); font-weight:700; letter-spacing:.02em;
  padding:8px 18px; border-radius:5px;
  margin-top:clamp(14px,1.8vw,20px);
}

/* ---------- the curators' line ----------
   Ashley asked for this in light blue italic, 27 Sep 2026, in his words and
   in the place he drew it: under the hero, above the search.

   THE BLUE IS NOT NEW. #7fb3e0 is the value css/vault.css already carries as
   --date, chosen and measured there on 20 Sep against this same near-black.
   It reads 8.88:1 here, which lands beside the gold's 8.29 on purpose - the
   two marks on this page then carry the same weight and neither shouts over
   the other. That was the reasoning in the vault and it holds here.

   It is DECLARED as its own token rather than borrowed from the vault's.
   --date means "this is a date" over there; this is a credit, and sharing a
   token would mean that adjusting one silently moves the other. Check what a
   token is FOR before reusing it - only the value travels, not the name.

   Not --faint, which is the obvious home for fine print and fails contrast at
   3.19:1. Nothing new goes on that token until it is lightened. */
:root{ --curator:#7fb3e0; }

.fam-curator{
  /* In the hero column now, under the standfirst - Ashley moved it there on
     27 Sep. So the margin is a gap ABOVE it rather than below: it is the last
     thing in that column, and the section beneath brings its own spacing. */
  margin:clamp(16px,2vw,22px) 0 0;
  font-family:var(--display); font-style:italic;
  font-size:clamp(13px,1.25vw,15px); line-height:1.5;
  color:var(--curator);
  max-width:36ch; text-wrap:pretty;
}

/* ---------- colour off ----------
   Joanne asked to see the photographs as they were taken; many of these are
   from before colour film and the versions on the wall are Ashley's
   colourisations. 27 Sep 2026.

   WHAT THIS IS, AND WHAT IT IS NOT - and the difference is worth keeping,
   because the honest label is what makes the feature trustworthy. This takes
   the colour OUT of the colourised photograph. It does not show the original
   scan. Measured on the one photograph that exists in both of Ashley's
   folders, 1959 Mum & Dad: the tonal spread matches almost exactly, 65.7
   against 66.1 of 255, so it reads as a real black-and-white photograph - but
   the mean is 22 levels darker and the original is 941x1351 where the wall's
   copy is cropped square. So the framing and the exposure are not the
   original's either. The button therefore says "black and white", never
   "original".

   Doing it in CSS rather than shipping a second set of files is what keeps
   this free: 210 photographs would otherwise become 420, and Ashley's folder
   would need a second name for every one of them. The toggle is instant
   because nothing is fetched.

   filter on the IMG, not the view - the gold year block and the caption stay
   in colour, because they are the page furniture rather than the photograph. */
.fam-view.is-mono img{ filter:grayscale(1); }

/* Square, like the download it sits beside, and the same quiet border until
   it is hovered or on. The icon is a circle half filled - the standard way of
   saying "colour or not" and legible at 18px, which a colour wheel is not. */
.fam-mono{
  flex:0 0 auto;
  display:inline-flex; align-items:center; justify-content:center;
  width:52px; height:52px;
  border:1px solid var(--hairline); border-radius:4px;
  color:var(--paper); background:transparent;
  cursor:pointer;
  transition:border-color var(--dur-fast) var(--ease-ui), color var(--dur-fast) var(--ease-ui), background var(--dur-fast) var(--ease-ui);
}
.fam-mono:hover{ border-color:var(--gold); color:var(--gold); background:var(--gold-soft); }
.fam-mono:focus-visible{ outline:2px solid var(--gold); outline-offset:3px; }
/* ON is a state, so it has to LOOK like a state and not like a hover. Gold
   ground, dark icon - the same filled-pill pairing the rest of the page uses,
   and 8.29:1 either way round. */
.fam-mono[aria-pressed="true"]{
  background:var(--gold); border-color:var(--gold); color:var(--canvas);
}

/* The bar holds three things now, so the two controls travel together and
   wrap as a pair rather than the download dropping to its own line. */
.fam-bar{ gap:10px; }
.fam-acts{ display:flex; align-items:center; gap:8px; flex:0 0 auto; }

@media (prefers-reduced-motion:reduce){ .fam-mono{ transition:none } }
