/* ═══════════════════════════════════════════════════════════════════════════
   LACCINO MOTION LAYER
   The interaction language of the reference (haptikos.tech), carried in our
   colours: one expo-out curve for everything, masked text rises, a wiping
   menu, underline wipes, a sweeping page transition. The scroll-driven parts
   live in js/main.js; this file holds the CSS half.

   Rules: transform and opacity only (clip-path for wipes); nothing here
   changes layout; everything is switched off under prefers-reduced-motion.
   Brand green is used exactly as defined (--green-bg / --accent), never tinted.
   ═══════════════════════════════════════════════════════════════════════════ */

:root {
  --motion-ease: cubic-bezier(0.19, 1, 0.22, 1);   /* expo.out, the reference curve */
  --motion-ink:  var(--button-green);               /* forest green, never black */
}

/* While a page is still loading (body hidden until .loaded) the canvas shows
   brand green, so an incoming page transition hands over without a flash. */
html { background: var(--green-bg); }

/* ── Text masks (built by splitWords in main.js) ────────────────────────── */
.wordMask {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
  /* Room for descenders inside the mask without moving the line box. */
  padding-bottom: 0.12em;
  margin-bottom: -0.12em;
}
.wordMask .word { display: inline-block; will-change: transform; }

/* Image reveal: GSAP now drives the zoom settle, so retire the old CSS scale
   (it relied on an .animated class nothing added, leaving images at 1.2x). */
.image-wrap[data-animate-in="image-reveal"] img { transform: none; transition: none; }

/* ── Scroll reveal (data-reveal): same curve, the reference's longer settle ─ */
.has-reveal [data-reveal] {
  transition: opacity 0.58s var(--motion-ease), transform 0.72s var(--motion-ease);
  transition-delay: calc(var(--reveal-i, 0) * 80ms);
}
@media (hover: none), (max-width: 1023px) {
  .has-reveal [data-reveal] { transition-delay: 0s; }
}

/* Text travels right to left, in scroll order. JS damps progress rather
   than running a timed reveal; the default and reduced-motion view is static. */
@media (prefers-reduced-motion: no-preference) {
  #more-services.is-text-motion { overflow-x: clip; }
  #more-services.is-text-motion .service-text__row {
    --text-drift: 80px;
    translate: calc((1 - var(--text-progress, 1)) * var(--text-drift)) 0;
    opacity: calc(0.25 + var(--text-progress, 1) * 0.75);
    transition: none;
  }
  #more-services.is-text-motion .service-text__row:focus-within {
    translate: 0 0;
    opacity: 1;
  }
}
@media (prefers-reduced-motion: no-preference) and (max-width: 767px) {
  #more-services.is-text-motion .service-text__row { --text-drift: 24px; }
}

/* ── Nav hover: a marker fades in and the label slides to make room ─────── */
header .header-nav-label { position: relative; transition: transform 0.42s var(--motion-ease), color 0.24s ease; }
header .header-nav-link:not([aria-current]):not(:has(.header-nav-marker)) .header-nav-label::before {
  content: '>';
  position: absolute;
  left: -12px;
  opacity: 0;
  transform: translateX(-4px);
  transition: opacity 0.42s var(--motion-ease), transform 0.42s var(--motion-ease);
}
@media (hover: hover) {
  header .header-nav-link:not([aria-current]):not(:has(.header-nav-marker)):hover .header-nav-label { transform: translateX(12px); }
  header .header-nav-link:not([aria-current]):not(:has(.header-nav-marker)):hover .header-nav-label::before { opacity: 1; transform: none; }
}

/* Home always carries a directional marker in the shared markup, while
   Contact carries one on its own page. Keep both out of sight at rest and
   reveal them only for hover or keyboard focus, matching the other links. */
