/* =================================================================
   FESTIVE BALL 2026 — page stylesheet (TASK-313)

   Loaded ONLY by /ball. Deliberately not part of assets/css/styles.css:
   donate.html sits ~369 bytes under its enforced first-paint budget
   (test/unit/perf-budget.test.ts), and that page is the donation money
   path — growing the shared bundle would break it.

   Everything here builds on the shared brand tokens in styles.css. The
   only new colours are the poster's night-sky and gold, scoped to this
   page, so the rest of the site is untouched.
   ================================================================= */

.ball-page {
  /* The advert's palette. Deep navy rather than black: the printed poster is a
     night sky, not a void, and a true black band would read as a broken image
     against the site's warm cream. */
  --night: #0B1020;
  --night-2: #131A31;
  /* Rules and borders on the night ground. Raised from 0.22 to 0.42: on a bright screen a
     22% gold hairline over near-black is close to invisible, and the fact-row rules in the
     hero simply were not there. This is the value every dark-ground border on the page uses,
     so one change fixes all of them. */
  --night-line: rgba(233, 210, 150, 0.42);
  /* Two golds. The bright one is for large type and rules only; the deep one is
     the accessible foreground for small text on cream. */
  --gold: #E4C87A;
  --gold-deep: #8A6A26;
  --on-night: #F2EEE4;
  --on-night-soft: rgba(242, 238, 228, 0.74);

  /* Exponential easings — things decelerate the way real objects do. No bounce or
     elastic: they draw attention to the animation rather than the content. */
  --ease-quart: cubic-bezier(0.25, 1, 0.5, 1);
  --ease-quint: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-expo: cubic-bezier(0.16, 1, 0.3, 1);
}

/* ---------- nav: always cream on this page ----------

   BRAND RULE: the NBCC logo is the full-colour red mark, and the brand guidelines
   require it to sit on cream. Everywhere else on the site the nav starts
   transparent over a cream page and only paints a background once scrolled, which
   is fine. Here the page opens on a near-black hero, so a transparent nav would
   put the red logo on black — off-brand, and it made the dark-slate nav links
   effectively invisible too (~1.5:1).

   So this page opts out of the transparent state entirely: the bar is solid cream
   from the first pixel, which gives the logo its correct ground and lets every
   shared nav style (slate links, maroon burger, crimson CTA) work exactly as
   designed. Solid rather than the shared translucent mix, because a 90% cream over
   a black hero reads as dirty grey behind the mark. */

.ball-page .nav,
.ball-page .nav.scrolled {
  background: var(--cream);
  box-shadow: var(--shadow-sm);
  border-bottom: 1px solid var(--line);
  /* No backdrop blur: the background is opaque, so it would cost a compositing
     layer and buy nothing. */
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

/* ---------- hero ---------- */

.ball-hero {
  background: var(--night);
  /* A single soft light shaft from above, echoing the poster's stage lighting.
     Radial rather than linear so it reads as a source, not a gradient wash. */
  background-image: radial-gradient(
    120% 90% at 50% -10%,
    rgba(233, 210, 150, 0.16) 0%,
    rgba(233, 210, 150, 0.05) 38%,
    transparent 72%
  );
  color: var(--on-night);
  /* Clear the fixed nav, exactly as the shared .hero does. Previously this was a
     bare clamp: the transparent nav sat over the hero and hid the overlap. */
  padding: calc(var(--nav-h) + clamp(28px, 6vw, 72px)) 0 clamp(40px, 7vw, 76px);
  text-align: center;
}

.ball-hero .wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(20px, 3.4vw, 34px);
}

.ball-kicker {
  margin: 0;
  /* Sentence case, not caps. Set in small letterspaced capitals this read as a system
     label above the artwork; it is a sentence someone says about the evening, so it is
     set as one — and it can carry a little more size once it is no longer shouting. */
  font-size: clamp(0.95rem, 1.7vw, 1.15rem);
  letter-spacing: 0.01em;
  color: var(--on-night-soft);
  /* Light type on a dark ground reads lighter than it is; give it room. */
  line-height: 1.7;
  max-width: 60ch;
  text-wrap: balance;
}
.ball-kicker em {
  font-style: normal;
  color: var(--gold);
}

/* The lockup is the artwork from the printed advert, so someone holding the
   magazine recognises the page at a glance. It is the ONLY baked-in text on the
   page — the poster's other lines are ~5px tall on a phone and unreadable. */
.ball-hero h1 {
  margin: 0;
  width: 100%;
}
.ball-lockup {
  display: block;
  width: min(100%, 780px);
  height: auto;
  margin: 0 auto;
}

