/* =====================================================================
   Matthew Klembara | internal tools and websites
   Achromatic on purpose: the only saturated colour on the page comes
   from the client screenshots.
   ===================================================================== */

/* Self-hosted so there is no third-party request on load.
   Both are subset to Latin; Google Sans Flex is instanced narrow
   (wdth 84, opsz 10) so the small tracked labels stay tight without
   resorting to a monospace. */
@font-face{
  font-family:"Lexend Deca";
  src:url("assets/fonts/lexend-deca.woff2") format("woff2-variations"),
      url("assets/fonts/lexend-deca.woff2") format("woff2");
  font-weight:300 800; font-style:normal; font-display:swap;
}
/* The body face. Same family as the utility face but a different instance:
   normal width (wdth 100) and opsz 16, which is the size running copy is set
   at. Weight stays live from 400 to 700. */
@font-face{
  font-family:"Google Sans Flex";
  src:url("assets/fonts/google-sans-flex.woff2") format("woff2-variations"),
      url("assets/fonts/google-sans-flex.woff2") format("woff2");
  font-weight:400 700; font-style:normal; font-display:swap;
}
@font-face{
  font-family:"GS Flex Narrow";
  src:url("assets/fonts/google-sans-flex-narrow.woff2") format("woff2-variations"),
      url("assets/fonts/google-sans-flex-narrow.woff2") format("woff2");
  font-weight:400 700; font-style:normal; font-display:swap;
}

:root{
  --ground:      #E9EAE5;
  --ground-2:    #DFE1DA;
  --panel:       #FBFBF9;
  --ink:         #16181A;
  --ink-soft:    #55595A;
  /* Darkened from #8B8F8B, which measured 2.49:1 on the closer ground and
     failed AA on all three grounds it sits on. This is the lightest value that
     clears 4.5:1 everywhere, so the small tracked labels stay quiet without
     being unreadable. */
  --ink-faint:   #5F635F;
  --rule:        #C6C9C0;
  --rule-soft:   #D6D8D1;
  --invert-bg:   #16181A;
  --invert-fg:   #EFEFEB;
  --invert-soft: #9CA09D;
  --invert-rule: #33383A;

  /* Three faces, two families. Lexend Deca takes the headings and the
     wordmark. Google Sans Flex takes everything you read at length, and its
     narrow instance takes the small tracked labels, where the condensed width
     does the job a monospace used to without the typewriter voice. */
  --display: "Lexend Deca", "Segoe UI", system-ui, sans-serif;
  --body:    "Google Sans Flex", "Segoe UI", system-ui, sans-serif;
  --util:    "GS Flex Narrow", "Segoe UI", system-ui, sans-serif;

  /* Radius and elevation come off the hero glass (26px shell, 24px pills),
     so every other surface on the page is a member of the same family. */
  --r-xl: 26px;
  --r-lg: 18px;
  --r-pill: 999px;
  --lift:  0 6px 18px rgba(22,24,26,.06), 0 1px 3px rgba(22,24,26,.05);
  --lift-2:0 0 0 1px rgba(22,24,26,.07), 0 10px 26px rgba(22,24,26,.08);
  --lift-dark:0 0 0 1px rgba(255,255,255,.09), 0 12px 30px rgba(0,0,0,.34);

  /* The lede and the notice text share one measure, so the card's copy lines
     up with the paragraph above it instead of nearly lining up. */
  --measure: 33rem;
  --notice-pad: 22px;

  --gut: clamp(20px, 4vw, 56px);
  --maxw: 1240px;
  --sect: clamp(56px, 10vw, 152px);
}

*,*::before,*::after{ box-sizing:border-box; }

html{ -webkit-text-size-adjust:100%; scroll-behavior:smooth; }
/* The top bar is sticky, so anything jumped to needs to clear it. */
section[id], article[id], main[id]{ scroll-margin-top:84px; }
@media (prefers-reduced-motion: reduce){
  html{ scroll-behavior:auto; }
  *,*::before,*::after{ animation-duration:.01ms !important; transition-duration:.01ms !important; }
}

body{
  margin:0;
  background:var(--ground);
  color:var(--ink);
  font-family:var(--body);
  font-size:clamp(1rem, .96rem + .18vw, 1.075rem);
  line-height:1.62;
  -webkit-font-smoothing:antialiased;
  font-synthesis:none;
}

h1,h2,h3{ margin:0; font-family:var(--display); font-weight:700; letter-spacing:-.026em; line-height:1.04; }
p{ margin:0; }
a{ color:inherit; }

:focus-visible{ outline:2px solid var(--ink); outline-offset:3px; }

.skip{
  position:absolute; left:-9999px; top:0; z-index:99;
  background:var(--ink); color:var(--invert-fg);
  padding:14px 18px; border-radius:var(--r-pill);
  font-family:var(--util); font-size:.72rem;
}
.skip:focus{ left:0; }

.wrap{ width:100%; max-width:var(--maxw); margin-inline:auto; padding-inline:var(--gut); }

/* ---------- shared bits ---------- */

.eyebrow{
  font-family:var(--util); font-weight:600;
  font-size:.63rem; letter-spacing:.2em; text-transform:uppercase;
  color:var(--ink-faint); margin-bottom:20px;
}

/* ---------- top bar ---------- */

