:root {
  --pad: 28px;
  --pad-row: 28px;
  --pad-menu-row: 28px;
  --fit-size: 10vw;
}

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

html, body {
  background: #fff;
  color: #2b2b2b;
  height: 100%;
}

body {
  font-family: "Helvetica Neue LT Pro", "Helvetica Neue", Helvetica, Arial, sans-serif;
  overflow-x: hidden;
  letter-spacing: -0.2em;
  cursor: none;
}

/* Cross-document page transitions (Chrome/Edge only for now — unsupported
   browsers just navigate normally, no fallback needed). Opting in via
   @view-transition makes every same-origin navigation, e.g. home -> a case
   study page, render as a view transition instead of a hard cut; every page
   includes this shared stylesheet so the opt-in only needs to live here
   once, not duplicated per page. */
@view-transition {
  navigation: auto;
}

/* Default cross-document transition is a plain cross-fade. Overridden here
   to a wipe reveal instead: the incoming page never moves — it sits in its
   final resting position the whole time — and a clip-path edge sweeps right
   to left across it, uncovering more of the new page as it goes, like a
   blind lifting from the right. That clip boundary is itself the "line" —
   a hard edge between revealed and not-yet-revealed page, rather than a
   soft cross-fade. The outgoing page stays put underneath, unanimated,
   until it's fully covered. */
::view-transition-old(root) {
  animation: none;
}

::view-transition-new(root) {
  animation: wipe-reveal-left 1.3s cubic-bezier(0.22, 1, 0.36, 1) both;
}

@keyframes wipe-reveal-left {
  from {
    clip-path: inset(0 0 0 100%);
  }
  to {
    clip-path: inset(0 0 0 0);
  }
}

#custom-cursor {
  position: fixed;
  top: 0;
  left: 0;
  width: 210px;
  height: 210px;
  pointer-events: none;
  z-index: 9999;
  opacity: 0;
  /* Grow out of the fingertip (33.28% 8.31% of the 1200x1200 art is where
     the tip sits, the same fractions script.js uses to pin it to the mouse),
     so the tip stays put on the pointer while the finger balloons to size. */
  scale: 0.35;
  transform-origin: 33.28% 8.31%;
  will-change: transform, scale;
  /* Disappears instantly (this transition applies when .is-visible is
     removed); only the appearance animates, via the rule below. */
  transition: none;
}

/* Slower, slightly overshooting pop on the way in (JS adds .is-visible
   on the first pointer move; see updateCursorVisibility in script.js). */
#custom-cursor.is-visible {
  opacity: 1;
  scale: 1;
  transition: opacity 0.2s ease, scale 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}

@media (prefers-reduced-motion: reduce) {
  #custom-cursor.is-visible {
    transition: none;
  }
}

/* While a content row is expanded, script.js hides the big splash cursor.
   The whole page otherwise runs cursor: none, so without this the pointer
   would vanish entirely inside an open row. Hand control back to the
   native cursor there. */
body.row-open,
body.row-open .row-toggle {
  cursor: auto;
}

body.row-open .work-project,
body.row-open .work-project-cta a,
body.row-open .contact-details a,
body.row-open .contact-details .copy-email {
  cursor: pointer;
}

/* Load sequence: the title, each menu row and the tagline start hidden and
   lifted slightly, then rise and fade in together as one slow, deliberate
   beat — after a pause, not immediately — once the flower (see
   .title-flower below) has finished its own faster, separate reveal. The
   contrast in pace is the point: a quick flower, then this settling in
   unhurried, so the finale reads as elevated rather than rushed. The rise
   (translateY, not just opacity) is what keeps this reading as one smooth
   arrival instead of a flat on/off pop. */
.reveal {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.9s cubic-bezier(0.16, 1, 0.3, 1), transform 0.9s cubic-bezier(0.16, 1, 0.3, 1);
}

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

