/* What's Happening — the events surface (list + one event).
   Rebuilt for craft on 2026-08-17; the identity is unchanged.

   THE PAGE HAS A COLOUR AND HAS NEVER USED IT. tokens.css names --c-ivory
   "palette: Happenings (warm ivory) — restrained accent band only". The brand palette
   assigns one warm tone to this page specifically, and until today the page was built
   entirely out of --bg-soft and --paper like every other. Ivory now carries the three
   moments that are actually about anticipation: the pinned event, a day that has
   something on it, and the empty state. That is how this surface gets an identity
   without spending a drop of the accent diet the 7/21 "less words, more calm" direction
   set — the colour was already allocated to it.

   SECTION HEADINGS ARE RULES. "Coming up", "Already happened" and "Every month at
   Amelia's" each carry a hairline out to the container edge. It is one device, it does
   the structural work the old stack of loose kickers only implied, and it costs no
   colour and no weight. The list's own border-top went with it: the heading rule is the
   top edge now, and two lines a heading apart was the seam that made this read as a
   document rather than a page.

   ONE AUTHORED MOTION, used everywhere something is clickable: the photograph eases up
   in scale while its title takes the accent. Nothing enters, nothing fades in on scroll.
   A page whose whole job is "is anything on?" should answer before it finishes moving.

   Tokens only, no raw values. */

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

/* THE SAME DEAD MODIFIER /menus HAD (fixed there 8/8, here 2026-08-17). Both templates
   in this file ask for .section--tight-top, but that class is only DEFINED in
   pages/menu.css and pages/menus.css, neither of which they load. So it did nothing and
   the section fell back to the full .section rhythm, up to 80px between the sticky
   header and the title.

   NOT the clamp(8px, 1.6vw, 14px) /menus uses, which was tried here first and the owner
   called immediately: "now the space between header and what's happening is too small."
   /menus can carry it because its title sits over a grid of photographs; this page opens
   on a line of text directly under a sticky bar. */
.section--tight-top { padding-top: clamp(14px, 1.9vw, 22px); }

.hap__h {
  margin: 0 0 clamp(10px, 1.4vw, 15px);
  /* Down from the global h1 clamp(2.2rem, 5vw, 3.2rem), the step /menus already took.
     Two words in tracked caps do not need display-hero size, and at 3.2rem "WHAT'S
     HAPPENING" wrapped to two lines before any content started. */
  font-size: clamp(1.8rem, 6vw, 2.6rem);
  line-height: 1.08;
}

/* SECTION HEADINGS. Until 2026-08-17 these were sized like the row's date line — .kicker
   is 0.8rem tracked caps, .hap__when was 0.72rem tracked caps — and the owner said so:
   "coming up and the date are very similar size, make it better."

   Each neighbour is separated on a different axis, which is what stops the heading
   colliding with either. Against the DATE it is size, 1.15rem against 0.68rem. Against
   the event TITLE, which is a similar size, it is colour: the heading keeps .kicker's
   muted grey, the title is full ink. Deliberately not promoted to ink — kickers are
   quiet wayfinding under the 7/21 direction, and winning the size argument by breaking a
   rule that governs every page is not winning it. */
.hap__kicker {
  display: flex;
  align-items: center;
  gap: clamp(12px, 2vw, 20px);
  margin: 0 0 clamp(6px, 1vw, 10px);
  font-size: clamp(0.95rem, 2vw, 1.15rem);
  letter-spacing: 0.14em;
}
.hap__kicker::after {
  content: "";
  flex: 1 1 auto;
  height: 1px;
  background: var(--line);
}
/* More space above a heading than below it: the rule below the heading already ties it
   to what it introduces, so the gap that matters is the one separating it from the block
   before. */
.hap__kicker--past { margin-top: clamp(22px, 2.3vw, 28px); }

/* ------------------------------------------------------------------ upcoming --- */

/* LIST LEFT, CALENDAR RIGHT, on a wide screen only (client, 2026-08-17). Below 1100px
   they stack in source order, list first: seven columns of month grid beside anything
   else is unreadable at that width. align-items:start so the sticky side column has a
   height to be sticky against, rather than being stretched to the row. */
