/* =========================================================
   Ouli — shared stylesheet for the static demo prototype
   Exported from Paper (fileId 01M32FTM3WVGDB1HCN1S9WB9G2)
   ========================================================= */

:root {
  --font-sans: "DM Sans", system-ui, sans-serif;
  /* A touch of overshoot (ease-out-back), not a flat deceleration --
     used on the Ouli Agent panel's entrances (messages, chips, the
     panel itself) so they read as settling into place instead of
     stopping dead at the end, which was the "rigid" feeling. */
  --ease-organic: cubic-bezier(0.34, 1.56, 0.64, 1);
  /* For exits, not entrances -- an accelerating curve (nothing
     settles or overshoots) reads as naturally falling/sliding away,
     paired with --ease-organic's bounce for things arriving. Using
     the same curve both directions was part of what made the Ouli
     Agent panel's pill interactions feel mechanical. */
  --ease-exit: cubic-bezier(0.4, 0, 1, 1);

  --color-ink: #121212;
  --color-cream: #F9F8F6;
  --color-offwhite: #F2F0ED;
  --color-white: #FFFFFF;

  --color-gray-999: #999999;
  --color-gray-666: #666666;
  --color-gray-555: #555555;
  --color-gray-333: #333333;
  --color-gray-777: #777777;

  --color-lime: #CEFE04;    /* Launchpad accent (Lime 400) */
  --color-forest: #2B6C41;  /* Knowbook accent */
  --color-purple: #9236F4;  /* Studio accent (Purple 500) */

  --color-lime-300: #38FB22;
  --color-lime-glow: #DBFD25;

  /* Type scale, matched to figma.com's own design tokens
     (--fig-typography-size-*, desktop breakpoint) so this site's
     hierarchy follows the same system: display1 > display2 > title1 >
     title2 > title3 > title4 > bodyXL > bodyL > body > caption.
     Sizes are in rem (root stays at the browser default 16px -- no
     62.5% trick -- so 1rem tracks the user's own font-size setting
     instead of overriding it) and come straight from Figma's own rem
     values. Weight and line-height are Ouli's own -- Figma's headings
     run on a variable font at odd numeric weights (320/540/...) that
     don't exist in DM Sans, so these use DM Sans's actual weight
     stops instead, kept at the bold/black end that's already this
     site's brand voice. */
  --type-display1-size: 6.5rem;   /* fig display1 desktop: 104px @16px root */
  --type-display1-weight: 900;
  --type-display1-leading: 105%;

  --type-display2-size: 4.5rem;   /* fig display2 desktop: 72px */
  --type-display2-weight: 900;
  --type-display2-leading: 108%;

  --type-title1-size: 4rem;       /* fig title1 desktop: 64px */
  --type-title1-weight: 900;
  --type-title1-leading: 110%;

  --type-title2-size: 3rem;       /* fig title2 desktop: 48px */
  --type-title2-weight: 800;
  --type-title2-leading: 115%;

  --type-title3-size: 2rem;       /* fig title3 desktop: 32px */
  --type-title3-weight: 800;
  --type-title3-leading: 120%;

  --type-title4-size: 1.5rem;     /* fig title4 desktop: 24px */
  --type-title4-weight: 700;
  --type-title4-leading: 125%;

  --type-bodyxl-size: 1.5rem;     /* fig bodyXL desktop: 24px */
  --type-bodyxl-weight: 500;
  --type-bodyxl-leading: 150%;

  --type-bodyl-size: 1.125rem;    /* fig bodyL desktop: 18px */
  --type-bodyl-weight: 400;
  --type-bodyl-leading: 155%;

  --type-body-size: 1rem;         /* fig body desktop: 16px */
  --type-body-weight: 400;
  --type-body-leading: 160%;

  --type-caption-size: 0.875rem;  /* fig caption/badge desktop: 14px */
  --type-caption-weight: 600;
  --type-caption-leading: 140%;

  /* Back-compat aliases -- the h1/h2 rules already written against
     these four still resolve the same values. */
  --type-display1: var(--type-display1-size);
  --type-display2: var(--type-display2-size);
  --type-title1: var(--type-title1-size);
  --type-title2: var(--type-title2-size);
  --type-title3: var(--type-title3-size);
  --type-title4: var(--type-title4-size);

  --page-max: 1440px;
}

/* Responsive type scale -- every component references these tokens
   (never a hardcoded font-size), so scaling the scale down at tablet
   and mobile breakpoints reflows the whole site's typography in one
   place instead of touching each component individually. Weight and
   line-height stay put; only size steps down. */
@media (max-width: 1024px) {
  :root {
    --type-display1-size: 4.5rem;   /* 104 -> 72px */
    --type-display2-size: 3.5rem;   /* 72 -> 56px */
    --type-title1-size: 3rem;       /* 64 -> 48px */
    --type-title2-size: 2.25rem;    /* 48 -> 36px */
    --type-title3-size: 1.75rem;    /* 32 -> 28px */
    --type-title4-size: 1.375rem;   /* 24 -> 22px */
    --type-bodyxl-size: 1.375rem;   /* 24 -> 22px */
    --type-bodyl-size: 1.0625rem;   /* 18 -> 17px */
  }
}

@media (max-width: 640px) {
  :root {
    --type-display1-size: 3rem;     /* -> 48px */
    --type-display2-size: 2.5rem;   /* -> 40px */
    --type-title1-size: 2.25rem;    /* -> 36px */
    --type-title2-size: 1.875rem;   /* -> 30px */
    --type-title3-size: 1.5rem;     /* -> 24px */
    --type-title4-size: 1.25rem;    /* -> 20px */
    --type-bodyxl-size: 1.25rem;    /* -> 20px */
    --type-bodyl-size: 1rem;        /* -> 16px */
    --type-body-size: 0.9375rem;    /* -> 15px */
    --type-caption-size: 0.8125rem; /* -> 13px */
  }
}

/* Typography utilities -- one class per type-scale level, applied
   directly on the tag. This is the single place font-size lives now;
   every BEM component class below keeps color/weight/line-height/
   spacing, but not size, so bumping a level only means touching the
   token above or swapping the class on the tag -- never hunting
   through every component rule that happens to reuse that size. */
/* The longest words in display headings ("transformation", "conversation") are about 7 times the font size wide
   and must fit a phone screen minus its 24px gutters, so these headings shrink on narrow phones and nowhere else. */
.t-display1 { font-size: min(var(--type-display1-size), calc((100vw - 48px) / 7)); }
.t-display2 { font-size: var(--type-display2-size); }
.t-title1   { font-size: var(--type-title1-size); }
.t-title2   { font-size: var(--type-title2-size); }
.t-title3   { font-size: var(--type-title3-size); }
.t-title4   { font-size: var(--type-title4-size); }
.t-bodyxl   { font-size: var(--type-bodyxl-size); }
.t-bodyl    { font-size: var(--type-bodyl-size); }
.t-body     { font-size: var(--type-body-size); }
.t-caption  { font-size: var(--type-caption-size); }

* {
  box-sizing: border-box;
}

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

html {
  scroll-behavior: smooth;
  /* The last-section release (animations.js, toggles its position
     between sticky/relative right at the scroll boundary near the
     footer) nudges layout right above the viewport on every frame
     near that threshold -- the browser's scroll anchoring then tries
     to "helpfully" correct scrollTop to compensate, which fights the
     toggle and made the page jump back up on its own instead of
     settling at the bottom. */
  overflow-anchor: none;
}

/* Nav anchor targets (the inline nav links / site menu now jump to
   sections on the landing page instead of separate pages). No
   scroll-margin-top offset here on purpose, even though the sticky
   nav visually overlaps these on landing -- this is a deck-of-cards
   layout (see animations.js) where every .section is position:sticky
   and, once scrolled past, never un-sticks. Offsetting the landing
   position by the nav's height means landing *before* the target
   reaches its own sticky top:0 activation point, which leaves
   whatever section was stuck before it showing through in the gap
   instead of the nav cleanly covering it. Confirmed with a screenshot
   comparison. Each section's own top padding (56-112px depending on
   breakpoint) is already more than the nav's height, so landing flush
   at the section boundary doesn't cover its actual heading either. */

body {
  font-family: var(--font-sans);
  background-color: var(--color-cream);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-synthesis: none;
}

img {
  display: block;
  max-width: 100%;
  height: auto;
}

/* Every <p> on the site is the same size as the Hero subtitle --
   one element-level rule instead of repeating the size in each
   block's own class (those still carry their own color/line-height/
   max-width/margin). */
p {
  font-size: var(--type-bodyxl-size);
}

a {
  color: inherit;
  text-decoration: none;
  cursor: pointer;
  transition: opacity 0.25s ease, color 0.25s ease;
}

.nav__brand:hover,
.nav__link:hover,
.footer__link:hover {
  opacity: 0.72;
}

/* Text-style CTAs (body links, not nav/footer/card links): animated
   underline instead of a plain opacity dim — the arrow moves with it. */
a:not(.nav__brand):not(.nav__link):not(.footer__link):not(.offering-card):not(.offering-card__media):not(.path-panel__media):not(.site-menu__link):not(.site-menu__sublink):not(.site-menu__email):not(.btn-primary) {
  position: relative;
  display: inline-block;
}

a:not(.nav__brand):not(.nav__link):not(.footer__link):not(.offering-card):not(.offering-card__media):not(.path-panel__media):not(.site-menu__link):not(.site-menu__sublink):not(.site-menu__email):not(.btn-primary)::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -2px;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.35s cubic-bezier(.4,0,.2,1);
}

a:not(.nav__brand):not(.nav__link):not(.footer__link):not(.offering-card):not(.offering-card__media):not(.path-panel__media):not(.site-menu__link):not(.site-menu__sublink):not(.site-menu__email):not(.btn-primary):hover::after {
  transform: scaleX(1);
}

a:not(.nav__brand):not(.nav__link):not(.footer__link):not(.offering-card):not(.offering-card__media):not(.path-panel__media):not(.site-menu__link):not(.site-menu__sublink):not(.site-menu__email):not(.btn-primary):hover {
  transform: translateX(2px);
  transition: transform 0.25s cubic-bezier(.4,0,.2,1);
}

/* Primary button -- the solid lime pill, same treatment as the nav's
   .ask-ouli-btn but sized for body-copy CTAs. Reserved for the single
   CTA in a section, or the first/main action when two CTAs appear
   together (the second stays the plain text-link style above). */
.btn-primary {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  gap: 8px;
  background: var(--color-lime);
  color: var(--color-ink);
  padding: 14px 28px;
  border-radius: 999px;
  font-weight: 700;
  width: fit-content;
  flex-shrink: 0;
  transition: transform 0.15s ease;
}

.btn-primary:hover {
  transform: scale(1.03);
}

/* White variant -- for a primary CTA placed on a lime background,
   where the default lime-on-lime pill would lose contrast. */
.btn-primary--white {
  background: var(--color-white);
}

/* =========================================================
   Layout primitives
   ========================================================= */

.section {
  width: 100%;
  box-sizing: border-box;
  position: sticky;
  top: 0;
  will-change: transform, filter;
}

/* The last section in the deck-of-cards stack has nothing after it
   to hold it pinned while it fully covers the one before -- it needs
   to be at least a full viewport tall itself, or the previous section
   stays visible peeking above it once you reach the bottom of the
   page. Applies on every page: the last .section is always a
   <section> tag (the hero, always first, is a <header>). */
body > section.section:last-of-type {
  min-height: 100vh;
}

/* z-index is assigned per section in DOM order by animations.js
   (1, 2, 3…) so later sections stack visually on top of earlier
   ones as they scroll up and over them — a deck-of-cards reveal. */

.section__inner {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 112px 64px;
  box-sizing: border-box;
}

@media (max-width: 1024px) {
  .section__inner {
    padding: 80px 40px;
  }
}

@media (max-width: 640px) {
  .section__inner {
    padding: 56px 24px;
  }
}

.section--dark {
  background-color: var(--color-ink);
  color: var(--color-white);
}

.section--cream {
  background-color: var(--color-cream);
}

.section--offwhite {
  background-color: var(--color-offwhite);
}

/* =========================================================
   Nav
   ========================================================= */

.nav {
  width: 100%;
  background-color: var(--color-ink);
  position: sticky;
  top: 0;
  /* Above .site-menu's overlay (z-index: 1000) -- the toggle button
     lives inside .nav and has to stay clickable/visible on top of
     the overlay it opens, so the user can click it again to close. */
  z-index: 1001;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0);
  transition: box-shadow 0.3s ease;
}

.nav--scrolled {
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.1);
}

.nav__inner {
  max-width: var(--page-max);
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 32px 48px;
  box-sizing: border-box;
}

@media (max-width: 640px) {
  .nav__inner {
    padding: 24px 24px;
  }
}

.nav__brand {
  display: flex;
  align-items: center;
  flex-shrink: 0;
}

.nav__brand-logo {
  height: 32px;
  width: auto;
}

.nav__links {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  gap: 24px;
}

.nav__link {
  color: #FFFFFF80;
  font-family: var(--font-sans);
  font-size: 0.8125rem; /* 13px @16px root -- was hardcoded in px, doesn't scale with the rest of the type system */
  font-weight: 500;
  letter-spacing: 0.08em;
  line-height: 16px;
  text-transform: uppercase;
  white-space: nowrap;
}

