/* ==========================================================================
   Site layer

   Everything the design system bundle does not cover: the page shell, the
   fixed header, the mobile nav sheet, every responsive rule, the footer and
   the CV page.

   Kept separate on purpose. bundle.css is the design system's file and
   should stay byte-identical to it so a newer version can be dropped in;
   anything site-specific belongs here, which loads after it and wins on
   source order alone without needing !important or raised specificity.

   The bundle sizes type in fixed pixels (76px display, 56px h1, 40px h2)
   and lays every section out as a desktop grid. Both need scaling down,
   which is most of what follows.
   ========================================================================== */

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

html{
  scroll-behavior:smooth;
  scroll-padding-top:96px;   /* clears the fixed header on anchor jumps */
  -webkit-text-size-adjust:100%;
}

body{
  font-family:var(--font-sans);
  font-size:17px;
  line-height:28px;
  background:var(--paper);
  color:var(--ink);
  overflow-x:hidden;
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
}

img,svg{display:block;max-width:100%;height:auto}
a{color:inherit}
button,input,textarea{font:inherit;color:inherit}

/* The measure for every band. The bundle gives sections their padding but
   no maximum width, so content would run to the edge of a wide display. */
.dm-inner{width:100%;max-width:1200px;margin-inline:auto}

.sr-only{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;
}

.skip{
  position:absolute;left:-9999px;top:0;z-index:200;
  padding:12px 20px;background:var(--navy-900);color:var(--ink-inverse);
  border-radius:0 0 var(--radius-sm) 0;font-weight:500;text-decoration:none;
}
.skip:focus{left:0}

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

/* --------------------------------------------------------------------------
   Header

   The bundle styles .dm-header as a static bar. Here it is fixed, and gains
   its shadow and blur only once the page has scrolled, per the SiteHeader
   notes. Nothing else about it changes on scroll.
   -------------------------------------------------------------------------- */

.dm-header{
  position:fixed;
  inset:0 0 auto 0;
  z-index:100;
  padding:0;
  /* Opaque by default. The SiteHeader notes say it sits on paper and gains
     shadow and blur once scrolled, and .is-stuck is added by nav.js. A
     transparent default means that whenever the JavaScript has not run, for
     any reason, the fixed bar sits unreadable on top of body copy. The page
     should degrade to plain and correct, never to broken. */
  background:var(--paper);
  transition:background-color .2s ease,box-shadow .2s ease;
}
.dm-header.is-stuck{
  background:rgba(255,255,255,.85);
  backdrop-filter:blur(14px) saturate(140%);
  -webkit-backdrop-filter:blur(14px) saturate(140%);
  box-shadow:var(--shadow-sm);
}

.dm-header-in{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:24px;
  width:100%;
  max-width:1200px;
  margin-inline:auto;
  padding:18px 48px;
}

/* Wordmark with the headshot beside it. The design system's .dm-avatar is a
   64px circle for the contact band; this is a small rounded square, which
   reads as a mark rather than a profile picture at header size. The image
   carries an empty alt, because the wordmark next to it already says the
   name and a screen reader should not hear it twice. */
.dm-header .dm-wordmark{display:inline-flex;align-items:center;gap:11px}

.hdr-avatar{
  width:34px;height:34px;
  flex:none;
  border-radius:var(--radius-sm);
  object-fit:cover;
  background:var(--mist-100);
}

.hdr-actions{display:flex;align-items:center;gap:10px}

/* CV sits in the nav sheet on small screens, where the outline button is
   hidden, so the page is never unreachable. */
.nav-mobile-only{display:none}

.menu-btn{
  display:none;
  width:40px;height:40px;
  background:var(--mist-100);
  border:0;border-radius:var(--radius-sm);
  color:var(--ink);
  cursor:pointer;
  place-items:center;
}
.menu-btn svg{width:18px;height:18px}

/* --------------------------------------------------------------------------
   Sections
   -------------------------------------------------------------------------- */

.dm-hero{padding:120px 48px 80px}
.dm-hero-copy{max-width:920px}
.dm-section{padding:var(--space-32) 48px}
.dm-section-tight{padding-block:var(--space-24)}

/* --------------------------------------------------------------------------
   Stats

   The bundle sets a 1.4fr 1fr 1fr grid. The lead number is 96px, so on
   anything under a laptop the three columns stop fitting well before the
   section padding does.
   -------------------------------------------------------------------------- */

