/* ==========================================================================
   CASE STUDY BASE -- the six Tailwind-built case studies.

   Loaded by exactly the six pages that do NOT load style.css: dispatch,
   enterprise, hybrid, innovators, sap and usda. It is the sibling of
   case-motion.css, which those same six pages already shared as a file while
   this block stayed inline and copied.

   WHY THIS FILE EXISTS. It used to be a <style id="case-study-base"> block
   pasted into all six pages. Comments stripped, 112 of its 137 code lines were
   byte-identical across the copies -- and the 25 that were not had drifted into
   five versions, none of them on purpose. Two pages, enterprise and hybrid,
   never received two fixes the other four got:

     - the table-of-contents link kept 4px of vertical padding, so its tap
       target was 31px tall against the 44px this code aims for (24px is the
       WCAG 2.5.8 floor, which it did clear);
     - its active state kept var(--color-accent-deep), 4.51:1, where the others read 15.33:1.

   Both variants date from the same import commit, so neither was newer; they
   had simply disagreed since the site arrived, with nothing able to notice.
   That is the argument for one file rather than six copies, and it is the same
   argument the tokens in color.css, type.css and shell.css already won.

   WHAT THIS FILE IS NOT. It is not a merge of style.css. Of the 41 selectors
   here, 5 exist there; the other 36 -- .case-toc, .case-closing, .case-readtime,
   .scroll-progress -- are components only a case study has. Regime A's nine
   pages would download all of it and render none of it.
   ========================================================================== */

/* Cross-document view transitions: this page's hero pairs with the
   clicked case card's thumbnail on the home page (and with other
   case pages' heroes via the next-case links). Reduced motion and
   unsupported browsers navigate normally. */
@view-transition { navigation: auto; }
@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
}
@media (max-width: 767.98px) {
  @view-transition { navigation: none; }
}
.vt-case-hero { view-transition-name: case-hero; }
::view-transition-group(case-hero) {
  animation-duration: var(--motion-enter);
  animation-timing-function: var(--ease);
}

html {
  /* Absolute, and must be: this is what every rem here resolves
     against, --text-base included. */
  font-size: 16px; scroll-behavior: smooth; scroll-padding-top: 140px;
  --font-sans: "Montserrat", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-serif: "Crimson Text", "Georgia", "Times New Roman", serif;
}
body { font-family: var(--font-sans); color: var(--color-charcoal); background: var(--color-white); }

/* Higher specificity beats Tailwind preflight. */
/* The heading ladder, same as style.css. TYPOGRAPHY.md section 4 is
   normative; the tokens come from type.css, which every page now loads.
   `body h*` rather than `h*` because Tailwind's preflight would otherwise
   win on specificity.

   This used to be a second, flat ladder: 80/48/34 with no viewport
   response at all, so an 80px h1 was 80px on a phone unless a utility
   overrode it -- which is why every hero carried three hand-tuned
   breakpoint sizes and a hand-set leading. It also disagreed with
   style.css on what an h4 is. Both regimes now read one ladder.

   Class names are deliberately not written out in these comments:
   Tailwind's scanner reads the whole document, comments included, and
   would generate a dead utility for each one it found. */
body h1, body h2, body h3 {
  font-family: var(--font-serif);
  font-weight: var(--weight-regular); color: var(--color-charcoal);
}
body h4, body h5, body h6 {
  font-family: var(--font-sans);
  font-weight: var(--weight-semibold); line-height: var(--leading-tight); color: var(--color-charcoal);
}
body h1 { font-size: var(--type-title);      line-height: var(--leading-display); letter-spacing: var(--tracking-display); }
body h2 { font-size: var(--type-section);    line-height: var(--leading-heading); letter-spacing: var(--tracking-title); }
body h3 { font-size: var(--type-subsection); line-height: var(--leading-heading); letter-spacing: var(--tracking-heading); }
body h4 { font-size: var(--text-xl);  letter-spacing: var(--tracking-heading); }
body h5 { font-size: var(--text-lg);  letter-spacing: var(--tracking-normal); }
body h6 { font-size: var(--text-base); letter-spacing: var(--tracking-normal); }
body p  { color: var(--color-charcoal); font-size: var(--text-base); line-height: var(--leading-body); }

