/* ===================================================================
   fresh.css — presentation layer for the hero-expansion pages
   (index.php carousel, site.php hero page, recent.php updates grid)

   Loaded AFTER style.css so it overrides the inherited 2000s rules at
   equal specificity. No frameworks, no webfonts, no external requests
   of any kind — the estate forbids CDN references, so every typeface
   here is one the visitor already has.

   DESIGN NOTES, so the next person knows these were choices:

   1. THE GRID UNIT IS 468px. The site banners are 468x60 — a legacy ad
      format. They cannot be upscaled without visible softening, and
      cropping destroys the artwork. So rather than fight the format,
      every card is exactly one banner wide and the layout is strict
      multiples of 468. That constraint IS the visual identity.

   2. WARM DARK GROUND, not neutral black. The real content is
      skin-tone photography. A brown-black (#16110f) flatters it; a
      cool grey-black makes it look morgue-lit. This is why the palette
      is warm rather than a default dark theme.

   3. THE BANNERS ARE NOT NORMALIZED. No grayscale, no filter, no
      overlay, no hover desaturation. They are chromatically wild
      because they were made by different people over 25 years, and
      that is the directory's actual identity. Everything AROUND them
      is disciplined instead. Do not "tidy" them.

   4. The table markup in recent.php is preserved and restyled into a
      CSS grid via display:contents on the rows. No PHP change needed.
   =================================================================== */

:root {
	/* Ground and surfaces — warm brown-black, see design note 2 */
	--ink:        #16110f;   /* page ground                       */
	--ink-2:      #1f1815;   /* card surface                      */
	--ink-3:      #2b211c;   /* raised / hover surface            */
	--rule:       #3a2c24;   /* hairlines and borders             */

	/* Ink on that ground — warm off-white, never pure #fff */
	--bone:       #eee6dc;   /* primary text                      */
	--bone-dim:   #a2948a;   /* secondary text, captions          */

	/* Accents. Brass carries forward the love001 banner's gold.    */
	--brass:      #c9922e;
	--brass-lift: #e8b84b;   /* hover / emphasis                  */
	--rose:       #b4485a;   /* RESERVED for join actions only     */

	/* The one measurement everything derives from */
	--unit:       468px;     /* native site-banner width           */
	--gap:        28px;

	/* Type roles — three, all locally available */
	--prose: Georgia, "Palatino Linotype", "Book Antiqua", serif;
	--ui:    system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
	--stamp: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
}

/* ---------- page ---------------------------------------------------- */

body {
	margin: 0;
	padding: 0 20px 72px;
	background: var(--ink);
	color: var(--bone);
	font-family: var(--ui);
	font-size: 15px;
	line-height: 1.55;
	-webkit-text-size-adjust: 100%;
}

a         { color: var(--brass); text-decoration: none; }
a:hover   { color: var(--brass-lift); }
a:focus-visible,
img:focus-visible {
	/* Keyboard focus must stay visible — this is a link-dense page */
	outline: 2px solid var(--brass-lift);
	outline-offset: 2px;
}
img { border: 0; display: block; }

/* ---------- masthead ------------------------------------------------ */

/* The masthead is a WHITE PLATE shaped as an inverted obtuse isosceles
   triangle: base flush across the top, apex pointing down at centre. It
   holds the logo out from the warm dark ground so the mark and wordmark
   read as one deliberate object rather than two images floating on the
   page.

   WHY aspect-ratio AND NOT A FIXED HEIGHT.
   The apex angle is set purely by the width:height ratio --

       apex = 2 * atan( (W/2) / H )

   -- so driving the height off aspect-ratio holds that angle CONSTANT as
   the viewport changes. A fixed px height would let the triangle sharpen
   toward acute on narrow screens and flatten toward a bar on wide ones:
   it would stop being the shape it is meant to be. The ratio is the one
   knob here; change it and the angle changes, nothing else.

   Obtuse requires W > 2H. Both breakpoints stay well inside that:

       6 / 1  ->  apex = 2*atan(3.0) = 143 degrees   (>= 700px)
       3 / 1  ->  apex = 2*atan(1.5) = 113 degrees   (<  700px)

   The narrow-screen ratio is deliberately different rather than an
   oversight: at 6/1 a 360px-wide phone yields a 60px-tall plate, which
   cannot hold the lockup at all. 3/1 is the shallowest ratio that still
   fits it while remaining obtuse.

   CONTENT INSIDE A clip-path IS CLIPPED, so the lockup sits in the top
   band where the plate is still near full width. Usable width at depth y
   is W * (1 - y/H); at the bottom of a ~126px lockup on a 1460x243 plate
   that is still ~700px, far more than the lockup occupies. */