.hap__cols { display: grid; gap: clamp(26px, 3vw, 40px); }
@media (min-width: 1100px) {
  .hap__cols {
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
    align-items: start;
  }
  /* Clears the sticky site header. The calendar holds while the list scrolls, which is
     the whole reason to put them side by side. */
  .hap__col-side { position: sticky; top: clamp(88px, 11vh, 116px); }
  .hap__col-side .hapcal { margin-top: 0; }

  /* THE ROW HAS TO GIVE UP ITS THIRD COLUMN HERE. The container caps at 1240px however
     wide the screen, so this column is about 580px whatever the viewport does — and the
     full-width row wants 250px of photograph plus a 20rem body plus a button beside it,
     which is roughly 780px. Left alone it does not overflow, it CRUSHES: the date line
     broke onto six lines, the blurb ran one word per line, and the Sign up button landed
     on top of the copy.

     So inside the two-column layout the photograph comes down, the body keeps a smaller
     basis, and the action wraps onto its own line instead of competing for width. The
     stacked layout below 1100px is untouched, and so is the row on a page where the list
     has the whole container. */
  .hap__cols .hap__row { gap: clamp(14px, 1.6vw, 20px); }
  .hap__cols .hap__thumb { width: clamp(130px, 13vw, 168px); }
  .hap__cols .hap__body { flex: 1 1 190px; }
  /* THE ACTION STAYS ON THE ROW, it does not wrap to its own line. Wrapping it was the
     first fix for the crushed column and it cost about 66px of height per event, which
     over three events is most of the reason "Every month at Amelia's" sat 400px below
     the fold. Measured: 165 + 22 + 190 + 22 + 130 is 529px inside a 580px column, so it
     fits inline once the photograph comes down a size. */
  .hap__cols .hap__act { flex: 0 0 auto; width: auto; padding-top: 2px; }
  .hap__cols .hap__row--featured .hap__thumb { width: clamp(150px, 15vw, 192px); aspect-ratio: 4 / 3; }
  .hap__cols .hap__row--featured .hap__title { font-size: clamp(1.25rem, 1.9vw, 1.5rem); }
  /* Price and location sit on one line at this width or they stack into a column of
     single words. */
  .hap__cols .hap__meta > span { white-space: nowrap; }
}

.hap__list {
  list-style: none;
  margin: 0;
  padding: 0;
  /* No border-top. The section heading's own rule is the top edge. */
}

/* "N more coming up" (client, 2026-08-25: limit the list to two or three at a time).
   A native <details>, so it opens with JS off and announces its own state. Styled as a
   quiet row rather than a button: it is a way to see the rest of a list, not a call to
   action competing with the Sign up buttons above it. */
.hap__more { border-top: 1px solid var(--line); }
.hap__more-toggle {
  cursor: pointer;
  list-style: none;
  padding: 14px 0;
  font-size: 0.86rem;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  display: flex;
  align-items: center;
  gap: 8px;
}
/* Safari still needs the -webkit pseudo to drop the disclosure triangle. */
.hap__more-toggle::-webkit-details-marker { display: none; }
.hap__more-toggle::after {
  content: '';
  width: 7px;
  height: 7px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform 0.18s ease;
}
.hap__more[open] > .hap__more-toggle::after { transform: rotate(-135deg) translate(-2px, -2px); }
.hap__more-toggle:hover { color: var(--ink); }
.hap__more-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .hap__more-toggle::after { transition: none; } }

/* THE ROW IS THE LINK (client, 2026-08-17: "details as a button is a no-no, just make
   the card clickable"). The stretched overlay lives on the title anchor; see the
   template for why it is not a wrapping <a>. */
.hap__row {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: clamp(12px, 2vw, 18px);
  padding: clamp(10px, 1.2vw, 13px) 0;
  border-bottom: 1px solid var(--line);
}
@media (min-width: 641px) {
  .hap__row { flex-direction: row; align-items: flex-start; gap: clamp(20px, 2.8vw, 34px); }
}

