:root {
  --bluebonnet: #4a4e9c;
  --bluebonnet-light: #6064b3; /* same hue and saturation, lighter only */
  --old-glory-blue: #00205b;   /* Pantone 281 C */
  --old-glory-red: #bf0a30;    /* flag red, used as accent only */
  --white: #ffffff;
  --sidebar-width: 88px;
  --bottombar-height: 68px;

  /* The bluebonnet areas that butt up against the sidebar - interior page
     banners and the footer on every page - are washed with this instead of a
     flat fill, so their left edge is distinct from the sidebar's flat
     --bluebonnet rather than dissolving into it.

     Deliberately one colour: both stops are hsl(237, 36%) and differ only in
     lightness, 54% down to the stock 45%, so nothing but bluebonnet appears.
     The light stop still carries white text at 5.3:1, past AA for body copy,
     and it only improves as the sweep darkens rightward.

     Every element using this must span the same box - full width, starting at
     the sidebar edge - or the sweep will not line up where two of them meet. */
  --bluebonnet-sweep: linear-gradient(90deg, var(--bluebonnet-light), var(--bluebonnet));
}

* {
  box-sizing: border-box;
}

html, body {
  margin: 0;
  padding: 0;
}

html {
  scroll-behavior: smooth;
}

body {
  font-family: "Outfit", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  color: var(--old-glory-blue);
  background: var(--white);
  line-height: 1.6;
}

a {
  color: var(--bluebonnet);
}

/* Text that only assistive technology gets. Used to finish a link's accessible
   name where repeating the whole thing on screen would be noise - "Learn more"
   reads fine beside the app it belongs to, but three identical "Learn more"
   entries in the VoiceOver rotor do not. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--old-glory-blue);
  color: var(--white);
  padding: 0.75rem 1.25rem;
  z-index: 100;
  border-radius: 0 0 8px 0;
}

.skip-link:focus {
  left: 0;
}

/* Sidebar */
.sidebar {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  width: var(--sidebar-width);
  background: var(--bluebonnet);
  color: var(--white);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 1.75rem 0;
  z-index: 50;
}

.sidebar-brand {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  color: var(--white);
  text-decoration: none;
  margin-bottom: 2.5rem;
}

/* The logo is a photograph, not a flat mark, so it needs treatment the old
   inline SVG didn't: object-fit keeps it from stretching if the source file
   isn't perfectly square, and the rounded corners plus the hairline border
   stop a dark photo edge from dissolving into the navy sidebar and footer. */
/* 52px in both the sidebar and the footer, so this one rule covers both.
   In the 88px sidebar that leaves 18px of clearance each side and sits at
   about 70% of the wordmark's width below it. It is deliberately bigger than
   the 39px nav icons: those are line art that only draws 26-29px of actual
   shape inside its box, so at equal box sizes the logo would read as the
   smaller object. Don't shrink the nav icons to compensate - they double as
   the mobile bottom bar, where the brand is hidden entirely. */
.brand-mark {
  width: 52px;
  height: 52px;
  object-fit: cover;
  border-radius: 12px;
  border: 1px solid rgba(255, 255, 255, 0.35);
  display: block;
  flex: none;
}

.brand-wordmark {
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1.1;
}

.brand-word-top {
  font-weight: 700;
  font-size: 0.64rem;
  letter-spacing: 0.05em;
}

.brand-word-bottom {
  font-weight: 500;
  font-size: 0.6rem;
  letter-spacing: 0.22em;
}

.sidebar-nav {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0;
  width: 100%;
  align-items: stretch;
}

.nav-link {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.25rem;
  width: 100%;
  padding: 1.1rem 0;
  border-radius: 0;
  color: var(--white);
  text-decoration: none;
}

.nav-link svg {
  width: 39px;
  height: 39px;
}

/* The icon labels stay in the markup as the link's accessible name for
   screen readers, but are hidden visually. The tooltip below replaces them
   for sighted visitors on hover. On small screens they become visible again,
   since touch devices have no hover. */
