/* Mobile tap-highlight: the blue flash reported on redesigns.html's case
   cards isn't from any rule on that page — no stylesheet there ever sets a
   color on that card's :active/:hover/:focus state. Checking site-wide
   turned up the real scope of it: even a plain <a> tag computes a
   non-transparent -webkit-tap-highlight-color, meaning this was never
   addressed anywhere on the site, on any element — Webflow's own hosted
   output resets this globally, but that reset lives in webflow.js
   (removed this session) or Webflow's own runtime, not in the exported
   CSS, so the static mirror lost it. Most elements mask the resulting
   flash by immediately navigating or transitioning away; the case cards
   just make it obvious because they sit still while their modal opens.
   Resetting it on <html> fixes it everywhere at once instead of chasing
   each element it happens to be visible on. */
html {
  -webkit-tap-highlight-color: transparent;
}

/* redesigns.html case modals (.rd-modal_media): a third-party Webflow app
   (rdmodal-6.1.0.js) sets a flat background-color, rgb(25,79,136) — a
   generic blue, identical across every one of the 6 cases, not a per-image
   placeholder — behind the case's own background-image. It's only ever
   visible for however long that image takes to paint in (reveal.js already
   preloads them on page load to shrink that window), so on a slow
   connection, a cold cache, or just unlucky timing it still flashes blue
   before the real image appears. Matching it to the modal's own background
   instead makes that flash imperceptible regardless of timing — the actual
   fix, rather than continuing to race the image load. */
.rd-modal_media {
  background-color: var(--main-background) !important;
}

/* "More Projects" section (case study pages), 768-991px: .padding-section-large
   is shared by several sections on this page (feedback, CTA, this one), so
   it can't just be turned down globally — scoped to this section's own id.
   Its own 6rem top/bottom here (992px+ uses 8rem/0, <=767px already drops to
   4rem) stacks with this section's internal spacer-large (before the grid)
   and spacer-xhuge (after it), compounding into a much bigger gap than
   either mobile or desktop actually has. Matching the <=767px value here
   too closes that gap instead of treating 768-991px as its own wider tier. */
@media screen and (min-width: 768px) and (max-width: 991px) {
  #section-cases-projects .padding-section-large {
    padding-top: 4rem;
    padding-bottom: 4rem;
  }
}

/* Case study CTA block ("From Idea to Launch — Let's Talk"), <=767px:
   Webflow's own .hide-mobile-landscape hides Anja's photo entirely below
   this width, leaving just badge/heading/subtext/buttons — reads as a
   generic, forgettable block without the personal photo that makes it work
   on wider screens. Un-hiding it needs two different treatments though,
   since Webflow's own single "<=767px" rule assumed that width always
   means portrait: in PORTRAIT it's right to stack to one column, so the
   photo is just reordered after the text/buttons (same content-then-photo
   order as the homepage hero) instead of its natural DOM position before
   them. In LANDSCAPE, that same one-column stack is the bug the portrait
   fix would otherwise also apply — a landscape phone at this width has
   the same side-by-side room as a tablet just above this breakpoint, so
   it gets that 2-column layout instead (photo/content stay in their
   natural DOM order — left/right — same as every wider breakpoint). */
