/* Vetmira marketing site.
 *
 * Plain CSS, no build step, no framework. The whole site is twelve HTML files
 * and this stylesheet, served straight off disk by Caddy, a marketing page that
 * needs a toolchain is a marketing page that stops getting edited.
 *
 * A practice that sees the site and then sees the software should be looking at
 * the same thing. That continuity is carried by the mark, the screenshots and
 * the typeface, not by a shared colour token: this file defines no blue at all,
 * and the two blues in play are different values on purpose. The mark is
 * #0284C7, a fixed brand value that tracks no palette (see the comment in
 * vetmira-admin/src/components/Logo.tsx), and the staff app's own accent is
 * blue-600, #2563EB. Neither is wrong and neither should be moved onto the
 * other without a brand decision.
 */

/* ---- the typeface ---------------------------------------------------- */
/*
 * Geist, self-hosted, and it is the same face the staff app loads: see
 * `--font-geist-sans` in vetmira-admin/src/app/globals.css. The comment above
 * says a practice that sees the site and then sees the software should be
 * looking at the same thing, and until now that rested on the mark alone. The
 * site was set in `ui-sans-serif, system-ui`, which is SF on a Mac, Segoe on
 * Windows and Roboto on Android: three different sites, none of them ours, and
 * none of them the product's.
 *
 * **Self-hosted, and that is the load-bearing part.** /privacy says this site
 * calls no third party, and a `fonts.googleapis.com` stylesheet would make that
 * sentence false the day it shipped. The files sit in /assets and are served by
 * the same Caddy as everything else. No CSP change was needed: the marketing
 * site's policy is `default-src 'self'` with no `font-src` of its own, so
 * `font-src` falls back to `'self'` and these already pass.
 *
 * The variable font, so one file covers 400 through 660 and every weight this
 * stylesheet asks for costs nothing extra. 29KB for latin, 16KB for latin-ext,
 * which is less than one of the screenshots.
 *
 * `font-display: swap`, never `block`. The first thing a practice owner reads
 * is the headline, and holding it back for a font is trading legibility for
 * typography. The fallback below it is metric-mismatched by a few percent, so
 * the swap moves the line slightly; that is the cheaper of the two failures.
 *
 * These are the Google Fonts builds of an OFL face rather than files we cut, so
 * re-fetching them is `curl` against the URLs in the css2 API, not a designer.
 */