.topbar{
  position:sticky; top:0; z-index:40;
  background:color-mix(in srgb, var(--ground) 88%, transparent);
  backdrop-filter:blur(10px);
  border-bottom:1px solid var(--rule);
}
.topbar-in{ display:flex; align-items:center; justify-content:space-between; gap:24px; min-height:70px; }

.mark{ text-decoration:none; display:flex; flex-direction:column; gap:3px; }
.mark-name{ font-family:var(--display); font-weight:700; font-size:1.01rem; letter-spacing:-.028em; }
.mark-role{
  font-family:var(--util); font-size:.58rem; letter-spacing:.16em;
  text-transform:uppercase; color:var(--ink-faint); white-space:nowrap;
}

.nav{ display:flex; align-items:center; gap:clamp(14px,2.4vw,32px); }
.nav a{
  text-decoration:none; font-family:var(--util); font-size:.68rem;
  letter-spacing:.09em; text-transform:uppercase; color:var(--ink-soft);
  padding-block:6px; border-bottom:1px solid transparent;
}
.nav a:hover{ color:var(--ink); border-bottom-color:var(--ink); }
.nav .nav-cta{
  background:var(--ink); color:var(--invert-fg);
  padding:14px 20px; border-bottom:none;
  border-radius:var(--r-pill); white-space:nowrap;
  transition:background .18s ease;
}
.nav .nav-cta:hover{ background:var(--ink-soft); color:var(--invert-fg); border-bottom-color:transparent; }

@media (max-width: 400px){
  .topbar-in{ gap:12px; }
  .nav .nav-cta{ padding:14px 15px; }   /* keep the 44px touch target on small phones */
}
@media (max-width: 340px){
  .mark-role{ display:none; }
}

@media (max-width: 860px){
  .nav a:not(.nav-cta){ display:none; }
}

/* ---------- hero ---------- */

.hero{
  position:relative; isolation:isolate; overflow:hidden;
  min-height:min(94vh, 920px);
  display:grid; align-items:center;
  padding-block:clamp(72px,11vh,132px);
  color:var(--ink);
}

/* Flat ground under the flow field, so the hero is never blank if the
   canvas fails or scripting is off. */
.hero-bg{
  position:absolute; inset:0; z-index:-3;
  background:#FAFAF8;
}
.hero-flow{
  position:absolute; inset:0; z-index:-2;
  display:block; width:100%; height:100%;
}
/* Settles the field behind the type and hands off to the page ground below. */
.hero-scrim{
  position:absolute; inset:0; z-index:-1;
  background:linear-gradient(180deg,
    rgba(250,250,248,.42) 0%, rgba(250,250,248,0) 40%, rgba(233,234,229,.92) 100%);
}

.hero-in{ position:relative; text-align:center; }
.hero :focus-visible{ outline:2px solid var(--ink); outline-offset:4px; }

.hero-eyebrow{
  font-family:var(--util); font-size:.6rem; letter-spacing:.22em;
  text-transform:uppercase; color:var(--ink-faint);
  margin-bottom:22px;
}
.hero h1{
  /* Capped at 4.85rem, not 5.75rem: "Customized apps or websites" needs the
     face at 81px or under to hold one line inside the 1128px measure, and a
     display line that wraps in the middle of itself reads as a mistake. */
  font-size:clamp(2.05rem,6.4vw,4.85rem);
  line-height:1.03; letter-spacing:-.036em; color:var(--ink);
  text-wrap:balance;
  margin-bottom:24px;
}
.hero-lede{
  font-size:clamp(1.05rem,.98rem + .42vw,1.35rem);
  line-height:1.55; color:var(--ink-soft);
  max-width:46ch; margin:0 auto clamp(38px,6vh,62px);
}

/* ---------- glass ---------- */

/* Glass on a light ground: white-on-white is invisible, so the read comes
   from the cast shadow and a hairline edge rather than a bright inner rim. */