header .header-nav-home-marker,
header .header-nav-link[href="/contact"] > .header-nav-label > .header-nav-marker {
  position: absolute;
  left: -12px;
  min-width: 0;
  margin-right: 0;
  opacity: 0;
  transform: translateX(-4px);
  animation: none !important;
  transition: opacity 0.24s ease, transform 0.24s var(--motion-ease);
}
header .header-nav-home-marker--back { transform: translateX(4px) rotate(180deg); }
header .header-nav-link:is(:hover, :focus-visible) > .header-nav-label--home,
header .header-nav-link[href="/contact"]:is(:hover, :focus-visible) > .header-nav-label {
  transform: translateX(12px);
}
header .header-nav-link:is(:hover, :focus-visible) .header-nav-home-marker,
header .header-nav-link[href="/contact"]:is(:hover, :focus-visible) > .header-nav-label > .header-nav-marker {
  opacity: 1;
  transform: none;
}
header .header-nav-link--back-home:is(:hover, :focus-visible) .header-nav-home-marker {
  transform: rotate(180deg);
}

/* ── Phone menu: JS drives the wipe; drop the old opacity fade so it does not
   fight the clip. ────────────────────────────────────────────────────────── */
.modal-menu.is-scripted { transition: none; }

/* ── Links: no underlines anywhere on the site ─────────────────────────────
   Arrow links carry a straight arrow that slides right on hover, whether
   they stay on the site or open another one. Links inside running text keep a distinct colour instead of a
   line, and every link keeps its keyboard focus ring. */
.link-arrow span { background-image: none; padding-bottom: 0; }
.link-arrow .icon-arrow { transition: transform 0.42s var(--motion-ease); }
.link-arrow:hover .icon-arrow { transform: translateX(4px); }
.link-arrow:hover span { opacity: 1; }

.pr-plan__cta,
.pr-more__link { border-bottom: 0; padding-bottom: 0; }