.nav-label {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Hover tooltip: white rounded rectangle to the right of the icon, with a
   soft-cornered pointer aimed back at it. Decorative only (aria-hidden). */
.nav-tooltip {
  position: absolute;
  left: calc(100% + 12px);
  top: 50%;
  transform: translateY(-50%);
  background: var(--white);
  color: var(--bluebonnet);
  font-size: 0.85rem;
  font-weight: 600;
  line-height: 1;
  padding: 0.55rem 0.8rem;
  border-radius: 8px;
  white-space: nowrap;
  box-shadow: 0 3px 12px rgba(0, 32, 91, 0.22);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  z-index: 60;
}

.nav-tooltip::before {
  content: "";
  position: absolute;
  left: -4px;
  top: 50%;
  width: 11px;
  height: 11px;
  background: var(--white);
  border-radius: 0 0 0 3px;
  transform: translateY(-50%) rotate(45deg);
}

.nav-link:hover .nav-tooltip,
.nav-link:focus-visible .nav-tooltip {
  opacity: 1;
  visibility: visible;
}

.nav-link:hover {
  background: var(--white);
  color: var(--bluebonnet);
}

.nav-link.is-active {
  background: var(--white);
  color: var(--bluebonnet);
}

.nav-link:focus-visible {
  outline: 3px solid var(--white);
  outline-offset: 2px;
}

/* Main layout */
main {
  margin-left: var(--sidebar-width);
}

/* Hero */
.hero {
  position: relative;
  /* Fill the screen before any scrolling happens. dvh tracks mobile browser
     chrome as the address bar hides and shows, so the hero keeps filling the
     screen rather than leaving a strip of the next section showing; plain vh
     is the fallback for browsers without dvh. min-height rather than height so
     the headline can still push the section taller on very short screens
     instead of being clipped. */
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background: var(--bluebonnet);
}

.hero-media,
.hero-overlay {
  position: absolute;
  inset: 0;
}

/* The poster sits behind the video as a background image, not only on the
   video element. That way anyone who never gets the video - reduced motion,
   data saver, no JavaScript - still sees the field rather than flat colour,
   and the video fades in over an already-painted frame. The slow drift means
   those visitors still get some life in the hero instead of a still.
   overflow: hidden on .hero keeps the scaled-up frame from widening the page.

   The drift lives on this pseudo-element, which carries only the poster, and
   deliberately NOT on .hero-media itself, which also contains the video. A CSS
   zoom updates every display refresh while video frames arrive 24 times a
   second, and that mismatch makes the frame steps far more visible - the
   footage reads as choppy. The still image has no such conflict, and it is the
   one that actually benefits from the movement. */
.hero-media {
  background: var(--bluebonnet);
}

.hero-media::before {
  content: "";
  position: absolute;
  inset: 0;
  background: url("/hero-poster.jpg") center/cover no-repeat;
  animation: kenburns 28s ease-in-out infinite alternate;
}

/* Sits above the drifting poster, and untransformed. */
.hero-video {
  position: relative;
}

@keyframes kenburns {
  from { transform: scale(1.06) translate3d(0, 0, 0); }
  to   { transform: scale(1.16) translate3d(-1.5%, -1%, 0); }
}

.hero-video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Plain black rather than a brand tint. The subject is blue flowers among
   green foliage, and a blue scrim pushes the already-blue flowers further blue
   while draining the green: measured over the real frames, old glory blue
   leaves the foliage at 32% of its colourfulness while black keeps flowers and
   foliage at 76% each, in proportion. Black also needs less opacity for the
   same legibility - 28% against 35% - because navy carries brightness of its
   own, so you pay more haze for the same readability.

   The 30-44% range is not a taste call. Every piece of hero text was measured
   against the worst 1% of pixels behind it across all 240 frames, on both the
   desktop and mobile crops. The binding constraint is the sub-paragraph, which
   at 21px desktop and 17px mobile is below the WCAG large-text threshold and
   so needs 4.5:1 rather than the headline's 3:1; it requires 33%. Everything
   clears its ratio with a few points to spare. Lighten this and the small text
   starts failing on the bright frames. */
.hero-overlay {
  background: linear-gradient(180deg, rgba(0, 0, 0, 0.30), rgba(0, 0, 0, 0.44));
}

.hero-content {
  position: relative;
  z-index: 1;
  max-width: 900px;
  padding: 4rem 2rem;
  text-align: center;
}

.hero-kicker {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  margin: 0 0 1.4rem;
  padding: 0.45rem 1rem;
  border: 1px solid var(--white);
  border-radius: 999px;
  background: transparent;
  color: var(--white);
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.hero-kicker .dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--old-glory-red);
  animation: pulse 2.4s ease-in-out infinite;
}

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}