.ball-facts {
  display: grid;
  /* Three fixed columns, not auto-fit: auto-fit dropped to two at tablet width and
     orphaned "Tickets" onto a second row, left-aligned under "When". Three across,
     then a clean stack on phones — never 2+1. */
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(16px, 3vw, 40px);
  margin: 0;
  width: 100%;
  max-width: 780px;
  /* 3px, not a hairline. Raising the OPACITY (0.22 to 0.42) was the previous attempt and it
     did not land: on the hero's dark ground a 1px gold rule reads as an artefact of the
     screen rather than a drawn line, however bright it is. Width is what makes it read as
     deliberate. */
  border-top: 3px solid var(--night-line);
  border-bottom: 3px solid var(--night-line);
  padding: clamp(18px, 2.6vw, 26px) 0;
}
.ball-facts > div {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.ball-facts dt {
  font-size: 0.72rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--gold);
  font-weight: 600;
}
.ball-facts dd {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.ball-facts dd b {
  font-family: "Playfair Display", Georgia, serif;
  font-size: clamp(1.05rem, 2.2vw, 1.3rem);
  font-weight: 600;
  line-height: 1.25;
}
.ball-facts dd span {
  font-size: 0.88rem;
  color: var(--on-night-soft);
}
@media (max-width: 620px) {
  .ball-facts { grid-template-columns: 1fr; gap: 18px; text-align: center; }
}

/* TASK-335: the line-up, announced 31 August. Sits directly beneath the facts band and takes
   only a BOTTOM rule: the band above already closes with one, and a border on both would draw
   a double line a few pixels apart, which reads as a mistake rather than a frame. */
.ball-lineup {
  width: 100%;
  max-width: 780px;
  padding: clamp(14px, 2vw, 20px) 0;
  border-bottom: 3px solid var(--night-line);
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: center;
  text-align: center;
}
.ball-lineup-label {
  font-size: 0.72rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--gold);
  font-weight: 600;
}
.ball-lineup-acts {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: baseline;
  gap: 2px 16px;
}
.ball-lineup-acts b {
  font-family: "Playfair Display", Georgia, serif;
  font-size: clamp(1.02rem, 2.1vw, 1.24rem);
  font-weight: 600;
  line-height: 1.3;
}
/* A gold point between names rather than a comma: at this size a comma sitting on the baseline
   between two serif names reads as punctuation inside one of them. */
.ball-lineup-acts b:not(:first-child)::before {
  content: "·";
  color: var(--gold);
  padding-right: 16px;
  font-family: system-ui, sans-serif;
}
.ball-lineup-acts i {
  font-size: 0.8rem;
  font-style: italic;
  color: var(--on-night-soft);
}

/* The same names again in the "Who's playing" column, under Michelle's card. Stacked, not a
   second inline run: this column is prose, and a wrapping inline list beside a paragraph reads
   as a sentence that lost its verbs. */
.ball-acts {
  list-style: none;
  margin: 0 0 14px;
  padding: 0;
  display: flex;
  flex-direction: column;
}
.ball-acts li {
  font-family: "Playfair Display", Georgia, serif;
  font-size: 1.06rem;
  font-weight: 600;
  padding: 8px 0;
  border-bottom: 1px solid var(--line);
}
.ball-acts li:first-child { border-top: 1px solid var(--line); }

.ball-hero-cta {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  justify-content: center;
}

.btn-gold {
  background: var(--gold);
  color: #241B06;
  border: 1px solid var(--gold);
  font-weight: 600;
}
.btn-gold:hover {
  background: #F0DCA0;
  border-color: #F0DCA0;
}
.btn-ghost-gold {
  /* An outline button on a near-black ground needs more than a faint hairline or it reads as
     plain text. A gold border at full strength plus a barely-there fill gives it an edge and a
     surface, so it is obviously pressable next to the solid gold primary without competing
     with it. */
  background: rgba(233, 210, 150, 0.06);
  color: var(--on-night);
  border: 1px solid rgba(233, 210, 150, 0.55);
}
.btn-ghost-gold:hover {
  background: rgba(233, 210, 150, 0.13);
  border-color: var(--gold);
  color: var(--gold);
}

/* The sponsor's mark in the hero (TASK-330). They organise AND pay for the evening, and the
   credit at the foot of the page is a long way from where that fact is worth knowing. Sized
   well under the ball's own lockup above it: a guest's mark, given room, not given the stage.
   Same optical nudge as the standing credit. */
.ball-credit {
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: flex-start;
  margin: 0 0 4px;
}
.ball-credit-label {
  font-size: 0.7rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--on-night-soft);
}
.ball-credit a {
  display: block;
  /* TASK-335: 210 -> 315 (+50%). NBCC asked for every Designer Rooms mark on the site to carry
     more weight: they are paying for the whole evening, and the credit read as a footnote
     rather than a thank-you. */
  width: 500px;
  max-width: 100%;
}
.ball-credit img {
  display: block;
  width: 100%;
  height: auto;
  opacity: 0.92;
  transform: translateX(3.1%);
  transition: opacity 0.2s var(--ease-quart);
}
.ball-credit a:hover img {
  opacity: 1;
}

.ball-organiser {
  margin: 0;
  max-width: 62ch;
  font-size: 0.95rem;
  line-height: 1.7;
  color: var(--on-night-soft);
}
.ball-organiser b {
  /* Gold, not a brighter white. The two-step lift from --on-night-soft to --on-night looks
     like a colour that has gone wrong rather than a phrase being stressed; the page's own
     accent reads as deliberate, and it is the same gold used for every other emphasis here. */
  color: var(--gold);
  font-weight: 600;
}