.hc__link,
.legal-doc a,
.review-aside__mail { text-decoration: none; color: var(--sys-accent-soft, #91c4ab); transition: color 0.3s ease; }
.hc__link:hover,
.legal-doc a:hover,
.review-aside__mail:hover { color: var(--text); opacity: 1; }
.cs__link { text-decoration: none; font-weight: 700; }

/* Work filters: the active filter is marked like the nav, with a ">" arrow,
   not a line. */
.wk__filter,
.wk__filter.is-active { background-image: none; }
.wk__filter::before {
  content: '>';
  display: inline-block;
  width: 0;
  opacity: 0;
  transform: translateX(-6px);
  transition: opacity 0.42s var(--motion-ease), transform 0.42s var(--motion-ease), width 0s;
}
.wk__filter.is-active::before { width: 14px; opacity: 1; transform: none; }

/* ── Page transition overlay (built by initPageTransitions) ─────────────── */
.page-transition {
  position: fixed;
  inset: 0;
  z-index: 20000;
  pointer-events: none;
  visibility: hidden;
}
.page-transition.is-active { visibility: visible; pointer-events: auto; }
.page-transition__panel {
  position: absolute;
  inset: 0;
  transform: translate3d(0, 100%, 0);
  will-change: transform;
}
.page-transition__panel--ink   { background: var(--motion-ink); }
.page-transition__panel--green { background: var(--green-bg); }

/* ── Reduced motion ─────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  html { background: var(--bg); }
  header .header-nav-label,
  header .header-nav-label::before,
  header .header-nav-marker,
  .link-arrow span,
  .link-arrow .icon-arrow { transition: none !important; }
  header .header-nav-link:is(:hover, :focus-visible) .header-nav-label { transform: none !important; }
  .wordMask { overflow: visible; }
  .page-transition { display: none; }
}

/* ── Live-site links: no underline. Applies to every link-arrow that opens
   in a new tab; the arrow stays level, as on every other link. ───────────── */
.link-arrow[target="_blank"] span { background-image: none; padding-bottom: 0; }
.link-arrow[target="_blank"] .icon-arrow { transition: transform 0.42s var(--motion-ease); }

/* The framed CTA keeps its distinctive wipe, but completes before it starts
   to feel like a separate scene transition. */
.button-frame svg.plaisio .hover,
.button-frame .label-wrap span,
.button-frame .label-wrap svg.icon-arrow path { transition-duration: 0.55s; }
.link-arrow[target="_blank"]:hover .icon-arrow { transform: translate3d(3px, 0, 0); }
@media (prefers-reduced-motion: reduce) {
  .link-arrow[target="_blank"]:hover .icon-arrow { transform: none; }
}

/* ── Shared interactive system ────────────────────────────────────────────
   Public CTAs and application actions share a simple rounded shape, readable
   labels and predictable sizing. Compact actions retain a 44px touch target. */
:root {
  --button-ink: #0E2A1E;
  --interactive-fill: var(--leaf);
  /* Leaf-green controls move to the brand Sky Blue on hover instead of
     disappearing into a white surface. */
  --interactive-fill-hover: var(--sky);
  --interactive-text: var(--button-ink);
  --interactive-text-hover: var(--deep);
}

.button-frame,
.sx-btn,
.pr-plan__cta,
.footer-contact-button,
.portal-button,
.portal-mini-button {
  display: inline-flex !important;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: fit-content;
  min-width: 0;
  max-width: 100%;
  min-height: 48px !important;
  margin-inline: 0;
  padding: 10px 16px;
  border: 0 !important;
  border-radius: 0 !important;
  clip-path: none !important;
  background: var(--interactive-fill) !important;
  box-shadow: none;
  color: var(--interactive-text) !important;
  font: 400 14px/1.4 "Suisse Int'l Mono", monospace;
  font-weight: 400;
  letter-spacing: 0.02em;
  line-height: 1.4;
  text-align: center;
  text-decoration: none;
  text-transform: uppercase;
  cursor: pointer;
  touch-action: manipulation;
  transition: background-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease;
}
.button-frame svg.plaisio { display: none !important; }
.button-frame .label-wrap {
  position: static;
  width: auto;
  transform: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
}
.button-frame .label-wrap span { font: inherit; letter-spacing: inherit; white-space: normal; }
.button-frame .label-wrap svg.icon-arrow { width: 14px; height: 14px; flex: 0 0 14px; }
.button-frame .label-wrap svg.icon-arrow { margin-left: 0; }
.button-frame .label-wrap span,
.button-frame .label-wrap svg.icon-arrow path,
.pr-plan__cta svg path,
.footer-contact-button svg path {
  color: currentColor !important;
  fill: currentColor !important;
}
.pr-blob .pr-plan__cta,
.sx-plan .sx-btn { width: 100%; align-self: stretch; }
.portal-mini-button { min-width: 0; min-height: 44px !important; padding: 10px 16px; }

.button-frame:hover,
.button-frame:focus-visible,
.sx-btn:hover,
.sx-btn:focus-visible,
.pr-plan__cta:hover,
.pr-plan__cta:focus-visible,
.footer-contact-button:hover,
.footer-contact-button:focus-visible,
.portal-button:hover,
.portal-button:focus-visible,
.portal-mini-button:hover,
.portal-mini-button:focus-visible {
  background: var(--interactive-fill-hover) !important;
  color: var(--interactive-text-hover) !important;
  opacity: 1;
  box-shadow: none;
  transform: none;
}
.button-frame:focus-visible,
.sx-btn:focus-visible,
.pr-plan__cta:focus-visible,
.footer-contact-button:focus-visible,
.portal-button:focus-visible,
.portal-mini-button:focus-visible {
  outline: 3px solid var(--deep);
  outline-offset: 4px;
  box-shadow: 0 0 0 4px var(--white);
}

.button-frame:hover .label-wrap span,
.button-frame:focus-visible .label-wrap span,
.button-frame:hover .label-wrap svg.icon-arrow path,
.button-frame:focus-visible .label-wrap svg.icon-arrow path {
  color: currentColor !important;
  fill: currentColor !important;
}

/* Text links are distinguished by colour, weight, arrows and focus rings,
   never by a decorative underline. Structural section rules are unaffected. */
a,
.link-arrow,
.pr-plan__cta,
.pr-more__link,
.hc__link,
.legal-doc a,
.review-aside__mail,
.cs__link { text-decoration: none; }
.link-arrow span,
.underlined-button--color-2,
.system-stage__index a:first-child { background-image: none; text-decoration: none; }

.link-arrow,
.pr-more__link,
.hc__link,
.legal-doc a,
.review-aside__mail,
.cs__link {
  color: var(--text);
  transition: color 0.25s ease, gap 0.25s ease;
}
.link-arrow:hover,
.pr-more__link:hover,
.hc__link:hover,
.legal-doc a:hover,
.review-aside__mail:hover,
.cs__link:hover {
  color: var(--white);
  opacity: 1;
}
/* Links on the light work cards use ink instead of white. */
.cs__card .link-arrow,
.cs__card .cs__link {
  color: var(--deep);
}
/* The legacy "deep" modifiers now render the shared white surface. */
.hx--deep .link-arrow,
.sx--deep .link-arrow,
.hx--deep .hc__link,
.sx--deep .hc__link,
.hx--deep .legal-doc a,
.sx--deep .legal-doc a {
  color: var(--deep);
}
.hx--deep .link-arrow:hover,
.sx--deep .link-arrow:hover,
.hx--deep .hc__link:hover,
.sx--deep .hc__link:hover,
.hx--deep .legal-doc a:hover,
.sx--deep .legal-doc a:hover {
  color: var(--deep);
}

@media (max-width: 767px) {
  .button-frame,
  .sx-btn,
  .pr-plan__cta,
  .footer-contact-button,
  .portal-button { min-width: 0; min-height: 48px !important; padding-inline: 16px; }
}

:is(.button-frame, .sx-btn, .pr-plan__cta, .footer-contact-button, .portal-button, .portal-mini-button):active {
  box-shadow: inset 0 0 0 2px currentColor;
}
:is(.button-frame, .sx-btn, .pr-plan__cta, .footer-contact-button, .portal-button, .portal-mini-button):disabled {
  opacity: 0.55;
  cursor: not-allowed;
  transform: none;
  background: var(--interactive-fill) !important;
  color: var(--interactive-text) !important;
}

@media (prefers-reduced-motion: reduce) {
  .button-frame,
  .sx-btn,
  .pr-plan__cta,
  .footer-contact-button,
  .portal-button,
  .portal-mini-button { transition: none; }
  .button-frame:hover,
  .sx-btn:hover,
  .pr-plan__cta:hover,
  .footer-contact-button:hover,
  .portal-button:hover,
  .portal-mini-button:hover { transform: none; }
}

/* Labels and a single flat arrow are the complete button content. */
.button-frame .label-wrap svg.icon-arrow { display: none; }
.button-frame .label-wrap::after {
  content: ''; width: 14px; height: 14px; flex: 0 0 14px;
  background: currentColor;
  clip-path: polygon(0 46%, 80% 46%, 44% 10%, 50% 4%, 96% 50%, 50% 96%, 44% 90%, 80% 54%, 0 54%);
}
footer .top-wrap { justify-content: flex-start; gap: 12px; }
footer .top-wrap .button-frame { flex: 0 0 auto; width: fit-content; }

/* ── Markers take the colour of their text ───────────────────────────────
   The small square or dot in front of an eyebrow, a step label or a work
   tile tag is drawn in the same colour as the words beside it. The bullet
   lists set --marker-color from their label in js/main.js, because there the
   colour sits on the inner span, not on the element that draws the dot. */
.sx__eyebrow::before,
.sx--light .sx__eyebrow::before,
.sx--deep .sx__eyebrow::before { background: currentColor; }
.bullet-green::before,
.hero .bullet-green::before { background: var(--marker-color, currentColor); }
.wk__kind::before,
.wk__item[data-kind='live'] .wk__kind::before { background: currentColor; }