@media screen and (max-width: 767px) and (orientation: portrait) {
  .cta-media-wrapper.hide-mobile-landscape {
    display: block !important;
  }
  .cases_cta-content-wrapper {
    order: 1;
  }
  .cta-media-wrapper {
    order: 2;
  }
  .cta_media {
    max-width: 100%;
  }
}
@media screen and (max-width: 767px) and (orientation: landscape) {
  .cta-media-wrapper.hide-mobile-landscape {
    display: block !important;
  }
  .case-cta-grid {
    grid-template-columns: 50% 50% !important;
    /* Matches the >=768px layout's own small left padding (2rem, vs 5rem
       on the text side) instead of the <=767px rule's 5rem — shifts the
       photo back toward the card's left edge instead of sitting as far
       right as the text column. */
    padding-left: 2rem !important;
    /* Smaller bottom breathing room for the buttons than the 4rem the
       portrait stack uses — this cramped landscape height doesn't have
       room to spare. Kept in sync with the photo's own margin-bottom
       below it, which bleeds through exactly this much padding. */
    padding-bottom: 24px !important;
  }
  .cta-media-wrapper {
    margin-bottom: -24px !important;
  }
  /* Buttons: always one row instead of Webflow's own auto-fit collapsing
     to a stack once two 9.8rem-minimum buttons don't fit the (narrow, at
     this width) text column — then widened and pulled left so the row
     spills a bit onto the photo instead of being squeezed to fit inside
     the text column alone. z-index:2 + position:relative already came
     from Webflow's own <=991px rule, so it renders over the photo without
     needing its own stacking-context fix. */
  .buttons-wrapper {
    grid-template-columns: 1fr 1fr !important;
    width: 130% !important;
    margin-left: -18% !important;
  }
}
/* Flush-to-edge photo at every width the photo is shown at (matching the
   >=768px layout's own padding-bottom:0 — the whole point of the media
   wrapper's align-self:end there): Webflow's own <=767px/<=479px rules put
   the bottom padding back (4rem/2rem), presumably harmless while the photo
   was hidden. Rather than zeroing that padding on the whole grid — which
   also pulls the buttons flush to the card's bottom edge whenever the text
   column ends up the taller one (as it does in the cramped landscape
   layout above) — only the photo gets a matching negative margin-bottom,
   bleeding it through the padding on its own while the text column keeps
   its normal breathing room. */
@media screen and (max-width: 767px) {
  .cta-media-wrapper {
    margin-bottom: -4rem;
  }
}
@media screen and (max-width: 479px) {
  .cta-media-wrapper {
    margin-bottom: -2rem;
  }
}

/* "More Projects" cross-link cards: .is-lumora never had an aspect-ratio at all
   (stuck at the default "auto"), and .is-rd only got one inside the narrowest
   (≤479px) media query — everywhere else both collapse to whatever height their
   flex/grid siblings dictate, cropping almost all of the background montage
   image down to a thin strip. Give both a real ratio at every width. */
.project-placeholder.is-lumora,
.project-placeholder.is-rd {
  aspect-ratio: 16 / 9;
}

/* "More Projects" grid (.projects-grid): a plain grid-template-columns:1fr 1fr
   doesn't actually force equal columns — each 1fr track has an implicit
   minmax(auto,1fr), so a grid item whose content has a wide min-content size
   (here, XenoPatch's video-preview card, .more-project_wrapper) can still
   claim more than its fair half, leaving its "1fr" sibling narrower. minmax(0,1fr)
   removes that auto floor so both tracks actually split the row evenly.
   Scoped to >=768px (where Webflow's own "1fr 1fr" already applies) since
   its own narrower rule — grid-template-columns:repeat(auto-fit,minmax(16.2rem,1fr))
   — doesn't have this bug (an explicit minmax floor, not "auto") and
   deliberately collapses to one column once two would be too cramped to
   read; overriding it unscoped would force two columns there too. */
@media screen and (min-width: 768px) {
  .projects-grid {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) !important;
  }
}

/* XenoPatch's "More Projects" video-preview card (.more-project_wrapper):
   has no aspect-ratio of its own (unlike its sibling .project-placeholder,
   fixed above), so its box is sized from the <video>'s own aspect-ratio
   (790/500 ≈ 1.58) instead — a different ratio than its sibling's 16/9
   (≈1.78), so even at equal widths the two cards come out different
   heights. Giving the wrapper the same 16/9 makes both cards match; the
   video (100%/100% + the inline object-fit:cover it already has) then
   just crops to fit instead of dictating the box's own height. */
.more-project_wrapper {
  aspect-ratio: 16 / 9;
}
.more-projects_video,
.case-video {
  width: 100% !important;
  height: 100% !important;
}
.case-video {
  aspect-ratio: auto !important;
}