/* Raised ordinal on the date. Sized and lifted by hand rather than left to the browser's
   default <sup>, which is set too high and too large and pushes the line box open — visible
   as an uneven gap above any line containing a date. */
.ord {
  font-size: 0.62em;
  vertical-align: baseline;
  position: relative;
  top: -0.42em;
  line-height: 0;
  letter-spacing: 0.02em;
}

/* The host, given a card of her own rather than a clause inside a paragraph about the line-up.
   A named host is a reason to buy a ticket; set as running text she read as a detail.
   
   It borrows the SHAPE of the practical-bits panel opposite (same radius, same padding rhythm)
   so the two read as a pair, but takes the page's night palette instead of the cream card. A
   dark block in a column of cream prose is the strongest pull available on this section, which
   is what a headline booking should have, and it echoes the hero and the sponsor band rather
   than introducing a new colour. */
.ball-host {
  margin: 0 0 20px;
  padding: clamp(18px, 3vw, 24px);
  background: var(--night);
  background-image: radial-gradient(
    120% 100% at 50% -20%,
    rgba(233, 210, 150, 0.18) 0%,
    rgba(233, 210, 150, 0.05) 45%,
    transparent 78%
  );
  border: 1px solid var(--night-line);
  border-radius: var(--radius);
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.ball-host-label {
  font-size: 0.72rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--on-night-soft);
}
.ball-host-name {
  font-family: "Playfair Display", Georgia, serif;
  font-weight: 600;
  font-size: clamp(1.6rem, 3.6vw, 2.3rem);
  line-height: 1.15;
  color: var(--gold);
}

/* ---------- the night ---------- */

.ball-split {
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
  gap: clamp(28px, 5vw, 64px);
  align-items: start;
}
@media (max-width: 860px) {
  .ball-split { grid-template-columns: 1fr; }
}

.ball-split .prose h3 {
  font-family: "Playfair Display", Georgia, serif;
  font-size: 1.25rem;
  margin: 28px 0 8px;
}
.ball-split .prose h3:first-child { margin-top: 0; }
.ball-split .prose p { max-width: 62ch; }

/* Honest placeholder for detail the venue has not confirmed. Muted, italic and
   clearly provisional, so the page never invents information about a £100
   ticket — and staff can replace it from the admin area once it is known. */
.ball-tbc {
  color: var(--slate-soft);
  font-style: italic;
}

.ball-details {
  list-style: none;
  margin: 0;
  padding: 0;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}
.ball-details li {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 8px 20px;
  padding: 16px 20px;
  border-bottom: 1px solid var(--line);
}
.ball-details li:last-child { border-bottom: 0; }
.ball-details .k {
  font-size: 0.74rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--slate-soft);
  font-weight: 600;
}
.ball-details .v {
  font-weight: 600;
  text-align: right;
}

/* ---------- ticket form ---------- */

.ball-availability {
  font-weight: 600;
  color: var(--crimson);
}

.ball-form {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: clamp(22px, 4vw, 40px);
  box-shadow: var(--shadow-sm);
  max-width: 760px;
  /* Centred under the centred .section-head. Without this the form sat flush left in
     the 1180px wrap while its heading sat mid-page, which read as a misaligned heading. */
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 26px;
}

.ball-choice {
  border: 0;
  padding: 0;
  margin: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 14px;
}
.ball-choice legend {
  /* Visually hidden: the two options say what they are, and a "What would you
     like?" heading above a two-card choice is the kind of restatement that adds
     reading without adding meaning. Kept for screen readers. */
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

.ball-option {
  position: relative;
  display: block;
  cursor: pointer;
}
.ball-option input {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
}
.ball-option-body {
  display: flex;
  flex-direction: column;
  gap: 5px;
  height: 100%;
  padding: 18px 20px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--white);
  transition: border-color 160ms cubic-bezier(0.2, 0.8, 0.3, 1),
              box-shadow 160ms cubic-bezier(0.2, 0.8, 0.3, 1);
}
.ball-option:hover .ball-option-body { border-color: var(--tan); }
.ball-option input:checked + .ball-option-body {
  border-color: var(--maroon);
  box-shadow: 0 0 0 1px var(--maroon);
}
.ball-option input:focus-visible + .ball-option-body {
  outline: 3px solid var(--crimson);
  outline-offset: 2px;
}
.ball-option-body b {
  font-family: "Playfair Display", Georgia, serif;
  font-size: 1.12rem;
}
.ball-price {
  font-weight: 600;
  color: var(--maroon);
}
.ball-note {
  font-size: 0.86rem;
  color: var(--slate-soft);
  line-height: 1.55;
}

.ball-row {
  display: flex;
  flex-wrap: wrap;
  gap: 18px 24px;
  /* flex-end suits the quantity row, where the running total should sit on the
     same baseline as the select. */
  align-items: flex-end;
}
/* The name/email row must NOT bottom-align: the email field carries a hint line
   underneath, so bottom-aligning drops the name label below the email label and
   the pair reads as a mistake. Top-align the labels instead. */