.dm-stats-band{padding-top:0;padding-bottom:var(--space-32)}

/* The bundle bottom-aligns each stat as a whole block, which lines up the
   last line of body copy rather than the figures. The figures are what the
   eye actually compares, so the three of them share one grid row sized to
   the largest, and sit on a common baseline.

   The parent's `gap` is both row and column gap, so the row gap has to go
   to zero or it would open 48px between a number and its own sentence;
   the bundle's margin-top on the paragraph does that spacing instead.

   Scoped above the single-column breakpoint, where there is no shared row
   and each stat is simply stacked. */
@media (min-width:761px){
  .dm-stats{
    grid-template-rows:auto auto;
    align-items:stretch;
    row-gap:0;
    column-gap:48px;
  }
  .dm-stat{display:grid;grid-template-rows:subgrid;grid-row:span 2}
  .dm-stat .num{align-self:baseline}
}

/* The hero's two parts enter 120ms apart. Kept as a class because the page
   carries no inline styles. */
.reveal-delay{animation-delay:.12s}

/* --------------------------------------------------------------------------
   Case studies
   -------------------------------------------------------------------------- */

/* The bundle crops the screenshot at the panel's bottom edge so it reads as
   a peek rather than a framed picture. That only works while the panel is
   tall enough to crop something, which the aspect-ratio handles. */
.dm-case-media.tall img.shot{top:40px;width:62%}

/* The CaseStudy notes allow navy-900 for the media panel "for variety", and
   two identical mist-100 panels in a row is exactly what that is for. Navy
   also suits a screenshot that is mostly white, which mist-100 does not. */
.dm-case-media.navy{background:var(--navy-900)}

/* The CaseStudy notes give the foot of a case one terminal action: a dm-link
   when the write-up exists, or the dm-soon note when it does not. Both run
   here, because the live product is worth reaching before the write-up
   lands, so they share a row with the link leading. */
.dm-case-actions{
  display:flex;
  align-items:center;
  gap:var(--space-6);
  flex-wrap:wrap;
}

/* --------------------------------------------------------------------------
   Footer

   The contact band is an inset dark panel; the footer is full bleed navy
   directly beneath it, so the page ends dark.
   -------------------------------------------------------------------------- */

.dm-cta-wrap{padding:var(--space-32) 48px;background:var(--paper)}

.site-foot{padding:var(--space-16) 48px var(--space-12)}

.foot-grid{
  display:grid;
  grid-template-columns:1.6fr repeat(3,minmax(0,1fr));
  gap:48px;
}

.site-foot .dm-wordmark{color:var(--ink-inverse)}
.foot-about{font-size:15px;line-height:24px;color:var(--ink-inverse-muted);margin-top:14px;max-width:32ch}

.foot-col h2{
  font:500 13px/20px var(--font-mono);
  color:var(--ink-inverse-muted);
  margin-bottom:16px;
}
.foot-col ul{list-style:none;display:grid;gap:11px}
.foot-col a{
  font-size:15px;
  color:var(--ink-inverse);
  text-decoration:none;
  opacity:.82;
  transition:opacity .18s ease;
}
.foot-col a:hover{opacity:1}

.foot-base{
  display:flex;
  justify-content:space-between;
  align-items:center;
  gap:16px;
  flex-wrap:wrap;
  margin-top:var(--space-12);
  padding-top:var(--space-6);
  box-shadow:inset 0 1px 0 rgba(169,180,208,.22);
  font-size:14px;
  color:var(--ink-inverse-muted);
}

/* --------------------------------------------------------------------------
   404

   The page has no hero, so the dark panel is the whole of it. Centring the
   main area keeps the footer at the bottom of the viewport instead of
   halfway up a tall screen.
   -------------------------------------------------------------------------- */

.nf-main{display:flex;flex-direction:column;justify-content:center;min-height:66vh}
.nf-main .dm-contact{margin-top:0}

/* The bundle resets every paragraph to margin:0, which is right where a CTA
   carries one paragraph, as the contact panel does. Two in a row then run
   together into what looks like one badly broken paragraph, so give the
   second one a gap. Same rule, same reason, as .cv-row above. */
.dm-cta .dm-body + .dm-body{margin-top:14px}

