/* ==========================================================================
   zoiboyz.css — the design system, on top of Bootstrap 5.3.8.

   Contents
     1. Fonts
     2. Tokens: colour, type, space, radius, shadow, motion
     3. Dark theme
     4. Bootstrap mapping
     5. Base elements
     6. Layout
     7. Header
     8. Footer
     9. Components
    10. Motion

   Everything is a CSS custom property, so Bootstrap is restyled by changing a
   value here, never by recompiling its Sass. There is no build step: this file
   is the file the browser reads.

   Every token is shown on /styleguide (signed in).
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Fonts

   Self-hosted, open-licensed, subset to Latin and served as WOFF2. No Google
   Fonts, no Adobe Fonts, nothing loaded from anyone else's server — the site
   has to look the same in 2030.

   The two uprights are variable, so one file covers every weight:
     Fraunces (display) — weight 100-900, optical size 9-144
     Figtree  (text)    — weight 300-900
   Fraunces italic is two fixed weights, 400 and 700: every italic on the
   site is 400, and a fixed weight is half the size of the variable file.

   Made from the variable TTFs in the google/fonts repository with fontTools.
   See reference/PHASE-1.md for the exact commands and why each axis was kept
   or pinned. Licences: Fraunces-OFL.txt and Figtree-OFL.txt beside the fonts.

   Each font is split in two by unicode-range (Phase 5): a "core" file with
   every character the site actually prints, and a "rest" file the browser
   only downloads when a page needs one of the others. The full fonts are
   kept in reference/fonts/ as the source for tools/subset-fonts.py.
   Change the character set or the italic weights there, not here.
   -------------------------------------------------------------------------- */

/* Written by tools/subset-fonts.py: change CORE there and run it again.
   Anything edited between the two markers is overwritten. */
/* fonts:start */
@font-face {
  font-family: "Fraunces";
  src: url("/assets/fonts/fraunces-core.woff2?v=0da8147b") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0020-007E, U+00A0, U+00A9, U+00AE, U+00B0, U+00B7,
    U+00C0-00C2, U+00C4, U+00C7-00CB, U+00CD, U+00D1, U+00D3, U+00D6-00D7,
    U+00DA, U+00DC, U+00DF-00E2, U+00E4, U+00E7-00EB, U+00ED-00EF, U+00F1,
    U+00F3-00F4, U+00F6, U+00FA-00FC, U+2013-2014, U+2018-2019, U+201C-201D,
    U+2022, U+2026, U+2032-2033, U+2039-203A, U+20AC, U+2122;
}

@font-face {
  font-family: "Fraunces";
  src: url("/assets/fonts/fraunces-rest.woff2?v=e8b00f51") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+00A1-00A8, U+00AA-00AD, U+00AF, U+00B1-00B6, U+00B8-00BF,
    U+00C3, U+00C5-00C6, U+00CC, U+00CE-00D0, U+00D2, U+00D4-00D5,
    U+00D8-00D9, U+00DB, U+00DD-00DE, U+00E3, U+00E5-00E6, U+00EC, U+00F0,
    U+00F2, U+00F5, U+00F7-00F9, U+00FD-00FF, U+0131, U+0152-0153, U+02BC,
    U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+2010, U+201A, U+201E,
    U+2020-2021, U+2030, U+2044, U+2052, U+2074, U+2212, U+2215;
}

@font-face {
  font-family: "Fraunces";
  src: url("/assets/fonts/fraunces-italic-400-core.woff2?v=098dceee") format("woff2");
  font-weight: 400;
  font-style: italic;
  font-display: swap;
  unicode-range: U+0020-007E, U+00A0, U+00A9, U+00AE, U+00B0, U+00B7,
    U+00C0-00C2, U+00C4, U+00C7-00CB, U+00CD, U+00D1, U+00D3, U+00D6-00D7,
    U+00DA, U+00DC, U+00DF-00E2, U+00E4, U+00E7-00EB, U+00ED-00EF, U+00F1,
    U+00F3-00F4, U+00F6, U+00FA-00FC, U+2013-2014, U+2018-2019, U+201C-201D,
    U+2022, U+2026, U+2032-2033, U+2039-203A, U+20AC, U+2122;
}

@font-face {
  font-family: "Fraunces";
  src: url("/assets/fonts/fraunces-italic-400-rest.woff2?v=9adb09db") format("woff2");
  font-weight: 400;
  font-style: italic;
  font-display: swap;
  unicode-range: U+00A1-00A8, U+00AA-00AD, U+00AF, U+00B1-00B6, U+00B8-00BF,
    U+00C3, U+00C5-00C6, U+00CC, U+00CE-00D0, U+00D2, U+00D4-00D5,
    U+00D8-00D9, U+00DB, U+00DD-00DE, U+00E3, U+00E5-00E6, U+00EC, U+00F0,
    U+00F2, U+00F5, U+00F7-00F9, U+00FD-00FF, U+0131, U+0152-0153, U+02BC,
    U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+201A, U+201E, U+2020-2021,
    U+2030, U+2044, U+2052, U+2074, U+2212, U+2215;
}

@font-face {
  font-family: "Fraunces";
  src: url("/assets/fonts/fraunces-italic-700-core.woff2?v=0f927b57") format("woff2");
  font-weight: 700;
  font-style: italic;
  font-display: swap;
  unicode-range: U+0020-007E, U+00A0, U+00A9, U+00AE, U+00B0, U+00B7,
    U+00C0-00C2, U+00C4, U+00C7-00CB, U+00CD, U+00D1, U+00D3, U+00D6-00D7,
    U+00DA, U+00DC, U+00DF-00E2, U+00E4, U+00E7-00EB, U+00ED-00EF, U+00F1,
    U+00F3-00F4, U+00F6, U+00FA-00FC, U+2013-2014, U+2018-2019, U+201C-201D,
    U+2022, U+2026, U+2032-2033, U+2039-203A, U+20AC, U+2122;
}

@font-face {
  font-family: "Fraunces";
  src: url("/assets/fonts/fraunces-italic-700-rest.woff2?v=2f77b467") format("woff2");
  font-weight: 700;
  font-style: italic;
  font-display: swap;
  unicode-range: U+00A1-00A8, U+00AA-00AD, U+00AF, U+00B1-00B6, U+00B8-00BF,
    U+00C3, U+00C5-00C6, U+00CC, U+00CE-00D0, U+00D2, U+00D4-00D5,
    U+00D8-00D9, U+00DB, U+00DD-00DE, U+00E3, U+00E5-00E6, U+00EC, U+00F0,
    U+00F2, U+00F5, U+00F7-00F9, U+00FD-00FF, U+0131, U+0152-0153, U+02BC,
    U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+201A, U+201E, U+2020-2021,
    U+2030, U+2044, U+2052, U+2074, U+2212, U+2215;
}

@font-face {
  font-family: "Figtree";
  src: url("/assets/fonts/figtree-core.woff2?v=734ebcb7") format("woff2");
  font-weight: 300 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0020-007E, U+00A0, U+00A9, U+00AE, U+00B0, U+00B7,
    U+00C0-00C2, U+00C4, U+00C7-00CB, U+00CD, U+00D1, U+00D3, U+00D6-00D7,
    U+00DA, U+00DC, U+00DF-00E2, U+00E4, U+00E7-00EB, U+00ED-00EF, U+00F1,
    U+00F3-00F4, U+00F6, U+00FA-00FC, U+2013-2014, U+2018-2019, U+201C-201D,
    U+2022, U+2026, U+2039-203A, U+20AC, U+2122;
}