/* Each cascading menu row (Work, About, Contact) is absent from layout
   until its own turn, then its background rises and fades in slowly — no
   grow, no wipe — so .menu-rows' natural height stays genuinely 0 for any
   row not yet revealed (keeping title-row full-screen while only the
   title is up, see the .accordion comment below). display can't itself be
   transitioned, so the reveal is two classes: script.js adds "is-visible"
   (display: none -> block, opacity/transform still at rest) a frame ahead
   of "is-in" (fade/rise to place), so the transition has a committed
   starting frame to animate from instead of being skipped.
   The row's background, its label and its swatch icon are three separate
   beats, not one: "is-in" only fades in the section's own background,
   the label (.fit) gets its own independent opacity switched on by
   "text-in", and the swatch (.row-swatch) by "icon-in" — see script.js
   for when each fires. All three currently fire together, so this reads
   as one slow rise rather than three staggered ones. The three rows
   themselves, though, each hold a slightly later --row-delay (set below
   by nth-child) so they arrive as a gentle top-to-bottom cascade instead
   of popping in as one flat block — living up to the class name. */
.row-cascade {
  display: none;
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.9s cubic-bezier(0.16, 1, 0.3, 1) var(--row-delay, 0ms),
    transform 0.9s cubic-bezier(0.16, 1, 0.3, 1) var(--row-delay, 0ms);
}

.row-cascade.is-visible {
  display: block;
}

.row-cascade.is-in {
  opacity: 1;
  transform: translateY(0);
}

.row-cascade .row-toggle .fit {
  opacity: 0;
  transition: opacity 0.9s cubic-bezier(0.16, 1, 0.3, 1) var(--row-delay, 0ms);
}

/* Higher specificity than .row-swatch below, so its border-radius
   transition has to live here too (transition is a shorthand — this rule
   would otherwise wipe it out rather than merge with it), keeping the
   square-to-circle toggle animated on top of the intro's opacity fade. */
.row-cascade .row-toggle .row-swatch {
  opacity: 0;
  transition: opacity 0.9s cubic-bezier(0.16, 1, 0.3, 1) var(--row-delay, 0ms),
    border-radius 0.2s ease;
}

.row-cascade.text-in .row-toggle .fit {
  opacity: 1;
}

.row-cascade.icon-in .row-toggle .row-swatch {
  opacity: 1;
}

.menu-rows > .row-cascade:nth-child(1) {
  --row-delay: 0ms;
}

.menu-rows > .row-cascade:nth-child(2) {
  --row-delay: 90ms;
}

.menu-rows > .row-cascade:nth-child(3) {
  --row-delay: 180ms;
}

/* Same selector list as the prefers-reduced-motion block below — toggled
   by script.js for one frame on repeat visits within the session, so the
   staged intro's settled state applies as a flat cut instead of racing
   (and sometimes losing) a fade against the browser's first paint of that
   navigation. See revealIntro's reduceMotion/introAlreadySeen branch. */
.no-intro-transition .reveal,
.no-intro-transition .row-cascade,
.no-intro-transition .row-cascade .row-toggle .fit,
.no-intro-transition .row-cascade .row-toggle .row-swatch,
.no-intro-transition .title-flower .flower-center,
.no-intro-transition .title-flower .flower-petal,
.no-intro-transition .title-flower .flower-stem {
  transition: none;
}

@media (prefers-reduced-motion: reduce) {
  .reveal,
  .row-cascade,
  .row-cascade .row-toggle .fit,
  .row-cascade .row-toggle .row-swatch,
  .title-flower .flower-center,
  .title-flower .flower-petal,
  .title-flower .flower-stem {
    transition: none;
  }

  ::view-transition-new(root) {
    animation: none;
  }

  .title-flower svg {
    animation: none;
  }
}

.page {
  min-height: 100%;
  display: flex;
  flex-direction: column;
}

.accordion {
  display: flex;
  flex-direction: column;
  flex: 1;
}