/* Lightbox: fullscreen viewer for .w-lightbox screenshot clicks (see reveal.js). */
.reveal-lightbox-overlay {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 9999;
  background: rgba(0, 0, 0, 0.9);
  align-items: center;
  justify-content: center;
  padding: 2rem;
  cursor: zoom-out;
}
.reveal-lightbox-overlay.is-visible {
  display: flex;
}
.reveal-lightbox-overlay img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
}

/* FAQ accordion: Webflow drove open/close (and the arrow rotation) entirely via IX2
   inline-style height changes — there's no CSS fallback at all, so with that JS dead
   every answer just renders at natural (open) height and clicking does nothing. */
.accordion-item-bottom-wrapper {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.3s ease;
}
.accordion-item-bottom-wrapper > * {
  min-height: 0;
  overflow: hidden;
}
.accordion-item.is-open .accordion-item-bottom-wrapper {
  grid-template-rows: 1fr;
}
/* .faq_answer's own top/bottom padding survives the 0fr collapse (padding isn't
   part of the row-track size), leaving a visible sliver — animate it out too. */
.faq_answer {
  padding-top: 0;
  padding-bottom: 0;
  transition: padding 0.3s ease;
}
.accordion-item.is-open .faq_answer {
  padding-top: 0.5rem;
  padding-bottom: 2rem;
}
.faq_arrow {
  transition: transform 0.3s ease;
}
.accordion-item.is-open .faq_arrow {
  transform: rotate(180deg);
}

/* Mobile nav fix: webflow.js still toggles the .w--open class, but the compiled CSS
   never got a `.w-nav-menu.w--open{...}` rule (that lived in the broken IX2 bundle) —
   not just `display`, but also the positioning that turns it into a dropdown instead
   of an in-flow flex item that overlaps and clips against the navbar row. `.w-nav`
   (the shared ancestor for both the desktop and mobile nav variants) is already
   `position:relative` in Webflow's base CSS, so anchor to that. */
.w-nav-menu.w--open {
  display: flex !important;
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  margin-top: 0.5rem;
}

/* Mobile/tablet nav dropdown (.nav_menu-wrapper, ≤991px): Webflow's own CSS
   redefines this element four separate times — an unscoped base rule plus
   three breakpoints (991/767/479) — each touching a different subset of
   width/radius/padding/columns/alignment, with no single rule owning the
   full picture. That's fragile (easy to get a half-updated look at
   in-between widths like 768px, e.g. a row misaligning when one column's
   label wraps to more lines than the other's — align-items:center from the
   991 rule, never revisited by the narrower ones) and hard to reason about.
   Replaced with one rule that owns the whole box for the entire ≤991 range:
   same two-column grid, same colors/border (still Webflow's, via its
   background-color/border custom properties, untouched here), just sized
   and aligned consistently instead of drifting between three variants.
   Typography, icons and the nav links' own classes are all untouched. */
@media screen and (max-width: 991px) {
  .nav_menu-wrapper {
    display: grid !important;
    grid-template-columns: 1fr 1fr !important;
    grid-auto-columns: 1fr !important;
    align-items: start !important;
    justify-content: flex-end !important;
    column-gap: 1.5rem !important;
    row-gap: 1.25rem !important;
    width: min(26rem, calc(100vw - 3rem)) !important;
    margin: 0.5rem 1rem 0 auto !important;
    padding: 2rem 1.5rem !important;
    border-radius: 1.5rem !important;
  }
  /* Each <li> also carries its own align-self:center from Webflow's base
     item styles, which wins over the wrapper's align-items above (a grid
     item's own align-self always overrides its container's align-items) —
     so a short single-line item still centers into its row's full height
     instead of sitting at the top, even once the wrapper itself is fixed. */
  .nav_menu-item {
    align-self: start !important;
  }
  /* A link that fits on one line (Lumora, XenoPatch...) shrink-wraps to its
     own text width, so the li's justify-content:center centers it fine. One
     that has to wrap ("nobody asked projects", via .is-permanent) instead
     ends up occupying the cell's full width once wrapped — a plain block
     element's rendered width after wrapping is however much space it was
     given, not the width of its longest line — and text-align:left (the
     inherited default) then sets each wrapped line flush against that
     box's left edge instead of centered, so it visibly starts further left
     than its single-line neighbors. Centering the text itself makes every
     line center within whatever width the box ends up with, matching them
     regardless of how many lines it wraps to. */
  .nav_menu-wrapper .nav_menu-link {
    text-align: center;
  }
}