.hero-content h1 {
  color: var(--white);
  font-weight: 700;
  font-size: clamp(2.2rem, 6vw, 4.2rem);
  line-height: 1.06;
  letter-spacing: -0.02em;
  margin: 0 0 1.25rem;
  text-wrap: balance;
}

/* The accent rule draws itself in under one word once the page has settled.
   It sits below the baseline rather than through it, so it never reduces the
   legibility of the word it is emphasising. */
.hero-content h1 .mark {
  position: relative;
  white-space: nowrap;
}

.hero-content h1 .mark::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -0.04em;
  height: 0.12em;
  background: var(--old-glory-red);
  border-radius: 2px;
  transform: scaleX(0);
  transform-origin: left;
  animation: underline 0.9s cubic-bezier(0.2, 0.8, 0.2, 1) 0.5s forwards;
}

@keyframes underline {
  to { transform: scaleX(1); }
}

.hero-sub {
  color: var(--white);
  font-size: clamp(1.05rem, 1.6vw, 1.3rem);
  max-width: 60ch;
  margin: 0 auto 2.25rem;
}

.hero-actions {
  display: flex;
  gap: 1rem;
  justify-content: center;
  flex-wrap: wrap;
}

.scroll-cue {
  position: absolute;
  z-index: 2;
  left: 50%;
  bottom: 2.25rem;
  transform: translateX(-50%);
  color: var(--white);
  opacity: 0.85;
}

.scroll-cue svg {
  width: 26px;
  height: 26px;
  animation: bob 2s ease-in-out infinite;
}

@keyframes bob {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(7px); }
}

/* Interior page banner.

   Deliberately not a second full-screen hero: on every page but the home page
   the content below is the reason the visitor came, and a 100vh banner would
   push it a whole screen down each time. This is tall enough to establish the
   page and no taller. */
.page-hero {
  position: relative;
  overflow: hidden;
  padding: 7rem 2rem 5.5rem;
  background: var(--bluebonnet-sweep);
  color: var(--white);
  text-align: center;
}

.page-hero-inner {
  position: relative;
  z-index: 1;
  max-width: 780px;
  margin: 0 auto;
}

/* The eyebrow is Old Glory Blue by default, which would vanish against this
   banner. Its rule is handled with the other eyebrow rules further down. */
.page-hero .eyebrow {
  color: var(--white);
}

.page-hero h1 {
  color: var(--white);
  font-weight: 700;
  font-size: clamp(2.1rem, 5vw, 3.4rem);
  line-height: 1.08;
  letter-spacing: -0.02em;
  margin: 0 0 1rem;
  text-wrap: balance;
}

.page-hero-sub {
  margin: 0 auto;
  max-width: 58ch;
  font-size: clamp(1.02rem, 1.5vw, 1.2rem);
  color: var(--white);
}

/* Buttons */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.95rem 1.9rem;
  border-radius: 10px;
  font-weight: 600;
  font-size: 1.02rem;
  text-decoration: none;
  transition: transform 0.18s ease, background-color 0.18s ease, box-shadow 0.18s ease;
}

.btn svg {
  width: 18px;
  height: 18px;
  transition: transform 0.18s ease;
}

.btn:hover svg {
  transform: translateX(4px);
}

.btn-primary {
  background: var(--old-glory-red);
  color: var(--white);
  box-shadow: 0 8px 24px rgba(191, 10, 48, 0.35);
}

.btn-primary:hover {
  transform: translateY(-2px);
  box-shadow: 0 12px 30px rgba(191, 10, 48, 0.45);
}

.btn-ghost {
  background: transparent;
  color: var(--white);
  border: 1.5px solid var(--white);
}

.btn-ghost:hover {
  background: var(--white);
  color: var(--old-glory-blue);
  transform: translateY(-2px);
}

.btn:focus-visible {
  outline: 3px solid var(--white);
  outline-offset: 3px;
}

/* The ghost button and the focus ring are both drawn in white, which only
   works over the hero footage and the dark bands. On a light band they would
   be invisible, so both flip to the dark ink. */
.band:not(.band-tint) .btn:focus-visible {
  outline-color: var(--old-glory-blue);
}

.band:not(.band-tint) .btn-ghost {
  background: transparent;
  color: var(--old-glory-blue);
  border-color: var(--old-glory-blue);
}

.band:not(.band-tint) .btn-ghost:hover {
  background: var(--old-glory-blue);
  color: var(--white);
}