.nav__link.is-current {
  color: var(--color-white);
}

/* Inline nav links need real horizontal room for the logo + all 5 --
   below that, fall back to the hamburger + full-screen overlay
   instead of wrapping or squeezing them. Generous margin on purpose
   (not a hairline cutoff) since flex-shrink is off on both sides and
   there's nowhere for the content to go if it doesn't fit. */
@media (max-width: 1024px) {
  .nav__links {
    display: none;
  }
}

/* ---------------------------------------------------------
   Nav menu toggle + full-screen site menu overlay
   --------------------------------------------------------- */

.nav__toggle {
  display: flex;
  align-items: center;
  gap: 16px;
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  cursor: pointer;
  font: inherit;
}

.nav__toggle-label {
  color: var(--color-white);
  font-family: var(--font-sans);
  font-weight: 500;
  letter-spacing: 0.08em;
  line-height: 16px;
  text-transform: uppercase;
  transition: opacity 0.25s ease;
}

.nav__toggle:hover .nav__toggle-label {
  opacity: 0.7;
}

.nav__toggle-icon {
  position: relative;
  width: 22px;
  height: 12px;
  flex-shrink: 0;
}

.nav__toggle-icon span {
  position: absolute;
  left: 0;
  width: 100%;
  height: 1px;
  background-color: var(--color-white);
  transition: transform 0.3s ease, top 0.3s ease, opacity 0.3s ease;
}

.nav__toggle-icon span:nth-child(1) { top: 0; }
.nav__toggle-icon span:nth-child(2) { top: 100%; }

.nav--menu-open .nav__toggle-icon span:nth-child(1) {
  top: 50%;
  transform: translateY(-50%) rotate(45deg);
}

.nav--menu-open .nav__toggle-icon span:nth-child(2) {
  top: 50%;
  transform: translateY(-50%) rotate(-45deg);
}

/* Has to come after the unconditional .nav__toggle rule above --
   same specificity (one class each), so source order is what
   decides the winner. Placed earlier, this was silently losing to
   that rule and the toggle never actually hid at any width, which
   is why "Menu" kept showing next to the inline links regardless of
   viewport size. */
@media (min-width: 1025px) {
  .nav__toggle {
    display: none;
  }
}

.site-menu {
  position: fixed;
  inset: 0;
  z-index: 1000;
  background-color: var(--color-ink);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: 144px 64px 40px;
  box-sizing: border-box;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-16px);
  transition: opacity 0.4s ease, transform 0.4s ease, visibility 0s linear 0.4s;
}

.site-menu.is-open {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  transition: opacity 0.4s ease, transform 0.4s ease, visibility 0s linear 0s;
}

.site-menu__inner {
  max-width: var(--page-max);
  width: 100%;
  margin: 0 auto;
  display: flex;
  justify-content: space-between;
  gap: 64px;
  flex-wrap: wrap;
}

.site-menu__col--links {
  display: flex;
  flex-direction: column;
}

.site-menu__link {
  display: flex;
  align-items: center;
  gap: 16px;
  color: var(--color-white);
  font-family: var(--font-sans);
  font-weight: 900;
  line-height: 128%;
  opacity: 0.4;
  transform: translateY(14px);
  transition: opacity 0.4s ease, transform 0.4s ease;
}

.site-menu.is-open .site-menu__link {
  transform: translateY(0);
}

.site-menu__link:hover,
.site-menu__link:focus-visible {
  opacity: 1;
}

/* Dimmed-until-hover only makes sense where hover exists -- touch
   devices can't hover at all, so this left every menu link stuck at
   0.4 opacity (hard to read white-on-black) permanently on phone. */
@media (hover: none) {
  .site-menu__link {
    opacity: 1;
  }
}

.site-menu__link .dot {
  width: 10px;
  height: 10px;
}

.site-menu__col--info {
  display: flex;
  flex-direction: column;
  gap: 40px;
  padding-top: 16px;
  min-width: 220px;
}

.site-menu__label {
  display: block;
  color: #FFFFFF66;
  font-weight: 700;
  letter-spacing: 0.1em;
  line-height: 16px;
  text-transform: uppercase;
  margin-bottom: 8px;
}

.site-menu__email {
  color: var(--color-white);
  font-weight: 600;
  line-height: 28px;
}

.site-menu__sublink {
  display: block;
  color: #FFFFFFB3;
  line-height: 26px;
}

.site-menu__sublink:hover {
  color: var(--color-white);
}

.site-menu__footer {
  max-width: var(--page-max);
  width: 100%;
  margin: 0 auto;
  display: flex;
  justify-content: space-between;
  color: #FFFFFF66;
  font-weight: 500;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding-top: 32px;
  border-top: 1px solid #FFFFFF1A;
}

body.menu-open {
  overflow: hidden;
}

@media (max-width: 720px) {
  .site-menu {
    padding: 112px 24px 32px;
  }

  .site-menu__inner {
    gap: 40px;
  }

  .site-menu__link {
    font-size: var(--type-title3-size);
  }

  /* space-between pushes the tagline flush to the right edge, right
     under the agent-launcher bubble (fixed, bottom:24px right:24px)
     -- the two collide/clip into each other on a narrow viewport. */
  .site-menu__footer {
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
  }
}

/* =========================================================
   Footer
   ========================================================= */

.footer {
  width: 100%;
  background-color: var(--color-ink);
}

.footer__inner {
  max-width: var(--page-max);
  margin: 0 auto;
  min-height: 120px;
  display: flex;
  align-items: center;
  padding: 24px 48px;
  box-sizing: border-box;
  flex-wrap: wrap;
  gap: 16px;
}

@media (max-width: 640px) {
  .footer__inner {
    padding: 24px 24px;
    justify-content: flex-start;
  }

  .footer__col {
    flex-basis: 100%;
    justify-content: flex-start;
  }

  .footer__col--right {
    gap: 24px;
  }
}

.footer__col {
  flex-basis: 0%;
  flex-grow: 1;
  display: flex;
  align-items: center;
}

.footer__col--center {
  justify-content: center;
}

.footer__col--right {
  justify-content: flex-end;
  gap: 32px;
}

.footer__copyright {
  color: #FFFFFF66;
  line-height: 16px;
}

.footer__tagline {
  color: #FFFFFF33;
  letter-spacing: 0.1em;
  line-height: 16px;
  text-transform: uppercase;
}

.footer__link {
  color: #FFFFFF66;
  line-height: 16px;
}

/* =========================================================
   Shared text patterns
   ========================================================= */

.kicker {
  color: var(--color-gray-999);
  font-weight: 700;
  letter-spacing: 0.12em;
  line-height: 16px;
  text-transform: uppercase;
}

.kicker--on-dark {
  color: #FFFFFF66;
}

.cta-link {
  display: inline-block;
  font-weight: 600;
}

/* small accent dot used by Launchpad (lime) / Studio (purple) / Knowbook (forest) */
.dot {
  width: 6px;
  height: 6px;
  flex-shrink: 0;
  background-color: var(--color-gray-999);
}

.dot--lime { background-color: var(--color-lime); }
.dot--purple { background-color: var(--color-purple); }
.dot--forest { background-color: var(--color-forest); }

.tag-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}

.tag-row__label {
  color: var(--color-gray-999);
  line-height: 16px;
}

/* =========================================================
   Home — Hero
   ========================================================= */

.hero {
  min-height: 900px;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}

.hero .section__inner {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 24px;
}

.hero__title {
  color: var(--color-white);
  font-weight: 900;
  line-height: 105%;
  margin: 0;
}

/* "transformation", "talent" and "business needs" -- the words
   animations.js wraps in per-letter spans -- get the Launchpad lime
   accent instead of white. */
.hero__title span,
.why-ouli__title span,
.work__title span {
  color: var(--color-lime);
}

.usecases__title span {
  color: var(--color-purple);
}

.hero__subtitle {
  color: #FFFFFFBF;
  line-height: 150%;
  max-width: 620px;
  margin: 0;
}

.hero__ctas {
  display: flex;
  align-items: center;
  gap: 32px;
  flex-wrap: wrap;
}

.hero__cta {
  color: var(--color-white);
  font-weight: 600;
  line-height: 22px;
}

.hero__cta--secondary {
  color: var(--color-white);
  opacity: 0.7;
}

/* =========================================================
   Home — Enterprise
   ========================================================= */

/* Three logo rows (unlabeled row, "Trusted by leading companies",
   "Partners") plus the title/text run taller than one viewport at any
   width, not just phones -- sticky + taller-than-a-viewport content
   traps scrolling, so rows past the first screenful never become
   reachable. Same "how-it-works"/"#knowbook" bug, same fix,
   unconditional like those two. animations.js excludes it from the
   sticky recede transform the same way. */
.enterprise {
  position: relative;
}

.enterprise .section__inner {
  display: flex;
  flex-direction: column;
  gap: 64px;
}

.enterprise__body {
  display: flex;
  flex-direction: column;
  gap: 32px;
}

.enterprise__title {
  color: var(--color-ink);
  font-weight: 900;
  line-height: 110%;
  margin: 0;
}

.enterprise__subtitle {
  color: var(--color-ink);
  font-weight: 700;
  line-height: 130%;
}

.enterprise__text {
  color: var(--color-gray-333);
  line-height: 160%;
  max-width: 700px;
  margin: 0;
}