@font-face {
  font-family: "Figtree";
  src: url("/assets/fonts/figtree-rest.woff2?v=12982cb3") format("woff2");
  font-weight: 300 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+00A1-00A8, U+00AA-00AC, U+00AF, U+00B1-00B6, U+00B8-00BF,
    U+00C3, U+00C5-00C6, U+00CC, U+00CE-00D0, U+00D2, U+00D4-00D5,
    U+00D8-00D9, U+00DB, U+00DD-00DE, U+00E3, U+00E5-00E6, U+00EC, U+00F0,
    U+00F2, U+00F5, U+00F7-00F9, U+00FD-0113, U+0116-0127, U+012A-012B,
    U+012E-0137, U+0139-013E, U+0141-0148, U+014A-014D, U+0150-0161,
    U+0164-0165, U+016A-017E, U+01CD-01CE, U+0218-021B, U+0237, U+02C6,
    U+02DA, U+02DC, U+0304, U+0308, U+1E80-1E85, U+1E9E, U+1EF2-1EF3, U+201A,
    U+201E, U+2020-2021, U+2044, U+2074, U+20B9, U+2191, U+2193, U+2212,
    U+2215;
}
/* fonts:end */

/* The fallback faces.

   font-display: swap means the page paints in a system font first and swaps
   when ours arrives. Without help, that swap moves every line on the page.
   These three faces are the system fonts rescaled to our fonts' own metrics,
   measured from the real files rather than guessed, so the swap changes the
   shapes and not the layout.

   Figtree against Arial comes out at 101.64%: body text does not move at all. */

@font-face {
  font-family: "Fraunces fallback";
  src: local("Georgia"), local("Times New Roman"), local("Times");
  size-adjust: 119.15%;
  ascent-override: 82.08%;
  descent-override: 21.40%;
  line-gap-override: 0%;
}

@font-face {
  font-family: "Fraunces fallback";
  font-style: italic;
  src: local("Georgia Italic"), local("Times New Roman Italic");
  size-adjust: 114.15%;
  ascent-override: 85.67%;
  descent-override: 22.34%;
  line-gap-override: 0%;
}

@font-face {
  font-family: "Figtree fallback";
  src: local("Arial"), local("Helvetica Neue"), local("Helvetica");
  size-adjust: 101.64%;
  ascent-override: 93.47%;
  descent-override: 24.60%;
  line-gap-override: 0%;
}

/* --------------------------------------------------------------------------
   2. Tokens
   -------------------------------------------------------------------------- */

:root {
  /* -- Colour: the existing green family, plus the "red dog" russet ---------
     Contrast ratios were measured and are noted. The site has to pass
     WCAG 2.2 AA, so a colour without a ratio beside it is decoration only. */

  --zb-forest:      #1B3A17; /* header, footer, headings. cream on forest 11.5:1 */
  --zb-pine:        #244F1E; /* raised surfaces on forest, hover on dark */
  --zb-pasture:     #2F6B25; /* links, primary buttons. 5.9:1 on cream */
  --zb-meadow:      #5E8F4E; /* decorative only (3.5:1) */
  --zb-sage:        #A9C9A0; /* tints, borders, light text on forest (6.9:1) */
  --zb-mist:        #E6EFE1; /* section tint, and the top of the page's gradient */
  --zb-cream:       #F7F4EC; /* text on forest */
  --zb-paper:       #FBFAF5; /* cards, and the foot of the page's gradient */
  --zb-russet:      #C4642F; /* "red dog": the hound, graphics, large display text */
  --zb-russet-text: #A2461C; /* russet for text and buttons. 5.6:1 on cream */
  --zb-gold:        #D4A63A; /* medals, focus ring on dark (5.6:1 on forest) */
  --zb-ink:         #1A2118; /* body text. 15:1 on cream */
  --zb-stone:       #5B6357; /* secondary text. 5.7:1 on cream */

  /* Roles, so a component never names a colour directly. The dark theme
     redefines these and nothing else has to change. */
  /* The page is a gradient, Mist at the top of the window to Paper at the
     foot (body::before). On Mist, ink is 14:1, pasture links 5.5:1,
     stone 5.3:1 and russet text 5.2:1. --zb-bg is its middle, where one
     colour has to stand for the page: the admin, the sticky filters. */
  --zb-bg:           color-mix(in srgb, var(--zb-mist) 50%, var(--zb-paper));
  --zb-bg-top:       var(--zb-mist);
  --zb-bg-bottom:    var(--zb-paper);
  --zb-bg-tint:      var(--zb-mist);
  --zb-bg-inverse:   var(--zb-forest);
  --zb-surface:      var(--zb-paper);
  --zb-surface-raised: #FFFFFF;
  --zb-text:         var(--zb-ink);
  --zb-text-muted:   var(--zb-stone);
  --zb-text-inverse: var(--zb-cream);
  --zb-link:         var(--zb-pasture);
  --zb-link-hover:   var(--zb-forest);
  --zb-accent:       var(--zb-russet-text);
  --zb-rule:         color-mix(in srgb, var(--zb-forest) 14%, transparent);
  --zb-rule-strong:  color-mix(in srgb, var(--zb-forest) 28%, transparent);
  --zb-focus:        var(--zb-forest);
  /* Header and heading ink. A role of its own because the dark theme needs
     the opposite of forest here, and forest-on-forest is invisible. */
  --zb-heading:      var(--zb-forest);
  --zb-header-text:  var(--zb-forest);

  /* -- Type ---------------------------------------------------------------- */

  --zb-font-display: "Fraunces", "Fraunces fallback", Georgia, "Times New Roman", serif;
  --zb-font-text: "Figtree", "Figtree fallback", -apple-system, BlinkMacSystemFont,
                  "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

  /* A fluid scale: each size grows with the viewport between two limits, so
     there are no breakpoint jumps. */
  --zb-size-display: clamp(2.75rem, 1.6rem + 4.6vw, 5.5rem);
  --zb-size-h1:      clamp(2.25rem, 1.6rem + 2.6vw, 3.75rem);
  --zb-size-h2:      clamp(1.75rem, 1.4rem + 1.4vw, 2.6rem);
  --zb-size-h3:      1.375rem;
  --zb-size-h4:      1.125rem;
  --zb-size-body:    1.0625rem;
  --zb-size-small:   0.875rem;
  --zb-size-eyebrow: 0.8125rem;

  --zb-leading-tight: 1.1;
  --zb-leading-snug:  1.3;
  --zb-leading-body:  1.65;

  --zb-measure: 68ch;              /* 60 to 70 characters, the reading width */

  /* -- Space: a 4 px base, so everything lines up ------------------------- */

  --zb-space-1: 0.25rem;
  --zb-space-2: 0.5rem;
  --zb-space-3: 0.75rem;
  --zb-space-4: 1rem;
  --zb-space-5: 1.5rem;
  --zb-space-6: 2rem;
  --zb-space-7: 3rem;
  --zb-space-8: 4rem;

  --zb-gutter: clamp(1rem, 4vw, 2.5rem);
  --zb-section-y: clamp(4rem, 8vw, 8rem);
  --zb-max-width: 1240px;

  /* -- Radius: one scale, three steps -------------------------------------- */

  --zb-radius-sm: 6px;
  --zb-radius:    12px;
  --zb-radius-lg: 20px;
  --zb-radius-pill: 999px;

  /* -- Shadow: two levels, and that is all --------------------------------- */

  --zb-shadow-1: 0 1px 2px rgb(27 58 23 / 0.06), 0 2px 8px rgb(27 58 23 / 0.06);
  --zb-shadow-2: 0 2px 6px rgb(27 58 23 / 0.08), 0 12px 28px rgb(27 58 23 / 0.10);

  /* -- Motion -------------------------------------------------------------- */

  --zb-ease: cubic-bezier(.2, .7, .2, 1);
  --zb-fast: 150ms;
  --zb-mid:  220ms;
  --zb-slow: 300ms;

  /* -- Sport colours, for the events system --------------------------------
     Always paired with a text label and an icon, never colour alone: about
     one man in twelve cannot tell red from green, and this is a schedule
     people drive four hours on. Light values are 4.6:1 or better on cream. */

  --zb-sport-asfa:         #2F6B25;   /* ASFA lure coursing */
  --zb-sport-akc:          #2C6E9B;   /* AKC coursing: LCT, CAT, Fast CAT, CABT */
  --zb-sport-lgra:         #A2461C;   /* LGRA / WRA straight racing */
  --zb-sport-notra:        #6E4A8B;   /* NOTRA oval racing */
  --zb-sport-zassc:        #8A6A12;   /* ZASSC */
  --zb-sport-zassc-fill:   #D4A63A;
  --zb-gold-text:          #6B520E;   /* gold words on a gold tint (5.7:1); the ZASSC colour is only 3.9:1 there */
  --zb-sport-funrun:       #1F7A75;   /* Fun Run / practice */
  --zb-sport-conformation: #4A5568;   /* conformation and specialties */
  --zb-sport-nationals:    #9B2C3A;   /* nationals and grand nationals */
}