/* THE PHOTOGRAPH IS THE POINT ON A PHONE. It was a 84px thumbnail at 375px, which is a
   postage stamp beside two lines of type and reads as a bullet rather than as the food.
   Full width and 3:2 under 641px, a proper 4:3 column above it. */
.hap__thumb {
  flex: 0 0 auto;
  width: 100%;
  aspect-ratio: 3 / 2;
  overflow: hidden;
  border-radius: var(--r);
  background: var(--bg-soft);
}
@media (min-width: 641px) {
  .hap__thumb { width: clamp(170px, 23vw, 250px); aspect-ratio: 4 / 3; }
}
.hap__thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.55s cubic-bezier(0.22, 0.7, 0.25, 1);
}
.hap__row:hover .hap__thumb img,
.hap__pastlink:hover .hap__collage img { transform: scale(1.045); }

/* THE FLEX BASIS ONLY EXISTS IN ROW DIRECTION. Written as a flat `flex: 1 1 20rem` this
   set a 20rem basis on the MAIN axis, and under 641px the main axis is vertical — so
   every row reserved 320px of height for a body two lines tall, and the phone layout ran
   with a void between each event and its own Sign up button. Basis belongs in the media
   query that establishes the row, and nowhere else. */
.hap__body { flex: 1 1 auto; min-width: 0; }
@media (min-width: 641px) { .hap__body { flex: 1 1 20rem; } }

.hap__when {
  margin: 0 0 6px;
  /* Down a notch from 0.72rem to widen the gap against the section heading (8/17). */
  font-size: 0.68rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}
/* The house rule in base.css uppercases public headings by SELECTOR — h1, h2, h3, .disp
   — and the archive's title is a <span> inside a link, so it was the one title on the
   page rendering in mixed case beside a column of tracked caps. Set here on the class so
   every .hap__title matches whatever element it happens to be written on. */