/* Content bands */
.band {
  padding: 6rem 2rem;
  background: var(--white);
  text-align: left;
}

.band-paper {
  background: var(--white);
}

.band-tint {
  background: var(--old-glory-blue);
  color: var(--white);
}

.band-inner {
  max-width: 1100px;
  margin: 0 auto;
}

.band-narrow {
  max-width: 780px;
}

.eyebrow {
  display: inline-block;
  position: relative;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--old-glory-blue);
  margin: 0 0 0.9rem;
  padding-left: 2.6rem;
}

.eyebrow::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: 2rem;
  height: 2px;
  background: var(--old-glory-red);
}

/* Site-wide rule for these marks: red on white, white on either blue. The flag
   red is barely visible on the dark grounds - about 1.2:1 on bluebonnet and
   2.4:1 on Old Glory Blue - so it reads as a smudge rather than a mark. White
   is 5.3:1 at the lightest point of the bluebonnet sweep and 15.5:1 on navy.
   Anywhere the ground is white the red stays, which is where it earns its
   contrast at 6.4:1. */
.page-hero .eyebrow::before,
.band-tint .eyebrow::before {
  background: var(--white);
}

/* Centred variant: the rule sits above the text rather than beside it, so it
   doesn't run through the words. */
.eyebrow-center {
  padding-left: 0;
}

.eyebrow-center::before {
  left: 50%;
  top: -0.9rem;
  transform: translateX(-50%);
}

.band h2,
.stats h2 {
  color: var(--old-glory-blue);
  font-weight: 700;
  font-size: clamp(1.9rem, 3.4vw, 2.8rem);
  line-height: 1.15;
  letter-spacing: -0.015em;
  margin: 0 0 1.1rem;
  text-wrap: balance;
}

.lede {
  font-size: 1.15rem;
  color: var(--old-glory-blue);
  margin: 0 0 1.25rem;
  max-width: 62ch;
}

.lede:last-child {
  margin-bottom: 0;
}

/* Everything inside the dark band flips to white. */
.band-tint .eyebrow,
.band.band-tint h2,
.band-tint .lede,
.band-tint a {
  color: var(--white);
}

/* Split sections: copy on one side, a supporting panel on the other */
.split {
  display: grid;
  grid-template-columns: 1.05fr 1fr;
  gap: 4rem;
  align-items: center;
}

.split-reverse .split-media {
  order: -1;
}

.split .lede {
  max-width: 48ch;
}

/* Device mockups */
.phones {
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
}

.phone {
  position: relative;
  width: 236px;
  aspect-ratio: 9 / 19.5;
  border-radius: 34px;
  padding: 9px;
  /* Device body: the brand navy, our darkest colour, reads as the phone bezel. */
  background: var(--old-glory-blue);
  box-shadow: 0 30px 60px rgba(0, 32, 91, 0.30), 0 4px 10px rgba(0, 32, 91, 0.18);
  transition: transform 0.5s cubic-bezier(0.2, 0.8, 0.2, 1);
}

.phone-back {
  transform: translateX(38px) rotate(7deg) scale(0.9);
  z-index: 1;
}

.phone-front {
  transform: translateX(-38px) rotate(-5deg);
  z-index: 2;
}

.phones:hover .phone-back {
  transform: translateX(54px) rotate(9deg) scale(0.92);
}

.phones:hover .phone-front {
  transform: translateX(-48px) rotate(-6deg) translateY(-8px);
}

.phone-screen {
  height: 100%;
  border-radius: 26px;
  overflow: hidden;
  background: var(--white);
  display: flex;
  flex-direction: column;
}

.phone-notch {
  position: absolute;
  top: 9px;
  left: 50%;
  transform: translateX(-50%);
  width: 84px;
  height: 20px;
  background: var(--old-glory-blue);
  border-radius: 0 0 12px 12px;
  z-index: 3;
}

.screen-head {
  padding: 2.1rem 1rem 0.9rem;
  background: var(--bluebonnet);
  color: var(--white);
}

.screen-head b {
  display: block;
  font-size: 1.05rem;
  font-weight: 700;
}

.screen-head span {
  font-size: 0.68rem;
  opacity: 0.85;
}

.screen-body {
  flex: 1;
  padding: 0.85rem;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  background: var(--white);
}

.screen-row {
  background: var(--white);
  border-radius: 10px;
  padding: 0.6rem 0.7rem;
  box-shadow: 0 1px 3px rgba(0, 32, 91, 0.10);
}