.masthead {
	max-width: min(100%, calc(var(--unit) * 3 + var(--gap) * 2));
	margin: 0 auto 34px;
	background: #fff;
	aspect-ratio: 6 / 1;
	clip-path: polygon(0 0, 100% 0, 50% 100%);
	display: flex;
	justify-content: center;
	align-items: flex-start;
	padding-top: clamp(12px, 2.1vw, 30px);
}

/* EVERY LOCKUP DIMENSION SCALES WITH THE VIEWPORT, and that is load-bearing
   rather than decoration. The plate's HEIGHT is derived from its width via
   aspect-ratio, but a lockup sized in fixed px does not shrink with it -- so
   at ~700px the plate is only 117px tall while a fixed 96px mark plus 30px
   of padding needs 126px, and the mark's own feet get cut off by the apex.
   Tying every dimension to vw keeps the lockup a roughly constant FRACTION
   of the plate, so the fit holds at every width instead of at the two
   widths that happened to get tested. */
.masthead-lockup {
	display: flex;
	align-items: center;
	gap: clamp(10px, 1.5vw, 22px);
}

/* Native file is 118x106. Sized down rather than up -- this is still a
   raster and upscaling it past native only softens it. */
.masthead-mark {
	width: clamp(44px, 6.4vw, 96px);
	height: auto;
	flex: none;
}

.masthead-words {
	display: flex;
	flex-direction: column;
	text-align: left;
}

/* The wordmark is REAL TEXT now, not the legacy BHE_Head.gif bitmap, so
   it is selectable, searchable, screen-reader-readable and sharp at any
   resolution. Georgia is already one of this stylesheet's three declared
   type roles (--prose) and is present on every target platform: the
   estate forbids webfonts and any external request, so nothing loads. */
.masthead-name {
	font-family: var(--prose);
	font-weight: 700;
	font-size: clamp(18px, 2.6vw, 40px);
	letter-spacing: .08em;
	text-transform: uppercase;
	color: var(--ink);
	line-height: 1.05;
}

/* Warm mid-brown rather than --bone-dim: that token is tuned for legibility
   ON the dark ground and is far too light to read on white.

   THE TAGLINE IS THE WIDEST THING IN THE LOCKUP, which is not obvious --
   it is smaller type than the name above it, but it is 23 characters to
   the name's 8, and letter-spacing multiplies per character. At the .16em
   this started with it measured ~230px against the name's ~110px, and it
   was the tagline, not the mark or the name, that got clipped by the
   taper on a phone. Tracking is the knob to reach for first here. */
.masthead-tag {
	font-family: var(--ui);
	font-size: clamp(9px, 0.95vw, 14px);
	letter-spacing: .12em;
	text-transform: uppercase;
	color: #6b5b50;
	margin-top: 6px;
}

/* NARROW SCREENS. The taper is the constraint here, not the raw width: at
   a fixed ratio the plate's height shrinks with the viewport, and usable
   width falls away as W*(1 - y/H), so a lockup that clears the top edge
   can still be clipped at its own baseline. On a 390px viewport a 6/1
   plate is 65px tall -- less than the lockup however small the type gets.

   The clamps above handle the lockup's own size; this block only makes
   the plate DEEPER relative to its width so there is vertical room at all.
   2.5/1 is apex = 2*atan(1.25) = 103 degrees -- still obtuse, which is the
   one property that may not be traded away for space. */
@media (max-width: 699px) {
	.masthead { aspect-ratio: 2.5 / 1; }
}

/* ---------- the 468 grid (see design note 1 and 4) ------------------ */

/* recent.php ships a <table>; we keep the markup and make it a grid.
   display:contents on tbody/tr promotes the cells to grid items, so
   the two-per-row markup reflows to 1/2/3 columns by viewport. */
table         { display: grid;
                grid-template-columns: repeat(auto-fit, var(--unit));
                justify-content: center;
                gap: var(--gap);
                max-width: calc(var(--unit) * 3 + var(--gap) * 2);
                margin: 0 auto;
                border-collapse: separate; }
