/* ============================================================
   Teapup — style.css
   Character: a bright farmhouse mudroom in Indiana with a puppy
   on a white blanket. Warm white, charcoal, one deep indigo.
   Unhurried, plain-spoken, proud of every year since 1989.
   Type: Outfit (display) + Albert Sans (body), self-hosted.
   Archetype: full-bleed photographic home; contact-sheet puppy
   grid; index/ledger rows for breeds, dogs and litters. One
   indigo block per page at most. No stats strip, no CTA ribbon.
   ============================================================ */

@font-face{font-family:'Outfit';font-style:normal;font-weight:300 700;font-display:swap;src:url(fonts/outfit-normal-latin.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:'Outfit';font-style:normal;font-weight:300 700;font-display:swap;src:url(fonts/outfit-normal-latin-ext.woff2) format('woff2');unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}
@font-face{font-family:'Albert Sans';font-style:normal;font-weight:300 800;font-display:swap;src:url(fonts/albertsans-normal-latin.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:'Albert Sans';font-style:normal;font-weight:300 800;font-display:swap;src:url(fonts/albertsans-normal-latin-ext.woff2) format('woff2');unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}
@font-face{font-family:'Albert Sans';font-style:italic;font-weight:300 800;font-display:swap;src:url(fonts/albertsans-italic-latin.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:'Albert Sans';font-style:italic;font-weight:300 800;font-display:swap;src:url(fonts/albertsans-italic-latin-ext.woff2) format('woff2');unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}

:root{
  --paper:#fffaf5;          /* the warm off-white the Wix site already used */
  --paper-deep:#f3ece2;     /* one step warmer, for the footer and quiet surfaces */
  --white:#ffffff;
  --ink:#26231f;            /* the charcoal of the wordmark. 15.5:1 on paper */
  --ink-soft:#615850;       /* secondary text. 6.0:1 on paper */
  --indigo:#302f51;         /* the site's own heading colour, promoted to the accent. 11.3:1 */
  --indigo-deep:#232140;
  --indigo-tint:#e9e8f1;
  --on-indigo:#fffaf5;
  --on-indigo-dim:#c9c7dc;  /* 7.4:1 on indigo */
  --paw:#cfc9c1;            /* the paw print grey, warmed. Decorative only, never text */
  --ok:#3e6b4e;             /* available */
  --hold:#8a5a0b;           /* reserved / expecting */
  --rule:1px solid rgba(38,35,31,.14);
  --rule-strong:1px solid rgba(38,35,31,.3);
  --maxw:1360px;            /* header, footer and most pages */
  /* the page's own left/right margin, matching .wrap. Only valid on an element that
     spans the full page width, because the percentage resolves against its parent. */
  --page-gutter:max(4%, calc((100% - 1360px) / 2));
  --maxw-narrow:1120px;     /* listing pages, so a ledger of six does not look stretched */
  --maxw-reading:70ch;
  --display:'Outfit',system-ui,'Segoe UI',Arial,sans-serif;
  --body:'Albert Sans',system-ui,'Segoe UI',Arial,sans-serif;
  --shadow:0 18px 40px -26px rgba(38,35,31,.45);
  --radius:6px;
  color-scheme: light only !important;
}

*{box-sizing:border-box}
/* No grey box on tap. A phone draws a rectangle over whatever was touched, which on a
   round breed photo or a photo tile is a rectangle over something that is not one, and it
   lingers after the finger has gone. It is feedback, though, so it is replaced rather than
   simply removed: the thing touched dips for as long as the finger is on it, in its own
   shape. :focus-visible is untouched, because that ring is how a keyboard finds its way
   around and has nothing to do with this. */
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}
@media (hover:none){
  .tile:active,.card:active,.breed-strip a:active,.lrow:active,.review:active{opacity:.72}
  .btn:active{transform:translateY(1px)}
  .breed-strip a:active .bs-photo{transform:scale(.95)}
  .bs-photo{transition:transform .12s ease}
}
@media (hover:none) and (prefers-reduced-motion:reduce){
  .btn:active{transform:none}
  .breed-strip a:active .bs-photo{transform:none}
}
body{margin:0;font-family:var(--body);font-size:1.0625rem;line-height:1.6;color:var(--ink);background:var(--paper);-webkit-font-smoothing:antialiased}
@media (min-width:1400px){body{font-size:1.125rem}}
img{max-width:100%;height:auto;display:block}
a{color:var(--indigo);text-decoration-thickness:1px;text-underline-offset:3px}
a:hover{color:var(--indigo-deep)}
:focus-visible{outline:3px solid var(--indigo);outline-offset:3px;border-radius:2px}
h1,h2,h3,h4{font-family:var(--display);font-weight:500;line-height:1.1;color:var(--ink);margin:0 0 .5em;letter-spacing:-.01em;text-wrap:balance}
h1{font-size:clamp(2.3rem,4.6vw,4.1rem)}
h2{font-size:clamp(1.75rem,3vw,2.6rem)}
h3{font-size:clamp(1.25rem,1.8vw,1.55rem)}
h4{font-size:1.1rem;font-weight:600}
p{margin:0 0 1.1em;max-width:70ch}
p:last-child{margin-bottom:0}
/* Cap the MEASURE of prose lists only. A blanket `ul,ol{max-width:70ch}` also caps
   every list-based component, and it did: the reviews grid, the how-it-works steps and
   the certificate grid were all silently held to 793px inside a 1302px container, which
   is what made the reviews section look broken. */
.prose ul,.prose ol,.prose li{max-width:70ch}
.wrap{width:min(92%,var(--maxw));margin-inline:auto}
/* A page whose content is ENTIRELY narrower than the banner is centred, so the spare
   space falls evenly either side rather than piling up on the right. A page that mixes
   narrow and full-width sections uses the plain .wrap throughout instead, so its
   sections line up with each other. */
.wrap.narrow{max-width:var(--maxw-narrow)}
/* A section that breaks out of the wrap to the full viewport. Used once, for the hero. */
/* main is already full width, so a section only needs this when it sits inside a
   constrained parent. Using it on a top-level section overflows by the scrollbar. */
.full-bleed{width:100vw;margin-inline:calc(50% - 50vw)}
/* Grid and flex children default to min-width:auto, so a child whose content cannot
   shrink (the carousel thumbnail strip, a long unbroken link) stretches its track past
   the viewport and gives the whole page horizontal scroll. .detail was missing from
   this list and did exactly that on every puppy and adult page at 375px: the single
   mobile column measured 566px inside a 353px container. */
.wrap > *,.grid > *,.split > *,.lrow > *,.pfacts > *,.detail > *,.detail-media,.detail-side,.carousel,.adults > *,.adult > *,.hero-grid > *,.creds.logos > *{min-width:0}
.kicker{font-family:var(--display);font-size:.8rem;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--indigo);display:block;margin-bottom:.7rem}
.lede{font-size:clamp(1.15rem,1.6vw,1.35rem);line-height:1.5;color:var(--ink-soft);max-width:62ch}
.muted{color:var(--ink-soft)}
.small{font-size:.92rem}
.num{font-variant-numeric:tabular-nums}
.prose{max-width:70ch}
/* A page that is nothing but a document (the guarantee, the questions, the privacy
   policy, the policy half of how it works) fills its column instead of stopping at 70ch
   inside it. Measured at 1920px the text used to end 735px from the right edge while the
   section began 393px from the left, which is what made it look like it had fallen over
   to one side. It now ends 407px from the right, against 393px on the left.

   Scoped to .prose that IS the .wrap, for two reasons: the About page has a .prose block
   in the right half of a two-column feature and that one still wants a 70ch measure, and
   the container keeps whatever width it already had, so the body stays in line with the
   heading above it rather than running out past it.

   The lines are longer than the typographic ideal, so the leading goes up with them. What
   makes a long line hard is losing your place on the way back to the left margin, and
   space between the lines is what fixes that. */
.wrap.prose{max-width:var(--maxw)}
.wrap.narrow.prose{max-width:var(--maxw-narrow)}
.wrap.prose p,.wrap.prose ul,.wrap.prose ol,.wrap.prose li,.wrap.prose blockquote{max-width:none}
.wrap.prose p,.wrap.prose li{line-height:1.72}
.prose h2{margin-top:2.2rem}
/* a heading that opens its block takes the section padding as its gap, not padding plus 2.2rem (2026-11-07) */
.prose > :first-child{margin-top:0}
.prose h3{margin-top:1.8rem}
.prose li{margin:.35em 0}
.prose blockquote{margin:1.5rem 0;padding:.2rem 0 .2rem 1.2rem;border-left:3px solid var(--indigo);color:var(--ink);font-style:italic}
.placeholder{background:#fff2c9;border:1px dashed #b98a00;color:#5b4300;padding:.15em .45em;border-radius:3px;font-style:normal;font-weight:600}

/* ---------- skip link ---------- */
.skip{position:absolute;top:-4rem;left:.75rem;z-index:100;background:var(--indigo);color:var(--on-indigo);padding:.65rem 1rem;border-radius:0 0 4px 4px;font-weight:700;text-decoration:none;transition:top .12s ease}
.skip:focus{top:0}

/* ---------- buttons ---------- */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:.5rem;min-height:48px;padding:.75rem 1.4rem;border-radius:var(--radius);font-family:var(--display);font-weight:600;font-size:1rem;letter-spacing:.01em;text-decoration:none;border:2px solid transparent;cursor:pointer;line-height:1.1;transition:background .15s ease,color .15s ease,border-color .15s ease}
.btn-primary{background:var(--indigo);color:var(--on-indigo);border-color:var(--indigo)}
.btn-ghost{background:transparent;color:var(--indigo);border-color:var(--indigo)}
.btn-light{background:var(--paper);color:var(--indigo);border-color:var(--paper)}
.btn-row{display:flex;flex-wrap:wrap;gap:.75rem;margin-top:1.4rem}
/* On a phone a row of buttons wrapped to one per line anyway, but each one kept its own
   width: the home page alone had 143, 156, 164, 196 and 252px stacked down the left, and
   a column of ragged buttons reads as a mistake rather than a set. They are one width
   here, which is the column's. 560 and not 900: between the two there is room for two
   buttons side by side, and stretching those to full width would be worse. */
@media (max-width:560px){
  .btn-row{flex-direction:column;align-items:stretch}
  .btn-row > .btn{width:100%}
  /* .center-block is itself a centred flex column, so its children shrink to fit and the
     row was 163px wide however its buttons were told to behave. The row takes the column
     first, then the buttons take the row. */
  .center-block .btn-row{align-items:stretch;width:100%}
  /* the same rule for the one button that is not in a row: the send button on the
     application and the contact form, which sat at 197px in a 290px form while every
     field above it ran the full width */
  .form > .btn{width:100%}
  .bleed-media .btn-row{align-items:stretch}
}
.btn svg{width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
@media (hover:hover){
  .btn-primary:hover{background:var(--indigo-deep);border-color:var(--indigo-deep);color:var(--on-indigo)}
  .btn-ghost:hover{background:var(--indigo-tint)}
  .btn-light:hover{background:var(--white)}
  .tile:hover img,.lrow:hover .l-thumb{filter:brightness(1.03)}
  a.lrow:hover{background:var(--paper-deep)}
}
@media (hover:none){
  .btn-primary:hover{background:var(--indigo)}
  .btn-ghost:hover{background:transparent}
  .tile:hover img,.lrow:hover .l-thumb{filter:none}
}

/* ---------- header ---------- */
.site-head{position:sticky;top:0;z-index:50;background:rgba(255,250,245,.94);border-bottom:var(--rule)}
.head-row{display:flex;align-items:center;justify-content:space-between;gap:1.5rem;min-height:74px}
.brand{display:inline-flex;align-items:center;gap:.75rem;text-decoration:none;color:var(--ink)}
.brand img{height:44px;width:auto}
.brand .brand-text{font-family:var(--display);font-weight:600;font-size:1.2rem;letter-spacing:.02em}
.nav-links{display:flex;align-items:center;gap:1.35rem;margin:0;padding:0;list-style:none}
.nav-links a{font-family:var(--display);font-weight:500;font-size:1rem;color:var(--ink);text-decoration:none;padding:.65rem .2rem;display:inline-block;border-bottom:2px solid transparent}
.nav-links a[aria-current="page"]{border-bottom-color:var(--indigo);color:var(--indigo)}
.nav-links a.nav-cta{background:var(--indigo);color:var(--on-indigo);padding:.7rem 1.2rem;border-radius:var(--radius);border-bottom:0;margin-left:.4rem}
.nav-links a.nav-cta[aria-current="page"]{background:var(--indigo-deep)}
.nav-toggle{display:none;background:none;border:2px solid var(--ink);border-radius:var(--radius);width:48px;height:48px;padding:0;cursor:pointer;position:relative}
.nav-toggle span{display:block;width:22px;height:2px;background:var(--ink);margin:5px auto;transition:transform .25s ease,opacity .25s ease}
/* The Apply button is excluded because its BACKGROUND is --indigo: the shared rule painted
   indigo text onto the indigo button, so on hover it turned solid purple and the word Apply
   vanished. It darkens a shade instead, as on the sister site (Alex, 2026-11-07). */
@media (hover:hover){
  .nav-links a:not(.nav-cta):hover{color:var(--indigo)}
  .nav-links a.nav-cta:hover{background:var(--indigo-deep);color:var(--on-indigo)}
}
/* "Retired adults" replaced "Breeds" in the menu (2026-10-22), and between the drawer and a
   wide window the two-word labels wrapped onto two lines. Labels never wrap, and from 901 to
   1180 the gaps and type tighten so the whole row still fits beside the logo at 901. */
.nav-links a{white-space:nowrap}
@media (min-width:901px) and (max-width:1180px){
  .nav-links{gap:.6rem}
  .nav-links a{font-size:.92rem}
  .nav-links a.nav-cta{padding:.7rem .9rem;margin-left:.2rem}
}

/* ---------- mobile drawer (see design-and-build.md: no backdrop-filter, overlay is a sibling) ---------- */
@media (max-width:900px){
  .site-head{background:var(--paper)}
  .head-row{min-height:66px}
  /* The menu button sat 88px in from the right edge, which is what "about an inch" was.
     <nav> holds the drawer, the drawer is position:fixed, so at this width <nav> is a
     zero-width flex item; space-between still gives a zero-width item its share of the
     free space, and the button became the middle of three. Out of the flex flow it goes,
     and the button takes the corner. */
  .head-row > nav{position:absolute}
  .nav-toggle{margin-left:auto}
  .brand img{height:34px}
  .nav-toggle{display:block;z-index:80}
  /* visibility, not just the transform. A drawer parked off-screen with transform alone
     is still in the tab order, so tabbing out of the header walked into nine invisible
     links. visibility:hidden takes it out of the tab order; the delay lets it slide shut
     before it disappears, and visibility is one of the few properties that transitions
     discretely rather than being ignored. */
  .nav-links{position:fixed;top:0;right:0;bottom:0;height:100vh;height:100dvh;width:min(82%,340px);background:var(--paper);flex-direction:column;align-items:stretch;justify-content:flex-start;padding:5.4rem 1.6rem 2rem;gap:.2rem;transform:translateX(100%);visibility:hidden;transition:transform .3s ease,visibility 0s linear .3s;box-shadow:-18px 0 50px -20px rgba(38,35,31,.5);z-index:70;overflow-y:auto}
  .nav-links.open{transform:none;visibility:visible;transition:transform .3s ease,visibility 0s}
  .nav-links a{font-size:1.2rem;padding:.75rem .2rem;border-bottom:var(--rule);width:100%}
  .nav-links a[aria-current="page"]{border-bottom:var(--rule);color:var(--indigo)}
  .nav-links a.nav-cta{margin:1rem 0 0;text-align:center;justify-content:center;border-bottom:0}
  /* the drawer colours the current page indigo, which on the Apply button put indigo
     text on the indigo-deep fill at 1.21:1 while you were on /apply */
  .nav-links a.nav-cta[aria-current="page"]{color:var(--on-indigo)}
  .nav-toggle.active span:nth-child(1){transform:translateY(7px) rotate(45deg)}
  .nav-toggle.active span:nth-child(2){opacity:0}
  .nav-toggle.active span:nth-child(3){transform:translateY(-7px) rotate(-45deg)}
  .nav-overlay{position:fixed;inset:0;background:rgba(38,35,31,.5);opacity:0;visibility:hidden;transition:opacity .3s ease,visibility .3s ease;z-index:60}
  .nav-overlay.open{opacity:1;visibility:visible}

}
@media (prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important;scroll-behavior:auto!important}}

/* ---------- hero ----------
   Sized to the first screen and shaped to the photograph. The picture is nearly square
   (1.19), and the puppy's face fills most of it, so a full-width band has to crop the
   muzzle away to fit above the fold: at 1500px a 2:1 band shows barely half the head.
   A media panel on the right lands at roughly the photo's own ratio instead, which means
   cover takes almost nothing off it, the face is whole, the puppy sits right of the copy,
   and the whole hero fits under the header without scrolling. */
.hero{position:relative;display:grid;overflow:hidden;background:var(--paper);
  grid-template-columns:var(--page-gutter) minmax(0,clamp(17rem,27vw,24rem)) minmax(0,1fr);
  /* one explicit row, not auto: an auto row takes its height from the photo, and the
     photo takes its height from the row, so the row grew to the photo's natural 1402px
     and height:100% on the image resolved against that instead of the hero */
  grid-template-rows:minmax(0,1fr);
  height:clamp(460px, min(calc(100vh - 76px), 60vw), 960px)}
/* The whole photo, never cropped: the box carries the source's own 1800x1511 ratio and
   is as tall as the hero, so her nose and paws are always in frame. It is pinned to the
   right, which puts her face well clear of the copy: she starts 21.7% into the photo, so
   at 1440x900 she begins at x 672 while the copy ends at 602. */
/* The photo runs from the right edge back across most of the hero. Three terms cap it,
   and all three are load-bearing. 80% of the width keeps the puppy clear of the copy at
   normal widths (she sits 21.7% into the photo, so her left edge lands at the width
   minus 78.3% of the photo). 1580px takes over past about 1950, where the page gutter
   pushes the copy right faster than the photo's left edge moves, and also holds the box
   under 1.65:1 once the hero height has capped. 165vh-125px holds that same 1.65:1 shape
   on short windows, past which a vertical crop starts eating her.

   THE BOX RATIO IS THE WHOLE ARGUMENT. She occupies y 0.234 to 0.910 of the source, so
   the crop has to show at least 0.676 of the photo's height or part of her is gone, and
   a cover crop shows exactly 1.191/ratio of it. At 1.76:1 that is 0.679 — the puppy and
   nothing else, which is why her paws sat flush against the bottom border on a 1990x1080
   screen and why 72% used to be the only object-position that worked. 1.65:1 shows 0.722
   instead, and the 0.046 of slack that buys is spent below her at object-position 80%:
   17px above her ears and 46px of blanket under her paws at 1580x960, never under 4.8%
   of the hero anywhere. The hero grows 900 -> 960 to pay for it, which only binds on
   windows taller than 1036px, where there is room.

   Every number here is solved, not chosen. docs/hero-check.py has the closed form and
   runs it over 22 window shapes; change any of them and run it again. */
.hero-media{grid-area:1/1/2/-1;grid-row:1;position:relative;justify-self:end;align-self:stretch;
  width:min(80%,1580px,calc(165vh - 125px));height:100%;min-height:0;z-index:2}
.hero-media{transform-origin:100% 50%;animation:heroFocus 2200ms cubic-bezier(.25,.6,.2,1)}
.hero-media img{width:100%;height:100%;object-fit:cover;object-position:50% 80%}
/* The seam. A flipped copy of the same photo, same box and same crop so every row lines
   up, stood against the photo's left edge. Clipped to the left fifth of the frame in its
   own coordinates, which the flip puts on the side touching the photo: the puppy starts
   at 21.7%, so nothing of her is ever mirrored. The join is pixel-identical, and the
   copy fades into the edge strip across its outer half so a mirrored plank or crate
   corner never reads as a reflection. clip-path and mask are applied before the
   transform, which is why they are written for the un-flipped element. */
.hero-mirror{position:absolute;top:0;right:100%;width:100%;height:100%;
  transform:scaleX(-1);clip-path:inset(0 80% 0 0);
  -webkit-mask-image:linear-gradient(90deg,#000 0,#000 7%,rgba(0,0,0,0) 20%);
  mask-image:linear-gradient(90deg,#000 0,#000 7%,rgba(0,0,0,0) 20%)}
/* A 1.19:1 photo cannot fill a 1.8:1 hero, so the photo's own left edge carries the
   ground out to the screen. img/home/hero-edge.webp is one averaged slice of the
   leftmost 3% of the source (492 bytes), stretched across the hero: every x in the strip
   is the colour of the photo's first pixel column at that height, so the seam is exact
   and the wood-to-blanket change the photo already has continues. Written by
   write_hero_edge() in scripts/make_images.py. */
.hero-edge{position:absolute;inset:0;z-index:0;
  background:url(img/home/hero-edge.webp) 0 0/100% 100% no-repeat}
/* She comes into focus on load. Two deliberate choices: the origin is the right edge so
   the scale never pushes the photo past the hero and clips the edge the layout promises
   to keep, and nothing fades from opacity 0. A paused animation holds its first frame,
   so a from-frame at opacity 0 means a hidden or backgrounded tab renders no hero at
   all; blur alone degrades to a soft photo instead of a blank one. */
@keyframes heroFocus{
  from{filter:blur(20px) saturate(.88);transform:scale(1.02)}
  to{filter:blur(0) saturate(1);transform:scale(1)}}
.hero-copy{grid-column:2;grid-row:1;position:relative;z-index:3;display:flex;flex-direction:column;
  justify-content:center;padding:clamp(1.5rem,3vw,2.5rem) clamp(1.5rem,3vw,2.75rem) clamp(1.5rem,3vw,2.5rem) 0}
/* the veil is only around the words and fades out well before she starts, so nothing
   washes over the puppy herself */
/* Solid across every pixel the text can occupy, then fading out entirely in the 3rem
   overhang past the copy column. The fade has to finish before the puppy starts: at
   1440 the column ends at 441, the fade runs 441 to 489, and she begins at 514. Fading
   inside the column instead put the kicker's tail at 44% veil and cost 5 stops of
   contrast. 70% and not more: the ground is the photo's own edge and should still read
   as the photo, and 70% over the strip's darkest row still gives 5.1:1 for the lede,
   9.3:1 for the kicker and 11.4:1 for the heading. */
.hero-copy::before{content:"";position:absolute;top:0;bottom:0;left:-100vw;right:-3rem;z-index:-1;
  background:linear-gradient(90deg,rgba(255,250,245,.7) 0,rgba(255,250,245,.7) calc(100% - 3rem),
    rgba(255,250,245,0) 100%)}
.hero-copy h1{max-width:12ch;margin-bottom:.4rem;font-size:clamp(2rem,3.4vw,3.1rem)}
.hero-copy .lede{max-width:30ch;font-size:clamp(1.02rem,1.3vw,1.15rem)}
.hero-copy .btn-row{margin-top:1.3rem}
/* Below 1180 the copy column would reach across the photo and land on the puppy, so the
   two become two rows with the whole photo on top. */
@media (max-width:1180px){
  .hero{grid-template-columns:1fr;grid-template-rows:auto auto;height:auto;background:var(--paper)}
  .hero-edge{display:none}
  .hero-media{grid-area:auto;grid-column:1;grid-row:auto;order:-1;justify-self:stretch;
    width:auto;height:auto}
  .hero-media{animation:none}
  .hero-media img{width:100%;height:auto;aspect-ratio:1800/1511;object-position:50% 50%;display:block}
  .hero-mirror{display:none}
  /* The wide hero carries the photograph's own ground out to the edge of the screen with
     hero-edge, so the picture never ends on a hard border. Stacked, the same photograph
     ended in a straight line across the page and read as a picture pasted onto cream.
     The ground continues the other way instead: the paper rises through the bottom of the
     frame, so the photo hands over to the page rather than stopping at it. The colour is
     the page's own, so the join needs no sampled strip the way the horizontal one does. */
  /* THE MOBILE HERO IS CROPPED TO THE PUPPY, AND THE COPY RISES INTO IT.

     It was 711px of an 812px screen before a word of the page proper. Two thirds of that
     was the picture showing the whole 1800x1511 frame, and the puppy only occupies y
     0.234 to 0.910 of it: a quarter of the height above her ears and a tenth below her
     paws, both empty. A 1.527 box shows 78% of the frame, and object-position 83% centres
     that window on her, leaving 5.2% of the frame clear above her ears and the same below
     her paws. Every figure is solved from PUPPY_TOP and PUPPY_BOTTOM in docs/hero-check.py,
     so it holds at any width: 69px off the picture at 375px.

     Then the copy rises into the bottom of the frame. Her paws land 93.3% down the
     cropped box, so the words may come up 6.7% of it before they reach her, which is
     -4.37% as a margin (a percentage margin resolves against the WIDTH, and the box is
     width / 1.527). The picture carries the words: full strength where they begin and the
     page's own paper by the time it is under them, so the photo hands over to the text
     rather than stopping above it. Nothing is ever painted over the puppy. */
  .hero{display:flex;flex-direction:column;height:auto}
  .hero-media{position:relative;z-index:0}
  .hero-media img{aspect-ratio:1.527;object-position:50% 83%}
  .hero-media::after{content:"";position:absolute;left:0;right:0;bottom:0;height:26%;
    pointer-events:none;z-index:1;
    background:linear-gradient(rgba(255,250,245,0) 0,rgba(255,250,245,.45) 52%,
      rgba(255,250,245,.9) 80%,var(--paper) 100%)}
  .hero-copy{position:relative;z-index:2;margin-top:-4.37%}
  .hero-copy{grid-column:1;grid-row:auto;padding:1.5rem 4vw 2rem}
  .hero-copy::before{display:none}
  .hero-copy h1,.hero-copy .lede{max-width:none}
}
@media (prefers-reduced-motion:reduce){
  .hero-media{animation:none}
}
.hero-slim{padding:clamp(3rem,6.5vw,5.5rem) 0 clamp(.75rem,1.5vw,1.25rem)}
.hero-slim h1{margin-bottom:.4rem}

/* ---------- an introduction that fills the container instead of running down it ----------
   Two paragraphs stacked in one 62ch column pushed the first dog most of a screen down the
   retired-adults page. This uses the full width between the margins, which is the point,
   but it does it as two columns rather than as one very long line: the container is
   1560px, and at this type size a single line across it measures about 140 characters,
   which is roughly twice what anybody reads comfortably. Two columns is the same
   horizontal reach and about half the height. Below 900px there is not enough width for
   two, so they stack again. */
.lede-cols{display:grid;gap:clamp(1rem,2.4vw,2.75rem);align-items:start}
@media (min-width:900px){.lede-cols{grid-template-columns:repeat(2,minmax(0,1fr))}}
.lede-cols > .lede{max-width:none;margin:0}

/* ---------- a two-column band with the media on the right ----------
   The picture used to run off the right edge, which put it past the margin every other
   section respects and left the two columns unbalanced. It stops at the same border as
   the rest of the page now, and both columns are centred against each other. */
.bleed{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);
  gap:clamp(1.5rem,4vw,3.5rem);align-items:center;
  width:min(92%,var(--maxw));margin-inline:auto}
.bleed > *{min-width:0}
.bleed-media{display:flex;flex-direction:column;gap:1rem}
.bleed-media figure{margin:0;border-radius:var(--radius);overflow:hidden;background:var(--paper-deep)}
.bleed-media img{width:100%;aspect-ratio:5/4;object-fit:cover;display:block}
.bleed-media .btn-row{margin:0;justify-content:flex-end}
@media (max-width:900px){
  .bleed{grid-template-columns:1fr;gap:1.5rem}
  .bleed-media{order:-1}
  .bleed-media .btn-row{justify-content:flex-start}
}

/* ---------- sections ---------- */
/* Sections were reading as one continuous stack. The base gap between two of them is
   twice this padding, and `tight` only trims the top rather than halving it. */
.section{padding:clamp(3rem,5vw,4.5rem) 0}
/* Three cases, in this order because they all have the same specificity and the later
   rule wins:
     .section.tight        the section is the top of the page, or sits under a page
                           header, so it hugs what is above it
     .section + .section   the section follows another section, so it gets the full
                           rhythm no matter what `tight` says. Without this, `tight`
                           mid-page (it is used on 20 sections) pulled two unrelated
                           sections to within 130px of each other
     .hero + .section      the homepage hero runs to the edge of the screen, so the
                           section under it needs more than the usual gap */
.section.tight{padding-top:clamp(1.25rem,2.5vw,2rem)}
.section + .section{padding-top:clamp(3rem,5vw,4.5rem)}
/* the hero runs to the edge of the screen, so the section under it needs more than the
   usual top padding before it reads as a separate thing */
.hero + .section{padding-top:clamp(3.5rem,5.5vw,5.25rem)}
.section-head{display:flex;align-items:flex-end;justify-content:space-between;gap:1rem 2rem;flex-wrap:wrap;margin-bottom:clamp(1.5rem,3vw,2.5rem)}
.section-head h2{margin:0}
/* a second list in the same section, such as Born recently under Expecting on the litters
   page: one list in two groups, so it takes a group gap rather than the 202px section
   rhythm, which left an empty band under a short first row (2026-10-22 review) */
.section-head.follow{margin-top:clamp(3rem,6vw,4.5rem)}
.section-head .more{font-family:var(--display);font-weight:600;white-space:nowrap}
.ruled{border-top:var(--rule-strong)}
.split{display:grid;grid-template-columns:1fr 1fr;gap:clamp(1.5rem,4vw,4rem);align-items:start}
.split.narrow-first{grid-template-columns:minmax(0,5fr) minmax(0,7fr)}
@media (max-width:900px){.split,.split.narrow-first{grid-template-columns:1fr}}

/* ---------- contact-sheet puppy grid ----------
   Flex with an explicit per-row basis rather than grid auto-fit or auto-fill: auto-fill
   leaves empty tracks and auto-fit stretches a short final row, so the same card would
   render at several widths down one page. */
/* ---------- breed and puppy finder ----------
   Replaces a row of filter chips. Chips only ever filtered in place; this searches by
   breed or by a puppy's name and takes you to that page, which is what someone typing
   "Westie" actually wants. */
.finder{max-width:420px;width:100%;margin:0}
.finder-head{align-items:flex-end;gap:1rem 2.5rem;margin-bottom:clamp(1.25rem,2.5vw,1.75rem)}
.finder-head h2{margin:0}
/* Pinned under the header on a phone. The puppies page is 30,000px long there, so the one
   control that gets you off it was scrolled away within a screen and the only way back to
   it was all the way up. 66px is the mobile header's own height, so it parks directly
   underneath rather than under or over it. It needs its own background (a sticky element
   is transparent and the cards would run under it), and a z-index below the header's 50
   and above the combobox list's 30. */
@media (max-width:900px){
  .finder-head{position:sticky;top:66px;z-index:40;background:var(--paper);
    margin-left:-4vw;margin-right:-4vw;padding:.6rem 4vw .75rem;
    border-bottom:var(--rule);box-shadow:0 8px 14px -12px rgba(38,35,31,.5)}
  .finder-head h2{font-size:1.15rem}
}
@media (max-width:760px){.finder{max-width:none}}
.finder-label{display:block;font-family:var(--display);font-size:.72rem;font-weight:600;
  letter-spacing:.12em;text-transform:uppercase;color:var(--ink-soft);margin-bottom:.35rem}
.combo{position:relative}
.combo input{width:100%;font:inherit;font-size:1rem;padding:.7rem 3rem .7rem .9rem;min-height:48px;
  border:1.5px solid rgba(38,35,31,.35);border-radius:var(--radius);background:var(--white);color:var(--ink)}
.combo input:focus{outline:3px solid var(--indigo);outline-offset:1px;border-color:var(--indigo)}
.combo-toggle{position:absolute;right:.35rem;top:50%;transform:translateY(-50%);width:44px;height:44px;
  display:flex;align-items:center;justify-content:center;border:0;background:none;color:var(--ink-soft);cursor:pointer}
.combo-toggle svg{stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.combo-list{position:absolute;top:calc(100% + .35rem);left:0;right:0;z-index:30;margin:0;padding:.3rem;
  list-style:none;max-height:19rem;overflow-y:auto;background:var(--white);border:1px solid rgba(38,35,31,.22);
  border-radius:var(--radius);box-shadow:var(--shadow)}
/* THE FINDER DID NOT FILTER, AND HAD NOT SINCE IT WAS BUILT. main.js sets o.hidden on
   every option that does not match, which is correct, but display:flex on the option
   beats the browser's own [hidden]{display:none}: typing "westie" marked 52 of the 60
   options hidden and left all 60 on the screen. Same class of bug as a .drawer that
   opened over its own work area. !important here rather than :not([hidden]) on the
   selector, so it also covers .combo-empty and anything else added to the list later. */
[hidden]{display:none!important}
.combo-list [role="option"]{display:flex;align-items:baseline;justify-content:space-between;gap:1rem;
  padding:.6rem .75rem;border-radius:4px;cursor:pointer;min-height:44px}
.combo-list [role="option"].is-active{background:var(--indigo-tint)}
.combo-list .o-name{font-family:var(--display);font-weight:600}
.combo-list .o-meta{color:var(--ink-soft);font-size:.9rem;white-space:nowrap}
.combo-empty{padding:.75rem;color:var(--ink-soft)}
.sheet{--cols:4;--gap:1.5rem;display:flex;flex-wrap:wrap;gap:var(--gap);margin:0;padding:0;list-style:none}
.sheet > *{flex:0 1 calc((100% - (var(--cols) - 1) * var(--gap)) / var(--cols))}
/* Five across on the puppies index only, above 1280px. The container caps at 1360px, so
   a tile is 219px at 1280, 235 at 1366, 249 at 1440 and 256 from 1536 up, against 322 at
   four across. Below 1280 the rules underneath take over at four, three, two and one, so
   the modifier only ever widens a row that already had room (Alex, 2026-09-21). */
@media (min-width:1280px){.sheet.five{--cols:5;--gap:1.25rem}}
@media (max-width:1100px){.sheet{--cols:3;--gap:1.25rem}}
@media (max-width:760px){.sheet{--cols:2;--gap:1rem}}
/* The row the "see more" button sits in. It only ever appears on a phone, and main.js is
   what decides that, so there is nothing to do here but give it room and centre it. */
.reveal-row{justify-content:center;margin-top:1.25rem}
.tile{display:block;text-decoration:none;color:var(--ink);background:var(--white);border:var(--rule);border-radius:var(--radius);overflow:hidden}
.tile img{aspect-ratio:4/5;width:100%;object-fit:cover;background:var(--paper-deep)}
.tile{position:relative}
/* The stamp is centred on the PHOTOGRAPH, not on the tile. It used to be positioned
   against .tile, whose height includes the caption, so it sat 51px below the middle of the
   picture at every width including the desktop. A tall 4:5 photograph hid that with 145px
   of margin to spare; a square one on a 320px phone left 3px, and one caption wrapping to
   an extra line would have dropped ADOPTED onto the text. Anchoring it to the photo's own
   box is right at any aspect ratio and any breakpoint (Alex, 2026-09-22). */
.tile-shot{display:block;position:relative}
.tile.adopted img{filter:grayscale(1) contrast(.92) brightness(1.04)}
.tile-stamp{
  position:absolute;left:0;right:0;top:calc(50% - 1.35rem);
  display:block;text-align:center;
  font-family:var(--display);font-size:.9rem;font-weight:700;letter-spacing:.22em;
  text-transform:uppercase;color:#fff;
  background:rgba(38,35,31,.62);padding:.42rem 0;
  /* The photo is 4/5 and the caption sits under it, so the band can run edge to edge
     without covering the name. White on this ground is 7.4:1. */
}
@media (prefers-reduced-motion:no-preference){.tile img{transition:filter .2s ease}}
.tile{display:flex;flex-direction:column}
.tile-cap{display:flex;flex-direction:column;flex:1 0 auto}
.tile-cap{padding:.75rem .9rem .9rem;border-top:var(--rule)}
.tile-cap .t-name{font-family:var(--display);font-weight:600;font-size:1.08rem;display:flex;justify-content:space-between;gap:.5rem;align-items:baseline}
/* A long name and a price that must not wrap will together overrun a narrow tile: at 320px
   two up, 26 of 49 names did on the other site. The price keeps its line and the name gives
   way, which is the right way round because the price is the shorter and the more precise
   of the two. Costs nothing whenever there is room.
   Alex changed this for phones on 2026-11-06: once prices read "$1,800 + tax", 12 of 31
   names on a breed page were cut to an ellipsis at 390px. Below 561px the name now wraps
   onto a second line and the price drops under it when the two do not fit (the block
   after the two-across rule below). The ellipsis stays above that width. */
.tile-cap .t-name{min-width:0}
.tile-cap .t-name > span:first-child{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
.tile-cap .t-price{font-weight:500;font-size:1rem;white-space:nowrap}
.tile-cap .t-meta{color:var(--ink-soft);font-size:.9rem;margin-top:.15rem}
.tile-cap .t-meta{margin-bottom:.55rem}
.tile-cap .t-status{font-family:var(--display);font-size:.78rem;font-weight:600;letter-spacing:.1em;text-transform:uppercase;margin-top:auto;color:var(--ok)}
.t-status::before,.l-status::before{content:"";display:inline-block;width:8px;height:8px;border-radius:50%;background:currentColor;margin-right:.45rem;vertical-align:1px}
.t-status.reserved,.l-status.reserved,.t-status.expecting,.l-status.expecting{color:var(--hold)}
.t-status.adopted,.l-status.adopted,.t-status.retired,.l-status.retired{color:var(--ink-soft)}
.tile.is-hidden{display:none}
/* TWO ACROSS ON A PHONE, with a square photograph (Alex, 2026-09-22). One tile per row
   cost most of a screen for one puppy. Two up with a 1:1 photograph and a tighter caption
   puts 6 puppies on a 390x844 screen against 1.

   The square crop SHOWS MORE of each dog rather than less: every cover photograph on both
   sites is landscape, 62 of 62 here and 49 of 49 on the sister site, and a landscape
   photograph keeps 80% of its width in a 1:1 box against 64% in the 4:5 box. Both boxes
   crop the same axis, so the focal points in data/photo-focus.json stay right.

   It also fixes the resolution. The sizes attribute has always read 46vw on a phone, which
   assumes two across, so the one-up layout was displaying a 345px tile while telling the
   browser to fetch for 179px. Shared verbatim with the sister site. */
@media (max-width:420px){
  .sheet{--cols:2;--gap:.55rem}
  .tile img{aspect-ratio:1}
  .tile-cap{padding:.45rem .55rem .55rem}
  .t-name{font-size:.94rem}
  .t-price{font-size:.88rem}
  .t-meta{font-size:.78rem;margin-bottom:.3rem}
  .tile-cap .t-status{font-size:.68rem}
}
/* Phone tiles: the name wraps rather than cutting to an ellipsis (Alex, 2026-11-06). The
   price wraps under the name as one unit when both do not fit on a line. Tiles in a row
   stay one height because the grid stretches them and the status keeps margin-top:auto. */
@media (max-width:560px){
  .tile-cap .t-name{flex-wrap:wrap;row-gap:0}
  .tile-cap .t-name > span:first-child{white-space:normal;overflow:visible;text-overflow:clip;overflow-wrap:anywhere}
}

.empty-note{padding:1.5rem 0;color:var(--ink-soft)}
/* An empty list under its own heading sits close to it, and its button follows the line
   (2026-11-06 review: the breed pages with no puppies had a gap and no next step). */
.section-head + .empty-note{padding-top:.5rem}
.empty-note + .btn-row{margin-top:0}

/* ---------- breed strip (home) ----------
   Nine breeds as one row of round portraits rather than nine cards. It is a signpost on
   the way to /breeds, not a listing, so it should cost one screen-inch and no more. */
/* ---------- the breed wheel ----------
   Nine breeds fitted on one line and a tenth started a second, so the section was one
   shape at nine and another at ten. It turns instead: one line whatever the count, and
   the ones off the end come round.

   It is a SCROLLING BOX that main.js advances, not a transform it animates. A transform
   cannot be swiped: on a phone the wheel was something to watch rather than something to
   use, and there was no way to go back to a breed that had just gone past. Moving
   scrollLeft instead means the native gesture works for free, momentum and all, and the
   script simply stops pushing while a finger is down and picks up from wherever it was
   let go.

   The track is the list twice over, cloned by main.js, and one copy plus one gap is the
   distance at which the two are indistinguishable, so the loop wraps there and the join
   never shows. The clone is aria-hidden with its links out of the tab order, so a
   keyboard and a screen reader meet each breed once.

   Without JavaScript, and for anybody who has asked for less movement, there is no clone
   and nothing advances: it is still a row you scroll or swipe, with every breed in it.
   That is also what it is before the script runs. */
/* TWO BOXES, and the split is the point. The fades used to live on the box that scrolls,
   and an absolutely positioned child of a scroll container scrolls with its content: it
   shares the coordinate system, so left:0 is the start of the STRIP and not the left of
   the window onto it. At rest that looks right. Push the strip and the left fade travels
   with it, and a band that is opaque paper at one edge and clear at the other comes to
   rest in the middle of the row, its opaque edge a hard line across whichever circle it
   landed on. The outer box is the window and carries the fades; the inner one scrolls. */
.wheel{position:relative}
/* the breed wheel again at the top of the Puppies page (Deborah, 2026-11-05) */
.puppies-wheel{margin-top:clamp(1.25rem,3vw,2rem);padding-bottom:clamp(1.25rem,3vw,2rem);border-bottom:var(--rule)}
/* centered while the breeds fit (Alex, 2026-11-06); a wheel wider than its window
   computes the auto margins to zero, so it still scrolls and turns from the left */
.puppies-wheel .breed-strip{width:max-content;margin-inline:auto}
.wheel-window{overflow-x:auto;overflow-y:hidden;
  /* html carries scroll-behavior:smooth, and inherited here it would animate every frame
     the script writes, so the wheel would lag and stutter behind its own position */
  scroll-behavior:auto;
  /* a sideways swipe on a phone must not become a back-navigation */
  overscroll-behavior-x:contain;
  scrollbar-width:none;-ms-overflow-style:none}
.wheel-window::-webkit-scrollbar{display:none}
.wheel::before,.wheel::after{content:"";position:absolute;top:0;bottom:0;width:3rem;z-index:2;
  pointer-events:none}
.wheel::before{left:0;background:linear-gradient(90deg,var(--paper),rgba(255,250,245,0))}
.wheel::after{right:0;background:linear-gradient(270deg,var(--paper),rgba(255,250,245,0))}
/* A fade says "there is more this way", so it is only drawn where there is. Standing at
   the first breed with it dimmed under a fade reads as a rendering fault, and on a phone,
   where the wheel stands still until it is pushed, that is the state it opens in. */
.wheel.at-start::before,.wheel.at-end::after{opacity:0}
.wheel::before,.wheel::after{transition:opacity .2s ease}
@media (prefers-reduced-motion:reduce){.wheel::before,.wheel::after{transition:none}}
.breed-strip{display:flex;gap:clamp(.9rem,1.6vw,1.6rem);margin:0;padding:.25rem 0;list-style:none;
  width:max-content}
.breed-strip > li{flex:0 0 clamp(92px,10vw,124px)}
.breed-strip a{display:flex;flex-direction:column;align-items:center;text-align:center;gap:.5rem;
  text-decoration:none;color:var(--ink);padding:.35rem 0}
/* Snap only while it is NOT being driven, which is to say on a phone and wherever the
   script has not run. Snapping fights something that moves the box a pixel at a time, so
   it is there for the hand and gone for the turn. The scrollbar stays hidden either way:
   on a phone it is an overlay that appears while the finger moves, which is the right
   amount of affordance, and a permanent trough under a row of circles is not. */
.wheel:not(.spinning) .wheel-window{scroll-snap-type:x proximity}
.wheel:not(.spinning) .breed-strip > li{scroll-snap-align:center}
.bs-photo{display:block;width:100%;aspect-ratio:1;border-radius:50%;overflow:hidden;background:var(--paper-deep)}
.bs-photo img{width:100%;height:100%;object-fit:cover}
.bs-name{font-family:var(--display);font-weight:600;font-size:.95rem;line-height:1.2}
.bs-meta{font-size:.78rem;color:var(--ok);font-family:var(--display);font-weight:600;
  letter-spacing:.04em;margin-top:-.15rem}
.bs-meta.expecting{color:var(--hold)}
.bs-meta.quiet{color:var(--ink-soft)}
@media (hover:hover){.breed-strip a:hover .bs-name{color:var(--indigo)}.breed-strip a:hover .bs-photo{filter:brightness(1.04)}}

/* ---------- alternating photo / text bands ----------
   The breeds page reads down the page, picture and copy trading sides, which is how the
   old site presented them and what suits nine entries far better than a grid of nine
   identical tiles. */
/* Each row is a breed in its own right, so it gets the gap and the hairline two
   sections would get, not the gap two paragraphs would. */
.alt-list{display:flex;flex-direction:column;gap:clamp(3.25rem,6.5vw,6rem)}
.alt-row + .alt-row{border-top:var(--rule-strong);padding-top:clamp(3.25rem,6.5vw,6rem)}
.alt-row{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);
  gap:clamp(1.5rem,4vw,3.5rem);align-items:center}
/* THE PHOTOGRAPH TAKES FIVE EIGHTHS, and only where there is room to give. A breed page
   is mostly a picture of the breed, and an even split had the photograph at 609px with
   the copy beside it running to 74 characters a line, which is long. Five to three puts
   the picture at 761 and the line at 55, so both ends improve at once.

   THE TEMPLATE FLIPS ON EVERY OTHER ROW, and it has to. The rows alternate by giving the
   media order:2, which moves it into the SECOND CELL, and a fixed 5fr 3fr means that cell
   is the narrow one: every right-hand photograph came out at the small size while the
   copy beside it took the wide column. Swapping the template on those rows puts the wide
   column under whichever side the photograph is on.

   1200px and not lower: the row stacks below 860, and between the two an even split is
   already down to 48 characters. Taking three eighths of that would be 36, which is not a
   column of text, it is a gutter with words in it. */
@media (min-width:1200px){
  .alt-row{grid-template-columns:minmax(0,5fr) minmax(0,3fr)}
  .alt-row:nth-child(even){grid-template-columns:minmax(0,3fr) minmax(0,5fr)}
}
.alt-row:nth-child(even) .alt-media{order:2}
.alt-media{margin:0;border-radius:var(--radius);overflow:hidden;background:var(--paper-deep)}
.alt-media img{width:100%;aspect-ratio:3/2;object-fit:cover;display:block}
.alt-body > *{max-width:54ch}
.alt-body h2{margin-bottom:.5rem}
.bfacts{margin:1.2rem 0 0;border-top:var(--rule)}
.bf{display:grid;grid-template-columns:9.5rem minmax(0,1fr);gap:1rem;padding:.55rem 0;border-bottom:var(--rule)}
.bf-l{font-family:var(--display);font-size:.75rem;font-weight:600;letter-spacing:.12em;
  text-transform:uppercase;color:var(--ink-soft);padding-top:.2rem}
.bf .card-status{font-size:.8rem}
@media (max-width:860px){
  .alt-row{grid-template-columns:1fr;gap:1.25rem}
  .alt-row:nth-child(even) .alt-media{order:0}
  .alt-media{order:-1}
  .bf{grid-template-columns:8rem minmax(0,1fr)}
}

/* ---------- collapsible breed panels (our dogs) ---------- */
.breed-panel{border-top:var(--rule-strong);background:var(--paper)}
.breed-panel:last-of-type{border-bottom:var(--rule-strong)}
.breed-panel > summary{display:grid;grid-template-columns:auto minmax(0,1fr) auto auto;align-items:center;
  gap:1rem;padding:1rem .25rem;cursor:pointer;list-style:none;min-height:64px}
.breed-panel > summary::-webkit-details-marker{display:none}
.breed-panel .bp-peek{display:flex}
.breed-panel .peek{display:block;width:44px;height:44px;margin-right:-12px;border-radius:50%;overflow:hidden;
  border:2px solid var(--paper);background:var(--paper-deep)}
.breed-panel .peek img{width:100%;height:100%;object-fit:cover}
.breed-panel .bp-name{font-family:var(--display);font-weight:600;font-size:1.25rem;margin:0;line-height:1.2}
.breed-panel .bp-count{color:var(--ink-soft);font-size:.95rem;white-space:nowrap}
.breed-panel .bp-more{display:flex;color:var(--indigo)}
.breed-panel .bp-more svg{stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;transition:transform .2s ease}
.breed-panel[open] .bp-more svg{transform:rotate(180deg)}
.breed-panel[open] > summary{border-bottom:var(--rule)}
.bp-body{padding:1.25rem 0 1.75rem}
.group-head{font-size:.8rem;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-soft);
  font-weight:600;margin:0 0 .8rem}
.bp-body .cards + .group-head{margin-top:2rem}
.link-btn{background:none;border:0;padding:.35rem 0;font:inherit;color:var(--indigo);
  text-decoration:underline;text-underline-offset:3px;cursor:pointer;min-height:44px}
@media (max-width:640px){
  .breed-panel > summary{grid-template-columns:minmax(0,1fr) auto;gap:.5rem 1rem;padding:.9rem .25rem}
  .breed-panel .bp-peek{display:none}
  .breed-panel .bp-count{grid-column:1;font-size:.9rem}
}
@media (prefers-reduced-motion:reduce){.breed-panel .bp-more svg{transition:none}}

/* ---------- cards ----------
   Breeds, litters and our dogs were ruled rows. On a site whose whole subject is what
   the animals look like, a row of text with a 96px thumbnail wasted the page and buried
   the photograph. Same flex-basis shape as the puppy sheet so every listing on the site
   lines up the same way. */
.cards{--cols:4;--gap:1.5rem;display:flex;flex-wrap:wrap;gap:var(--gap);margin:0;padding:0;list-style:none}
.cards.three{--cols:3}
.cards.two{--cols:2}
.cards > *{flex:0 1 calc((100% - (var(--cols) - 1) * var(--gap)) / var(--cols));display:flex}
/* Sits as the last item of the grid, so it fills the gap a short final row leaves and
   drops to a row of its own when the cards happen to come out even. */
.card-cta{flex-direction:column;justify-content:center;align-items:flex-start;gap:.6rem;
  padding:clamp(1.25rem,2.5vw,1.75rem);border:1px dashed rgba(38,35,31,.28);border-radius:var(--radius);
  background:transparent}
.card-cta h3{margin:0;font-size:1.15rem}
.card-cta p{margin:0;color:var(--ink-soft);font-size:.95rem}
.card-cta .btn{margin-top:.35rem;white-space:nowrap;padding-inline:1rem}
/* the same call to action as the last item of a puppy sheet (littermates, a litter) */
.sheet > .card-cta{display:flex;min-width:0}
.sheet > .card-cta .btn{white-space:normal}
/* the four "Other <breed> puppies" drop to three where the sheet is three across, so one
   tile is never alone on its last row (2026-10-22 review) */
@media (min-width:761px) and (max-width:1100px){.sheet.related > :nth-child(4){display:none}}
@media (max-width:1100px){.cards{--cols:3;--gap:1.25rem}.cards.two{--cols:2}}
@media (max-width:760px){.cards{--cols:2;--gap:1rem}.cards.three,.cards.two{--cols:2}}
@media (max-width:440px){.cards,.cards.three,.cards.two{--cols:1}}

.card{display:flex;flex-direction:column;width:100%;background:var(--white);border:var(--rule);
  border-radius:var(--radius);overflow:hidden;text-decoration:none;color:var(--ink)}
.card > img,.card > picture > img{aspect-ratio:4/3;width:100%;object-fit:cover;background:var(--paper-deep)}
.card.portrait > img,.card.portrait > picture > img{aspect-ratio:4/5}
/* flex:1 0 auto, not flex:1. With a 0 basis the body is forced to exactly the leftover
   space, so a subtitle that wraps to a second line overflows instead of making the card
   taller, margin-top:auto then has no free space to distribute, and the status line sits
   a few pixels lower than its neighbours. With an auto basis the tallest card sets the
   row height and every body stretches to match. */
.card-body{display:flex;flex-direction:column;flex:1 0 auto;padding:.8rem 1rem 1rem;border-top:var(--rule)}
.card-name{font-family:var(--display);font-weight:600;font-size:1.1rem;line-height:1.25}
.card-sub{color:var(--ink-soft);font-size:.92rem;margin:.15rem 0 .7rem}
/* the status line is pinned to the bottom so every card in a row agrees, whatever the
   name above it wrapped to */
/* margin-top:auto, not "let the subtitle grow": a two-line subtitle has a larger flex
   basis than a one-line one, so relying on the subtitle to absorb the slack left the
   status line sitting up to 31px higher in some cards than its neighbours in the row. */
.card-foot{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:baseline;
  gap:.35rem .75rem;margin-top:auto;padding-top:.6rem;border-top:var(--rule)}
.card-price{font-family:var(--display);font-weight:600;font-size:1.02rem;white-space:nowrap}
.card-status{font-family:var(--display);font-size:.74rem;font-weight:600;letter-spacing:.1em;
  text-transform:uppercase;color:var(--ok);white-space:nowrap}
.card-status::before{content:"";display:inline-block;width:8px;height:8px;border-radius:50%;
  background:currentColor;margin-right:.42rem;vertical-align:1px}
.card-status.expecting,.card-status.reserved{color:var(--hold)}
.card-status.quiet,.card-status.adopted{color:var(--ink-soft)}
/* the status chip stays at the right whether or not a price sits before it, so the
   footers in one row line up (2026-10-22 review) */
.card-foot .card-status{margin-left:auto}
@media (hover:hover){a.card:hover{border-color:rgba(38,35,31,.35)}a.card:hover .card-name{color:var(--indigo)}}

/* ---------- puppy / dog detail ---------- */
.detail{display:grid;grid-template-columns:minmax(0,7fr) minmax(0,5fr);
  column-gap:clamp(1.5rem,4vw,4rem);row-gap:1rem;align-items:start}
/* The name block is the left column's first row and .detail-rule is the right column's,
   so the two share a grid row: the rule under the name and the rule above the facts land
   on the same line whatever the name's height, and the breed and gender are not repeated
   here because the facts table beside it already carries them. */
.detail-head{grid-column:1;grid-row:1;display:flex;align-items:flex-end;justify-content:space-between;
  gap:.5rem 1.5rem;flex-wrap:wrap;padding-bottom:.85rem;border-bottom:var(--rule-strong)}
.detail-head h1{margin-bottom:0}
.detail-rule{grid-column:2;grid-row:1;align-self:stretch;border-bottom:var(--rule-strong)}
.detail-media{grid-column:1;grid-row:2}
.detail-side{grid-column:2;grid-row:2}
.detail-side .pfacts{border-top:0;margin-top:0}
/* AFTER the rules above, not before them: at equal specificity the later rule wins, and
   declared first this block lost to .detail-side{grid-column:2} at every width, which
   left the photo and the facts stacked on top of each other at 390px. */
@media (max-width:960px){
  .detail{grid-template-columns:minmax(0,1fr)}
  /* one column: the shared row is gone, so the facts carry their own rule again */
  .detail-head,.detail-media,.detail-side{grid-column:1;grid-row:auto}
  .detail-rule{display:none}
  .detail-side .pfacts{border-top:var(--rule-strong);margin-top:0}
}
/* The price used to be sent to its own full-width line ABOVE the name on a phone, which
   cost a line of vertical space to say something short. It sits at the end of the name's
   line instead, where it reads as a label on the name rather than a heading of its own.
   "Limited registration" wraps under the figure inside that same column. */
@media (max-width:600px){
  .detail-head{align-items:baseline;gap:.5rem 1rem}
  .detail-head h1{min-width:0}
  .detail-head .price-line{text-align:right;white-space:normal;flex:0 0 auto}
  .detail-head .price-line small{display:block;line-height:1.25;margin-top:.1rem}
}
.price-line{font-family:var(--display);font-size:clamp(1.4rem,2vw,1.8rem);font-weight:500;color:var(--indigo);white-space:nowrap}
.price-line small{font-size:.65em;color:var(--ink-soft);font-weight:400}
.pfacts{border-top:var(--rule-strong);margin:1rem 0 0}
.pfacts .row{display:grid;grid-template-columns:8.5rem minmax(0,1fr);gap:1rem;padding:.5rem 0;border-bottom:var(--rule);align-items:center}
.pfacts .lbl{font-family:var(--display);font-size:.75rem;font-weight:600;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-soft)}
/* Every row the same height, and the label and the value on the same centre line. They
   were top-aligned and ragged: 44px for a plain value, 61px wherever the value is a link
   (a link carries the 44px touch target and the row's own padding on top of it), and 58
   for Grown weight, whose label is the one that needs two lines in this column. Setting
   the target on the ROW rather than on the link inside it makes them agree, and the link
   fills the row so the target is not lost. */
@media (max-width:900px){
  .pfacts .row{min-height:52px;padding:.3rem 0}
  /* VERTICAL PADDING ON AN INLINE LINK. The row is 52px and the link inside it was 20,
     so the row looked like the target and less than half of it was one. The anchor is not
     a grid item, it is inside the value cell, so stretching it does nothing; making it a
     flex box instead blockified it and broke "Bambi and Alfie" onto two lines, taking
     that row to 92px. Padding on an INLINE box extends what a finger hits without
     touching the line box, which is the one thing here that enlarges the target and
     changes no layout at all. .75rem either side of a 20px line is 44px. */
  .pfacts .row a{min-height:0;display:inline;padding-block:.75rem}
}
@media (max-width:480px){.pfacts .row{grid-template-columns:7rem minmax(0,1fr)}}
/* COMES WITH (2026-11-02). The one facts row with a list in it: one item to a line, a
   small square marker on the first line of each, and the label set against the top of the
   list rather than its middle, so a four-line answer does not float its label. The items
   come from the owners' list in the editor's Settings (comes_with in site.json). */
.pfacts .row.kit-row{align-items:start;padding-block:.65rem}
.pfacts .row.kit-row .lbl{padding-top:.2rem}
/* A Comes with line with a bracketed part opens to show it (Alex, 2026-11-07). The marker is a
   plus that turns to a minus, so it reads as more to see rather than as a menu. */
.kit-more summary{cursor:pointer;list-style:none}
.kit-more summary::-webkit-details-marker{display:none}
.kit-more summary::after{content:"+";font-weight:700;line-height:1;margin-left:.4rem}
.kit-more[open] summary::after{content:"\2212"}
.kit-more > span{display:block;margin-top:.2rem;font-size:.92em;color:var(--ink-soft, inherit)}
/* On a phone the Comes with list sits under its label and takes the panel's whole width, so
   each line holds more and the row is far shorter (Alex, 2026-11-07). The other rows keep the
   label beside the value. */
@media (max-width:560px){
}
.pfacts .kit{list-style:none;margin:0;padding:0;display:grid;gap:.35rem}
.pfacts .kit li{position:relative;padding-left:1rem;line-height:1.45}
.pfacts .kit li::before{content:"";position:absolute;left:0;top:.6em;width:.36rem;height:.36rem;background:var(--ink-soft)}
.reserve{margin-top:1.3rem;padding:1.4rem;background:var(--white);border:var(--rule);border-radius:var(--radius)}
.reserve h2{margin-bottom:.4rem;font-size:clamp(1.25rem,1.8vw,1.55rem)}
.reserve .btn-row{margin-top:1rem}
.footnote{font-size:.9rem;color:var(--ink-soft);max-width:70ch;margin-top:1rem}

/* ---------- carousel ---------- */
.carousel{position:relative}
.carousel .frame{position:relative;aspect-ratio:4/3;background:var(--paper-deep);border-radius:var(--radius);overflow:hidden}
.carousel .frame img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0;transition:opacity .45s ease}
.carousel .frame img.is-on{opacity:1}
.carousel .frame img:first-child{position:relative}
.carousel .cnav{position:absolute;top:50%;transform:translateY(-50%);width:48px;height:48px;border-radius:50%;border:0;background:rgba(255,250,245,.92);color:var(--ink);cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:var(--shadow)}
.carousel .cprev{left:.7rem}
.carousel .cnext{right:.7rem}
.carousel .cnav svg{width:22px;height:22px;stroke:currentColor;fill:none;stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round}
.carousel .count{position:absolute;right:.9rem;bottom:.8rem;background:rgba(38,35,31,.72);color:var(--paper);font-family:var(--display);font-size:.85rem;padding:.25rem .6rem;border-radius:999px}
.cthumbs{display:flex;gap:.5rem;margin:.6rem 0 0;padding:0 0 .3rem;list-style:none;overflow-x:auto;scrollbar-width:thin}
/* A thumbnail strip is a second way to reach photographs you can already reach by
   swiping, and on a phone it is the more awkward of the two: 74px targets in a sideways
   scroller under a carousel that scrolls sideways itself. It costs six more images on
   every puppy page for that. The counter and the arrows stay. */
@media (max-width:640px){.cthumbs{display:none}}
.cthumbs button{flex:0 0 auto;width:74px;height:74px;padding:0;border:2px solid transparent;border-radius:4px;background:none;cursor:pointer;overflow:hidden}
.cthumbs button img{width:100%;height:100%;object-fit:cover}
.cthumbs button[aria-current="true"]{border-color:var(--indigo)}

/* ---------- retired adults cards (option C, Alex 2026-10-22) ----------
   Two cards to a row from 1080px, each with its photo beside the breed, name, price, facts
   and button. One card to a row below that, the photo still beside the facts down to 560px
   and on top of them below it. No thumbnail strip: the arrows and the count are the way
   between photos, and they sit in the photo's bottom-left corner at 44px, the smallest
   touch target allowed, so they cover the feet and the grass rather than the dog's face.
   The carousel does not turn by itself here (main.js skips autoplay on .compact), because
   four or five photos changing at once in a grid pull the eye away from the facts. */
.adults{display:grid;grid-template-columns:minmax(0,1fr);gap:1.25rem}
@media (min-width:1080px){.adults{grid-template-columns:repeat(2,minmax(0,1fr));gap:1.4rem}}
.adult{background:var(--white);border:var(--rule);border-radius:var(--radius);padding:14px;
  display:grid;grid-template-columns:minmax(0,1fr);gap:14px}
@media (min-width:560px){.adult{grid-template-columns:minmax(0,min(42%,340px)) minmax(0,1fr);gap:20px;align-items:start}}
.carousel.compact .frame{aspect-ratio:4/3}
@media (min-width:560px){.carousel.compact .frame{aspect-ratio:1/1}}
.carousel.compact .cnav{top:auto;bottom:8px;transform:none;width:44px;height:44px}
.carousel.compact .cprev{left:8px}
.carousel.compact .cnext{left:58px;right:auto}
.carousel.compact .cnav svg{width:20px;height:20px}
.carousel.compact .count{right:8px;bottom:17px;font-size:.78rem;padding:.2rem .55rem;line-height:1.2}
.adult-body{display:flex;flex-direction:column;align-self:stretch;min-width:0}
.adult .kicker{font-size:.74rem;margin-bottom:.35rem}
.adult .name-row{display:flex;flex-wrap:nowrap;align-items:baseline;justify-content:space-between;gap:1rem}
.adult h2{font-size:1.7rem;margin:0;min-width:0;flex:1 1 auto;overflow-wrap:anywhere}
.adult .price-line{font-size:1.35rem;line-height:1.2;flex:0 0 auto;text-align:right}
.adult .price-line small{display:block;white-space:nowrap;line-height:1.2}
.adult .t-status{font-family:var(--display);font-size:.78rem;font-weight:600;letter-spacing:.1em;text-transform:uppercase;margin:.35rem 0 0}
.adult .pfacts{margin-top:.6rem}
.adult .pfacts .row{grid-template-columns:5.4rem minmax(0,1fr);gap:.6rem;padding:.3rem 0;min-height:0;font-size:.98rem;line-height:1.45}
.adult .pfacts .lbl{font-size:.7rem}
.adult .btn-row{margin-top:auto;padding-top:1rem}
.single .frame img{position:relative;opacity:1}

/* ---------- certificate documents ---------- */
.docs{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:1rem;list-style:none;padding:0;margin:1rem 0 0}
@media (max-width:900px){.docs{grid-template-columns:repeat(2,minmax(0,1fr))}}
.docs a{display:block;text-decoration:none;color:var(--ink);background:var(--white);border:var(--rule);border-radius:var(--radius);overflow:hidden}
/* Certificates are landscape documents. A portrait 3:4 box with object-fit:cover cut
   the sides off every one of them. The set mixes landscape OFA forms with portrait
   pedigrees, so the box is a compromise 4:3 and the image is contained: some letterbox
   either way, but no document is cropped and each opens full size. */
.docs img{aspect-ratio:4/3;width:100%;object-fit:contain;background:var(--white);padding:.4rem}
.docs .doc-cap{padding:.6rem .75rem;font-family:var(--display);font-weight:600;font-size:.95rem;border-top:var(--rule)}
.docs .doc-cap span{display:block;font-family:var(--body);font-weight:400;color:var(--ink-soft);font-size:.85rem}

/* ---------- process steps (a real sequence) ----------
   Scoped to DIRECT children. `.steps li` also matched the list nested inside a step, so
   that list incremented the same counter and drew its own "03" marker inside step 02,
   in the narrow column meant for the number. That is what made How it Works look broken. */
.steps{counter-reset:s;list-style:none;padding:0;margin:0;border-top:var(--rule-strong)}
.steps > li{counter-increment:s;display:grid;grid-template-columns:3.2rem minmax(0,1fr);gap:1.2rem;padding:1.3rem 0;border-bottom:var(--rule)}
.steps > li::before{content:counter(s,decimal-leading-zero);font-family:var(--display);font-size:1.5rem;font-weight:500;color:var(--indigo);line-height:1.1;font-variant-numeric:tabular-nums}
.steps h3{margin-bottom:.3rem}
.steps p{max-width:62ch}

/* The How it Works page runs the same sequence as alternating photo / text bands, so a
   long process is not a wall of prose. */
.process{counter-reset:p;list-style:none;margin:0;padding:0}
.process > li{counter-increment:p;display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);
  gap:clamp(1.5rem,4vw,3.5rem);align-items:center;padding:clamp(2rem,4vw,3.25rem) 0;border-top:var(--rule)}