/* --------------------------------------------------------------------------
   3. Dark theme

   Follows the operating system by default, with a toggle in the footer that
   wins when the visitor has chosen. data-bs-theme is Bootstrap's own hook, so
   its components come along for free.
   -------------------------------------------------------------------------- */

@media (prefers-color-scheme: dark) {
  :root:not([data-bs-theme="light"]) {
    color-scheme: dark;

    --zb-bg:           #0F1A0D;
    --zb-bg-top:       #0F1A0D;   /* no gradient in the dark */
    --zb-bg-bottom:    #0F1A0D;
    --zb-bg-tint:      #16261A;
    --zb-bg-inverse:   #16261A;
    --zb-surface:      #16261A;
    --zb-surface-raised: #1C3021;
    --zb-text:         #ECEFE6;
    --zb-text-muted:   #A6B29E;
    --zb-text-inverse: #ECEFE6;
    --zb-link:         var(--zb-sage);
    --zb-link-hover:   #C8DDC0;
    --zb-accent:       #E08A57;
    --zb-rule:         color-mix(in srgb, var(--zb-sage) 20%, transparent);
    --zb-rule-strong:  color-mix(in srgb, var(--zb-sage) 36%, transparent);
    --zb-focus:        var(--zb-gold);
    --zb-heading:      var(--zb-text);
    --zb-header-text:  var(--zb-text);

    --zb-shadow-1: 0 1px 2px rgb(0 0 0 / 0.4), 0 2px 8px rgb(0 0 0 / 0.3);
    --zb-shadow-2: 0 2px 6px rgb(0 0 0 / 0.45), 0 12px 28px rgb(0 0 0 / 0.4);

    /* Lifted so each clears 6.7:1 on the dark background. */
    --zb-sport-asfa:         #7FBF6A;
    --zb-sport-akc:          #6FB0DD;
    --zb-sport-lgra:         #E08A57;
    --zb-sport-notra:        #B79BD3;
    --zb-sport-zassc:        #E3BE5C;
    --zb-sport-zassc-fill:   #E3BE5C;
    --zb-gold-text:          #E3BE5C;
    --zb-sport-funrun:       #5FC0B9;
    --zb-sport-conformation: #A7B3C4;
    --zb-sport-nationals:    #E8828F;
  }
}

/* The same values again, for when the visitor has picked dark themselves.
   Duplicated rather than shared because a media query cannot be opted into. */
:root[data-bs-theme="dark"] {
  color-scheme: dark;

  --zb-bg:           #0F1A0D;
  --zb-bg-top:       #0F1A0D;
  --zb-bg-bottom:    #0F1A0D;
  --zb-bg-tint:      #16261A;
  --zb-bg-inverse:   #16261A;
  --zb-surface:      #16261A;
  --zb-surface-raised: #1C3021;
  --zb-text:         #ECEFE6;
  --zb-text-muted:   #A6B29E;
  --zb-text-inverse: #ECEFE6;
  --zb-link:         var(--zb-sage);
  --zb-link-hover:   #C8DDC0;
  --zb-accent:       #E08A57;
  --zb-rule:         color-mix(in srgb, var(--zb-sage) 20%, transparent);
  --zb-rule-strong:  color-mix(in srgb, var(--zb-sage) 36%, transparent);
  --zb-focus:        var(--zb-gold);
  --zb-heading:      var(--zb-text);
  --zb-header-text:  var(--zb-text);

  --zb-shadow-1: 0 1px 2px rgb(0 0 0 / 0.4), 0 2px 8px rgb(0 0 0 / 0.3);
  --zb-shadow-2: 0 2px 6px rgb(0 0 0 / 0.45), 0 12px 28px rgb(0 0 0 / 0.4);

  --zb-sport-asfa:         #7FBF6A;
  --zb-sport-akc:          #6FB0DD;
  --zb-sport-lgra:         #E08A57;
  --zb-sport-notra:        #B79BD3;
  --zb-sport-zassc:        #E3BE5C;
  --zb-sport-zassc-fill:   #E3BE5C;
  --zb-gold-text:          #E3BE5C;
  --zb-sport-funrun:       #5FC0B9;
  --zb-sport-conformation: #A7B3C4;
  --zb-sport-nationals:    #E8828F;
}

/* --------------------------------------------------------------------------
   4. Bootstrap mapping

   The compiled Bootstrap CSS reads these, including the -rgb variants it uses
   for anything with transparency. Setting them here restyles every Bootstrap
   component at once.
   -------------------------------------------------------------------------- */