tbody, tr.row { display: contents; }

/* ---------- the card ------------------------------------------------ */

/* TWO CARD SHAPES, because the two pages put different things in the
   same .site_div class:
     - index.php's legacy list holds a 468-wide BANNER + description.
       That must be VERTICAL (banner on top). Making it a flex ROW turns
       the banner into a shrinkable flex item sharing 468px with the
       text, which squeezes a 468x60 banner down to a fraction of its
       size. That was the "banners are tiny" bug.
     - recent.php holds a 160x120 THUMB + description, which reads
       better side by side.
   Default is COLUMN (safe for banners); the thumb variant opts into
   row via :has(). If a browser lacks :has() support everything stays
   vertical, which is the harmless direction to fail. */
td.site_div,
div.site_div {
	display: flex;
	flex-direction: column;
	align-items: stretch;
	width: var(--unit);
	max-width: 100%;
	padding: 0;                        /* banner is full-bleed        */
	background: var(--ink-2);
	border: 1px solid var(--rule);
	border-radius: 3px;                /* just enough to not be a box */
	overflow: hidden;
	transition: border-color .18s ease, background .18s ease, transform .18s ease;
}
td.site_div:hover,
div.site_div:hover {
	background: var(--ink-3);
	border-color: var(--brass);
	transform: translateY(-2px);
}

/* Banners are NOT uniformly 468x60 — measured across the 87 files:
   65 are 468x60, plus 480x80, 468x120, 460x60, 385x60, 318x59, 200x50,
   180x45, 134x29, 99x30, 93x25, and a large number of 1x1 placeholder
   files for sites that never had artwork.

   So: cap at 468 and CENTRE at natural size. Never width:100% — that
   upscales a 93x25 banner five times over into mush, and smears a 1x1
   placeholder across the whole card. Same rule as the thumbs and the
   hero: respect the artifact, don't stretch it to the container. */
.site_div img.banner,
.hero_banner_div img {
	max-width: var(--unit);
	width: auto;
	height: auto;
	margin: 0 auto;
	image-rendering: auto;
}

/* A 1x1 placeholder cannot be detected in CSS (there is no intrinsic-
   size selector). Left visible-but-tiny deliberately rather than
   stretched — a 1px dot is honest, a 468px smear is not. Suppressing
   them properly needs a getimagesize() check server-side; see the
   note in the deploy report. */

/* Post thumbnails are 160x120 on disk. NEVER stretch them to the card
   width — a 2.9x upscale turns them to mush. Same principle as the
   banners: respect the artifact's native size and build the grid from
   it. So the thumbs become a crisp contact sheet at 1:1 pixels. */
img.post_thumb {
	width: 160px;
	height: 120px;
	flex: 0 0 auto;
	background: var(--ink-3);
	border-radius: 2px;
}

/* ---------- card text ----------------------------------------------- */

.description {
	flex: 1 1 auto;
	min-width: 0;
	padding: 12px 16px;
	font-family: var(--prose);
	font-size: 14px;
	line-height: 1.45;
	color: var(--bone-dim);
	border-top: 1px solid var(--rule);   /* vertical card default */
	display: flex;
	flex-direction: column;
	justify-content: center;
}

/* ---- the horizontal (thumbnail) variant ---------------------------- */
td.site_div:has(img.post_thumb),
div.site_div:has(img.post_thumb) { flex-direction: row; }

/* Beside the thumb, the divider becomes vertical instead of horizontal. */
td.site_div:has(img.post_thumb) > .description,
div.site_div:has(img.post_thumb) > .description {
	border-top: 0;
	border-left: 1px solid var(--rule);
}

/* Banners must never be squeezed by their flex parent. */
.site_div img.banner { flex: 0 0 auto; align-self: center; }
/* Long site names must not blow out the fixed 468 card. */
.description a { overflow-wrap: break-word; }
.description a {
	display: block;
	font-family: var(--ui);
	font-size: 15px;
	font-weight: 600;
	letter-spacing: .01em;
	color: var(--bone);
	margin-bottom: 6px;
}
.description a:hover { color: var(--brass-lift); }

/* Freshness stamp. This is a recency directory — the date is data,
   so it gets the monospace treatment rather than being buried. */