.hap__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(1.25rem, 2.6vw, 1.65rem);
  line-height: 1.14;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  transition: color 0.2s ease;
}
.hap__blurb {
  margin: 6px 0 0;
  color: var(--ink-2);
  line-height: 1.55;
  max-width: 62ch;
}
.hap__meta {
  margin: 8px 0 0;
  display: flex;
  flex-wrap: wrap;
  gap: 4px 16px;
  font-size: 0.8rem;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

/* Sign up is the only control on the row, because it is the only thing that goes
   somewhere the row does not. It lifts above the stretched overlay. */
.hap__act { width: 100%; position: relative; z-index: 1; }
@media (min-width: 641px) {
  .hap__act { width: auto; flex: 0 0 auto; padding-top: 2px; }
}

/* PINNING AN EVENT NOW LOOKS LIKE SOMETHING. The admin has had a "Pin to the top of
   Coming up" checkbox since 0031 and its entire visual expression was
   `.hap__row--featured .hap__title { font-style: normal }` — a rule that unsets italic on
   a title that was never italic, so it did exactly nothing. The owner ticked a box and
   the page did not change. It is the page's one ivory panel, with the photograph and the
   title stepped up to match. */
.hap__row--featured {
  margin-bottom: clamp(10px, 1.4vw, 16px);
  padding: clamp(12px, 1.6vw, 18px);
  background: var(--ivory);
  border-bottom-color: transparent;
  border-radius: var(--r);
}
.hap__row--featured .hap__blurb { color: var(--ink); }
@media (min-width: 641px) {
  /* Centred, not top-aligned: the pinned row's photograph is half as tall again as its
     three lines of type, and flex-start left that difference as a slab of empty ivory
     under the text. Every other row keeps flex-start, where the images and the copy are
     close enough in height for a shared top edge to be the tidier answer. */
  .hap__row--featured { align-items: center; }
  .hap__row--featured .hap__thumb { width: clamp(210px, 30vw, 320px); aspect-ratio: 3 / 2; }
  .hap__row--featured .hap__title { font-size: clamp(1.5rem, 3.2vw, 2.05rem); }
}

/* Focus rides the row, not the fragment of text the anchor wraps. BOTH rules sit inside
   :has() on purpose: written bare, the outline:none would still apply in a browser
   without :has() support, where the row rule is dropped, and the row would have no
   visible focus at all. Guarded this way an unsupporting browser keeps the title ring. */
.hap__row:has(.hap__link:focus-visible),
.hap__program:has(.hap__link:focus-visible),
.hap__pastrow:has(.hap__pastlink:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: 5px;
}
.hap__row:has(.hap__link:focus-visible) .hap__link,
.hap__program:has(.hap__link:focus-visible) .hap__link,
.hap__pastrow:has(.hap__pastlink:focus-visible) .hap__pastlink { outline: none; }

.hap__row:hover .hap__title,
.hap__program:hover .hap__title,
.hap__pastlink:hover .hap__title { color: var(--accent); }

/* --------------------------------------------------------------- empty state --- */

/* THE EMPTY STATE IS THE PAGE, most weeks. One line and one thing to do about it, on the
   page's own ivory. A restaurant with nothing on next week is normal, and dressing that
   as a failure state is what reads as amateur. */
.hap__empty {
  padding: clamp(30px, 5vw, 56px) clamp(22px, 4vw, 44px);
  background: var(--ivory);
  border-radius: var(--r);
  text-align: center;
}
.hap__empty p { margin: 0; line-height: 1.62; max-width: 46ch; margin-inline: auto; }
/* The first line carries the answer, so it is sized like an answer rather than like body
   copy. Display face, not bold body — this page is calm, and a bolded sentence in a soft
   panel reads as an error message, which is precisely what it is not. */
.hap__empty-lead {
  font-family: var(--font-display);
  font-size: clamp(1.2rem, 2.6vw, 1.6rem);
  line-height: 1.2;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.hap__empty-links { margin-top: clamp(16px, 2.2vw, 22px); }

/* ------------------------------------------------------------------ calendar --- */

/* A month grid over the same rows the page already renders, upcoming and past, so the
   two can never disagree. It sits UNDER the list as of 8/17. */
/* FULL CONTAINER WIDTH (owner, 2026-08-17). It was briefly capped at 760px on the
   argument that seven columns across 1240px gives 133px cells carrying a 12px label, so
   the heaviest block on the page was also the emptiest. Overruled, and the cap is gone:
   the calendar reads as a calendar at full width, and a month grid that stops short of
   the rules above and below it looks like a mistake rather than a decision. */
.hapcal { margin: clamp(30px, 4.4vw, 52px) 0 0; }
.hapcal__head {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(4px, 1vw, 10px);
  margin: 0 0 clamp(12px, 1.8vw, 18px);
}
.hapcal__month {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(0.95rem, 2vw, 1.1rem);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  font-weight: 400;
  /* The month name changes width as you page; a fixed minimum stops the two arrows
     sliding in and out under the cursor you are clicking them with. */
  min-width: clamp(9rem, 26vw, 12rem);
  text-align: center;
}
.hapcal__nav {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 44px: the month arrows are the only controls on this block and they are used on a
     phone, where anything smaller is a miss. */
  width: 44px;
  height: 44px;
  border-radius: var(--r);
  color: var(--text-muted);
  font-size: 1.35rem;
  line-height: 1;
  text-decoration: none;
  transition: color 0.2s ease, background-color 0.2s ease;
}
.hapcal__nav:hover, .hapcal__nav:focus-visible { color: var(--ink); background: var(--bg-soft); }

.hapcal__grid { width: 100%; border-collapse: collapse; table-layout: fixed; }
.hapcal__grid th {
  padding: 0 0 10px;
  font-family: var(--font-body);
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.hapcal__grid th abbr { text-decoration: none; border: 0; }
.hapcal__day {
  border: 1px solid var(--line);
  vertical-align: top;
  /* The grid is always six rows now, so this is multiplied by six every time. The floor
     stays at 48px: on a phone the whole square is the tap target. */
  height: clamp(46px, 5.2vw, 70px);
  padding: 0;
}
/* DAYS FROM THE MONTHS EITHER SIDE. They were blank padding cells until 2026-08-17;
   they now carry their real date and their events.

   Recessed by DROPPING THE BORDER, not by lightening the number. Dimmer grey is the
   conventional treatment and it cannot be done here honestly: --text-muted is already
   at 4.76:1 on this background, so anything lighter fails AA, and the fix that had just
   been made to that token would be spent straight back. An unbordered cell reads as
   outside the month at a glance and every number on the grid stays legible.

   An out-of-month day that HAS something on it keeps its border and its fill, because an
   event on the 1st of next month is worth seeing from this one. */
.hapcal__day--out { border-color: transparent; }
.hapcal__day--out.hapcal__day--on { border-color: var(--line); }
.hapcal__n {
  display: block;
  padding: 6px 8px 0;
  font-size: 0.8rem;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}

/* A DAY WITH SOMETHING ON IT WAS INVISIBLE. This was `background: var(--paper)`, which
   resolves to the near-white card surface, against a page whose background is the bone
   near-white — the two are about one percent of luminance apart, so the calendar's only
   job, marking the days that have an event, was being done by a fill nobody could see.
   Ivory is the page's own brand tone and it actually reads. */
.hapcal__day--on { background: var(--ivory); }
/* Today is a ring rather than a fill, so it can coincide with an event day and both
   still read. */
.hapcal__day--today { box-shadow: inset 0 0 0 2px var(--accent); }
.hapcal__day--today .hapcal__n { color: var(--ink); font-weight: 700; }

.hapcal__link {
  display: block;
  height: 100%;
  text-decoration: none;
  color: inherit;
  transition: box-shadow 0.2s ease;
}
.hapcal__link:hover, .hapcal__link:focus-visible { box-shadow: inset 0 0 0 1px var(--accent); }
.hapcal__link .hapcal__n { color: var(--ink); font-weight: 700; }
/* THE CONTENTS OF A DAY WITH SOMETHING ON IT (client, 2026-08-17: "improve the content
   inside each calendar day square so it's more readable").

   It used to be the day number and a 0.72rem title, clamped to two lines and breaking
   mid-word, with no time anywhere on it. Now: number, then the hour, then the title. The
   hour is the fact a calendar square is actually asked for and it was the one thing
   missing; the title alone told you nothing the list above did not.

   The block is inset from the number and given real leading, so the square reads as a
   small entry rather than as text jammed into a table cell. */
.hapcal__ev { display: block; padding: 1px 6px 6px; }
.hapcal__time {
  display: block;
  font-size: 0.63rem;
  letter-spacing: 0.06em;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* ONE LINE, ELLIPSIZED — NOT A TWO-LINE CLAMP. Measured: a cell in the side column is
   72px wide, which leaves the title 57px of text. "Ice Cream Social" has no two words
   that fit on one line together at that width, so the old two-line clamp rendered it as
   "Ice" / "Cream" and threw "Social" away. Three stacked fragments of a name are harder
   to read than a truncated one, and they made every square a different shape.

   A single ellipsized line is what a month grid actually wants: "Ice Cream So…" is read
   at a glance, every square is the same height by construction rather than by clamping,
   and the full name is one hover or one tap away. */
.hapcal__title {
  display: block;
  font-size: 0.74rem;
  line-height: 1.3;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.hapcal__more { display: block; padding: 0 8px 6px; font-size: 0.66rem; color: var(--text-muted); }

/* ON A PHONE THE SQUARE IS ~48px WIDE and an event title cannot live in it: "Ice Cream
   Social" clamped to two lines still broke mid-word and spilled past the cell border.
   Below 560px the title is dropped for a dot. The information does not go anywhere,
   because the whole square is a link to the event and the ivory fill already says
   something is on; the dot just makes that unmissable at a glance down the month. */
@media (max-width: 559px) {
  .hapcal__ev, .hapcal__more { display: none; }
  .hapcal__n { padding: 5px 0 0; text-align: center; }
  .hapcal__link { display: flex; flex-direction: column; align-items: center; gap: 4px; }
  .hapcal__link::after {
    content: "";
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--accent);
  }
}

/* ------------------------------------------------------------------- archive --- */

.hap__past {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: clamp(16px, 2.2vw, 26px);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 210px), 1fr));
}
.hap__pastrow { position: relative; }
.hap__pastlink { display: block; }
.hap__collage {
  display: grid;
  gap: 3px;
  aspect-ratio: 3 / 2;
  overflow: hidden;
  border-radius: var(--r);
  background: var(--bg-soft);
  margin-bottom: clamp(8px, 1.1vw, 11px);
}
/* EVERY TRACK IS minmax(0, …), NOT A BARE fr, AND THE COLLAGE HAS BEEN HALF-BROKEN
   WITHOUT IT. A bare `1fr` is `minmax(auto, 1fr)`, and that `auto` floor is the item's
   min-content height — for a photograph, its intrinsic height. So a four-photo collage
   in a box 192px tall was laying out two rows of 190px, 380px of content in a 192px box
   with overflow:hidden over the top, and the bottom two photographs were simply cut off.
   Measured, not guessed: rows computed to "190.031px 190.031px" inside a 256x192 box.

   Which mattered, because the collage is the thing Stacey asked for by name: "a photo
   collage of the ice cream social, of the wine dinner." Half of every one of them was
   being thrown away. */
.hap__collage[data-n="1"] { grid-template-columns: minmax(0, 1fr); }
.hap__collage[data-n="2"] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.hap__collage[data-n="3"] { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); grid-template-rows: repeat(2, minmax(0, 1fr)); }
.hap__collage[data-n="3"] img:first-child { grid-row: span 2; }
.hap__collage[data-n="4"] { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-rows: repeat(2, minmax(0, 1fr)); }
.hap__collage img {
  width: 100%;
  height: 100%;
  min-width: 0;
  min-height: 0;
  object-fit: cover;
  transition: transform 0.55s cubic-bezier(0.22, 0.7, 0.25, 1);
}
.hap__pastbody { display: block; }
.hap__pastbody .hap__when,
.hap__pastbody .hap__title,
.hap__pastbody .hap__blurb { display: block; }
.hap__pastbody .hap__title { font-size: clamp(1.1rem, 2.1vw, 1.3rem); }
.hap__pastbody .hap__blurb { font-size: 0.9rem; margin-top: 6px; }