.screen-row .l1 {
  height: 8px;
  width: 72%;
  border-radius: 4px;
  background: var(--old-glory-blue);
  opacity: 0.85;
}

.screen-row .l2 {
  height: 6px;
  width: 45%;
  border-radius: 4px;
  background: var(--old-glory-blue);
  opacity: 0.28;
  margin-top: 6px;
}

.screen-row.accent {
  border-left: 3px solid var(--old-glory-red);
}

.screen-tabs {
  display: flex;
  justify-content: space-around;
  padding: 0.6rem 0 0.9rem;
  background: var(--white);
  border-top: 1px solid var(--bluebonnet);
}

.screen-tabs i {
  width: 18px;
  height: 18px;
  border-radius: 5px;
  background: var(--old-glory-blue);
  opacity: 0.22;
  display: block;
}

.screen-tabs i.on {
  opacity: 1;
  background: var(--bluebonnet);
}

/* Demo panels: show the claim rather than only describing it */
.demo-card {
  background: var(--white);
  border: 1px solid var(--bluebonnet);
  border-radius: 20px;
  padding: 1.6rem;
  box-shadow: 0 24px 60px rgba(0, 32, 91, 0.16);
}

.demo-head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 1.1rem;
}

.demo-head .dots {
  display: flex;
  gap: 5px;
}

.demo-head .dots i {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--bluebonnet);
  display: block;
}

.demo-head b {
  font-size: 0.85rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--bluebonnet);
}

.demo-line {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  padding: 0.85rem;
  border-radius: 12px;
  background: var(--white);
  /* Rows are white on a white card, so a bluebonnet outline is what separates
     them - the same border colour used everywhere else on the page. */
  border: 1px solid var(--bluebonnet);
}

.demo-line + .demo-line {
  margin-top: 0.7rem;
}

/* Mirrors how a VoiceOver cursor looks when it lands on a control. Decorative,
   so it is drawn rather than announced. */
.demo-line.focused {
  background: var(--white);
  outline: 3px solid var(--bluebonnet);
  outline-offset: -3px;
}

.demo-line span {
  font-size: 0.95rem;
  color: var(--old-glory-blue);
}

.demo-line .tag {
  margin-left: auto;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--bluebonnet);
}

.demo-caption {
  margin: 1.1rem 0 0;
  font-size: 0.88rem;
  color: var(--bluebonnet);
}

/* The caption lives inside a white card even in the dark band, so its link
   must stay dark rather than inheriting the band's white. */
.band-tint .demo-card .demo-caption,
.band-tint .demo-card a {
  color: var(--bluebonnet);
}

/* Feature grid */
.feature-grid {
  list-style: none;
  margin: 3rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.5rem;
}

.feature {
  position: relative;
  overflow: hidden;
  background: var(--bluebonnet);
  border: 2px solid var(--old-glory-red);
  border-radius: 16px;
  padding: 1.9rem 1.6rem;
}

.feature-icon {
  width: 52px;
  height: 52px;
  border-radius: 14px;
  margin-bottom: 1.1rem;
  display: grid;
  place-items: center;
  /* Reversed out of the card, which is bluebonnet on every band. */
  background: var(--white);
  color: var(--bluebonnet);
}

.feature-icon svg {
  width: 26px;
  height: 26px;
}

.feature h3 {
  color: var(--white);
  font-weight: 700;
  font-size: 1.18rem;
  margin: 0 0 0.5rem;
}

.feature p {
  margin: 0;
  font-size: 0.98rem;
  color: var(--white);
}

/* App portfolio grid.

   White cards on the white band, separated by the same bluebonnet hairline and
   soft shadow the demo panels use. Deliberately not the bluebonnet-filled
   feature cards: the app icons are the subject here, and a saturated card
   behind them would fight every icon we ever drop in. */
.app-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 2rem;
}

/* The card is a container, not a control - only the button inside it is
   clickable. So it deliberately has no hover state: a card that lifts under the
   pointer promises that clicking it will do something, and here it would not. */
.app-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  background: var(--white);
  border: 1px solid var(--bluebonnet);
  border-radius: 22px;
  padding: 2.4rem 1.6rem 2rem;
  box-shadow: 0 24px 60px rgba(0, 32, 91, 0.16);
}