.process > li:first-child{border-top:0;padding-top:.5rem}
/* the last step ends the section, so the section padding is the only gap under it (2026-11-07) */
.process > li:last-child{padding-bottom:0}
/* the closing step carries the most text, so its picture is the tall one and takes the
   narrower column, leaving the copy a comfortable measure */
.process > li.tall{grid-template-columns:minmax(0,1.55fr) minmax(0,1fr)}
.process > li.tall .p-media{order:2}
.process > li.tall .p-media img{aspect-ratio:3/4}
.process > li.tall .p-body > *{max-width:62ch}
@media (max-width:860px){.process > li.tall{grid-template-columns:1fr}.process > li.tall .p-media{order:-1}}
.process > li:nth-child(even) .p-media{order:2}
.process .p-media{margin:0;border-radius:var(--radius);overflow:hidden;background:var(--paper-deep)}
.process .p-media img{width:100%;aspect-ratio:4/3;object-fit:cover;display:block}
.process .p-body > *{max-width:56ch}
.process .p-num{display:block;font-family:var(--display);font-size:.78rem;font-weight:600;
  letter-spacing:.16em;text-transform:uppercase;color:var(--indigo);margin-bottom:.5rem}
.process .p-num::before{content:"Step " counter(p,decimal-leading-zero)}
.process h2{font-size:clamp(1.5rem,2.4vw,2.1rem);margin-bottom:.5rem}
.process ul{list-style:none;padding:0;margin:1rem 0 0;max-width:56ch}
.process ul li{display:grid;grid-template-columns:auto minmax(0,1fr);gap:.7rem;padding:.6rem 0;border-top:var(--rule)}
.process ul li::before{content:"";width:9px;height:9px;border-radius:50%;background:var(--indigo);margin-top:.55rem}
@media (max-width:860px){
  .process > li{grid-template-columns:1fr;gap:1.25rem;padding:1.75rem 0}
  .process > li:nth-child(even) .p-media{order:0}
  .process .p-media{order:-1}
}