:root,
[data-bs-theme="light"],
[data-bs-theme="dark"] {
  --bs-body-bg: var(--zb-bg);
  --bs-body-color: var(--zb-text);
  --bs-body-font-family: var(--zb-font-text);
  --bs-body-font-size: var(--zb-size-body);
  --bs-body-line-height: var(--zb-leading-body);
  --bs-emphasis-color: var(--zb-text);
  --bs-secondary-color: var(--zb-text-muted);
  --bs-tertiary-color: var(--zb-text-muted);
  /* The two tinted surfaces: input-group labels ("/journal/2026/" before a
     slug) and disabled fields. Left as Bootstrap's greys, they stayed light
     in dark mode chosen by the system, under light text: 2.1:1. */
  --bs-secondary-bg: var(--zb-bg-tint);
  --bs-tertiary-bg: var(--zb-bg-tint);

  --bs-primary: var(--zb-pasture);
  --bs-primary-rgb: 47, 107, 37;
  --bs-secondary: var(--zb-stone);
  --bs-danger: var(--zb-russet-text);
  --bs-warning: var(--zb-gold);
  --bs-success: var(--zb-pasture);

  --bs-link-color: var(--zb-link);
  --bs-link-color-rgb: 47, 107, 37;
  --bs-link-hover-color: var(--zb-link-hover);
  --bs-link-hover-color-rgb: 27, 58, 23;

  --bs-border-color: var(--zb-rule);
  --bs-border-radius: var(--zb-radius);
  --bs-border-radius-sm: var(--zb-radius-sm);
  --bs-border-radius-lg: var(--zb-radius-lg);
  --bs-border-radius-pill: var(--zb-radius-pill);

  --bs-box-shadow: var(--zb-shadow-1);
  --bs-box-shadow-lg: var(--zb-shadow-2);
}

[data-bs-theme="dark"] {
  --bs-link-color-rgb: 169, 201, 160;
  --bs-link-hover-color-rgb: 200, 221, 192;
}

/* Buttons. Bootstrap builds every state from these, so overriding the
   variables keeps the component and changes only the colours. */

.btn {
  --bs-btn-font-family: var(--zb-font-text);
  --bs-btn-font-weight: 600;
  --bs-btn-border-radius: var(--zb-radius);
  --bs-btn-padding-x: 1.25rem;
  --bs-btn-padding-y: 0.625rem;
  --bs-btn-focus-box-shadow: 0 0 0 3px color-mix(in srgb, var(--zb-pasture) 35%, transparent);
  transition: background-color var(--zb-fast) var(--zb-ease),
              border-color var(--zb-fast) var(--zb-ease),
              transform var(--zb-fast) var(--zb-ease);
}

/* btn-meadow is the journal editor's name for its main button (it came
   from the ZASSC site). Undefined here, its "Publish" confirm rendered as
   bare text. */
.btn-primary,
.btn-meadow {
  --bs-btn-bg: var(--zb-pasture);
  --bs-btn-border-color: var(--zb-pasture);
  --bs-btn-color: var(--zb-cream);
  --bs-btn-hover-bg: var(--zb-forest);
  --bs-btn-hover-border-color: var(--zb-forest);
  --bs-btn-hover-color: var(--zb-cream);
  --bs-btn-active-bg: var(--zb-forest);
  --bs-btn-active-border-color: var(--zb-forest);
  --bs-btn-active-color: var(--zb-cream);
  --bs-btn-disabled-bg: var(--zb-meadow);
  --bs-btn-disabled-border-color: var(--zb-meadow);
  --bs-btn-disabled-color: var(--zb-cream);
}

/* The link colour, not the fixed brand green: pasture on cream, sage in
   dark mode. Pasture on the dark background was 2.8:1. */
.btn-outline-primary {
  --bs-btn-color: var(--zb-link);
  --bs-btn-border-color: var(--zb-link);
  --bs-btn-bg: transparent;
  --bs-btn-hover-bg: var(--zb-link);
  --bs-btn-hover-border-color: var(--zb-link);
  --bs-btn-hover-color: var(--zb-bg);
  --bs-btn-active-bg: var(--zb-link-hover);
  --bs-btn-active-border-color: var(--zb-link-hover);
  --bs-btn-active-color: var(--zb-bg);
  --bs-btn-disabled-color: var(--zb-meadow);
  --bs-btn-disabled-border-color: var(--zb-meadow);
}

/* The quiet button: "View", "Cancel", "Preview". Bootstrap's own grey is
   4.3:1 on cream, under AA; muted is 5.6:1 there and lifts in dark mode. */
.btn-outline-secondary {
  --bs-btn-color: var(--zb-text-muted);
  --bs-btn-border-color: var(--zb-text-muted);
  --bs-btn-bg: transparent;
  --bs-btn-hover-bg: var(--zb-bg-tint);
  --bs-btn-hover-border-color: var(--zb-text);
  --bs-btn-hover-color: var(--zb-text);
  --bs-btn-active-bg: var(--zb-bg-tint);
  --bs-btn-active-border-color: var(--zb-text);
  --bs-btn-active-color: var(--zb-text);
  --bs-btn-disabled-color: var(--zb-text-muted);
  --bs-btn-disabled-border-color: var(--zb-rule-strong);
}

/* "Yes, delete it". The accent is russet on cream and a lifted russet in
   dark mode, so this follows the theme whichever way it was chosen. */
.btn-danger {
  --bs-btn-bg: var(--zb-accent);
  --bs-btn-border-color: var(--zb-accent);
  --bs-btn-color: var(--zb-bg);
  --bs-btn-hover-bg: color-mix(in srgb, var(--zb-accent) 82%, var(--zb-text));
  --bs-btn-hover-border-color: color-mix(in srgb, var(--zb-accent) 82%, var(--zb-text));
  --bs-btn-hover-color: var(--zb-bg);
  --bs-btn-active-bg: color-mix(in srgb, var(--zb-accent) 70%, var(--zb-text));
  --bs-btn-active-border-color: color-mix(in srgb, var(--zb-accent) 70%, var(--zb-text));
  --bs-btn-active-color: var(--zb-bg);
}
.text-danger { color: var(--zb-accent) !important; }   /* Bootstrap's own is !important too */

/* On a photo or a forest band: cream outline, readable on anything behind it. */
.btn-outline-cream {
  --bs-btn-color: var(--zb-cream);
  --bs-btn-border-color: color-mix(in srgb, var(--zb-cream) 70%, transparent);
  --bs-btn-bg: color-mix(in srgb, var(--zb-forest) 35%, transparent);
  --bs-btn-hover-bg: var(--zb-cream);
  --bs-btn-hover-border-color: var(--zb-cream);
  --bs-btn-hover-color: var(--zb-forest);
  --bs-btn-active-bg: var(--zb-cream);
  --bs-btn-active-color: var(--zb-forest);
  --bs-btn-focus-box-shadow: 0 0 0 3px color-mix(in srgb, var(--zb-gold) 55%, transparent);
  backdrop-filter: blur(2px);
}

/* Forms */
.form-control,
.form-select {
  --bs-body-bg: var(--zb-surface);
  border-color: var(--zb-rule-strong);
  border-radius: var(--zb-radius);
  padding: 0.625rem 0.875rem;
  font-size: var(--zb-size-body);
}

.form-control:focus,
.form-select:focus {
  border-color: var(--zb-pasture);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--zb-pasture) 22%, transparent);
}

.form-label {
  font-weight: 600;
  margin-bottom: 0.3125rem;
}

.form-check-input:checked {
  background-color: var(--zb-pasture);
  border-color: var(--zb-pasture);
}

/* Alerts: each also carries an icon and a word, never colour alone. */
.alert {
  --bs-alert-border-radius: var(--zb-radius);
  border-left-width: 6px;
  padding: var(--zb-space-4) var(--zb-space-5);
}