/* -------------------------------------------------------- standing programmes --- */

.hap__programs {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: clamp(16px, 2.4vw, 26px);
}
@media (min-width: 720px) { .hap__programs { grid-template-columns: 1fr 1fr; } }
/* Clickable cards, same stretched link as the rows above. Each one used to carry its
   title as a link AND a "See Sunday Supper" arrow to the same URL. */
.hap__program {
  position: relative;
  padding: clamp(16px, 2.1vw, 24px);
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--surface);
  transition: border-color 0.2s ease;
}
.hap__program:hover { border-color: var(--accent); }
.hap__program .hap__title { margin: 0 0 10px; }
/* No bottom margin: the blurb is the last thing in the card now. */
.hap__program .hap__blurb { margin: 0; }

/* -------------------------------------------------------------- one event --- */

/* THE PAGE THE BARTENDER CAN POINT AT, and it is a SHORT page. Rebuilt three times on
   2026-08-17. First a hero with a sticky facts sidebar, which is right for an event with
   a photograph and a long write-up. Then, once Renato pointed at a real one, a single
   column with an ivory masthead and fallback Reserve / Call buttons. Then this, on his
   verdict: "this is terrible. we need to compact vertically not make them scroll to see
   things."

   Both earlier passes made the same mistake in opposite directions — they added height
   to a thin page so it would look fuller. The masthead's padding and the two buttons
   were pushing the three lines somebody came to read below the fold. A short page should
   BE short: the 3 Year Anniversary Party, which is everything the owners typically fill
   in, now fits on one screen with nothing to scroll for.

   So the spacing here is deliberately tight and the type does the work instead. */