/* ---------- reviews ----------
   The first build put a 120px thumbnail beside the quote inside a half-width column,
   which left the text about 40 characters wide and, with the families' own email line
   breaks preserved, made every letter read as a ragged poem. Now the photo sits above
   the quote as a proper card, the measure is comfortable, and the generator joins the
   line breaks into paragraphs. */
.reviews{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:clamp(1.5rem,3vw,2.5rem);margin:0;padding:0;list-style:none}
.reviews.home{grid-template-columns:repeat(3,minmax(0,1fr))}
@media (max-width:1000px){.reviews.home{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:700px){.reviews,.reviews.home{grid-template-columns:1fr}}
.review{display:flex;flex-direction:column;background:var(--white);border:var(--rule);border-radius:var(--radius);overflow:hidden}
.review .r-photo{background:var(--paper-deep)}
.review .r-photo img{width:100%;aspect-ratio:4/3;object-fit:cover;display:block}
.review blockquote{margin:0;padding:1.3rem 1.4rem 1.4rem;display:flex;flex-direction:column;flex:1;font-size:1.02rem;line-height:1.6}
.review blockquote p{margin:0 0 .7em;max-width:60ch;flex:0 0 auto}
.review blockquote p:last-of-type{margin-bottom:0}
.review cite{display:block;margin-top:auto;padding-top:1rem;font-style:normal;font-family:var(--display);font-weight:600;color:var(--ink-soft);font-size:.92rem}
.review.no-photo blockquote{border-top:3px solid var(--indigo-tint)}
/* rows are built from quotes of similar length, so a short letter does not leave a
   half-empty card beside a long one */
.reviews.grouped{--cols:3;--gap:1.5rem;display:flex;flex-wrap:wrap;gap:var(--gap)}
.reviews.grouped > li{flex:0 1 calc((100% - (var(--cols) - 1) * var(--gap)) / var(--cols))}
@media (max-width:1000px){.reviews.grouped{--cols:2}}
@media (max-width:700px){.reviews.grouped{--cols:1}}

/* ---------- indigo band: used once per page at most ---------- */
.band-indigo{background:var(--indigo);color:var(--on-indigo);padding:clamp(4rem,8vw,7rem) 0}
.band-indigo h2,.band-indigo h3{color:var(--on-indigo)}
.band-indigo .kicker{color:var(--on-indigo-dim)}
.band-indigo p,.band-indigo .muted{color:var(--on-indigo-dim)}
.band-indigo a:not(.btn){color:var(--on-indigo)}
.band-indigo .btn-primary{background:var(--paper);color:var(--indigo);border-color:var(--paper)}
.band-indigo .btn-ghost{color:var(--on-indigo);border-color:var(--on-indigo)}
@media (hover:hover){.band-indigo .btn-primary:hover{background:var(--white);border-color:var(--white);color:var(--indigo-deep)}.band-indigo .btn-ghost:hover{background:rgba(255,250,245,.12)}}
.contact-lines{list-style:none;margin:1rem 0 0;padding:0;max-width:34rem}
.contact-lines li{display:grid;grid-template-columns:6.5rem minmax(0,1fr);gap:1rem;padding:.8rem 0;border-top:var(--rule)}
/* the value is one grid cell, not two: a bare second element would land back in the
   label column on the next row */
.contact-lines .val{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:.4rem}
.contact-lines .lbl{font-family:var(--display);font-size:.78rem;font-weight:600;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-soft);padding-top:.25rem}
.contact-lines a{font-weight:600;text-decoration:none;display:inline-block;padding:.2rem 0;min-height:32px}
.band-indigo .contact-lines li{border-top:1px solid rgba(255,250,245,.2)}
.band-indigo .contact-lines .lbl{color:var(--on-indigo-dim)}