/* Squared-off source art, rounded here, so a real icon can be dropped in as a
   plain square PNG without anyone having to mask it first. The width and height
   attributes on the img reserve the space before it loads.

   The shadow is a plain box-shadow, which is drawn from the element's rounded
   box rather than from the picture inside it. That is correct as long as every
   icon fills its square, which is the rule here: app icons always arrive with an
   opaque background. An icon that was a shape on a transparent background would
   need filter: drop-shadow() instead, or it would get a rounded-square haze
   around it. */
.app-icon {
  display: block;
  width: 100%;
  max-width: 168px;
  height: auto;
  aspect-ratio: 1;
  border-radius: 38px;
  box-shadow: 0 12px 28px rgba(0, 32, 91, 0.22);
}

/* Outranks the generic .band h2 sizing: these are card titles, not section
   headings, and should not compete with the banner above them. */
.band .app-card h2 {
  font-size: 1.45rem;
  margin: 1.5rem 0 0.5rem;
}

.app-tagline {
  margin: 0 0 1.5rem;
  max-width: 32ch;
  font-size: 1rem;
  color: var(--old-glory-blue);
}

/* The card's call to action carries .btn .btn-primary in the markup, so it is
   the same red button the rest of the site uses - same size, same radius, same
   shadow, same arrow nudge - rather than a lookalike defined twice. Everything
   below is only what a button inside a card needs on top of that.

   margin-top: auto pins it to the bottom of the card, so the three buttons line
   up even when one tagline wraps to an extra line. */
.app-link {
  margin-top: auto;
}

/* Nothing else is needed here. The hover lift, the arrow nudge, the deeper
   shadow, and the navy keyboard focus ring all come from .btn / .btn-primary,
   so these behave exactly like every other button on the site. */

/* Pull quote */
.pullquote {
  margin: 0 auto;
  max-width: 900px;
  text-align: center;
  font-size: clamp(1.5rem, 3.2vw, 2.4rem);
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: -0.01em;
  text-wrap: balance;
}

.pullquote .hl {
  color: var(--old-glory-red);
}

/* Numbers strip: the whole promise in four figures, sitting after the three
   sections that earn them rather than on top of the hero footage. */
.stats {
  /* No bottom padding: the closing rule and the gap above it now live on the
     inner block, so the rule is drawn at content width rather than edge to
     edge - matching the footer's rule. */
  padding: 4rem 2rem 0;
  background: var(--white);
}

/* Rules follow the site-wide policy: red on a white ground, white on a
   coloured one. This border and the stat separators below sit on white, so
   both are red. */
.stats-inner {
  max-width: 1100px;
  margin: 0 auto;
  padding-bottom: 4rem;
  border-bottom: 1px solid var(--old-glory-red);
  text-align: center;
}

.stats-list {
  list-style: none;
  margin: 2.25rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
}

.stat {
  padding: 0 1rem;
}

.stat + .stat {
  border-left: 1px solid var(--old-glory-red);
}

.stat b {
  display: block;
  font-size: clamp(2.4rem, 5vw, 3.6rem);
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -0.02em;
  color: var(--old-glory-blue);
}

.stat span {
  display: block;
  margin-top: 0.35rem;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--bluebonnet);
}

/* Shaped section dividers, so one band spills into the next rather than
   changing colour on a hard edge.

   The svg's background is the colour of the band ABOVE and the path paints the
   colour of the band BELOW over it, with the path running past the bottom edge
   so it is clipped rather than drawn to it. Both edges of the divider therefore
   meet a matching colour. Doing it the other way round leaves a hairline: a
   section boundary rarely lands on a whole pixel, and the path's edge then
   antialiases against a background of the opposite colour. */
.divider {
  display: block;
  width: 100%;
  height: 70px;
  background: var(--divider-from);
}

.divider path {
  fill: var(--divider-to);
}

.divider-paper-navy {
  --divider-from: var(--white);
  --divider-to: var(--old-glory-blue);
}

.divider-navy-white {
  --divider-from: var(--old-glory-blue);
  --divider-to: var(--white);
}

.divider-white-navy {
  --divider-from: var(--white);
  --divider-to: var(--old-glory-blue);
}

.divider-navy-paper {
  --divider-from: var(--old-glory-blue);
  --divider-to: var(--white);
}

/* Interior pages: the banner is bluebonnet rather than navy, so it needs its
   own pairing to spill into the first white band.

   The band above is swept rather than flat, so this overrides the background
   outright instead of setting --divider-from. A flat --bluebonnet here would
   re-state the full-strength colour directly under the banner's lighter left
   edge and read as a darker bar across the bottom of the header. The sweep
   lines up exactly because this strip and the banner share a box. */