/* Load sequence, part 2: .menu-rows itself needs no special CSS — each of
   its 3 row-cascade children is display: none until its own turn (see
   .row-cascade above), so .menu-rows' own natural height is already 0 on
   first paint, letting title-row (flex: 1, above) fill the entire viewport
   while just the flower and title are up there, exactly like a full-screen
   splash. As script.js reveals each row on its own turn, title-row's
   flex-basis shrinks to make room automatically — no separate shrink
   animation needed there. */

.row {
  border-top: 1px solid rgba(0, 0, 0, 0.08);
  background: #fff;
}

.contact-row {
  background: #ffafc2;
}

.title-row {
  position: relative;
  overflow: hidden;
  border-top: none;
  background-color: #2e7d5a;
  color: #fff;
  flex: 1;
  display: flex;
  flex-direction: column;
}

/* Flower graphic: fetched and injected as inline SVG (see script.js) rather
   than a CSS background-image, so its individual paths can be targeted and
   staggered in as the load sequence's opening stage. Sized/positioned the
   same way the old background-image was — in vw rather than % or "contain"
   (tied to actual window width, not the row's own height, which changes
   every time the accordion opens/closes another row), and larger than the
   typical open row height so the row's own box (overflow: hidden, above)
   naturally clips off roughly the bottom portion instead of shrinking the
   artwork to fit. Top offset shrinks as the window widens, going negative
   around 1500px and beyond, floored at -60px so it doesn't over-crop on
   very wide screens. */
.title-flower {
  position: absolute;
  left: 50%;
  top: clamp(-60px, calc(260px - 20vw), 150px);
  width: 65vw;
  transform: translateX(-50%);
  pointer-events: none;
  z-index: 0;
}

.title-flower svg {
  display: block;
  width: 100%;
  height: auto;
  /* Gentle sway, pivoting at the base of the stem (bottom-middle of the
     art). The visible bloom sits a long way above that pivot, so a tiny
     rotation there reads as the whole flower leaning on its stem. The
     keyframe stops are deliberately uneven, and start/end at rest, so it
     drifts organically rather than ticking like a metronome. */
  transform-origin: 50% 100%;
  animation: flower-sway 11s ease-in-out infinite;
}

@keyframes flower-sway {
  0% { transform: rotate(0deg); }
  22% { transform: rotate(0.75deg); }
  41% { transform: rotate(-0.2deg); }
  58% { transform: rotate(-0.85deg); }
  74% { transform: rotate(-0.15deg); }
  88% { transform: rotate(0.4deg); }
  100% { transform: rotate(0deg); }
}

/* Flower.svg's own layer names (flower middle / petal 1-6 / stem) are
   mapped to these classes by script.js right after the SVG is injected —
   see the fetch() call there, which also sets each path's own
   transition-delay individually (circle, then petal 1 through petal 6 in
   order) — so the reveal order comes from real names instead of DOM
   position or a bounding-box guess. Each layer unfurls in place — scaling
   up from just under full size as it fades, rather than a flat opacity
   pop — which reads as a soft bloom instead of a flash even when caught
   mid-transition (e.g. only one petal in so far). */
.title-flower .flower-center,
.title-flower .flower-petal {
  opacity: 0;
  transform: scale(0.88);
  transform-box: fill-box;
  transform-origin: center;
  transition: opacity 0.22s cubic-bezier(0.22, 1, 0.36, 1), transform 0.22s cubic-bezier(0.22, 1, 0.36, 1);
}

/* The stem breaks from the petals' quick unfurl on purpose: script.js times
   its transition-delay (STEM_DELAY_MS) to land in the same beat as the
   bottom elements' own reveal, not the next step in the petals' rhythm —
   so it needs to share their unhurried 0.9s fade too. Reusing the petals'
   fast duration here would make the stem snap into place well before its
   slower finale partners finish settling, undoing the point of syncing
   the delay in the first place. */
.title-flower .flower-stem {
  opacity: 0;
  transition: opacity 0.9s cubic-bezier(0.16, 1, 0.3, 1);
}

.title-flower.is-in .flower-center,
.title-flower.is-in .flower-petal {
  opacity: 1;
  transform: scale(1);
}

.title-flower.is-in .flower-stem {
  opacity: 1;
}