/* ---------- forms ---------- */
.form{max-width:760px}
.form-card{background:var(--white);border:var(--rule);border-radius:var(--radius);padding:clamp(1.25rem,3vw,2rem)}
.form-intro{margin-bottom:1.5rem;padding-bottom:1.25rem;border-bottom:var(--rule)}
.form-intro h2{margin-bottom:.35rem}
.apply-layout{display:grid;grid-template-columns:minmax(0,7fr) minmax(0,4fr);gap:clamp(1.5rem,4vw,3rem);align-items:start}
.apply-layout > *{min-width:0}
@media (max-width:960px){.apply-layout{grid-template-columns:1fr}}
.aside-card{background:var(--white);border:var(--rule);border-radius:var(--radius);overflow:hidden;position:sticky;top:92px}
.aside-card img{width:100%;aspect-ratio:4/3;object-fit:cover;display:block;background:var(--paper-deep)}
.aside-card .inner{padding:1.1rem 1.2rem 1.3rem}
.aside-card h2{font-size:1.15rem;margin-bottom:.4rem}
.aside-card ul{list-style:none;margin:.6rem 0 0;padding:0}
.aside-card li{display:grid;grid-template-columns:auto minmax(0,1fr);gap:.6rem;padding:.5rem 0;border-top:var(--rule);font-size:.94rem}
.aside-card li::before{content:"";width:8px;height:8px;border-radius:50%;background:var(--indigo);margin-top:.5rem}
@media (max-width:960px){.aside-card{position:static}}
.contact-layout{display:grid;grid-template-columns:minmax(0,5fr) minmax(0,7fr);gap:clamp(1.5rem,4vw,3.5rem);align-items:start}
.contact-layout > *{min-width:0}
.contact-photo{margin:1.5rem 0 0;border-radius:var(--radius);overflow:hidden;background:var(--paper-deep)}
.contact-photo img{width:100%;aspect-ratio:4/3;object-fit:cover;display:block}
@media (max-width:960px){.contact-layout{grid-template-columns:1fr}}
.form fieldset{border:0;padding:0;margin:0 0 2rem}
.form legend{font-family:var(--display);font-weight:600;font-size:1.25rem;padding:0;margin:0 0 1rem;border-bottom:var(--rule-strong);width:100%;padding-bottom:.5rem}
.field{margin-bottom:1.1rem}
.field label,.field .lbl{display:block;font-weight:600;margin-bottom:.35rem}
.field .hint{display:block;color:var(--ink-soft);font-size:.9rem;font-weight:400;margin-top:.1rem}
/* Text-like fields only. This used to read `.field input`, which also matched every radio
   and checkbox inside a .choices list, so each one inherited width:100%, the 48px tap
   height, the padding and the border: a radio rendered 20 wide and 48 TALL, and the focus
   ring drew a dark slab around it that read as a box around the chosen answer
   (Alex, 2026-10-02). Shared verbatim with the sister site. */