.ball-row-fields { align-items: flex-start; }
.ball-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  flex: 1 1 220px;
  min-width: 0;
}
.ball-field-narrow { flex: 0 1 200px; }
.ball-field > span {
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.04em;
}
.ball-field small {
  font-size: 0.8rem;
  color: var(--slate-soft);
  line-height: 1.5;
}
.ball-field input,
.ball-field select {
  font: inherit;
  padding: 11px 13px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--white);
  color: inherit;
  min-width: 0;
}
.ball-field input:focus-visible,
.ball-field select:focus-visible {
  outline: 3px solid var(--crimson);
  outline-offset: 1px;
  border-color: var(--maroon);
}

.ball-amount {
  display: flex;
  align-items: center;
  gap: 6px;
}
.ball-amount span {
  font-weight: 600;
  color: var(--slate-soft);
}
.ball-amount input { flex: 1 1 auto; }

.ball-total {
  margin: 0 0 4px;
  font-size: 0.95rem;
  color: var(--slate-soft);
}
.ball-total b {
  font-family: "Playfair Display", Georgia, serif;
  font-size: 1.6rem;
  color: var(--maroon);
  margin-left: 6px;
  font-variant-numeric: tabular-nums;
}

.ball-extras {
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding-top: 22px;
  border-top: 1px solid var(--line);
}
.ball-check {
  display: grid;
  grid-template-columns: 20px 1fr;
  gap: 12px;
  align-items: start;
  font-size: 0.92rem;
  line-height: 1.6;
  cursor: pointer;
}
.ball-check input {
  width: 20px;
  height: 20px;
  margin: 1px 0 0;
  accent-color: var(--maroon);
}
/* TASK-340: reported THREE times as invisible, and weight was never going to fix it.
   assets/fonts/poppins-400.woff2 is a STATIC 400-weight file, but styles.css declares it as
   `font-weight: 400 600` — telling the browser that one file covers the whole 400-600 range. So
   every weight in that range renders at 400 and no synthetic bold is applied, because the
   browser has been told the face already provides it. Sixty-six rules across the site ask for
   500 or 600 from this font; none of them do anything. Fixing the declaration is the real
   answer, but it changes every page at once and is not a thing to ship unreviewed days before
   a launch — so it is written up for NBCC to decide on.

   Here, the emphasis has to actually work. Colour is not subject to any of the above: the four
   phrases are the scannable summary of what each box does.

   --gold-deep, NOT --maroon. Maroon is the site's link colour, and the terms box carries a real
   link ("ticket terms") a few words away — emphasis in the same colour would read as a second
   link that does not go anywhere. --gold-deep is this page's own accent, defined at the top of
   this file as the accessible foreground for small text on cream, which is exactly this. The
   weight stays at 700 so these become properly bold the moment the font declaration is fixed. */
.ball-check b {
  font-weight: 700;
  color: var(--gold-deep);
}
/* The per-ticket breakdown under the fee checkbox. A block so it starts its own line under
   the sentence rather than trailing it, and quieter, because it is an aside not a term. */
.ball-check small {
  display: block;
  margin-top: 4px;
  font-size: 0.86rem;
  color: var(--slate-soft);
}