.title-toggle,
.title-row .row-content {
  position: relative;
  z-index: 1;
}


.fit {
  white-space: nowrap;
  display: inline-block;
  font-weight: 500;
  line-height: 1;
}

.menu-toggle .fit {
  font-size: calc(var(--fit-size) * 0.6);
}

@media (min-width: 1200px) {
  .menu-toggle .fit {
    font-size: calc(var(--fit-size) * 0.33);
  }
}

.row-toggle.menu-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-top: var(--pad-menu-row);
  padding-bottom: var(--pad-menu-row);
}

.row-swatch {
  flex-shrink: 0;
  width: 64px;
  height: 64px;
  background: #7ee8ba;
  border-radius: 0;
}

.contact-row .row-swatch {
  background: #ff5c72;
}

.row-swatch.is-circle {
  border-radius: 50%;
}

.title-toggle .fit {
  font-size: var(--fit-size);
  margin-top: calc(var(--fit-size) * -0.12);
  margin-bottom: calc(var(--fit-size) * -0.12);
}

.with-word {
  position: relative;
  display: inline-block;
}

.with-text {
  display: inline-block;
  transition: opacity 0.3s ease;
}

.with-word::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 0.12em;
  background: #fff;
  transform: translateY(calc(-50% + 12px));
  opacity: 0;
  transition: opacity 0.3s ease;
}

.title-row[data-open="false"] .with-text {
  opacity: 0;
}

.title-row[data-open="false"] .with-word::after {
  opacity: 1;
}

.row-toggle {
  display: block;
  width: 100%;
  margin: 0 auto;
  background: none;
  border: none;
  text-align: left;
  cursor: none;
  color: inherit;
  font-family: inherit;
  padding: var(--pad-row) var(--pad);
}

.row-content {
  height: 0;
  overflow: hidden;
  contain: layout paint;
  will-change: height;
  transition: height 0.4s cubic-bezier(0.33, 1, 0.68, 1);
}

.row-content-inner {
  display: flex;
  align-items: center;
  margin: 0 auto;
  font-size: 1.1rem;
  color: #444;
  letter-spacing: normal;
  padding: 1.25rem var(--pad) 2.5rem;
  opacity: 0;
  transition: opacity 0.2s ease;
}

.row[data-open="true"] .row-content-inner {
  opacity: 1;
}

.contact-row .row-content-inner {
  padding-top: 0;
  padding-bottom: calc(var(--pad-menu-row) * 2);
}

.title-row .row-content-inner {
  color: #fff;
  padding-bottom: var(--pad);
}

.title-row .contact-col-location {
  font-weight: 420;
}

.title-row .contact-col-location p {
  max-width: 300px;
}

.title-row .row-content {
  margin-top: auto;
}

.contact-details {
  display: flex;
  width: 100%;
  gap: 66px;
}

.contact-col {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  text-align: left;
  gap: 0.4em;
}

.contact-col-location {
  flex: 1 1 50%;
  font-size: 21px;
  font-weight: 666;
  gap: 33px;
  line-height: 1.4;
}

.about-row .contact-col-location {
  font-weight: 420;
}

/* Work row: a horizontally-scrollable strip of project cards. Mouse users
   without a trackpad can click-and-drag the strip instead of only being
   able to use a scrollbar/trackpad gesture — see the drag handling in
   script.js, which also suppresses the image-cycle click below if the
   pointer actually moved (so a drag doesn't also cycle the image it
   started on). .is-dragging (added while dragging) turns off scroll-snap
   so it doesn't fight the manual scrollLeft updates.
   Each card's media area can hold several images/videos stacked on top of
   each other via position: absolute — only the one with .is-active
   showing — and script.js cycles which one that is on click/tap. Cards
   with just one asset never register that click handler, since there's
   nothing to cycle to. */
.work-projects {
  display: flex;
  gap: 16px;
  width: 100%;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}

.work-projects.is-dragging {
  scroll-snap-type: none;
  user-select: none;
}

.work-projects::-webkit-scrollbar {
  display: none;
}