@font-face {
	font-family: "Geist";
	font-style: normal;
	font-weight: 100 900;
	font-display: swap;
	src: url(/assets/geist-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;
}
/* An owner's name is the reason this second file exists. The site's own prose is
   plain latin, so this is fetched only when a page actually contains one. */
@font-face {
	font-family: "Geist";
	font-style: normal;
	font-weight: 100 900;
	font-display: swap;
	src: url(/assets/geist-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;
}

/* One palette, and it is a warm one.
 *
 * This file used to carry two: a light set here and a dark set in a media query
 * at the foot. That block is gone, and its removal is what made everything below
 * possible rather than being a tidy-up on the side. Every colour here had to
 * work in two contexts, and warm colours do not survive that. Cream has no dark
 * equivalent: inverted it goes muddy brown, or it loses the warmth entirely and
 * lands on the same neutral charcoal every other site uses. Holding both meant
 * the palette could only ever be neutral, which is exactly what it was.
 *
 * **The cost is real and is being paid on purpose.** Somebody whose system asks
 * for dark now gets a light page. The comment that used to sit on the dark block
 * worried about a clinic reading this at seven in the morning with the lights
 * off, and that reader has not gone away. What they get instead is a ground that
 * is warm rather than #ffffff, which is materially gentler at that hour than the
 * page they were being handed before.
 *
 * ## Where the warmth comes from
 *
 * The amber was already ours. `assets/vetmira-mark.svg` is four blue toes and
 * one amber, and this stylesheet had never referenced amber once in its life.
 * So this is not a new brand, it is the half of the existing mark that was being
 * ignored, brought up to the size it should always have been.
 *
 * ## What stays cool, and why
 *
 * Blue is demoted, not deleted. The product is a blue application and the rule
 * at the top of this file is that a practice seeing the site and then the
 * software should be looking at the same thing. So blue survives in the mark and
 * in the screenshots, and warmth owns everything around them: a warm room with
 * cool instruments in it. The mark's blue and the app's are two different
 * values, which is fine here because this file never has to name either one.
 *
 * That is also a constraint rather than a preference. Every screenshot on this
 * site is a white application on cool grey, #ffffff and #f6f7f9 by a long way.
 * A full cream ground turns each one into a bright hole punched in the page, so
 * the warmth is layered instead: --paper is barely warm and is what screenshots
 * sit on, --paper-soft carries the alternating bands, and the strongest warmth
 * is kept for the ornaments and the closing block, where nothing cool sits.
 *
 * ## Contrast
 *
 * Every pairing below was calculated rather than eyeballed, because a warm
 * palette on a warm ground has much less headroom than ink on white did. The
 * ratios are recorded beside each token. Anything new has to be checked the same
 * way: several of these are within a tenth of failing.
 */
:root {
	/* 14.8:1 on --paper. A warm charcoal, not slate navy: this single token is
	   most of the difference between the page reading as clinical and reading as
	   human, because it is the colour of nearly every word on the site. */
	--ink: #2b2320;
	--ink-soft: #6b5d52;            /* 6.1:1 on --paper, 5.6:1 on --paper-soft */
	/* 5.3:1 on --paper and 4.85:1 on --paper-soft. It was #7a6b5c first, which is
	   4.94 and 4.50: the second of those is AA by a hundredth, on the captions
	   and hints that are already the smallest text here. Not a margin to ship. */
	--ink-faint: #756657;
	--line: #e8dccb;                /* tan hairline, decorative only */
	--paper: #fdfaf5;
	--paper-soft: #f6efe4;

	/* Terracotta. --brand is the decorative weight (list markers, the disclosure
	   chevron, the ornaments) and --brand-dark is the one that carries text and
	   fills buttons.
	 *
	 * --brand-dark was #a8502f until the hero was checked. The hero's tint is
	 * warmest exactly where the text sits, and a link on it came out 4.24:1. Both
	 * the tint and this moved: 5.8:1 on --paper, 5.3:1 on --paper-soft, 5.2:1 on
	 * the hero at its warmest, and 6.1:1 for white on it. */
	--brand: #c2673e;
	--brand-dark: #9e4a2b;
	--brand-deep: #7f3a20;          /* hover only, darker still */
	--brand-wash: #fbf0e8;
	--brand-tint: #f9eadf;

	/* The mark's fifth toe, at last used for something. Decorative only: at
	   2.8:1 on --paper it is under the 3:1 an interface element would need, so it
	   may mark a heart in an ornament and may not carry meaning on its own. */
	--accent: #d98324;

	/* The closing block. Deep warm brown rather than the deep blue it replaced,
	   which was the one colour on the site the old dark scheme was forbidden from
	   flipping. Now that nothing flips, this is simply a colour.
	   White on it is 13.7:1 and --invert-ink-soft is 9.1:1. */
	--invert-bg: #3a2a22;
	--invert-ink: #ffffff;
	--invert-ink-soft: #e0cfc2;

	--measure: 68ch;

	/* Corner radius and elevation, as three values each rather than as a number
	   typed at each call site.
	 *
	 * Every framed thing on this site was 12px or 14px with a hairline border and
	 * no shadow, which is a flat document. The scale below is the difference
	 * between a card and a screenshot stated once: a control is --radius, a panel
	 * is --radius-lg, and anything holding a picture is --radius-xl.
	 *
	 * The shadows are deliberately two-layer. One large soft shadow reads as a
	 * drop shadow from 2012; a tight 1px contact shadow under a wide diffuse one
	 * is what makes a card look like it is resting on the page. Both are the ink
	 * colour at low alpha rather than black, so they tint rather than grey out. */
	--radius: 12px;
	--radius-lg: 18px;
	--radius-xl: 20px;
	--shadow-sm: 0 1px 2px rgba(67, 50, 38, 0.05);
	--shadow-md: 0 1px 2px rgba(67, 50, 38, 0.04), 0 8px 24px rgba(67, 50, 38, 0.06);
	--shadow-lg: 0 1px 2px rgba(67, 50, 38, 0.04), 0 18px 44px rgba(67, 50, 38, 0.10);

	/* One duration and one curve, so nothing on the site animates at a speed
	   nothing else does. 160ms is under the threshold where a hover starts to
	   feel like it is lagging behind the pointer. Every use of these is disabled
	   wholesale by the reduced-motion block at the foot of this file. */
	--motion: 160ms;
	--ease: cubic-bezier(0.4, 0, 0.2, 1);
	/* How wide a phone screenshot is drawn. Named because two rules need to
	   agree on it: the picture itself, and the column it sits in when it is
	   beside prose. They disagreed once and the column won, at 642px.
	   Also the `sizes` hint on the picture itself, which has to be changed
	   with it or the browser fetches for the old width.

	   260 and not 320. One phone viewport is 2.17 times as tall as it is
	   wide, so at 320 the picture came out 691px against about 370px of prose
	   beside it, and the section carried 450px of nothing under the text. The
	   crop is not the problem and should not be trimmed to fix this: 2700px is
	   exactly one 900px screen at 3x, which is what makes it a screenshot
	   rather than a composition. Drawing it smaller is the honest lever. */
	--phone-width: 260px;
}

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

/* 18px, not 17. The old size was the app's, and an application is read by
   somebody who is already using it while a marketing page is read by somebody
   deciding whether to. Every competitor in this category sets 18 to 20.

   The fallback stack is the one that was here, unchanged, because it is what a
   visitor sees for the first few hundred milliseconds and on any request where
   the font 404s. */
body {
	margin: 0;
	background: var(--paper);
	color: var(--ink);
	font: 400 18px/1.6 "Geist", ui-sans-serif, system-ui, -apple-system, "Segoe UI",
	      Roboto, "Helvetica Neue", Arial, sans-serif;
	-webkit-font-smoothing: antialiased;
}

/* `font-variant-numeric: tabular-nums` on .table .num predates this and could
   not work: the system stack had no `tnum` to switch on, so three money columns
   a reader is meant to compare were set in proportional figures and the digits
   did not line up between rows. Geist ships `tnum`, so that rule is doing its
   job for the first time. Stated here as a comment rather than a new property
   because the property is already written where it belongs. */

a { color: var(--brand-dark); }

/* Nothing here suppressed the browser's own focus ring, so this is not a repair.
   It is that Safari draws a blue one, and a blue ring on a blue button is a
   focus indicator you cannot see. Stated once, so every control gets it. */
:focus-visible {
	outline: 3px solid var(--brand-dark);
	outline-offset: 2px;
	border-radius: 4px;
}
.btn:focus-visible { outline-color: var(--ink); }

/* Only .logo img and .shot img were constrained, so the first image added
   outside those classes would have overflowed on a phone. */
img, video { max-width: 100%; height: auto; }

/* Off screen until focused, then the first thing tabbed to on every page.
   <main> already exists on all of them, so this is the whole cost. */
.skip {
	position: absolute;
	left: -9999px;
	top: 0;
	background: var(--paper);
	color: var(--ink);
	border: 1px solid var(--ink-faint);
	border-radius: 0 0 8px 0;
	padding: 12px 18px;
	z-index: 100;
}
.skip:focus { left: 0; }

.wrap {
	max-width: 1080px;
	margin: 0 auto;
	padding: 0 24px;
	/* A grid or flex item refuses to shrink past its own min-content width
	   unless told to, which is how one long word or one unbounded track inside
	   a shell drags the whole document wider than the viewport. Stated on every
	   container that holds a track: the shell, the two bars, and the navs. */
	min-width: 0;
}

/* ---- header ---------------------------------------------------------- */

/* The bar is tinted to the top of the hero rather than left white.
 *
 * Every page opens with `section.hero`, and that section now starts in
 * --brand-wash. A white bar above it drew a hard seam across the top of all
 * twelve pages, which is the one place a seam is most visible. Matching the
 * wash at 0.85 means the header and the first 60px of the page are the same
 * colour at rest, and the bar only separates itself once there is content
 * moving underneath it, which is when a sticky header should appear.
 *
 * The alpha is doing the work with the blur behind it: fully opaque and the bar
 * is a slab, fully transparent and the nav sits on top of the headline.
 */
header {
	border-bottom: 1px solid var(--line);
	background: rgba(251, 240, 232, 0.85);
	backdrop-filter: saturate(180%) blur(10px);
	position: sticky;
	top: 0;
	z-index: 10;
}

.bar {
	display: flex;
	align-items: center;
	gap: 28px;
	height: 66px;
	min-width: 0;
}

/* The lockup, as a file rather than as type.
 *
 * It was set in the system font with a coloured second syllable, which is a
 * stand-in and reads as one: the letterforms changed between macOS and Windows,
 * and it did not match the mark anywhere else. The real thing is outlined Inter
 * Display, so it needs no font and cannot reflow.
 *
 * Sized by height. The intrinsic width follows the aspect ratio, so nothing has
 * to be kept in sync with the file, and the brand's 110px minimum for this
 * lockup is comfortably cleared at 30px tall (about 127px wide).
 */
.logo {
	display: inline-flex;
	align-items: center;
	margin-right: auto;
	text-decoration: none;
	/* Focus lands on the link, and the link is now a picture: without this the
	   ring hugs the baseline of an inline box instead of the artwork. */
	line-height: 0;
}
.logo img {
	height: 30px;
	width: auto;
	display: block;
}

/* The padding is the point. These were 24.75px tall, clearing WCAG 2.2's 24px
   target minimum by a quarter of a pixel and only by an accident of inherited
   line-height: any change to the font stack would have failed it silently. */
nav a {
	color: var(--ink-soft);
	text-decoration: none;
	font-size: 15px;
	display: inline-block;
	padding: 10px 4px;
	transition: color var(--motion) var(--ease);
}
nav a:hover, nav a[aria-current="page"] { color: var(--ink); }

/* The only nav item that is an action rather than a destination, and it was
   reaching a visitor as the sixth of six identical links. It is also the only
   call to action on the pages that have none of their own, /security and
   /privacy, where somebody who has finished reading otherwise has nowhere to go.

   Outlined rather than filled. The hero's primary button is filled and points at
   this same place, and two solid buttons competing in one view is the thing the
   design language calls a bug. --ink-faint for the border for the same reason
   .btn.ghost uses it rather than --line: a 1.23:1 edge is not a boundary.

 * Two further rules about it, and both are about where it goes rather than how
 * it looks.
 *
 * **It goes to /contact, not to /contact#demo.** The header is navigation, and
 * a fragment aimed at a radio row inside the form landed a visitor 678px down
 * the contact page: past the headline, past "if your practice is not a fit we
 * will say so on the call", past "three boxes to fill in", with the submit
 * button the most prominent thing on screen. A contextual CTA at the foot of
 * /pricing may deep-link, because the reader has already decided what they
 * want and the fragment picks the right intent. A nav item has no such context
 * and should open the page at the top.
 *
 * **It is not a button on the two pages that are the contact flow.** On
 * /contact it pointed at the page it was on, and on /contact/thanks it invited
 * somebody to book the demo they had just requested. Both keep the nav item, so
 * the header does not change shape as you move around, and drop `cta` so it
 * stops reading as something to press.
 *
 * The vertical padding stays at the other links' 10px so the row keeps one
 * baseline and the bar does not grow; only the horizontal padding changes. */
nav a.cta {
	color: var(--ink);
	border: 1px solid var(--ink-faint);
	/* A pill, to match .btn. These two are the same offer in two places and were
	   drawn with two different corner radii. */
	border-radius: 999px;
	padding: 10px 16px;
	font-weight: 600;
	transition: background var(--motion) var(--ease),
	            border-color var(--motion) var(--ease),
	            color var(--motion) var(--ease);
}
nav a.cta:hover { background: var(--paper-soft); border-color: var(--ink); }

nav {
	display: flex;
	gap: 24px;
	align-items: center;
	flex-wrap: wrap;
	min-width: 0;
}

/* ---- type ------------------------------------------------------------ */

/* The headings got bigger and tighter, and both are the same change: they are
 * now set in a face this stylesheet chose rather than in whatever the operating
 * system supplied.
 *
 * Tracking is the part that could not be done before. -0.035em on a 60px line is
 * right in Geist and would have been wrong in Roboto, so the old values had to be
 * the compromise that survived three faces. A variable weight is the other half:
 * 680 is a real instance here, where the old stack was rounding 660 to whichever
 * of Regular and Bold it happened to have.
 */
h1 {
	font-size: clamp(36px, 5.6vw, 60px);
	line-height: 1.05;
	letter-spacing: -0.035em;
	font-weight: 680;
	margin: 0 0 20px;
}

h2 {
	font-size: clamp(26px, 3.4vw, 36px);
	line-height: 1.15;
	letter-spacing: -0.028em;
	font-weight: 660;
	margin: 0 0 16px;
}

/* 20px against 18px body. It was 19 against 17, which is the same ratio, and at
   the new body size 19 would have been close enough to read as an accident. */
h3 {
	font-size: 20px;
	line-height: 1.3;
	letter-spacing: -0.015em;
	font-weight: 640;
	margin: 0 0 8px;
}

p { margin: 0 0 18px; max-width: var(--measure); }

.lead {
	font-size: clamp(18px, 2.1vw, 21px);
	line-height: 1.55;
	color: var(--ink-soft);
}

.eyebrow {
	text-transform: uppercase;
	letter-spacing: 0.09em;
	font-size: 12.5px;
	font-weight: 640;
	/* --brand-dark and not --brand: 5.3:1 against --paper-soft rather than 3.4:1,
	   and this is small uppercase text, which is the hardest thing on the page to
	   read already. */
	color: var(--brand-dark);
	margin: 0 0 14px;
}

/* ---- layout ---------------------------------------------------------- */

/* Three weights, not one.
 *
 * Every section on this site was 76px top and bottom, which is not a rhythm, it
 * is an absence of one: six blocks of identical height reading as a document
 * rather than as a page, where nothing is more important than anything else
 * because everything is the same size. `.tight` has been in this file unused
 * since it was written, which is its own evidence that the rhythm was never
 * actually applied.
 *
 * `.tight` is for a section that is an aside: text only, no cards, read by
 * somebody who is already convinced or already leaving. `.invert` is for the
 * one section a page is closing on, and it gets the most air of the three
 * because it is the last thing on the page and has to feel like an arrival.
 */
section { padding: 76px 0; }
section.tight { padding: 52px 0; }
section.soft { background: var(--paper-soft); border-block: 1px solid var(--line); }

/* ---- the inverted close ---------------------------------------------- */
/*
 * One full-bleed colour block per page, at the end, and nowhere else.
 *
 * Sections here are already full width with `.wrap` holding the content in, so
 * this needs no special layout: it is a background and a set of colours. What it
 * buys is the thing six identical sections cannot, which is a shape. The page
 * now opens on a tint, runs through white and grey, and lands on a solid block,
 * so a reader scrolling it fast can tell where they are without reading a word.
 *
 * Everything below is an override of a palette token, and each one is here
 * because the token it replaces resolves to something invisible on this
 * background: --ink is near-black, --brand-dark is a blue three shades from the
 * one behind it, --line is a hairline meant for white. None of that is wrong
 * anywhere else on the site, and all of it is wrong inside this block.
 *
 * The radial is the same device as the hero's, at the opposite corner, so the
 * block has a light source rather than being a flat rectangle of one colour.
 */
section.invert {
	background:
		radial-gradient(900px 460px at 88% -30%, rgba(255, 255, 255, 0.13), transparent 60%),
		var(--invert-bg);
	color: var(--invert-ink);
	padding: 96px 0;
}

section.invert h2 { color: var(--invert-ink); }
section.invert p,
section.invert .lead { color: var(--invert-ink-soft); }
/* 5.6:1 on this background, and it is 12.5px uppercase, which is the hardest
   thing on any page here to read. Not a mid blue for the same reason. */
section.invert .eyebrow { color: var(--invert-ink-soft); }

/* Reversed: the filled button is white here, because a --brand-dark fill on
   --invert-bg is one blue on another and stops being a button. White on this
   background is 7.6:1 the other way round, so the text is --invert-bg. */
section.invert .btn {
	background: var(--invert-ink);
	color: var(--invert-bg);
}
section.invert .btn:hover { background: var(--brand-wash); }

/* 0.6 alpha over --invert-bg is 3.4:1, which clears SC 1.4.11 for a control's
   own boundary. --ink-faint, which this rule replaces, is 1.4:1 here. */
section.invert .btn.ghost {
	background: transparent;
	color: var(--invert-ink);
	border-color: rgba(255, 255, 255, 0.6);
}
section.invert .btn.ghost:hover {
	background: rgba(255, 255, 255, 0.12);
	border-color: var(--invert-ink);
}

/* Underlined, because colour alone cannot mark a link that is the same white as
   the sentence around it. Everywhere else on the site a link is distinguished by
   --brand-dark against --ink and does not need the rule. */
section.invert a { color: var(--invert-ink); }

/* Both focus rules resolve to something invisible in here: the global ring is
   --brand-dark and .btn's is --ink. Specificity (0,2,1) beats .btn:focus-visible
   at (0,2,0), so this covers buttons and plain links in one line. */
section.invert :focus-visible { outline-color: var(--invert-ink); }

section.invert .signature p { color: var(--invert-ink-soft); }
section.invert .signature-name { color: var(--invert-ink); }
section.invert .signature img { border-color: rgba(255, 255, 255, 0.28); }

/* Spacing, as three classes rather than as `style="margin-top:30px"`.
 *
 * There were twenty-four of those across the site and **not one of them was
 * doing anything in production.** The Content Security Policy says
 * `style-src 'self'`, which blocks a style attribute exactly as it blocks an
 * inline <style>, so every one was applied locally, photographed for a
 * screenshot, and then dropped by the browser on the live site. Nothing looked
 * broken enough to investigate; the spacing was just quietly wrong on every
 * page, for everybody, and right on the machine of anybody checking.
 *
 * Six different values collapsed to three on the way in. They were 22, 26, 30,
 * 32, 36 and 44px, which is not a scale, it is six people making the same
 * decision separately. */
.gap-sm { margin-top: 24px; }
.gap-md { margin-top: 32px; }
.gap-lg { margin-top: 44px; }

/* The way-out list on /404. Loose, because every line is a destination and the
   page's whole job is to make one of them easy to hit. */
.link-list { line-height: 2.1; padding-left: 1.1em; }

/* The hero, on a tint rather than on nothing.
 *
 * Every page on this site opens with `section.hero`, so this is the site's top
 * edge rather than the home page's. It was flat ground, which meant the first
 * thing a visitor met was a black video poster and a blue button floating on
 * white, and the page had no opening at all: the first visible boundary was the
 * `.soft` section 700px down.
 *
 * Two layers, and the order matters. The linear gradient is the band: wash at
 * the top falling to the page's own background by 92%, so the hero *ends* in
 * --paper and needs no bottom border to stop. The radial sits off the top-left
 * corner, which is where the headline is, and gives the band somewhere to be
 * brighter than the rest of itself. One flat colour would have read as a
 * coloured box; this reads as the page lightening under the words.
 *
 * No border-bottom, deliberately. `.soft` carries `border-block`, and on the
 * home page it is the section directly below this one, so a border here would
 * draw two lines a pixel apart.
 *
 * Both gradients are in the tint family rather than the brand family. Terracotta
 * at any alpha over the ground lands on a colour that competes with the primary
 * button, and the one thing the top of this page must not do is give the eye two
 * things of equal weight to look at.
 *
 * --brand-tint was #f7e4d6 until the hero was measured: the radial is warmest
 * exactly where the headline and its links sit, and a link on it came out
 * 4.24:1. Lightening the tint and darkening --brand-dark together took it to
 * 5.2:1. Do not warm this token without re-checking the links in the hero.
 */
.hero {
	padding: 88px 0 72px;
	background:
		radial-gradient(1100px 520px at 8% -20%, var(--brand-tint), transparent 62%),
		linear-gradient(180deg, var(--brand-wash), var(--paper) 92%);
}

/* An <ol>, because four numbered things in sequence are a list, and a screen
   reader should say "list of 4 items" rather than read a stray "1" out of a
   <span>. min() so it stops overflowing below a 308px viewport, which is a
   folded Galaxy Fold rather than a mainstream phone, but costs one word. */
.grid {
	display: grid;
	gap: 30px;
	grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
	margin-top: 40px;
	list-style: none;
	padding: 0;
}

/* A card that rests on the page rather than being drawn on it.
 *
 * This was a 1px hairline and nothing else, which is a table cell with rounded
 * corners. The contact shadow is what makes it an object, and the hover is what
 * makes the whole card read as the link it already contains: every one of these
 * ends in a "how X works" link, and until now the only thing that responded to a
 * pointer was the eight words of the link itself.
 *
 * The lift is 2px. It was 4 first, which at three cards abreast makes the row
 * bounce as the pointer crosses it. The border darkening to --ink-faint is
 * carrying more of the effect than the transform is, and it is the half that
 * survives `prefers-reduced-motion`.
 */
.card {
	border: 1px solid var(--line);
	border-radius: var(--radius-lg);
	padding: 26px;
	background: var(--paper);
	box-shadow: var(--shadow-sm);
	transition: box-shadow var(--motion) var(--ease),
	            border-color var(--motion) var(--ease),
	            transform var(--motion) var(--ease);
}

/* `:has()` rather than a hover on the card itself, so a card with no link in it
   does not offer a state it cannot honour. Every card on the site has one
   today; this is what keeps that from being an assumption. */
.card:has(a):hover {
	border-color: var(--ink-faint);
	box-shadow: var(--shadow-md);
	transform: translateY(-2px);
}

/* Two by two, for the grids that hold four.
 *
 * `auto-fit` with a 260px floor fits three across the 1032px column and drops
 * the fourth onto a row of its own. Four parallel items rendered as three and
 * an orphan reads as a mistake in the layout rather than as a list, and the
 * lone card is the one a reader skips.
 *
 * Two columns instead, which also gives each card about 500px rather than 320
 * and takes these 21-to-28-word paragraphs from six lines to three. Applied by
 * a modifier and not to `.grid` itself, because the third grid on the site
 * holds two cards and `auto-fit` is already right for it.
 */
@media (min-width: 620px) {
	.grid.pairs { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.card p:last-child { margin-bottom: 0; }

/* The "read more" link at the foot of a card, which is a paragraph containing
   nothing but a link.
 *
 * WCAG 2.2's target minimum exempts a link sitting inside a sentence, because
 * its height is set by the line-height of the text around it and shrinking the
 * target is not the author's doing. This is not that: it is a standalone
 * destination that happened to inherit a 20px line box, which fails 2.5.8 at
 * 24px with nothing to appeal to. The padding is the whole fix. */
.card > p:last-child > a:only-child {
	display: inline-block;
	padding-block: 4px;
}

/* Scoped to ol.grid, not .grid, so a grid of cards that is not a numbered
   sequence, the pricing tiers, gets no numbers. The number is drawn by CSS
   rather than typed into the markup, so it stays the decoration it always was
   and cannot be read out as content. */
ol.grid { counter-reset: card; }
ol.grid > .card { counter-increment: card; }
ol.grid > .card::before {
	content: counter(card);
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 28px; height: 28px;
	border-radius: 8px;
	background: var(--brand-wash);
	color: var(--brand-dark);
	font-size: 14px;
	font-weight: 660;
	margin-bottom: 14px;
}

/* ---- buttons --------------------------------------------------------- */

/* --brand-dark, not --brand: white on --brand is 3.5:1, which fails AA for the
   one element on every page we most want pressed. --brand-dark is 6.1:1. */
/* A pill, and slightly larger than it was.
 *
 * 999px rather than 10px is the single most dated-to-current change on this
 * page, and it costs one value. 17px rather than 16 because the body around it
 * went to 18 and a call to action smaller than the paragraph above it reads as a
 * footnote.
 *
 * The shadow is --shadow-sm and not --shadow-md: this is the only filled thing
 * on most pages and it should sit *on* the page, not hover above it. The lift on
 * hover is 1px, half what the cards get, because a button is small enough that
 * 2px reads as the whole thing jumping.
 */
.btn {
	display: inline-block;
	background: var(--brand-dark);
	color: #fff;
	text-decoration: none;
	font-weight: 600;
	font-size: 17px;
	padding: 14px 26px;
	border-radius: 999px;
	border: 1px solid transparent;
	box-shadow: var(--shadow-sm);
	transition: background var(--motion) var(--ease),
	            box-shadow var(--motion) var(--ease),
	            transform var(--motion) var(--ease);
}
.btn:hover {
	background: var(--brand-deep);
	box-shadow: var(--shadow-md);
	transform: translateY(-1px);
}
/* Pressed goes back down to the resting position rather than below it. A button
   that sinks past where it started reads as broken on a trackpad, where the
   press and the release are ten milliseconds apart. */
.btn:active { transform: none; box-shadow: var(--shadow-sm); }

/* --ink-faint, not --line: a tan hairline is not a visible boundary, and
   SC 1.4.11 asks 3:1 of a control's own outline. --ink-faint is 5.3:1. */
.btn.ghost {
	background: transparent;
	color: var(--ink);
	border-color: var(--ink-faint);
}
.btn.ghost:hover { background: var(--paper-soft); }

.actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 28px; }

/* The hero's outcome line: what changes about a working day.
 *
 * Full ink rather than --ink-soft, and it is the only paragraph in the hero
 * that gets it. The mechanism sentence above it is what the software does; this
 * is what it is for, and a practice owner recognises themselves in one of these
 * three clauses or in none of them. It sits above the buttons because somebody
 * deciding in ninety seconds reads a headline, one sentence, and a button.
 */
.outcome {
	font-size: clamp(17px, 1.9vw, 19px);
	line-height: 1.5;
	font-weight: 550;
	color: var(--ink);
}

/* The category line. Quiet on purpose: it answers "what kind of thing is this
 * and is it for me", which is a filing question rather than an argument, and it
 * was competing with the outcome above it at .lead size. */
.audience {
	font-size: 15.5px;
	line-height: 1.6;
	color: var(--ink-soft);
}

/* The hero's reversible-adoption proof, under the two buttons.
 *
 * Deliberately quieter than .lead and deliberately not four more buttons. It is
 * the answer to "what happens if this goes wrong", which is a question a
 * visitor asks after the pitch rather than instead of it, so it must not
 * compete with the demo CTA for the same glance.
 *
 * Four items rather than one sentence. As prose this was 34 words carrying four
 * separate commercial facts, and a reader skimming took away a general
 * impression of reassurance rather than four things they could go and check.
 *
 * Each one keeps its link. Every claim here is a page of this site in a phrase,
 * and the link is what makes it evidence rather than a promise: an unlinked
 * version of this block is marketing. They stay ordinary links, because styling
 * them as buttons would give the hero six calls to action and therefore none.
 */
.proof-list {
	list-style: none;
	padding: 0;
	margin: 24px 0 0;
	display: grid;
	gap: 10px;
	font-size: 15px;
	line-height: 1.5;
	color: var(--ink-soft);
}

/* Two columns once there is room for two, which on the home page means once the
   film has moved beside the text rather than under it. */
@media (min-width: 900px) {
	.proof-list { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 24px; }
}

.proof-list li {
	padding-left: 20px;
	position: relative;
}

/* A marker rather than a list bullet, so it can carry the brand colour without
   also carrying the browser's list indentation. Decorative: the <li> is what a
   screen reader counts, and this adds nothing for it to say. */
.proof-list li::before {
	content: "";
	position: absolute;
	left: 2px;
	top: 8px;
	width: 7px;
	height: 7px;
	border-radius: 50%;
	background: var(--brand);
}

.proof-list a { font-weight: 550; }

/* ---- the walkthrough ------------------------------------------------- */

/* The deepest shadow on the site, and the screenshots are the right thing to
   spend it on: they are the evidence, and the whole argument of this site is
   that they are the running product rather than a composition. */
.shot {
	border: 1px solid var(--line);
	border-radius: var(--radius-xl);
	overflow: hidden;
	background: var(--paper);
	box-shadow: var(--shadow-lg);
}
.shot img { display: block; width: 100%; height: auto; }

/* The screenshot as a link to its own full-size file.
 *
 * `script-src 'none'` rules out a lightbox, and it was never wanted: the
 * useful behavior is the browser's own, where the image opens in a tab that
 * can be zoomed, saved and sent to somebody else. So it is an anchor around
 * the <picture>, pointing at the largest candidate in that picture's own
 * srcset rather than at a path assembled by hand.
 *
 * `display: block`, and not `display: contents`, which was the first attempt
 * and was wrong. Contents removes the anchor's box altogether: it measured
 * 0x0, `offsetParent` was null, and calling .focus() on it left activeElement
 * on <body>. A link a keyboard cannot reach is not a link. axe passed the page
 * in both states, so this was caught by asking the element rather than the
 * scanner.
 *
 * Block costs nothing here, because the image inside is already
 * `display: block; width: 100%`. The papers rules below select the flex child
 * rather than the <picture>, for the same reason.
 *
 * The accessible name is the alt text plus "Open at full size", because the
 * alt describes the picture and gives no hint that the thing is clickable. */
.shot-link { display: block; }
.shot-link:focus-visible {
	outline: 2px solid var(--brand);
	outline-offset: 3px;
}

/* A screenshot standing on its own, on /product, rather than beside the text of
   a step. The caption says what the user got out of the screen, not what is on
   it: the alt text already describes the picture, and repeating that in a
   caption makes a screen reader say it twice. */
/* Held to 820px rather than the 1080px shell.
 *
 * A screenshot allowed the full column width is about 1032px of application
 * dropped into a page whose prose is 68 characters wide, and it stops being an
 * illustration and becomes the thing you scroll past. At 820 it sits with the
 * text instead of interrupting it, and every source is 2080px or wider, so it
 * is still over 2x density on a retina screen. */
.shot-figure { margin: 34px 0 0; max-width: 820px; }

/* Width is the constraint again, and this time every screenshot can obey it.
 *
 * The height cap that briefly lived here was a workaround for one picture being
 * the wrong shape. `encounter-main.png` is the `main` element, 1:1.18, so at
 * 820 wide it drew 964px tall, taller than the screen it was photographed on.
 * Capping the height fixed that and broke something else: each screenshot has a
 * different aspect, so a shared height gave them all different widths, and the
 * first three on /product came out 528, 818 and 696 wide down one column.
 *
 * The picture was the problem, so the picture changed. /product now uses the
 * 1280x900 viewport capture, 1:0.70, and every desktop screenshot on the page
 * is landscape and within a hand's breadth of the others: 0.56, 0.70, 0.73,
 * 0.80, 0.89. One width, and the tallest of them is 730px.
 *
 * `fit-content` on the frame so the border hugs the picture rather than leaving
 * a strip of background beside it, which is what happens on the phone shot,
 * whose own width rule is narrower than this column. */
.shot-figure .shot { width: fit-content; max-width: 100%; margin-inline: auto; }
.shot-figure .shot img { width: 100%; height: auto; }
.shot-figure figcaption {
	margin-top: 12px;
	font-size: 15px;
	color: var(--ink-faint);
	max-width: var(--measure);
}

/* A phone screenshot, drawn at phone width.
 *
 * The other two shots are desktop panels, so filling the column is right for
 * them. This one is a portrait capture, and stretched to the same 585px it drew
 * a 1265px tall image beside neighbours of 270 and 426: the row was three times
 * the height of the first, and it read as a mistake rather than as a phone.
 * Centered in the column it keeps its own proportions.
 *
 * Width is the only bound it needs, now that the picture is the right shape.
 *
 * This briefly had a height cap, which was treating the symptom. The capture
 * was 1242x3050, or 1:2.46, because the crop in `make-webp.py` had been sized
 * to fit one more row of content in; no phone is that shape, so the picture
 * read as a long strip and shrinking it only made it an unreadable long strip.
 * It is now cropped to one screen of the phone it was taken on, 1:2.17, so it
 * can be drawn at a phone's width and simply looks like a phone.
 *
 * 320px is that width. It is taller than the two desktop panels beside it
 * because a phone is taller than a panel, which is the one proportion a reader
 * has no trouble with. */
.shot.phone {
	max-width: var(--phone-width);
	margin-inline: auto;
}

/* The owner journey on /product: six phone screens in the order they happen.
 *
 * An <ol>, because it is a sequence and a screen reader should say so. Three
 * across on a desktop rather than six: six across inside this column is 155px
 * per screen, at which point the thing being photographed is illegible and the
 * row is decoration rather than evidence.
 *
 * `min(280px, 100%)` for the same reason the .grid above uses it: without it a
 * track refuses to shrink below its own content and drags the document wider
 * than a narrow phone.
 */
.journey {
	list-style: none;
	padding: 0;
	margin: 36px 0 0;
	display: grid;
	gap: 32px 24px;
	grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
}

.journey figure { margin: 0; }

.journey img {
	display: block;
	width: 100%;
	height: auto;
	border: 1px solid var(--line);
	border-radius: var(--radius);
}

.journey figcaption {
	margin-top: 12px;
	font-size: 15px;
	line-height: 1.5;
	color: var(--ink-soft);
}

.journey b {
	display: block;
	color: var(--ink);
	font-weight: 620;
}

/* Also an <ol>: three screens in the order they happen during a visit. No
   visible numbers here, the .tag labels do that job, so this is purely about
   what a screen reader announces. */
.steps { margin-top: 40px; display: grid; gap: 44px; list-style: none; padding: 0; }

/* Text in the narrow track, picture in the wide one, on whichever side each
 * happens to sit.
 *
 * The tracks are 5fr and 7fr because a screenshot needs the room and a paragraph
 * does not. The alternating rule below then moves the text to the second
 * position on every even step, to give the list a zig-zag rather than six
 * identical rows. Those two were written separately and only the first was true:
 * `order` moves the *content* between tracks and leaves the track sizes where
 * they are, so on every even step the text took the 7fr column and the
 * screenshot was drawn in the 5fr one.
 *
 * It rendered at 417px against 585px for the step directly above it, on the two
 * pictures the page most wants compared: the charge panel under the approved
 * ceiling, and the same panel once it goes over. They are the same screen
 * photographed twice and they were shown at two different sizes.
 *
 * So the tracks reverse with the content. The picture is always in the 7fr
 * track, the text always in the 5fr one, and the side they each sit on still
 * alternates.
 */
.step {
	display: grid;
	gap: 26px;
	align-items: center;
	grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
}
.step:nth-child(even) {
	grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
}
.step:nth-child(even) .step-text { order: 2; }

.step-text .tag {
	display: inline-block;
	font-size: 12.5px;
	font-weight: 640;
	letter-spacing: 0.05em;
	text-transform: uppercase;
	color: var(--ink-faint);
	margin-bottom: 10px;
}

/* A section whose picture sits beside its prose rather than under it.
 *
 * For the one screenshot on /product that is a phone. The desktop captures are
 * 1280px of application and earn the full column; a phone is 320px, and centered
 * under the text it left two thirds of the row empty while pushing everything
 * below it down past a tall picture.
 *
 * The second track is `--phone-width`, stated, and not `auto`. `auto` sizes to
 * the content's max-content width, and the widest thing in that column is not
 * the 318px picture but its caption, which inherits the 68ch measure every
 * paragraph on the site gets. So the figure column came out **642px** and the
 * prose was left 334px at 1280, and 154px at 900, which is four or five words a
 * line. The picture looked right and the column it lived in was twice its size.
 *
 * `align-items: start` rather than `center`: the phone is taller than the
 * paragraphs, and centering it dropped the eyebrow and heading into the middle
 * of the row with white space above them. The two columns should start on the
 * same line, which is what a reader expects of a heading.
 *
 * One column below 880px, which is where the prose would otherwise be squeezed
 * under 400px. The figure follows the text there, which is the order the source
 * is already in, so nothing needs reordering.
 */
.split { display: grid; gap: 40px; align-items: start; }

@media (min-width: 880px) {
	.split { grid-template-columns: minmax(0, 1fr) var(--phone-width); gap: 56px; }
	/* The figure's own top margin is for when it sits under a paragraph. Beside
	   one it would push the phone below the heading it is level with. */
	.split .shot-figure { margin-top: 0; max-width: var(--phone-width); }
	/* And the caption belongs to the picture here, not to the page measure. */
	.split .shot-figure figcaption { max-width: 100%; }
}

/* The founder's face beside their name, on /about.
 *
 * A byline rather than a portrait: the block below it is six paragraphs of
 * prose, so a picture in its own column would leave most of that column empty
 * and a large photograph would make the page about the person rather than about
 * what they are accountable for. 120px is the size a name is introduced at.
 *
 * Round, which is what marks an image as a person rather than a screenshot;
 * every other picture on this site is a rectangle with a 12px radius, and that
 * difference is doing the work here.
 *
 * The heading keeps its own bottom margin for the paragraph that follows, so
 * the row is aligned on the centre of the two rather than on their boxes.
 */
.byline {
	display: flex;
	align-items: center;
	gap: 20px;
	margin-bottom: 14px;
}

.byline img {
	width: 120px;
	height: 120px;
	border-radius: 50%;
	object-fit: cover;
	/* The same hairline every framed picture on the site carries, so a face on a
	   near-white background still has an edge. */
	border: 1px solid var(--line);
	flex: none;
}

.byline h2 { margin: 0; }

@media (max-width: 480px) {
	/* Under the name rather than beside it: at 390px the two together leave the
	   heading about 200px, which breaks "Moe Huzaifa" across two lines next to a
	   picture that is taller than both. */
	.byline { flex-direction: column; align-items: flex-start; gap: 14px; }
}

/* ---- the divider ----------------------------------------------------- */
/*
 * Paw prints walking across the seam between two sections. One instance, on the
 * home page, as a prototype: if this earns its place it becomes a small set, and
 * if it does not it comes out in one commit.
 *
 * It sits below the seam rather than straddling it, and the first attempt is
 * worth recording because the reasoning was fine and the result was not. The
 * idea was to lift the divider onto the hairline `.soft` draws and give it a
 * --paper background, so the line appeared to break and let the paws stand in
 * the gap. That works where a rule crosses one background. This seam is the join
 * between two different ones: --paper-soft above, --paper below. Half the
 * divider's box therefore sat on the darker band as a plainly visible rectangle
 * poking up into it.
 *
 * There is no background to match at a seam that is itself a change of
 * background, so the cut is gone and with it the need for one. The hairline
 * stays whole and the paws sit under it.
 */
.divider {
	width: fit-content;
	margin: 0 auto;
	padding-top: 44px;
	line-height: 0;
}

/* Quiet, but not so quiet it is a smudge. At full --brand this is five bright
   blue marks with nothing to say, sitting between two headings that do have
   something to say; at the 0.3 it started on, printed at 18px, it read as dust
   on the screen rather than as anything drawn on purpose. An ornament nobody
   can quite make out is worse than no ornament, because it still costs the
   markup.

   The opacity is on the container so the animation below can own `opacity` on
   each paw without the two fighting over one property. */
.motif {
	display: block;
	color: var(--brand);
	opacity: 0.55;
}

/* The hearts, in the mark's own amber rather than in the terracotta everything
   else is drawn in. Two warm colours in one ornament is what stops a row of five
   shapes reading as a stamp repeated five times.
 *
 * `color` and not `fill`, because these are `<use>` elements and the shape they
 * clone lives in a shadow tree: `fill` set out here would not reach it, and
 * `currentColor` inside the shape resolves against the inherited colour, which
 * does cross. The same mechanism the whole sprite depends on.
 *
 * Amber is 2.8:1 on --paper, which is under the 3:1 an interface element needs.
 * That is allowed here and nowhere else: these are `aria-hidden` ornaments that
 * carry no meaning, and if they vanished entirely no reader would lose anything.
 * Do not reach for --accent for anything a person has to see. */
.motif .warm { color: var(--accent); }

/* The resting state is *visible*, which is the part that matters.
 *
 * Everything below only adds motion to a divider that is already drawn. A
 * browser with no scroll-driven animation, a browser with no CSS animation at
 * all, and a reader who has asked for reduced motion all get five paw prints,
 * which is the whole of what this element is for.
 */
.motif use { opacity: 1; }

/* The shape library, which is markup rather than a picture: a <defs> block and
   nothing that draws. It is taken out of the flow rather than given
   `display: none`, because a zero-sized svg is the arrangement `<use>` is
   reliably specified against. */
.sprite {
	position: absolute;
	width: 0;
	height: 0;
	overflow: hidden;
}

/* The second divider, walking back the other way.
 *
 * A flip rather than a second set of coordinates, so there is one arrangement of
 * paws on the site and not two that have to be kept looking related. It also
 * reverses the animation for free: `nth-of-type(1)` is still the paw that fades
 * first and is now the one on the right, so the walk runs right to left.
 *
 * Safe to put `transform` here, unlike on the `<use>` elements below, where a
 * CSS transform would replace the `transform` attribute doing the positioning
 * and stack all five paws on top of each other at the origin. */
.motif.flip { transform: scaleX(-1); }

@keyframes motif-in {
	from { opacity: 0; }
	to   { opacity: 1; }
}

/*
 * Driven by scroll position, not by a clock, and that is not decoration for its
 * own sake: this divider is about 1,700px down the home page. A time-based
 * animation would start on load, finish long before anybody scrolled to it, and
 * be a thing that runs for nobody. This site already has a history of shipping
 * rules that were correct, applied, and invisible in production; a paw animation
 * that has always finished by the time it is on screen would be another.
 *
 * The stagger is in `animation-range` rather than in `animation-delay`, because
 * a delay is a duration and there are no durations on a view timeline. Each paw
 * takes a later slice of the element's entry, so they land left to right as it
 * comes up the screen, like something walked past.
 *
 * `700ms` is dead on a view timeline, where progress is scroll position. It is
 * there for the fallback: a browser that does not know `animation-timeline`
 * ignores that line, keeps the rest, and fades all five in together on load.
 * That is a worse animation and a perfectly good divider.
 *
 * Chromium and Safari drive this from scroll today; Firefox does not yet, and
 * takes the fallback above. Nothing needs changing here when it does.
 */
@media (prefers-reduced-motion: no-preference) {
	.motif use {
		animation: motif-in 700ms var(--ease) both;
		animation-timeline: view();
	}
	/* `nth-of-type`, not `nth-child`: <defs> is the svg's first element child and
	   would take the 1 out of every index. */
	.motif use:nth-of-type(1) { animation-range: entry 0% entry 30%; }
	.motif use:nth-of-type(2) { animation-range: entry 15% entry 45%; }
	.motif use:nth-of-type(3) { animation-range: entry 30% entry 60%; }
	.motif use:nth-of-type(4) { animation-range: entry 45% entry 75%; }
	.motif use:nth-of-type(5) { animation-range: entry 60% entry 90%; }
}

/* ---- the signature --------------------------------------------------- */
/*
 * The founder's face against the two places that promise him by name.
 *
 * /contact opens "Book thirty minutes with the person who built Vetmira" and the
 * home page's founding-practices block asks a practice to go first on a product
 * with no production customers. Both are personal asks, and both were made by a
 * nameless "we" with nothing to look at. This is the only photograph on the site
 * and the only warmth available to it that is also true: everything else here is
 * a screenshot of the running product, and it is going to stay that way.
 *
 * A signature, not a portrait, which is why it sits *after* the buttons rather
 * than between them and the text above. It signs an offer that has already been
 * made; it is not a step on the way to the offer, and putting 110px of face
 * between a paragraph and the only call to action on the section would be
 * exactly the mistake the hero's own comments spend forty lines undoing.
 *
 * 88px against /about's 120px. There the picture introduces a person the page is
 * about and six paragraphs follow it. Here it is a byline on somebody else's
 * section, and at 120 it started competing with the heading above it.
 *
 * Round, for the same reason the byline is: every other image on this site is a
 * rectangle with a radius, and the circle is what marks this one as a person.
 */
.signature {
	display: flex;
	align-items: center;
	gap: 18px;
	margin-top: 32px;
}

.signature img {
	width: 88px;
	height: 88px;
	border-radius: 50%;
	object-fit: cover;
	/* The hairline every framed picture on the site carries, so a face on a
	   near-white background still has an edge. */
	border: 1px solid var(--line);
	flex: none;
}

.signature p {
	margin: 0;
	font-size: 15.5px;
	line-height: 1.5;
	color: var(--ink-soft);
	max-width: 32rem;
}

.signature-name {
	display: block;
	color: var(--ink);
	font-weight: 620;
	font-size: 17px;
	letter-spacing: -0.01em;
}

@media (max-width: 480px) {
	/* Under the name rather than beside it, the same as `.byline`: at 390px the
	   two together leave the text about 230px and the role line breaks over
	   three. */
	.signature { flex-direction: column; align-items: flex-start; gap: 14px; }
}

/* ---- honest limits --------------------------------------------------- */

.limits {
	display: grid;
	gap: 0;
	border: 1px solid var(--line);
	border-radius: var(--radius-lg);
	overflow: hidden;
	margin-top: 32px;
	max-width: var(--measure);
}
.limit {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr);
	gap: 20px;
	padding: 18px 22px;
	border-bottom: 1px solid var(--line);
	font-size: 15.5px;
}
.limit:last-child { border-bottom: 0; }
.limit dt { font-weight: 600; margin: 0; }
.limit dd { margin: 0; color: var(--ink-soft); }

/* ---- footer ---------------------------------------------------------- */

footer {
	border-top: 1px solid var(--line);
	padding: 44px 0;
	color: var(--ink-faint);
	font-size: 15px;
}
footer .bar { height: auto; align-items: flex-start; gap: 40px; flex-wrap: wrap; }
footer a { color: var(--ink-soft); text-decoration: none; }
footer a:hover { color: var(--ink); }
/* The line under the lockup. Was a style attribute on all twelve pages, and so
   was ignored on all twelve: see the gap classes above. */
.footer-note { margin-top: 10px; }

/* One flowing row, using the width the footer already has.
 *
 * This was a four-row column grid, written when there were eight links and
 * correct for eight: two columns of four. The mailbox made it nine, and nine
 * into four-row columns is 4 + 4 + 1, so the ninth link stood alone in a third
 * column. The whole block was then 445px wide inside a 1032px footer, leaving
 * nearly 600px of nothing beside it: a list squeezed into a third of the space
 * available to it, next to a void.
 *
 * Flex wrap instead. The links flow left to right in the order they are
 * written, take the room that is there, and rewrap at any width without a
 * breakpoint. The reading order the old comment was protecting is now simply
 * the source order, which is what a wrapped row gives for free and what a
 * column-filled grid had to be configured to imitate.
 *
 * `flex: 1 1 320px` is doing two jobs and needs both numbers. The grow makes
 * it expand rather than shrink-to-fit beside the lockup, without which the row
 * keeps its content width and the footer looks exactly as it did. The 320px
 * basis is what stops it being crushed: with a plain `flex: 1` a 390px phone
 * left the lockup on the same line and gave the nav 69px, one link per row,
 * nine rows, and a footer 646px tall, which was worse than the grid it
 * replaced. Below the basis the whole nav wraps onto a line of its own and
 * gets the full width instead.
 */
footer nav {
	display: flex;
	flex-wrap: wrap;
	gap: 2px 30px;
	align-items: flex-start;
	flex: 1 1 320px;
	min-width: 0;
}

/* The header's 10px is there to hold a 24px touch target on a 15px link. Here
   the same padding, on twice as many links, is most of why the block was so
   tall. 6px still clears the minimum with room to spare and reads as a footer
   list rather than as a menu. */
footer nav a { padding: 6px 4px; }

/* The line under everything else. Separated by a rule rather than by space, so
   it reads as a colophon and not as a ninth link.

   The year is typed, because this site has no build step and no JavaScript and
   there is nowhere else for it to come from. It therefore has to be changed by
   hand each January, in eleven files: see site/README.md. A copyright notice
   does not need a year to be valid, so if that upkeep is not going to happen,
   deleting the year is better than carrying a stale one, which reads as an
   abandoned site to exactly the person we want reading it. */
.colophon {
	margin-top: 34px;
	padding-top: 20px;
	border-top: 1px solid var(--line);
	font-size: 14px;
	color: var(--ink-faint);
	/* Centered, which is where a colophon belongs and is also the only way this
	   line stops looking like a tenth link that fell off the list above it. */
	text-align: center;
}
.colophon p { margin: 0; max-width: none; }

/* ---- responsive ------------------------------------------------------ */

@media (max-width: 760px) {
	/* Both selectors, because `.step:nth-child(even)` is (0,2,0) and would
	   otherwise keep its two-track rule here and win against `.step` at (0,1,0). */
	.step,
	.step:nth-child(even) { grid-template-columns: 1fr; }
	.step:nth-child(even) .step-text { order: 0; }
	.limit { grid-template-columns: 1fr; gap: 4px; }
	nav { gap: 4px 16px; }
	section { padding: 56px 0; }
	.hero { padding: 60px 0 48px; }

	/* The header wraps rather than hiding a link. About is where "who it is not
	   for" lives, which does real qualifying work in this sale, and it used to
	   be reachable on a phone only from the footer. With Pricing added there is
	   no width for five links beside the lockup at any font size worth reading,
	   so the row gives way instead of the content.

	   And it stops sticking, because the wrapped header is about a third of a
	   phone screen tall. Pinning that to the top for the whole visit would be a
	   worse trade than the hidden link it replaces. */
	header { position: static; }
	.bar { height: auto; flex-wrap: wrap; gap: 10px 16px; padding-block: 12px; }
	.logo { margin-right: 0; }
	header nav { width: 100%; }

	/* The box comes off here. The nav is already its own full-width row on a
	   phone, and outlining one link inside a header that is a third of the
	   screen already only makes that row taller. The darker ink stays, which is
	   enough to mark it apart from five destinations at this size. */
	header nav a.cta {
		border: 0;
		padding: 10px 4px;
		font-weight: inherit;
	}

	/* The whole of the horizontal overflow on this site was here.
	 *
	 * `grid-auto-flow: column` with four fixed rows sizes each column to its own
	 * content and adds columns until the links run out. Nine links made three of
	 * them: 75px, 64px, and 155px for the mailbox address, plus two 44px gaps, so
	 * the footer asked for 383px inside a 327px content box on a 375px phone. The
	 * document went to 406px and every page scrolled sideways, including the ones
	 * with nothing wide on them.
	 *
	 * Two bounded columns instead. `minmax(0, 1fr)` is the part that matters:
	 * `1fr` alone is `minmax(auto, 1fr)`, which floors each column at its own
	 * min-content width and would put the address straight back through the edge.
	 *
	 * Filled across rather than down, which reverses the desktop arrangement and
	 * is right here: reading order follows the source either way, and two short
	 * columns beat one 400px ribbon on a screen this size. */
	footer nav {
		grid-auto-flow: row;
		grid-template-rows: none;
		grid-template-columns: repeat(2, minmax(0, 1fr));
		gap: 0 20px;
	}

	/* An email address has no space in it, so it has no break opportunity and no
	   amount of column bounding will make it fit a narrow phone on its own.
	   `anywhere` lets it break mid-word, and unlike `break-word` it also lets the
	   column's min-content width shrink, which is what the grid above is reading
	   when it decides how wide to be. */
	footer nav a[href^="mailto:"] { overflow-wrap: anywhere; }
}

/* ---- reduced motion -------------------------------------------------- */
/*
 * The other preference this site has to honour, and it arrived with the same
 * change that made it necessary: before the transitions above there was nothing
 * on any page that moved.
 *
 * Motion is removed, not slowed. `animation: none` and `transition: none` rather
 * than a 0.01ms duration: the millisecond trick exists to keep `animationend`
 * firing for scripts that wait on it, and this site runs no script at all.
 *
 * The transforms go too, which is the part a duration override would miss. A
 * card that jumps 2px the instant a pointer touches it is *more* of a vestibular
 * problem than one that eases there over 160ms, so `transform: none` has to be
 * stated for the hover states rather than left to the transition rule.
 *
 * Everything these states also do stays: the border darkens, the shadow deepens,
 * the button changes colour. Somebody who has asked for less motion still needs
 * to be able to tell what is interactive.
 */
@media (prefers-reduced-motion: reduce) {
	*, *::before, *::after {
		animation: none !important;
		transition: none !important;
		scroll-behavior: auto !important;
	}
	.card:has(a):hover,
	.btn:hover {
		transform: none;
	}
}

/* The viewer's own preference wins. A clinic looking at this at 7am with the
   lights off should not be flashed. */
/* ---- the walkthrough film -------------------------------------------- */

/* The hero, once the film moved into it.
 *
 * One column by default, so the source order is the rendered order on a phone:
 * heading, both paragraphs, the two calls to action, then the film directly
 * under them. That is the arrangement the page is written for, and the desktop
 * one is the exception rather than the base.
 *
 * 900px rather than the 760px used elsewhere: the breakpoint is not about
 * phones here, it is about whether a 16:10 video and 68 characters of prose can
 * sit beside each other without either becoming unreadable. Below that they
 * cannot.
 */
/* Three children, not two, and the third moves.
 *
 * On a phone the hero stacks in source order, and the proof list used to sit
 * inside the text block between the buttons and the film. Measured at 375x812:
 * the film began at 1,015px, which is 1.3 screens down, while the button that
 * says "Watch the 46-second visit" sat at 602px pointing at it. The strongest
 * thing this site owns was the one thing a phone could not see.
 *
 * So the list is a grid child of its own, after the film in the source. On a
 * phone that is the order you get: pitch, buttons, proof, then the four links
 * that answer "what happens if this goes wrong", which is a question asked
 * after the demo rather than instead of it.
 *
 * Above 900px the areas put it back exactly where it was, under the buttons in
 * the left column, because there the film is already beside the text and
 * nothing was ever in its way. `row-gap: 0` because `.proof-list` brings its
 * own 24px top margin and the grid gap would double it.
 */
.hero-grid { display: grid; gap: 36px; align-items: center; }
.hero-text > :last-child { margin-bottom: 0; }
.hero .proof-list { margin-top: 0; }

@media (min-width: 900px) {
	.hero-grid {
		grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
		grid-template-areas:
			"text  film"
			"proof film";
		column-gap: 48px;
		row-gap: 0;
	}
	.hero-text        { grid-area: text; }
	.hero .film       { grid-area: film; }
	.hero .proof-list { grid-area: proof; margin-top: 24px; }
}

.film { margin: 36px 0 0; }
.hero .film { margin: 0; }
.film video {
	display: block;
	width: 100%;
	height: auto;
	border: 1px solid var(--line);
	border-radius: var(--radius-xl);
	background: #000;
	box-shadow: var(--shadow-lg);
}
.film figcaption {
	margin-top: 12px;
	font-size: 15px;
	color: var(--ink-faint);
	max-width: var(--measure);
}
/* The dark-scheme shadow override that used to sit here is gone, along with the
   matching one on .shot: both are now --shadow-lg, which the dark block
   redefines once for everything that casts a shadow. */

/* ---- the enquiry form ------------------------------------------------ */
/*
 * The form is the primary call to action on /contact, because a mailto: is not
 * one: it does nothing on a machine with no mail client configured, which is
 * most machines a practice owner reads this on. The mailbox stays underneath it
 * and in the footer, where it is a fallback rather than the whole path.
 *
 * No CAPTCHA, deliberately. A veterinarian meeting a puzzle at the point of
 * enquiry is a conversion loss we would be able to measure; the spam it would
 * prevent is a problem we cannot yet quantify. The honeypot costs the person
 * filling it in nothing, and the Worker rate limits on top of it.
 *
 * Labels are elements above their field, never placeholders. A placeholder
 * disappears the moment somebody types, which is exactly when they need to
 * check what the field was for.
 */

/* For a <caption> that a sighted reader does not need, because the heading
   above the table already says it, and a screen reader does. */
.visually-hidden {
	position: absolute;
	width: 1px;
	height: 1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}

/* 46rem, not 34: it holds two columns now. On a wide screen the fields still
   stop well short of the 68ch measure the prose uses, because an input you have
   to track across is harder to proof-read than a paragraph. */
.form { max-width: 46rem; margin-top: 32px; }

/* One row per field, two per row above 620px.
 *
 * Below that a second column leaves every input too narrow to read back what
 * you typed, which is worse than the scrolling it saves. `minmax(0, 1fr)`
 * rather than `1fr`, so a long `<option>` cannot push a column past its share
 * and take the page with it. */
.form-grid {
	display: grid;
	gap: 22px 24px;
	grid-template-columns: minmax(0, 1fr);
	align-items: start;
}
@media (min-width: 620px) {
	.form-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}

/* The gap owns the spacing between fields, so nothing inside one carries a
   bottom margin. That is what lets two fields of different heights, one with a
   hint and one without, sit level at the top of a row. */
.field { min-width: 0; }
.field.wide { grid-column: 1 / -1; }

.form label {
	display: block;
	font-size: 15px;
	font-weight: 600;
	margin: 0 0 6px;
}

/* "optional" belongs in the label, not in a note underneath it.
 *
 * Four fields carried a one-line hint that said nothing but "Optional", which
 * cost a line each and, worse, made rows end at different heights: a grid row
 * is as tall as its tallest cell, so one hint left a gap beside the field that
 * did not have one. In the label it is also read out as part of the label,
 * which is where somebody using a screen reader expects to hear it. */
.opt {
	font-weight: 400;
	font-size: 13px;
	color: var(--ink-faint);
	margin-left: 5px;
}

/* An example, never a label: the real label sits above and stays there. This is
   the browser's own placeholder colour territory, so it is stated rather than
   inherited, and it is deliberately not carrying anything a reader needs. */
.form ::placeholder { color: var(--ink-faint); opacity: 1; }
.field input,
.field select,
.field textarea { margin-bottom: 0; }
.field .hint {
	font-weight: 400;
	color: var(--ink-faint);
	font-size: 14px;
	margin: 6px 0 0;
	max-width: none;
}

.form-send {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 12px 20px;
	margin-top: 26px;
}
.form-send .hint {
	margin: 0;
	font-size: 14px;
	color: var(--ink-faint);
	max-width: 30rem;
}

.form input[type="text"],
.form input[type="email"],
.form select,
.form textarea {
	display: block;
	width: 100%;
	max-width: 100%;
	font: inherit;
	font-size: 16px;                 /* 16px or iOS zooms the page on focus. */
	color: var(--ink);
	background: var(--paper);
	border: 1px solid var(--ink-faint);
	border-radius: var(--radius);
	padding: 11px 13px;
	margin: 0 0 20px;
}
.form textarea { resize: vertical; }
/* A hint sits between its field and the next label, so the field's own bottom
   margin would read as a gap in the wrong place. */
.form input + .hint, .form textarea + .hint, .form select + .hint {
	margin: -14px 0 20px;
	font-size: 14px;
	color: var(--ink-soft);
	max-width: 34rem;
}

.form fieldset {
	border: 0;
	padding: 0;
	margin: 0;
	min-width: 0;                    /* Firefox floors a fieldset at min-content. */
}
.form legend {
	padding: 0;
	font-size: 15px;
	font-weight: 600;
	margin-bottom: 6px;
}

/* One row per option, and the whole row is the label, so the target is the
   sentence rather than the 13px circle beside it. WCAG 2.2 asks 24px minimum
   and recommends 44; a row of body text with this padding is about 46. */
/* Three across on a desktop, stacked on a phone.
 *
 * As three full-width rows this was about 200px, the tallest thing on the form,
 * for a question with three short answers. Across, it is one row. The labels
 * were shortened to make that fit, and the "written answer, no call" option had
 * to stay visible rather than move into a <select>: it is the one that lets
 * somebody ask without booking anything, and hiding it behind a dropdown is how
 * a low-pressure option stops being chosen. */
.choices { display: grid; gap: 4px; margin-top: 10px; }
@media (min-width: 620px) {
	.choices { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
}
/* Scoped through .form, not `.choice` alone: `.form label` above is (0,1,1) and
   would otherwise win on `display` and `font-weight`, leaving a block element
   in bold where a flex row in body weight was intended. */
.form .choice {
	display: flex;
	gap: 12px;
	align-items: flex-start;
	margin: 0;
	padding: 11px 12px;
	border: 1px solid var(--line);
	border-radius: var(--radius);
	font-weight: 400;
	font-size: 15.5px;
	cursor: pointer;
}
.form .choice:hover { background: var(--paper-soft); }
.form .choice input { width: auto; margin: 3px 0 0; flex: none; }
.form .choice:has(input:checked) { border-color: var(--brand-dark); background: var(--brand-wash); }

/* Every page links here with a fragment: #demo, #dry-run or #question. Without
   this the browser scrolls the row into view and gives no sign of why, which is
   a jump to the middle of a form for no visible reason.
 *
 * The ring is `--brand` rather than `--brand-wash`. The wash is three pixels of
 * almost-ground on an almost-ground page: it was in the stylesheet, it applied
 * correctly, and an audit inspecting the live target recorded no visible change,
 * which is the same outcome as not having written it. This is now the only thing on the screen wearing the brand colour, which
 * is what it has to be: with no default selection, this highlight is the only
 * instruction a visitor gets about which row to click.
 *
 * `scroll-margin-top` because the header is sticky. Landing a highlighted row
 * underneath the thing that hid it is the failure mode this whole rule exists
 * to prevent. Three values because the header wraps: 66px of bar on a desktop,
 * two nav rows on a phone, three at 320px. */
.form .choice:target {
	border-color: var(--brand);
	background: var(--brand-wash);
	box-shadow: 0 0 0 3px var(--brand);
}
.form .choice { scroll-margin-top: 216px; }
@media (min-width: 360px) { .form .choice { scroll-margin-top: 168px; } }
@media (min-width: 768px) { .form .choice { scroll-margin-top: 82px; } }

.form button {
	font: inherit;
	cursor: pointer;
}

/* The error summary the Worker sends back when a submission does not validate.
   Focused on arrival via autofocus on a tabindex="-1" container, which is the
   no-JavaScript way of moving focus after a failed submit. */
.errors {
	border-left: 4px solid var(--brand-dark);
	padding-left: 20px;
}
.errors ul { max-width: var(--measure); line-height: 1.9; }

/* The honeypot. Not `display: none`: some bots skip what is not rendered, and
   more importantly a screen reader must not meet a field nobody can explain.
   Taken out of the layout, out of the tab order, and named as ignorable. */
.trap {
	position: absolute;
	left: -9999px;
	width: 1px;
	height: 1px;
	overflow: hidden;
}

/* ---- pricing --------------------------------------------------------- */

.price {
	font-size: clamp(30px, 4.2vw, 40px);
	line-height: 1.1;
	letter-spacing: -0.03em;
	font-weight: 660;
	margin: 0 0 6px;
}

/* The home page's price, as an object on the page rather than as a paragraph.
 *
 * The card is white on `.soft`, which is the only place on the site where a
 * panel sits on the tinted band rather than on --paper, and that inversion is
 * what makes it read as a thing lifted off the page rather than another block of
 * prose. It is the elevation the cards get and not the screenshots': this is a
 * fact, not evidence.
 *
 * Held to 40rem so the number and the four conditions stay in one glance. Given
 * the full 1080px shell the list runs to a single line each and the block reads
 * as a footnote under a large number, which is the layout it is replacing.
 */
.price-card {
	background: var(--paper);
	border: 1px solid var(--line);
	border-radius: var(--radius-lg);
	box-shadow: var(--shadow-md);
	padding: 32px;
	margin-top: 28px;
	max-width: 40rem;
}

/* Bigger than the /pricing page's own .price, which is the right way round: that
   page has a table under it doing the explaining, and this one has four lines
   and a link. It is also, deliberately, the largest thing on the home page after
   the h1. */
.price-card .price {
	font-size: clamp(44px, 6vw, 64px);
	letter-spacing: -0.04em;
	margin: 0 0 18px;
}
.price-card .per {
	font-size: 19px;
	letter-spacing: -0.01em;
}

/* Checks rather than bullets. Three of these four are things a practice is *not*
   charged for, and a disc reads as neutral where this reads as inclusion.
 *
 * The mark is a CSS-drawn tick and not a character, because "✓" in a list item
 * is content: a screen reader announces it, and "check mark Each additional
 * veterinarian is $79" is not what this row says. Decorative, so the <li> is
 * what gets counted and the tick adds nothing to read.
 *
 * Two borders rotated 45deg is the same construction as the disclosure chevron
 * on /product, at a different angle. Nothing here needs a font or an SVG. */
.price-included {
	list-style: none;
	padding: 0;
	margin: 0 0 26px;
	display: grid;
	gap: 12px;
	font-size: 16.5px;
	line-height: 1.5;
	color: var(--ink-soft);
	max-width: var(--measure);
}
.price-included li {
	position: relative;
	padding-left: 30px;
}
.price-included li::before {
	content: "";
	position: absolute;
	left: 4px;
	top: 6px;
	width: 6px;
	height: 11px;
	border-right: 2px solid var(--brand);
	border-bottom: 2px solid var(--brand);
	transform: rotate(45deg);
}

/* The actions row brings its own 28px top margin, which is right when it follows
   a paragraph and too much when it follows a list that already has one. */
.price-card .actions { margin-top: 0; }

@media (max-width: 560px) {
	.price-card { padding: 24px; }
}
.price .per {
	font-size: 17px;
	font-weight: 400;
	letter-spacing: 0;
	color: var(--ink-faint);
}

/* A plain table reads better here than cards: the whole argument of this page
   is that the numbers are comparable and nothing is hidden behind a call. */
.table {
	width: 100%;
	border-collapse: collapse;
	margin: 24px 0 8px;
	font-size: 15.5px;
	max-width: var(--measure);
}
.table th, .table td {
	text-align: left;
	padding: 11px 14px 11px 0;
	border-bottom: 1px solid var(--line);
	vertical-align: top;
}
.table th { font-weight: 600; }
.table td.num, .table th.num { text-align: right; padding-right: 0; font-variant-numeric: tabular-nums; }

/* Money columns of one width, for tables that compare like with like.
 *
 * Left to itself a table sizes each column to its widest cell, so the three
 * cost columns on /pricing came out 78px, 96px and 190px: the widest was the
 * one whose *header* is longest ("36 months, paid yearly"), not the one whose
 * numbers are biggest. Three totals a reader is meant to compare were drawn at
 * three different sizes, which is the one thing a comparison table must not do.
 *
 * `table-layout: fixed` so the percentages are obeyed rather than treated as
 * hints, and the row-label column takes whatever is left. Only on tables that
 * ask for it: the coverage tables elsewhere hold prose of very different
 * lengths and are right to size themselves. */
.table.even-nums { table-layout: fixed; }
.table.even-nums .num { width: 19%; }
.table tr:last-child td { border-bottom: 0; }

/* Wide tables scroll inside their own box rather than making the page scroll. */
.scroll-x { overflow-x: auto; max-width: 100%; }

.table caption {
	text-align: left;
	color: var(--ink-soft);
	font-size: 15px;
	line-height: 1.5;
	padding-bottom: 10px;
	max-width: var(--measure);
}

/* ---- export coverage ------------------------------------------------- */
/*
 * The two tables on /leaving. Wider than the pricing tables because the third
 * column is a sentence rather than a number, and generated rather than typed:
 * see site/make-coverage.py.
 */

.table.coverage {
	max-width: none;
	min-width: 42rem;               /* Below this the third column is unreadable;
	                                   .scroll-x gives it its own scroll instead
	                                   of making the page scroll. */
	font-size: 15px;
}
.table.coverage th[scope="row"] { white-space: nowrap; padding-right: 20px; }
.table.coverage td { color: var(--ink-soft); }

/* Named, not positional. This was `td:first-of-type`, meaning "the file column"
   in the three-column coverage tables it was written for. Every two-column
   table reusing this class then had its *description* held on one line: the
   security fact table came out 2,820px wide inside a 1,032px box, which
   `.scroll-x` dutifully turned into a horizontal scrollbar rather than a
   readable table. A filename is the only thing here that should never wrap. */
.table.coverage td.file { white-space: nowrap; padding-right: 20px; }
.table.coverage code {
	font-size: 13.5px;
	color: var(--ink);
	background: var(--paper-soft);
	border: 1px solid var(--line);
	border-radius: 5px;
	padding: 1px 5px;
}

.verified {
	font-size: 15px;
	color: var(--ink-soft);
	border-left: 3px solid var(--line);
	padding-left: 16px;
}

/* ---- what is included today ------------------------------------------ */
/*
 * The capability list on /product. A definition list rather than a table: each
 * row is one term and one answer, which is what a <dl> is, and it collapses to
 * one column on a phone without needing a horizontal scroll.
 *
 * Every status is a word. An icon on its own cannot say "included, but the
 * lot number has to be typed", and that is exactly the kind of thing a buyer
 * needs to read before a demo rather than discover during one.
 */
.caps { margin-top: 32px; display: grid; gap: 0;
        border: 1px solid var(--line); border-radius: var(--radius-lg); overflow: hidden; }
.cap {
	display: grid;
	grid-template-columns: minmax(0, 1fr) 150px minmax(0, 1.5fr);
	gap: 18px;
	padding: 16px 22px;
	border-bottom: 1px solid var(--line);
	font-size: 15.5px;
	align-items: baseline;
}
.cap:last-child { border-bottom: 0; }
.cap dt { font-weight: 600; margin: 0; }
.cap dd { margin: 0; color: var(--ink-soft); }

.status {
	display: inline-block;
	font-size: 13px;
	font-weight: 640;
	letter-spacing: 0.02em;
	padding: 3px 10px;
	border-radius: 999px;
	border: 1px solid var(--line);
	white-space: nowrap;
}
.status.yes { background: var(--brand-wash); color: var(--brand-deep); border-color: transparent; }
.status.part { background: var(--paper-soft); color: var(--ink); }
.status.no { background: transparent; color: var(--ink-soft); }
/* The included list, one click away.
 *
 * `<details>` and not a scripted accordion, because this site's CSP is
 * `script-src 'none'` and always will be. It costs nothing, it is keyboard
 * operable and announced as a disclosure without any ARIA, find-in-page opens
 * it in current browsers, and every word inside stays in the document for a
 * crawler. An accordion built by hand would need all of that written and none
 * of it would be better.
 */
.caps-lead { margin-top: 28px; color: var(--ink-soft); }
.caps-lead + .caps { margin-top: 14px; }

.caps-more { margin-top: 14px; }
.caps-more > summary {
	cursor: pointer;
	display: flex;
	align-items: center;
	gap: 10px;
	padding: 14px 22px;
	border: 1px solid var(--line);
	border-radius: var(--radius-lg);
	font-weight: 600;
	font-size: 15.5px;
	list-style: none;
	transition: background var(--motion) var(--ease),
	            border-color var(--motion) var(--ease);
}
/* Safari draws its own triangle through `::-webkit-details-marker` and ignores
   `list-style: none` on the summary itself. */
.caps-more > summary::-webkit-details-marker { display: none; }
.caps-more > summary::before {
	content: "";
	width: 9px; height: 9px;
	border-right: 2px solid var(--brand);
	border-bottom: 2px solid var(--brand);
	transform: rotate(-45deg);
	margin-left: 2px;
	flex: none;
	transition: transform var(--motion) var(--ease);
}
.caps-more[open] > summary::before { transform: rotate(45deg); margin-top: -4px; }
.caps-more[open] > summary { border-radius: var(--radius-lg) var(--radius-lg) 0 0; border-bottom: 0; }
.caps-more > summary:hover { background: var(--paper-soft); border-color: var(--ink-faint); }
.caps-more[open] > .caps { margin-top: 0; border-radius: 0 0 var(--radius-lg) var(--radius-lg); }

/* The same disclosure around something that is not a capability panel.
   `.caps-more` hands the bottom half of its border to `.caps`, which draws it.
   On /switching it wraps a scrolling table, which does not, so the summary
   keeps all four sides and the content sits below it instead of welded to it. */
.caps-more.bare[open] > summary {
	border-radius: var(--radius-lg);
	border-bottom: 1px solid var(--line);
}
.caps-more.bare > .disclosed { margin-top: 18px; }

/* The focus ring is the page's, not the browser's, and it has to clear the
   border rather than sit on top of it. */
.caps-more > summary:focus-visible {
	outline: 2px solid var(--brand);
	outline-offset: 2px;
}

@media (max-width: 760px) {
	.cap { grid-template-columns: 1fr; gap: 6px; }
	.table.coverage { min-width: 34rem; }
	.caps-more > summary { padding: 14px 18px; }
}

/* ---- citations ------------------------------------------------------- */
/*
 * For any claim about the market rather than about our own software. If a
 * sentence needs one of these and cannot have one, the sentence is wrong and
 * gets rewritten as a Vetmira position instead of a fact about everybody else.
 */
.cite {
	font-size: 14.5px;
	color: var(--ink-faint);
	max-width: var(--measure);
	margin-top: -6px;
	padding-left: 16px;
	border-left: 3px solid var(--line);
}
.cite a { color: var(--ink-soft); }

/* The group headings inside the checklist disclosure. Quiet, because they
   separate two lists rather than opening a new argument, and the badges on
   each row already say which list a reader is in. */
.caps-group {
	font-size: 15px;
	font-weight: 640;
	letter-spacing: 0.01em;
	color: var(--ink-soft);
	margin: 22px 0 10px;
}
.caps-more[open] > .caps-group:first-of-type { margin-top: 18px; }

/* The two documents a practice hands over, beside each other.
 *
 * They are different sizes on purpose and the CSS has to keep them that way:
 * the certificate is a page and the label is 56mm of thermal roll, and drawing
 * them the same width would make the label look like a second certificate.
 * `align-items: start` so they hang from one top edge rather than centering
 * against each other, which is how two pieces of paper land on a counter. */
.shot.papers {
	display: flex;
	align-items: flex-start;
	gap: 20px;
	background: none;
	box-shadow: none;
}
/* The flex child, whatever it is. These named <picture> until each one was
   wrapped in a link, at which point the anchors became the flex items and the
   two documents both fell to their content width. Selecting the child rather
   than the tag means unwrapping them later cannot break it back. */
.shot.papers > :first-child { flex: 0 1 380px; }
.shot.papers > :last-child { flex: 0 0 150px; }
.shot.papers img { width: 100%; height: auto; display: block; }

@media (max-width: 520px) {
	.shot.papers { gap: 14px; }
	.shot.papers > :last-child { flex: 0 0 110px; }
}