/* --------------------------------------------------------------------------
   5. Base elements
   -------------------------------------------------------------------------- */

/* The page: light green at the top of the window, fading to white at the
   foot, the same on every screenful however long the page is. A layer of
   its own, fixed behind everything, because background-attachment: fixed
   repaints the whole page on every scroll, and phones ignore it anyway.
   The window's own colour, under it, is what an overscroll shows. */
html {
  background: var(--zb-bg-bottom);
}

body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  background: linear-gradient(to bottom, var(--zb-bg-top), var(--zb-bg-bottom));
  pointer-events: none;
}

body {
  background: transparent;
  color: var(--zb-text);
  font-family: var(--zb-font-text);
  font-size: var(--zb-size-body);
  line-height: var(--zb-leading-body);
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4, .zb-display {
  font-family: var(--zb-font-display);
  font-optical-sizing: auto;        /* Fraunces adjusts its shapes to the size */
  font-weight: 600;
  line-height: var(--zb-leading-tight);
  color: var(--zb-heading);
  /* Stops a heading leaving one word alone on the last line. */
  text-wrap: balance;
  margin-block: 0 var(--zb-space-4);
}

.zb-display { font-size: var(--zb-size-display); font-weight: 700; }
h1 { font-size: var(--zb-size-h1); font-weight: 700; }
h2 { font-size: var(--zb-size-h2); }
h3 { font-size: var(--zb-size-h3); line-height: var(--zb-leading-snug); }
h4 { font-size: var(--zb-size-h4); line-height: var(--zb-leading-snug); }

p { text-wrap: pretty; margin-block: 0 var(--zb-space-4); }
p:last-child { margin-bottom: 0; }

/* Links in running text keep their underline, since colour alone must never
   be the only sign of a link, but it is a quiet one until the pointer
   arrives, when it darkens and thickens. Menus, cards and buttons set their
   own decoration and are untouched by this. */
a {
  color: var(--zb-link);
  text-decoration-color: color-mix(in srgb, currentColor 45%, transparent);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
  transition: color var(--zb-fast) var(--zb-ease),
              text-decoration-color var(--zb-fast) var(--zb-ease),
              text-decoration-thickness var(--zb-fast) var(--zb-ease);
}
a:hover {
  color: var(--zb-link-hover);
  text-decoration-color: currentColor;
  text-decoration-thickness: 2px;
}

small, .zb-small { font-size: var(--zb-size-small); }

/* Registered names are set in small caps: "UKC CH Zoiboyz Hudson at
   Starswift SC FCh TKN" reads as a name rather than as shouting. */
.zb-regname {
  font-variant-caps: all-small-caps;
  letter-spacing: 0.04em;
  color: var(--zb-text-muted);
}

/* Schedules and results line up only with tabular figures. */
.zb-tabular,
table { font-variant-numeric: tabular-nums; }

.zb-eyebrow {
  font-size: var(--zb-size-eyebrow);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--zb-text-muted);
  margin-bottom: var(--zb-space-2);
}

.zb-lede {
  font-size: 1.25rem;
  line-height: 1.5;
  color: var(--zb-text-muted);
}

/* Pull quote, in Fraunces italic, with the opening quote hung into the margin. */
.zb-pullquote {
  font-family: var(--zb-font-display);
  font-style: italic;
  font-size: clamp(1.375rem, 1.1rem + 1.2vw, 2rem);
  line-height: 1.35;
  color: var(--zb-heading);
  border-left: 3px solid var(--zb-russet);
  padding-left: var(--zb-space-5);
  margin-block: var(--zb-space-6);
  hanging-punctuation: first;
  max-width: 34ch;
}

/* the pull quote follows --zb-heading too */

hr, .zb-rule { border: 0; border-top: 1.5px solid var(--zb-rule); margin-block: var(--zb-space-6); }

::selection { background: var(--zb-sage); color: var(--zb-forest); }

/* --------------------------------------------------------------------------
   6. Layout
   -------------------------------------------------------------------------- */

.zb-container {
  width: 100%;
  max-width: var(--zb-max-width);
  margin-inline: auto;
  padding-inline: var(--zb-gutter);
}

.zb-section { padding-block: var(--zb-section-y); }
.zb-section--tight { padding-block: clamp(2rem, 4vw, 3.5rem); }

/* Alternating bands give the page rhythm. Text never sits on a photo. */
.zb-section--tint { background: var(--zb-bg-tint); }
.zb-section--forest {
  background: var(--zb-forest);
  color: var(--zb-text-inverse);
  /* Muted text on forest is the dark theme's muted, not the cream page's.
     Without this, an eyebrow or lede in a forest band is stone on forest:
     2.0:1, unreadable. #A6B29E on forest is 5.7:1. */
  --zb-text-muted: #A6B29E;
}
.zb-section--forest h1,
.zb-section--forest h2,
.zb-section--forest h3 { color: var(--zb-cream); }
.zb-section--forest a { color: var(--zb-sage); }

/* A 2 to 3% paper grain on the cream sections, as in the wordmark. Drawn,
   not an image file, so it costs nothing to download. */
.zb-section--grain {
  background-image:
    radial-gradient(circle at 1px 1px, rgb(27 58 23 / 0.025) 1px, transparent 0);
  background-size: 4px 4px;
}

.zb-prose { max-width: var(--zb-measure); }
.zb-prose > * + * { margin-top: var(--zb-space-4); }

.zb-grid { display: grid; gap: var(--zb-space-5); }
.zb-grid--2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr)); }
.zb-grid--3 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr)); }
.zb-grid--4 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr)); }

/* --------------------------------------------------------------------------
   7. Header

   Transparent over the home hero, turning solid forest after 80 px of scroll
   (site.js adds .is-stuck). Solid from the start on every other page.
   -------------------------------------------------------------------------- */

.zb-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--zb-surface);
  border-bottom: 1.5px solid var(--zb-rule);
  transition: background-color var(--zb-mid) var(--zb-ease),
              box-shadow var(--zb-mid) var(--zb-ease);
}

.zb-header--over-hero {
  position: fixed;
  inset-inline: 0;
  background: transparent;
  border-bottom-color: transparent;
}

.zb-header--over-hero.is-stuck {
  background: var(--zb-forest);
  box-shadow: var(--zb-shadow-1);
}

.zb-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--zb-space-4);
  max-width: var(--zb-max-width);
  margin-inline: auto;
  padding: var(--zb-space-3) var(--zb-gutter);
}

.zb-header__logo { display: block; line-height: 0; }

/* No display here on purpose. The two logo artworks below switch themselves
   on and off, and a `display: block` on this shared rule would outrank them:
   `.zb-header__logo img` is a class plus an element, which beats a class on
   its own however far down the file it sits. */
.zb-header__logo img {
  width: 200px;             /* never below the brand minimum of 160 px */
  max-width: 46vw;
  height: auto;
  transition: width var(--zb-mid) var(--zb-ease);
}

/* Each artwork's own proportions, from its viewBox, so the box is the right
   height before the file arrives. The logos load after the first paint
   (header.php), and with the 200×56 the attributes used to say, the header
   grew 4 px when the real 729×220 drawing landed and pushed the page down.
   The reverse artwork carries a forest margin, hence a deeper box. */