.ball-donation {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.ball-donation-fields {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 16px 18px;
  background: var(--tint);
  border-radius: var(--radius);
}
.ball-donation-fields[hidden] { display: none; }

.ball-error {
  margin: 0;
  padding: 12px 16px;
  border: 1px solid var(--crimson);
  border-radius: 10px;
  background: #FCF0F1;
  color: #8A1626;
  font-size: 0.92rem;
}
.ball-error[hidden] { display: none; }

.ball-submit { align-self: flex-start; }
.ball-submit[disabled] { opacity: 0.6; cursor: progress; }

/* ---- the guest form (TASK-409) --------------------------------------------------------
   Everything below styles /ball/guests, which had no rules of its own at all: the fieldsets
   rendered with the browser's default inset border on a cream page, which is not a look the
   rest of the site has anywhere.

   The hints are the point of the pass. They used to be `placeholder` attributes, which are
   low contrast by design and thrown away the moment somebody types. They are now labels in
   the ORDINARY body colour rather than the muted one this stylesheet uses elsewhere for
   small print, because a hint on a question about a disability is content, not decoration.

   Scoped under .ball-field on purpose: `.ball-field small` is (0,1,1) and a bare .ball-hint
   is (0,1,0), so the muted colour would win and the hint would render in exactly the grey
   this pass exists to get rid of. Two classes (0,2,0) takes it back. */
.ball-field .ball-hint {
  font-size: 0.86rem;
  line-height: 1.5;
  color: var(--slate);
  font-weight: 400;
  letter-spacing: normal;
}

.ball-guest-form { gap: 22px; }

.ball-guest {
  margin: 0;
  padding: clamp(16px, 2.4vw, 22px);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
  display: flex;
  flex-direction: column;
  gap: 16px;
}
/* .ball-field carries `flex: 1 1 220px`, which is a WIDTH everywhere it was designed to be
   used: inside .ball-row, a row-direction container. Dropped straight into this column, the
   same 220px becomes a flex-basis on the vertical axis and every field grows a ~220px tall
   box with the input stranded at the top of it. Reset the basis for fields that are direct
   children of a column; the ones nested in a .ball-row keep the original rule and stay side
   by side. */
.ball-guest > .ball-field,
.ball-guest-form > .ball-field {
  flex: 0 0 auto;
}
.ball-guest > legend {
  padding: 0 10px;
  font-family: var(--font-head);
  font-size: 1.02rem;
  font-weight: 600;
  color: var(--maroon);
}

/* The group name sits above the guests and is the one field that decides where a whole party
   sits, so it takes the gold edge that marks the other "this one matters" blocks. */
.ball-group-field {
  padding: clamp(16px, 2.4vw, 20px);
  background: var(--card);
  border: 1px solid var(--line);
  border-left: 3px solid var(--gold-deep);
  border-radius: var(--radius);
}

.ball-guest-clear { margin: 0; }
.ball-guest-clear[hidden] { display: none; }
.btn-small {
  padding: 7px 14px;
  font-size: 0.84rem;
}

.ball-progress {
  margin: 0 0 20px;
  line-height: 1.7;
  max-width: 62ch;
}
.ball-saved {
  margin: 0 0 20px;
  padding: 12px 16px;
  border: 1px solid var(--gold-deep);
  border-radius: 10px;
  background: #FBF6EA;
  font-size: 0.94rem;
}
.ball-privacy {
  padding: clamp(16px, 2.4vw, 20px);
  border-radius: var(--radius);
  background: var(--tint);
  border: 1px solid var(--line);
}
.ball-privacy h2 {
  margin: 0 0 8px;
  font-size: 1rem;
  color: var(--maroon);
}
.ball-privacy p {
  margin: 0 0 8px;
  font-size: 0.9rem;
  line-height: 1.65;
  color: var(--slate-soft);
}
.ball-privacy p:last-child { margin-bottom: 0; }

.ball-smallprint {
  margin: 0;
  font-size: 0.84rem;
  line-height: 1.65;
  color: var(--slate-soft);
  max-width: 62ch;
}

/* The invoice / larger-booking route. Sized and centred to the form so it reads as the
   next thing on the same track, and given the gold edge so it is visibly an offer rather
   than more terms. Deep gold, not the bright one: this sits on cream. */
.ball-invoice {
  max-width: 760px;
  margin: 20px auto 0;
  padding: clamp(20px, 3vw, 28px);
  background: var(--card);
  border: 1px solid var(--line);
  border-left: 3px solid var(--gold-deep);
  border-radius: var(--radius-lg);
}
.ball-invoice h3 {
  margin: 0 0 10px;
  font-size: 1.06rem;
}
.ball-invoice p {
  margin: 0;
  font-size: 0.95rem;
  line-height: 1.7;
  color: var(--slate-soft);
}
.ball-invoice-contact {
  margin-top: 14px;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 10px;
}
.ball-invoice-contact a {
  font-weight: 600;
  color: var(--gold-deep);
}

/* Links on the night-sky bands. The shared crimson link colour is ~2:1 against #0B1020 and
   effectively invisible there — and the first thing we put on that ground is the sponsor
   credit we had just promised to link properly. Gold is the page's accent and reads. */
.ball-hero a:not(.btn),
.ball-sponsor a,
.ball-organiser a {
  color: var(--gold);
  text-decoration-color: rgba(228, 200, 122, 0.45);
  text-underline-offset: 3px;
}
.ball-hero a:not(.btn):hover,
.ball-sponsor a:hover,
.ball-organiser a:hover {
  color: #F0DCA0;
  text-decoration-color: currentColor;
}
/* The credit is now their own wordmark, so there is no text to underline. The supplied file
   is black line art on solid white with no transparency, which would have shown as a white
   slab on this band; assets/img/the-designer-rooms-cream.png is the same artwork with the
   paper knocked out and the strokes painted cream. See the README for how it was derived. */
.ball-sponsor-name {
  margin: 0 0 18px;
}
/* TASK-334: block with a DEFINITE width, centred by auto margins — the same shape .ball-credit
   above already uses, and for the same reason.

   As an inline-block wrapping a `width: min(100%, 320px)` image, this anchor shrank to fit the
   image's INTRINSIC 486px (percentages count as auto while a shrink-to-fit width is being
   resolved, so the cap could not participate). The image then painted at 320px, flush left
   inside a 486px box, leaving it ~83px left of centre in a band that centres everything else.
   Read as "the logo is not centred", which is exactly what it was. */
.ball-sponsor-name a {
  display: block;
  /* 320 -> 480 (+50%), see .ball-credit above. */
  width: 500px;
  max-width: 100%;
  margin-inline: auto;
  text-decoration: none;
}
.ball-sponsor-name img {
  display: block;
  width: 100%;
  height: auto;
  /* Optically centred, not geometrically. The swoosh trails right with almost no ink in it,
     so centring the BOX leaves the lettering visibly left of centre — measured at 15px of 486,
     or 3.1% of the width. A percentage transform holds that correction at any size, and
     transform does not disturb the layout around it. */
  transform: translateX(3.1%);
  /* It is a guest's mark on our page: present and legible, never louder than the ball's own
     lockup a few sections above. */
  opacity: 0.92;
  transition: opacity 0.2s var(--ease-quart);
}
.ball-sponsor-name a:hover img {
  opacity: 1;
}

/* The sponsor credit on the thank-you page. Quiet: the buyer has just paid and the page's job
   is to reassure them, so this sits at the foot as a thank-you rather than an advert. Dark
   version of the wordmark, because this page is cream. */
/* TASK-335: the thank-you page now ends on the ball page's own .ball-sponsor band, so the
   cream-ground .ball-ty-sponsor block it used to carry is gone rather than left to rot.

   What remains here is only what the confirmation needs on top of the shared hero: a smaller
   lockup, and a real heading under it. The ball page's lockup IS its headline at 780px; here
   the headline is the sentence telling someone their seat is booked, so the artwork sits back
   and lets it lead. */
/* The confirmation's booking details carry .ball-facts for its SHAPE - three columns, rules
   above and below - but they sit on the cream section, not the night hero. Until now the
   modifier existed in the markup with no rule behind it, so the band arrived wearing the hero's
   palette on cream: gold labels bleaching out, and rules at 42% gold that were effectively
   invisible. It reads as the one part of the page nobody styled, which is exactly what it was.

   Colours only. The geometry is inherited, so the two bands stay the same object. */
.ball-facts-light {
  border-top-color: var(--line);
  border-bottom-color: var(--line);
}
.ball-facts-light dt {
  color: var(--crimson);
}
.ball-facts-light dd b {
  color: var(--slate);
}
.ball-facts-light dd span {
  color: var(--slate-soft);
}

/* TASK-337: the lockup steps back so the sentence can lead. On the ball page the artwork IS the
   headline; here the headline is the line telling someone their booking worked, and two display
   elements at the same size means neither reads first. */
.ball-ty-lockup {
  width: min(100%, 380px);
}
.ball-ty-title {
  font-family: "Playfair Display", Georgia, serif;
  /* Display scale, fluid. This is the one thing on the page a buyer needs to read at a glance,
     and at 2.15rem it was barely larger than the body copy beneath it. */
  font-size: clamp(2.2rem, 6.5vw, 4rem);
  font-weight: 600;
  /* Tighter as it grows: display type left at body line-height drifts apart. */
  line-height: 1.08;
  letter-spacing: -0.015em;
  margin: 0;
  color: var(--on-night);
  /* Held to a readable measure so it breaks into two or three shapely lines rather than one
     very long one on a wide screen. */
  max-width: 16ch;
  text-wrap: balance;
}
.ball-ty-lede {
  margin: 0;
  font-size: 1.02rem;
  color: var(--on-night-soft);
}

/* The booking section needs to look like somewhere you have ARRIVED, not more of the page.
   The shared .tint is a gradient from #F8F5EE to #F3EEE3 — five units of difference, which is
   invisible on most screens and was invisible here. A flat, deeper ground plus a defined top
   edge makes the section start somewhere. */
.ball-page .section.tint {
  background: #EFE7D8;
  border-top: 1px solid rgba(138, 106, 38, 0.16);
  border-bottom: 1px solid rgba(138, 106, 38, 0.16);
}

/* ---------- who you're supporting ---------- */

.ball-impact {
  font-family: "Playfair Display", Georgia, serif;
  font-size: 1.2rem;
  line-height: 1.5;
  color: var(--maroon);
  max-width: 34ch;
}

/* ---------- practical ---------- */

/* ---------- practical ----------

   Rebuilt (was six equal tiles in a 3x2 grid, which read as flat and unfinished at
   full width). The content is not six equal things: ONE of them is the exciting bit
   and five are logistics, so the layout says so.

   A 1.15fr / 1fr split puts a dark feature panel against a quiet 2x2 of admin. The
   dark panel is the same night navy as the hero and the sponsor band, so this reads
   as the page's third dark beat rather than a stray coloured box, and it breaks up a
   long run of cream. Scale contrast does the rest: the feature heading is roughly
   twice the tile headings, not a timid step up. */

.ball-practical {
  display: grid;
  grid-template-columns: 1.15fr 1fr;
  gap: clamp(18px, 2.4vw, 30px);
  align-items: stretch;
}
@media (max-width: 900px) { .ball-practical { grid-template-columns: 1fr; } }

/* --- the feature --- */

.ball-feature {
  position: relative;
  overflow: hidden;
  background: var(--night);
  /* Same light shaft as the hero, angled from the top-left here so the two dark
     panels feel related without being a copy. */
  background-image: radial-gradient(
    110% 80% at 12% -10%,
    rgba(233, 210, 150, 0.18) 0%,
    rgba(233, 210, 150, 0.05) 40%,
    transparent 74%
  );
  color: var(--on-night);
  border-radius: var(--radius-lg);
  padding: clamp(28px, 3.6vw, 52px);
  display: flex;
  flex-direction: column;
  gap: 14px;
  justify-content: center;
}

.ball-feature-eyebrow {
  font-size: 0.72rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--gold);
  display: flex;
  align-items: center;
  gap: 12px;
}
/* A short gold rule leading out of the eyebrow — an invitation-card device rather
   than a decorative flourish, and it points the eye into the headline. */