.work-project {
  flex: 0 0 auto;
  scroll-snap-align: start;
  width: min(460px, 80vw);
  display: flex;
  flex-direction: column;
  cursor: none;
}

.work-project-title {
  display: block;
  margin-bottom: 0.5em;
  font-size: 18px;
  font-weight: 700;
  color: #2b2b2b;
  letter-spacing: normal;
}

.work-project-media {
  position: relative;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  background: #7be0b2;
}

.work-project-media img,
.work-project-media video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease;
}

.work-project-media img.is-active,
.work-project-media video.is-active {
  opacity: 1;
}

/* Sector and discipline share one compact line instead of two stacked
   ones — .work-project-category (e.g. "Arts, Nature & Culture") is the
   small uppercase brand-green eyebrow; .work-project-type (e.g. "Custom
   Website") follows it after a middle-dot separator. */
.work-project-meta {
  display: block;
  margin-top: 0.6em;
  letter-spacing: normal;
}

.work-project-category {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #2e7d5a;
}

.work-project-type {
  font-size: 12px;
  color: #767676;
}

.work-project-type::before {
  content: "\2022";
  margin: 0 0.4em;
  color: #999;
}

.work-project-cta {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 0.7em;
}

.work-project-cta button,
.work-project-cta a {
  display: inline-flex;
  align-items: center;
  padding: 6px 14px;
  border-radius: 30px;
  font-size: 13px;
  font-weight: 700;
  text-decoration: none;
  font-family: inherit;
  letter-spacing: normal;
  border: none;
  white-space: nowrap;
}

.work-project-cta button {
  background: #f0f0f0;
  color: #999;
  cursor: default;
}

.work-project-cta a {
  background: #2e7d5a;
  color: #fff;
  cursor: none;
  transition: background 0.2s ease, color 0.2s ease;
}

.work-project-cta a:hover {
  background: #7ee8ba;
  color: #2b2b2b;
}

.work-project-cta a.work-project-cta-pink {
  background: #ffafc2;
  color: #2b2b2b;
}

.work-project-cta a.work-project-cta-pink:hover {
  background: #ff5c72;
  color: #fff;
}

.about-row .contact-col-location {
  gap: 16px;
}

@media (min-width: 701px) {
  .about-row .contact-col-location > p {
    width: 88%;
    max-width: 88%;
  }
}

.contact-col-location p {
  max-width: 666px;
}

.contact-row .contact-col-location {
  color: #ff5c72;
  gap: 0.4em;
  line-height: 1;
  font-weight: 420;
}

.contact-col-group {
  display: flex;
  flex: 1 1 50%;
  gap: 33px;
}

.contact-col-links {
  flex: 1 1 50%;
  font-size: 16px;
}

.services-note {
  margin-bottom: 1em;
  font-weight: bold;
}

.services-note:not(:first-child) {
  margin-top: 1em;
}

.about-mobile-boxes {
  display: none;
}

.contact-details a,
.contact-details .copy-email {
  position: relative;
  color: inherit;
  text-decoration: none;
  cursor: pointer;
}

.contact-details a::after,
.contact-details .copy-email::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -0.05em;
  height: 1px;
  background: currentColor;
  opacity: 0;
  transition: opacity 0.2s ease;
}

.contact-details a:hover::after,
.contact-details .copy-email:hover::after {
  opacity: 1;
}

.contact-details .copy-email {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  letter-spacing: inherit;
  cursor: none;
}

.accordion .row:last-child {
  border-bottom: 1px solid rgba(0, 0, 0, 0.08);
}