.glass{
  position:relative; overflow:hidden; border-radius:26px;
  box-shadow:0 14px 30px rgba(22,24,26,.11),
             0 3px 8px rgba(22,24,26,.07);
  transition:transform .6s cubic-bezier(.175,.885,.32,1.6),
             box-shadow .4s ease;
}
.glass-warp{
  position:absolute; inset:0; z-index:0;
  -webkit-backdrop-filter:blur(3px); backdrop-filter:blur(3px);
  filter:url(#glass-distortion);
  isolation:isolate;
}
.glass-tint{
  position:absolute; inset:0; z-index:1;
  background:rgba(255,255,255,.52);
}
.glass-edge{
  position:absolute; inset:0; z-index:2;
  border-radius:inherit; pointer-events:none;
  box-shadow:inset 0 1px 1px 0 rgba(255,255,255,.95),
             inset 0 0 0 1px rgba(22,24,26,.07);
}
.glass-body{ position:relative; z-index:3; }
.glass-filter{ position:absolute; width:0; height:0; overflow:hidden; }

/* Without backdrop-filter the tint carries the panel on its own. */
@supports not ((backdrop-filter: blur(3px)) or (-webkit-backdrop-filter: blur(3px))){
  .glass-tint{ background:rgba(255,255,255,.86); }
}

/* ---------- the dock ---------- */

.dock{
  display:block; width:fit-content; margin-inline:auto;
  padding:12px; border-radius:28px;
}
.dock-row{ display:flex; align-items:center; gap:clamp(6px,1.4vw,12px); }
.dock-sep{
  width:1px; align-self:stretch; flex:0 0 1px;
  margin-inline:clamp(2px,.8vw,7px);
  background:rgba(22,24,26,.16);
}

.dock-item{
  display:flex; flex-direction:column; align-items:center; gap:9px;
  text-decoration:none; color:var(--ink);
}
/* Only the tile lifts. A label that moves is harder to read, not livelier. */
.dock-ico{
  display:block; width:clamp(44px,10.5vw,64px); height:auto;
  filter:drop-shadow(0 3px 6px rgba(22,24,26,.22));
  transition:transform .4s cubic-bezier(.175,.885,.32,1.8);
}
.dock-item:hover .dock-ico,
.dock-item:focus-visible .dock-ico{ transform:translateY(-7px) scale(1.07); }

.dock-name{
  font-family:var(--util); font-size:.58rem; font-weight:600;
  letter-spacing:.09em; text-transform:uppercase;
  color:var(--ink-faint); white-space:nowrap;
  transition:color .18s ease;
}
.dock-item:hover .dock-name,
.dock-item:focus-visible .dock-name{ color:var(--ink); }

/* Two service lines, one row. The connector sits between the buttons rather
   than inside one, so neither label has to begin with "or". */
.hero-cta{
  display:flex; flex-wrap:wrap;
  align-items:center; justify-content:center;
  gap:clamp(10px,1.6vw,18px);
  margin-top:clamp(26px,4vh,40px);
}
.hero-or{
  font-family:var(--util); font-size:.58rem; font-weight:600;
  letter-spacing:.16em; text-transform:uppercase;
  color:var(--ink-faint);
}

.glass-btn{
  /* Two one-word labels of different lengths would make a lopsided pair,
     so both pills sit on the same floor and the choice reads as a choice. */
  display:block; width:fit-content; min-width:186px; margin:0;
  padding:19px 30px; border-radius:24px;
  text-decoration:none; color:var(--ink);
  font-family:var(--util); font-size:.68rem;
  letter-spacing:.1em; text-transform:uppercase;
  text-align:center;
}
.glass-btn:hover{ transform:scale(1.03); }

@media (max-width: 640px){
  .h1-brk{ display:none; }
}

@media (max-width: 560px){
  .hero-cta{ flex-direction:column; gap:12px; }
  .glass-btn{ width:100%; min-width:0; padding-inline:20px; }
}

/* On a phone the row becomes three-up: apps on the first line, sample sites
   on the second, with the divider turning horizontal to keep them apart. */
@media (max-width: 620px){
  .dock-row{
    display:grid; grid-template-columns:repeat(3,1fr);
    gap:14px 10px; align-items:start;
  }
  .dock-sep{
    display:block; grid-column:1 / -1;
    width:auto; height:1px; flex:none; align-self:auto;
    margin:2px 0; background:rgba(22,24,26,.13);
  }
  .dock-name{ font-size:.53rem; letter-spacing:.06em; }
}

/* ---------- tiers ---------- */

.tier{ padding-block:var(--sect); border-top:1px solid var(--rule); }
.tier-dark{
  background:var(--invert-bg); color:var(--invert-fg); border-top:none;
}
.tier-dark .eyebrow{ color:var(--invert-soft); }
.tier-dark .tier-lede{ color:var(--invert-soft); }

/* Title left, the reading matter beside it, so the head spans the page
   not leave half of it empty. */
.tier-head{
  display:grid;
  grid-template-columns:minmax(0,.92fr) minmax(0,1.08fr);
  column-gap:clamp(32px,5vw,80px);
  align-items:start;
  margin-bottom:clamp(36px,6vw,88px);
}
.tier-head > .eyebrow{ grid-column:1; grid-row:1; }
.tier-head > .tier-title{ grid-column:1; grid-row:2; margin-bottom:0; }
.tier-head > .tier-lede{ grid-column:2; grid-row:2; padding-top:.35em; }
/* Full width under both columns. Parked in the right column this left a tall
   empty block beside the title, and a notice is a disclosure rather than
   reading matter, so a wide short strip is the honest shape for it. */
.tier-head > .notice{
  grid-column:1 / -1; grid-row:3; max-width:none;
  margin-top:clamp(26px,3vw,38px);
}
@media (max-width: 860px){
  .tier-head{ grid-template-columns:minmax(0,1fr); }
  .tier-head > .eyebrow,
  .tier-head > .tier-title,
  .tier-head > .tier-lede,
  .tier-head > .notice{ grid-column:1; grid-row:auto; }
  .tier-head > .tier-title{ margin-bottom:18px; }
  .tier-head > .tier-lede{ padding-top:0; }
}
.tier-title{
  font-size:clamp(2rem,4.6vw,3.55rem);
  letter-spacing:-.032em; margin-bottom:20px;
}
.tier-lede{
  color:var(--ink-soft); font-size:1.08rem;
  max-width:var(--measure); text-wrap:balance;
}

.notice{
  margin-top:26px; padding:18px var(--notice-pad);
  border-radius:var(--r-lg); background:var(--panel);
  box-shadow:var(--lift);
  font-size:.84rem; line-height:1.58; color:var(--ink-soft);
  max-width:calc(var(--measure) + var(--notice-pad) * 2);
  text-wrap:pretty;
}
/* On the dark tier a cast shadow reads as nothing, so the card lifts off the
   ground with a wash instead. */
.notice-invert{
  background:rgba(255,255,255,.05); box-shadow:none; color:var(--invert-soft);
}
.notice-tag{
  display:inline-block; margin-right:10px;
  font-family:var(--util); font-size:.58rem; font-weight:600;
  letter-spacing:.14em; text-transform:uppercase;
  color:var(--ink); background:rgba(22,24,26,.08);
  border-radius:var(--r-pill); padding:4px 11px;
  vertical-align:1px;
}
.notice-invert .notice-tag{ color:var(--invert-fg); background:rgba(255,255,255,.14); }

/* ---------- case files ---------- */

.case{
  display:grid; gap:clamp(28px,4vw,60px);
  grid-template-columns:minmax(0,.82fr) minmax(0,1.18fr);
  align-items:start;
  padding-block:clamp(32px,5vw,72px);
  border-top:1px solid var(--rule);
}
.case-flip .case-meta{ order:2; }
.case-flip .case-fig{ order:1; }
/* The flip swaps which element sits left, so the tracks have to swap with it.
   Without this the screenshot inherits the narrow column and bottoms out well
   above the copy beside it. */
.case-flip{ grid-template-columns:minmax(0,1.18fr) minmax(0,.82fr); }
@media (max-width: 940px){
  .case{ grid-template-columns:minmax(0,1fr); }
  .case-flip .case-meta{ order:0; }
  .case-flip .case-fig{ order:0; }
}

.case-client{
  font-family:var(--util); font-size:.6rem; letter-spacing:.16em;
  text-transform:uppercase; color:var(--ink-faint); margin-bottom:14px;
}
.case-name{
  font-size:clamp(1.4rem,2.3vw,1.92rem);
  letter-spacing:-.03em; line-height:1.12; margin-bottom:26px;
}

.spec{ margin:0 0 26px; border-top:1px solid var(--rule-soft); }
.spec > div{
  display:grid; grid-template-columns:9.5rem minmax(0,1fr);
  gap:14px; padding-block:11px;
  border-bottom:1px solid var(--rule-soft);
}
.spec dt{
  font-family:var(--util); font-size:.58rem; letter-spacing:.14em;
  text-transform:uppercase; color:var(--ink-faint); padding-top:3px;
}
.spec dd{ margin:0; font-size:.92rem; line-height:1.5; }
@media (max-width: 520px){
  .spec > div{ grid-template-columns:minmax(0,1fr); gap:2px; padding-block:8px; }
}

/* Vertical budget on phones: the page has to stay under the height cap. */
@media (max-width: 620px){
  .case-body{ font-size:.94rem; line-height:1.58; }
  .case-name{ margin-bottom:18px; }
  .spec{ margin-bottom:18px; }
  .steps li{ padding-block:24px; }
}

.case-body{ color:var(--ink-soft); font-size:.98rem; line-height:1.66; max-width:46ch; }
/* p has no default margin here, so stacked paragraphs need their own gap. */
.case-body + .case-body{ margin-top:1.05em; }

/* A landscape screenshot can never stand as tall as a column of prose. On a
   case that carries this much of both, the spec sheet and the prose sit side
   by side and the screenshot runs the full width underneath, where it is big
   enough to actually read. One continuous column of copy, not three. */
.case-stack{ grid-template-columns:minmax(0,.88fr) minmax(0,1.12fr); }
.case-prose{ min-width:0; }
.case-prose .case-body + .case-body{ margin-top:1.05em; }
/* The tall record sits in the right track beside the copy; the wide board runs
   the full width underneath, where it is the largest thing in the case. */
.case-stack .case-fig-wide{
  grid-column:1 / -1;
  margin-top:clamp(26px,3.2vw,48px);
}
@media (max-width: 940px){
  .case-stack{ grid-template-columns:minmax(0,1fr); }
}

.case-fig{ margin:0; }
.case-fig figcaption{
  margin-top:14px; font-size:.8rem; line-height:1.5; color:var(--ink-faint);
}
.tag{
  display:inline-block; margin-right:9px;
  font-family:var(--util); font-size:.55rem; font-weight:600;
  letter-spacing:.14em; text-transform:uppercase;
  color:var(--ink-soft); background:rgba(22,24,26,.07);
  border-radius:var(--r-pill); padding:3px 10px;
  vertical-align:1px;
}

/* screenshots */
.shot{
  display:block; width:100%; height:auto;
  border-radius:var(--r-lg); background:var(--panel);
  box-shadow:var(--lift-2);
}
/* The crop trimmed this from a 1:2 phone screen to roughly square, so it no
   longer has to be small to fit. 490px keeps it comfortably inside the
   700px source. */
.shot-phone{ max-width:490px; margin-inline:auto; }
/* This used to be capped again inside the phone budget block above, but that
   rule sat earlier in the file at equal specificity and never won, so the cap
   was dead for as long as it existed. It is no longer needed: cropping the
   screen from 1:2 to roughly square cut what it costs in height on a phone
   from about 690px to 365px, so it can simply fill the column. */
/* Panel shots open the full-size image, so they lift to say so. */
.panel-shots a{ display:block; }
.panel-shots a .shot{ transition:transform .45s cubic-bezier(.22,.61,.36,1),
                                 box-shadow .45s ease; }
.panel-shots a:hover .shot{
  transform:translateY(-4px) scale(1.008);
  box-shadow:0 0 0 1px rgba(255,255,255,.16), 0 20px 44px rgba(0,0,0,.44);
}
.tier-dark .shot{ background:#1D2124; box-shadow:var(--lift-dark); }

/* ---------- sample sites ---------- */

/* One sample per row, hero beside its copy. Three-up held these heroes at
   349px wide, which is too small to judge a website by. One per row gives each
   about 575px, and 575px is close to the ceiling: the source files are 800px
   wide, so anything much larger would be the browser scaling them up. */
.samples{
  display:grid; gap:clamp(30px,3.4vw,54px);
  grid-template-columns:minmax(0,1fr);
}
.sample{
  display:grid;
  grid-template-columns:minmax(0,1.12fr) minmax(0,1fr);
  grid-template-rows:auto auto auto 1fr;
  column-gap:clamp(28px,3.4vw,52px);
}
.sample .shot{ grid-column:1; grid-row:1 / -1; align-self:start;
               aspect-ratio:2/1; object-fit:cover; }
.sample-name{ grid-column:2; grid-row:1; margin-top:0; }
.sample-kind{ grid-column:2; grid-row:2; }
.sample-body{ grid-column:2; grid-row:3; }
/* The rule sits on the floor of the copy column, level with the foot of the
   hero beside it, so the two columns close together. */
.sample .more{ grid-column:2; grid-row:4; align-self:end; margin-top:0; }

/* Too narrow to sit side by side: back to a stacked card. */
@media (max-width: 760px){
  .samples{ gap:clamp(26px,4vw,42px); }
  .sample{ display:flex; flex-direction:column; }
  .sample-name{ margin-top:20px; }
  .sample .more{ margin-top:auto; }
}

.sample-name{ font-size:1.08rem; letter-spacing:-.026em; margin-top:20px; }
.sample-kind{
  font-family:var(--util); font-size:.58rem; letter-spacing:.13em;
  text-transform:uppercase; color:var(--invert-soft); margin-top:9px;
}
.sample-body{
  margin-top:14px; margin-bottom:22px;
  font-size:.9rem; line-height:1.6; color:var(--invert-soft);
}

/* view more */
.more{
  display:flex; align-items:baseline; gap:10px;
  /* 44px minimum touch target: this was 28px tall. */
  margin-top:auto; padding:13px 0 15px; width:100%;
  background:none; border:none; border-top:1px solid var(--invert-rule);
  color:var(--invert-fg); cursor:pointer; text-align:left;
  font-family:var(--util); font-size:.66rem;
  letter-spacing:.11em; text-transform:uppercase;
}
.more:hover .more-label{ border-bottom-color:var(--invert-fg); }
.more-label{ border-bottom:1px solid transparent; padding-bottom:2px; }
.more-count{ margin-left:auto; color:var(--invert-soft); font-size:.58rem; }
.more[aria-expanded="true"] .more-count{ opacity:0; }

/* Each panel now sits inside the samples grid, directly under the card that
   opens it, so the grid gap already spaces it and it needs no margin of its
   own. Opening one used to drop the screens at the foot of the section, a long
   way from the site they belonged to. */
.panel{ border-top:1px solid var(--invert-rule); }
.panel[hidden]{ display:none; }

/* The site name is already the heading directly above, so the panel does not
   repeat it. Only the close control remains, pushed to the right. */
.panel-head{
  display:flex; align-items:baseline; justify-content:flex-end;
  gap:20px; padding-block:18px 22px;
}
.panel-close{
  background:rgba(255,255,255,.08); border:none; cursor:pointer;
  border-radius:var(--r-pill);
  color:var(--invert-soft); padding:9px 17px;
  font-family:var(--util); font-size:.6rem;
  letter-spacing:.13em; text-transform:uppercase;
  transition:background .18s ease, color .18s ease;
}
.panel-close:hover{ color:var(--invert-fg); background:rgba(255,255,255,.16); }

.panel-shots{
  display:grid; gap:clamp(20px,2.6vw,34px);
  grid-template-columns:repeat(2,minmax(0,1fr));
}
@media (max-width: 760px){ .panel-shots{ grid-template-columns:minmax(0,1fr); } }

.panel-shots figure{ margin:0; }
.panel-shots a{ display:block; }
.panel-shots figcaption{
  margin-top:12px; font-size:.82rem; line-height:1.55; color:var(--invert-soft);
}

/* The rule divides the whole section, so it runs the whole width. Capping the
   paragraph capped its border too, which left the line stopping half way
   across looking half drawn. The measure now sits on the inner span. */
.samples-price{
  margin-top:clamp(44px,5vw,72px); padding-top:26px;
  border-top:1px solid var(--invert-rule);
  font-size:1rem; line-height:1.6; color:var(--invert-soft);
}
.samples-price > span{ display:block; max-width:56ch; }
.samples-price strong{ color:var(--invert-fg); font-weight:700; font-family:var(--util);
  font-variant-numeric:tabular-nums; letter-spacing:.01em; }

/* ---------- how it runs ---------- */

.steps{
  list-style:none; margin:0; padding:0;
  display:grid; gap:0; grid-template-columns:repeat(2,minmax(0,1fr));
  border-top:1px solid var(--rule);
}
@media (max-width: 760px){ .steps{ grid-template-columns:minmax(0,1fr); } }

.steps li{
  padding:clamp(28px,3vw,40px) clamp(20px,2.4vw,36px);
  border-bottom:1px solid var(--rule);
  border-right:1px solid var(--rule);
}
.steps li:nth-child(2n){ border-right:none; }
@media (max-width: 760px){ .steps li{ border-right:none; } }

.step-n{
  display:block; font-family:var(--util); font-size:.62rem; font-weight:600;
  letter-spacing:.16em; color:var(--ink-faint); margin-bottom:18px;
}
.steps h3{ font-size:1.07rem; letter-spacing:-.026em; margin-bottom:12px; line-height:1.28; }
.steps p{ font-size:.92rem; line-height:1.62; color:var(--ink-soft); max-width:42ch; }

/* ---------- closer ---------- */

.closer{
  padding-block:var(--sect);
  background:var(--ground-2); border-top:1px solid var(--rule);
}
.closer-in{
  display:grid; gap:clamp(40px,5vw,72px);
  grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr);
  align-items:start;
}
@media (max-width: 900px){ .closer-in{ grid-template-columns:minmax(0,1fr); } }

.closer-title{
  font-size:clamp(1.9rem,3.55vw,2.65rem);
  letter-spacing:-.032em; line-height:1.08; margin-bottom:22px;
}
.closer-lede{ color:var(--ink-soft); max-width:38ch; }

/* Same pill as the top bar CTA, sized up for the foot of the page. */
.closer-cta{
  display:inline-block; margin-top:clamp(24px,3vw,32px);
  background:var(--ink); color:var(--invert-fg); text-decoration:none;
  padding:17px 30px; border-radius:var(--r-pill);
  font-family:var(--util); font-size:.68rem; font-weight:600;
  letter-spacing:.1em; text-transform:uppercase;
  transition:background .18s ease;
}
.closer-cta:hover{ background:var(--ink-soft); }

.closer-links{ border-top:1px solid var(--rule); }
.big-link{
  display:grid; grid-template-columns:7rem minmax(0,1fr); gap:14px;
  align-items:baseline; text-decoration:none;
  padding-block:20px; border-bottom:1px solid var(--rule);
}
a.big-link:hover .big-link-v{ border-bottom-color:var(--ink); }
.big-link-k{
  font-family:var(--util); font-size:.58rem; letter-spacing:.16em;
  text-transform:uppercase; color:var(--ink-faint);
}
.big-link-v{
  font-size:clamp(.95rem,1.5vw,1.13rem); font-weight:700; letter-spacing:-.024em;
  overflow-wrap:anywhere;
  border-bottom:1px solid transparent; transition:border-color .16s ease;
}
@media (max-width: 520px){
  .big-link{ grid-template-columns:minmax(0,1fr); gap:6px; }
}

/* ---------- footer ---------- */

.foot{
  background:var(--invert-bg); color:var(--invert-soft);
  padding-block:40px 56px;
}
.foot-in{
  display:flex; flex-wrap:wrap; gap:18px 40px;
  align-items:baseline; justify-content:space-between;
}
.foot-name{
  font-family:var(--util); font-size:.62rem; letter-spacing:.16em;
  text-transform:uppercase; color:var(--invert-fg);
}
.foot-note{ font-size:.78rem; line-height:1.55; max-width:46ch; }

/* =====================================================================
   Motion
   The page is selling instruments, so nothing here dissolves into view.
   Hairlines draw, screens wipe on, readouts settle a row at a time.
   All of it is gated on .js so the page is complete without scripting,
   and every bit of it is off under prefers-reduced-motion.
   ===================================================================== */

.js .rv, .js .rv-shot{ will-change:auto; }

@media (prefers-reduced-motion: no-preference){

  /* the workhorse: a short rise, never a long float */
  .js .rv{
    opacity:0; transform:translateY(28px);
    transition:opacity .75s cubic-bezier(.16,.84,.28,1) var(--d,0s),
               transform .85s cubic-bezier(.16,.84,.28,1) var(--d,0s);
  }
  .js .rv.is-in{ opacity:1; transform:none; }

  /* Headings are the loudest thing in these two sections, so they get the
     loudest entrance: the line climbs out from behind a hard edge. The inner
     wrapper's padding is cancelled by its own negative margin, so masking
     descenders costs no layout. */
  .js .mask{
    display:block; overflow:hidden;
    padding-bottom:.18em; margin-bottom:-.18em;
  }
  .js .mask-i{
    display:block; transform:translateY(108%);
    transition:transform 1.05s cubic-bezier(.16,.84,.28,1) var(--d,0s);
  }
  .js .rv-head.is-in .mask-i{ transform:none; }

  /* The three sample sites arrive as cards being laid down, not as a fade. */
  .js .samples{ perspective:1500px; }
  .js .rv-card{
    opacity:0; transform:translateY(52px) rotateX(10deg) scale(.97);
    transform-origin:50% 0; 
    transition:opacity .8s ease var(--d,0s),
               transform 1s cubic-bezier(.16,.84,.28,1) var(--d,0s);
  }
  .js .rv-card.is-in{ opacity:1; transform:none; }

  /* Each spec row slides in behind its own rule, so the block fills like a
     readout rather than appearing all at once. */
  .js .spec > div{
    opacity:0; transform:translateX(-18px);
    transition:opacity .55s ease var(--d,0s),
               transform .7s cubic-bezier(.16,.84,.28,1) var(--d,0s);
  }
  .js .spec > div.is-in{ opacity:1; transform:none; }

  /* a screenshot is a display, so it comes up from the bottom edge.
     `round` keeps the 18px corners while the clip animates. */
  .js .rv-shot .shot{
    clip-path:inset(0 0 100% 0 round var(--r-lg));
    filter:saturate(.2) brightness(1.07) contrast(.94);
    transform:scale(.985);
    transition:clip-path .8s cubic-bezier(.3,.72,.25,1) var(--d,0s),
               filter .95s ease calc(var(--d,0s) + .18s),
               transform .95s cubic-bezier(.22,.61,.36,1) var(--d,0s);
  }
  .js .rv-shot.is-in .shot{
    clip-path:inset(0 0 0 0 round var(--r-lg));
    filter:none; transform:none;
  }
  .js .rv-shot figcaption{
    opacity:0; transition:opacity .5s ease calc(var(--d,0s) + .38s);
  }
  .js .rv-shot.is-in figcaption{ opacity:1; }

  /* section rules draw rather than appear */
  .js .tier{ border-top-color:transparent; }
  .js .tier::before{
    content:''; position:absolute; inset:0 0 auto 0; height:1px;
    background:var(--rule); transform:scaleX(0); transform-origin:left center;
    transition:transform 1.1s cubic-bezier(.22,.61,.36,1);
  }
  .js .tier.is-in::before{ transform:scaleX(1); }
  .js .tier-dark::before{ display:none; }

  /* the spec list is the readout: each rule draws, then its row settles */
  .js .spec > div{ position:relative; }
  .js .spec > div::after{
    content:''; position:absolute; inset:auto 0 -1px 0; height:1px;
    background:var(--rule-soft); transform:scaleX(0); transform-origin:left center;
    transition:transform .6s cubic-bezier(.22,.61,.36,1) var(--d,0s);
  }
  .js .spec > div.is-in::after{ transform:scaleX(1); }

  /* hero: an opening sequence, then it gets out of the way on the way down */
  .js .hero-in > *{
    opacity:0; transform:translateY(22px);
    animation:heroIn .9s cubic-bezier(.22,.61,.36,1) var(--d,0s) forwards;
  }
  @keyframes heroIn{ to{ opacity:1; transform:none; } }
}

.js .tier{ position:relative; }

/* the top bar carries a readout of how far down the page you are */
.topbar::after{
  content:''; position:absolute; left:0; right:0; bottom:-1px; height:2px;
  background:var(--ink); transform:scaleX(var(--sp,0)); transform-origin:left center;
  pointer-events:none;
}

.case-fig{ align-self:start; }

/* =====================================================================
   Start a project
   A dialog, not a page. It borrows the notice card's surface and the
   hero glass radius so it belongs to the same family. Fields are filled
   surfaces rather than bordered boxes, which keeps the no enclosures
   rule intact.
   ===================================================================== */

.modal[hidden]{ display:none; }
.modal{
  position:fixed; inset:0; z-index:80;
  display:grid; place-items:center;
  padding:clamp(16px,4vw,40px);
}
.modal-veil{
  position:absolute; inset:0;
  background:rgba(22,24,26,.46);
  -webkit-backdrop-filter:blur(3px); backdrop-filter:blur(3px);
}
.modal-card{
  position:relative; width:min(660px,100%);
  max-height:calc(100vh - clamp(32px,8vw,80px));
  overflow-y:auto; overscroll-behavior:contain;
  background:var(--panel); color:var(--ink);
  border-radius:var(--r-xl); box-shadow:var(--lift-2), 0 30px 70px rgba(0,0,0,.28);
  padding:clamp(26px,4vw,44px);
}
.modal-x{
  position:absolute; top:14px; right:14px;
  width:38px; height:38px; display:grid; place-items:center;
  background:rgba(22,24,26,.06); border:none; border-radius:var(--r-pill);
  color:var(--ink-soft); cursor:pointer;
  transition:background .18s ease, color .18s ease;
}
.modal-x svg{ width:19px; height:19px; display:block; }
.modal-x:hover{ background:rgba(22,24,26,.12); color:var(--ink); }

.modal-title{
  font-size:clamp(1.5rem,3vw,2rem);
  letter-spacing:-.03em; line-height:1.1; margin-bottom:14px;
  padding-right:36px;
}
.modal-lede{ color:var(--ink-soft); font-size:.96rem; max-width:44ch; }

/* ---------- the form ---------- */

.form{ margin-top:clamp(22px,3vw,30px); }
.form-row{ display:grid; gap:14px; grid-template-columns:repeat(2,minmax(0,1fr)); }
@media (max-width: 520px){ .form-row{ grid-template-columns:minmax(0,1fr); } }

.field{ display:block; margin:0 0 14px; }
.form-row .field{ margin-bottom:0; }
.form-row + .field{ margin-top:14px; }

.field label{
  display:block; margin-bottom:7px;
  font-family:var(--util); font-size:.58rem; font-weight:600;
  letter-spacing:.14em; text-transform:uppercase; color:var(--ink-faint);
}
.field input, .field select, .field textarea{
  width:100%; display:block;
  font-family:var(--body); font-size:.96rem; line-height:1.5; color:var(--ink);
  background:rgba(22,24,26,.055);
  border:none; border-radius:14px;
  padding:13px 15px;
  transition:background .18s ease, box-shadow .18s ease;
}
.field textarea{ resize:vertical; min-height:104px; }
.field select{
  appearance:none; cursor:pointer;
  background-image:linear-gradient(45deg,transparent 50%,var(--ink-faint) 50%),
                   linear-gradient(135deg,var(--ink-faint) 50%,transparent 50%);
  background-position:calc(100% - 20px) 50%, calc(100% - 14px) 50%;
  background-size:6px 6px, 6px 6px;
  background-repeat:no-repeat;
  padding-right:42px;
}
.field input:hover, .field textarea:hover, .field select:hover{ background:rgba(22,24,26,.085); }
.field input:focus-visible, .field select:focus-visible, .field textarea:focus-visible{
  outline:2px solid var(--ink); outline-offset:2px; background:rgba(22,24,26,.085);
}
/* Invalid only after a submit has been attempted, so nothing turns red while
   the visitor is still typing their first character. */
.form.tried :invalid{ box-shadow:inset 0 0 0 1.5px rgba(160,60,52,.55); }

.form-send{
  margin-top:18px; width:100%;
  background:var(--ink); color:var(--invert-fg);
  border:none; border-radius:var(--r-pill); cursor:pointer;
  padding:16px 26px;
  font-family:var(--util); font-size:.68rem; font-weight:600;
  letter-spacing:.1em; text-transform:uppercase;
  transition:background .18s ease, opacity .18s ease;
}
.form-send:hover{ background:var(--ink-soft); }
.form-send[disabled]{ opacity:.55; cursor:default; }

.form-note{
  font-size:.86rem; line-height:1.5; color:var(--ink-soft);
  margin-bottom:0;
}
.form-note:not(:empty){ margin-bottom:14px; }
.form-note.is-bad{ color:#A03C34; }

.form-alt{
  margin-top:16px; text-align:center;
  font-size:.82rem; color:var(--ink-faint);
}
.form-alt a{ color:var(--ink-soft); text-underline-offset:3px; }

@media (prefers-reduced-motion: no-preference){
  .js .modal-veil{ animation:veilIn .28s ease both; }
  .js .modal-card{ animation:cardIn .42s cubic-bezier(.16,.84,.28,1) both; }
  @keyframes veilIn{ from{ opacity:0 } }
  @keyframes cardIn{ from{ opacity:0; transform:translateY(14px) scale(.985) } }
}

/* Honeypot: off-screen rather than display:none, because some bots skip
   fields that are not rendered at all. */
.form-trap{
  position:absolute; left:-9999px; top:auto;
  width:1px; height:1px; overflow:hidden;
}

/* ---------- start a project: the sent state ---------- */

/* When the message is away the card stops being a form and becomes a receipt,
   so the intro and the fields get out of the way entirely. */
.modal-card.is-done .eyebrow,
.modal-card.is-done .modal-title,
.modal-card.is-done .modal-lede,
.modal-card.is-done .form{ display:none; }

.form-done[hidden]{ display:none; }
.form-done{
  text-align:center;
  padding-block:clamp(14px,3vw,26px) 4px;
}
.form-done-mark{
  display:grid; place-items:center;
  width:58px; height:58px; margin:0 auto 20px;
  border-radius:var(--r-pill);
  background:var(--ink); color:var(--invert-fg);
}
.form-done-mark svg{ width:27px; height:27px; display:block; }
.form-done-title{
  font-size:clamp(1.35rem,2.6vw,1.7rem);
  letter-spacing:-.03em; line-height:1.1; margin-bottom:12px;
}
.form-done-title:focus{ outline:none; }
.form-done-title:focus-visible{ outline:2px solid var(--ink); outline-offset:5px; }
.form-done-body{
  color:var(--ink-soft); font-size:.96rem; line-height:1.55;
  max-width:36ch; margin:0 auto;
}
.form-done-close{
  margin-top:26px;
  background:rgba(22,24,26,.06); border:none; cursor:pointer;
  color:var(--ink); border-radius:var(--r-pill);
  padding:14px 30px;
  font-family:var(--util); font-size:.68rem; font-weight:600;
  letter-spacing:.1em; text-transform:uppercase;
  transition:background .18s ease;
}
.form-done-close:hover{ background:rgba(22,24,26,.12); }

@media (prefers-reduced-motion: no-preference){
  .js .form-done:not([hidden]){ animation:doneIn .45s cubic-bezier(.16,.84,.28,1) both; }
  .js .form-done:not([hidden]) .form-done-mark{
    animation:markIn .5s cubic-bezier(.175,.885,.32,1.5) .06s both;
  }
  @keyframes doneIn{ from{ opacity:0; transform:translateY(10px) } }
  @keyframes markIn{ from{ opacity:0; transform:scale(.6) } }
}