/* Framer-motion fade-up: keep initial state, reveal via JS observer. */
.min-h-screen [style*="opacity: 0"],
.min-h-screen [style*="opacity:0"] {
  transition:
    opacity var(--motion-enter) var(--ease-enter), 
    transform var(--motion-enter) var(--ease-enter);
  will-change: opacity, transform;
}
.min-h-screen .revealed { opacity: 1 !important; transform: none !important; }
@media (prefers-reduced-motion: reduce) {
  .min-h-screen [style*="opacity: 0"],
  .min-h-screen [style*="opacity:0"] {
    opacity: 1 !important; transform: none !important; transition: none !important;
  }
  html { scroll-behavior: auto; }
}

.skip-link { position: absolute; left: -9999px; top: 0; background: var(--color-charcoal); color: var(--ink-on-dark); padding: var(--space-xs) var(--space-md); z-index: 1000; }
.skip-link:focus { left: 0; }

.border-border-light { border-color: var(--color-tag-border) !important; }

/* ---------- Scroll progress bar ---------- */
.scroll-progress {
  position: fixed; top: 0; left: 0;
  height: 2px; width: 100%;
  /* Full width, scaled from the left edge: the script writes scaleX(progress)
     rather than a percentage width, so the bar moves on the compositor and
     never asks for layout on scroll -- which is the one event it listens
     to. */
  transform: scaleX(0); transform-origin: 0 50%;
  background: var(--color-accent-deep);
  z-index: 100;
  transition: transform var(--motion-response) linear;
  pointer-events: none;
}

/* ---------- Read-time line in the hero ---------- */
.case-readtime {
  display: block;
  margin-bottom: var(--space-xl);
  font-size: var(--text-sm);
  color: var(--color-muted-gray);
  letter-spacing: 0.02em;
}

/* ---------- Sticky in-page section nav ---------- */
/* The captured markup wraps the whole page in `overflow-x-hidden`, and
   `overflow-x: hidden` turns that wrapper into a scroll container, which
   silently broke `position: sticky` on .case-toc below, so the section
   nav scrolled away instead of pinning under the header. `clip` clips the
   same overflow without creating a scrollport, so sticky resolves against
   the viewport again. */
.min-h-screen.overflow-x-hidden { overflow-x: clip; }