/* A featured approach row sits on navy-900. The bundle colours .dm-h3 and
   .dm-body for paper and only lifts them back to the inverse tokens inside
   .dm-cta, so on this row the heading renders navy on navy and vanishes
   while the body sits at about 1.4:1. .dm-dark alone does not fix it: it
   sets an inherited colour, and both of those rules set colour directly.
   Scoped here rather than touched in the bundle. */
/* The row's 48px grid gap is sized for its two-column form. Stacked to one
   column it reads as a hole between the heading and its own paragraph. */
.dm-approach-row.featured{gap:18px}
.dm-approach-row.featured .dm-h3{color:var(--ink-inverse)}
.dm-approach-row.featured .dm-body{color:var(--ink-inverse-muted)}
.dm-approach-row.featured .dm-points li{color:var(--ink-inverse)}

/* Stat cards, case study pages only.
   The home page sets its three figures loose on paper, which works because
   the Overlap chart sits directly above them and already breaks the page up.
   A case study has nothing but prose either side, so the same figures get
   the treatment the quotes use: paper cards on mist-50, one shadow, no
   border. The baseline subgrid above still applies, so the figures line up
   across the cards rather than each sitting wherever its own card puts it. */
/* Hero watermark, case study pages.

   Geometry, because rotation makes this non-obvious. The artwork is 565x484
   of ink centred on a 600x600 transparent canvas, so at a rendered width W
   the ink is 0.94W by 0.81W. Rotated 30 degrees about the centre, its
   bounding box becomes 1.22W wide and 1.17W tall, which means the ink pokes
   out roughly 0.11W past each side of the element's own box. The right
   offset below is that overhang plus the gap wanted from the content column,
   so the ink lands just inside the column edge instead of poking past it.
   Set it to zero and the artwork hangs 48px further right than it looks like
   it should. Change the width and that offset changes with it.

   overflow:hidden on the hero stays as a backstop: nothing should reach it
   at these numbers, but a rotated absolute element that escapes widens the
   document and gives every page a horizontal scrollbar.

   Behind the copy via z-index, and out of hit testing, so it can never
   swallow a click meant for the text. */
.dm-hero.has-mark{position:relative;overflow:hidden}
.dm-hero.has-mark .dm-inner{position:relative}
.dm-hero.has-mark .dm-hero-copy{position:relative;z-index:1}
.hero-mark{
  position:absolute;
  z-index:0;
  top:50%;
  right:72px;           /* 48px of rotation overhang, plus a 24px gap */
  width:440px;
  height:auto;
  transform:translateY(-50%) rotate(-30deg);
  transform-origin:center;
  opacity:.1;
  pointer-events:none;
  user-select:none;
}

/* The TradeSoft mark is landscape, not square, and its SVG is trimmed to the
   ink, so the overhang above has to be recalculated rather than reused.
   At 500px wide the artwork is 500 by 381. Rotated 30 degrees its bounding
   box is 500cos30 + 381sin30 = 624px wide, so the ink reaches 62px past each
   side of its own box. The right offset is that 62px plus the same 24px gap.
   Change the width and this offset changes with it. */
.hero-mark.wide{
  width:500px;
  right:86px;
}

/* Below 1100px the hero copy reaches across the full width and the mark
   would sit under the lead paragraph. It is decoration: drop it rather than
   have it compete with body text for contrast. */
@media (max-width:1100px){
  .hero-mark{display:none}
}

.dm-stats-band.cards{background:var(--mist-50);padding-top:var(--space-16);padding-bottom:var(--space-16)}
.dm-stats.cards .dm-stat{background:var(--paper);border-radius:var(--radius-lg);padding:28px;box-shadow:var(--shadow-md)}
@media (max-width:760px){
  .dm-stats.cards{gap:16px}
}

/* Between 761px, where the single-column rule lets go, and roughly 900px,
   the three cards still share a row and each gets a 1fr track, and a wide
   figure can be wider than its own track. Those tracks have 48px column
   gaps and 28px of card padding taken out of them first, so on the
   TradeSoft band the row cannot compress below 875px: at any viewport
   narrower than that it pushes straight past the edge and gives the whole
   document a horizontal scrollbar. ThriveCart's band escapes only because
   200+ happens to be a narrow number, so this was latent there too.
   Tightening the gap, the padding and the figures through this window is
   enough, and the layout stays three across instead of reflowing. */