.hapd { max-width: var(--maxw); }

.hapd__hero {
  margin: 0 0 clamp(14px, 2vw, 22px);
  aspect-ratio: 3 / 2;
  overflow: hidden;
  border-radius: var(--r);
  background: var(--bg-soft);
}
@media (min-width: 900px) { .hapd__hero { aspect-ratio: 21 / 9; } }
.hapd__hero img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Capped in pixels, not ch: ch resolves against the element's OWN font-size, the
   inherited 17px body rather than the heading inside it, and at 34ch that computed to
   about 290px and folded "WINE DINNER" onto two lines. */
.hapd__head { max-width: 900px; }

.hapd__when {
  margin: 0 0 6px;
  font-size: 0.7rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}
.hapd__when--past { color: var(--text-muted); }
/* Down from clamp(2rem, 5.4vw, 3.1rem). At 3.1rem a three-word title took two lines of
   50px before the page had said anything, which on a short event is most of the screen.
   It is still comfortably the largest thing here. */
.hapd__h {
  margin: 0;
  font-size: clamp(1.65rem, 3.6vw, 2.4rem);
  line-height: 1.08;
}
.hapd__lede {
  margin: clamp(8px, 1.2vw, 12px) 0 0;
  max-width: 52ch;
  font-size: clamp(1rem, 1.8vw, 1.12rem);
  line-height: 1.5;
  color: var(--ink-2);
}