.field input:not([type=checkbox]):not([type=radio]),.field select,.field textarea{width:100%;font:inherit;font-size:1.0625rem;padding:.75rem .9rem;min-height:48px;border:1.5px solid rgba(38,35,31,.35);border-radius:var(--radius);background:var(--white);color:var(--ink)}
.field textarea{min-height:120px;resize:vertical}
.field input:not([type=checkbox]):not([type=radio]):focus,.field select:focus,.field textarea:focus{outline:3px solid var(--indigo);outline-offset:1px;border-color:var(--indigo)}
/* A chosen option shows itself by the radio being filled, not by a ring round it. The
   ring still exists for anyone moving through the form by keyboard, but it goes on the
   whole option and is thin, rather than a heavy square sitting on the control. */
.choices input{min-height:0;padding:0;border:0}
/* The ring hugs the radio and is round, so a chosen option never reads as a boxed one.
   Chrome treats a mouse click on a radio as keyboard-style focus, so a ring drawn round
   the whole LABEL would put the box straight back, which is what this replaced. Keeping it
   on the control also means no reliance on :has(), so a browser without it still shows
   somebody tabbing through where they are. */
.choices input:focus-visible{outline:2px solid var(--indigo);outline-offset:2px;border-radius:50%}
.two{display:grid;grid-template-columns:1fr 1fr;gap:1rem}
/* Side by side fields whose labels are different heights put their inputs at different
   heights: "When are you hoping to bring a puppy home?" runs to two lines and drops its
   select below the one next to it. The grid already stretches both cells to the same
   height, so pushing each control to the bottom of its own cell lines them up whatever
   the labels do. */
.two .field{display:flex;flex-direction:column}
.two .field>:last-child{margin-top:auto}
@media (max-width:600px){.two{grid-template-columns:1fr}}
.choices{display:flex;flex-wrap:wrap;gap:.5rem .9rem;margin:0;padding:0;list-style:none}
/* A long list of options, laid out as equal tiles rather than shrink-to-fit boxes on a
   wrapping row. Nine breeds at widths from 121px to 284px gave 2, 2, 3, 2 and a lone box
   on the last line.

   TWO COLUMNS IS ALWAYS THE RIGHT ANSWER HERE, because the form is capped at 760px and
   the longest breed name needs 284px. The difficulty is that the form's width does not
   track the viewport: the apply page's sidebar makes it 758px at a 900px viewport and
   541px at 1100px, so a viewport media query gets it backwards, and auto-fill cannot
   both keep two columns at 502px and refuse a third at 758px.

   So the count is decided by the form's own width with a container query. The auto-fill
   line stays as the fallback for anything without container query support, where it is
   right everywhere except one narrow band around a 1024px viewport. */
/* The application's address row, three across on a desktop and stacked on a phone. Scoped
   to .form because a bare .three would also hit `.cards three`, whose own display and
   columns it would override. */
.form .three{display:grid;grid-template-columns:minmax(0,2fr) minmax(0,1.4fr) minmax(0,1fr);gap:1rem}
@media (max-width:620px){.form .three{grid-template-columns:1fr}}
/* Yes/No and other short answer sets run along one line instead of down the page. */
.choices-row{display:flex;flex-wrap:wrap;gap:.6rem 1.25rem}
.choices-row label{width:auto}
/* A field that only applies to some answers. Hidden until its trigger, and its contents
   are cleared when it closes, so a family cannot submit an answer to a question the form
   has stopped showing them. */
/* `.two .field{display:flex}` is (0,0,2,0) and would outrank a bare .is-conditional, so a
   conditional field sitting inside a two-column row never hid. These match its specificity
   and come later in the file, which is what decides it (2026-10-02). */
.is-conditional,.two .is-conditional{display:none}
.is-conditional.is-open{display:block}
.two .is-conditional.is-open{display:flex;flex-direction:column}
/* The count in each legend, so the length of the form is visible from the first screen. */
.form legend .of{font-family:var(--display);font-size:.72rem;font-weight:600;letter-spacing:.12em;
  text-transform:uppercase;color:var(--ink-soft);display:block;margin-top:.3rem}
