/* Base structural utilities */
* { box-sizing: border-box; }
img { max-width: 100%; height: auto; }
.container { max-width: var(--container-width); margin: 0 auto; padding: 0 2rem; }

/* Header & Nav */
.site-header {
  background-color: var(--color-bg-dark);
  color: var(--color-white);
  position: sticky;
  top: 0;
  z-index: 1000;
  box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.header-inner {
  display: flex;
  justify-content: space-between;
  align-items: center;
  max-width: var(--container-width);
  margin: 0 auto;
  padding: 1rem 2rem;
}
.site-logo { max-height: 60px; }

.site-nav ul { list-style: none; margin: 0; padding: 0; }
.nav-level-1 { display: flex; gap: 2rem; align-items: center; }
.nav-level-1 > li > a, .nav-level-1 > li > span {
  color: var(--color-white);
  font-family: var(--font-heading);
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  padding: 0.5rem 0;
  text-transform: uppercase;
  font-size: 0.9rem;
}
.nav-level-1 > li > a:hover, .nav-level-1 > li > span:hover {
  color: var(--color-primary);
}

/* Dropdown */
.has-dropdown { position: relative; }
.nav-level-2 {
  position: absolute;
  top: 100%;
  left: 0;
  background-color: var(--color-bg-dark);
  min-width: 200px;
  opacity: 0;
  visibility: hidden;
  transform: translateY(10px);
  transition: all 0.2s ease;
  box-shadow: 0 8px 16px rgba(0,0,0,0.2);
  border-top: 3px solid var(--color-primary);
}
.has-dropdown:hover .nav-level-2 {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}
.nav-level-2 li a {
  display: block;
  padding: 0.75rem 1rem;
  color: var(--color-white);
  text-decoration: none;
  border-bottom: 1px solid #333;
}
.nav-level-2 li a:hover {
  background-color: var(--color-primary);
  color: var(--color-white);
}

.site-phone {
  background-color: var(--color-primary);
  color: var(--color-white);
  padding: 0.75rem 1.5rem;
  border-radius: 4px;
  font-weight: 700;
  text-decoration: none;
  font-family: var(--font-heading);
  transition: background 0.2s;
}
.site-phone:hover {
  background-color: var(--color-primary-dark);
}

/* Typography & Links */
a { color: var(--color-primary); }
a:hover { text-decoration: underline; }
.eyebrow {
  color: var(--color-primary);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: 1rem;
  display: block;
  margin-bottom: 0.5rem;
}

/* Buttons */
.cta-group { margin-top: 2rem; display: flex; gap: 1rem; flex-wrap: wrap; }
.btn-primary {
  background: var(--color-primary);
  color: var(--color-white);
  padding: 1rem 2.5rem;
  text-decoration: none;
  font-weight: 700;
  border-radius: 4px;
  text-transform: uppercase;
  display: inline-block;
  transition: background 0.2s;
}
.btn-primary:hover { background: var(--color-primary-dark); text-decoration: none; }

.btn-secondary {
  color: var(--color-primary);
  border: 2px solid var(--color-primary);
  padding: 1rem 2.5rem;
  text-decoration: none;
  font-weight: 700;
  border-radius: 4px;
  text-transform: uppercase;
  display: inline-block;
  transition: all 0.2s;
}
.btn-secondary:hover { background: var(--color-primary); color: var(--color-white); text-decoration: none; }

/* Sections */
.section { padding: 4rem 0; }
/* The hero is defined once, in the TREATMENT A block at the foot of this
   file. The pre-gate hero rules that stood here are gone rather than left to
   be overridden: two definitions of the same component is how a page ends up
   depending on source order nobody can see. */

.two-column {
  display: flex;
  gap: 4rem;
  align-items: center;
}
.two-column > div { flex: 1; }
.two-column img { border: 2px solid var(--color-primary); width: 100%; object-fit: cover; }
@media (max-width: 768px) {
  .two-column { flex-direction: column; }
}

.qa-section {
  max-width: 1000px;
  margin: 0 auto;
  padding: 3rem 0;
  border-top: 2px solid var(--color-primary);
}
.qa-section h2 { font-size: 2rem; margin-top: 0; color: var(--color-primary); font-weight: 600; text-align: center; }
.qa-section p { text-align: center; max-width: 800px; margin: 0 auto 1.5rem; }

/* Footer */
.site-footer {
  background-color: #000000;
  color: #ffffff;
  padding: 4rem 0 2rem;
  font-family: var(--font-heading);
}
.footer-container {
  max-width: var(--container-width);
  margin: 0 auto;
  padding: 0 2rem;
  display: grid;
  grid-template-columns: 1fr 1fr 1.2fr;
  gap: 3rem;
}
@media (max-width: 900px) {
  .footer-container { grid-template-columns: 1fr; text-align: center; }
  .footer-col.text-center { text-align: center; }
}
/* The footer columns are headed with <h2>, and only h3 was ever given a
   colour here -- so every column heading fell through to the global purple
   and sat at 3.14 on black, on every page of the site. */
.footer-col h2,
.footer-col h3 { color: #ffffff; font-size: 1.5rem; margin-top: 0; margin-bottom: 1.5rem; font-weight: 600; }
.footer-col.text-center { text-align: center; }
/* The email and any other prose link in a footer column fell through to the
   global brand purple: 3.14 on black, on every page. The nav rule below is
   equal specificity and comes later, so footer nav links stay white. */
.footer-col a { color: var(--color-link-on-dark); }
.footer-nav ul { list-style: none; padding: 0; margin: 0; }
.footer-nav li { margin-bottom: 0.8rem; }
.footer-nav a { 
  color: #ffffff; 
  text-decoration: none; 
  text-transform: uppercase; 
  font-size: 0.95rem; 
  font-weight: 700;
  display: block;
}
.footer-nav a:hover { color: var(--color-primary); }
.footer-nav li:first-child a {
  background-color: var(--color-primary);
  display: inline-block;
  padding: 0.5rem 1rem;
  margin-left: -1rem;
}
.footer-bottom {
  text-align: center;
  margin-top: 4rem;
  padding-top: 2rem;
  border-top: 1px solid #333;
  font-size: 0.85rem;
  font-weight: 600;
}
.footer-bottom p { margin: 0.5rem 0; }
/* The brand purple is a 3.14 on black. --color-link-on-dark is the same hue
   lightened until it clears AA (6.5), so the footer email and credit links
   are readable without ceasing to look like the brand. Two links on every
   page of the site were failing here: 463 of the run's contrast failures. */
.footer-bottom a { color: var(--color-link-on-dark); text-decoration: underline; }
.footer-bottom a:hover { color: #ffffff; }

.skip-link { position: absolute; top: -100px; left: 0; padding: 1rem; background: var(--color-primary); color: #fff; z-index: 9999; } .skip-link:focus { top: 0; }

.band-white { background-color: #ffffff; color: #111111; }
.band-white h2, .band-white h3, .band-white p { color: #111111; }
/* :not(.btn) matters. A band's link colour is more specific than
   .btn-primary, so without it the band repaints the button's label in the
   button's own background colour -- purple on purple, a 1.00, on every
   CTA outside the hero. A button is not body text and is coloured as a
   button. */
.band-white a:not(.btn) { color: var(--color-primary); }

/* Inside a purple band every piece of type is white, and a link is
   underlined so it is still a link once colour stops distinguishing it.
   The selectors name the elements one level down (.band-purple .card a and
   friends) because the brand purple on the band purple measures 1.14 -- a
   link nobody can read. No !important anywhere: the specificity is the
   argument. */
.band-purple { background-color: #79588a; color: #ffffff; }
.band-purple h2,
.band-purple h3,
.band-purple h4,
.band-purple p,
.band-purple li,
.band-purple strong,
.band-purple blockquote { color: #ffffff; }
.band-purple a:not(.btn),
.band-purple p a:not(.btn),
.band-purple li a:not(.btn),
.band-purple .card a:not(.btn),
.band-purple blockquote a:not(.btn) {
  color: #ffffff;
  text-decoration: underline;
}
.band-purple a:not(.btn):hover { color: #ffffff; text-decoration: none; }
/* A secondary button on purple would otherwise be purple-on-purple. */
.band-purple .btn-secondary { color: #ffffff; border-color: #ffffff; }
.band-purple .btn-secondary:hover { background: #ffffff; color: #79588a; }

/* ---------------------------------------------------------------------------
   Components reached by a copy contract token.

   The compiler emits these classes and nothing else -- no inline styles --
   so every presentational decision about a card, a map, a video or a
   pending note is made once, here, and applies everywhere that token is
   used. That is the whole point of the token: one component, one look.
   --------------------------------------------------------------------------- */

.breadcrumb-bar { padding: 1rem 2rem 0; }

/* Cards. Varied widths come from the grid, not from per-card styling
   (craft rule 7: three identical tiles is the generated-look tell). */
.card {
  padding: 1.5rem;
  border: 1px solid #ddd;
  border-radius: 8px;
  margin: 1rem 0;
  background-color: inherit;
}
.band-purple .card { border-color: rgba(255,255,255,0.45); }

.cta-link { align-self: center; font-weight: 600; }

/* Media band: the rhythm break. Two columns on desktop, one on a phone. */
.media-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 2rem;
  align-items: start;
}
.media-figure img, .media-figure { width: 100%; border-radius: 8px; display: block; }

/* Click-to-load video and map. Nothing is requested from a third party
   until the reader asks for it. */
/* F1, second half. The poster variant collapsed vertically; this one
   collapsed HORIZONTALLY, and for the same reason. The contact page puts a
   video in a grid column declared minmax(0, 1fr), so the column's width came
   from its content -- the play button's label. Swap the button for an iframe,
   which has no intrinsic width, and the column shrinks to nothing: the
   measured iframe was 6px by 400px.

   One rule fixes both: the facade owns its own box, 16:9, whatever is
   inside it at the time. That is the shape of the thing that will fill it. */
.video-embed {
  position: relative;
  aspect-ratio: 16 / 9;
  min-width: 12rem;
  border: 1px solid #ddd;
  border-radius: 8px;
  margin: 1rem 0;
  overflow: hidden;
  background: #2b1636;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
}
.map-facade {
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 1.5rem;
  margin: 1rem 0;
  text-align: center;
}
.video-embed-play, .map-facade-load {
  font: inherit;
  font-weight: 700;
  cursor: pointer;
  padding: 0.8rem 1.5rem;
  border: 0;
  border-radius: 4px;
  background: var(--color-primary);
  color: var(--color-white);
}
.video-embed-play:hover, .map-facade-load:hover { background: var(--color-primary-dark); }
/* The iframe fills the box the facade already reserved, rather than
   trying to establish one of its own. */
.video-embed iframe { position: absolute; inset: 0; width: 100%; height: 100%;
                      border: 0; display: block; }
.map-facade iframe { width: 100%; height: 400px; border: 0; display: block; }
.map-facade-address { margin: 1rem 0 0; }

/* The staging-only note standing where a client answer is still missing.
   Marked as clearly not copy, and never rendered in production.
   Selected as p.pending-note so it keeps its own colours inside a band --
   .band-purple p would otherwise repaint it white on its pale ground. */
p.pending-note {
  display: inline-block;
  padding: 0.4rem 0.8rem;
  border: 1px dashed #8a6d00;
  border-radius: 4px;
  background: #fff8e1;
  color: #5c4800;
  font-size: 0.9rem;
  font-weight: 700;
}

/* Lists built from data/ */
.recipe-index { column-count: 2; column-gap: 2rem; }
@media (max-width: 700px) { .recipe-index { column-count: 1; } }
.recipe-index-letter { margin-top: 0; }
.recipe-index-list { list-style: none; padding: 0; margin-bottom: 2rem; }
.recipe-index-list li { margin-bottom: 0.5rem; }

/* --- A RECIPE PAGE: the look of Dale's reference (ptkc-walk-5 V5) ---------
   Sampled from recipe-style-reference.jpg: the content area #eecee5; the
   title bold sans-serif in #401878; the section headings bold Georgia and the
   body Georgia, both #181818. The recipe's first photo (moved to the top of
   the text by tools/restyle-recipes.py) sits top right at about 45% of the
   column, never wider than its own pixels, the text running beside and under
   it; under 640px it stands above the text, as wide as the column allows and
   never enlarged. The recipe's own dashes, numbers and line breaks stay as
   they are: nothing here turns them into lists. The recipes hub is not a
   recipe page and keeps its own look. Contrast on #eecee5: title and links
   9.0:1, headings and body 12.4:1. */
.recipe-look { background-color: #eecee5; }
.recipe-look .recipe-container { max-width: 800px; margin: 2rem auto; line-height: 1.6; }
.recipe-look .recipe-text {
  font-family: Georgia, "Times New Roman", serif;
  font-size: 1.1rem;
  color: #181818;
}
.recipe-look .recipe-text::after { content: ""; display: block; clear: both; }
/* The reference sets a recipe's lines close together; the old pages give
   each ingredient and step its own <p>, so the paragraph gap is a line's. */
.recipe-look .recipe-text p { margin: 0 0 0.35em; }
/* The title: the recipe's own <h1>. The one page whose old title lost its
   opening tag (<p>Hamburger Pizza! ...</h1>) has no <h1>, and its first line
   is that title. */
.recipe-look .recipe-text h1,
.recipe-look .recipe-text:not(:has(h1)) > p:first-child,
.recipe-look .recipe-text:not(:has(h1)) > .recipe-photo:first-child + p {
  font-family: Arial, Helvetica, sans-serif;
  font-weight: 700;
  font-size: clamp(1.75rem, 4vw, 2.25rem);
  line-height: 1.2;
  color: #401878;
  text-transform: none;
  letter-spacing: normal;
  margin: 0 0 1rem;
}
.recipe-look .recipe-text h2,
.recipe-look .recipe-text h3,
.recipe-look .recipe-text h4,
.recipe-look .recipe-text h5,
.recipe-look .recipe-text h6 {
  font-family: Georgia, "Times New Roman", serif;
  font-weight: 700;
  font-size: 1.3rem;
  line-height: 1.3;
  color: #181818;
  text-transform: none;
  letter-spacing: normal;
  margin: 1.5rem 0 0.75rem;
}
.recipe-look .recipe-text a { color: #401878; text-decoration: underline; }
.recipe-look .recipe-text img { max-width: 100%; height: auto; }
.recipe-look .recipe-photo {
  float: right;
  width: fit-content;
  max-width: 45%;
  margin: 0.25rem 0 1rem 1.5rem;
}
.recipe-look .recipe-photo a { display: block; }
.recipe-look .recipe-photo img {
  display: block;
  float: none;
  width: auto;
  height: auto;
  max-width: 100%;
  margin: 0;
}
.recipe-look .recipe-back { margin-top: 3rem; clear: both; }
@media (max-width: 639.98px) {
  .recipe-look .recipe-photo { float: none; max-width: 100%; margin: 0 auto 1.25rem; }
}

.album-grid, .audience-list, .testimonial-list, .newsletter-list {
  list-style: none;
  padding: 0;
  margin: 1rem 0;
}
.album-grid, .audience-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 1rem;
}
.album-card, .audience-card {
  padding: 1rem;
  border: 1px solid #ddd;
  border-radius: 8px;
}
.audience-line { display: block; margin-top: 0.4rem; font-size: 0.95rem; }
.testimonial { margin-bottom: 2rem; }
.testimonial-quote { margin: 0 0 0.5rem; font-size: 1.05rem; }
.testimonial-source { margin: 0; font-weight: 600; }
.newsletter-list li { margin-bottom: 0.5rem; }
/* ptkc-walk-2 M1: the members area and the slim bar on every gated page. */
.members-bar { background: #2b1636; color: #ffffff; font-size: 0.9rem; }
.members-bar-inner { display: flex; align-items: center; justify-content: flex-end; gap: 0.4rem; padding-top: 0.45rem; padding-bottom: 0.45rem; }
.members-bar a { color: #ffffff; text-decoration: underline; }
.members-bar-form { display: inline; margin: 0; }
.members-bar-signout { background: none; border: 0; padding: 0; color: #ffffff; font: inherit; text-decoration: underline; cursor: pointer; }
.members-bar-signout:focus-visible, .members-bar a:focus-visible { outline: 2px solid #ffffff; outline-offset: 2px; }
.members-panel { max-width: 42rem; }
.members-state { font-weight: 700; font-size: 1.1rem; }
.members-links { padding-left: 1.2rem; margin: 1rem 0 1.5rem; }
.members-links li { margin-bottom: 0.5rem; }
.members-form { display: flex; flex-direction: column; gap: 0.75rem; max-width: 22rem; margin: 1.5rem 0; }
.members-form .btn-primary { align-self: flex-start; }
/* ptkc-walk-1 W5: the gated archive, a year per heading. */
.newsletter-year { margin-top: 2rem; }
.newsletter-month { display: inline-block; min-width: 8.5rem; font-weight: 700; }
.newsletter-kind { font-size: 0.875rem; }

/* The testimonial marquee heading. Despite the name it sits in
   .testimonial-section on #f9f9f9, ABOVE the black footer, not inside it --
   measured, not assumed. Brand purple there is 7.4 and passes; it is moved
   off an inline style and onto a class, and keeps its colour. */
.footer-marquee-heading {
  color: var(--color-primary);
  font-size: 1.3rem;
  margin-bottom: 1.5rem;
}

/* The studio email in the footer. It carried an inline brand-purple that
   measured 3.14 on the black footer -- on every page of the site. */
.footer-email { color: var(--color-link-on-dark); text-decoration: underline; }
.footer-email:hover { color: #ffffff; }
/* ptkc-walk-10 V3: the footer's address (to Google Maps) and phone (tel:) are
   links now, set exactly as the email link under them. */
.footer-nap-link { color: var(--color-link-on-dark); text-decoration: underline; }
.footer-nap-link:hover { color: #ffffff; }

/* The breadcrumb, per the copy contract: inside the container, directly
   under the hero, ONE SLIM LINE. The markup is an <ol> for the semantics
   and the schema; unstyled it rendered as a numbered list stacked over two
   lines on every page. Separator is generated content so it is decoration
   a screen reader skips, not text in the trail. */
.breadcrumbs ol {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin: 0;
  padding: 0;
  font-size: 0.85rem;
}
.breadcrumbs li { display: inline-flex; align-items: center; }
.breadcrumbs li + li::before {
  content: '\203A';
  margin-right: 0.5rem;
  color: #767676;
}
.breadcrumbs [aria-current="page"] { color: #333333; }

/* The universal form. Labels are visible and above their field: a
   placeholder is not a label, and it disappears the moment someone types. */
.ws-form { max-width: 34rem; margin: 1rem 0; }
.ws-form-row { margin-bottom: 1.25rem; }
.ws-form-label { display: block; font-weight: 700; margin-bottom: 0.35rem; }
.ws-form-optional { font-weight: 400; color: #595959; }
.ws-form-input {
  width: 100%;
  padding: 0.7rem 0.8rem;
  font: inherit;
  color: #111111;
  background: #ffffff;
  border: 1px solid #767676;
  border-radius: 4px;
}
.ws-form-input:focus { outline: 3px solid var(--color-primary); outline-offset: 1px; }
.band-purple .ws-form-label { color: #ffffff; }
.band-purple .ws-form-optional { color: #f0e6f5; }

/* ---------------------------------------------------------------------------
   MOBILE NAVIGATION

   At 390 the desktop nav row, the marquee and the footer grid together forced
   the document to 774px inside a 390 viewport -- every page scrolled
   sideways. Fixed here rather than per page.
   --------------------------------------------------------------------------- */

.nav-toggle {
  display: none;
  align-items: center;
  gap: 0.6rem;
  background: transparent;
  color: var(--color-white);
  border: 2px solid var(--color-white);
  border-radius: 4px;
  padding: 0.6rem 0.9rem;
  font: inherit;
  font-weight: 700;
  text-transform: uppercase;
  cursor: pointer;
}
.nav-toggle:focus-visible { outline: 3px solid var(--color-primary); outline-offset: 2px; }
.nav-toggle-bars, .nav-toggle-bars::before, .nav-toggle-bars::after {
  display: block;
  width: 20px;
  height: 2px;
  background: currentColor;
  content: '';
}
.nav-toggle-bars { position: relative; }
.nav-toggle-bars::before { position: absolute; top: -6px; }
.nav-toggle-bars::after  { position: absolute; top: 6px; }

@media (max-width: 900px) {
  .header-inner { flex-wrap: wrap; gap: 0.75rem; padding: 0.75rem 1rem; }
  .site-logo { max-height: 44px; }
  .nav-toggle { display: inline-flex; order: 3; }
  .site-phone { order: 2; padding: 0.55rem 0.9rem; font-size: 0.9rem; }

  /* Closed is the default, so a phone with no JS gets a page it can read
     rather than a menu stuck open across the content. */
  .site-nav {
    order: 4;
    flex-basis: 100%;
    display: none;
  }
  .site-nav.is-open {
    display: block;
    /* ptkc-walk-2 M3: never taller than the visible screen below the header;
       main.js sets --nav-max-h from the real visual viewport, and the calc is
       the no-script fallback. The panel scrolls on its own. */
    max-height: var(--nav-max-h, calc(100dvh - 4.5rem));
    overflow-y: auto;
    /* Room below the last link: a wheel or finger stops a pixel short of the
       exact end (measured: 979 of 980), which left "Contact" clipped. */
    padding-bottom: 1rem;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
  }
  /* The page behind an open menu does not scroll. */
  html.nav-open, html.nav-open body { overflow: hidden; }

  .nav-level-1 { flex-direction: column; align-items: stretch; gap: 0; }
  .nav-level-1 > li { border-top: 1px solid #333; }
  .nav-level-1 > li > a,
  .nav-level-1 > li > span { display: block; padding: 0.85rem 0.25rem; }

  /* A hover dropdown is unusable on a touch screen: the submenu is simply
     open, indented, and part of the list. */
  .nav-level-2 {
    position: static;
    opacity: 1;
    visibility: visible;
    transform: none;
    box-shadow: none;
    border-top: 0;
    min-width: 0;
    background-color: transparent;
    padding-left: 1rem;
  }
  .nav-level-2 li a { border-bottom: 0; padding: 0.6rem 0.25rem; }
  .has-dropdown > .nav-label { cursor: default; }
}

/* ---------------------------------------------------------------------------
   The rest of the 390 overflow: the marquee and the footer grid.
   --------------------------------------------------------------------------- */

/* The marquee is a decorative strip whose cards are 350px wide and set
   nowrap, which is most of the 774px the document measured at 390. On a
   narrow screen it becomes a plain stacked list: readable, and it stops
   dragging the page sideways. */
.testimonial-marquee-wrapper { flex: 1; overflow: hidden; white-space: nowrap; position: relative; }
.testimonial-marquee { display: inline-flex; align-items: stretch; animation: marquee 40s linear infinite; }
.testimonial-card {
  display: flex;
  flex-direction: column;
  white-space: normal;
  background: #111111;
  color: #ffffff;
  padding: 1.5rem;
  border-radius: 8px;
  width: 350px;
  margin-right: 2rem;
  height: 17rem;              /* ptkc-walk-2 M2: every card the same height */
  overflow: hidden;
}
@media (max-width: 900px) {
  .testimonial-marquee-wrapper { white-space: normal; }
  .testimonial-marquee { display: block; animation: none; }
  .testimonial-card { width: auto; margin: 0 0 1rem; height: 17rem; }
  .testimonial-marquee .testimonial-card:nth-child(n+4) { display: none; }
}
@media (max-width: 700px) {
  .footer-container { grid-template-columns: 1fr; }
  .container { padding: 0 1rem; }
  .section { padding: 2.5rem 0; }
  .hero { padding: 5rem 0; }
}

/* The honeypot. The markup already carried aria-hidden and tabindex="-1",
   but nothing ever styled .hp-field, so "Do not fill this in" was printed
   on the page for every visitor to read.

   Positioned off-screen rather than display:none on purpose: a field that
   is display:none is the first thing a spam bot skips, and the point of the
   field is to be filled in by something that is not a person. It stays in
   the layout, stays submittable, and stays out of sight and out of the
   accessibility tree. */
.hp-field {
  position: absolute;
  left: -9999px;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* Testimonial card internals, moved off inline styles. */
.testimonial-card-head { display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem; }
.testimonial-avatar { width: 40px; height: 40px; border-radius: 50%; object-fit: cover; }
.testimonial-name { font-weight: 700; }
.testimonial-when { font-size: 0.8rem; color: #cccccc; }
.testimonial-stars { color: #fbbc05; font-size: 1.2rem; margin-bottom: 0.5rem; }
.testimonial-text { font-size: 0.9rem; line-height: 1.4; margin: 0; flex: 1; }
.visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
.testimonial-more { color: #fbbc05; font-weight: 700; font-size: 0.9rem; margin-top: 0.75rem; align-self: flex-start; }
.testimonial-more:focus-visible { outline: 2px solid #ffffff; outline-offset: 2px; }
.footer-rating-line { font-size: 0.95rem; margin-top: 0.5rem; font-weight: 600; }

/* ---------------------------------------------------------------------------
   Footer, off inline styles.

   The 4-column grid was set by an inline style attribute, which beats every
   media query -- so the footer stayed four columns wide inside a 390 phone
   and dragged the whole document sideways on every page. That is the reason
   "classes only" is a law and not a preference.
   --------------------------------------------------------------------------- */

.testimonial-section {
  background-color: var(--color-bg-light);
  border-top: 2px solid var(--color-primary);
  padding: 3rem 0;
  overflow: hidden;
}
.testimonial-strip {
  display: flex;
  gap: 3rem;
  align-items: center;
  max-width: 1400px;
}
.testimonial-rating { flex: 0 0 200px; text-align: center; }
.testimonial-rating-stars { color: #fbbc05; font-size: 2.2rem; line-height: 1; }

.footer-container--4 { grid-template-columns: 1fr 1fr 1fr 1.5fr; }
.footer-nav--spaced { margin-bottom: 2rem; }
.footer-logo { max-width: 150px; margin-bottom: 1rem; }
.footer-nap { margin: 0 0 1rem; font-size: 0.95rem; line-height: 1.6; }
.footer-social { text-align: left; margin-top: 2rem; display: inline-block; }
.footer-social-row { margin-bottom: 0.5rem; }
.footer-social-link {
  color: #ffffff;
  text-decoration: none;
  font-size: 0.85rem;
  display: flex;
  align-items: center;
  gap: 8px;
}
.footer-social-link:hover { text-decoration: underline; }
.footer-map { margin-bottom: 1.5rem; }
.footer-map-frame { width: 100%; height: 200px; border: 0; border-radius: 4px; }
.footer-hours-heading { margin-bottom: 1rem; }
.footer-hours { margin: 0; font-size: 0.95rem; line-height: 1.6; }
.footer-hours.hours-list { list-style: disc inside; padding: 0; margin: 0; }
.footer-hours-after { margin: 0; }
.footer-hours-note { font-size: 0.85rem; color: #aaaaaa; margin-top: 0.5rem; display: block; }

@media (max-width: 900px) {
  .testimonial-strip { flex-direction: column; gap: 1.5rem; align-items: stretch; }
  .testimonial-rating { flex: 0 0 auto; }
}
@media (max-width: 700px) {
  .footer-container--4 { grid-template-columns: 1fr; }
  .footer-social { display: block; }
}

/* A section that carries a photograph: prose and picture side by side, the
   picture never wider than its own pixels. Photographs carry sections; they
   are not a gallery at the bottom of the page. */
/* The media column was "auto", so it took its intrinsic width -- and the
   contact page's photograph is 1040px wide, which starved the text column
   down to 192px. Both columns are now flexible and neither can starve the
   other; the media still gets the larger share, which is the intent. */
.media-split { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr); gap: 2.5rem; align-items: start; }
.media-split > h2 { grid-column: 1 / -1; }
.media-split-text { min-width: 0; }
.section-media { margin: 0; }
/* Never wider than the file's own pixels, and never wider than the column it
   sits in either: the meal-planning photograph is 900x1200, and at its native
   width it swallowed the section it was supposed to illustrate. */
.section-media-img, .section-media img {
  display: block;
  height: auto;
  border-radius: 6px;
  width: auto;
  max-width: min(100%, 340px);
}
@media (max-width: 900px) {
  .media-split { grid-template-columns: 1fr; gap: 1.5rem; }
  .section-media img, .section-media-img { width: 100%; }
}
/* ptkc-walk-5 V4: a [figure] the copy marks FRAMED is the print at its own
   pixel size on a white mat, centred -- not the 340px section photograph.
   Never wider than its native pixels (the <img> carries them) or than the
   column; never enlarged. */
.section-media.section-media--framed {
  width: fit-content;
  max-width: 100%;
  margin: 0 auto;
  background: #ffffff;
  padding: 0.75rem;
  border-radius: 2px;
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.18);
}
.section-media.section-media--framed img,
.section-media.section-media--framed .section-media-img {
  width: auto;
  max-width: 100%;
  border-radius: 0;
}

/* Long unbreakable tokens must wrap rather than widen the document.
   Three legacy recipe titles carry a bare URL -- "ANYTIME SNACK
   (WWW.BODYFORLIFE.COM) - RECIPE" -- and at 390 that single token is wider
   than the screen in a 900-weight display face. No element's box exceeded
   the viewport, so an element-by-element check found nothing; the page
   still scrolled sideways. Content decides where text can break, and a
   client's old recipe titles are content. */
h1, h2, h3, h4, p, li, td, th, blockquote, .card, figcaption {
  overflow-wrap: break-word;
}

/* ===========================================================================
   TREATMENT A, PROPAGATED SITE-WIDE  (Dale's pick, 2026-09-19)

   The brand band carries the type; the photograph is an object on it, at its
   own size, matted like a print rather than blown up. Dale's amendment: when
   a photograph is BIG ENOUGH it becomes the hero background instead, and he
   is having those photographs taken.

   Which of the two a page gets is decided by the FILE, not by the page --
   tools/compile-pages.py measures each hero's header and emits
   .hero--background or .hero--framed. That is why there is no list of
   special pages here and none is needed when the new photographs land: drop
   the file in assets/img/heroes/, rebuild, and the page changes mode by
   itself.

   THE LAW, in both modes: nothing renders wider than its native pixel width.
   Nothing is upscaled, extended, outpainted or generated.
   =========================================================================== */

.hero {
  background-color: #4b2d5c;
  background-image: linear-gradient(160deg, #5d3a71 0%, #3b2349 100%);
  padding: 3.5rem 0 0;
  display: flex;
  flex-direction: column;
  position: relative;
}
.hero .container { max-width: 1040px; }
.hero-body { text-align: left; order: 1; position: relative; z-index: 2; }
.hero h1 {
  font-size: clamp(1.9rem, 4.2vw, 3.1rem);
  text-transform: none;
  letter-spacing: -0.015em;
  text-shadow: none;
  max-width: 20ch;
  color: var(--color-white);
}
.hero .eyebrow { color: #e6d4ef; text-shadow: none; }
/* ptkc-walk-10 V4, Dale's pick B (previewed in ptkc-walk-9 as /_preview/eyebrow-b/):
   the hero eyebrow -- ws_split_heading()'s small half, which no stylesheet had
   ever sized, so it rendered at the H1's full size -- on its own line at the
   lead paragraph's 1.25rem. It stays inside the H1 (one heading, one sentence)
   and keeps the H1's weight, colour and letter-spacing. B's one rule, as
   previewed, without the preview's body class. */
.hero .split-heading-lead { display: block; font-size: 1.25rem; }
/* ptkc-walk-10 V3 follow-up: a link inside a hero paragraph (the address or the
   phone, now linked where the copy states them) is white and underlined, like
   the hero text it sits in, exactly as a link in a purple band is (above).
   Unstyled it took the brand purple: 1.36:1 on the hero band. */
.hero p a { color: #ffffff; text-decoration: underline; }
.hero p a:hover { color: #ffffff; text-decoration: none; }
.hero p {
  margin: 0 0 1.5rem;
  max-width: 56ch;
  text-shadow: none;
  font-size: 1.25rem;
  color: var(--color-white);
}
.hero .cta-group { justify-content: flex-start; margin-bottom: 2.5rem; }

/* The hero's secondary button. Its inherited purple-on-purple is a 1.14. */
.hero .btn-secondary { color: #ffffff; border-color: #ffffff; background: transparent; }
.hero .btn-secondary:hover,
.hero .btn-secondary:focus-visible { background: #ffffff; color: #4b2d5c; }

/* --- FRAMED: the photograph as a matted print on the band ---------------- */
.hero--framed .hero-figure {
  order: 2;
  background: #ffffff;
  padding: 0.75rem;
  border-radius: 2px;
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.35);
  margin-bottom: -3rem;
  position: relative;
  z-index: 3;
  width: fit-content;
  max-width: min(100%, var(--img-native-w, 100%));
  margin-inline: auto;
}
.hero--framed .hero-figure img {
  display: block;
  height: auto;
  max-width: min(100%, var(--img-native-w, 100%));
}
.hero--framed .hero-figure { margin-inline: auto; }
.site-main > .section.band:first-of-type { padding-top: 5.5rem; }
.hero--framed::before { content: none; }         /* no scrim: no photo behind */

/* --- PORTRAIT: the photograph unframed beside the name -------------------
   ptkc-walk-1 W3, Dale's ruling for Bjorn's page (data/hero-portrait.json):
   "put his image unframed to the right of the header and float his name to
   the top left centered on the left side." A portrait photograph is too
   small and too tall for a full-bleed hero, and as a print it had floated
   top-left above the name. Unframed: no mat, border or shadow. Never wider
   than its native pixels, and never taller than the screen can show. The
   text is beside or above the photograph, never over it. */
.hero--portrait { padding: 0; }
.hero--portrait::before { content: none; }
.hero--portrait .hero-figure-link { display: block; }
.hero--portrait .hero-figure {
  display: block;
  width: auto;
  height: auto;
  max-width: min(100%, var(--img-native-w, 100%));
}
.hero--portrait .hero-body { text-align: center; }
.hero--portrait .hero-body h1,
.hero--portrait .hero-body p { margin-inline: auto; }
@media (min-width: 901px) {
  .hero.hero--portrait {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    align-items: start;
    column-gap: 2rem;
    padding: 2.5rem clamp(2rem, 5vw, 4.5rem);
  }
  .hero.hero--portrait .hero-body {
    grid-column: 1; grid-row: 1;
    align-self: start; justify-self: center;
    max-width: none; margin: 0; padding: 2rem 0 0;
  }
  .hero.hero--portrait .hero-figure-link,
  .hero.hero--portrait > .hero-figure { grid-column: 2; grid-row: 1; justify-self: center; }
  .hero.hero--portrait .hero-figure { max-height: min(44rem, 80vh); }
}
@media (max-width: 900px) {
  .hero.hero--portrait { display: flex; flex-direction: column; align-items: center; }
  .hero.hero--portrait .hero-body { order: 1; padding: 2rem 1.5rem 1.5rem; }
  .hero.hero--portrait .hero-figure-link,
  .hero.hero--portrait > .hero-figure { order: 2; width: 100%; }
  .hero.hero--portrait .hero-figure { margin-inline: auto; }
}

/* --- BACKGROUND: layout B (SIDE) -- ptkc-propagate-b --------------------
   Dale's pick of three layouts rendered on real pages (ptkc-hero-layout).
   The layout it replaces centred a dark wedge and the text column over the
   middle of the photograph, so a face there was covered at 1280 and crossed
   by the headline at 390 -- Christina's own hero among them.

   DESKTOP (901px and up): the photograph full-bleed. The headline, lead and
   buttons sit in a column exactly 45% wide on the side away from the subject
   (focal's first word is the side the SUBJECT is on), and the scrim covers
   that column and nothing else: solid to 43%, gone by 45%. On a right-focal
   hero every line of text sits on solid scrim at every width: the column's
   2.5rem inner edge covers it up to 2000px, and above about 1670px the 40rem
   line cap keeps it well inside. On a focal-left hero the column is mirrored
   but the text still starts at its left, next to the fade, so above 2000px
   about the first 11px of each line (at 2560) sits on the 43-45% fade;
   measured contrast there is at least 12.5:1 against a 4.5 target
   (ptkc-hero-tune). The subject's side is never covered.

   PHONE AND TABLET (900px and down): the photograph is a 5:4 band ABOVE the
   text, cropped by focal, and the headline sits on purple below it -- never
   on the photograph.

   MEASURED with tools/measure-heroes.py: on Christina's page, Who We Train:
   Women and Tour Our Gym before it was propagated (ptkc-propagate-b B1), and
   on every BACKGROUND hero at 390, 901, 1000, 1100, 1280, 1440, 1600, 1920
   and 2560 (ptkc-hero-tune T1), every face and subject clear. The narrowest
   margins: 21px for a face (tandem at 390, against the header's shadow) and
   20px for a subject (Tour Our Gym's HH mark at 1440). A page whose photograph
   still cannot run this way without covering a face falls back to FRAMED
   (data/hero-framed.json); it never ships with a covered face.

   Per-hero data comes from heroes.css: --hero-img-md (the phone band's
   photograph) and --hero-pos-x / --hero-pos-y (the focal key). */
.hero--background {
  background-size: cover;
  background-position: var(--hero-pos-x, 50%) var(--hero-pos-y, 50%);
  background-repeat: no-repeat;
  padding: 0;
}
.hero--background .hero-figure { display: none; }   /* the CSS layer carries it */
.hero--background .cta-group { margin-bottom: 0; }

@media (min-width: 901px) {
  /* Never flatter than 2.5:1 (ptkc-hero-tune T1). A hero with only a 36rem
     floor, exactly 36rem on these pages at wide widths, shows
     a smaller slice of a 3:2 photograph's height the wider the window: at
     2560 it cut off Christina's and Women's chins and Tour Our Gym's wall
     mark. 40vw equals 36rem at exactly 1440px, so nothing at or below 1440
     changes. */
  .hero.hero--background { min-height: max(36rem, 40vw); justify-content: center; }
  .hero.hero--background::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 1;
    background: linear-gradient(90deg, #2b1636 0%, #2b1636 43%, rgba(43, 22, 54, 0) 45%);
  }
  .hero.hero--background.hero--focal-left::before {
    background: linear-gradient(270deg, #2b1636 0%, #2b1636 43%, rgba(43, 22, 54, 0) 45%);
  }
  .hero.hero--background .hero-body {
    width: 45%;
    max-width: 45%;
    margin: 0;
    padding: 3.5rem 2.5rem 3.5rem clamp(2rem, 5vw, 4.5rem);
  }
  .hero.hero--background.hero--focal-left .hero-body {
    margin: 0 0 0 auto;
    padding: 3.5rem clamp(2rem, 5vw, 4.5rem) 3.5rem 2.5rem;
  }
  .hero.hero--background .hero-body h1 { font-size: clamp(1.9rem, 3.1vw, 2.6rem); max-width: 40rem; }
  .hero.hero--background .hero-body p,
  .hero.hero--background .hero-body .eyebrow { max-width: 40rem; }   /* a readable line on a wide window */
  .hero.hero--background .hero-body p { font-size: 1.1rem; }
}

@media (max-width: 900px) {
  .hero.hero--background {
    background-image: none;
    background-color: #2b1636;
    min-height: 0;
    justify-content: flex-start;
  }
  /* The photograph becomes a band ABOVE the text, never behind it. */
  .hero.hero--background::before {
    content: '';
    display: block;
    width: 100%;
    aspect-ratio: 5 / 4;
    background: var(--hero-img-md) var(--hero-pos-x, 50%) var(--hero-pos-y, 50%) / cover no-repeat;
  }
  .hero.hero--background .hero-body {
    display: block;
    width: 100%;
    max-width: none;
    margin: 0;
    padding: 1.75rem 2rem 2.25rem;
    background: #2b1636;
  }
  .hero.hero--background .hero-body h1,
  .hero.hero--background .hero-body p { max-width: none; }
}
@media (max-width: 540px) {
  .hero.hero--background .hero-body { padding: 1.5rem 1rem 2rem; }
}

/* --- LAYOUT B-BAND: the photograph as a band above the text --------------
   ptkc-walk-2 M4 (Dale's pick for /creative-cardio/, "B", from
   /_preview/hero/). Moved here unchanged from the preview's stylesheet: the
   whole photograph as a full-width 3:2 band ABOVE the text at every width,
   nothing over it, the headline and lead on purple below. Above 900px the band
   paints the section's own responsive photograph (background-image: inherit)
   while the section draws none of it (background-size 0 0); at 900px and down
   layout B has already removed the section's photograph, so the band takes
   --hero-img-md. A 3:2 band shows a 3:2 photograph whole: cover crops nothing.
   A page gets it from data/picks.json ("layout": "band-above"). */
.hero.hero--background.hero--layout-band-above {
  min-height: 0;
  justify-content: flex-start;
  background-color: #2b1636;
  background-size: 0 0;
}
.hero.hero--background.hero--layout-band-above::before {
  content: '';
  position: static;
  inset: auto;
  z-index: auto;
  display: block;
  width: 100%;
  aspect-ratio: 3 / 2;
  background: none;
  background-image: inherit;
  background-size: cover;
  background-position: 50% 50%;
  background-repeat: no-repeat;
}
.hero.hero--background.hero--layout-band-above .hero-body {
  display: block;
  width: 100%;
  max-width: none;
  margin: 0;
  padding: 2.25rem clamp(1rem, 5vw, 4.5rem) 2.75rem;
  background: #2b1636;
}
.hero.hero--background.hero--layout-band-above .hero-body h1,
.hero.hero--background.hero--layout-band-above .hero-body p,
.hero.hero--background.hero--layout-band-above .hero-body .eyebrow { max-width: 40rem; }
@media (max-width: 900px) {
  .hero.hero--background.hero--layout-band-above::before { background-image: var(--hero-img-md); }
  .hero.hero--background.hero--layout-band-above .hero-body { padding: 1.75rem 2rem 2.25rem; }
  .hero.hero--background.hero--layout-band-above .hero-body h1,
  .hero.hero--background.hero--layout-band-above .hero-body p { max-width: none; }
}
@media (max-width: 540px) {
  .hero.hero--background.hero--layout-band-above .hero-body { padding: 1.5rem 1rem 2rem; }
}

/* --- BAND: the photograph above, the H1 directly under it ---------------
   ptkc-walk-5 V2 (Dale, 2026-10-04: the home hero; data/hero-band.json).
   At every width the photograph is a band at the top of the hero and the
   headline, lead and buttons sit below it on the hero's own colour, in a
   column as wide as the photograph. Nothing is drawn over the photograph.
   It never renders wider than its native pixels (--img-native-w): on a
   wider screen it sits centred on #2b1636. The band's aspect ratio is fixed
   by the record's rows (--hero-band-ratio) and the photograph is pinned to
   the record's anchor edge (--hero-band-pos, object-fit: cover), so every
   width shows the ruled crop and no width crops more. */
.hero.hero--band {
  padding: 0;
  background: #2b1636;
  justify-content: flex-start;
}
.hero.hero--band::before { content: none; }
.hero.hero--band .hero-band-figure {
  order: 0;
  margin: 0 auto;
  width: 100%;
  max-width: var(--img-native-w, 100%);
  aspect-ratio: var(--hero-band-ratio, auto);
  overflow: hidden;
}
.hero.hero--band .hero-band-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--hero-band-pos, 50% 100%);
}
.hero.hero--band .hero-body {
  order: 1;
  width: 100%;
  max-width: var(--img-native-w, 100%);
  margin: 0 auto;
  padding: 1.75rem clamp(1rem, 4vw, 3rem) 2.5rem;
}
.hero.hero--band .hero-body h1,
.hero.hero--band .hero-body p,
.hero.hero--band .hero-body .eyebrow { max-width: 40rem; }
.hero.hero--band .cta-group { margin-bottom: 0; }
@media (max-width: 540px) {
  .hero.hero--band .hero-body { padding: 1.5rem 1rem 2rem; }
}

/* --- the bands and cards A carries -------------------------------------- */
.band-white { background-color: #faf7fb; }
.band-purple { background-color: #4b2d5c; }
.card {
  border: 0;
  border-left: 4px solid var(--color-primary);
  border-radius: 0;
  background: #ffffff;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.06);
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.card:hover { transform: translateY(-3px); }
.band-purple .card { background: rgba(255, 255, 255, 0.08); border-left-color: #ffffff; }
.btn-primary { transition: transform 0.15s ease, background 0.2s ease; }
.btn-primary:hover { transform: translateY(-2px); }

@media (prefers-reduced-motion: reduce) {
  .card, .btn-primary { transition: none; }
  .card:hover, .btn-primary:hover { transform: none; }
}

/* A card that leads with its video (C1). */
.card--video .video-embed { margin: 0 0 1rem; }

/* The client's own promo graphics ([promo]). */
.promo-row {
  display: flex;
  flex-wrap: wrap;
  gap: 2rem;
  justify-content: center;
  margin: 2rem 0;
}
.promo-item { display: block; line-height: 0; }
.promo-img {
  display: block;
  height: auto;
  width: auto;
  max-width: min(100%, 320px);
  border-radius: 8px;
  box-shadow: 0 5px 15px rgba(0, 0, 0, 0.12);
}

/* {{TRANSFORMATIONS_LIST}} */
.transformations-group { margin: 1.5rem 0 0.5rem; }
.transformations-items { list-style: none; padding: 0; margin: 0 0 1rem; }
.transformations-items li { margin-bottom: 0.4rem; }

/* D3: the video facade with a LOCAL poster. Nothing is requested from any
   outside host until the reader presses play. */
/* F1 -- THE VIDEOS DID NOT PLAY, AND THIS IS WHY.
   Clicking Play swapped the facade for the iframe and the video vanished.
   Nothing was broken in the JavaScript: there was no console error, no
   page error, and the iframe was created with the right src every time.
   The box collapsed. This container took its height from the poster <img>
   inside it, the iframe is position:absolute and therefore out of flow, and
   the swap removes the poster -- so the host measured 363x0 and height:100%
   of nothing is nothing. The video was playing, 0 pixels tall.

   The fix is that the container owns its own height. A 16:9 box, because
   that is the shape of the video that will fill it, and it no longer
   depends on which child happens to be inside at the time. YouTube gives
   us posters in two shapes -- 8 of ours are 1280x720 and 8 are 480x360 --
   so the poster is covered into the box rather than setting it, which also
   crops away the black bars YouTube bakes into a 4:3 thumbnail. */
.video-embed--poster {
  position: relative;
  padding: 0;
  border: 0;
  overflow: hidden;
  aspect-ratio: 16 / 9;
}
.video-embed-poster {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 8px;
}
.video-embed--poster .video-embed-play {
  position: absolute;
  inset: 0;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  background: rgba(20, 8, 26, 0.34);
  border-radius: 8px;
  padding: 1rem;
  color: #ffffff;
}
.video-embed--poster .video-embed-play:hover,
.video-embed--poster .video-embed-play:focus-visible { background: rgba(20, 8, 26, 0.5); }
.video-embed-play-icon {
  width: 58px; height: 58px; border-radius: 50%;
  background: var(--color-primary);
  position: relative; flex: 0 0 auto;
  box-shadow: 0 2px 12px rgba(0,0,0,0.45);
}
.video-embed-play-icon::after {
  content: ''; position: absolute; top: 50%; left: 54%;
  transform: translate(-50%, -50%);
  border-style: solid; border-width: 11px 0 11px 18px;
  border-color: transparent transparent transparent #ffffff;
}
/* The label sits on a SOLID pill, not on the translucent wash. White on
   rgba(20,8,26,0.34) over a photograph measured 2.38 -- and a poster is a
   photograph, so there is no single background to tune the wash against.
   A solid ground is the only honest way to make type over a picture
   readable, and it measures the same on every poster. */
.video-embed-play-label {
  font-size: 0.9rem;
  font-weight: 700;
  max-width: 30ch;
  background: #2b1636;
  color: #ffffff;
  padding: 0.4rem 0.8rem;
  border-radius: 4px;
  text-shadow: none;
}
.video-embed--poster iframe { position: absolute; inset: 0; width: 100%; height: 100%; }

/* A7: the client gate. It speaks to someone standing in the studio: it names
   what is behind it and says where today's code is. It never hints at the
   code, shows an example, or carries an old value -- and the error says the
   same thing as the page, because a person who mistyped needs the same
   sentence, not a different one. */
.gate-panel { max-width: 34rem; padding: 2.5rem 0; }
.gate-lede { font-size: 1.05rem; max-width: 46ch; }
.gate-form { display: flex; flex-direction: column; gap: 0.75rem; max-width: 22rem; margin: 1.5rem 0; }
.gate-label { font-weight: 700; }
.gate-input {
  padding: 0.7rem 0.8rem;
  font: inherit;
  color: #111111;
  background: #ffffff;
  border: 1px solid #767676;
  border-radius: 4px;
}
.gate-input:focus { outline: 3px solid var(--color-primary); outline-offset: 1px; }
.gate-form .btn-primary { align-self: flex-start; }
.gate-error {
  padding: 0.8rem 1rem;
  border-left: 4px solid #8a1f2b;
  background: #fdeced;
  color: #5c1018;
  font-weight: 600;
  max-width: 46ch;
}
.gate-where { color: #444444; max-width: 46ch; }

/* D4/D7: a row of videos, and a section laid out three-up. Both collapse to
   one column on a phone; neither renders an image wider than its own pixels. */
.video-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 1.5rem;
  margin: 1.5rem 0;
}
.video-cell { min-width: 0; }

.band--grid3 .container {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 1.5rem;
  align-items: start;
}
.band--grid3 > .container > h2 { grid-column: 1 / -1; }
.band--grid3 .card { margin: 0; height: 100%; }

.figure-row {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  justify-content: center;
  margin: 1.5rem 0;
}
.figure-cell { margin: 0; }
.figure-cell-img, .figure-cell img {
  display: block;
  height: auto;
  width: auto;
  max-width: min(100%, var(--img-native-w, 300px));
  border-radius: 6px;
}
@media (max-width: 700px) {
  .video-row, .band--grid3 .container { grid-template-columns: 1fr; }
}

/* --- GALLERIES -----------------------------------------------------------
   The hub lists 94 albums and the largest album holds 176 photographs, so
   every rule here is written for the big page first. Tiles are a fixed
   aspect box: 1,162 photographs have 1,162 different shapes, and a grid
   that reflows to each one is a page that jumps while it loads. */
.album-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  gap: 1.25rem;
}
.album-card { margin: 0; }
.album-card-link {
  display: block;
  position: relative;
  text-decoration: none;
  color: inherit;
  border-radius: 0.375rem;
  overflow: hidden;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.14);
  height: 100%;
}
.album-card-link:hover,
.album-card-link:focus-visible { box-shadow: 0 3px 10px rgba(0, 0, 0, 0.24); }
.album-card-cover {
  display: block;
  aspect-ratio: 4 / 3;
  background: #efecf1;
  overflow: hidden;
}
.album-card-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* An album with nothing to show still gets a tile of the same size, so the
   grid does not develop holes where the disk backup did. */
.album-card-cover--none {
  background: repeating-linear-gradient(135deg, #efecf1 0 12px, #e7e3ea 12px 24px);
}
.album-card-body { display: block; padding: 0.75rem 0.875rem 0.9rem; }
.album-card-title { display: block; font-weight: 700; line-height: 1.3; }
.album-card-count { display: block; font-size: 0.875rem; color: #5a4a63; margin-top: 0.2rem; }
.album-card-count--none { color: #77697e; font-style: italic; }
.album-card-date { display: block; font-size: 0.8125rem; color: #77697e; }
.album-card--empty .album-card-link { background: #faf9fb; }
.album-card .is-gated {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  background: rgba(43, 22, 54, 0.88);
  color: #fff;
  border-radius: 999px;
  padding: 0.15rem 0.45rem;
  font-size: 0.8125rem;
  line-height: 1.4;
}

.photo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
  gap: 0.75rem;
}
.photo-cell { margin: 0; position: relative; }
.photo-open {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: #efecf1;
  cursor: zoom-in;
  border-radius: 0.25rem;
  overflow: hidden;
  aspect-ratio: 1 / 1;
}
.photo-open img { width: 100%; height: 100%; object-fit: cover; display: block; }
.photo-open:focus-visible { outline: 3px solid #7b3fa0; outline-offset: 2px; }
.photo-download {
  display: block;
  font-size: 0.8125rem;
  padding: 0.3rem 0.1rem 0;
  color: #5a3a6b;
}
/* ptkc-walk-1 W4: a page's own photographs as the gallery grid
   (ws_photo_grid). One crop ratio, 2:3 -- nine of the twelve Join the Team
   photographs are 2:3 already, so it trims the least -- four across at
   desktop, two on phones (below). */
.photo-grid.photo-grid--4up { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.photo-grid--4up .photo-open { aspect-ratio: 2 / 3; }
.photo-open picture { display: block; width: 100%; height: 100%; }

/* The lightbox is a <dialog>, so Escape and focus trapping are the
   platform's job rather than ours. With no JS the grid is still a grid of
   links that download -- the photographs never depend on script. */
.lightbox {
  border: 0;
  padding: 0;
  background: transparent;
  max-width: 100vw;
  max-height: 100vh;
  width: 100%;
  height: 100%;
}
.lightbox::backdrop { background: rgba(20, 10, 26, 0.92); }
.lightbox-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100%;
  gap: 0.75rem;
  padding: 1rem;
}
.lightbox img {
  max-width: 100%;
  max-height: calc(100vh - 8rem);
  object-fit: contain;
  background: #2b1636;
}
.lightbox-bar {
  display: flex;
  gap: 1rem;
  align-items: center;
  flex-wrap: wrap;
  justify-content: center;
  color: #fff;
}
.lightbox-bar a, .lightbox-bar button {
  color: #fff;
  background: rgba(255, 255, 255, 0.14);
  border: 1px solid rgba(255, 255, 255, 0.5);
  border-radius: 0.25rem;
  padding: 0.4rem 0.8rem;
  font: inherit;
  cursor: pointer;
  text-decoration: none;
}
.lightbox-caption { color: #fff; font-size: 0.9375rem; text-align: center; }

/* This note sets its own light background, so it MUST set its own text
   colour too. Inheriting it measured 1.05:1 on Christina's and Bjorn's
   pages, where the surrounding band is purple and the type is white: a
   component that carries a background and borrows a foreground is only
   legible by luck about which band it lands in. */
/* p.album-empty-note, not .album-empty-note: the band rules are
   ".band-purple p" (0,1,1) and ".band-purple a:not(.btn)" (0,2,1), and a
   bare class loses to both. Matching their specificity and winning on
   source order is how the rest of this stylesheet does it -- no
   !important, which is the site's standing rule. */
p.album-empty-note {
  background: #faf9fb;
  color: #2b1636;
  border-left: 4px solid #7b3fa0;
  padding: 0.9rem 1.1rem;
  margin: 0;
}
p.album-empty-note a:not(.btn) { color: #5a2d78; text-decoration: underline; }

@media (max-width: 700px) {
  .album-grid { grid-template-columns: repeat(auto-fill, minmax(10.5rem, 1fr)); gap: 0.875rem; }
  .photo-grid { grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr)); gap: 0.5rem; }
  .photo-grid.photo-grid--4up { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.photo-page-note { color: inherit; font-size: 0.9375rem; margin: 0 0 0.75rem; }
.photo-pager {
  display: flex;
  gap: 1rem;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  margin-top: 1.5rem;
}
/* currentColor, not a fixed grey. "Page 1 of 2" measured 1.42:1 on the
   purple band, for the same reason .album-empty-note did: a component that
   names its own foreground without knowing which band it lands on is
   legible by luck. Inheriting is right on both. */
.photo-pager a {
  border: 1px solid currentColor;
  border-radius: 0.25rem;
  padding: 0.45rem 0.9rem;
  text-decoration: none;
  color: inherit;
}
.photo-pager-pos { color: inherit; }

.town-list {
  list-style: none;
  margin: 0 0 1.25rem;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
  gap: 0.4rem 1.25rem;
}
.town-list li { margin: 0; }

/* T4 -- a Who We Train card carrying its group's picked photograph. Top-
   anchored: these are photographs of people, and the head is what a crop
   must keep. */
.audience-card--photo { display: flex; flex-direction: column; gap: 0.5rem; }
.audience-card-media {
  display: block;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border-radius: 0.375rem;
  background: #efecf1;
}
.audience-card-media img { width: 100%; height: 100%; object-fit: cover; object-position: center top; display: block; }
.plain-list { margin: 0 0 1rem; padding-left: 1.25rem; }
.plain-list li { margin: 0.2rem 0; }

/* [hours] (ptkc-walk-4 W3): the hours of record as bullet points, the client's
   ask, from includes/hours.php. One list wherever hours appear. */
.hours-list { margin: 0 0 1rem; padding-left: 1.25rem; list-style: disc; }
.hours-list li { margin: 0.2rem 0; }
.card--hours .hours-list { margin: 0.5rem 0; }

/* [towns-list]: every town with its straight-line miles, nearest first, in
   columns that collapse to one on a phone. Plain text by design -- no town
   has a page until it has two sourced facts -- so nothing here is styled as
   a link. */
.towns-list {
  list-style: none;
  padding: 0;
  margin: 1rem 0 1.5rem;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 0.35rem 2rem;
}
.towns-list li { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 0 0.75rem; padding: 0.35rem 0; border-bottom: 1px solid #e7e3ea; }
.towns-list-name { font-weight: 700; }
.towns-list-dist { color: #5a4a63; }
.towns-list-name a { color: inherit; text-decoration: underline; text-underline-offset: 0.15em; }
/* A town page's Local Context (includes/location.php): each fact with its
   source and date visible, as the location contract requires. */
.local-facts { list-style: none; margin: 1.5rem 0; padding: 0; }
.local-fact { padding: 0.6rem 0; border-bottom: 1px solid #e7e3ea; }
.local-fact:last-child { border-bottom: 0; }
.fact-source { display: block; margin-top: 0.2rem; font-size: 0.875rem; color: #5a4a63; }
.hero-line { margin-top: 0.25rem; font-style: italic; }
.hero-figure-link { display: inline-block; }