.divider-bluebonnet-paper {
  --divider-to: var(--white);
  background: var(--bluebonnet-sweep);
}

/* Closing call to action */
.band-cta {
  position: relative;
  overflow: hidden;
  text-align: center;
}

.band-cta .band-inner {
  position: relative;
  z-index: 1;
  max-width: 760px;
}

.band-cta .lede {
  margin-left: auto;
  margin-right: auto;
}

.band-cta .hero-actions {
  margin-top: 2rem;
}

/* Footer */
/* Swept like the interior banners, on every page including the home page: the
   sidebar runs the full height of the viewport, so the footer meets it too and
   would otherwise share its exact colour along that edge. */
.site-footer {
  margin-left: var(--sidebar-width);
  padding: 3rem 2rem 2rem;
  background: var(--bluebonnet-sweep);
  color: var(--white);
  font-size: 0.92rem;
}

.footer-inner {
  max-width: 1100px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr;
  gap: 2.5rem;
}

/* The brand block stays left, the link columns fan out from it: middle centred,
   right flushed to the edge. Positional selectors because the two link columns
   are plain divs; both are overridden once the footer stacks. */
.footer-inner > div:nth-child(2) {
  text-align: center;
}

.footer-inner > div:nth-child(3) {
  text-align: right;
}

/* Full opacity, not muted, so the heading sits crisply against the bluebonnet
   and reads as a clear label. Presence comes from contrast, not from a larger
   size - the small-caps overline is the conventional footer-heading treatment. */
.footer-inner h2 {
  margin: 0 0 0.8rem;
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.footer-inner ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
}

.footer-inner a {
  color: var(--white);
  text-decoration: none;
}

.footer-inner a:hover {
  text-decoration: underline;
}

.footer-brand p {
  margin: 0.6rem 0 0;
  max-width: 34ch;
}

.footer-bottom {
  max-width: 1100px;
  margin: 2.5rem auto 0;
  padding-top: 1.5rem;
  border-top: 1px solid var(--white);
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}

.footer-bottom p {
  margin: 0;
}

.footer-bottom a {
  color: var(--white);
}

.site-footer a:focus-visible {
  outline: 2px solid var(--white);
  outline-offset: 3px;
}

/* Scroll reveal.

   Gated on the .js class, which an inline script in the head adds before the
   page paints. Without JavaScript the rule never applies and every section is
   simply visible, so the content is never hidden behind a script that might
   not run. The class only changes opacity and offset - never DOM order or
   text - so a screen reader reads the page identically either way. */
.js .reveal {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity 0.7s ease, transform 0.7s cubic-bezier(0.2, 0.8, 0.2, 1);
}

.js .reveal.is-visible {
  opacity: 1;
  transform: none;
}

.reveal-delay-1 { transition-delay: 0.09s; }
.reveal-delay-2 { transition-delay: 0.18s; }

/* Tablet and narrow desktop: two-column layouts collapse before the sidebar does */
@media (max-width: 980px) {
  .band {
    padding: 4.5rem 2rem;
  }

  .split {
    grid-template-columns: 1fr;
    gap: 3rem;
  }

  /* Copy first on a single column, whichever side the panel was on. */
  .split-reverse .split-media {
    order: 0;
  }

  .split .lede {
    max-width: 62ch;
  }

  .feature-grid {
    grid-template-columns: 1fr;
  }

  /* Two across rather than straight to one: the cards are icon-led and mostly
     square, so a single column would leave a lot of empty band beside them. */
  .app-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: 1.5rem;
  }

  .page-hero {
    padding: 5.5rem 2rem 4.5rem;
  }

  .stats-list {
    grid-template-columns: repeat(2, 1fr);
    gap: 2rem 0;
  }

  .stat:nth-child(3) {
    border-left: none;
  }

  /* Stacked into one column, so every block centres - a right-flushed column
     reads as a mistake once it is no longer balancing anything beside it. The
     two nth-child selectors repeat here to outrank the desktop rules above. */
  .footer-inner {
    grid-template-columns: 1fr;
    text-align: center;
  }

  .footer-inner > div:nth-child(2),
  .footer-inner > div:nth-child(3) {
    text-align: center;
  }

  /* Both of these are block-level boxes - the logo is a block image and the
     tagline is width-capped - so centring their text does nothing on its own.
     They need auto margins to centre the boxes themselves. */
  .footer-brand .brand-mark,
  .footer-brand p {
    margin-left: auto;
    margin-right: auto;
  }

  /* Bottom strip stacks with the rest: copyright and privacy policy on their
     own lines, centred, rather than pushed to opposite edges. The default
     space-between splits them left and right, which only makes sense in the
     wide layout above this breakpoint. */
  .footer-bottom {
    flex-direction: column;
    align-items: center;
    text-align: center;
  }
}