.choices-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(16rem,100%),1fr));gap:.6rem}
.choices-grid label{width:100%}
@supports (container-type:inline-size){
  .form{container-type:inline-size}
  .choices-grid{grid-template-columns:1fr}
  @container (min-width:31rem){.choices-grid{grid-template-columns:1fr 1fr}}
}
.choices label{display:inline-flex;align-items:center;gap:.55rem;min-height:44px;padding:.4rem .9rem .4rem .6rem;border:1.5px solid rgba(38,35,31,.3);border-radius:var(--radius);background:var(--white);font-weight:500;cursor:pointer}
.choices input{width:20px;height:20px;margin:0;accent-color:var(--indigo);flex:none}
.ack{display:grid;grid-template-columns:24px minmax(0,1fr);gap:.8rem;align-items:start;padding:.8rem 0}
.ack input{width:22px;height:22px;margin:.2rem 0 0;accent-color:var(--indigo)}
.req{color:var(--hold)}
.guard-msg{display:none;margin-top:1rem;padding:1rem 1.2rem;background:var(--indigo-tint);border-left:4px solid var(--indigo);border-radius:0 var(--radius) var(--radius) 0}
.guard-msg.show{display:block}
.guard-msg.is-error{background:#f7e6e2;border-color:#9e3b2e}
.hp{position:absolute;left:-9999px;top:auto;width:1px;height:1px;overflow:hidden}

/* ---------- blog ---------- */

/* ---------- home: family, breeds, credentials ---------- */
.story-photo{border-radius:var(--radius);overflow:hidden}
.story-photo img{aspect-ratio:4/3;object-fit:cover;width:100%}
/* The licence logos sit under the photo, in the photo's column (Alex, 2026-09-11), so
   the text column reads as one piece and the logos read as the photo's caption. */
.creds{display:flex;flex-wrap:wrap;gap:1rem 2.2rem;align-items:center;margin:2rem 0 0;padding:1.4rem 0 0;border-top:var(--rule);list-style:none}
.feature-media{min-width:0}
/* On the home and Our team pages the strip is its own band above the footer (Alex,
   2026-11-07), so it takes the section's spacing rather than a gap of its own, and it has no
   rule line: the logos sit in the space (the section is not .ruled either). */
.creds-home .creds.strip{margin-top:0;border-top:0;padding-top:0}
.creds.strip{margin-top:clamp(2rem,4vw,3.25rem)}
/* a two-column feature with a photo that carries real weight */
.feature{display:grid;grid-template-columns:minmax(0,6fr) minmax(0,5fr);gap:clamp(1.5rem,4vw,3.5rem);align-items:center}
.feature > *{min-width:0}
.feature .story-photo,.feature-media .story-photo{margin:0}
@media (max-width:900px){.feature{grid-template-columns:1fr}}
.creds li{display:flex;align-items:center;gap:.7rem;color:var(--ink-soft);font-family:var(--display);font-weight:500;font-size:.95rem}
/* Logos only, in their own colours, with no caption: each mark names itself. 56px is
   the ceiling the smallest source allows (the OFA seal is 126px tall, so 56px still
   holds at 2x). multiply drops the USDA JPEG's white ground onto the paper; the PNGs
   have alpha and are unaffected. */
.creds img{height:56px;width:auto;mix-blend-mode:multiply}
/* Six marks on six different canvases, laid out as SIX EQUAL COLUMNS.
   A flex row with space-between sized every logo by its own height and then spaced them
   by whatever width was left over, which is how the wide ones (Paw Print, AKC) came to
   carry more than twice the area of the square ones and why the gaps looked arbitrary. A
   grid inverts that: the column is the fixed thing and the mark fits inside it. One line
   is then guaranteed at any width rather than hoped for, the gaps are identical, and the
   optical spread closes from 2.3x to about 1.4x without anyone nudging a single logo.

   The canvas margin is already off at build time (trim_margin in make_images.py), which
   is where ICAW's 42% of empty square and AKC's 33% of padding went; without that the
   columns would be sizing air.

   FULL COLOUR, on purpose. Grey was tried and Alex asked for the colour back: these are
   somebody else's certification marks and they are recognised by their colour. */
/* ONE GAP, AND THE GROUP CENTRED. It was a six-column grid with each logo centred in its
   own cell, which means the space a reader SEES between two of them is whatever their
   widths left over: measured at 1400px, 99px, 69, 23, 35 and 68 between the six. A centred
   flex row has one gap, so the space between any two is the same number and the slack sits
   outside the group rather than being shared out unevenly inside it. */
.creds.logos{display:flex;flex-wrap:wrap;justify-content:center;align-items:center;
  gap:clamp(1.5rem,3vw,3.25rem);width:min(100%,76rem);margin-inline:auto}
.creds.logos li{gap:0;width:auto;justify-content:center}
/* :not(.sr-only) is load-bearing. Each certificate item carries a visually hidden span
   with the certifier's name, and this rule was matching it too: an absolutely positioned
   1px box became 360px wide, and the home page and About scrolled sideways on a phone,
   475px of content in a 375px window. clip: rect(0,0,0,0) hides the paint, not the box. */
.creds.logos a,.creds.logos > li > span:not(.sr-only){display:inline-flex;align-items:center;
  justify-content:center;min-height:44px}
/* EQUAL AREA, NOT EQUAL HEIGHT. --k is 1/sqrt(the logo's own ratio), written onto each
   item by logo_scale() in build_site.py, and --logo-h is the height the squarest mark in
   the set gets. Multiplying the two holds the AREA of every logo the same, which is what
   makes a seal and a wordmark read as the same size. The height is the responsive part,
   so the whole strip scales together and the relationship survives every width. */
.creds.logos{--logo-h:clamp(58px,6.6vw,98px)}
.creds.logos img{height:calc(var(--logo-h) * var(--k,1));width:auto;max-width:100%;
  object-fit:contain}
/* Three and three on a phone, and the flex row wraps into them on its own. Six across a
   350px container is 45px a logo, which is not a credential anybody can read. */
@media (max-width:620px){
  .creds.logos{--logo-h:62px;gap:1.25rem 1.5rem;max-width:22rem}
}
.creds a{color:inherit;text-decoration:none}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
.maps-link{display:inline-flex;align-items:center;gap:.4rem;margin-top:.45rem;font-weight:600;text-decoration:none;min-height:32px}
.maps-link svg{flex:none}
.photo-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:clamp(.75rem,1.5vw,1.25rem);margin:0;padding:0;list-style:none}
.photo-grid img{width:100%;aspect-ratio:4/3;object-fit:cover;border-radius:var(--radius);background:var(--paper-deep)}
@media (max-width:900px){.photo-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:560px){.photo-grid{grid-template-columns:1fr}}
.photo-strip{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:.75rem;margin:0;padding:0;list-style:none}
.photo-strip img{aspect-ratio:1;object-fit:cover;width:100%;border-radius:var(--radius);background:var(--paper-deep)}
@media (max-width:900px){.photo-strip{grid-template-columns:repeat(4,minmax(0,1fr))}}
@media (max-width:560px){.photo-strip{grid-template-columns:repeat(3,minmax(0,1fr));gap:.5rem}}
/* the About lead: a larger photo than a plain half-and-half split */

.center-block{text-align:center;display:flex;flex-direction:column;align-items:center}
.center-block p,.center-block .lede{max-width:60ch}
.center-block .btn-row{justify-content:center}

/* ---------- footer (light; the indigo block is spent on the contact band) ---------- */
.site-foot{background:var(--paper-deep);border-top:var(--rule);padding:clamp(1.4rem,2.2vw,1.9rem) 0 5.25rem;color:var(--ink-soft);font-size:.9rem;line-height:1.45}
@media (min-width:901px){.site-foot{padding-bottom:clamp(1.5rem,2.5vw,2.1rem)}}
/* 1.7fr on the first column, not 1.35: the tagline is one sentence and was wrapping to
   a second line beside the seal. */
.foot-grid{display:grid;grid-template-columns:minmax(0,1.85fr) minmax(0,.78fr) minmax(0,.78fr) minmax(0,1.1fr);gap:1rem 2rem;align-items:start}
@media (max-width:900px){.foot-grid{grid-template-columns:1fr 1fr}}
@media (max-width:560px){.foot-grid{grid-template-columns:1fr}}
/* h2, not h4: the footer repeats on every page and an h4 after an h1-only page is a
   heading-order jump. Level is semantic, size is visual. */
.foot-grid .foot-h{font-size:.75rem;letter-spacing:.14em;text-transform:uppercase;color:var(--ink);margin:0 0 .4rem;font-weight:600}
.foot-grid ul{list-style:none;margin:0;padding:0}
.foot-grid li{margin:0}
.foot-grid li a{display:inline-block;padding:.14rem 0;color:var(--ink);text-decoration:none;line-height:1.45}
/* the seal reads at a proper size beside the words rather than stacked above them */
/* start, not center: centering the seal against the text pushed the first line of the
   brand column below the headings in the other three. */
/* THE SEAL RUNS THE FULL HEIGHT OF THE TEXT BESIDE IT, AND SITS ON THE LEFT EDGE.
   Both faults were inside the file rather than the layout. seal-charcoal.png is a 500px
   square holding a 304px ring, so about a fifth of every side is transparent: the img box
   was already flush left and top-aligned, and the ring inside it floated 32px in and 32px
   down, which is what read as indented and disjointed. The footer uses a trimmed copy now
   and the original stays put for the structured-data logo.
   With the padding gone the seal can stretch to the text block instead of being a fixed
   164px that overhung by 45 at one width and fell 40 short at another. */
.foot-brand{display:grid;grid-template-columns:auto minmax(0,1fr);gap:1.1rem;align-items:stretch}
/* The wrapper is what stretches, and aspect-ratio turns that height into a matching
   width, so the ring genuinely runs top to bottom instead of being centred in a tall box
   with air above and below it. object-fit:contain on a stretched IMG looked right in the
   numbers and wrong on the page for exactly that reason: the box spanned, the ring did not. */
/* Base rule kept identical to the sister site; the footer seal block near the
   end of this file supersedes it on both sites. */
.seal-wrap{align-self:stretch;aspect-ratio:1;display:block;min-height:88px;max-height:190px}
.foot-grid .seal{width:100%;height:100%;margin:0;object-fit:contain;object-position:left center}
.foot-brand-text .tag{margin-bottom:.25rem}
.foot-brand-text p{margin-bottom:.4rem}
@media (max-width:560px){.foot-brand{grid-template-columns:auto minmax(0,1fr);gap:.9rem}}
/* The one width where matching the text is the wrong answer. On a 320px screen the
   equilibrium lands at a 190px seal in a 294px container, which is a logo with a caption
   beside it. Capped, it stops short of the text by about 30px, and that is the better
   trade at a width almost nothing is any more. */
@media (max-width:340px){.seal-wrap{max-height:120px}}
/* The footer is four columns of links on a desktop and four stacked blocks on a phone,
   which put about 700px of navigation under every page. Each one is a <details> now,
   written open and closed by main.js below the breakpoint, so with JavaScript off they
   stay open and nothing is lost. Same device as the breed panels on the mamas and papas
   page, and the same look. */
.foot-sec > summary{list-style:none;cursor:pointer}
.foot-sec > summary::-webkit-details-marker{display:none}
.foot-sec .foot-h{display:flex;align-items:center;justify-content:space-between;gap:1rem}
.foot-sec .foot-more{display:none;color:var(--ink-soft);transition:transform .2s ease}
.foot-sec .foot-more svg{width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:2;
  stroke-linecap:round;stroke-linejoin:round;display:block}
.foot-sec[open] .foot-more{transform:rotate(180deg)}
@media (max-width:700px){
  .foot-sec > summary{min-height:48px;display:flex;align-items:center;
    border-top:var(--rule);margin-top:-1px}
  .foot-sec > summary .foot-h{width:100%;margin:0}
  .foot-sec .foot-more{display:block}
  .foot-sec > summary:focus-visible{outline:2px solid var(--indigo);outline-offset:2px}
}
@media (prefers-reduced-motion:reduce){.foot-sec .foot-more{transition:none}}
.foot-contact{list-style:none;margin:0;padding:0}
.foot-contact li{padding:.16rem 0}
.foot-contact .lbl{display:none}
.foot-contact a{color:var(--ink);text-decoration:none;border-bottom:1px solid rgba(38,35,31,.25);overflow-wrap:anywhere}

.foot-grid .tag{font-family:var(--display);color:var(--ink);font-weight:500;margin-bottom:.4rem;font-size:.95rem;line-height:1.3}
.foot-brand-text .tag{white-space:nowrap}
@media (max-width:1100px){.foot-brand-text .tag{white-space:normal}}
.foot-bottom{display:flex;flex-wrap:wrap;justify-content:space-between;gap:.5rem 2rem;margin-top:1.1rem;padding-top:.8rem;border-top:var(--rule);font-size:.85rem}
.foot-bottom a{color:var(--ink-soft)}
.social{display:flex;gap:.5rem;margin-top:.7rem}
.social a{display:inline-flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:50%;border:var(--rule-strong);color:var(--ink)}
.social svg{width:20px;height:20px;stroke:currentColor;fill:none;stroke-width:1.8}

/* ---------- let's chat launcher ----------
   This replaces a three-up call bar pinned across the bottom of every phone screen. The
   bar was permanent furniture: 56px of fixed height on a 812px screen for two links most
   visitors never used, and it sat on the last line of every page. A launcher costs a
   54px circle in the corner and opens everything the bar had plus the things it had no
   room for, which on this site are the hours and where we are. */
.chat-fab{position:fixed;right:18px;bottom:18px;z-index:60;display:inline-flex;
  align-items:center;gap:.5rem;background:var(--indigo);color:var(--on-indigo);
  border:none;border-radius:999px;padding:.8rem 1.25rem;font-family:var(--display);
  font-size:1rem;font-weight:600;cursor:pointer;min-height:48px;
  box-shadow:0 6px 18px rgba(48,47,81,.32)}
.chat-fab:hover{background:var(--indigo-deep)}
.chat-fab svg{width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:2;
  stroke-linecap:round;stroke-linejoin:round}
.chat-panel{position:fixed;right:18px;bottom:82px;z-index:60;display:none;
  width:min(92vw,24rem);background:var(--white);border:2px solid var(--indigo);
  border-radius:var(--radius);padding:1.25rem;box-shadow:0 14px 38px rgba(38,35,31,.26)}
.chat-panel.open{display:block}
.chat-panel h2{font-size:1.2rem;margin-bottom:.25em}
.chat-panel > p{font-size:.92rem;color:var(--ink-soft);margin-bottom:.9rem}
/* A grid and not a flex row. min-width on the label let "Call or text" grow to 97px
   while every other label stayed at 67, so that one value started 31px right of the rest
   and the panel read as two ragged columns. A fixed first column holds the line: the long
   label stacks onto two lines inside it and every value starts at the same place. */
.chat-panel .row{display:grid;grid-template-columns:4.6rem minmax(0,1fr);gap:.85rem;
  align-items:center;padding:.3rem 0;border-top:var(--rule);min-height:44px}
.chat-panel .row .lbl{font-family:var(--display);font-size:.72rem;font-weight:700;
  letter-spacing:.13em;text-transform:uppercase;color:var(--indigo);line-height:1.35}
.chat-panel .row .v{min-width:0}
.chat-panel .row a{font-weight:600;color:var(--ink);overflow-wrap:anywhere}
/* "text" beside the number: the same line reaches both, and it is the quieter of the two
   because tapping the number is what most people want. */
.chat-panel .row a.alt{font-weight:500;font-size:.88rem;color:var(--indigo);margin-left:.35rem}
.chat-panel .row span.val{color:var(--ink-soft);font-size:.92rem}
/* The panel never runs off the top of the screen. It is fixed to the bottom, so on a
   short phone a six row panel simply grew past the viewport: 579px in a 568px window on
   the sister site and 509px on Teapup, both with the top rows unreachable and no way to
   scroll to them (measured 2026-09-21 at 320x568). Capping it against the viewport and
   letting it scroll inside itself is the fix, and overscroll-behavior stops the page
   behind it moving when it reaches the end. Shared verbatim with the other site. */
.chat-panel{max-height:calc(100vh - 96px);overflow-y:auto;overscroll-behavior:contain}
@supports (height:100dvh){.chat-panel{max-height:calc(100dvh - 96px)}}
.chat-panel .shut{position:absolute;top:.4rem;right:.4rem;width:40px;height:40px;
  display:grid;place-items:center;background:none;border:0;cursor:pointer;
  color:var(--ink-soft);font-size:1.3rem;line-height:1}
@media (max-width:640px){
  /* icon only: the labelled pill measured 147px, and on a 375px screen that is a strip
     of furniture lying across two fifths of the page */
  .chat-fab{right:12px;bottom:12px;padding:0;width:54px;height:54px;border-radius:50%;
    justify-content:center;gap:0}
  .chat-fab .fab-label{display:none}
  .chat-panel{right:12px;left:12px;width:auto;bottom:76px}
}
/* It must not come to rest on the last line of the page, and the last line of the page is
   in the FOOTER, which sits outside <main>: padding on main only opens a gap above a
   footer the launcher still covers. The bottom-right of .foot-bottom is the privacy and
   application links, which is exactly where a corner launcher lands. 4.5rem clears the
   tallest of the two shapes (54px circle at 12px, 48px pill at 18px). */
.foot-bottom{padding-bottom:4.5rem}
/* the drawer's overlay is z-index 60 as well, and a launcher floating over an open menu
   is the same bug the call bar had */
body.nav-open .chat-fab,body.nav-open .chat-panel{display:none}

/* ---------- draft banner ---------- */
/* Pinned to the bottom of the window rather than sitting above the header. In flow it
   pushed the hero down by its own height, so the hero no longer ended inside the first
   screen, and its height changes with wrapping so no constant could compensate. The
   banner is preview-only and disappears at cutover; the page below it must measure the
   same either way. */