.post_date {
	display: inline-block;
	margin-top: 10px;
	font-family: var(--stamp);
	font-size: 11px;
	letter-spacing: .08em;
	text-transform: uppercase;
	color: var(--brass);
	margin-top: 6px;
}

/* ---------- site.php hero ------------------------------------------- */

.hero_banner_div,
.hero_image_div,
.join_div {
	max-width: calc(var(--unit) * 2 + var(--gap));
	margin: 0 auto;
}
.hero_banner_div { width: var(--unit); max-width: 100%; }

/* The hero is the posting's FULL-SIZE image — and its orientation is
   unpredictable (angelfoot's is 1600x2400 portrait; others are
   landscape). So constrain by HEIGHT and let width follow. Forcing
   width:100% on a portrait source renders it ~1400px tall and the page
   becomes a single enormous photograph. No cropping either: these are
   composed photographs, not textures. */
.hero_image_div  { margin: 26px auto; text-align: center; }
.hero_image_div img {
	max-height: 62vh;
	width: auto;
	max-width: 100%;
	height: auto;
	margin: 0 auto;
	border: 1px solid var(--rule);
	border-radius: 3px;
	background: var(--ink-2);
}
.hero_image_div .post_date { margin-top: 14px; }

.sitetitle {
	display: block;
	max-width: calc(var(--unit) * 2 + var(--gap));
	margin: 30px auto 4px;
	font-family: var(--prose);
	font-size: 30px;
	line-height: 1.2;
	font-weight: normal;
	letter-spacing: -.01em;
	color: var(--bone);
}

/* ---------- join action — the only rose on the page ----------------- */

.join_div { text-align: center; margin: 32px auto 44px; }