/* ---- the facts strip ---- */

/* Hairlines top and bottom rather than a boxed card, matching the section rules on the
   list page. Facts left, the sign-up button right, wrapping to a stack on a phone. Not
   rendered at all when it would be empty. */
.hapd__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: clamp(14px, 2.2vw, 30px);
  margin: clamp(14px, 1.8vw, 20px) 0 0;
  padding: clamp(10px, 1.3vw, 14px) 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.hapd__facts {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: clamp(14px, 2.6vw, 38px);
}
.hapd__fact { min-width: 0; }
.hapd__facts dt {
  margin: 0 0 2px;
  font-size: 0.64rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.hapd__facts dd { margin: 0; line-height: 1.4; }
.hapd__facts dd strong { font-weight: 600; }

/* margin-left:auto rather than space-between, so a strip carrying only the button still
   pushes it to the right edge instead of the left. */
.hapd__acts { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-left: auto; }
.hapd__over { margin: 0; max-width: 44ch; font-size: 0.86rem; color: var(--text-muted); line-height: 1.45; }
@media (max-width: 599px) {
  .hapd__acts { margin-left: 0; width: 100%; }
  .hapd__acts .btn { flex: 1 1 auto; justify-content: center; }
}

/* ---- body + gallery ---- */

/* Measure capped in ch, so the text stays readable however wide the page gets. */
.hapd__body { max-width: 66ch; margin-top: clamp(14px, 1.8vw, 20px); }
.hapd__body p { margin: 0 0 0.85em; line-height: 1.65; }
.hapd__body p:last-child { margin-bottom: 0; }

/* The first frame leads at double width; a uniform grid gave a wine dinner and a
   portrait the same weight. */
.hapd__gallery {
  list-style: none;
  margin: clamp(18px, 2.6vw, 30px) 0 0;
  padding: 0;
  display: grid;
  gap: clamp(8px, 1.2vw, 12px);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 190px), 1fr));
}
.hapd__gallery li { overflow: hidden; border-radius: var(--r); background: var(--bg-soft); }
.hapd__gallery img { width: 100%; height: 100%; aspect-ratio: 4 / 3; object-fit: cover; display: block; }
@media (min-width: 720px) {
  .hapd__gallery li:first-child { grid-column: span 2; grid-row: span 2; }
  .hapd__gallery li:first-child img { aspect-ratio: 1 / 1; }
}

.hapd__back { margin: clamp(18px, 2.4vw, 26px) 0 0; padding-top: clamp(12px, 1.6vw, 16px); border-top: 1px solid var(--line); }

/* ------------------------------------------------------------------- motion --- */