.draft-banner{position:fixed;left:0;right:0;bottom:0;z-index:60;background:#fff2c9;color:#5b4300;
  border-top:1px solid #e0c46a;font-size:.9rem;text-align:center;padding:.45rem 1rem}

/* ---------- 404 ---------- */
.notfound{min-height:50vh;display:grid;place-items:center;text-align:center}
/* the lede keeps its 62ch measure, so it needs centering as a box too, or it hugs the
   left edge under a centered heading and buttons (2026-10-22 review) */
.notfound .lede{margin-inline:auto}

/* ---------- touch targets ----------
   Last, on purpose. These are overrides of the component rules above, and the component
   rules for the footer and the contact band are declared after the mobile drawer block,
   so an identically-specific override placed up there silently loses the cascade.
   Measured at 375px before this block: the section "more" links were 27px, the contact
   rows 34px, the footer sitemap 36px. Links inside a sentence are deliberately left
   alone; padding those out would break the line box. */
@media (max-width:900px){
  .brand{min-height:44px}
  .section-head .more{display:inline-flex;align-items:center;min-height:44px}
  .contact-lines a{min-height:44px;display:inline-flex;align-items:center;padding:0}
  .foot-grid li a{min-height:44px;display:inline-flex;align-items:center;padding:0}
  .social a{width:44px;height:44px}
  .foot-bottom a{display:inline-flex;align-items:center;min-height:44px}
  .crumbs a{display:inline-flex;align-items:center;min-height:44px}
  /* the breed and parent links in a facts table are the only route from a puppy to its
     parents, so they are navigation rather than prose. The target is on the row now (see
     .pfacts above): a 44px link inside a padded row made every linked row 17px taller
     than every other one, and the row is 52px, so the target is bigger than it was. */
  .foot-grid .small a{display:inline-flex;align-items:center;min-height:44px}
  .brand{min-width:44px}
  .lrow{min-height:56px}
}

/* ---------- light-mode lock: iOS Safari must never invert this site ---------- */
@media (prefers-color-scheme:dark){
  html,body{background:var(--paper)!important;color:var(--ink)!important}
  .site-head,.tile,.reserve,.docs a,.field input,.field select,.field textarea,.choices label{background:var(--white)!important;color:var(--ink)!important}
  .site-head{background:rgba(255,250,245,.94)!important}
  .site-foot{background:var(--paper-deep)!important}
  /* deliberately dark surfaces keep their own colours */
  .band-indigo{background:var(--indigo)!important;color:var(--on-indigo)!important}
  .chat-fab{background:var(--indigo)!important;color:var(--on-indigo)!important}
  .chat-panel{background:var(--white)!important;color:var(--ink)!important}
  .filters button.active{background:var(--ink)!important;color:var(--paper)!important}
  .btn-primary{background:var(--indigo)!important;color:var(--on-indigo)!important}
  .carousel .count{background:rgba(38,35,31,.72)!important;color:var(--paper)!important}
}

@media print{
  .site-head,.chat-fab,.chat-panel,.nav-toggle,.btn,.filters,.cthumbs,.cnav,.draft-banner{display:none!important}
  body{background:#fff;color:#000}
  a{color:#000;text-decoration:none}
  .hero-photo{height:auto}
  .hero-photo::after{display:none}
}

/* ---- the footer sits at the bottom of the window on a short page ------------------
   A page whose content does not fill the viewport left the page background showing
   under the footer: --paper rgb(255,250,245) below --paper-deep rgb(243,236,226). Two
   creams that close together do not read as empty space, they read as a second strip
   of footer (Alex, 2026-09-20). Measured on /puppies at 1440x1016: the content ended
   at 942 against a 1016 window, so 74px of it.

   100svh, not 100vh: on mobile Safari 100vh is the TALLER address-bar-hidden height,
   so the page would overflow by the bar's height and a page with nothing to scroll
   would scroll anyway. */
body{min-height:100svh;display:flex;flex-direction:column}
.site-foot{margin-top:auto}


/* ---- footer seal. Brought over from the sister site on 2026-09-20, where this
   was worked out first and never came back here: object-position:left pinned the seal
   into the corner, and the stretched aspect-ratio box let it size itself off whatever
   the text beside it happened to be, which is why Teapup rendered 98px while the
   sister site rendered 132px from what looked like the same rule. A round seal wants a
   FIXED size and its own column, aligned with the top of the text it sits next to.

   Both seal files are trimmed to 100% ink (teapup 304x305, the sister site 1121x1113),
   so a matching box is a matching ring rather than matching whitespace. Change one site
   and change the other. */
.foot-brand{grid-template-columns:auto minmax(0,1fr);gap:1.25rem;align-items:start}
.seal-wrap{align-self:start;aspect-ratio:auto;width:132px;height:132px;min-height:0;max-height:none}
.foot-grid .seal{width:132px;height:132px;object-fit:contain;object-position:center}
@media (max-width:560px){
  .seal-wrap,.foot-grid .seal{width:104px;height:104px}
  .foot-brand{gap:.9rem}
}

/* ---- a litter card shows the pairing ------------------------------------------------
   The subject of a litter is a pair, not a dog, and the card used to lead with a
   photograph of one of the PUPPIES, which is not what it is about. Two photographs with
   a real gap and a role under each, three cards per row: at four the halves were narrow
   enough that a row of similar dogs ran together (Alex, 2026-09-21).

   Shared verbatim with the sister site. Change one site and change the other. */
.card-pair{display:grid;grid-template-columns:1fr 1fr;gap:.6rem;
  padding:.6rem .6rem 0;background:none}
.card-pair .pair-half{display:block;min-width:0}
.card-pair .pair-half img{width:100%;height:auto;aspect-ratio:1;object-fit:cover;
  display:block;border-radius:var(--radius);background:var(--paper-deep)}
.card-pair .pair-role{display:block;text-align:center;font-family:var(--body);
  font-size:.7rem;font-weight:600;letter-spacing:.11em;text-transform:uppercase;
  color:var(--ink-soft);padding:.45rem .1rem .15rem}

/* ---- phone: section links at the foot, not the head (2026-09-25) --------------------
   "See every available puppy", "All our dogs" and the like sat above the cards, where they
   read as a caption. Below 760px they move after the content, where they read as the next
   step. display:contents lifts the heading and the link into the wrap's own flex column, so
   the link can take order:99 without the generator writing two copies of it. :has() limits
   it to sections whose header carries a link, so no other layout changes. */
@media (max-width: 760px){
  .wrap:has(> .section-head > .more){display:flex;flex-direction:column}
  .wrap:has(> .section-head > .more) > .section-head{display:contents}
  .wrap:has(> .section-head > .more) > .section-head > :not(.more){margin-bottom:1.5rem}
  .wrap:has(> .section-head > .more) > .section-head > .more{order:99;margin-top:1.25rem;align-self:flex-start}
}

/* Guide links (2026-09-26). The breed pages, the guides hub and every guide list their
   guides as a ruled index of a title and one line, the same ledger rhythm as the breed
   rows, since this site does not use card grids. */
.guide-list{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(auto-fill,minmax(min(100%,20rem),1fr));gap:0 2.5rem}
.guide-list li{border-top:1px solid var(--rule);padding:.85rem 0 .95rem;min-width:0;max-width:none}
.guide-list a{font-weight:600}
.guide-list span{display:block;margin-top:.2rem;color:var(--ink-soft);font-size:.95rem;max-width:52ch}
.alt-body .guide-list{grid-template-columns:1fr;margin:.75rem 0 1rem}
.guide-facts{margin-top:1.4rem;max-width:40rem}
.guide-prose{max-width:var(--maxw-reading)}
.guide-prose h2:not(:first-child){margin-top:2.25rem}
.guide-facts .row{grid-template-columns:12rem minmax(0,1fr)}
@media (max-width:480px){.guide-facts .row{grid-template-columns:8.5rem minmax(0,1fr)}}

/* Questions page (2026-09-26): each question is a <details> that opens to its answer, so the
   page reads as a short list of questions. It works with JavaScript off, is keyboard operable,
   and the browser still finds text inside a closed answer. */
.faq-tools{margin:0 0 1rem}
.faq-list{border-bottom:var(--rule-strong)}
.faq-item{border-top:var(--rule-strong)}
.faq-item > summary{display:flex;align-items:center;justify-content:space-between;gap:1rem;min-height:44px;
  padding:1.05rem 0;cursor:pointer;list-style:none}
.faq-item > summary::-webkit-details-marker{display:none}
.faq-item .faq-q{margin:0;font-size:1.15rem;line-height:1.3}
.faq-item .faq-more{display:flex;flex:none;color:var(--ink-soft)}
.faq-item .faq-more svg{transition:transform .2s ease}
.faq-item[open] .faq-more svg{transform:rotate(180deg)}
.faq-item .faq-a{padding:0 0 1.25rem}
@media (prefers-reduced-motion:reduce){.faq-item .faq-more svg{transition:none}}
/* In-copy links to the guides (2026-09-26): the breeds index names each breed's guides, and
   litter pages and the breed hero point at them in one line. */
.breed-guides{margin:.9rem 0 0;line-height:1.7}
.breed-guides .bf-l{margin-right:.5rem}
.guide-pointer{margin-top:.75rem}

/* The price on a puppy page is set in the name's own size and sits on the name's baseline,
   so "Autumn  $2,000" reads as one line (Alex, 2026-09-26: "that should line up with the
   name of the puppy"). It was a smaller heading bottom-aligned beside a larger one, so the
   two never shared a baseline. "Limited registration" stays small and doesn't scale with it. */
.detail-head{align-items:baseline}
.detail-head .price-line{font-size:clamp(2.3rem,4.6vw,4.1rem);line-height:1.1}
.detail-head .price-line small{font-size:1rem}
@media (max-width:600px){.detail-head .price-line small{font-size:.9rem}}
/* A second way to apply, under the name and price on a phone (2026-11-06 review: the
   Reserve card's button sat about 1,700px down). The Reserve card keeps its own button. */
.detail-head .head-apply{display:none}
@media (max-width:767px){
  .detail-head .head-apply{display:flex;flex:1 0 100%;min-height:44px;padding:.55rem 1rem}
}

/* ---------- reviews, 2026-09-27 ----------
   One card shape for letters and Google reviews. On the home page they sit in a row that
   stays still, four to a screen on a desktop, with an arrow on each side that pages through
   the rest (Alex asked for this in place of a drifting track). A phone swipes the same row,
   and the arrows are still there for anyone who would rather tap. Without scripts it is a
   plain scrolling row. The breed wheel is circles and photos, so this is rectangles and
   words on purpose. The reviews page lays the same cards out in columns at full length. */
.rtrack{--rt-gap:clamp(1rem,1.6vw,1.5rem);--rt-accent:var(--indigo,var(--accent));
  display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:clamp(.4rem,1vw,.9rem)}
.rtrack-window{container-type:inline-size;overflow-x:auto;scroll-snap-type:x mandatory;scrollbar-width:none;
  padding:.25rem 0 .75rem;outline-offset:4px;scroll-behavior:smooth}
.rtrack-window::-webkit-scrollbar{display:none}
.rtrack-list{display:flex;gap:var(--rt-gap);margin:0;padding:0;list-style:none;width:max-content}
.rtrack-list > li{flex:0 0 auto;width:calc((100cqw - 3 * var(--rt-gap)) / 4);scroll-snap-align:start;display:flex}
@container (max-width: 900px){.rtrack-list > li{width:calc((100cqw - var(--rt-gap)) / 2)}}
@container (max-width: 560px){.rtrack-list > li{width:100cqw}}
.rtrack-arrow{width:44px;height:44px;border-radius:50%;border:var(--rule);background:var(--white);color:var(--ink);
  display:grid;place-items:center;cursor:pointer;padding:0;flex:none;transition:background .15s,opacity .15s}
.rtrack-arrow.prev svg{transform:scaleX(-1)}
.rtrack-arrow:hover:not(:disabled){background:var(--paper-deep)}
.rtrack-arrow:disabled{opacity:.35;cursor:default}
.rtrack-arrow[hidden]{display:none}
.rtrack.fits .rtrack-arrow{visibility:hidden}
/* the arrows stay 44px on a phone, the tap-target floor (2026-10-22 review); only the gap closes */
@media (max-width:560px){.rtrack{gap:.35rem}}
@media (prefers-reduced-motion: reduce){.rtrack-window{scroll-behavior:auto}}
.rcard{flex:1;display:flex;flex-direction:column;gap:.85rem;background:var(--white);border:var(--rule);border-radius:var(--radius);padding:1.3rem 1.35rem 1.25rem;min-width:0}
.rc-head{display:flex;align-items:center;gap:.8rem;margin-top:auto}
.rcard:not(.full) .rc-head{margin-top:0;order:-1}
.rc-face{flex:0 0 56px;width:56px;height:56px;border-radius:12px;overflow:hidden;background:var(--paper-deep);display:grid;place-items:center}
.rc-face img{width:100%;height:100%;object-fit:cover;display:block}
.rc-mono{font-family:var(--display);font-weight:600;font-size:1.45rem;color:var(--rt-accent,var(--ink))}
.rc-who{display:flex;flex-direction:column;line-height:1.3;min-width:0}
.rc-name{font-family:var(--display);font-weight:600;color:var(--ink)}
.rc-src{font-size:.84rem;color:var(--ink-soft)}
.rc-stars{color:#946200;letter-spacing:.12em;font-size:1rem;line-height:1}
.rc-quote{margin:0;font-size:1rem;line-height:1.62;color:var(--ink)}
.rc-quote p{margin:0 0 .7em;max-width:none}
.rc-quote p:last-child{margin-bottom:0}
.rcard:not(.full) .rc-quote p{display:-webkit-box;-webkit-line-clamp:7;-webkit-box-orient:vertical;overflow:hidden}
.rc-more{font-family:var(--display);font-weight:600;font-size:.92rem;margin-top:auto}
.rwall{columns:4 270px;column-gap:1.5rem;margin:0;padding:0;list-style:none}
.rwall > li{break-inside:avoid;margin:0 0 1.5rem;display:flex}
.rcard.full{--rt-accent:var(--indigo,var(--accent))}
.rc-photo{margin:-1.3rem -1.35rem .1rem;border-radius:var(--radius) var(--radius) 0 0;overflow:hidden;background:var(--paper-deep)}
.rc-photo img{width:100%;aspect-ratio:4/3;object-fit:cover;display:block}

/* ---------- our team, 2026-09-27 ----------
   One card a person under the family's story. Square photos, so a row of faces lines up
   whatever shape the owners' photos arrive in. */
/* Team cards (Alex, 2026-11-06). --n is the people per row, written by team_per_row():
   up to six on one row, then 4 and 3, 4 and 4, 5 and 4. Capping the list at that many cards'
   width forces the break, and center puts a short row in the middle. On a tablet --t keeps
   rows of four or fewer and takes five or six three at a time, and a phone shows two a row. */
.team-grid{--per:var(--n,4);--g:clamp(1.25rem,2.2vw,2rem);--card:15rem;display:flex;flex-wrap:wrap;justify-content:center;gap:var(--g);
  max-width:calc(var(--per) * var(--card) + (var(--per) - 1) * var(--g));margin:1.5rem auto 0;padding:0;list-style:none}
.team-grid > .team-card{flex:0 0 calc((100% - (var(--per) - 1) * var(--g)) / var(--per));min-width:0}
@media (max-width:900px){.team-grid{--per:var(--t,3)}}
.team-card{display:flex;flex-direction:column;gap:.35rem}
.team-photo{margin:0 0 .6rem;border-radius:var(--radius);overflow:hidden;background:var(--paper-deep)}
.team-photo img{width:100%;aspect-ratio:1/1;object-fit:cover;display:block}
.team-name{font-size:1.15rem;margin:0}
.team-role{margin:0;font-size:.9rem;color:var(--ink-soft);font-family:var(--display);font-weight:600}
.team-bio{margin:.2rem 0 0;font-size:.96rem}
@media (max-width:560px){.team-grid{--per:1;--card:22rem;--g:1.75rem}}

/* ---------- puppies page filters (Alex, 2026-10-05) ----------
   On a computer, tags beside Available now, each opening a small menu; a chosen tag fills in
   and its cross clears it. Square corners match the puppy tiles. On a phone the tags give way
   to one Filters button and a bottom sheet with every filter, and the chosen filters show as
   tags under the heading. The heading row stays pinned under the header there, as the old
   finder did, because the page runs very long on a phone. */
.avail-head{display:flex;align-items:center;justify-content:space-between;gap:.75rem 1.5rem;flex-wrap:wrap;margin-bottom:clamp(1.25rem,2.5vw,1.75rem)}
.avail-head h2{margin:0}
.ftags{display:flex;flex-wrap:wrap;gap:.5rem;justify-content:flex-end;align-items:center}
.ft-wrap{position:relative;display:inline-flex}
.ft{display:inline-flex;align-items:center;gap:.4rem;min-height:42px;padding:0 .9rem;border:1.5px solid rgba(38,35,31,.2);border-radius:var(--radius);background:var(--white);font:inherit;font-size:.95rem;color:var(--ink);cursor:pointer;white-space:nowrap}
.ft:hover{border-color:var(--indigo)}
.ft:focus-visible,.ft-search:focus-within,.ft-x:focus-visible,.f-open:focus-visible,.f-chip:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(48,47,81,.25)}
.ft .chev{color:var(--indigo)}
.ft.on{background:var(--indigo);border-color:var(--indigo);color:#fff;border-top-right-radius:0;border-bottom-right-radius:0}
.ft.on .chev{display:none}
.ft-x{min-width:38px;border:0;background:var(--indigo);color:#fff;font-size:1.15rem;line-height:1;cursor:pointer;border-radius:0 var(--radius) var(--radius) 0;border-left:1px solid rgba(255,255,255,.3)}
.ft-search{cursor:text;color:var(--ink-soft);width:9.5rem}
.ft-search input{border:0;outline:0;background:none;font:inherit;color:var(--ink);width:100%;min-width:0}
.ft-menu{position:absolute;top:calc(100% + .35rem);left:0;z-index:45;list-style:none;margin:0;padding:.3rem;min-width:14rem;max-height:20rem;overflow-y:auto;background:var(--white);border:1px solid rgba(38,35,31,.14);border-radius:var(--radius);box-shadow:0 12px 28px -10px rgba(38,35,31,.4)}
.ft-menu.flip{left:auto;right:0}
.ft-menu li{padding:.6rem .8rem;border-radius:4px;cursor:pointer;white-space:nowrap}
.ft-menu li:hover,.ft-menu li.act{background:rgba(48,47,81,.08)}
.ft-menu li[aria-selected="true"]{font-weight:600}
.ft-menu li[aria-selected="true"]::after{content:"\2713";float:right;margin-left:1rem;color:var(--indigo)}
.f-open,.f-active{display:none}
.f-empty{margin:0 0 1.5rem}
.link-btn{background:none;border:0;padding:0;font:inherit;color:inherit;text-decoration:underline;cursor:pointer}
.f-scrim{position:fixed;inset:0;z-index:90;background:rgba(38,35,31,.45)}
.f-sheet{position:fixed;left:0;right:0;bottom:0;z-index:91;max-height:82vh;display:flex;flex-direction:column;background:var(--paper);border-radius:12px 12px 0 0;box-shadow:0 -12px 32px -12px rgba(38,35,31,.45);padding-bottom:env(safe-area-inset-bottom)}
.f-sheet-head{display:flex;align-items:center;justify-content:space-between;padding:1rem 1.1rem .5rem}
.f-sheet-head h3{margin:0;font-size:1.2rem}
.f-sheet-close{width:44px;height:44px;border:0;background:none;font-size:1.6rem;line-height:1;color:var(--ink);cursor:pointer}
.f-sheet-body{overflow-y:auto;padding:.25rem 1.1rem 1rem}
.f-sheet-foot{display:flex;gap:.75rem;padding:.75rem 1.1rem 1rem;border-top:var(--rule)}
.f-sheet-foot button{flex:1;min-height:48px;border-radius:var(--radius);font:inherit;font-weight:600;cursor:pointer}
.f-clear-all{border:1.5px solid rgba(38,35,31,.25);background:var(--white);color:var(--ink)}
.f-show{border:0;background:var(--indigo);color:#fff}
.f-group{margin-bottom:1.1rem}
.f-group h4{margin:.4rem 0 .55rem;font-family:var(--display);font-size:.75rem;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-soft)}
.f-chips{display:flex;flex-wrap:wrap;gap:.45rem}
.f-chip{min-height:42px;padding:0 .9rem;border:1.5px solid rgba(38,35,31,.2);border-radius:var(--radius);background:var(--white);font:inherit;color:var(--ink);cursor:pointer}
.f-chip[aria-pressed="true"]{background:var(--indigo);border-color:var(--indigo);color:#fff}
.f-name{display:flex;align-items:center;gap:.5rem;min-height:46px;padding:0 .9rem;border:1.5px solid rgba(38,35,31,.2);border-radius:var(--radius);background:var(--white);color:var(--ink-soft)}
.f-name input{border:0;outline:0;background:none;font:inherit;font-size:1rem;width:100%;color:var(--ink)}
@media (prefers-reduced-motion:no-preference){.f-sheet{animation:fSheetUp .22s ease-out}}
@keyframes fSheetUp{from{transform:translateY(24px);opacity:.6}to{transform:none;opacity:1}}
@media (max-width:760px){
  .avail-head{position:sticky;top:66px;z-index:40;background:var(--paper);margin-left:-4vw;margin-right:-4vw;padding:.6rem 4vw .7rem;border-bottom:var(--rule);box-shadow:0 8px 14px -12px rgba(38,35,31,.5)}
  .avail-head h2{font-size:1.2rem}
  .ftags{display:none}
  .f-open{display:inline-flex;align-items:center;gap:.45rem;min-height:42px;padding:0 .95rem;border:1.5px solid var(--indigo);border-radius:var(--radius);background:var(--white);color:var(--indigo);font:inherit;font-weight:600;cursor:pointer}
  .f-open .dot{min-width:20px;height:20px;border-radius:10px;background:var(--indigo);color:#fff;font-size:.75rem;display:inline-flex;align-items:center;justify-content:center;padding:0 .3rem}
  .f-active:not(:empty){display:flex;flex-wrap:wrap;gap:.4rem;flex-basis:100%}
  .f-active .ft{min-height:36px;font-size:.9rem}
}
.sheet .tile[hidden]{display:none}

/* GUIDES HUB ROWS (2026-10-03, revised 2026-10-04). Each photo sits centered against the
   guides beside it (Alex, 2026-10-04: "i want the image to be vertically aligned with the
   guides to the side of them"). That only reads well while a list stays short, which is why
   the color pages fold into one line under their breed's colors guide. */
.hub-row{align-items:center}

/* DELIVERY PAGE (Alex, 2026-10-20: delivery "deserves its own page" and should "look
   professional in terms of the four services they offer"). The four options are cards in the
   owners' order. Furry Freight runs across the top with its logo beside the copy, the pattern
   blessyourpaws process.html and the Puppy Connection transportation page use, and its states
   sit in region columns as on the Bless Your Paws puppies page rather than a bullet outline.
   The other three are equal cards whose actions sit on the bottom edge, so the buttons line up
   whatever each card says. Square 4px corners (Alex, 2026-10-14). Shared with the sister
   site; change one and change the other. */
.process > li[id]{scroll-margin-top:96px}
.dlv-pickup{margin:.9rem 0 0;color:var(--ink-soft)}
.dlv{display:grid;gap:clamp(1rem,2vw,1.5rem)}
.dlv > *,.dlv-main > *,.dlv-trio > *,.dlv-regions > *{min-width:0}
.dlv-card{background:var(--white);border:var(--rule);border-radius:4px;padding:clamp(1.35rem,3vw,2.25rem);display:flex;flex-direction:column;box-shadow:0 1px 3px rgba(38,35,31,.07)}
.dlv-card h2{font-size:clamp(1.45rem,2.2vw,1.9rem);margin:0 0 .6rem}
.dlv-card p{margin:0 0 .9rem}
.dlv-card .kicker{margin-bottom:.5rem}
.dlv-card a[href^="mailto:"]{overflow-wrap:anywhere}
.dlv-main{display:grid;grid-template-columns:minmax(0,200px) minmax(0,1fr);gap:clamp(1.5rem,4vw,3.25rem);align-items:center;border-top:3px solid var(--indigo)}
.dlv-main.dlv-solo{grid-template-columns:minmax(0,1fr);text-align:center}
.dlv-solo .dlv-copy{margin:0 auto;max-width:62ch}
.dlv-solo .btn-row{justify-content:center}
.dlv-main.dlv-solo{gap:.75rem}
.dlv-solo > .kicker{justify-self:start;text-align:left;margin:0}
.dlv-solo > .dlv-solo-link{justify-self:end;text-align:right;margin:0}
.dlv-logo img{display:block;width:100%;height:auto}
.dlv-copy > *{max-width:62ch}
.dlv-copy .btn-row{margin-top:1.1rem}
.dlv-fine{font-size:.94rem;color:var(--ink-soft)}
.dlv-copy .dlv-fine{margin:.9rem 0 0}
.dlv-states{grid-column:1 / -1;border-top:var(--rule);padding-top:clamp(1.2rem,2.5vw,1.6rem)}
.dlv-states h3{font-size:clamp(1.1rem,1.5vw,1.3rem);margin:0 0 1rem}
.dlv-regions{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1.1rem clamp(1.25rem,3vw,2.5rem)}
.dlv-regions h4{font-family:var(--display);font-size:.78rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--indigo);margin:0 0 .45rem}
.dlv-regions p{margin:0;line-height:1.6}
.dlv-trio{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:clamp(1rem,2vw,1.5rem)}
.dlv-act{margin-top:auto;padding-top:.5rem}
.dlv-act .btn-row{margin-top:0}
.dlv-act .dlv-fine{margin:.8rem 0 0}
.dlv-rate{font-family:var(--display);color:var(--indigo);font-size:1.05rem;line-height:1.2}
.dlv-rate b{font-size:2.4rem;font-weight:600;line-height:1;margin-right:.25rem}
@media (max-width:960px){.dlv-trio{grid-template-columns:minmax(0,1fr)}}
@media (max-width:700px){.dlv-main{grid-template-columns:minmax(0,1fr)}.dlv-logo{max-width:160px}.dlv-regions{grid-template-columns:minmax(0,1fr)}}

/* ---- tap targets (survey 2026-10-22, shared with the sister site; change one, change the other).
   Where a row holds several links, its heading is the real link and its photo is a second link
   hidden from screen readers and the keyboard (.media-link), so a photo tap goes where the
   heading goes and a screen reader hears one link per destination. The look does not change:
   the heading keeps its color and takes the accent on hover, as a card name does, and the
   photo brightens as a puppy tile does. The footer seal is a hidden link home, like the header
   lockup, which has no hover change either. */
.media-link{display:block;color:inherit}
.head-link{color:inherit;text-decoration:none}
@media (hover:hover){
  .head-link:hover,.alt-row:has(.media-link:hover) .head-link,.feature:has(.media-link:hover) .head-link{color:var(--indigo)}
  .media-link:hover img{filter:brightness(1.03)}
}
@media (hover:none){.media-link:active{opacity:.72}}
/* A swipe on a carousel photo turns it (main.js). pan-y leaves the vertical page scroll and
   pinch zoom to the browser and hands only the sideways movement to the script. */
.carousel .frame{touch-action:pan-y pinch-zoom}

/* ---------- For families menu (2026-11-04, Alex; Puppy Connection's pattern) ----------
   A details element in the main menu. On a wide screen the list drops below the label and
   main.js also opens it on hover; in the phone drawer it expands in place. The label is a
   summary, so it is styled to match the links beside it. */
.nav-drop{position:relative}
.nav-drop>summary{list-style:none;cursor:pointer;font-family:var(--display);font-weight:500;font-size:1rem;color:var(--ink);padding:.65rem .2rem;display:inline-flex;align-items:center;gap:.4rem;border-bottom:2px solid transparent;white-space:nowrap}
.nav-drop>summary::-webkit-details-marker{display:none}
.nav-drop>summary::after{content:"";width:.4em;height:.4em;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:translateY(-.18em) rotate(45deg);transition:transform .2s ease}
.nav-drop[open]>summary::after{transform:translateY(.08em) rotate(-135deg)}
.nav-drop>summary.is-current{border-bottom-color:var(--indigo);color:var(--indigo)}
.nav-drop-menu{list-style:none;margin:0;padding:.45rem 0;position:absolute;top:100%;left:-1rem;min-width:15.5rem;background:var(--paper);border:var(--rule);border-radius:var(--radius);box-shadow:0 16px 36px -16px rgba(38,35,31,.38);z-index:90}
.nav-links .nav-drop-menu a{display:block;padding:.6rem 1rem;border-bottom:0}
.nav-links .nav-drop-menu a[aria-current="page"]{border-bottom:0;color:var(--indigo);background:var(--indigo-tint)}
@media (hover:hover){
  .nav-drop>summary:hover{color:var(--indigo)}
  .nav-links .nav-drop-menu a:hover{background:var(--paper-deep)}
}
@media (min-width:901px) and (max-width:1180px){
  .nav-drop>summary{font-size:.92rem}
}
@media (max-width:900px){
  .nav-drop>summary{display:flex;justify-content:space-between;width:100%;font-size:1.2rem;padding:.75rem .2rem;border-bottom:var(--rule)}
  .nav-drop>summary.is-current{border-bottom:var(--rule)}
  .nav-drop-menu{position:static;min-width:0;padding:0 0 .35rem 1rem;background:none;border:0;border-radius:0;box-shadow:none}
  .nav-links .nav-drop-menu a{font-size:1.05rem;padding:.6rem .2rem;border-bottom:var(--rule)}
  .nav-links .nav-drop-menu a[aria-current="page"]{border-bottom:var(--rule);background:none}
}

/* ---------- NuVet supplements page (2026-11-04) ----------
   The order code sits in a quiet panel under the lede. Each product row keeps the copy and
   the button on the left and the photograph on the right on both rows, unlike the guides'
   alternating rows, and the photograph goes above the copy once the row stacks. */
.nv-rows .alt-row{grid-template-columns:minmax(0,1fr) minmax(0,1fr)}
.nv-rows .alt-row .alt-media{order:2}
.nv-facts{margin:1.1rem 0 1.4rem}
/* Each product row's buttons end on NuVet's order code (Alex, 2026-11-06), in place of the
   code panel the hero had. Centered on the buttons' line, and under them on a phone. */
.nv-rows .btn-row{align-items:center}
.nv-chip{display:inline-flex;flex-direction:column;justify-content:center;padding:0 .25rem}
.nv-chip-lbl{font-family:var(--display);font-size:.7rem;font-weight:600;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-soft)}
.nv-chip-num{font-family:var(--display);font-size:1.45rem;line-height:1.1;letter-spacing:.05em;color:var(--indigo)}
.nv-steps{list-style:none;margin:1.5rem 0 0;padding:0;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:2.25rem;counter-reset:nv}
.nv-steps li{counter-increment:nv;border-top:var(--rule-strong);padding-top:1.1rem}
.nv-steps li::before{content:counter(nv);display:block;font-family:var(--display);font-size:2.2rem;line-height:1;color:var(--indigo);margin-bottom:.7rem}
.nv-steps h3{margin:0 0 .45rem}
.nv-steps p{margin:0}
@media (max-width:860px){
  .nv-rows .alt-row{grid-template-columns:1fr}
  .nv-rows .alt-row .alt-media{order:-1}
  .nv-steps{grid-template-columns:1fr;gap:1.5rem}
}

/* NuVet's product photographs are 400x300 on white, so the cell becomes a white panel of the
   row's 3:2 shape and the bottle sits in it at its own size (2026-11-04). */
.alt-media.nv-product{background:#fff;aspect-ratio:3/2;display:flex;align-items:center;justify-content:center;
  border:1px solid rgba(38,35,31,.14);box-shadow:0 12px 28px -18px rgba(38,35,31,.35)}  /* option C, Alex 2026-11-04 */
.alt-media.nv-product img{width:auto;max-width:min(100%,400px);height:auto;aspect-ratio:auto;object-fit:contain}

/* Policies page (2026-11-05): one short answer per question, in the document column */
.policies h2{font-size:clamp(1.2rem,2vw,1.45rem);margin:2.1rem 0 .55rem}
.policies h2:first-child{margin-top:0}
/* A product bag beside the food paragraph of a guide (2026-11-05), on a white panel like the NuVet products */
.food-bag{float:right;width:min(42%,190px);margin:.25rem 0 1rem 1.5rem;padding:.75rem;background:#fff;border-radius:4px}
.food-bag img{display:block;width:100%;height:auto}
@media (max-width:560px){.food-bag{float:none;width:170px;margin:0 auto 1rem}}
/* How it works step 3 (2026-11-07): the food bag beside the age rule, and the care package
   under its own small heading. flow-root keeps the floated bag inside the step. */
.food-rule{display:flow-root;margin-top:1.25rem}
.food-rule .food-bag{float:right;width:min(38%,150px);margin:0 0 .5rem 1.25rem}
.process .p-body h3{font-size:1.15rem;margin:1.5rem 0 .25rem}
.process .p-body ul + p{margin-top:1rem}
/* step 3's copy runs far longer than its photo, so the photo sits at the top and stays in view
   rather than floating in the middle of an empty column */
@media (min-width:861px){.process > li#prepare{align-items:start}.process > li#prepare .p-media{position:sticky;top:96px}}
/* Step 3's three parts start closed (Alex, 2026-11-07): the Questions page's rows and chevron
   (.faq-item) inside the step, with the step heading and intro sentence left open above them */
.prep-parts{margin-top:1.25rem;border-bottom:var(--rule-strong)}
.process .p-body .prep-part .faq-q{margin:0;font-size:1.1rem;line-height:1.3}
.prep-part > summary{padding:.75rem 0}
.prep-part .faq-a > :first-child{margin-top:0}
/* The words wrap around the bag in a guide row too, as on the sister site (Alex,
   2026-11-07, replacing the 2026-11-06 review rule that set it above the text there). */

/* Team section (Alex, 2026-11-06): the line under the heading sits close to it, and the cards
   get the room instead */
.section-head:has(+ .team-lede){margin-bottom:.75rem}
.team-lede{margin-bottom:clamp(2rem,3.5vw,2.75rem)}
.team-lede + .team-grid{margin-top:0}