/* Custom reveal system — replaces Webflow IX2 (hero cascade on load + scroll cascade).
   Selector list is the union of every page's own Webflow-generated hidden-until-js
   list (site-wide, shared across all pages — unmatched classes on a given page are
   simply no-ops there). */
html.w-mod-js :is(.is-ph, .anim-text-h3, .anim-text-h2, .anim-text-h5, .blur, .anim-persona-info, .anim-cases-button, .anim-cases-body, .stage-3-title, .text-animation, .footer_email-wrapper, .anim-footer-text, .anim-feedback, .anim-brow, .anim-features-stagger, .anim-benefits-stagger, .anim-feature-body, .hero_badge.margin-bottom.margin-medium, .text-align-center.margin-bottom.margin-small, .button.is-large, .is-step-1-frame, .is-step-2-frame, .stage-3-cubes, .accordion-item, .heading-style-h1, .text-size-large, .is-xeno-title, .about_p-wrapper p, .subheading) {
  opacity: 0;
  transform: translateY(28px);
  /* transition intentionally omitted: GSAP (reveal.js) owns duration/ease/stagger for this tween */
}

/* Safety net: if JS never runs (CDN down, script error), never leave content permanently invisible. */
html.reveal-fallback :is(.is-ph, .anim-text-h3, .anim-text-h2, .anim-text-h5, .blur, .anim-persona-info, .anim-cases-button, .anim-cases-body, .stage-3-title, .text-animation, .footer_email-wrapper, .anim-footer-text, .anim-feedback, .anim-brow, .anim-features-stagger, .anim-benefits-stagger, .anim-feature-body, .hero_badge.margin-bottom.margin-medium, .text-align-center.margin-bottom.margin-small, .button.is-large, .is-step-1-frame, .is-step-2-frame, .stage-3-cubes, .accordion-item, .heading-style-h1, .text-size-large, .is-xeno-title, .about_p-wrapper p, .subheading) {
  opacity: 1 !important;
  transform: none !important;
}