.zb-header__logo--dark { aspect-ratio: 729 / 220; }
.zb-header__logo--light { aspect-ratio: 809 / 300; }

/* Scrolled: a compact logo, as the brief asks. */
.zb-header.is-stuck .zb-header__logo img { width: 160px; }

/* Which logo shows.

   There are two artworks: the normal one, drawn for light backgrounds, and
   the reverse one for forest and for photographs. The header can be any of
   three things — light bar, dark bar, or transparent over the hero — so the
   choice is made here rather than in PHP, which cannot know the theme.

   Default (light theme, solid bar): the normal logo. */
.zb-header__logo--light { display: none; }
.zb-header__logo--dark { display: block; }

/* Over the hero photo, and on the forest bar once scrolled: the reverse one. */
.zb-header--over-hero .zb-header__logo--dark { display: none; }
.zb-header--over-hero .zb-header__logo--light { display: block; }

/* Dark theme: the bar is dark, so the reverse logo everywhere. */
[data-bs-theme="dark"] .zb-header__logo--dark { display: none; }
[data-bs-theme="dark"] .zb-header__logo--light { display: block; }

@media (prefers-color-scheme: dark) {
  :root:not([data-bs-theme="light"]) .zb-header__logo--dark { display: none; }
  :root:not([data-bs-theme="light"]) .zb-header__logo--light { display: block; }
}

/* Narrow screens get the menu button; wide ones get the full navigation.
   The switch lives at the end of this section, after both are defined, so
   neither can quietly override it. */
.zb-nav { display: none; }

.zb-nav > ul {
  display: flex;
  align-items: center;
  gap: var(--zb-space-5);
  margin: 0;
  padding: 0;
  list-style: none;
}

.zb-nav a:not(.btn) {
  display: inline-block;
  padding-block: var(--zb-space-2);
  color: var(--zb-header-text);
  font-weight: 600;
  text-decoration: none;
}

.zb-header--over-hero:not(.is-stuck) .zb-nav a:not(.btn) { color: var(--zb-cream); }
.zb-header--over-hero.is-stuck .zb-nav a:not(.btn) { color: var(--zb-cream); }

/* The underline grows from the left rather than appearing all at once. */
.zb-nav a:not(.btn)::after {
  content: "";
  display: block;
  height: 2px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--zb-mid) var(--zb-ease);
}

.zb-nav a:not(.btn):hover::after,
.zb-nav a:not(.btn):focus-visible::after,
.zb-nav a[aria-current="page"]::after { transform: scaleX(1); }

.zb-nav__out { width: 0.7em; height: 0.7em; opacity: 0.75; }

/* The dropdowns. Bootstrap's JS drives them; these are the colours. */
.zb-nav .dropdown-menu {
  --bs-dropdown-bg: var(--zb-surface);
  --bs-dropdown-border-color: var(--zb-rule);
  --bs-dropdown-border-radius: var(--zb-radius);
  --bs-dropdown-link-color: var(--zb-text);
  --bs-dropdown-link-hover-bg: var(--zb-bg-tint);
  --bs-dropdown-link-hover-color: var(--zb-heading);
  --bs-dropdown-link-active-bg: var(--zb-bg-tint);
  --bs-dropdown-link-active-color: var(--zb-heading);
  --bs-dropdown-padding-y: var(--zb-space-2);
  --bs-dropdown-item-padding-y: 0.5rem;
  box-shadow: var(--zb-shadow-2);
  font-weight: 500;
}

/* The menu button on a phone: a 48 px target, as everywhere else. */
.zb-header__burger {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 48px;
  padding-inline: var(--zb-space-3);
  background: none;
  border: 1.5px solid var(--zb-rule-strong);
  border-radius: var(--zb-radius);
  color: inherit;
  font-family: inherit;
  font-weight: 600;
}

.zb-header--over-hero:not(.is-stuck) .zb-header__burger {
  color: var(--zb-cream);
  border-color: color-mix(in srgb, var(--zb-cream) 55%, transparent);
}

/* The one place the header changes shape. Last in the section, so it wins. */
@media (min-width: 62em) {
  .zb-nav { display: block; }
  .zb-header__burger { display: none; }
}

/* The offcanvas menu, in from the right. */
.zb-offcanvas {
  --bs-offcanvas-bg: var(--zb-bg);
  --bs-offcanvas-color: var(--zb-text);
  --bs-offcanvas-width: min(22rem, 86vw);
  --bs-offcanvas-border-color: var(--zb-rule);
}

.zb-offcanvas .nav-link {
  display: block;
  min-height: 48px;           /* large tap targets: this is used outdoors */
  padding: var(--zb-space-3) 0;
  border-bottom: 1px solid var(--zb-rule);
  color: var(--zb-text);
  font-size: 1.125rem;
  font-weight: 600;
}

.zb-offcanvas .nav-link--sub {
  padding-left: var(--zb-space-4);
  font-size: 1rem;
  font-weight: 500;
  color: var(--zb-text-muted);
}

/* "Next at the ranch", pinned at the top of the phone menu. */
.zb-offcanvas__next {
  display: block;
  margin-bottom: var(--zb-space-4);
  padding: var(--zb-space-4);
  background: var(--zb-forest);
  color: var(--zb-cream);
  border-radius: var(--zb-radius);
  text-decoration: none;
}

.zb-offcanvas__next:hover,
.zb-offcanvas__next:focus-visible { background: var(--zb-pine); color: var(--zb-cream); }

/* --------------------------------------------------------------------------
   8. Footer
   -------------------------------------------------------------------------- */

.zb-footer {
  background: var(--zb-forest);
  color: var(--zb-sage);
  padding-block: var(--zb-space-8) var(--zb-space-6);
}

.zb-footer__inner {
  max-width: var(--zb-max-width);
  margin-inline: auto;
  padding-inline: var(--zb-gutter);
}

.zb-footer__top {
  display: grid;
  gap: var(--zb-space-6);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr));
}

.zb-footer__brand { max-width: 22rem; }
.zb-footer__crest { width: 96px; height: auto; }    /* brand minimum is 72 px */

.zb-footer__tagline {
  font-family: var(--zb-font-display);
  font-style: italic;
  font-size: 1.25rem;
  line-height: 1.35;
  color: var(--zb-cream);
  margin-block: var(--zb-space-4) var(--zb-space-2);
}

.zb-footer h2 {
  font-size: var(--zb-size-eyebrow);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--zb-sage);
  font-family: var(--zb-font-text);
  margin-bottom: var(--zb-space-3);
}

.zb-footer ul { list-style: none; margin: 0; padding: 0; }
.zb-footer li + li { margin-top: var(--zb-space-2); }
.zb-footer a { color: var(--zb-cream); text-decoration: none; }
.zb-footer a:hover,
.zb-footer a:focus-visible { color: var(--zb-cream); text-decoration: underline; }

.zb-footer__next {
  display: block;
  padding: var(--zb-space-4);
  background: var(--zb-pine);
  border-radius: var(--zb-radius);
  text-decoration: none;
}

.zb-footer__bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--zb-space-4);
  margin-top: var(--zb-space-7);
  padding-top: var(--zb-space-5);
  border-top: 1px solid color-mix(in srgb, var(--zb-sage) 25%, transparent);
  font-size: var(--zb-size-small);
}