@media (min-width:761px) and (max-width:900px){
  .dm-stats.cards{column-gap:24px}
  .dm-stats.cards .dm-stat{padding:20px}
  .dm-stats.cards .dm-stat .num{font-size:44px}
  .dm-stats.cards .dm-stat.lead .num{font-size:68px}
}

/* --------------------------------------------------------------------------
   Form messages

   The design system covers fields and errors but not the status line the
   contact handler replies with.
   -------------------------------------------------------------------------- */

.form-msg{font-size:15px;line-height:23px;display:none}
.form-msg.show{display:block}
.form-msg.is-warn,
.form-msg.is-ok,
.form-msg.is-info{display:flex;gap:9px;align-items:baseline}
.form-msg::before{content:"";flex:none;width:7px;height:7px;border-radius:50%;margin-top:8px}
.form-msg.is-warn{color:var(--ink-inverse)}
.form-msg.is-warn::before{background:var(--ember-500)}
.form-msg.is-ok{color:var(--ink-inverse)}
.form-msg.is-ok::before{background:var(--signal-green)}
.form-msg.is-info{color:var(--ink-inverse-muted)}
.form-msg.is-info::before{background:var(--ink-inverse-muted)}

/* The bundle allows the message box to be dragged taller. The grip sits in
   the field's bottom-right corner, inside the rounded dark panel, where it
   reads as a rendering artefact rather than a control. The field is already
   tall enough for a first message, so it is fixed. */
textarea.dm-input{resize:none}

/* The honeypot. Off-screen rather than display:none, because some bots skip
   fields that are not rendered at all. Name and behaviour must stay in step
   with contact.php. */
.hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}

/* Name and email share a row. The design system preview did this with an
   inline style; the site keeps styling out of the markup. */
.dm-form-pair{display:grid;grid-template-columns:1fr 1fr;gap:16px}

/* --------------------------------------------------------------------------
   Reveal

   The bundle's .dm-reveal runs its fade as soon as the class is present,
   which is right for the hero and wrong for everything below the fold. The
   hidden state is scoped to .js-reveal, a class reveal.js puts on <html>,
   so with JavaScript off or broken nothing is left invisible.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion:no-preference){
  .js-reveal [data-reveal]{opacity:0}
  .js-reveal [data-reveal].dm-reveal{opacity:1}
}

/* ==========================================================================
   CV page

   A document rather than a landing page: one column of prose, label left and
   content right, and a print stylesheet so saving to PDF produces something
   worth sending. It uses the same tokens, so it stays part of the site.
   ========================================================================== */

.cv-head{padding:132px 48px var(--space-12)}

.cv-head-in{
  display:flex;
  align-items:center;
  gap:20px;
  margin-bottom:var(--space-8);
}
.cv-head .dm-avatar{width:72px;height:72px}
.cv-head-id b{display:block;font-weight:600;font-size:17px}
.cv-head-id span{font-size:15px;color:var(--ink-subtle)}

.cv-body{padding:0 48px var(--space-32)}

.cv-row{
  display:grid;
  grid-template-columns:200px minmax(0,1fr);
  gap:56px;
  padding-block:var(--space-8);
}
.cv-row + .cv-row{box-shadow:inset 0 1px 0 var(--mist-100)}

.cv-label{
  font:500 13px/20px var(--font-mono);
  color:var(--ink-subtle);
  padding-top:4px;
}

.cv-row .dm-body + .dm-body{margin-top:14px}

.cv-role + .cv-role{margin-top:var(--space-8)}
.cv-role .dm-role{font-size:20px}
.cv-meta{font:400 13px/20px var(--font-mono);color:var(--ink-subtle);margin:5px 0 12px}
.cv-meta b{color:var(--ink);font-weight:500}

.cv-points{list-style:none;display:grid;gap:8px;margin-top:12px}
.cv-points li{position:relative;padding-left:20px;font-size:16px;line-height:26px;color:var(--ink-muted)}
.cv-points li::before{
  content:"";position:absolute;left:0;top:11px;
  width:6px;height:6px;border-radius:50%;background:var(--mist-300);
}

.cv-cols{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-8) 48px}
.cv-cols h3{font:600 15px/22px var(--font-sans);color:var(--ink);margin-bottom:12px}
.cv-cols ul{list-style:none;display:grid;gap:7px}
.cv-cols li{font-size:15px;line-height:23px;color:var(--ink-muted)}