/* Small screens: sidebar becomes a bottom bar */
@media (max-width: 768px) {
  main,
  .site-footer {
    margin-left: 0;
  }

  /* The bar the footer has to stand apart from is now underneath it rather
     than down its left edge, so a left-to-right sweep no longer separates
     anything - the footer's bottom edge would meet the bar at whatever colour
     the sweep happened to be at that x. Flat --bluebonnet-light instead, which
     keeps the whole boundary a consistent step away from the bar's flat
     --bluebonnet. The banner keeps its sweep: nothing sits beside it here. */
  .site-footer {
    background: var(--bluebonnet-light);
  }

  /* Icon-only here too. There is no hover on touch, so the tooltips are off
     and the labels stay visually hidden - they remain in the markup as the
     links' accessible names for screen readers. */
  .nav-tooltip {
    display: none;
  }

  .band {
    padding: 3.5rem 1.25rem;
  }

  .band-inner {
    max-width: none;
  }

  .hero-content {
    padding: 3rem 1.25rem;
  }

  .page-hero {
    padding: 4rem 1.25rem 3.5rem;
  }

  .app-grid {
    grid-template-columns: 1fr;
  }

  /* One card per row now, so the icon can go back to being large without
     crowding anything. */
  .app-icon {
    max-width: 180px;
  }

  /* The cue would sit under the fixed bottom bar, and touch users already know
     to scroll. */
  .scroll-cue {
    display: none;
  }

  /* Stacked, the pair would run to roughly two screens of decoration on a
     phone, so only the front device survives and it sits straight. */
  .phone-back {
    display: none;
  }

  .phone-front,
  .phones:hover .phone-front {
    transform: none;
  }

  .stats {
    padding: 3rem 1.25rem 0;
  }

  .stats-inner {
    padding-bottom: 3rem;
  }

  body {
    padding-bottom: var(--bottombar-height);
  }

  .sidebar {
    top: auto;
    right: 0;
    bottom: 0;
    width: auto;
    height: var(--bottombar-height);
    flex-direction: row;
    justify-content: space-around;
    align-items: center;
    padding: 0;
  }

  .sidebar-brand {
    display: none;
  }

  .sidebar-nav {
    flex-direction: row;
    justify-content: space-around;
    gap: 0;
    height: 100%;
  }

  .sidebar-nav li {
    flex: 1;
    display: flex;
    justify-content: center;
  }

  .nav-link {
    width: 100%;
    height: 100%;
    border-radius: 0;
    justify-content: center;
    gap: 0;
    padding: 0;
  }

  .nav-link.is-active {
    background: var(--white);
    color: var(--bluebonnet);
  }
}

/* Motion opt-out. Everything above degrades to a static page: no video, no
   drift, no pulse, no reveal, no hover travel, no smooth scrolling. */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  .hero-video {
    display: none;
  }

  /* ::before, not .hero-media - the drift moved onto the pseudo-element when it
     was taken off the video, and targeting the parent here would silently stop
     disabling it. */
  .hero-media::before,
  .hero-kicker .dot,
  .scroll-cue svg {
    animation: none;
  }

  .hero-content h1 .mark::after {
    animation: none;
    transform: scaleX(1);
  }

  .js .reveal {
    opacity: 1;
    transform: none;
    transition: none;
  }

  /* The card buttons are .btn now, and the card itself no longer animates, so
     the .btn entries here cover everything on the apps page. */
  .btn,
  .btn svg,
  .phone {
    transition: none;
  }

  .btn:hover,
  .btn-primary:hover,
  .btn-ghost:hover {
    transform: none;
  }

  .btn:hover svg {
    transform: none;
  }

  .phones:hover .phone-back {
    transform: translateX(38px) rotate(7deg) scale(0.9);
  }

  .phones:hover .phone-front {
    transform: translateX(-38px) rotate(-5deg);
  }
}