/* On forest, the focus ring is gold: forest on forest cannot be seen. */
.zb-footer :focus-visible,
.zb-section--forest :focus-visible,
.zb-header--over-hero :focus-visible { outline-color: var(--zb-gold); }

/* The light/dark toggle. */
.zb-theme-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 44px;
  padding: 0 var(--zb-space-3);
  background: none;
  border: 1px solid color-mix(in srgb, var(--zb-sage) 40%, transparent);
  border-radius: var(--zb-radius-pill);
  color: var(--zb-sage);
  font: inherit;
  font-size: var(--zb-size-small);
  cursor: pointer;
}

.zb-theme-toggle:hover { color: var(--zb-cream); border-color: var(--zb-sage); }

/* --------------------------------------------------------------------------
   9. Components
   -------------------------------------------------------------------------- */

/* -- Skip link and focus -------------------------------------------------- */

.zb-skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: var(--zb-space-3) var(--zb-space-5);
  background: var(--zb-forest);
  color: var(--zb-cream);
  border-radius: 0 0 var(--zb-radius) 0;
}

.zb-skip-link:focus { left: 0; }

:focus-visible {
  outline: 2px solid var(--zb-focus);
  outline-offset: 3px;
  border-radius: 2px;
}

/* A photo that opens larger in the lightbox (zoomable() in helpers.php,
   assets/js/gallery.js). Here rather than in gallery.css, which arrives
   later, so the link is the photo's own shape from the start and its focus
   ring follows the rounded corners. */
.zb-zoom,
.news-zoom {
  display: block;
  border-radius: var(--zb-radius);
  cursor: zoom-in;
}
.zb-zoom img, .news-zoom img { transition: filter var(--zb-fast) var(--zb-ease); }
.zb-zoom:hover img, .news-zoom:hover img { filter: brightness(1.06); }

/* -- Icons ---------------------------------------------------------------- */

.zb-icon {
  width: 1em;
  height: 1em;
  vertical-align: -0.125em;
  fill: currentColor;
  flex: none;
}

/* A duotone icon's background layer: russet at 40% behind forest. */
.zb-icon-duotone .zb-icon-secondary { fill: var(--zb-russet); opacity: 0.4; }

.zb-icon-2x { width: 1.5em; height: 1.5em; }
.zb-icon-3x { width: 2.25em; height: 2.25em; }

/* -- Sport chips ----------------------------------------------------------
   Colour, an icon and a word together. An event can carry more than one. */

.zb-chip {
  --chip-color: var(--zb-text-muted);
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.1875rem 0.625rem;
  border: 1.5px solid var(--chip-color);
  border-radius: var(--zb-radius-pill);
  /* Filled, not see-through: the ZASSC gold and the Fun Run teal are
     4.3:1 on the page's Mist green, and 5:1 or better on this. */
  background: var(--zb-surface-raised);
  color: var(--chip-color);
  font-size: var(--zb-size-small);
  font-weight: 600;
  line-height: 1.4;
  white-space: nowrap;
}

.zb-chip--asfa         { --chip-color: var(--zb-sport-asfa); }
.zb-chip--akc          { --chip-color: var(--zb-sport-akc); }
.zb-chip--lgra         { --chip-color: var(--zb-sport-lgra); }
.zb-chip--notra        { --chip-color: var(--zb-sport-notra); }
.zb-chip--zassc        { --chip-color: var(--zb-sport-zassc); }
.zb-chip--funrun       { --chip-color: var(--zb-sport-funrun); }
.zb-chip--conformation { --chip-color: var(--zb-sport-conformation); }
.zb-chip--nationals    { --chip-color: var(--zb-sport-nationals); }

/* Status badges, which say the word as well as showing the colour. */
.zb-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.3125rem;
  padding: 0.125rem 0.5rem;
  border-radius: var(--zb-radius-sm);
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.02em;
}

.zb-badge--ranch     { background: var(--zb-forest); color: var(--zb-cream); }
.zb-badge--tentative { background: color-mix(in srgb, var(--zb-gold) 25%, transparent); color: var(--zb-gold-text); }
.zb-badge--cancelled { background: color-mix(in srgb, var(--zb-accent) 15%, transparent); color: var(--zb-accent); text-decoration: line-through; }
.zb-badge--postponed { background: var(--zb-bg-tint); color: var(--zb-text-muted); }

/* -- Cards ---------------------------------------------------------------- */

.zb-card {
  display: flex;
  flex-direction: column;
  background: var(--zb-surface);
  border: 1.5px solid var(--zb-rule);
  border-radius: var(--zb-radius-lg);
  overflow: hidden;
  transition: transform var(--zb-mid) var(--zb-ease),
              box-shadow var(--zb-mid) var(--zb-ease);
}

.zb-card__media {
  position: relative;
  aspect-ratio: 4 / 3;      /* one crop everywhere, so grids line up */
  overflow: hidden;
  background: var(--zb-bg-tint);
}

.zb-card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* object-position comes from each photo's stored focal point, written as a
     style attribute, which is why the CSP allows style-src-attr. */
  transition: transform var(--zb-slow) var(--zb-ease);
}

.zb-card__body { padding: var(--zb-space-5); flex: 1; }
.zb-card__title { font-size: var(--zb-size-h3); margin-bottom: var(--zb-space-2); }
.zb-card__text { color: var(--zb-text-muted); margin: 0; }

.zb-card--portrait .zb-card__media { aspect-ratio: 4 / 5; }   /* hound cards */
.zb-card--feature .zb-card__media { aspect-ratio: 3 / 2; }

/* The whole card is the link, but only the title is in the tab order. */
.zb-card__link::after {
  content: "";
  position: absolute;
  inset: 0;
}

.zb-card { position: relative; }

.zb-card:hover { transform: translateY(-2px); box-shadow: var(--zb-shadow-2); }
.zb-card:hover .zb-card__media img { transform: scale(1.03); }
.zb-card:has(.zb-card__link:focus-visible) { box-shadow: var(--zb-shadow-2); }

.zb-card__arrow { transition: transform var(--zb-mid) var(--zb-ease); }
.zb-card:hover .zb-card__arrow { transform: translateX(4px); }

/* -- Event rows ------------------------------------------------------------ */

.zb-event {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--zb-space-4);
  align-items: start;
  padding-block: var(--zb-space-4);
  border-bottom: 1px solid var(--zb-rule);
}

.zb-event__date {
  display: grid;
  place-items: center;
  width: 4.25rem;
  padding: var(--zb-space-2);
  background: var(--zb-bg-tint);
  border-radius: var(--zb-radius);
  text-align: center;
  line-height: 1.15;
}

.zb-event__day-name { font-size: 0.75rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--zb-text-muted); }
.zb-event__day { font-family: var(--zb-font-display); font-size: 1.5rem; font-weight: 700; color: var(--zb-heading); }
.zb-event__month { font-size: 0.75rem; font-weight: 600; text-transform: uppercase; color: var(--zb-text-muted); }


.zb-event__title { font-size: 1.125rem; font-weight: 600; margin: 0 0 0.25rem; }
.zb-event__meta { display: flex; flex-wrap: wrap; gap: 0.375rem var(--zb-space-3); align-items: center; font-size: var(--zb-size-small); color: var(--zb-text-muted); }