.cv-actions{display:flex;gap:12px;flex-wrap:wrap;margin-top:var(--space-12)}

/* --------------------------------------------------------------------------
   Print

   Saving to PDF is the most likely thing anyone does with the CV, so it gets
   a real stylesheet rather than whatever the browser decides. Site chrome
   goes, colour drops to ink on white, links print their destination, and
   roles are kept off page breaks.
   -------------------------------------------------------------------------- */

@media print{
  @page{margin:15mm 14mm}

  .dm-header,.site-foot,.dm-cta-wrap,.cv-actions,.skip,.dm-avatar{display:none !important}

  html,body{background:#fff;color:#000;font-size:10.5pt;line-height:1.5}

  .cv-head{padding:0 0 14pt}
  .cv-body{padding:0}
  .cv-head-in{margin-bottom:10pt}

  .dm-display,.dm-h1,.dm-h2{font-size:24pt;line-height:1.1}
  .dm-role{font-size:12pt}

  /* break-inside:avoid only helps on something that fits a page. Experience
     and Technical are each taller than one, so asking the printer to keep
     them whole pushed all of Experience onto page two, left most of page one
     empty, and produced a blank page at the end. The rule now lives on the
     rows that genuinely are short (.keep) and on the individual roles, which
     are the units a reader actually minds seeing split. */
  .cv-row{
    grid-template-columns:96pt 1fr;
    gap:18pt;
    padding-block:10pt;
    box-shadow:none;
  }
  .cv-row.keep{break-inside:avoid}
  .cv-row + .cv-row{box-shadow:inset 0 1px 0 #ddd}

  /* A section label or a job title alone at the foot of a page is worse than
     an uneven page, and a single trailing line of a paragraph is worse than
     both. */
  .cv-label,.dm-role,.dm-h3{break-after:avoid}
  p,li{orphans:3;widows:3}

  .dm-body,.cv-points li,.cv-cols li{color:#222;font-size:10pt;line-height:1.45}
  .cv-label,.cv-meta{color:#555}

  .cv-role{break-inside:avoid}
  .cv-role + .cv-role{margin-top:10pt}

  .dm-link,a{color:#000;text-decoration:underline}
  .cv-row a[href^="http"]::after{content:" (" attr(href) ")";font-size:8pt;color:#555}

  [data-reveal],.dm-reveal{opacity:1 !important;animation:none !important}
}

/* ==========================================================================
   Responsive

   The bundle is written for a wide desktop. Everything below scales it down.
   ========================================================================== */

@media (max-width:1100px){
  .dm-display{font-size:60px}
  .dm-h1{font-size:44px}
  .dm-h2{font-size:34px}
  .dm-cta .dm-h2{font-size:38px}
  .dm-case .dm-h2{font-size:30px}

  .dm-section{padding:var(--space-24) 40px}
  .dm-hero{padding:112px 40px 68px}
  .dm-header-in{padding:16px 40px}
  .dm-cta-wrap{padding:var(--space-24) 40px}
  .site-foot{padding:var(--space-16) 40px var(--space-12)}
  .cv-head{padding:124px 40px var(--space-12)}
  .cv-body{padding:0 40px var(--space-24)}

  .dm-cta{padding:48px;gap:48px}
  .dm-toolkit{grid-template-columns:repeat(2,1fr);gap:40px 48px}
  .dm-stat.lead .num{font-size:76px}
  .dm-stat .num{font-size:46px}
  .dm-case{gap:48px}
  .dm-case + .dm-case{margin-top:88px}
  .dm-timeline li{gap:40px}
}

@media (max-width:900px){
  .dm-nav{
    position:absolute;
    top:100%;left:0;right:0;
    flex-direction:column;
    gap:0;
    padding:8px 40px 20px;
    background:rgba(255,255,255,.97);
    backdrop-filter:blur(14px);
    -webkit-backdrop-filter:blur(14px);
    box-shadow:var(--shadow-md);
    opacity:0;visibility:hidden;transform:translateY(-8px);
    transition:opacity .2s ease,transform .2s ease,visibility .2s ease;
  }
  .dm-nav.open{opacity:1;visibility:visible;transform:none}
  .dm-nav a{padding:13px 0;background-position:left bottom}
  /* The bundle's header is space-between, which is right with a nav in the
     middle. Once the nav moves into the sheet, the remaining items are the
     wordmark and a right-hand group, so the wordmark claims the free space
     and the trigger sits beside the button rather than stranded between
     them. */
  .dm-header-in{justify-content:flex-start;gap:12px}
  .dm-header .dm-wordmark{margin-right:auto}
  .menu-btn{display:grid;order:2;margin-left:0}
  .hdr-actions{order:3}
  .dm-header .dm-btn-outline{display:none}
  .nav-mobile-only.for-cv{display:block}

  /* Section heads, cases, quotes and the contact band all stop working as
     two columns before this width. */
  .dm-section-head{grid-template-columns:1fr;gap:20px;margin-bottom:var(--space-12)}
  .dm-approach-row{grid-template-columns:1fr;gap:12px;padding:28px 0}
  .dm-case,
  .dm-case.flip{grid-template-columns:1fr;gap:32px}
  .dm-case.flip .dm-case-media{order:0}
  .dm-case + .dm-case{margin-top:72px}
  .dm-quotes{grid-template-columns:1fr;gap:40px}
  .dm-cta{grid-template-columns:1fr;gap:40px;padding:40px}
  .foot-grid{grid-template-columns:1fr 1fr;gap:36px}
  .foot-grid .foot-brand{grid-column:1 / -1}

  .cv-row{grid-template-columns:1fr;gap:10px}
  .cv-label{padding-top:0}

  /* Timeline: the rail moves to the left edge and the dates sit above the
     role, so the 176px date column is no longer holding the layout open. */
  .dm-timeline::before{left:5px}
  .dm-timeline li{grid-template-columns:1fr;gap:6px;padding-left:32px;padding-bottom:36px}
  .dm-timeline li::after{left:0}
  .dm-when{text-align:left}
  .dm-when span{display:inline;margin-left:8px}
  .dm-timeline li.past .dm-body{-webkit-line-clamp:4}
}

@media (max-width:760px){
  .dm-display{font-size:40px;letter-spacing:-.028em}
  .dm-h1{font-size:32px}
  .dm-h2{font-size:27px}
  .dm-h3,.dm-approach-row .dm-h3{font-size:21px}
  .dm-cta .dm-h2{font-size:30px}
  .dm-case .dm-h2{font-size:25px}
  .dm-lead{font-size:18px;line-height:29px}
  .dm-quote-lead p{font-size:23px}
  .dm-quote-lead p::before{font-size:68px}

  .dm-section{padding:var(--space-16) var(--space-4)}
  .dm-hero{padding:104px var(--space-4) var(--space-16)}
  .dm-header-in{padding:14px var(--space-4)}
  .dm-nav{padding:8px var(--space-4) 18px}
  .dm-cta-wrap{padding:var(--space-16) var(--space-4)}
  .site-foot{padding:var(--space-12) var(--space-4) var(--space-8)}
  .cv-head{padding:104px var(--space-4) var(--space-8)}
  .cv-body{padding:0 var(--space-4) var(--space-16)}

  .dm-stats{grid-template-columns:1fr;gap:var(--space-8)}
  .dm-stat.lead .num{font-size:64px}
  .dm-stat .num{font-size:42px}
  .dm-stat p{max-width:38ch}

  .dm-cta{padding:28px;gap:32px;border-radius:var(--radius-lg)}
  .dm-form{padding:22px}
  .dm-form-pair{grid-template-columns:1fr}

  .dm-toolkit{grid-template-columns:1fr;gap:var(--space-8)}
  .dm-case-media{aspect-ratio:4 / 3}
  .dm-case-stats{gap:20px;flex-wrap:wrap}
  .dm-case-stats div:first-child b{font-size:36px}
  .dm-case-stats b{font-size:26px}

  .foot-grid{grid-template-columns:1fr;gap:28px}
  .cv-cols{grid-template-columns:1fr;gap:var(--space-6)}

  /* Overlap on a phone.

     The bundle's track grid is 148px / 1fr / 112px. The label and the axis
     survive a narrow screen; the right-hand time column does not, so it is
     dropped here. No information is lost: the sentence above the chart
     already gives the time in New Zealand, San Francisco and New York,
     which is the part a reader actually needs. */
  .dm-overlap{padding:20px}
  .dm-overlap-top{flex-direction:column;gap:14px;margin-bottom:22px}
  .dm-overlap-now{font-size:19px}
  .dm-tracks,
  .dm-axis{grid-template-columns:92px 1fr}
  .dm-tracks .t{display:none}
  .dm-tracks .lbl{font-size:13px;padding-right:10px}
  .dm-axis div:last-child{display:none}
  .dm-band-label{font-size:11px}
}

@media (max-width:420px){
  .dm-display{font-size:34px}
  .dm-actions{gap:10px}
  .dm-actions .dm-btn{width:100%;justify-content:center}
  .dm-tracks,
  .dm-axis{grid-template-columns:76px 1fr}

  /* Below this the wordmark, the sheet trigger and the button stop fitting
     on one line, so the button goes and the sheet carries the action. */
  .dm-header .dm-btn{display:none}
  .hdr-actions{display:none}
  .nav-mobile-only.for-contact{display:block}
  .hdr-avatar{width:30px;height:30px}
}

/* The engagement facts under the hero overlap. They reuse .dm-points rather
   than inventing a component, but the bundle sizes that row for a capability
   list inside an approach row: 15px in full ink, which next to the 14px
   subtle note above would make the paperwork louder than the sentence it
   qualifies. Matched to the note instead. */
.dm-hire-facts{margin-top:12px}
.dm-hire-facts li{font-size:14px;color:var(--ink-muted)}

/* --------------------------------------------------------------------------
   App screenshots, TradeSoft case study
   -------------------------------------------------------------------------- */

/* The source captures are 1206x2622 iOS exports, served at 480 wide so every
   slot on this page is covered at roughly 2x. They are square-cornered, so
   the radius here is what makes one read as a phone rather than a poster.
   It is given as a horizontal/vertical pair of percentages because the
   images appear at three different widths: a single px radius that looks
   right in the gallery looks like a pillow in the trio. 8% of the width and
   3.7% of the height are the same number of real pixels at every size.

   The shadow is the one the case study media panels already use, so a screen
   in the body and a screen in a card read as the same kind of object. */
.ts-shot{
  display:block;
  width:100%;
  height:auto;
  border-radius:8% / 3.7%;
  box-shadow:0 2px 4px rgba(0,19,61,.06),0 18px 40px rgba(0,19,61,.18);
}

/* Screens that sit inside an approach row are evidence for the paragraph
   above them, so they stay inside the text column rather than breaking out
   of it. The gallery at the foot of the page is where the product gets to
   be looked at instead of cited. */
.ts-trio,.ts-pair{display:grid;gap:16px;margin-top:22px}
.ts-trio{grid-template-columns:repeat(3,1fr)}
.ts-pair{grid-template-columns:repeat(2,1fr);max-width:420px}
.ts-single{margin-top:22px;max-width:220px}

.ts-trio figure,.ts-pair figure,.ts-single figure{margin:0}
.ts-cap{
  margin-top:10px;
  font:400 12px/1.4 var(--font-mono);
  letter-spacing:.02em;
  color:var(--ink-subtle);
}
/* Inside a navy row the caption needs the inverse muted ink, since the
   default is tuned for paper. */
.dm-approach-row.featured .ts-cap{color:var(--ink-inverse-muted)}

/* The gallery. Four across on a wide screen, and below 900px it turns into
   a sideways scroller with snap points rather than four thumbnails nobody
   can see: a phone screenshot shrunk to 90px is not a screenshot.

   It sits on paper rather than mist, because the clubs section above it is
   already mist and two tinted sections running together lose the break. */
.ts-gallery{
  display:grid;
  grid-template-columns:repeat(4,1fr);
  column-gap:28px;
  /* Two rows, so the row gap has to clear a caption as well as the space
     between images. 28px would park the next row's phone right under the
     previous row's label. */
  row-gap:48px;
  margin-top:var(--space-10);
}
.ts-gallery figure{margin:0}
.ts-gallery .ts-cap{font-size:13px}

@media (max-width:900px){
  .ts-gallery{
    grid-template-columns:none;
    grid-auto-flow:column;
    grid-auto-columns:220px;
    overflow-x:auto;
    scroll-snap-type:x mandatory;
    padding-bottom:10px;
  }
  .ts-gallery figure{scroll-snap-align:start}
}

@media (max-width:560px){
  .ts-trio{grid-template-columns:repeat(2,1fr)}
  .ts-trio figure:last-child{grid-column:1 / -1;max-width:calc(50% - 8px)}
}