.enterprise__logos {
  width: 100%;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to right, transparent, #000 64px, #000 calc(100% - 64px), transparent);
  mask-image: linear-gradient(to right, transparent, #000 64px, #000 calc(100% - 64px), transparent);
}

@media (max-width: 640px) {
  /* A fixed 64px fade eats a much bigger share of a phone-width
     screen than it does on desktop -- narrower fade keeps more of
     the actual logos visible. */
  .enterprise__logos {
    -webkit-mask-image: linear-gradient(to right, transparent, #000 24px, #000 calc(100% - 24px), transparent);
    mask-image: linear-gradient(to right, transparent, #000 24px, #000 calc(100% - 24px), transparent);
  }
}

/* Two identical sets side by side, animated as one -- translating
   the whole track from -50% to 0 moves it by exactly one set's
   width, so the loop point is seamless (the duplicate set is
   aria-hidden, screen readers only see the real one). */
.enterprise__logos-track {
  display: flex;
  width: max-content;
  animation: enterprise-logos-marquee 36s linear infinite;
}

.enterprise__logos:hover .enterprise__logos-track {
  animation-play-state: paused;
}

/* Static row (Partners, only 4 logos) -- no animation, no edge fade,
   no duplicate set to loop into. */
.enterprise__logos--static {
  mask-image: none;
  -webkit-mask-image: none;
}

.enterprise__logos--static .enterprise__logos-track {
  animation: none;
  width: 100%;
}

/* Only 4 logos -- at their natural content width they sat narrow and
   left-aligned, well short of the section's full measure, leaving a
   large dead gap on the right unlike every other full-width logo row
   on the site. Stretching the set and distributing with
   justify-content lets them spread across the whole available width
   instead (gap stays as a floor, space-between adds whatever's left
   beyond it). */
.enterprise__logos--static .enterprise__logos-set {
  width: 100%;
  justify-content: space-between;
  padding-right: 0;
}

@keyframes enterprise-logos-marquee {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

.enterprise__logos-set {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  gap: 64px;
  padding-right: 64px;
}

.enterprise__logo {
  height: 52px;
  width: auto;
  flex-shrink: 0;
  filter: grayscale(1);
  opacity: 0.5;
  transition: filter 0.3s ease, opacity 0.3s ease;
}

/* Microsoft and WIRED have almost no internal padding in their own
   artwork, so at the shared 52px height they read much larger than
   every other mark in the strip -- scaled down 50% to match the
   others' visual weight. */
.enterprise__logo--compact {
  height: 26px;
}

/* Touch devices rarely (if ever) trigger :hover -- left as-is, every
   logo would sit permanently grayed out with no way to see it in
   color. Show full color by default there instead of gating it
   behind a hover that will never fire. */
@media (hover: none) {
  .enterprise__logo {
    filter: grayscale(0);
    opacity: 1;
  }
}

.enterprise__logo:hover {
  filter: grayscale(0);
  opacity: 1;
}

/* Partners row: half the shared logo height, as it was until 2026-10-08. Anthropic's mark is 35% smaller again to
   match the others' visual weight. */
.enterprise__logos--static .enterprise__logo {
  height: 26px;
}
.enterprise__logos--static .enterprise__logo--anthropic {
  height: 17px;
}
@media (max-width: 640px) {
  /* Four logos with 64px gaps are wider than a phone, and the row clips what does not fit. Wrap instead. */
  .enterprise__logos--static .enterprise__logos-set {
    flex-wrap: wrap;
    justify-content: flex-start;
    gap: 32px 40px;
  }
}

/* The team's former companies, under "Why us" on a dark background: half height, and the marks flattened to white
   at low opacity so dark wordmarks stay visible. No color on hover or touch, unlike the client strip. */
.enterprise__logos--team .enterprise__logo,
.enterprise__logos--team .enterprise__logo:hover {
  height: 26px;
  filter: grayscale(1) brightness(0) invert(1);
  opacity: 0.5;
}

@media (prefers-reduced-motion: reduce) {
  .enterprise__logos-track {
    animation: none;
  }
}

/* =========================================================
   Home — What we do
   ========================================================= */
.what-we-do .section__inner {
  display: flex;
  flex-direction: column;
  gap: 64px;
}

.what-we-do__header {
  display: flex;
  flex-direction: column;
  gap: 24px;
  max-width: 720px;
}

.what-we-do__title {
  color: var(--color-ink);
  font-weight: 900;
  line-height: 110%;
  margin: 0;
}

.what-we-do__text {
  color: var(--color-gray-333);
  line-height: 155%;
  max-width: 600px;
  margin: 0;
}

.what-we-do__stats {
  display: flex;
  gap: 1px;
  flex-wrap: wrap;
  background: var(--color-ink);
  border: 1px solid var(--color-ink);
}

.what-we-do__stats-item {
  flex: 1 1 240px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  background: var(--color-offwhite);
  padding: 24px;
}

.what-we-do__stats-label {
  color: var(--color-ink);
  font-weight: 700;
  line-height: 125%;
  margin: 0;
}

.what-we-do__stats-desc {
  color: var(--color-gray-333);
  line-height: 140%;
  margin: 0;
}

.what-we-do__tagline {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.what-we-do__tagline-lead {
  color: var(--color-ink);
  font-weight: 800;
  line-height: 140%;
  margin: 0;
}

.what-we-do__tagline-text {
  color: var(--color-gray-333);
  line-height: 150%;
  margin: 0;
}

/* =========================================================
   Home — Use Cases (horizontal snap carousel)
   ========================================================= */

/* Text left, carousel right (openai.com/business/customer-stories
   layout) -- wraps to stacked on narrow viewports, same technique
   used for every other side-by-side section on the site. */
.usecases .section__inner {
  display: flex;
  align-items: center;
  gap: 64px;
  flex-wrap: wrap;
}

.usecases__header {
  flex: 1 1 380px;
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.usecases__title {
  color: var(--color-ink);
  font-weight: 900;
  line-height: 110%;
  margin: 0;
}

.usecases__text {
  color: var(--color-gray-333);
  line-height: 150%;
  margin: 0;
}

/* Stacked-card carousel (openai.com/business/customer-stories
   pattern): one active card in front, the next two peeking
   behind it top-left, arrows step through the deck with a
   slide + cross-fade transition -- positions are set inline by
   animations.js so the transform/opacity can be interpolated
   by the CSS transition below on every step. */
.usecases-stack-wrap {
  flex: 1 1 570px;
  display: flex;
  flex-direction: column;
  gap: 32px;
  min-width: 0;
}

.usecases-stack {
  position: relative;
  height: 460px;
}

.usecases-stack .usecases-carousel__card {
  position: absolute;
  top: 0;
  left: 0;
  width: min(570px, 100%);
  min-height: 460px;
  border-radius: 8px;
  background: var(--color-cream);
  padding: 48px;
  display: flex;
  flex-direction: column;
  cursor: default;
  overflow: hidden;
  transition: transform 0.6s cubic-bezier(.22,1,.36,1), opacity 0.5s ease;
}

/* Narrower card = more text wrapping, so the longest description
   needs more vertical room than the desktop 460px -- the stack's
   height grows to fit instead of clipping, and padding tightens up
   so it doesn't eat most of a phone-width card. */
@media (max-width: 640px) {
  .usecases-stack {
    height: 560px;
  }

  .usecases-stack .usecases-carousel__card {
    min-height: 560px;
    padding: 32px;
  }
}

/* Live animated mesh-gradient (WebGL, @paper-design/shaders) fills
   the first few cards edge to edge instead of a static image -- see
   shaders.js. The shader mount injects its own canvas + positioning
   via a self-authored stylesheet (position:absolute; inset:0), so
   this card just needs to stay a positioned container; a dark scrim
   keeps the title/desc readable stacked above it. */
/* Scoped with the same .usecases-stack ancestor as the base card rule
   (matching specificity, 2 classes each) -- a bare .usecases-carousel__card--shader
   rule was silently losing to `.usecases-stack .usecases-carousel__card`'s
   padding, since that selector counts two classes against this one's one.
   The card kept a 48px/28px border instead of the shader reaching edge
   to edge like a real background. */
.usecases-stack .usecases-carousel__card--shader {
  padding: 0;
  justify-content: flex-end;
}

/* The mesh-gradient presets run light/pastel (cyan, cream, lime,
   tan) -- a stronger, taller scrim than a typical dark photo needs
   to keep white text reliably readable regardless of which preset
   a card randomly gets (see shaders.js). */
.usecases-carousel__card--shader::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(18, 18, 18, 0.88) 0%, rgba(18, 18, 18, 0.45) 40%, rgba(18, 18, 18, 0.1) 68%, transparent 85%);
  z-index: 1;
  pointer-events: none;
}

.usecases-carousel__card--shader .usecases-carousel__content {
  position: relative;
  z-index: 2;
  padding: 32px;
}

.usecases-carousel__card--shader .usecases-carousel__title,
.usecases-carousel__card--shader .usecases-carousel__desc {
  color: var(--color-white);
}

/* Default state is just the title -- the description only reveals
   on hover, stacked below it in the same content block. */
.usecases-carousel__card--shader .usecases-carousel__desc {
  color: #FFFFFFCC;
  max-height: 0;
  opacity: 0;
  padding-top: 0;
  overflow: hidden;
  transition: max-height 0.45s cubic-bezier(.4,0,.2,1), opacity 0.3s ease, padding-top 0.3s ease;
}

.usecases-carousel__card--shader:hover .usecases-carousel__desc {
  max-height: 220px;
  opacity: 1;
  padding-top: 16px;
}

/* Touch devices can't hover -- a hover-only reveal would make the
   description permanently unreachable there instead of just less
   convenient. Show it plainly instead of gating it behind a gesture
   that will never fire. */
@media (hover: none) {
  .usecases-carousel__card--shader .usecases-carousel__desc {
    max-height: none;
    opacity: 1;
    padding-top: 16px;
  }
}


/* A quiet fill-tint that sweeps up from the bottom on hover, behind
   the text — reads as a highlight, not a button. */
/* Semi-transparent on purpose -- solid ink completely blacked out
   the animated shader background on hover, which read as an abrupt
   flat cut instead of a highlight. */
.usecases-carousel__card::before {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(18, 18, 18, 0.3);
  transform: translateY(100%);
  transition: transform 0.45s cubic-bezier(.4,0,.2,1);
  z-index: 0;
}

.usecases-carousel__card:hover::before {
  transform: translateY(0);
}

.usecases-carousel__title,
.usecases-carousel__desc {
  position: relative;
  z-index: 1;
  transition: color 0.35s ease;
}

.usecases-carousel__title {
  color: var(--color-ink);
  font-weight: 800;
  line-height: 125%;
  transition: color 0.35s ease, transform 0.35s cubic-bezier(.4,0,.2,1);
}

.usecases-carousel__card:hover .usecases-carousel__title {
  color: var(--color-white);
  transform: translateX(2px);
}

.usecases-carousel__desc {
  color: var(--color-gray-555);
  font-size: var(--type-bodyl-size);
  line-height: 150%;
  padding-top: 16px;
  margin: 0;
  transition: color 0.35s ease;
}

.usecases-carousel__card:hover .usecases-carousel__desc {
  color: #FFFFFFB3;
}

/* Controls: a thin progress line + two square arrow buttons.
   No pills, no fills at rest — matches the CTA-link language
   used everywhere else on the site. */
.usecases-carousel__controls {
  display: flex;
  align-items: center;
  gap: 32px;
}

.usecases-carousel__progress {
  flex: 1;
  height: 1px;
  background: rgba(18, 18, 18, 0.15);
  position: relative;
}

.usecases-carousel__progress-bar {
  position: absolute;
  top: 0;
  left: 0;
  height: 1px;
  background: var(--color-ink);
  width: 25%;
  transition: width 0.15s ease-out;
}

.usecases-carousel__arrows {
  display: flex;
  gap: 8px;
  flex-shrink: 0;
}

.usecases-carousel__arrow {
  width: 40px;
  height: 40px;
  border: 1px solid var(--color-ink);
  background: transparent;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-sans);
  line-height: 1;
  color: var(--color-ink);
  cursor: pointer;
  padding: 0;
  transition: background-color 0.25s ease, color 0.25s ease;
}

.usecases-carousel__arrow:hover:not(:disabled) {
  background: var(--color-ink);
  color: var(--color-white);
}

.usecases-carousel__arrow:disabled {
  opacity: 0.25;
  cursor: default;
}

/* =========================================================
   Home — How It Works (offering cards w/ thumbnail image)
   ========================================================= */

/* Taller than one viewport (header + 3 stacked product cards) --
   left sticky, it would trap scrolling and jump straight to the next
   section before the rest of it is reachable. Scrolls normally instead.
   Stays "relative" rather than "static" so its z-index (assigned by
   animations.js) still applies -- otherwise the still-sticky section
   behind it would stack on top and cover it while it scrolls past. */
.how-it-works {
  position: relative;
}

.how-it-works .section__inner {
  display: flex;
  flex-direction: column;
  gap: 56px;
}

.how-it-works__header {
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.how-it-works__kicker-title {
  color: var(--color-ink);
  font-weight: 900;
  line-height: 110%;
  max-width: 1050px;
  margin: 0;
}

.how-it-works__lede {
  color: var(--color-ink);
  font-weight: 500;
  line-height: 24px;
  margin: 0;
}

.how-it-works__text {
  color: var(--color-gray-333);
  line-height: 160%;
  max-width: 680px;
  margin: 0;
}

.how-it-works__unsure {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 20px;
}

/* Media on one side, copy on the other -- alternates with
   .offering-card--reverse so a row of these doesn't read as the same
   card repeated three times. Wraps to stacked (media above copy) once
   there isn't room for both side by side. */
/* No media column anymore (see .offering-card__media below, now
   unused -- placeholders were reading as unfinished filler rather
   than content, and there's no real photography for this page yet).
   .offering-card is just the info row now: "title+CTA" on the left,
   "labeled outcome/points" on the right -- the same two-column split
   the Launchpad/Studio pages already use. */
.offering-card {
  display: flex;
  gap: 64px;
  flex-wrap: wrap;
}

.offering-card__media {
  flex: 1.3 1 520px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-width: 0;
}

.offering-card__info {
  display: contents;
}

.offering-card__info-left {
  flex: 1.1 1 320px;
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.offering-card__info-right {
  flex: 1 1 320px;
  display: flex;
  flex-direction: column;
  gap: 24px;
}

/* Same focus-pull entrance as the Use Cases cards: blur, not fade. */
.offering-card.reveal {
  opacity: 1;
  transform: translateY(22px) scale(0.97);
  filter: blur(14px);
  transition: transform 0.6s cubic-bezier(.22,1,.36,1), filter 0.6s cubic-bezier(.22,1,.36,1);
}

.offering-card.reveal.is-visible {
  transform: translateY(0) scale(1);
  filter: blur(0);
}

.offering-card__thumb-wrap {
  width: 100%;
  aspect-ratio: 1312 / 738;
  overflow: hidden;
  border-radius: 8px;
}

/* Knowbook's card shows a real, live product screenshot (an iframe)
   instead of a static image -- same fixed-size embed technique used
   on the Knowbook and Knowbook App pages, since it needs a known
   pixel size to scale the embedded page down reliably. */
/* Fixed 1160x725 on desktop, but fluid below that -- container query
   units let the iframe's fit-to-frame scale track the frame's actual
   rendered width continuously, instead of only being correct at one
   hardcoded pixel size. */
.offering-card__thumb-wrap--live {
  container-type: inline-size;
  width: 100%;
  max-width: 1160px;
  aspect-ratio: 1160 / 725;
  height: auto;
  margin: 0 auto;
}

.offering-card__thumb-wrap--live iframe {
  display: block;
  width: 1280px;
  height: 800px;
  border: 0;
  transform: scale(calc(100cqw / 1280px));
  transform-origin: top left;
  transition: transform 0.6s cubic-bezier(.4,0,.2,1);
}

/* Same hover-zoom the static thumbnails get, applied on top of the
   base fit-to-frame scale instead of replacing it. */
.offering-card__media:hover .offering-card__thumb-wrap--live iframe {
  transform: scale(calc(100cqw / 1280px * 1.045));
}

/* A real photo can sit in the same live-embed frame -- fills it with
   object-fit instead of the iframe's fit-to-frame transform trick. */
.offering-card__thumb-wrap--live img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.6s cubic-bezier(.4,0,.2,1);
}

.offering-card__media:hover .offering-card__thumb-wrap--live img {
  transform: scale(1.045);
}

.offering-card__thumb {
  width: 100%;
  height: 100%;
  aspect-ratio: 1312 / 738;
  object-fit: cover;
  background-color: var(--color-gray-999);
  transition: transform 0.6s cubic-bezier(.4,0,.2,1);
}

/* Placeholder media -- same size/shape as the real image and iframe
   thumbnails so cards line up, but a flat fill instead of borrowing
   Home's actual photos/screenshots for a page that isn't ready to
   show real ones yet. */
/* Same footprint as Home's real hero photos (.offering-card__thumb-wrap--live
   on .path-panel__media): capped at 1160px wide, 1160:725 ratio. */
.offering-card__placeholder {
  width: 100%;
  max-width: 1160px;
  aspect-ratio: 1160 / 725;
  height: auto;
  margin: 0 auto;
  border-radius: 8px;
  background-color: var(--color-gray-999);
  display: flex;
  align-items: center;
  justify-content: center;
}

.offering-card__placeholder-label {
  color: var(--color-gray-666);
}

.offering-card__media:hover .offering-card__thumb {
  transform: scale(1.045);
}

.offering-card__name {
  transition: transform 0.25s cubic-bezier(.4,0,.2,1);
}

.offering-card__media:hover .offering-card__name {
  transform: translateX(4px);
}

.offering-card__foot {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px;
}

.offering-card__name {
  color: var(--color-ink);
  font-weight: 900;
  line-height: 110%;
}

.offering-card__body {
  color: var(--color-gray-333);
  line-height: 160%;
  max-width: 680px;
  margin: 0;
}

.offering-card__body--prompt {
  color: var(--color-ink);
  font-weight: 700;
}

.offering-card__tagline {
  color: var(--color-ink);
  font-weight: 700;
  line-height: 130%;
  margin: 0;
}

/* Knowbook's teaser is several short lines meant to read as one
   flowing passage -- a tighter internal gap keeps them from reading
   as separate, equally-weighted paragraphs like the rows below. */
.offering-card__body-group {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

/* Labeled, divider-bound rows -- same pattern as the "Best for /
   Format / Includes / Outcome" rows on the Launchpad/Studio pages,
   so an offering's home-page teaser and its full page read as one
   consistent system instead of two different typographic voices. */
.offering-card__row {
  max-width: 680px;
  border-top: 1px solid #121212;
  padding-top: 16px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.offering-card__row--last {
  border-bottom: 1px solid #121212;
  padding-bottom: 16px;
}

/* Same flat card look as Process/Learn (cream fill, rounded corners)
   instead of the plain divider-line row -- used for Work with AI /
   Build with AI's Outcome, which otherwise sat as bare text with a
   lot of empty space around it in the right column. */
.offering-card__row--card {
  border-top: none;
  /* White, not cream -- this section's own background alternates
     cream/offwhite between cards, and a cream fill would disappear
     against the cream one. White holds contrast against both. */
  background: var(--color-white);
  border-radius: 8px;
  padding: 24px;
}

.offering-card__row-label {
  color: var(--color-gray-999);
  font-weight: 600;
  letter-spacing: 0.08em;
  line-height: 16px;
  text-transform: uppercase;
}

.offering-card__row-desc {
  color: var(--color-gray-333);
  line-height: 150%;
  margin: 0;
}

.offering-card__includes-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 0;
  padding-left: 0;
  list-style: none;
}

.offering-card__includes-list li {
  color: var(--color-gray-333);
  font-size: var(--type-body-size);
  line-height: 150%;
}

.offering-card__cta {
  color: var(--color-ink);
  font-weight: 600;
  line-height: 20px;
  display: inline-block;
  width: fit-content;
  padding-top: 8px;
}

/* ---------------------------------------------------------
   How It Works -- two tabbed paths (Work with AI / Build with
   AI) instead of three identical stacked cards, plus Knowbook
   as a separate flagship product with its own carousel.
   --------------------------------------------------------- */

/* Vertical tab list (chatgpt.com/overview pattern): a plain stacked
   list separated by hairlines, no pills or background fills -- the
   active item is distinguished purely by expanding to show its
   name + description; inactive items collapse to just the label. */
.path-layout {
  display: flex;
  gap: 64px;
  align-items: flex-start;
}

.path-tabs-v {
  flex: 0 0 320px;
  display: flex;
  flex-direction: column;
}

/* Used standalone (Offerings' Knowbook card has no media panel to sit
   beside, unlike Home's .path-layout usage) -- fixed 320px doesn't
   apply without a sibling to share the row with. */
.path-tabs-v--full {
  flex-basis: auto;
  width: 100%;
}

/* Below this, side-by-side no longer fits -- flex-wrap alone would
   leave the 320px tab list stranded on its own row without growing
   to fill it (dead space next to it, content dropping awkwardly
   below). A clean full-width stack instead: tabs on top, content
   below, each spanning the full row. Placed after both base rules
   above so its override actually wins the cascade instead of being
   silently reset by them (equal specificity, later wins). */
@media (max-width: 900px) {
  .path-layout {
    flex-direction: column;
  }

  /* align-items:flex-start on .path-layout means stacked children
     shrink to their own content width instead of stretching -- the
     media panel (a percentage-sized image/iframe inside) needs an
     explicit 100% or it can collapse toward zero width with nothing
     definite to size against. Every child of the stack needs this,
     not just the tab list. */
  .path-tabs-v,
  .path-content,
  .path-panel__media,
  .path-subpanel-media {
    flex-basis: auto;
    width: 100%;
  }
}

.path-tabs-v__rule {
  height: 1px;
  background: var(--color-ink);
  opacity: 0.15;
}

.path-tab-v {
  display: block;
  width: 100%;
  padding: 24px 0;
  border: none;
  background: none;
  cursor: pointer;
  text-align: left;
  font-family: var(--font-sans);
}

.path-tab-v .kicker {
  margin-bottom: 6px;
}

.path-tab-v__label {
  display: block;
  color: var(--color-ink);
  font-weight: 800;
  line-height: 125%;
  opacity: 0.4;
  transition: opacity 0.25s ease;
}

.path-tab-v:hover .path-tab-v__label {
  opacity: 0.7;
}

.path-tab-v.is-active .path-tab-v__label {
  opacity: 1;
}

.path-tab-v__expand {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 0;
  max-height: 0;
  opacity: 0;
  overflow: hidden;
  transition: max-height 0.4s cubic-bezier(.4,0,.2,1), opacity 0.3s ease, margin-top 0.3s ease;
}

.path-tab-v.is-active .path-tab-v__expand {
  max-height: 240px;
  opacity: 1;
  margin-top: 16px;
}

.path-tab-v__name {
  color: var(--color-ink);
  font-weight: 400;
  line-height: 140%;
}

.path-tab-v__desc {
  color: var(--color-gray-333);
  font-size: var(--type-bodyl-size);
  line-height: 150%;
  margin: 0;
}

.path-content {
  flex: 1 1 auto;
  min-width: 0;
}

.path-panel {
  display: none;
  flex-direction: column;
  gap: 48px;
}

.path-panel.is-active {
  display: flex;
}

.path-panel__media {
  display: block;
  flex: 1 1 auto;
  min-width: 0;
}

.path-panel__desc {
  color: var(--color-gray-333);
  font-size: var(--type-bodyl-size);
  line-height: 150%;
  margin: 0;
}

.path-panel__tagline {
  color: var(--color-ink);
  font-weight: 600;
  line-height: 140%;
  margin: 0;
}

/* Knowbook's 5 sub-tabs each get their own screenshot instead of
   sharing one static image -- only the active one renders, so the
   blur-to-sharp reveal (see path-media-focus-in) replays each time
   a sub-tab swaps which one that is. */
.path-subpanel-media {
  flex: 1 1 auto;
  min-width: 0;
  position: relative;
}

.path-subpanel-media__item {
  display: none;
}

.path-subpanel-media__item.is-active {
  display: block;
}

/* A very slight idle breathing motion so the product screenshot
   doesn't read as a static image -- multiplies the same fit-to-frame
   scale the iframe already needs (see .offering-card__thumb-wrap--live
   iframe), so it keeps sizing correctly while it drifts. */
@keyframes path-media-breathe {
  0%, 100% { transform: scale(calc(100cqw / 1280px * 1)); }
  50% { transform: scale(calc(100cqw / 1280px * 1.012)); }
}

/* Blur-to-sharp on reveal -- .path-panel goes display:none -> flex
   when its tab becomes active, which is enough on its own to replay
   any animation on its descendants (paused/reset while display:none,
   starts fresh the moment it renders again), so this doesn't need an
   .is-active-specific selector. */
@keyframes path-media-focus-in {
  from { filter: blur(16px); opacity: 0.5; }
  to { filter: blur(0); opacity: 1; }
}

.path-panel__media .offering-card__thumb-wrap--live iframe {
  animation: path-media-breathe 9s ease-in-out infinite, path-media-focus-in 0.7s cubic-bezier(.22,1,.36,1);
}

.path-panel__media .offering-card__thumb-wrap--live img {
  animation: path-media-focus-in 0.7s cubic-bezier(.22,1,.36,1);
}

.path-steps {
  display: flex;
  gap: 40px;
  flex-wrap: wrap;
}

.path-steps__item {
  flex: 1 1 240px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  border-top: 1px solid var(--color-ink);
  padding-top: 24px;
}

.path-steps__label {
  color: var(--color-ink);
  font-weight: 800;
  line-height: 125%;
}

.path-steps__desc {
  color: var(--color-gray-333);
  font-size: var(--type-bodyl-size);
  line-height: 150%;
  margin: 0;
}

.path-panel__outcome {
  color: var(--color-gray-333);
  font-size: var(--type-bodyl-size);
  line-height: 150%;
  margin: 0;
}

/* Knowbook -- always visible, independent from the Work/Build
   tab pair above, but reuses the same vertical-tabs pattern for
   its own 5 sub-points. */
.knowbook-block {
  display: flex;
  flex-direction: column;
  gap: 40px;
  padding-top: 56px;
  border-top: 1px solid var(--color-ink);
}

/* Taller than one viewport at any width (the card row + the 4-point
   list below it run past it) -- sticky here would trap scrolling
   exactly like .how-it-works would without its own position:relative,
   leaving the list's tail end unreachable. Stays "relative" rather
   than "static" so its JS-assigned z-index still applies. */
#knowbook {
  position: relative;
}

/* The points list is a full-width row below the card, not squeezed
   into its ~650px left column, so all 4 items fit in one line like
   Home's needs list does. */
#knowbook .section__inner {
  display: flex;
  flex-direction: column;
  gap: 64px;
}

.knowbook-block__header {
  display: flex;
  flex-direction: column;
  gap: 16px;
  max-width: 720px;
}

.knowbook-block__keywords {
  color: var(--color-gray-999);
  line-height: 16px;
  margin: 0;
}

.knowbook-block__tagline {
  color: var(--color-ink);
  font-weight: 800;
  line-height: 140%;
  margin: 0;
}

.knowbook-block__ctas {
  display: flex;
  align-items: center;
  gap: 32px;
  flex-wrap: wrap;
}

/* =========================================================
   Home — Knowbook: Connected ecosystem visual
   ========================================================= */

/* The whole diagram (blobs, connector lines, chips, center card) is
   authored at a fixed 1440x940 canvas -- text, icons, gaps and the
   connector-line anchors all need to shrink together to stay lined
   up, so this scales as one image rather than reflowing like normal
   responsive UI. --kb-scale is set by animations.js from the
   wrapper's real rendered width. */
.knowbook-ecosystem {
  width: 100%;
  aspect-ratio: 1440 / 940;
  position: relative;
  overflow: hidden;
}

.knowbook-ecosystem__canvas {
  width: 1440px;
  height: 940px;
  position: absolute;
  top: 0;
  left: 0;
  transform: scale(var(--kb-scale, 1));
  transform-origin: top left;
}

.knowbook-ecosystem__blob {
  position: absolute;
  border-radius: 50%;
  box-sizing: border-box;
}

.knowbook-ecosystem__blob--forest-1 {
  background: var(--color-forest);
  opacity: 0.28;
  filter: blur(90px);
  width: 420px;
  height: 420px;
  left: 180px;
  top: 160px;
}

.knowbook-ecosystem__blob--lime {
  background: var(--color-lime);
  opacity: 0.22;
  filter: blur(90px);
  width: 380px;
  height: 380px;
  left: 860px;
  top: 300px;
}

.knowbook-ecosystem__blob--forest-2 {
  background: var(--color-forest);
  opacity: 0.2;
  filter: blur(80px);
  width: 320px;
  height: 320px;
  left: 760px;
  top: 160px;
}

.knowbook-ecosystem__lines {
  position: absolute;
  top: 0;
  left: 0;
}

.knowbook-ecosystem__center {
  position: absolute;
  left: 470px;
  top: 300px;
  width: 500px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
}

.knowbook-ecosystem__card {
  width: 100%;
  height: 316px;
  flex-shrink: 0;
  border: 1px solid #FFFFFFCC;
  border-radius: 4px;
  overflow: hidden;
  box-shadow: 0 24px 48px #1212121A;
  box-sizing: border-box;
}

.knowbook-ecosystem__card img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.knowbook-ecosystem__header {
  position: absolute;
  left: 0;
  top: 48px;
  width: 1440px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}

.knowbook-ecosystem__kicker {
  font-family: var(--font-sans);
  font-size: var(--type-caption-size);
  font-weight: 700;
  letter-spacing: 0.12em;
  line-height: 18px;
  text-transform: uppercase;
  color: var(--color-gray-999);
}

.knowbook-ecosystem__title {
  font-family: var(--font-sans);
  font-size: var(--type-title3-size);
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 40px;
  color: var(--color-ink);
}

.knowbook-ecosystem__chip {
  position: absolute;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: 14px;
  height: 64px;
  width: 300px;
  padding-inline: 20px;
  border-radius: 999px;
  border: 1px solid #FFFFFFB3;
  background: #FFFFFF73;
  backdrop-filter: blur(20px);
  box-shadow: 0 8px 32px #12121214;
}

.knowbook-ecosystem__chip-icon {
  width: 36px;
  height: 36px;
  border-radius: 10px;
  flex-shrink: 0;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
}

.knowbook-ecosystem__chip-label {
  font-family: var(--font-sans);
  font-size: var(--type-body-size);
  font-weight: 500;
  line-height: 20px;
  color: var(--color-ink);
}

.knowbook-ecosystem__chip--salesforce { left: 140px; top: 240px; }
.knowbook-ecosystem__chip--sap { left: 140px; top: 344px; }
.knowbook-ecosystem__chip--excel { left: 140px; top: 448px; }
.knowbook-ecosystem__chip--ms365 { left: 1000px; top: 240px; }
.knowbook-ecosystem__chip--gdrive { left: 1000px; top: 344px; }
.knowbook-ecosystem__chip--systems { left: 1000px; top: 448px; }

.knowbook-ecosystem__group {
  position: absolute;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: 14px;
  height: 64px;
  width: 260px;
  padding-inline: 18px;
  border-radius: 32px;
  border: 1px solid #FFFFFFB3;
  background: #FFFFFF73;
  backdrop-filter: blur(20px);
  box-shadow: 0 8px 32px #12121214;
}

.knowbook-ecosystem__group--people { left: 440px; top: 780px; }
.knowbook-ecosystem__group--agents { left: 740px; top: 780px; }

.knowbook-ecosystem__avatars {
  position: relative;
  width: 84px;
  height: 40px;
  flex-shrink: 0;
}

.knowbook-ecosystem__avatar {
  position: absolute;
  top: 0;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 2px solid var(--color-white);
  overflow: hidden;
  box-sizing: border-box;
}

.knowbook-ecosystem__avatar:nth-child(1) { left: 0; }
.knowbook-ecosystem__avatar:nth-child(2) { left: 22px; }
.knowbook-ecosystem__avatar:nth-child(3) { left: 44px; }

.knowbook-ecosystem__avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.knowbook-ecosystem__avatar--agent {
  background: var(--color-lime);
  display: flex;
  align-items: center;
  justify-content: center;
}

.knowbook-ecosystem__avatar--agent img {
  width: 24px;
  height: 24px;
  object-fit: contain;
}

.knowbook-ecosystem__group-label {
  font-family: var(--font-sans);
  font-size: 16px;
  font-weight: 500;
  line-height: 20px;
  color: var(--color-ink);
}

/* Dedicated mobile layout (Paper artboard "Knowbook — Connected
   ecosystem (Mobile)") -- a 2-column chip grid instead of the desktop
   radial diagram, authored at a 390x900 baseline canvas. Swapped in
   below the same breakpoint the nav collapses to the hamburger menu. */
.knowbook-ecosystem-mobile {
  display: none;
  width: 100%;
  aspect-ratio: 390 / 900;
  position: relative;
  overflow: hidden;
}

@media (max-width: 640px) {
  .knowbook-ecosystem { display: none; }
  .knowbook-ecosystem-mobile { display: block; }
}

.knowbook-ecosystem-mobile__canvas {
  width: 390px;
  height: 900px;
  position: absolute;
  top: 0;
  left: 0;
  transform: scale(var(--kbm-scale, 1));
  transform-origin: top left;
}

.knowbook-ecosystem-mobile__blob {
  position: absolute;
  border-radius: 50%;
  box-sizing: border-box;
}

.knowbook-ecosystem-mobile__blob--forest {
  background: var(--color-forest);
  opacity: 0.25;
  filter: blur(70px);
  width: 200px;
  height: 200px;
  left: 95px;
  top: 120px;
}

.knowbook-ecosystem-mobile__blob--lime {
  background: var(--color-lime);
  opacity: 0.2;
  filter: blur(70px);
  width: 200px;
  height: 200px;
  left: 95px;
  top: 512px;
}

.knowbook-ecosystem-mobile__lines {
  position: absolute;
  top: 0;
  left: 0;
}

.knowbook-ecosystem-mobile__header {
  position: absolute;
  left: 24px;
  top: 40px;
  width: 342px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}

.knowbook-ecosystem-mobile__kicker {
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.12em;
  line-height: 16px;
  text-transform: uppercase;
  color: var(--color-gray-999);
}

.knowbook-ecosystem-mobile__title {
  font-family: var(--font-sans);
  font-size: 24px;
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 30px;
  text-align: center;
  color: var(--color-ink);
}

.knowbook-ecosystem-mobile__card {
  position: absolute;
  left: 24px;
  top: 170px;
  width: 342px;
  height: 216px;
  flex-shrink: 0;
  border: 1px solid var(--color-border, #E2E4E7);
  border-radius: 4px;
  overflow: hidden;
  box-shadow: 0 16px 32px #1212121A;
  box-sizing: border-box;
}

.knowbook-ecosystem-mobile__card img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

.knowbook-ecosystem-mobile__chip {
  position: absolute;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: 10px;
  height: 56px;
  width: 163px;
  padding-inline: 14px;
  border-radius: 999px;
  border: 1px solid var(--color-border, #E2E4E7);
  background: var(--color-white);
  box-shadow: 0 2px 6px #1212120D;
}

.knowbook-ecosystem-mobile__chip-icon {
  width: 30px;
  height: 30px;
  border-radius: 9px;
  flex-shrink: 0;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
}

.knowbook-ecosystem-mobile__chip-label {
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 500;
  line-height: 18px;
  color: var(--color-ink);
}

.knowbook-ecosystem-mobile__chip--salesforce { left: 24px; top: 438px; }
.knowbook-ecosystem-mobile__chip--ms365 { left: 203px; top: 438px; }
.knowbook-ecosystem-mobile__chip--sap { left: 24px; top: 510px; }
.knowbook-ecosystem-mobile__chip--gdrive { left: 203px; top: 510px; }
.knowbook-ecosystem-mobile__chip--excel { left: 24px; top: 582px; }
.knowbook-ecosystem-mobile__chip--systems { left: 203px; top: 582px; }
.knowbook-ecosystem-mobile__chip--systems .knowbook-ecosystem-mobile__chip-label { font-size: 13px; line-height: 16px; }

.knowbook-ecosystem-mobile__group {
  position: absolute;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: 8px;
  height: 56px;
  width: 163px;
  padding-inline: 10px;
  border-radius: 999px;
  border: 1px solid var(--color-border, #E2E4E7);
  background: var(--color-white);
  box-shadow: 0 2px 6px #1212120D;
}

.knowbook-ecosystem-mobile__group--people { left: 24px; top: 678px; }
.knowbook-ecosystem-mobile__group--agents { left: 203px; top: 678px; }

.knowbook-ecosystem-mobile__avatars {
  position: relative;
  width: 62px;
  height: 30px;
  flex-shrink: 0;
}

.knowbook-ecosystem-mobile__avatar {
  position: absolute;
  top: 0;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 2px solid var(--color-white);
  overflow: hidden;
  box-sizing: border-box;
}

.knowbook-ecosystem-mobile__avatar:nth-child(1) { left: 0; }
.knowbook-ecosystem-mobile__avatar:nth-child(2) { left: 16px; }
.knowbook-ecosystem-mobile__avatar:nth-child(3) { left: 32px; }

.knowbook-ecosystem-mobile__avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.knowbook-ecosystem-mobile__avatar--agent {
  background: var(--color-lime);
  display: flex;
  align-items: center;
  justify-content: center;
}

.knowbook-ecosystem-mobile__avatar--agent img {
  width: 18px;
  height: 18px;
  object-fit: contain;
}

.knowbook-ecosystem-mobile__group-label {
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 500;
  line-height: 16px;
  color: var(--color-ink);
}

/* =========================================================
   Home — What do you need AI to do for your business?
   ========================================================= */

/* Taller than one viewport at nearly every common desktop resolution
   (title + text + 6-item list runs ~1058px, exceeding even 1440x900)
   -- sticky + taller-than-a-viewport content traps scrolling, same
   bug as .how-it-works/#knowbook/.enterprise. Unconditional, not
   breakpoint-gated, since this one fails even at ordinary desktop
   sizes, not just small/narrow viewports. animations.js excludes it
   from the sticky recede transform the same way. */
.needs {
  position: relative;
}

/* Same "Insights from the work" pattern below -- header, then a row
   of equal-width columns each marked off with a border-top divider
   instead of a card. */
.needs .section__inner {
  display: flex;
  flex-direction: column;
  gap: 80px;
}

.needs__header {
  display: flex;
  flex-direction: column;
  gap: 32px;
  max-width: 720px;
}

.needs__title {
  color: var(--color-ink);
  font-weight: 900;
  line-height: 110%;
  margin: 0;
}

.needs__text {
  color: var(--color-gray-333);
  line-height: 155%;
  max-width: 700px;
  margin: 0;
}

.needs__list {
  display: flex;
  gap: 72px 40px;
  flex-wrap: wrap;
}

.needs__item {
  flex-basis: 0%;
  flex-grow: 1;
  min-width: 220px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  border-top: 1px solid #121212;
  padding-top: 32px;
}

.needs__item-title {
  color: var(--color-ink);
  font-weight: 600;
  line-height: 135%;
}

.needs__item-desc {
  color: var(--color-gray-333);
  font-size: var(--type-bodyl-size);
  line-height: 150%;
  margin: 0;
}

.needs__cta {
  color: var(--color-ink);
  font-weight: 600;
  line-height: 22px;
}

/* Offerings' Knowbook sub-points -- same flat, non-interactive list
   design as the "What do you need AI to do" list above (border-top
   divider row, t-bodyl title) instead of Home's interactive
   .path-tabs-v accordion, which only earns its keep on Home where a
   tab click also highlights a media panel. */
.knowbook-points {
  display: flex;
  gap: 72px 40px;
  flex-wrap: wrap;
}

.knowbook-points__item {
  flex-basis: 0%;
  flex-grow: 1;
  min-width: 220px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  border-top: 1px solid #121212;
  padding-top: 32px;
}

.knowbook-points__item-title {
  color: var(--color-ink);
  font-weight: 600;
  line-height: 135%;
}

.knowbook-points__item-desc {
  color: var(--color-gray-333);
  font-size: var(--type-bodyl-size);
  line-height: 150%;
  margin: 0;
}

/* =========================================================
   Home — Why Ouli
   ========================================================= */

/* ~889px of content -- fine on tall viewports but exceeds one
   viewport's height once it drops below ~900px (common laptop/tablet-
   landscape heights: 1366x768, 1280x800, 1280x720, iPad Air/Android
   tablet landscape), same sticky-trap bug as .needs/.network above.
   Gated by *height*, not the site's usual width breakpoints -- this
   bug tracks available vertical space, not device category.
   animations.js's heightStackedQuery mirrors this same breakpoint to
   also drop the sticky recede transform here. */
@media (max-height: 960px) {
  .why-ouli {
    position: relative;
  }
}

.why-ouli .section__inner {
  display: flex;
  flex-direction: column;
  gap: 64px;
}

.why-ouli__body {
  display: flex;
  flex-direction: column;
  gap: 32px;
}

.why-ouli__title {
  color: var(--color-white);
  font-weight: 900;
  line-height: 115%;
  max-width: none;
  margin: 0;
}

.why-ouli__subtitle {
  display: block;
  color: var(--color-white);
  font-weight: 700;
  line-height: 130%;
}

.why-ouli__text {
  color: #FFFFFFB3;
  line-height: 160%;
  max-width: 720px;
  margin: 0;
}

.why-ouli__subtext {
  color: #FFFFFF73;
  line-height: 160%;
  max-width: 700px;
  margin: 0;
}

.why-ouli__points {
  display: flex;
  gap: 40px;
  flex-wrap: wrap;
}

.why-ouli__points-item {
  flex: 1 1 240px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  border-top: 1px solid #FFFFFF33;
  padding-top: 24px;
}

.why-ouli__points-label {
  color: var(--color-white);
  font-weight: 800;
  line-height: 125%;
  margin: 0;
}

.why-ouli__points-desc {
  color: #FFFFFFB3;
  line-height: 150%;
  margin: 0;
}

.why-ouli__keywords {
  color: var(--color-lime);
  font-weight: 700;
  line-height: 16px;
  margin: 0;
}

.why-ouli__ctas {
  display: flex;
  align-items: center;
  gap: 32px;
  flex-wrap: wrap;
}

.why-ouli__closer {
  color: var(--color-white);
  font-weight: 600;
  line-height: 140%;
  max-width: 640px;
  margin: 0;
}

/* =========================================================
   Home — Insights
   ========================================================= */

/* Same height-gated sticky-trap fix as .why-ouli -- only exceeds one
   viewport at reduced heights (~776px of content), not at the site's
   width breakpoints. */
@media (max-height: 960px) {
  .insights {
    position: relative;
  }
}

.insights .section__inner {
  display: flex;
  flex-direction: column;
  gap: 80px;
}

.insights__header {
  display: flex;
  flex-direction: column;
  gap: 32px;
}

.insights__title {
  color: var(--color-ink);
  font-weight: 900;
  line-height: 110%;
  max-width: 1000px;
  margin: 0;
}

.insights__text {
  color: var(--color-gray-333);
  line-height: 155%;
  max-width: 700px;
  margin: 0;
}

.insights-list {
  display: flex;
  gap: 72px;
  flex-wrap: wrap;
}

.insights-list__item {
  flex-basis: 0%;
  flex-grow: 1;
  min-width: 220px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  border-top: 1px solid #121212;
  padding-top: 32px;
}

.insights-list__eyebrow {
  color: var(--color-gray-999);
  font-weight: 700;
  letter-spacing: 0.08em;
  line-height: 14px;
  text-transform: uppercase;
}

.insights-list__title {
  color: var(--color-ink);
  font-weight: 600;
  line-height: 135%;
}

.insights__all-link {
  color: var(--color-ink);
  font-weight: 600;
  line-height: 18px;
}

/* =========================================================
   Home — Process
   ========================================================= */

/* No min-height floor: this section is now just a heading + the
   Discover block, so its own padding is the whole height. It used to
   be pinned to 100vh to give the 6-card loop diagram empty space
   below it as buffer for its reveal sequence -- that diagram is gone,
   and with it the @media (max-height: 960px) escape hatch that
   dropped .process out of the sticky deck when the row didn't fit.
   That escape hatch mattered: animations.js's syncStickyFit clears
   the inline position with "" for any section that fits the viewport,
   so the CSS rule won, and Process was the only section in the deck
   that stopped being sticky below a 960px-tall viewport while still
   getting the recede scale/blur meant for a pinned one. */
.process {
  min-height: auto;
  padding: 160px 0;
  background: linear-gradient(-25deg, var(--color-lime-300), var(--color-lime-glow));
}

.process .section__inner {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  gap: 32px 64px;
}

@media (max-width: 900px) {
  .process .section__inner {
    grid-template-columns: 1fr;
  }
}

.process__header {
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.process__title {
  color: var(--color-ink);
  font-weight: 900;
  line-height: 110%;
  max-width: 1000px;
  margin: 0;
}

.process__text {
  color: var(--color-gray-333);
  line-height: 150%;
  max-width: 680px;
  margin: 0;
}

.process__cta {
  color: var(--color-ink);
  font-weight: 600;
  line-height: 22px;
}

.process__discover {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Shared-context frame: wraps the steps + Learn so the whole thing
   reads as one governed loop instead of a flat list. It's a direct
   child of .section__inner, so it's already picked up by the site's
   generic scroll-reveal (fade + rise) -- no extra JS needed. */
.process-frame {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.process-frame__tag {
  color: var(--color-gray-666);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.process-steps {
  position: relative;
  display: flex;
  gap: 40px;
  flex-wrap: wrap;
  margin-top: 80px;
}

/* The two feedback loops: Achieve's result feeds back into Plan, and
   what actually gets adopted feeds back into Define. Positioned
   above the spine, pinned to each stage's dot position -- x
   coordinates are ratios of the 6-item flex row (equal columns,
   40px gaps), so they stay aligned regardless of viewport width. */
.process-loop-arrows-wrap {
  position: absolute;
  left: 0;
  right: 0;
  width: 100%;
  bottom: calc(100% + 8px);
  height: 64px;
  overflow: visible;
}

.process-loop-arrows {
  display: block;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.process-loop-arrows__caption {
  position: absolute;
  top: 12px;
  transform: translate(-50%, -50%);
  white-space: nowrap;
  color: var(--color-gray-666);
  font-family: var(--font-sans);
  font-size: var(--type-caption-size);
  font-weight: var(--type-caption-weight);
  letter-spacing: 0.12em;
}

.process-loop-arrows__caption--short {
  top: 42px;
  color: var(--color-ink);
  opacity: 0.6;
}

/* One-time reveal instead of continuous scroll-scrubbing -- simpler
   and more robust: fires once via the same .is-visible trigger the
   generic reveal system already gives .process-steps (see
   genericSelectors in animations.js), well before the section-level
   recede effect (updateLayers above) starts dimming this section as
   the next one covers it, so nothing here is fighting its own
   fade-out. */
/* stroke-dashoffset is set directly by the same Process tick loop
   that drives the cards (see animations.js) -- the short transition
   just smooths the frame-to-frame jump, it isn't what's driving the
   reveal. */
.process-loop-arrows__track {
  fill: none;
  stroke: var(--color-ink);
  stroke-width: 1.5;
  stroke-opacity: 0.4;
  stroke-dasharray: 780;
  stroke-dashoffset: 780;
  transition: stroke-dashoffset 0.2s linear;
}

.process-loop-arrows__short .process-loop-arrows__track {
  stroke-opacity: 0.3;
  stroke-dasharray: 400;
  stroke-dashoffset: 400;
}

.process-loop-arrows__tip {
  fill: none;
  stroke: var(--color-ink);
  stroke-opacity: 0.6;
  stroke-width: 1.5;
  opacity: 0;
  transition: opacity 0.15s linear;
}

@media (prefers-reduced-motion: reduce) {
  .process-loop-arrows__tip { opacity: 1 !important; }
}

.process-loop-arrows__flow--long { stroke-dasharray: 780; }
.process-loop-arrows__flow--short { stroke-dasharray: 400; }

/* Mobile counterpart to everything above -- same two feedback loops,
   rotated into the vertical timeline's left gutter instead of sitting
   above a horizontal row. Hidden by default; shown and positioned
   (see the media query below and animations.js, which computes the
   path `d` attributes and caption position from each card's actual
   rendered top, since card height varies with text length) only
   below 1024px. */
.process-loop-arrows-vertical,
.process-loop-arrows-vertical__caption {
  display: none;
}

.process-loop-arrows__flow {
  fill: none;
  stroke-linecap: round;
  stroke: var(--color-forest);
  stroke-dasharray: 24 2000;
  opacity: 0;
  filter: drop-shadow(0 0 5px rgba(43, 108, 65, 0.65));
  transition: opacity 0.3s linear;
  animation: process-loop-flow 4.2s linear infinite;
}

.process-loop-arrows__short .process-loop-arrows__flow {
  stroke: var(--color-ink);
  stroke-opacity: 0.6;
  filter: drop-shadow(0 0 4px rgba(18, 18, 18, 0.35));
  animation-duration: 3.4s;
}

@keyframes process-loop-flow {
  to { stroke-dashoffset: -2024; }
}

@media (prefers-reduced-motion: reduce) {
  .process-loop-arrows__track { transition: none; stroke-dashoffset: 0; }
  .process-loop-arrows__flow { animation: none; opacity: 0 !important; }
}

.process-steps__item {
  position: relative;
  flex-basis: 0%;
  flex-grow: 1;
  min-width: 140px;
}

/* Solid card instead of text floating directly on the lime gradient
   -- the gradient is too bright/busy for the caption-sized copy to
   stay reliably legible on its own. Each card fades + rises in on
   its own delay once the section scrolls into view. */
/* Rise-and-settle is scroll-tied (see the Process cards handler in
   animations.js): opacity/transform get set on every scroll frame
   from one shared progress value, and only the short transition +
   a small per-card delay below make them settle one after another
   instead of in perfect lockstep. */
/* No CSS transition here on purpose -- the scroll position is
   already smoothed frame-by-frame by the lerp loop in animations.js,
   so a transition chasing those per-frame values would just add a
   second, conflicting layer of easing on top of it. */
.process-steps__card {
  height: 100%;
  background: var(--color-cream);
  border-radius: 8px;
  padding: 24px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.process-steps__card:hover {
  transform: translateY(-4px) !important;
  transition: transform 0.2s cubic-bezier(.4,0,.2,1);
}

@media (prefers-reduced-motion: reduce) {
  .process-steps__card { transform: none !important; }
}

.process-steps__label {
  color: var(--color-ink);
  font-weight: 700;
  letter-spacing: 0.08em;
  line-height: 18px;
}

.process-steps__desc {
  color: var(--color-gray-333);
  line-height: 150%;
}

/* Vertical guides connecting every stage down to Learn -- it isn't
   one more step, it's the layer that reads a signal from all six and
   feeds the two loops back in, so it needs to visibly touch all of
   them, not just sit underneath as an unrelated card. */
/* Cancels the flex gap on both sides (the parent .process-frame is
   gap:16px) so this element's own box touches the cards above and
   Learn below directly -- the line spans its full height, so it
   actually reaches both instead of floating in the middle of a
   larger gap with dead space on either end. */
.process-learn-connectors {
  display: block;
  width: 100%;
  height: 24px;
  margin: -16px 0;
  overflow: visible;
}

.process-learn-connectors__line {
  fill: none;
  stroke: var(--color-ink);
  stroke-width: 1.5;
  stroke-opacity: 0.35;
  stroke-dasharray: 24;
  stroke-dashoffset: 24;
  transition: stroke-dashoffset 0.2s linear;
}

@media (prefers-reduced-motion: reduce) {
  .process-learn-connectors__line { transition: none; stroke-dashoffset: 0; }
}

.process-learn {
  background: var(--color-cream);
  border-radius: 8px;
  padding: 24px;
  display: flex;
  align-items: baseline;
  gap: 16px;
  flex-wrap: wrap;
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 0.2s linear, transform 0.2s linear;
}

@media (prefers-reduced-motion: reduce) {
  .process-learn { transition: none; opacity: 1 !important; transform: none !important; }
}

.process-learn__name {
  font-family: var(--font-sans);
  font-weight: 900;
  font-style: italic;
  font-size: 1.0625rem; /* 17px @16px root -- was hardcoded in px */
  color: var(--color-ink);
  flex-shrink: 0;
  width: 72px;
}

.process-learn__desc {
  color: var(--color-gray-333);
}

/* The animated spine assumes one continuous row -- once the 6 items
   wrap (min-width:140px each means that happens well before mobile,
   already at tablet widths), a single left-to-right line stops
   making sense against a multi-row grid. Below that point, stack
   instead -- but still connected, just as a vertical timeline (a line
   down the left edge threading a dot through each card, continuing
   into Learn) instead of the desktop's horizontal one. Deliberately a
   vertical stack, not a horizontal swipe -- tried the swipeable-row
   pattern (like Use Cases) first, but the explicit call was to not
   make people swipe sideways for this one. The curved feedback
   arrows stay dropped (pinned to exact desktop pixel positions over a
   full-width row, not something to make responsive) -- the timeline
   is what carries the "this is one connected flow" idea here. */
@media (max-width: 1024px) {
  /* Sticky + taller-than-a-viewport content traps scrolling: the box
     stays pinned showing only what fits in the first screenful, and
     everything past that (Learn, Achieve, the back half of the stack)
     is never reachable at all -- same reasoning as .how-it-works
     above, which hits the identical problem from stacking its tabs
     content. animations.js checks this same breakpoint live to also
     stop applying the sticky recede transform here. */
  .process {
    position: relative;
  }

  .process-steps {
    flex-direction: column;
    gap: 0;
    margin-top: 48px;
    position: relative;
    padding-left: 88px;
  }

  .process-steps::before {
    content: "";
    position: absolute;
    top: 20px;
    bottom: 20px;
    left: 68px;
    width: 2px;
    background: rgba(18, 18, 18, 0.3);
  }

  .process-loop-arrows-wrap {
    display: none;
  }

  .process-steps__item {
    position: relative;
    min-width: 0;
    padding: 8px 0;
  }

  .process-steps__item::before {
    content: "";
    position: absolute;
    left: -25px;
    top: 32px;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--color-ink);
    z-index: 1;
  }

  /* The two feedback loops (Achieve->Plan, Adopt->Define), rotated
     into the gutter to the left of the main timeline. Positioned in
     JS -- see layoutVerticalProcessArrows() in animations.js -- since
     it needs each card's real rendered top (card height varies with
     description length, so this can't be pixel-hardcoded the way the
     desktop viewBox is). */
  .process-loop-arrows-vertical {
    display: block;
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    overflow: visible;
    pointer-events: none;
  }

  .process-loop-arrows-vertical__track {
    fill: none;
    stroke: var(--color-ink);
    stroke-width: 1.5;
    stroke-opacity: 0.4;
  }

  .process-loop-arrows-vertical__track--short {
    stroke-opacity: 0.3;
  }

  .process-loop-arrows-vertical__tip {
    fill: none;
    stroke: var(--color-ink);
    stroke-opacity: 0.6;
    stroke-width: 1.5;
  }

  .process-loop-arrows-vertical__caption {
    display: block;
    position: absolute;
    left: 28px;
    transform: rotate(90deg) translate(-50%, 0);
    transform-origin: left center;
    white-space: nowrap;
    color: var(--color-gray-666);
    font-family: var(--font-sans);
    font-size: var(--type-caption-size);
    font-weight: var(--type-caption-weight);
    letter-spacing: 0.1em;
  }

  .process-loop-arrows-vertical__caption--short {
    left: 4px;
    color: var(--color-ink);
    opacity: 0.6;
  }

  /* Continues the same timeline from the last card down into Learn,
     rather than the desktop version's 6 individual tick marks (those
     are positioned via exact pixel math over the full-width row,
     which doesn't translate to a stacked column). A short fixed-length
     segment bridging the gap into Learn's own dot, not a precise
     measurement of the last card's exact position -- the two ends
     don't need to touch pixel-for-pixel for the line to read as one
     continuous thread. */
  .process-learn-connectors {
    display: none;
  }

  .process-learn {
    position: relative;
    padding-left: 88px;
  }

  .process-learn::before {
    content: "";
    position: absolute;
    left: 68px;
    top: -24px;
    width: 2px;
    height: 52px;
    background: rgba(18, 18, 18, 0.3);
  }

  .process-learn::after {
    content: "";
    position: absolute;
    left: 64px;
    top: 28px;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--color-ink);
  }
}

/* =========================================================
   Home — Network
   ========================================================= */

/* Same unconditional fix as .needs -- taller than one viewport at
   ordinary desktop sizes (~946px, grew further once the Partners
   logo row moved in here), same sticky-trap bug. */
.network {
  position: relative;
}

.network .section__inner {
  display: flex;
  flex-direction: column;
  gap: 48px;
}

.network__body {
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.network__title {
  color: var(--color-ink);
  font-weight: 900;
  line-height: 110%;
  margin: 0;
}

.network__lede {
  color: var(--color-ink);
  font-weight: 600;
  line-height: 32px;
  margin: 0;
}

.network__text {
  color: var(--color-gray-333);
  line-height: 160%;
  max-width: 700px;
  margin: 0;
}

.network__subtext {
  color: var(--color-gray-777);
  line-height: 160%;
  max-width: 700px;
  margin: 0;
}

.network__cta {
  color: var(--color-ink);
  font-weight: 600;
  line-height: 20px;
}

/* =========================================================
   Home — Final CTA
   ========================================================= */

.final-cta {
  /* 100vh, not a fixed px value -- this section's real height is just
     its content (527px: padding + title + text), shorter than almost
     any real viewport. Since it's the LAST section in the sticky
     "deck of cards" stack, once it's stuck at top:0 its bottom edge
     sits at its own content height regardless of viewport size --
     any viewport taller than that still shows Process's own sticky
     section (lime gradient background) pinned underneath it, peeking
     through below. A fixed px (900, matching .hero) covered that gap
     on typical viewports but reopened it on anything taller; 100vh
     always matches the real viewport exactly. */
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

@media (max-width: 640px) {
  .hero {
    min-height: 640px;
  }
}

.final-cta .section__inner {
  display: flex;
  flex-direction: column;
  gap: 32px;
}

.final-cta__title {
  color: var(--color-lime);
  font-weight: 900;
  line-height: 105%;
  margin: 0;
}

.final-cta__text {
  color: #FFFFFFB3;
  line-height: 150%;
  max-width: 680px;
  margin: 0;
}

.final-cta__link {
  color: var(--color-white);
  font-weight: 600;
  line-height: 22px;
}

.final-cta__guarantee {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.final-cta__guarantee-lead {
  color: var(--color-white);
  font-weight: 800;
  line-height: 140%;
  margin: 0;
}

.final-cta__guarantee-text {
  color: #FFFFFFB3;
  line-height: 150%;
  margin: 0;
}

/* =========================================================
   Scroll-reveal (vanilla JS, see animations.js)
   Elements start slightly lower + transparent, then settle in
   as they cross the viewport. Degrades gracefully with no JS —
   the .js-ready gate below keeps content visible if the script
   never runs.
   ========================================================= */

.reveal {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 0.6s cubic-bezier(.4,0,.2,1), transform 0.6s cubic-bezier(.4,0,.2,1);
  will-change: opacity, transform;
}

.reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
}

/* .process-steps only borrows .reveal/.is-visible as a scroll-into-view
   flag to trigger the line-draw graphic above -- it shouldn't also get
   the generic fade-up (its own children already fade individually). */
.process-steps.reveal {
  opacity: 1;
  transform: none;
}

/* Only hide .reveal elements once we know the JS is alive and about
   to animate them in — if the script fails to load, content stays
   visible at its resting state instead of stuck invisible. */
body:not(.js-ready) .reveal {
  opacity: 1;
  transform: none;
  transition: none;
}

/* =========================================================
   Floating agent launcher -- draggable bubble that snaps to
   the nearest viewport corner on release (see animations.js).
   A plain click (no drag) opens the chat panel instead --
   Intercom-style launcher, plus the drag+snap Intercom itself
   doesn't have.
   ========================================================= */

.agent-launcher {
  position: fixed;
  right: 24px;
  bottom: 24px;
  z-index: 2000;
  user-select: none;
  -webkit-user-select: none;
  /* A damped-spring curve (slight overshoot, settles back down) reads
     as a physical snap into the corner instead of a mechanical ease --
     linear() plots the actual spring simulation as sampled points. */
  --spring: linear(0, .0772, .2534, .4625, .6612, .8257, .9467, 1.0252, 1.0676, 1.0829, 1.08, 1.067, 1.0499, 1.0328, 1.0181, 1.007, .9994, .9951, .9933, .9931, .994, .9954, .9969, .9982, 1);
  transition: left 0.55s var(--spring), right 0.55s var(--spring),
    top 0.55s var(--spring), bottom 0.55s var(--spring);
}

/* No transition while actively dragging -- it must track the
   pointer 1:1, the eased slide is only for the snap afterward. */
.agent-launcher.is-dragging {
  transition: none;
}

.agent-launcher__bubble {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  border: none;
  background: var(--color-ink);
  color: var(--color-lime);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: grab;
  box-shadow: 0 8px 24px rgba(18, 18, 18, 0.35);
  transition: transform 0.2s cubic-bezier(.4,0,.2,1), box-shadow 0.2s ease;
  touch-action: none;
}

.agent-launcher__bubble:hover {
  transform: scale(1.06);
}

.agent-launcher.is-dragging .agent-launcher__bubble {
  cursor: grabbing;
  transition: none;
}

.agent-launcher__icon {
  width: 26px;
  height: 26px;
  pointer-events: none;
}

.agent-chat {
  position: absolute;
  width: 340px;
  max-width: calc(100vw - 32px);
  height: 460px;
  max-height: calc(100vh - 140px);
  background: var(--color-white);
  border-radius: 16px;
  box-shadow: 0 24px 60px rgba(18, 18, 18, 0.25);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  opacity: 0;
  transform: translateY(12px) scale(0.98);
  pointer-events: none;
  transition: opacity 0.25s ease, transform 0.25s cubic-bezier(.4,0,.2,1);
}

.agent-chat.is-open {
  opacity: 1;
  transform: translateY(0) scale(1);
  pointer-events: auto;
}

.agent-chat__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 24px;
  background: var(--color-ink);
  color: var(--color-white);
  flex-shrink: 0;
}

.agent-chat__title {
  font-weight: 700;
  font-size: var(--type-body-size);
}

.agent-chat__close {
  background: none;
  border: none;
  color: var(--color-white);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  padding: 0;
  opacity: 0.7;
  transition: opacity 0.2s ease;
}

.agent-chat__close:hover {
  opacity: 1;
}

.agent-chat__body {
  flex: 1 1 auto;
  padding: 24px;
  overflow-y: auto;
}

.agent-chat__message {
  background: var(--color-offwhite);
  color: var(--color-ink);
  border-radius: 12px;
  padding: 16px 16px;
  font-size: var(--type-body-size);
  line-height: 150%;
  max-width: 85%;
}

.agent-chat__footer {
  flex-shrink: 0;
  padding: 16px 16px;
  border-top: 1px solid rgba(18, 18, 18, 0.1);
}

.agent-chat__input {
  width: 100%;
  border: 1px solid rgba(18, 18, 18, 0.15);
  border-radius: 20px;
  padding: 8px 16px;
  font-family: var(--font-sans);
  font-size: var(--type-body-size);
  outline: none;
  box-sizing: border-box;
}

.agent-chat__input:disabled {
  background: var(--color-cream);
  color: var(--color-gray-666);
  cursor: not-allowed;
}

@media (max-width: 640px) {
  .agent-chat {
    width: calc(100vw - 32px);
  }
}

/* =========================================================
   Ouli Agent panel -- replaces the floating .agent-launcher bubble
   above on pages that opt in (body.agent-enabled). The two systems
   don't conflict: animations.js's old launcher IIFE bails out as
   soon as it can't find .agent-launcher in the page, so pages using
   this instead simply never run that code. Ported from the verified
   prototype at prototype/ouli-agent-panel.html -- see that file's
   comments for the reasoning behind each decision (viewport-height
   page-shell instead of document-height, margin inset instead of
   scale(), flush seam with the panel, shadow+z-index ordering,
   mask-image fade on the shader, etc).
   ========================================================= */

html {
  height: 100%;
}

/* display:flex so .page-shell's own top/bottom margin (below) can't
   collapse through body and quietly add extra, invisible document
   height -- margins on flex items never collapse with their
   container, by spec. Scoped to this class, not plain `body`, so
   pages that still use the old floating launcher are untouched. */
body.agent-enabled {
  height: 100%;
  background: var(--color-offwhite);
  display: flex;
}

.page-shell {
  width: 100%;
  height: 100%;
  overflow-y: auto;
  /* overflow-y: auto alone makes overflow-x auto too, so anything a few pixels too wide let the whole page slide
     sideways on phones. */
  overflow-x: hidden;
  overflow-anchor: none;
  scroll-behavior: smooth;
  background: var(--color-cream);
  position: relative;
  z-index: 2;
  transition: width 0.25s var(--ease-organic), height 0.25s var(--ease-organic), margin 0.25s var(--ease-organic), border-radius 0.25s var(--ease-organic);
}

body.agent-panel-open .page-shell {
  width: calc(100% - 420px - 8px - 8px);
  height: calc(100% - 16px);
  margin: 8px 0 8px 8px;
  border-top-left-radius: 12px;
  border-bottom-left-radius: 12px;
  /* overflow-y: auto (not hidden) -- still clips the nav/sections'
     own square corners to this rounded shape (clipping to the
     border-radius happens for any overflow value other than
     visible), but unlike hidden it doesn't also disable scrolling.
     The panel being open shouldn't stop someone from reading the
     rest of the page while they chat. */
  overflow-y: auto;
  overflow-x: hidden;
  filter: drop-shadow(3px 0 6px rgba(0,0,0,0.3));
}

.ask-ouli-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--color-lime);
  color: var(--color-ink);
  padding: 8px 18px;
  border-radius: 999px;
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  font-weight: 700;
  border: none;
  cursor: pointer;
  flex-shrink: 0;
  transition: transform 0.15s ease;
}
.ask-ouli-btn:hover {
  transform: scale(1.04);
}
.ask-ouli-btn svg {
  flex-shrink: 0;
}

/* Mobile entry point -- below 1024px .nav__links (and the desktop
   pill inside it) disappears entirely in favor of the hamburger +
   .site-menu overlay, so without this, phones/tablets would have no
   way to open the agent at all. */
.site-menu .ask-ouli-btn {
  width: fit-content;
}

.agent-panel {
  position: fixed;
  top: 8px;
  right: 8px;
  bottom: 8px;
  width: 420px;
  max-width: 92vw;
  background: var(--color-cream);
  border-top-right-radius: 12px;
  border-bottom-right-radius: 12px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  transform: translateX(calc(100% + 8px));
  transition: transform 0.25s var(--ease-organic);
  /* Below .page-shell's z-index: 2 (not above it, despite being
     position:fixed) -- on purpose, so the content card's drop-shadow
     can paint over this panel's left edge instead of this opaque
     background hiding it. Their boxes never overlap horizontally
     once open (the content's width already subtracts this panel's
     full footprint), so being "underneath" doesn't hide any of the
     panel's own content -- only lets the sliver of shadow through. */
  z-index: 1;
}
body.agent-panel-open .agent-panel {
  transform: translateX(0);
}

/* width:100% (not an oversized 800px block centered + relying on
   .agent-panel's overflow:hidden to crop the sides) -- overflow:hidden
   does not reliably clip a WebGL canvas when the clipping ancestor
   also has its own CSS transform applied (confirmed: .agent-panel
   needs transform for its slide-in animation), a known
   browser-rendering edge case. Keeping the shader's own box already
   sized to fit removes the dependency on that clip working at all,
   instead of working around a quirk that could silently regress. */
.agent-panel__shader {
  position: absolute;
  top: -40px;
  bottom: -40px;
  left: 0;
  width: 100%;
  opacity: 0.1;
  z-index: 0;
  pointer-events: none;
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 92%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 0%, #000 92%, transparent 100%);
}

.agent-panel__header {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 20px 20px 0;
  flex-shrink: 0;
}

.agent-panel__badge {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  font-weight: 700;
  color: var(--color-ink);
}
.agent-panel__badge-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--color-lime);
  box-shadow: 0 0 0 4px rgba(206,254,4,0.25);
}
/* The only "something changed" cue when a CTA is clicked while the
   panel is already open -- there's no open/close transition to lean
   on in that case, unlike a fresh open which already slides in. */
.agent-panel__badge-dot.is-pulsing {
  animation: agent-badge-pulse 0.6s ease;
}
@keyframes agent-badge-pulse {
  0% { box-shadow: 0 0 0 4px rgba(206,254,4,0.25); }
  40% { box-shadow: 0 0 0 10px rgba(206,254,4,0.45); transform: scale(1.3); }
  100% { box-shadow: 0 0 0 4px rgba(206,254,4,0.25); transform: scale(1); }
}

.agent-panel__close {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: none;
  background: transparent;
  color: var(--color-ink);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
.agent-panel__close:hover {
  background: rgba(0,0,0,0.06);
}

.agent-panel__body {
  position: relative;
  z-index: 1;
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 32px 24px;
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.agent-panel__greeting {
  font-family: var(--font-sans);
  font-size: var(--type-title4-size);
  font-weight: 700;
  line-height: 130%;
  color: var(--color-ink);
  margin: 8px 0 0;
}

.agent-panel__messages {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

/* Reserves scroll runway below the conversation right before pinning
   a message to the top (see scrollPinToTop in animations.js) -- a
   freshly-added message doesn't have its own typing indicator/reply
   below it yet to make the container tall enough to actually scroll
   that far, so without this reservation the browser just clamps the
   scroll position back down and the pin silently does nothing.
   Collapses back to 0 on the next normal scrollToBottom. */
/* No transition -- this space is never actually visible (it's either
   scrolled past or collapsed to 0), so animating it buys nothing.
   Worse: the JS reads scrollHeight/sets scrollTop in the same tick
   right after changing this height -- with a transition, that read
   happens before the transition has advanced at all, so it still
   sees the OLD (pre-change) layout height and the pin silently fails. */
.agent-spacer {
  flex-shrink: 0;
  height: 0px;
}

.agent-msg {
  max-width: 85%;
  overflow-wrap: anywhere;
  font-family: var(--font-sans);
  font-size: var(--type-body-size);
  line-height: 150%;
  opacity: 0;
  /* 8px / 3% scale was too subtle for --ease-organic's overshoot to
     actually read -- the curve applies to whatever range you give it,
     and a tiny range makes even a real overshoot invisible. Bigger
     motion (14px, 14% scale) gives the bounce room to show.
     Negative translateY -- starts above its resting spot and settles
     downward, not the other way around. */
  transform: translateY(-14px) scale(0.86);
  animation: agent-msg-in 0.32s var(--ease-organic) forwards;
}
@keyframes agent-msg-in {
  to { opacity: 1; transform: translateY(0) scale(1); }
}
.agent-msg--user {
  align-self: flex-end;
  background: var(--color-ink);
  color: var(--color-white);
  padding: 12px 16px;
  border-radius: 16px;
  border-bottom-right-radius: 4px;
  /* Grows from its own tail corner (bottom-right, where the radius
     is squared off) instead of the box center -- reads as the bubble
     anchored at the point it "comes from", not scaling in place. */
  transform-origin: bottom right;
}
/* No background/padding here anymore -- this is just the flex column
   that stacks the actual bubble (.agent-msg__bubble) above its copy
   button, which sits below and OUTSIDE the white box instead of
   crowding the reply text inside it. */
.agent-msg--agent {
  align-self: flex-start;
  color: var(--color-ink);
  transform-origin: bottom left;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
}
.agent-msg__bubble {
  background: var(--color-white);
  padding: 12px 16px;
  border-radius: 16px;
  border-bottom-left-radius: 4px;
}

/* Copy-to-clipboard -- only real agent replies get this (built in
   addMessage), never the typing indicator, which has no finished
   text worth copying. Sits outside .agent-msg__bubble, not inside
   its padding, so it reads as an action on the message rather than
   part of its content. */
.agent-msg__copy {
  width: 22px;
  height: 22px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--color-ink);
  opacity: 0.4;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: opacity 0.15s ease, background 0.15s ease;
}
.agent-msg__copy:hover {
  opacity: 1;
  background: var(--color-offwhite);
}
.agent-msg__copy.is-copied {
  opacity: 1;
}

/* Typing indicator -- a reply that just appears after a pause reads
   as a lookup, not a reply; the three-dot bubble is what actually
   sells "this is being composed right now", same convention as
   iMessage/Slack/Intercom. Declares its own background/padding/radius
   directly (.agent-msg--agent no longer carries those -- they moved
   to .agent-msg__bubble, which the typing indicator doesn't use). */
.agent-typing {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 4px;
  padding: 14px 16px;
  background: var(--color-white);
  border-radius: 16px;
  border-bottom-left-radius: 4px;
}
.agent-typing span {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: rgba(18, 18, 18, 0.45);
  animation: agent-typing-bounce 1.1s ease-in-out infinite;
}
.agent-typing span:nth-child(2) {
  animation-delay: 0.15s;
}
.agent-typing span:nth-child(3) {
  animation-delay: 0.3s;
}
@keyframes agent-typing-bounce {
  0%, 60%, 100% { transform: translateY(0); opacity: 0.45; }
  30% { transform: translateY(-4px); opacity: 1; }
}

.agent-panel__chips,
.agent-chip-group {
  display: flex;
  flex-direction: column;
  gap: 10px;
  transition: opacity 0.18s var(--ease-exit), transform 0.18s var(--ease-exit);
  animation: agent-chips-in 0.26s var(--ease-organic);
}
@keyframes agent-chips-in {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}
/* Mid-conversation contextual chips (injectContextualChips) sit
   inline in the message flow, left-aligned like the agent's own
   turn, not full-width like the opening set. */
.agent-chip-group {
  align-self: flex-start;
  max-width: 85%;
}

/* Press + fade-out on pick, shared by .agent-panel__chips,
   .agent-chip-group and .agent-quick-replies -- an instant
   display:none read as an abrupt jump cut; this overlaps with the
   new message bubble's own fade-in instead. --ease-exit (not
   --ease-organic) here -- an accelerating fall-away, not a bounce,
   is what the GROUP leaving should feel like; the bounce belongs to
   the individual chip's own press feedback below. */
.agent-panel__chips.is-dismissing,
.agent-chip-group.is-dismissing,
.agent-quick-replies.is-dismissing {
  opacity: 0;
  transform: translateY(6px);
}

.agent-chip {
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--color-white);
  border: 1px solid rgba(18,18,18,0.12);
  border-radius: 999px;
  padding: 12px 18px;
  font-family: var(--font-sans);
  font-size: var(--type-caption-size);
  font-weight: 500;
  color: var(--color-ink);
  cursor: pointer;
  text-align: left;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.agent-chip:hover {
  border-color: var(--color-ink);
  background: var(--color-offwhite);
}
/* A static scale(0.96) that just sits there until the chip disappears
   isn't a press, it's a shrink with no release -- a real tap presses
   down and springs back (past 1, then settling), all in one motion.
   Replaces the old transform-transition approach, which could only
   move in the one direction a class toggle triggers. */
.agent-chip.is-pressed {
  animation: agent-chip-press 0.26s var(--ease-organic);
}
@keyframes agent-chip-press {
  0% { transform: scale(1); }
  45% { transform: scale(0.93); }
  80% { transform: scale(1.03); }
  100% { transform: scale(1); }
}
.agent-chip svg {
  flex-shrink: 0;
  opacity: 0.6;
}

/* Hidden until the user actually clicks the attach button -- it was
   showing unconditionally from the moment the panel opened, before
   there was anything to disclaim about yet. */
.agent-panel__disclaimer {
  display: none;
  position: relative;
  background: var(--color-offwhite);
  border-radius: 12px;
  padding: 14px 36px 14px 16px;
  font-family: var(--font-sans);
  font-size: 0.75rem;
  line-height: 150%;
  color: var(--color-gray-333);
}
.agent-panel__disclaimer button {
  position: absolute;
  top: 10px;
  right: 10px;
  width: 22px;
  height: 22px;
  border: none;
  background: transparent;
  color: var(--color-gray-333);
  font-size: 15px;
  cursor: pointer;
}

/* Profiling example: the agent's own quick-reply options (not the
   opening suggestion chips) -- smaller and left-aligned under the
   agent's bubble, like the agent itself is offering them, instead of
   reusing .agent-chip's pill-row styling which reads as "start of
   conversation" rather than "mid-conversation follow-up". */
.agent-quick-replies {
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-self: flex-start;
  max-width: 85%;
  transition: opacity 0.16s ease, transform 0.16s ease;
  animation: agent-chips-in 0.26s var(--ease-organic);
}
.agent-quick-reply {
  background: var(--color-white);
  border: 1px solid rgba(18,18,18,0.14);
  border-radius: 10px;
  padding: 10px 14px;
  font-family: var(--font-sans);
  font-size: var(--type-caption-size);
  color: var(--color-ink);
  text-align: left;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.agent-quick-reply.is-pressed {
  animation: agent-chip-press 0.26s var(--ease-organic);
}
.agent-quick-reply:hover {
  border-color: var(--color-ink);
  background: var(--color-offwhite);
}

/* The payoff of the profiling flow -- offered only once the agent
   has read enough signal to make a specific (not generic) offer to
   connect with the team. */
.agent-connect-btn {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--color-lime);
  color: var(--color-ink);
  border: none;
  border-radius: 999px;
  padding: 10px 18px;
  font-family: var(--font-sans);
  font-size: var(--type-caption-size);
  font-weight: 700;
  cursor: pointer;
  transition: transform 0.15s ease;
}
.agent-connect-btn:hover {
  transform: scale(1.04);
}
.agent-connect-btn:disabled {
  opacity: 0.6;
  cursor: default;
  transform: none;
}

.agent-panel__footer {
  position: relative;
  z-index: 1;
  flex-shrink: 0;
  padding: 16px 20px 20px;
}

/* The one line that says what happens to the conversation. Under the greeting, out of the way. */
.agent-panel__privacy {
  font-family: var(--font-sans);
  font-size: 12px;
  line-height: 150%;
  color: rgba(18,18,18,0.55);
  margin: 10px 0 0;
}
.agent-panel__privacy a { color: inherit; text-decoration: underline; }

/* Turnstile renders nothing unless Cloudflare needs the visitor to click; then the widget shows here. */
.agent-panel__turnstile:empty { display: none; }
.agent-panel__turnstile { margin: 0 0 10px; }

.agent-panel__input-row {
  display: flex;
  align-items: flex-end;
  gap: 10px;
  background: var(--color-white);
  border: 1px solid rgba(18,18,18,0.14);
  border-radius: 20px;
  padding: 6px 6px 6px 16px;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
/* Typing into the composer had no visible effect of its own -- the
   row never acknowledged focus and the send button looked identical
   whether there was anything to send or not. focus-within gives the
   row itself a clear "you're typing here" state. */
.agent-panel__input-row:focus-within {
  border-color: rgba(18,18,18,0.32);
  box-shadow: 0 0 0 3px rgba(18,18,18,0.06);
}

.agent-panel__attach {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: none;
  background: transparent;
  color: var(--color-ink);
  opacity: 0.6;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.agent-panel__attach:hover {
  opacity: 1;
  background: var(--color-offwhite);
}

.agent-panel__input {
  flex: 1 1 auto;
  border: none;
  background: transparent;
  font-family: var(--font-sans);
  font-size: var(--type-caption-size);
  line-height: 20px;
  color: var(--color-ink);
  outline: none;
  resize: none;
  overflow-y: hidden;
  padding: 6px 0;
  max-height: 120px;
}
.agent-panel__input::placeholder {
  color: rgba(18,18,18,0.4);
}

.agent-panel__send {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: none;
  background: var(--color-ink);
  color: var(--color-white);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  flex-shrink: 0;
  transition: background 0.15s ease, opacity 0.15s ease, transform 0.1s ease;
}
.agent-panel__send:hover:not(:disabled) {
  background: #000;
}
.agent-panel__send:active:not(:disabled) {
  transform: scale(0.92);
}
/* Nothing typed yet -- dim it and drop the pointer cursor so it reads
   as "nothing to send" instead of looking identical to the ready
   state and silently doing nothing on click. */
.agent-panel__send:disabled {
  opacity: 0.35;
  cursor: default;
}

@media (max-width: 640px) {
  .agent-panel {
    width: calc(100vw - 16px);
  }
  body.agent-panel-open .page-shell {
    /* Below 640px the panel is nearly full-width, leaving no room to
       also show a sliver of the content card beside it -- hide it
       off to the left instead of squeezing it into a few leftover
       px. */
    width: 0;
    margin: 0;
    opacity: 0;
  }
}


/* ---------------------------------------------------------
   Cookie notice: one line at the bottom, out of the way.
   --------------------------------------------------------- */
.cookie-note {
  position: fixed;
  left: 16px;
  right: 16px;
  bottom: 16px;
  z-index: 60;
  display: none;
  align-items: center;
  gap: 16px;
  max-width: 720px;
  margin: 0 auto;
  padding: 14px 18px;
  background: var(--color-white);
  color: var(--color-ink);
  border: 1px solid rgba(18,18,18,0.12);
  border-radius: 16px;
  box-shadow: 0 12px 40px rgba(18,18,18,0.18);
  font-family: var(--font-sans);
}
.cookie-note.is-visible { display: flex; animation: agent-chips-in 0.26s var(--ease-organic); }
.cookie-note__text { margin: 0; font-size: 13px; line-height: 150%; }
.cookie-note__text a { color: inherit; text-decoration: underline; }
.cookie-note__btn {
  flex-shrink: 0;
  background: var(--color-ink);
  color: var(--color-white);
  border: 0;
  border-radius: 999px;
  padding: 10px 16px;
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
@media (max-width: 600px) { .cookie-note { flex-direction: column; align-items: stretch; } }

/* Privacy page */
.privacy .section__inner { max-width: 760px; }
.privacy h2 { font-family: var(--font-sans); font-size: 22px; line-height: 130%; margin: 40px 0 12px; color: var(--color-ink); }
.privacy p, .privacy li { font-family: var(--font-sans); font-size: 16px; line-height: 160%; color: var(--color-ink); }
.privacy ul { padding-left: 20px; }
.privacy a { color: inherit; text-decoration: underline; }