.case-toc {
  position: sticky;
  top: 80px;
  z-index: 30;
  /* The one literal left in this file, and it stays on purpose. Written as
     color-mix(in srgb, var(--color-white) 94%, transparent) it is the same
     color, but it COMPUTES to color(srgb 1 1 1 / 0.94), which resting.mjs
     and states.mjs cannot composite -- they read the bar as opaque charcoal
     and report every link in the table of contents at 1.24:1. The tokenized
     spelling costs the check its ability to see 38 pieces of text. rgba()
     keeps it measurable, which matters more here than the token does. */
  background: rgba(255, 255, 255, 0.94);
  backdrop-filter: saturate(180%) blur(10px);
  -webkit-backdrop-filter: saturate(180%) blur(10px);
  border-bottom: 1px solid var(--color-tag-border);
}
.case-toc-inner {
  max-width: 1280px;
  margin: 0 auto;
  /* Horizontal padding tracks site-nav.css's .nav-inner (1.5rem / 2rem)
     so the section links line up under the logo at every width. */
  padding: var(--space-sm) var(--gutter);
  display: flex; gap: var(--space-xl);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.case-toc-inner::-webkit-scrollbar { display: none; }
.case-toc-inner a {
  color: var(--color-muted-gray);
  font-size: var(--text-sm);
  font-weight: 500;
  letter-spacing: 0.01em;
  white-space: nowrap;
  /* Vertical padding bumped from 0.25rem to 0.75rem so the link
     hits the 44px touch target minimum without resizing text. */
  padding: var(--space-sm) 0;
  border-bottom: 2px solid transparent;
  transition: color var(--motion-state) var(--ease),  border-color var(--motion-state) var(--ease);
  display: inline-flex;
  align-items: center;
}
.case-toc-inner a:hover { color: var(--color-charcoal); }
/* Active state: charcoal text (was sage var(--color-accent-deep) which failed 4.5:1
   at this size). The sage underline still signals which section
   the user is in. */
.case-toc-inner a.is-active { color: var(--color-charcoal); border-bottom-color: var(--color-accent-deep); }
@media (max-width: 640px) {
  .case-toc-inner { padding: var(--space-sm) var(--space-xl); gap: var(--space-lg); }
  .case-toc-inner a { font-size: var(--text-sm); }
}

/* ---------- Closing block at end of page ---------- */
.case-closing {
  background: var(--color-warm);
  border-top: 1px solid var(--color-tag-border);
  padding: var(--space-section) 0;
}
.case-closing-inner {
  max-width: 1280px;
  margin: 0 auto;
  padding: 0 var(--gutter);
}
.case-closing-next {
  display: block;
  padding: var(--space-xl) 0;
  border-top: 1px solid var(--color-accent-deep);
}
@media (min-width: 768px) {
  .case-closing-next { padding: var(--space-2xl) 0; }
}
.case-closing-next-eyebrow {
  display: block;
  /* Sage, and specifically --color-accent-text (var(--color-accent-text)) rather than the
     var(--color-accent-deep) this once was. That older sage is --color-accent-deep, a FILL
     color, and this eyebrow sits on the WARM ground at 12px, where deep
     measures 4.12:1 -- a real failure, which is why four of the six pages
     had already retreated to charcoal. accent-text is the darker step the
     palette keeps for small type: 5.32:1 here, and over 4.5 on all four
     light grounds. So the sage is keepable; it just has to be the right
     one. COLOR.md section 2 has the split. */
  color: var(--color-accent-text);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  margin-bottom: var(--space-xs);
}
.case-closing-next-title {
  display: block;
  color: var(--color-charcoal);
  font-family: var(--font-serif);
  font-size: var(--text-3xl);
  font-weight: 400;
  line-height: 1.15;
  letter-spacing: -0.01em;
  transition: color var(--motion-state) var(--ease);
}
@media (min-width: 768px) {
  .case-closing-next-title { font-size: var(--text-4xl); }
}
.case-closing-next:hover .case-closing-next-title { color: var(--color-accent-deep); }

/* The engagement a case study is evidence for, named on the case study.
   The link already ran the other way -- each engagement page closes on the
   client work that proves it, through .eng3-proof-cta -- and only that way,
   so a reader who arrived at the proof had no way back to the offer it
   proves. One direction of a two-way relationship is the easiest kind of
   link to leave out, because from either page the site looks joined up.

   Small type, so the sage has to be the darker step: --color-accent-text
   measures 5.32:1 on this warm ground where --color-accent-deep is 4.12:1.
   That is also why the hover goes to charcoal rather than to deep, the way
   the next-case title above it does. The title can take deep because it is
   --text-3xl and answers to the 3:1 large-text floor; this line is
   --text-sm and answers to 4.5. Same palette, different floor, and the
   difference is the whole reason COLOR.md keeps two sages. */
.case-closing-service {
  margin: 0 0 var(--space-xl);
  color: var(--color-charcoal);
  font-size: var(--text-sm);
}
.case-closing-service a {
  color: var(--color-accent-text);
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 0.2em;
  transition: color var(--motion-state) var(--ease);
}
.case-closing-service a:hover,
.case-closing-service a:focus-visible { color: var(--color-charcoal); }

/* ---------- The ask, at the foot of every case study ----------
   A reader who lands on a case study from a search or a shared link has just
   seen the strongest evidence the site has, and until 2026-09-05 the page
   offered them the next case study and nothing else. This is the same ask
   the homepage's contact section makes, in three lines, pointed at that
   section. A text link rather than a button: it is the next step, not a
   pitch, and a button on this warm ground would outshout the next-case
   title beneath it.

   Small type, so the sage is the darker step and the hover goes to charcoal,
   for the reasons .case-closing-service gives. style.css carries the same rules for the
   other family; the two deliberately do not share a stylesheet. */
.case-ask {
  margin: 0 0 var(--space-2xl);
  max-width: var(--measure);
}
.case-ask-title {
  margin: 0 0 var(--space-sm);
  font-family: var(--font-serif);
  font-size: var(--text-2xl);
  font-weight: 400;
  line-height: var(--leading-heading);
  letter-spacing: -0.01em;
  color: var(--color-charcoal);
}
.case-ask p {
  margin: 0 0 var(--space-md);
  font-size: var(--text-base);
  line-height: var(--leading-body);
  color: var(--color-charcoal);
}
.case-ask-link {
  display: inline-block;
  color: var(--color-accent-text);
  font-size: var(--text-base);
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 0.2em;
  transition: color var(--motion-state) var(--ease);
}
.case-ask-link:hover,
.case-ask-link:focus-visible { color: var(--color-charcoal); }

/* ---------- Breathing room between hero image and the first section ----------
   The captured markup wraps the hero image in a `mt-14 md:mt-20` div and
   then begins the next section with very tight pt-2 / md:pt-3. That makes
   the Overview heading feel pinned to the image. Add explicit bottom
   margin to the image's wrapper so the page has room to breathe. */
.min-h-screen .mt-14.md\:mt-20 { margin-bottom: var(--space-3xl); }
@media (min-width: 768px) {
  .min-h-screen .mt-14.md\:mt-20 { margin-bottom: var(--space-block); }
}

/* ---------- Body paragraph readability ----------
   The captured markup lets prose span the full content container, about
   1200px at 1440, which is far wider than a comfortable line. Cap it at
   --measure, the 52ch SPACING.md sets, which is 70 characters of the
   size the paragraph takes. A case study caps only its prose: the
   figures, step cards and the impact table itself keep the whole column.

   Every prose signature the six pages use is listed. The step paragraphs
   were `mb-2 pb-[30px]` when the first version of this rule was written
   and have been `mb-2 pb-8` since #20, so the selector matched nothing
   and the five approach paragraphs on the dispatch page ran
   1216px. The impact table's outcome cells and its pull line under it are
   divs and a white paragraph on the accent ground, which the earlier
   rule never named. The two essay pages' paragraphs are `mb-4` alone in
   the markup, but case-motion.js appends `cm cm-rise-sm` before first
   paint, so the exact [class="mb-4"] test the earlier rule used matched
   them in the file and never in the browser. The centered intros keep
   their center by also getting an auto inline margin, which is what the
   mx-auto on two of them did. */

/* The size is the reading column's too. body p above is --text-base on
   --leading-body because it is also every card line, caption and fact on
   these pages; the prose signatures listed here are the paragraphs a
   reader sits with, and they take --text-lg on --leading-reading, the same
   paragraph .case-section p gives the build write-ups and .writing-measure
   gives the essays. TYPOGRAPHY.md §2 names --text-lg as reading prose and
   --leading-reading as the long-form column, and these six were the one
   family of long-form pages still at the 16px default. The measure is in
   ch, so it grows with the size: about 619px here, the same 52 characters
   as the build write-ups. The impact table's outcome cells are the one
   listed shape that is not a paragraph -- divs in a table, next to the
   table's own figures -- so they keep the base size and take only the
   measure.

   The mb-4 signature is the two essay pages' paragraph, and it is also the
   four step labels on the Innovators Studio page, twelve-pixel semibold
   words in white cards that carry mb-4 with their size utility. A
   two-class selector here beats the one-class utility, so without the
   exclusion those labels took 18px; the measure alone never showed it,
   because a nine-character word never reaches the cap. */
.min-h-screen p.text-charcoal.leading-relaxed,
.min-h-screen p.mb-10.text-charcoal,
.min-h-screen p.mb-2.pb-8.text-charcoal,
.min-h-screen p.mb-4:not(.text-xs),
.min-h-screen .bg-accent > p.text-white,
.min-h-screen p.text-center.text-charcoal {
  max-width: var(--measure);
  font-size: var(--text-lg);
  line-height: var(--leading-reading);
}
.min-h-screen .bg-warm .grid > .text-charcoal:last-child { max-width: var(--measure); }
.min-h-screen p.text-center.text-charcoal { margin-inline: auto; }

/* ---------- The impact table is as wide as its own contents ----------
   The rule above caps the outcome cells at the measure, and the paragraph
   above that says a case study caps only its prose while the impact table
   keeps the whole column. That was half a decision. Capped text inside an
   uncapped box does not read as a wide table; it reads as a narrow table
   with 290px of empty warm ground down its right-hand side, which is what
   1216px of card holding 929px of content actually looks like.

   So the card takes the width its own parts add up to, and the number is
   not typed: the measure the cells are already held to, plus the dimension
   column, plus the gap between the two, plus the card's own inset on both
   sides. Three of those four are the tokens the utilities already resolve
   to -- gap-12 is --space-4xl and md:p-10 is --space-3xl -- and the fourth
   is the 250px grid track the captured markup authors, which has no token
   because it is a column width rather than a space. At 1440 it comes to
   929px, and the outcome column lands exactly on the measure: the cap stops
   binding, and the longest line ends on the card's padding rather than
   short of it. Change --measure and the card follows.

   It is a max-width, so every width below it is untouched: the phone's
   130px column and the single-column stack reach this rule already narrower
   than it, and the card is the container's width until the viewport passes
   about 1010px.

   What this costs is the card no longer ranging with the full-width figures
   and the two-up step cards above it. That is the right trade for a table:
   those are pictures, which are as wide as they are drawn, and this is
   reading, which is as wide as the measure. `.bg-warm.font-sans` is the
   impact card on all four pages that have one and nothing else on any of
   the nine. */
.min-h-screen .bg-warm.font-sans {
  max-width: calc(var(--measure) + 250px + var(--space-4xl) + 2 * var(--space-3xl));
}

/* ---------- The pull line under the table, and the mark beside it ----------
   The closing line on the accent ground is the one sentence of the section a
   reader will repeat, and it sat alone in its band. It has a drawing of what
   it says: the stamp and the row it produced for SAP, the metronome the
   dispatch floor found, the trellis the Hub's network grew on, the lantern
   the studio lit. One per page, because the sentence is one per page.

   Since 2026-09-18 the drawing is a picture, not an inline SVG. The mark used
   to be a hairline in a 100-unit box that drew itself on the scroll, and the
   owner did not like watching it; it is now a white line on a transparent
   PNG in the writing illustrations' hand, 16:9, drawn in ChatGPT with the
   same reference the articles use and filed at 320x180 for a 160px slot. A
   picture needs no stroke arithmetic and no pathLength, so the notes about
   hairlines and non-scaling-stroke that used to sit here went with the SVG.

   White on --color-accent-deep is about 4.3:1, past the 3:1 a non-text mark
   answers to -- the band is the same ground the button ink sits on at 4.12,
   and this is lighter than that ink. COLOR.md section 2 has the row. The
   line is the only ink in the file; everything else is alpha.

   The mark holds its 160px against a paragraph of any length: `flex: none`
   so the measure gets every pixel it is entitled to and the mark is not the
   thing that gives. The height follows the width at the file's own 16:9, so
   the width is the one number here. Under 640 the two stack, because 160px
   of mark plus the gap out of a 342px card is over half of the line. */
.min-h-screen .bg-warm.font-sans > .bg-accent {
  display: flex;
  align-items: center;
  gap: var(--space-2xl);
}
.min-h-screen .impact-mark {
  flex: none;
  width: 160px;
  height: auto;
  display: block;
}
@media (max-width: 640px) {
  .min-h-screen .bg-warm.font-sans > .bg-accent {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-lg);
  }
}