.ball-feature-eyebrow::after {
  content: "";
  height: 1px;
  flex: 1 1 auto;
  background: linear-gradient(to right, var(--gold), transparent);
  opacity: 0.7;
}

.ball-feature h3 {
  font-family: "Playfair Display", Georgia, serif;
  /* The shared stylesheet colours headings crimson, which is right on cream and
     nearly invisible on this panel (3.18:1). Inherit the panel's own foreground. */
  color: var(--on-night);
  font-weight: 600;
  font-size: clamp(1.45rem, 2.9vw, 2.1rem);
  line-height: 1.16;
  letter-spacing: -0.01em;
  margin: 0;
  text-wrap: balance;
  max-width: 20ch;
}
.ball-feature p {
  margin: 0;
  font-size: 1rem;
  /* Light type on dark reads lighter than it is, so give it more leading than the
     cream tiles get. */
  line-height: 1.75;
  color: var(--on-night-soft);
  max-width: 46ch;
}
.ball-feature-note {
  font-size: 0.86rem !important;
  color: rgba(242, 238, 228, 0.56) !important;
}

/* --- the quiet four --- */

.ball-tiles {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: clamp(14px, 1.6vw, 20px);
}
@media (max-width: 560px) { .ball-tiles { grid-template-columns: 1fr; } }