/* The authored moment is a hover on a photograph. Nobody needs it to answer "is anything
   on tonight", so it is the first thing to go. */
@media (prefers-reduced-motion: reduce) {
  .hap__thumb img,
  .hap__collage img,
  .hap__title,
  .hapcal__nav,
  .hapcal__link,
  .hap__program { transition: none; }
  .hap__row:hover .hap__thumb img,
  .hap__pastlink:hover .hap__collage img { transform: none; }
}

/* The one-sheeter link on an event page (client, 2026-08-25). Same visual weight as the
   catering menu link on /catering — a real download, not dressed as a primary button,
   because the primary action on this page is Sign up. */
.hapd__pdf { margin: clamp(18px, 2.4vw, 28px) 0 0; }
.hapd__pdf-link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 0.9rem;
  color: var(--text-muted);
  text-decoration: none;
  border-bottom: 1px solid var(--line);
  padding-bottom: 3px;
}
.hapd__pdf-link:hover { color: var(--ink); border-bottom-color: currentColor; }

/* THE OTHER DATES IN A STANDING SERIES (2026-08-26). One row now carries the whole
   programme, so these are the dates after the one in the row's when-line.

   They sit ABOVE the row's stretched link (z-index), because each is its own destination
   and the row overlay would otherwise swallow the clicks — the same reason the Sign up
   button is lifted. Quiet by default: this is a secondary route into a date, not a call
   to action competing with Sign up. */
.hap__series {
  position: relative;
  z-index: 2;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 8px;
  margin: 6px 0 0;
  font-size: 0.8rem;
}
.hap__series-lbl {
  color: var(--text-muted);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-size: 0.68rem;
}
.hap__series-date {
  color: var(--ink-2);
  text-decoration: none;
  border-bottom: 1px solid var(--line);
  padding-bottom: 1px;
  font-variant-numeric: tabular-nums;
}
.hap__series-date:hover { color: var(--ink); border-bottom-color: currentColor; }
.hap__series-date:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.hap__series-more { color: var(--text-muted); }

/* ===================== EVENT FLYER / ARTWORK =====================
   Renato, 2026-08-26: "if the event has a flyer or art work it stays uncropped and shows
   at 80% height ish of the screen but with no crop."

   ONE SIZING RULE, EVERY RATIO. The image is never cropped and never distorted: it keeps
   its intrinsic aspect (width/height auto) and is bounded on BOTH axes. A tall poster is
   stopped by max-height; a wide banner is stopped by max-width; whichever binds first
   wins, so a 1:3 and a 3:1 both fit with no special case and no object-fit.

   Why not object-fit: cover — that is precisely the crop being removed. A flyer carries
   its date and its invitation inside the image, so cropping it deletes information, which
   is what happened to the anniversary one-sheeter. */
.hapd__flyer { margin: 0; }
.hapd__flyer img {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 80vh;
  margin-inline: auto;
  border-radius: 2px;
}

/* LANDSCAPE / SQUARE: above the details, centred. */
.hapd__flyer--top { margin-bottom: clamp(20px, 3vw, 32px); }

/* PORTRAIT: beside the details. Below 900px it stacks — a portrait poster at 80vh next to
   a column of text is unreadable on a phone, and there is no width to give either of them.

   Stacked, the cap drops to 60vh so the title and date are on screen WITH the artwork
   rather than a scroll below it. On a phone an 80vh image plus the sticky header leaves
   nothing else visible, which turns the page into a poster and hides the thing somebody
   came for. */
.hapd__split { display: grid; gap: clamp(22px, 3vw, 40px); }
.hapd__flyer--side img { max-height: 60vh; }
@media (min-width: 900px) {
  .hapd__split {
    grid-template-columns: minmax(0, 1fr) minmax(0, auto);
    align-items: start;
  }
  /* The artwork is LAST in the source (details first for screen readers and no-CSS) and
     first to the eye on the right — grid places it, the DOM order stays correct. */
  .hapd__flyer--side { position: sticky; top: clamp(88px, 11vh, 116px); }
  .hapd__flyer--side img { max-height: 80vh; }
}