@media (max-width: 700px) {
  :root {
    --pad: 10px;
    --pad-row: 10px;
    --pad-menu-row: 10px;
  }

  .row-swatch {
    width: 32px;
    height: 32px;
  }

  .title-flower {
    top: 50px;
    width: 125vw;
    transform: translateX(calc(-50% + 30px));
  }

  /* 84px butterflies read as oversized on a narrow phone hero — scale them
     down to match. Keep in step with SIZE in butterflies.js. */
  .butterfly {
    width: 56px;
    height: 56px;
  }

  /* .work-project-media is already 1:1 at every size (see the base rule
     above) — this just narrows the card itself for mobile. */
  .work-project {
    width: 78vw;
  }

  /* Two side-by-side pills get cramped at 78vw — stack and stretch them
     full-width instead. */
  .work-project-cta {
    flex-direction: column;
    align-items: stretch;
  }

  .work-project-cta button,
  .work-project-cta a {
    justify-content: center;
  }

  /* First 3 cards (Sterts, Orlando's, Voicenote Library) are the only ones
     with both a real CTA link and a second button/link (Sterts now has its
     "View Project" case study link; Orlando's and Voicenote Library still
     have the disabled "Case study coming soon" button) — the rest have just
     the case-study button alone, so reordering it wouldn't change anything
     there. On mobile, that second CTA reads first, above the live link. */
  .work-projects > .work-project:nth-child(-n+3) .work-project-cta button,
  .work-projects > .work-project:nth-child(-n+3) .work-project-cta a.work-project-cta-pink {
    order: -1;
  }

  /* Body copy shrinks on mobile; the row headings (title + Projects/Products/
     etc, which use --fit-size) are left alone since they already resize
     responsively via the auto-fit script. */
  .row-content-inner {
    font-size: 0.95rem;
    padding: 10px;
  }

  .contact-col-location {
    font-size: 18px;
    gap: 1.25rem;
  }

  .about-row .contact-col-location {
    gap: 0.8rem;
  }

  .title-row .contact-col-location p {
    max-width: 70%;
  }

  .contact-col-links {
    font-size: 15px;
  }

  .contact-details {
    flex-direction: column;
    gap: 1.25rem;
  }

  .contact-col-group {
    flex-direction: column;
    gap: 1.25rem;
  }

  .contact-col {
    flex: none;
    width: 100%;
  }

  .with-word::after {
    transform: translateY(calc(-50% + 5px));
  }

  /* About row on mobile: swap the stacked two-column layout for a row of
     fixed-height, horizontally-scrollable cards. */
  .about-row .contact-details {
    display: none;
  }

  .about-mobile-boxes {
    display: flex;
    width: 100%;
    gap: 10px;
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }

  .about-mobile-boxes::-webkit-scrollbar {
    display: none;
  }

  .about-mobile-box {
    flex: 0 0 100%;
    scroll-snap-align: start;
    width: 100%;
    height: 300px;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 0.6em;
    padding: var(--pad);
    background: #7be0b2;
    color: #2b2b2b;
    font-size: 15px;
  }

  .about-mobile-box:not(:first-child) {
    background: none;
    padding-left: 0;
    padding-right: 0;
  }

  .about-mobile-box-cols {
    height: 330px;
    flex-direction: column;
    gap: 1.1em;
  }

  .about-mobile-box-col {
    flex: none;
    display: flex;
    flex-direction: column;
    gap: 0.3em;
  }

  .about-mobile-box-heading {
    font-size: 21px;
    font-weight: 700;
    line-height: 1.4;
  }

  .about-mobile-box-intro {
    position: relative;
    justify-content: flex-end;
  }

  .about-mobile-box-headshot {
    position: absolute;
    top: var(--pad);
    right: var(--pad);
    width: 30%;
    aspect-ratio: 1 / 1;
    object-fit: cover;
  }

  .about-row .row-content-inner {
    position: relative;
  }

  .about-mobile-dots {
    position: absolute;
    bottom: calc(var(--pad) * 2);
    right: calc(var(--pad) * 2);
    display: flex;
    gap: 6px;
    z-index: 2;
  }

  .about-mobile-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: rgba(43, 43, 43, 0.2);
  }

  .about-mobile-dot.is-active {
    background: #2e7d5a;
  }
}

/* Touch devices: no real mouse, so the custom cursor graphic has nothing to
   track. script.js skips all cursor wiring here (it gates on a fine pointer),
   and this hides the <img> and restores native cursor behavior as a backstop. */