.ball-tiles li {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: clamp(18px, 2vw, 24px);
  display: flex;
  flex-direction: column;
  gap: 7px;
  transition: transform 200ms var(--ease-quart), border-color 200ms var(--ease-quart),
              box-shadow 200ms var(--ease-quart);
}
.ball-tiles li:hover {
  transform: translateY(-3px);
  border-color: var(--tan);
  box-shadow: var(--shadow-sm);
}
.ball-tiles h3 {
  font-family: "Playfair Display", Georgia, serif;
  font-size: 1.05rem;
  margin: 0;
  color: var(--maroon);
}
.ball-tiles p {
  margin: 0;
  font-size: 0.9rem;
  line-height: 1.6;
  color: var(--slate-soft);
}

/* --- the closing ask --- */

.ball-ask {
  margin-top: clamp(22px, 2.6vw, 32px);
  padding-top: clamp(20px, 2.4vw, 28px);
  border-top: 1px solid var(--line);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px 28px;
}
.ball-ask-lead {
  margin: 0;
  font-family: "Playfair Display", Georgia, serif;
  font-size: clamp(1.05rem, 2vw, 1.3rem);
  color: var(--slate);
}
.ball-ask-links {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px 20px;
  font-size: 0.95rem;
}
.ball-ask-links a { font-weight: 600; color: var(--crimson); }
.ball-ask-links span { color: var(--slate-soft); font-size: 0.86rem; }

/* ---------- sponsor ---------- */

.ball-sponsor {
  background: var(--night);
  color: var(--on-night);
  padding: clamp(40px, 6vw, 72px) 0;
  text-align: center;
}
.ball-sponsor .wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
}
.ball-sponsor-name {
  margin: 0;
  font-family: "Playfair Display", Georgia, serif;
  font-size: clamp(1.6rem, 4.2vw, 2.4rem);
  letter-spacing: 0.04em;
  color: var(--gold);
}
.ball-sponsor-note {
  margin: 0;
  max-width: 58ch;
  font-size: 0.95rem;
  line-height: 1.75;
  color: var(--on-night-soft);
}

/* ---------- motion ----------

   One orchestrated moment: the hero assembles on load, top to bottom, rather than
   five things animating independently. Transform and opacity only, so it composites
   on the GPU and never triggers layout. */

@keyframes ball-rise {
  from { opacity: 0; transform: translate3d(0, 20px, 0); }
  to   { opacity: 1; transform: none; }
}

/* The lockup gets its own entrance — a touch of scale, so the artwork settles into
   place rather than sliding like body copy. It is the thing people came for. */
@keyframes ball-lockup-in {
  from { opacity: 0; transform: scale(0.94); }
  to   { opacity: 1; transform: none; }
}