.button, .buttonsmall,
.join_div a {
	display: inline-block;
	padding: 13px 30px;
	background: var(--rose);
	color: #fff;
	font-family: var(--ui);
	font-size: 13px;
	font-weight: 700;
	letter-spacing: .1em;
	text-transform: uppercase;
	border: 0;
	border-radius: 2px;
	cursor: pointer;
	transition: filter .16s ease;
}
.button:hover, .buttonsmall:hover,
.join_div a:hover { filter: brightness(1.14); color: #fff; }

/* ---------- carousel (index.php) ------------------------------------ */


/* ---------- legacy classes still present on these pages ------------- */

.bold, .largebold { font-weight: 600; color: var(--bone); }
.small            { font-size: 12.5px; color: var(--bone-dim); }

/* ---------- responsive: the grid already reflows; tune type --------- */

@media (max-width: 560px) {
	body        { padding: 0 12px 48px; }
	.sitetitle  { font-size: 24px; }
	:root       { --unit: 100%; --gap: 18px; }
	table       { grid-template-columns: 1fr; }
	/* Keep the thumb at native pixels; just let text wrap under it. */
	td.site_div, div.site_div { flex-direction: row; }
	.description { padding: 10px 12px; font-size: 13.5px; }
}

/* Respect a reduced-motion preference — the carousel auto-advances. */
@media (prefers-reduced-motion: reduce) {
	*, *::before, *::after {
		animation-duration: .001ms !important;
		transition-duration: .001ms !important;
	}
	td.site_div:hover, div.site_div:hover { transform: none; }
}

/* ===================================================================
   VISUAL REVIEW PASS — corrections made from actual screenshots
   (headless chromium, 1440px). Everything above this line was written
   blind from the CSS; these are the things that only showed up once
   the pages could be seen.
   =================================================================== */

/* 1. LINKS. Underlines survived `a { text-decoration:none }`, so state
      it across the link pseudo-classes rather than relying on the bare
      element selector to win. */
a, a:link, a:visited, a:active {
	color: var(--brass);
	text-decoration: none;
}
a:hover { color: var(--brass-lift); text-decoration: none; }

/* 2. HERO IMAGE was rendering 930px tall — 62vh is still enormous on a
      tall window, and it pushed the posts grid entirely below the fold.
      A hero should lead the page, not be the page. */
.hero_image_div img { max-height: min(520px, 52vh); }

/* 3. "View the full set" is a LINK that inherited .post_date's
      monospace stamp styling, so it read as a broken timestamp.
      Give it back its own voice. */
.hero_image_div a.post_date {
	font-family: var(--ui);
	font-size: 13px;
	letter-spacing: .04em;
	text-transform: none;
	color: var(--brass);
	border: 1px solid var(--rule);
	border-radius: 2px;
	padding: 8px 18px;
}
.hero_image_div a.post_date:hover {
	border-color: var(--brass);
	background: var(--ink-2);
}

/* 4. RECENT CARDS. The description panel was centred, which left the
      site name floating in the middle of a wide empty band. Left-align
      the text column; give the thumb column a fixed 160 and let the
      model-name caption (a text node inside the thumb's own anchor)
      read as a caption rather than a link. */
td.site_div:has(img.post_thumb) > a,
div.site_div:has(img.post_thumb) > a {
	flex: 0 0 160px;
	display: flex;
	flex-direction: column;
	gap: 5px;
	padding: 10px 0 10px 10px;
	font-family: var(--ui);
	font-size: 11.5px;
	line-height: 1.3;
	color: var(--bone-dim);
	text-align: center;
}
td.site_div:has(img.post_thumb) > a:hover { color: var(--brass-lift); }

td.site_div:has(img.post_thumb) > .description,
div.site_div:has(img.post_thumb) > .description {
	text-align: left;
	align-items: flex-start;
	padding: 14px 18px;
}

/* 5. CARDS WITH NO WRITEUP left a dead band under the banner. Collapse
      the panel when it holds nothing but whitespace. */
.description:empty { display: none; }
.site_div > .description:only-child:empty { display: none; }

/* 6. CAROUSEL. The slide image ran to ~300px tall against a 940px
      container so it sat left of a large void; the caption was a dark
      underlined link on a dark ground; and Prev/Next were unstyled
      browser buttons. */
/* COSMETICS ONLY below. Do NOT set display/flex/width on
   .hero-carousel-track, .hero-carousel-slide, or the dot elements:
   S3 built that geometry and C3 verified it with a real headless-browser
   test (8 of 8 slides reachable, zero alignment drift). Overriding the
   track's display broke slide clipping and collapsed the dots — my
   error, reverted. Style colour and type here; leave layout alone. */

.hero-carousel-title  { display: block; margin: 16px auto 6px;
                        font-family: var(--prose); font-size: 21px;
                        color: var(--bone); }
.hero-carousel-title a, .hero-carousel-title a:link { color: var(--bone); }
.hero-carousel-title a:hover { color: var(--brass-lift); }

.hero-carousel-desc   { display: block; max-width: 62ch; margin: 0 auto;
                        font-family: var(--prose); font-size: 14px;
                        line-height: 1.5; color: var(--bone-dim); }
.hero-carousel-desc a, .hero-carousel-desc a:link { color: var(--bone-dim); }

/* Prev/Next and the dot indicators, whatever element the JS emits. */
.hero-carousel button {
	background: transparent;
	color: var(--brass);
	border: 1px solid var(--rule);
	border-radius: 2px;
	padding: 7px 16px;
	margin: 16px 5px 0;
	font-family: var(--ui);
	font-size: 12px;
	letter-spacing: .08em;
	text-transform: uppercase;
	cursor: pointer;
	transition: border-color .16s ease, color .16s ease;
}
.hero-carousel button:hover {
	border-color: var(--brass);
	color: var(--brass-lift);
	background: var(--ink-3);
}

/* Centre the slide's inline content. text-align cannot affect the
   track's widths, transforms or clipping, so this is safe to set where
   display/flex/width are not — the image was sitting left of centre. */
.hero-carousel-slide { text-align: center; }

/* ===================================================================
   TWO-COLUMN HERO (site.php) — photo left, text right.

   Centring a portrait hero left two dead margins either side of it, so
   the photo goes left and the title / writeup / join action stack in the
   right column beside it.

   Done with a grid on the page wrapper rather than new markup. The
   wrapper has no class of its own, so it is selected via
   `body:has(.hero_image_div) > div` — which matches site.php and
   nothing else, because it is the only page with a hero image.
   sitetitle, hero_image_div, description and join_div are already
   siblings there, so they can be placed directly.
   =================================================================== */

@media (min-width: 900px) {
	body:has(.hero_image_div) > div {
		display: grid;
		grid-template-columns: auto minmax(0, 1fr);
		column-gap: 34px;
		align-items: start;
		max-width: calc(var(--unit) * 3 + var(--gap) * 2);
		margin: 0 auto;
	}

	/* Full-width bands: masthead, the site's own banner, the posts grid. */
	body:has(.hero_image_div) > div > .masthead,
	body:has(.hero_image_div) > div > .hero_banner_div,
	body:has(.hero_image_div) > div > table,
	body:has(.hero_image_div) > div > a { grid-column: 1 / -1; }

	/* Photo occupies the left column and spans the three text blocks. */
	body:has(.hero_image_div) > div > .hero_image_div {
		grid-column: 1;
		grid-row: span 3;
		margin: 0;
		text-align: left;
	}
	.hero_image_div img { max-height: min(560px, 60vh); }

	/* Right column: title, writeup, join — left-aligned, not centred. */
	body:has(.hero_image_div) > div > .sitetitle,
	body:has(.hero_image_div) > div > .description,
	body:has(.hero_image_div) > div > .join_div {
		grid-column: 2;
		max-width: none;
		margin: 0;
		text-align: left;
	}
	body:has(.hero_image_div) > div > .sitetitle  { margin-bottom: 12px; align-self: end; }
	body:has(.hero_image_div) > div > .description {
		border: 0;
		padding: 0;
		font-size: 15.5px;
		line-height: 1.62;
		align-self: start;
	}
	body:has(.hero_image_div) > div > .join_div { margin-top: 22px; align-self: start; }

	/* The "view the full set" link sits under the photo, left-aligned. */
	.hero_image_div a.post_date { display: inline-block; margin-top: 14px; }
}

/* ---- corrections from the two-column hero screenshot ---------------- */

/* 1. JOIN NOW rendered red-on-pink and unreadable: style.css styles the
      link pseudo-classes, and `a:link` (0,1,1) outranks `.button`
      (0,1,0). Raise specificity past it rather than reaching for
      !important. */
a.button, a.button:link, a.button:visited, a.button:active,
.join_div a, .join_div a:link, .join_div a:visited {
	background: var(--rose);
	color: #fff;
}
a.button:hover, .join_div a:hover { color: #fff; filter: brightness(1.14); }

/* 2. The 3-column grid (3*468 + 2*28 = 1460) overflowed a 1440 viewport
      and clipped the right-hand card. Cap on the viewport as well. */
table,
body:has(.hero_image_div) > div {
	max-width: min(100%, calc(var(--unit) * 3 + var(--gap) * 2));
}
/* .masthead is NOT in the list above -- it caps itself in its own block,
   because the plate's width is what its aspect-ratio derives height from
   and the two must not be separated. */

/* 3. The masthead shrank to the logo's width once it became a grid item
      sized to its content. Make the band span. */
body:has(.hero_image_div) > div > .masthead { justify-self: stretch; width: 100%; }

/* 4. A 560px portrait left a tall void beside ~150px of text. Shorter
      photo, and the writeup column reads better at a measure that is
      not full width. */
@media (min-width: 900px) {
	.hero_image_div img { max-height: min(430px, 52vh); }
	body:has(.hero_image_div) > div > .description { max-width: 62ch; }
}

/* ---- explicit hero grid rows ----------------------------------------
   Auto-placement let the photo's spanned rows be sized by the short text
   blocks, so the photo (and its "view the full set" link) overflowed
   into the posts grid below. site.php's sibling order is fixed and
   known, so pin the rows rather than relying on placement. */
@media (min-width: 900px) {
	/* The masthead was two sibling divs (.logo_div row 1, .head_div row 2)
	   and is now a single .masthead. Row 2 is deliberately left unassigned
	   rather than renumbering rows 3-9: an empty auto-sized grid row with
	   row-gap:0 collapses to zero height, so the layout is unchanged, and
	   not renumbering keeps this diff to one line instead of seven. */
	body:has(.hero_image_div) > div > .masthead        { grid-row: 1; }
	body:has(.hero_image_div) > div > a                { grid-row: 3; }
	body:has(.hero_image_div) > div > .hero_banner_div { grid-row: 4; }
	body:has(.hero_image_div) > div > .hero_image_div  { grid-row: 5 / span 3; grid-column: 1; }
	body:has(.hero_image_div) > div > .sitetitle       { grid-row: 5; grid-column: 2; }
	body:has(.hero_image_div) > div > .description     { grid-row: 6; grid-column: 2; }
	body:has(.hero_image_div) > div > .join_div        { grid-row: 7; grid-column: 2; }
	body:has(.hero_image_div) > div > table            { grid-row: 8; grid-column: 1 / -1;
	                                                     margin-top: 30px; }
}

/* The join link's inline style (width:400px, color:red, its own border)
   has been removed from site.php, so the button styling above now
   applies. Give it a sensible intrinsic width instead of 400px. */
.join_div a.button { min-width: 240px; text-align: center; }

/* Slack row: the photo spans 5-8 while the text occupies 5-7, so the
   leftover height lands in the empty row 8 instead of being distributed
   across the text rows (which pushed JOIN NOW away from the writeup).
   Plus breathing room under the site's banner. */
@media (min-width: 900px) {
	body:has(.hero_image_div) > div                     { row-gap: 0; }
	body:has(.hero_image_div) > div > .hero_banner_div  { margin-bottom: 22px; }
	body:has(.hero_image_div) > div > .hero_image_div   { grid-row: 5 / span 4; }
	body:has(.hero_image_div) > div > table             { grid-row: 9; }
}

/* ===================================================================
   FRONT PAGE (index.php) — density and rhythm.
   =================================================================== */

/* 1. THREE COLUMNS. 3*468 + 2*28 = 1460, which just misses a 1440
      viewport, so the directory was rendering two-up with wide dead
      margins. 468 is a hard floor (the banners are native 468 and must
      not be scaled), so the gap gives instead of the card. */
@media (min-width: 1400px) {
	/* 3*468 = 1404 is fixed, so only the gap and the body padding can
	   give. 1404 + 2*12 = 1428, which clears a 1440 viewport. The cap
	   carries +12px slack: capping at EXACTLY the required width leaves
	   the third track nothing to land in and the grid silently falls
	   back to two columns. */
	:root { --gap: 10px; }
	body  { padding-left: 8px; padding-right: 8px; }
	table { max-width: calc(var(--unit) * 3 + var(--gap) * 2 + 10px); }
}

/* 2. UNIFORM CARD HEIGHTS. Roughly 40% of sites have no writeup, which
      left a ragged mix of tall and short cards. Clamping every writeup
      to three lines and holding that height whether or not there is
      text turns the inconsistency into a rhythm — and a directory grid
      reads far better when the rows line up. */
.site_div > .description {
	min-height: calc(1.45em * 3);
	display: -webkit-box;
	-webkit-line-clamp: 3;
	-webkit-box-orient: vertical;
	overflow: hidden;
}
/* The horizontal (thumb) cards on recent.php keep their own flex
   layout — line-clamp would break the name/date stack. */
.site_div:has(img.post_thumb) > .description {
	display: flex;
	-webkit-line-clamp: none;
	min-height: 0;
	overflow: visible;
}

/* 3. CAROUSEL PROPORTION. The slide box ran much wider than the image
      inside it, so the featured photo looked lost. Narrow the frame to
      the image's own scale rather than resizing the image — no width or
      display on the track or slide, so the verified geometry is intact. */
.hero-carousel {
	max-width: calc(var(--unit) * 2 + var(--gap));
	margin-left: auto;
	margin-right: auto;
}

/* ===================================================================
   THE ACTUAL WIDTH CONSTRAINT: #apDiv1 { width: 1000px }

   index.php (and site.php) wrap their whole body in <div id="apDiv1">,
   and the page's own <style> block gives it a FIXED 1000px width. That
   is an ID selector, so it outranks every class rule in this file — the
   grid was never able to use more than 1000px no matter what max-width
   the table was given, which is why three 468 columns "wouldn't fit"
   in a 1440 viewport.

   Matched at ID specificity to release it. Fourth instance today of the
   page's own styling silently outranking this stylesheet, after the
   <body> background attribute, the join link's inline style, and
   a:link beating a bare `a`. On this codebase, when a rule is served
   and does nothing, suspect the page before the stylesheet.
   =================================================================== */

#apDiv1 {
	width: auto;
	max-width: min(100%, calc(var(--unit) * 3 + var(--gap) * 2 + 12px));
	margin: 0 auto;
}

/* The carousel image was left-aligned inside its slide: the base
   `img { display: block }` rule makes text-align:center on the slide
   ineffective, so centre it with auto margins instead. Margins do not
   affect the track's transform or clipping. */
.hero-carousel-image { margin-left: auto; margin-right: auto; }

/* One content width for the page: the carousel had been narrowed to two
   columns while the directory grid runs three, which read as two
   different pages stacked. Match them. */
.hero-carousel {
	max-width: min(100%, calc(var(--unit) * 3 + var(--gap) * 2 + 10px));
}

/* ===================================================================
   CAROUSEL CONTROLS — full-height buttons flanking the picture.
   JWR: "picture height left and right buttons on the left and right of
   the image area."

   hero-carousel.js appends `.hero-carousel-nav` (two bare <button>s,
   told apart by their aria-label) and `.hero-carousel-dots` to the
   carousel root, so both can be repositioned from CSS without touching
   the script.

   The image gets a FIXED height so the button strips can match it
   exactly — which also evens out the carousel between portrait and
   landscape slides. object-fit:contain, so nothing is cropped or
   stretched. Height does not affect the track's horizontal transform.
   =================================================================== */

.hero-carousel { position: relative; }

.hero-carousel-image {
	height: 380px;
	width: auto;
	max-width: 100%;
	object-fit: contain;
	object-position: center;
}

/* The nav spans exactly the picture area and lets clicks through, so the
   image underneath stays clickable between the two buttons. */
.hero-carousel-nav {
	position: absolute;
	top: 18px;
	left: 0;
	right: 0;
	height: 380px;
	margin: 0;
	pointer-events: none;
	z-index: 2;
}

.hero-carousel-nav button {
	position: absolute;
	top: 0;
	bottom: 0;
	width: 58px;
	height: 100%;
	margin: 0;
	padding: 0;
	pointer-events: auto;
	background: linear-gradient(to right, rgba(22,17,15,.72), rgba(22,17,15,0));
	border: 0;
	border-radius: 0;
	color: var(--brass);
	font-size: 0;              /* hide "Prev"/"Next"; a chevron replaces it */
	cursor: pointer;
	transition: background .18s ease, color .18s ease;
}
.hero-carousel-nav button[aria-label^="Previous"] { left: 0; }
.hero-carousel-nav button[aria-label^="Next"] {
	right: 0;
	background: linear-gradient(to left, rgba(22,17,15,.72), rgba(22,17,15,0));
}

/* Chevrons, drawn rather than typed, so there is no glyph dependency. */
.hero-carousel-nav button::before {
	content: "";
	position: absolute;
	top: 50%;
	left: 50%;
	width: 13px;
	height: 13px;
	border-top: 2px solid currentColor;
	border-left: 2px solid currentColor;
}
.hero-carousel-nav button[aria-label^="Previous"]::before {
	transform: translate(-50%, -50%) rotate(-45deg);
}
.hero-carousel-nav button[aria-label^="Next"]::before {
	transform: translate(-50%, -50%) rotate(135deg);
}

.hero-carousel-nav button:hover { color: var(--brass-lift); }
.hero-carousel-nav button[aria-label^="Previous"]:hover {
	background: linear-gradient(to right, rgba(22,17,15,.92), rgba(22,17,15,0));
}
.hero-carousel-nav button[aria-label^="Next"]:hover {
	background: linear-gradient(to left, rgba(22,17,15,.92), rgba(22,17,15,0));
}
.hero-carousel-nav button:focus-visible {
	outline: 2px solid var(--brass-lift);
	outline-offset: -4px;
}

/* Dots move up under the caption now that the buttons have left the flow. */
.hero-carousel-dots { margin-top: 4px; }

/* Match the carousel to the grid's width at ID specificity. Two earlier
   .hero-carousel max-width rules were fighting and the narrower one kept
   winning; the element carries id="heroCarousel", so settle it there
   rather than adding another class-level rule to the pile. */
#heroCarousel {
	/* index.php's own style block sets `.hero-carousel { width: 940px }`
	   — a FIXED width. max-width only caps a width, it can never widen
	   one, so overriding max-width alone did nothing. Release the width
	   itself and then cap it to the grid's measure. */
	width: auto;
	max-width: min(100%, calc(var(--unit) * 3 + var(--gap) * 2 + 10px));
	margin-left: auto;
	margin-right: auto;
}