@media (hover: none), (pointer: coarse) {
  #custom-cursor {
    display: none;
  }

  body,
  .row-toggle,
  .contact-details a,
  .contact-details .copy-email,
  .work-project-cta a {
    cursor: auto;
  }
}

/* Butterflies: a few butterflies in the brand palette drifting across the
   green title-row. They wander, land near the middle of the flower, and —
   on a real pointer — come and perch on the cursor for a moment before
   startling away. All the flight is butterflies.js writing `transform` on
   each .butterfly every frame; the only thing done in CSS is the wing-flap
   below.

   No fade: the container is always visible, and butterflies.js keeps each
   butterfly parked off-screen until the intro has finished, then flies them
   in from the edges. They keep flying if a menu row collapses the title-row
   — no fade-out, they just adapt to the smaller space.

   The host fills the title-row's padding box (which already has
   overflow: hidden and position: relative). It's pointer-events: none and
   normally sits at z-index 1 — same layer as .title-toggle but earlier in
   the DOM, so the "Made with Ace" text always paints on top. butterflies.js
   lifts it to z-index 3 only while a butterfly is interacting with the
   cursor, so that one moment reads above the title; the custom cursor
   (z-index 9999) still stays above everything. */
.butterflies {
  position: absolute;
  inset: 0;
  z-index: 1;
  overflow: hidden;
  pointer-events: none;
}

.butterflies.is-lifted {
  z-index: 3;
}

.butterfly {
  position: absolute;
  top: 0;
  left: 0;
  width: 84px;
  height: 84px;
  will-change: transform;
}

.bf-svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}

/* Two poses in one SVG, always both rendered — the swap is opacity, not
   display, so the wing animations never stop and restart (which showed up
   as a butterfly "gliding" after it left the cursor) and the change reads
   as a quick crossfade:
     .bf-fly  — top-down, wings spread. Flight, and resting on the flower.
     .bf-rest — side-on profile, facing right. Only when perched on the
                cursor (butterflies.js adds .is-perched).  */
.bf-fly,
.bf-rest {
  transition: opacity 0.2s ease;
}

.bf-rest {
  opacity: 0;
}

.butterfly.is-perched .bf-fly {
  opacity: 0;
}

.butterfly.is-perched .bf-rest {
  opacity: 1;
}

/* Perched wings slowly fan open and closed, pivoting at the wing root, so
   the butterfly still moves a little while it's sitting on the cursor. */
.bf-rest-fan {
  transform-box: fill-box;
  transform-origin: 57% 78%;
  animation: bf-fan 1.7s ease-in-out infinite;
}

/* Wing-flap: each side's inner group scales toward its own inner edge
   (transform-box: fill-box + transform-origin: 100% 50% puts the pivot at
   the wing root, next to the body, for both the plain left group and the
   mirrored right one). Quick in flight, slow and shallow while resting on
   the flower. */
.bf-flap {
  transform-box: fill-box;
  transform-origin: 100% 50%;
  animation: bf-flap 0.34s ease-in-out infinite;
}

.butterfly.is-resting .bf-flap {
  animation: bf-flap-rest 2.7s ease-in-out infinite;
}

@keyframes bf-flap {
  0%, 100% { transform: scaleX(1); }
  50% { transform: scaleX(0.32); }
}

@keyframes bf-flap-rest {
  0%, 100% { transform: scaleX(0.94); }
  50% { transform: scaleX(0.8); }
}

@keyframes bf-fan {
  0%, 100% { transform: rotate(-3deg) scaleX(0.95); }
  50% { transform: rotate(2deg) scaleX(1.02); }
}

/* Reduced motion: butterflies.js skips the flight loop entirely and just
   rests a couple near the middle of the flower — kill the wing motion so
   they simply sit there, already settled. */
@media (prefers-reduced-motion: reduce) {
  .bf-flap {
    animation: none;
    transform: scaleX(0.92);
  }

  .bf-rest-fan {
    animation: none;
  }
}