.ball-hero .wrap > * {
  /* Slower than a normal UI transition on purpose: a quick fade reads as an interface
     responding, a long one as a curtain going up, and the second is what this page is for.
     
     LONG DURATIONS, SHORT DELAYS. These are not the same lever and only one of them is free.
     Stretching the delays too is the obvious way to make a hero feel slower, and it was how
     this was first written -- but `both` holds an element invisible until its delay elapses,
     so the stagger was keeping "Book tickets" off the screen for over a second. Someone
     arriving from the printed advert's QR code was waiting on a decoration.
     
     So the fades are longer than before and the delays are SHORTER: everything is on screen
     and readable inside ~0.6s, and then takes its time settling. Slow to watch, never slow to
     use. */
  animation: ball-rise 1.7s var(--ease-quart) both;
}
.ball-hero .wrap > :nth-child(1) { animation-delay: 0.06s; }
.ball-hero .wrap > :nth-child(2) { animation: ball-lockup-in 2.2s var(--ease-expo) 0.14s both; }
.ball-hero .wrap > :nth-child(3) { animation-delay: 0.34s; }
.ball-hero .wrap > :nth-child(4) { animation-delay: 0.5s; }
.ball-hero .wrap > :nth-child(5) { animation-delay: 0.62s; }
/* TASK-335: the line-up inserted a child at position 4, pushing the CTA to 5 and the sponsor
   credit to 6. Without these the credit and the organiser line fall off the end of the stagger
   and animate at zero delay - arriving BEFORE the buttons above them, which reads as the page
   assembling itself backwards. */
.ball-hero .wrap > :nth-child(6) { animation-delay: 0.74s; }
.ball-hero .wrap > :nth-child(7) { animation-delay: 0.86s; }

/* The scroll reveals are the shared site transition (.reveal, 0.65s), slowed here to sit with
   the hero's longer entrance so the whole page moves at one pace.
   
   These have a lower ceiling than the hero does, and it is a usability ceiling rather than a
   taste one: the hero plays once while somebody is still arriving, but a reveal fires every
   time they scroll to something they have decided to read. Past roughly a second and a half it
   stops feeling composed and starts feeling like the page is struggling to keep up. The
   opacity carries most of the perceived slowness; the transform finishes sooner so the text
   has stopped moving well before it has finished brightening, which is far easier to read
   during. */
.ball-page .reveal {
  transition:
    opacity 1.45s var(--ease-quart) var(--d, 0ms),
    transform 1.05s var(--ease-quart) var(--d, 0ms);
}

/* Snow sits behind the hero content and never intercepts a click. */
.ball-snow {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 0;
}
.ball-hero { position: relative; overflow: hidden; }
.ball-hero .wrap { position: relative; z-index: 1; }

/* Feedback: the option cards lift a little under the pointer. 1.5% is enough to
   register as responsive without the card appearing to jump. */
.ball-option-body {
  transition:
    border-color 180ms var(--ease-quart),
    box-shadow 180ms var(--ease-quart),
    transform 180ms var(--ease-quart);
}
.ball-option:hover .ball-option-body { transform: translateY(-2px); }
.ball-option input:checked + .ball-option-body { transform: translateY(-2px); }

.ball-submit,
.btn-gold,
.btn-ghost-gold {
  transition: transform 180ms var(--ease-quart), background 200ms var(--ease-quart),
              border-color 200ms var(--ease-quart), color 200ms var(--ease-quart);
}
.ball-submit:hover:not([disabled]),
.btn-gold:hover { transform: translateY(-2px); }

/* The donation panel opens by animating grid rows rather than height, so there is
   no fixed max-height guess to get wrong when the copy changes. */
.ball-donation-fields {
  display: grid;
  grid-template-rows: 1fr;
  transition: grid-template-rows 320ms var(--ease-quart), opacity 240ms var(--ease-quart);
  opacity: 1;
}
.ball-donation-fields > * { min-height: 0; }
.ball-donation-fields[hidden] {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  padding-top: 0;
  padding-bottom: 0;
  overflow: hidden;
}

/* A brief pulse when the running total changes, so the number acknowledges the
   change instead of silently swapping. */
@keyframes ball-total-pulse {
  0%   { transform: none; }
  35%  { transform: scale(1.06); }
  100% { transform: none; }
}
.ball-total b {
  display: inline-block;
  transform-origin: left center;
}
.ball-total b.is-changed { animation: ball-total-pulse 420ms var(--ease-quart); }

@media (prefers-reduced-motion: reduce) {
  .ball-hero .wrap > *,
  .ball-total b.is-changed { animation: none !important; }
  .ball-option-body,
  .ball-submit,
  .btn-gold,
  .btn-ghost-gold,
  .ball-donation-fields { transition: none; }
  .ball-option:hover .ball-option-body,
  .ball-option input:checked + .ball-option-body,
  .ball-submit:hover:not([disabled]),
  .btn-gold:hover,
  .ball-tiles li:hover { transform: none; }
  .ball-tiles li { transition: none; }
  .ball-snow { display: none; }
}

/* TASK-347: the statements Appendix A of the fundraising agreement requires verbatim.
   Deliberately quiet, and deliberately NOT hidden: this is a required disclosure, so it is set
   as a legal note - smaller, softer, still comfortably readable - rather than either competing
   with the copy around it or shrinking to the size people stop reading. */
.ball-sponsor-statement {
  margin: 0 0 14px;
  max-width: 62ch;
  font-size: 0.86rem;
  line-height: 1.7;
  color: var(--on-night-soft);
}
.ball-buy-statement {
  margin: 0 0 12px;
  font-size: 0.82rem;
  line-height: 1.65;
  color: var(--slate-soft);
}
.ball-buy-statement a {
  color: var(--maroon);
}