@media (prefers-reduced-motion: reduce) {
  html.w-mod-js :is(.is-ph, .anim-text-h3, .anim-text-h2, .anim-text-h5, .blur, .anim-persona-info, .anim-cases-button, .anim-cases-body, .stage-3-title, .text-animation, .footer_email-wrapper, .anim-footer-text, .anim-feedback, .anim-brow, .anim-features-stagger, .anim-benefits-stagger, .anim-feature-body, .hero_badge.margin-bottom.margin-medium, .text-align-center.margin-bottom.margin-small, .button.is-large, .is-step-1-frame, .is-step-2-frame, .stage-3-cubes, .accordion-item, .heading-style-h1, .text-size-large, .is-xeno-title, .about_p-wrapper p, .subheading) {
    transition: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
}

/* Services pricing cards: fall into their fanned position from above instead of
   rising like everything else (see reveal.js for the drop + the center card's
   landing bounce). The two side cards already carry an inline 3D fan transform
   (translateX/rotateY/scale, set directly on the element in the HTML) — a
   stylesheet transform can never win over an inline one, so the "fallen" y
   offset is set via gsap.set() in JS instead, which composes with that fan
   transform rather than replacing it. This block only needs to own opacity. */
html.w-mod-js .services_card-wrapper.is-left-card,
html.w-mod-js .services_card-wrapper.is-right-card,
html.w-mod-js .services_card-wrapper.is-central {
  opacity: 0;
}
html.reveal-fallback .services_card-wrapper.is-left-card,
html.reveal-fallback .services_card-wrapper.is-right-card,
html.reveal-fallback .services_card-wrapper.is-central {
  opacity: 1 !important;
}
@media (prefers-reduced-motion: reduce) {
  html.w-mod-js .services_card-wrapper.is-left-card,
  html.w-mod-js .services_card-wrapper.is-right-card,
  html.w-mod-js .services_card-wrapper.is-central {
    transition: none !important;
    opacity: 1 !important;
  }
}

/* Services pricing cards, ≤991px: Webflow's own responsive behavior here is
   either 2-up-then-1-orphan (991 breakpoint, auto-fit grid) or a full-width
   single column (767/479), depending on width — neither reads well for
   exactly 3 cards. Replaced with one horizontal, swipeable row instead.
   scroll-snap-align keeps a swipe settling on a card rather than stopping
   mid-card. Desktop's 3D fan (>991px, inline transform on the side cards)
   is untouched — this only overrides the container's own layout, not each
   card's transform. !important throughout this block because Webflow's own
   .services_card-wrapper.is-central{width:100%} rule has higher specificity
   (two classes vs. one) and would otherwise stretch just the center card
   to fill the row while the side cards sized correctly. */
@media screen and (max-width: 991px) {
  .services_grid-wrapper {
    height: auto !important;
  }
  .services_grid {
    display: flex !important;
    flex-direction: row !important;
    flex-wrap: nowrap !important;
    grid-template-columns: none !important;
    height: auto !important;
    align-content: normal !important;
    gap: 1rem;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    padding-bottom: 0.25rem;
  }
  .services_card-wrapper {
    flex: 0 0 auto !important;
    /* Webflow's own height:45rem (720px) never responsively shrinks at any
       width — fine at the card's original ~300-400px desktop width, but
       once narrowed to a peek-carousel card on a phone it produces an
       absurdly tall, mostly-empty card. This can't just become height:auto
       though: .services_card-inner has its own height:100% (an inline
       <style> rule, inherits from this element) so the front/back faces
       — position:absolute; inset:0, needed for the flip — collapse to zero
       height the moment this element's height is indeterminate. A smaller
       fixed height keeps that chain intact while actually fitting a narrow
       card's content (picked to fit the longest back-face list — the
       "Full package" 7-item back needs the most room of the three: at
       anything less than 40rem its back face is tight enough that the
       divider above "E-mail Me" gets flex-shrunk to 0 (invisible) and the
       button itself creeps past the card's own bottom edge). 40rem itself
       turned out to only be ~35px taller than that 7-item content actually
       needs (measured in Chromium) — thin enough that real-device font
       metrics (a fallback font rendering mid-load, iOS's own line-height
       rounding, etc.) can eat the difference, which is exactly what
       reportedly pushed "E-mail Me" flush against the card's bottom edge
       on a real phone. 43rem restores a real margin instead of a
       knife-edge fit, since .services_card-back's justify-content:center
       (set right below) means any shortfall shows up as equal overflow at
       *both* the top and bottom, not just the bottom. */
    height: 43rem !important;
    scroll-snap-align: start;
  }
  /* The divider above "E-mail Me" on every card back is the shortest flex
     child (1px) in its column, so on any width/content combination where
     the back face is even a hair short of the content's natural height,
     flexbox takes that missing space from the divider first — shrinking it
     to 0 (invisible) well before anything else visibly moves. Excluding it
     from shrinking means a slightly-too-short back face pushes content
     against the bottom padding instead of silently deleting this line. */
  .divider.is-services-cards {
    flex-shrink: 0 !important;
  }
  /* Webflow's own inline min-height:600px on the center card would otherwise
     force it 24px taller than the two side cards even after the rule above —
     all three should swipe at one consistent height. There's a second,
     narrower copy of this same min-height (scoped to <=479px, alongside the
     preserve-3d/backface-visibility fix the real rotateY flip needs at that
     width) sitting directly on .services_card-inner too — left alone it
     still forces the inner face 24px past the wrapper's own height, pushing
     the front face's rounded top corner out above the row (hidden behind
     whatever sits above it) and its bottom gradient border past the row's
     own bottom edge. */
  .services_card-wrapper.is-central,
  .services_card-wrapper.is-central .services_card-inner {
    min-height: 0 !important;
  }
  .services_card-inner {
    height: 100% !important;
  }
  /* Webflow's own .services_card-front/-back{height:45rem} is a fixed 720px,
     same problem as the wrapper above — inset:0 alone can't shrink them
     because an explicit height wins over top/bottom:0 auto-sizing. Without
     this the back face overflows past the (now shorter) card's edge. */
  .services_card-front,
  .services_card-back {
    height: 100% !important;
  }
  /* Tablet-ish widths: two cards fit comfortably side by side, so size each
     to ~46% — two sit fully visible and the third's edge peeks in. */
  .services_card-wrapper {
    width: calc(46% - 0.5rem) !important;
  }
}
/* Narrow phones: fitting two cards at ~46% each would squeeze them down to
   ~200px — too tight for a price + description to read comfortably. Below
   this a single card takes most of the row instead, with the next one's
   edge peeking in the same way. */
@media screen and (max-width: 600px) {
  .services_card-wrapper {
    width: 85% !important;
  }
}

/* Numbered list items on the services/stage-3 card backs ("01 Research and
   competitor analysis", etc.): Webflow's own .text-size-regular{flex:none}
   at <=991px stops the description text from shrinking, so once the card
   itself is narrower than the text's natural single-line width — which the
   carousel above deliberately makes it — it overflows past the card's edge
   instead of wrapping. Letting it shrink (min-width:0 is what actually lets
   a flex item wrap below its content's natural width) fixes the overflow
   without changing how it looks at any width where it already fit. */
@media screen and (max-width: 991px) {
  .card-item-wrapper .text-size-regular {
    flex: 1 1 auto !important;
    min-width: 0 !important;
  }
}

/* Timeline Stage 2 step cards (I/II/III), <=991px: same fix as the services
   pricing cards above and for the same reason — Webflow's own auto-fit grid
   here wraps to 2-up-then-1-orphan at this breakpoint. Card III is
   deliberately taller than I/II on desktop (its aspect-ratio:2/3 plus extra
   decorative frame content), which is a nice touch in a 3-column grid but
   just reads as misaligned in a 2-up-then-orphan wrap — so on top of the
   same swipeable-peek treatment, all three are leveled to equal height here
   (aspect-ratio reset + flex's own stretch, rather than a fixed height, so
   they still match whichever card's content is naturally tallest).

   Cards I and II are each wrapped in their own extra .step-1-wrapper /
   .step-2-wrapper div (card III has no such wrapper). Those wrappers are
   themselves flex containers around their single child, and neither a
   height:100% nor an align-self:stretch on the inner .stage2-grid-item
   actually filled them — collapsing the wrapper out of the box tree with
   display:contents instead makes each .stage2-grid-item a direct flex
   child of .timeline_stage2-grid, same as III, so one set of rules sizes
   all three identically with no nested-flex fighting. */
@media screen and (max-width: 991px) {
  .timeline_stage2-grid {
    display: flex !important;
    flex-wrap: nowrap !important;
    grid-template-columns: none !important;
    align-items: stretch !important;
    gap: 1rem;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    padding-bottom: 0.25rem;
  }
  .step-1-wrapper,
  .step-2-wrapper {
    display: contents !important;
  }
  .stage2-grid-item {
    flex: 0 0 auto !important;
    aspect-ratio: auto !important;
    height: auto !important;
    align-self: stretch !important;
    scroll-snap-align: start;
    width: calc(46% - 0.5rem) !important;
  }
}
@media screen and (max-width: 600px) {
  .stage2-grid-item {
    width: 85% !important;
  }
}

/* Case study "Highlights" grid (Lumora: 3 cards, XenoPatch: 6), <=991px: same
   auto-fit-grid orphan-wrap problem and same swipeable-peek fix as the
   services/timeline cards above. No flip mechanism or fixed aspect-ratio on
   the card itself here, so this is the plain version of the fix — align-
   items:stretch just evens out any small natural height differences between
   cards instead of needing an explicit height. XenoPatch overrides this
   back to a plain grid below — see that rule for why. */
@media screen and (max-width: 991px) {
  .highlights-grid {
    display: flex !important;
    flex-wrap: nowrap !important;
    grid-template-columns: none !important;
    align-items: stretch !important;
    gap: 1rem;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    padding-bottom: 0.25rem;
  }
  .highlights-grid-item {
    flex: 0 0 auto !important;
    scroll-snap-align: start;
    width: calc(46% - 0.5rem) !important;
  }
}
@media screen and (max-width: 600px) {
  .highlights-grid-item {
    width: 85% !important;
  }
}

/* XenoPatch's own Highlights grid (6 cards, .page-xenopatch on <body>):
   asked to be a plain grid instead of the swipe carousel above — 2 columns
   (so a 3-row 2x3 grid) in landscape, 1 column stacked in portrait. Lumora
   keeps the swipe carousel untouched. */
@media screen and (max-width: 991px) {
  .page-xenopatch .highlights-grid {
    display: grid !important;
    overflow-x: visible !important;
    scroll-snap-type: none !important;
  }
  .page-xenopatch .highlights-grid-item {
    flex: initial !important;
    width: auto !important;
    scroll-snap-align: none !important;
  }
}
@media screen and (max-width: 991px) and (orientation: landscape) {
  .page-xenopatch .highlights-grid {
    grid-template-columns: 1fr 1fr !important;
  }
}
@media screen and (max-width: 991px) and (orientation: portrait) {
  .page-xenopatch .highlights-grid {
    grid-template-columns: 1fr !important;
  }
}

/* Navbar entrance: slides down from above after the hero content settles, then
   the individual nav links stagger-drop onto it (see reveal.js for the timing —
   this only sets the initial hidden state and the no-JS/reduced-motion escape). */
html.w-mod-js .nav_container.shadow-l {
  opacity: 0;
  transform: translateY(-100%);
}
html.w-mod-js .nav_menu-item {
  opacity: 0;
  transform: translateY(-16px);
}
html.reveal-fallback .nav_container.shadow-l,
html.reveal-fallback .nav_menu-item {
  opacity: 1 !important;
  transform: none !important;
}
@media (prefers-reduced-motion: reduce) {
  html.w-mod-js .nav_container.shadow-l,
  html.w-mod-js .nav_menu-item {
    transition: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
}

/* Timeline "how it works" step cards (I/II/III): cascade falling in from
   above, left to right — their natural reading order — instead of the
   generic rise-from-below the rest of the page uses (see reveal.js for the
   stagger). */
html.w-mod-js .stage2-grid-item {
  opacity: 0;
  transform: translateY(-40px);
}
html.reveal-fallback .stage2-grid-item {
  opacity: 1 !important;
  transform: none !important;
}
@media (prefers-reduced-motion: reduce) {
  html.w-mod-js .stage2-grid-item {
    transition: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
}

/* Stage 3 flip cards (Design Only / Full Build): front and back should be
   the exact same height, since .stage-3-inner has a fixed height and both
   faces use position:absolute; inset:0 to fill it — but Webflow's own CSS
   also has a THREE-class rule, .stage-3-back.u-double-gradient-frame.
   shadow-l{position:static}, unscoped (applies at every width) and more
   specific than our plain .stage-3-back selector, so it always won
   regardless of source order. With position:static the back face sizes
   itself to its own content instead of filling the card, so front/back
   heights only matched by coincidence depending on how much text was on
   each — this was never a "make them match" sizing problem, just this rule
   never taking effect. Restoring position:absolute is the actual fix, not
   shrinking one face or averaging the two. */
.stage-3-back {
  position: absolute !important;
  inset: 0 !important;
}

/* Stage 3's two cards (Design Only / Full Build), <=767px: Webflow's own
   grid-template-columns:repeat(auto-fit,minmax(18.7rem,1fr)) needs each
   column to be at least 18.7rem (~299px). That's fine down at narrow
   portrait widths (~375px container, well under even one column's minimum
   past a certain point) and fine from 768px up (two fixed 1fr columns,
   no auto-fit) — but in between, container widths of roughly 530-750px
   (a phone in LANDSCAPE, not tablet) are wide enough for two comfortable
   columns yet narrower than 2*18.7rem+gap, so auto-fit collapses to one
   full-width column per card instead. Lowering the floor to 16rem keeps
   narrow portrait phones stacked (2*16rem+gap still exceeds their width)
   while letting landscape phones keep their two side-by-side cards.

   That alone isn't enough though: this grid has a THIRD child sharing the
   same two columns — the "Stage 3" ring header, Webflow's own CSS orders
   it first (order:-9999, before both cards). Once two columns are live, it
   takes column 1 of row 1 and pushes card one into column 2 alongside it,
   leaving card two alone on its own row instead of paired with card one.
   Spanning the ring header across both columns keeps it as its own full-
   width row, so the two cards (order 3 and 4) flow into row two together,
   same pairing this rule was meant to restore in the first place. */
@media screen and (max-width: 767px) {
  .timeline_stage-header.is-stage-3 {
    grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)) !important;
  }
  .stage-3-header-wrapper {
    grid-column: 1 / -1 !important;
  }
}

/* Stage 3 ring header ("Stage 3" + circle): part of Stage 3's own dedicated
   cascade (ring -> title/description -> cards, see reveal.js), not the
   shared reveal system. Stage 1/2 use the identical .timeline_circle-wrapper
   markup but are left untouched — only this stage's copy is targeted, via
   its unique .is-stage-3 modifier on the parent. */
html.w-mod-js .timeline_stage-header.is-stage-3 .timeline_circle-wrapper {
  opacity: 0;
  transform: translateY(28px);
}
html.reveal-fallback .timeline_stage-header.is-stage-3 .timeline_circle-wrapper {
  opacity: 1 !important;
  transform: none !important;
}
@media (prefers-reduced-motion: reduce) {
  html.w-mod-js .timeline_stage-header.is-stage-3 .timeline_circle-wrapper {
    transition: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
}

/* Mobile hamburger button (.nav_menu-button), <=479px: Webflow's own compiled
   CSS adds padding-top:.25rem here without a matching padding-bottom (the
   base rule is padding:0 on all sides), so the closed-state icon sits 4px
   lower than centered inside the button's flex box. The open state
   (.w--open) resets both to 0 — Webflow clearly intended the icon centered,
   this asymmetric top padding just never got cleaned up. Zeroing it lets the
   button's own align-items:center do the centering as intended. */
@media screen and (max-width: 479px) {
  .nav_menu-button {
    padding-top: 0 !important;
  }
}

/* Stage 3 cards' "tap me" hint: on the services pricing cards the same hint
   sits 64px below the description (Webflow's own 24px flex gap either side
   of a 16px .spacer-small div in between). The Stage 3 front face has no
   such spacer — just its container's flat 1rem (16px) flex gap — so the
   hint sat noticeably closer to the description there than on the services
   cards. Adding the missing 48px on top of that existing 16px gap matches
   the services cards' total spacing without touching the gap between this
   card's other elements (icon/title/description), which was already fine. */
.stage-3-front .services-flip-me {
  margin-top: 48px;
}