/* -- Hound card ------------------------------------------------------------ */

.zb-hound__name { font-family: var(--zb-font-display); font-size: var(--zb-size-h3); line-height: var(--zb-leading-snug); font-weight: 700; margin: 0; }
.zb-hound__regname { display: block; margin-top: 0.125rem; }
.zb-hound__facts { display: flex; flex-wrap: wrap; gap: 0.375rem var(--zb-space-3); margin-top: var(--zb-space-2); font-size: var(--zb-size-small); color: var(--zb-text-muted); }

/* A title abbreviation, explained on hover and on focus. */
abbr.zb-title {
  text-decoration: underline dotted;
  text-underline-offset: 0.2em;
  cursor: help;
}

/* -- Photo galleries -------------------------------------------------------
   A hound's photographs, and the ones under a journal post. Each opens
   larger in the lightbox (assets/js/gallery.js). */

.zb-gallery {
  display: grid;
  gap: var(--zb-space-3);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 13rem), 1fr));
  margin-block: var(--zb-space-4);
}

.zb-gallery__item { margin: 0; }
.zb-gallery__item img { width: 100%; height: auto; border-radius: var(--zb-radius); display: block; }
.zb-gallery__item figcaption { margin-top: 0.35rem; color: var(--zb-text-muted); font-size: var(--zb-size-small); }

/* -- Empty and loading states ---------------------------------------------
   Every list has one. "No events match those filters" beats a blank page. */

.zb-empty {
  padding: var(--zb-space-7) var(--zb-space-5);
  text-align: center;
  color: var(--zb-text-muted);
  border: 1.5px dashed var(--zb-rule-strong);
  border-radius: var(--zb-radius-lg);
}

.zb-empty__icon { color: var(--zb-meadow); margin-bottom: var(--zb-space-3); }

.zb-skeleton {
  background: linear-gradient(90deg, var(--zb-bg-tint) 25%,
              color-mix(in srgb, var(--zb-bg-tint) 60%, var(--zb-surface)) 37%,
              var(--zb-bg-tint) 63%);
  background-size: 400% 100%;
  animation: zb-shimmer 1.4s ease-in-out infinite;
  border-radius: var(--zb-radius-sm);
}

@keyframes zb-shimmer {
  from { background-position: 100% 50%; }
  to { background-position: 0 50%; }
}

/* -- Announcement bar ------------------------------------------------------ */

.zb-announcement {
  background: var(--zb-bg-tint);
  border-bottom: 1.5px solid var(--zb-rule);
  font-size: var(--zb-size-small);
}

.zb-announcement__inner {
  display: flex;
  align-items: center;
  gap: var(--zb-space-3);
  max-width: var(--zb-max-width);
  margin-inline: auto;
  padding: var(--zb-space-3) var(--zb-gutter);
}

.zb-announcement p { margin: 0; flex: 1; }
.zb-announcement--notice  { background: color-mix(in srgb, var(--zb-sage) 30%, var(--zb-bg-tint)); }
.zb-announcement--warning { background: color-mix(in srgb, var(--zb-gold) 22%, var(--zb-bg)); }
.zb-announcement--urgent  { background: color-mix(in srgb, var(--zb-russet) 18%, var(--zb-bg)); }

.zb-announcement__dismiss {
  background: none;
  border: 0;
  padding: var(--zb-space-2);
  min-width: 44px;
  min-height: 44px;
  color: inherit;
  cursor: pointer;
}

/* The beta ribbon. Impossible to mistake for the live site, which is the point. */
.zb-env-ribbon {
  margin: 0;
  padding: 0.375rem 1rem;
  background: var(--zb-gold);
  color: var(--zb-forest);
  font-size: var(--zb-size-eyebrow);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-align: center;
  text-transform: uppercase;
}

/* --------------------------------------------------------------------------
   10. Motion

   Short, soft, and never decorative for its own sake. Everything here is
   switched off for anyone who has asked their system for less movement —
   which section 11 of the brief requires, and which section 10 of this file
   must therefore never contradict.
   -------------------------------------------------------------------------- */

/* Sections rise and fade in once, the first time they are scrolled to.
   The .js is added by site.js the moment it runs, so a visitor whose
   JavaScript is off or blocked sees every section rather than a page of
   invisible content. */
.js .zb-reveal {
  opacity: 0;
  transform: translateY(12px);
  transition: opacity var(--zb-slow) var(--zb-ease),
              transform var(--zb-slow) var(--zb-ease);
}

.js .zb-reveal.is-in { opacity: 1; transform: none; }

/* The home hero's very slow push-in, once. */
.zb-hero__media img { animation: zb-push-in 20s var(--zb-ease) forwards; }

@keyframes zb-push-in {
  from { transform: scale(1); }
  to { transform: scale(1.06); }
}

/* Soft page changes between documents. The page cross-fades; the header,
   which is the same on both, is captured on its own and so stays put
   instead of blinking. The timing is the site's own, not the browser's. */
@view-transition { navigation: auto; }

.zb-header { view-transition-name: zb-header; }

::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
  animation-duration: var(--zb-mid);
  animation-timing-function: var(--zb-ease);
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  .zb-reveal { opacity: 1; transform: none; }
  .zb-hero__media img { animation: none; }
  .zb-card:hover { transform: none; }
  .zb-card:hover .zb-card__media img { transform: none; }

  @view-transition { navigation: none; }
}

/* --------------------------------------------------------------------------
   Print: event pages, the season schedule and hound profiles print cleanly.
   The detail is in print.css; this is the part that applies everywhere.
   -------------------------------------------------------------------------- */

@media print {
  .zb-header, .zb-footer, .zb-announcement, .zb-env-ribbon, .zb-skip-link { display: none; }
  body { background: #fff; color: #000; font-size: 11pt; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 9pt; word-break: break-all; }
}

/* -- "Change this" ---------------------------------------------------------
   Only ever drawn for somebody signed in (see includes/footer.php). It sits
   out of the way bottom-right rather than beside each heading: a row of
   little pencils turns reading the site into using the admin, and Tom and
   KC read these pages far more often than they edit them. */

.zb-editthis {
  position: fixed;
  right: clamp(0.75rem, 3vw, 1.5rem);
  bottom: clamp(0.75rem, 3vw, 1.5rem);
  z-index: 40;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 48px;
  padding: 0 1.125rem;
  background: var(--zb-forest);
  color: var(--zb-cream);
  border: 2px solid var(--zb-gold);
  border-radius: var(--zb-radius-pill);
  font-size: 1rem;
  font-weight: 600;
  text-decoration: none;
  box-shadow: var(--zb-shadow-2);
}

.zb-editthis:hover { background: var(--zb-meadow); color: var(--zb-cream); }
.zb-editthis:focus-visible { outline: 3px solid var(--zb-gold); outline-offset: 3px; }

/* On a phone it would sit on top of the last line of the page, so the page
   is given room underneath it rather than the button being moved. */
@media (max-width: 36em) {
  body:has(.zb-editthis) .zb-footer { padding-bottom: 5rem; }
}

@media print {
  .zb-editthis { display: none; }
}
