/* ==========================================================================
   Feature Joinery — Global Styles
   Shared across every page on the site.
   ========================================================================== */

/* Base ---------------------------------------------------------------------- */
html {
  scroll-behavior: smooth;
}

body {
  background-color: #131313;
  color: #ffffff;
  font-family: 'Plus Jakarta Sans', system-ui, -apple-system, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Parallax background — used on the Quality section of the Values page.
   The container clips the oversized image; the JS handler at the bottom
   of OUR_VALUES PAGE/index.html applies a translateY transform per frame
   based on the section's position relative to the viewport. The image is
   sized 130% of the container so it has ±15% room to slide without
   exposing top/bottom edges, regardless of scroll direction. */
.parallax-bg {
  overflow: hidden;
}
.parallax-bg-image {
  position: absolute;
  top: -15%;
  left: 0;
  width: 100%;
  height: 130%;
  object-fit: cover;
  will-change: transform;
  /* Promote to its own compositor layer so the transform is GPU-accelerated
     and doesn't trigger paint/layout on every scroll frame. */
  transform: translate3d(0, 0, 0);
  backface-visibility: hidden;
}

/* Respect reduced-motion: lock the image in place at its natural position. */
@media (prefers-reduced-motion: reduce) {
  .parallax-bg-image {
    transform: none !important;
    top: 0;
    height: 100%;
  }
}

/* Skip-to-content link ----------------------------------------------------
   Accessibility: keyboard users can tab into this link as the first
   focusable element on every page, then press Enter to jump straight to
   #main and bypass the repeated nav. Visually hidden until focused. */
.skip-link {
  position: absolute;
  top: 0;
  left: 0;
  transform: translateY(-100%);
  padding: 0.75rem 1.25rem;
  background: #1CAEE5;
  color: #ffffff;
  font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
  font-weight: 700;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  text-decoration: none;
  z-index: 9999;
  transition: transform 0.2s ease-out;
}
.skip-link:focus {
  transform: translateY(0);
  outline: 2px solid #ffffff;
  outline-offset: 2px;
}

/* Nav — Flip "Enquire" button ---------------------------------------------- */
.flip-button-container {
  perspective: 1000px;
  width: 138px;
  height: 34px;
}

.flip-button-inner {
  position: relative;
  width: 100%;
  height: 100%;
  text-align: center;
  transition: transform 0.6s;
  transform-style: preserve-3d;
}

.flip-button-container:hover .flip-button-inner {
  transform: rotateX(180deg);
}

/* Contact page override — invert the nav Enquire button so the phone number
   is the DEFAULT state and the "Enquire →" face appears on hover. Makes
   sense contextually: if you're already on the contact page, the phone
   number is the more useful affordance. Gated by body.page-contact which
   is only set on CONTACT PAGE/index.html. */
body.page-contact .flip-button-container .flip-button-inner {
  transform: rotateX(180deg);
}
body.page-contact .flip-button-container:hover .flip-button-inner {
  transform: rotateX(0deg);
}

.flip-button-front,
.flip-button-back {
  position: absolute;
  width: 100%;
  height: 100%;
  backface-visibility: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 0.25rem;
}

.flip-button-back {
  transform: rotateX(180deg);
}

/* CTA variant of the flip button — used on the Values page CTA section.
   Larger than the nav variant, same flip behavior. Reuses .flip-button-inner,
   .flip-button-front, and .flip-button-back styles defined above. */
.cta-flip-button {
  perspective: 1000px;
  display: inline-block;
  width: 150px;
  height: 34px;
}

.cta-flip-button:hover .flip-button-inner {
  transform: rotateX(180deg);
}

/* Larger CTA flip button — sized to match the home page hero buttons (40px
   tall, padded by inner contents). Used on the Values page CTA so the
   "Get in Touch" button visually balances with the "View Portfolio" button. */
.cta-flip-button-lg {
  perspective: 1000px;
  display: inline-block;
  width: 128px;
  height: 36px;
}

.cta-flip-button-lg:hover .flip-button-inner {
  transform: rotateX(180deg);
}

/* Mount lockout: the nav is injected at runtime by js/includes.js, which
   briefly tags the <nav> with .nav-mounting on first render. During that
   window we disable the flip-button transition and ignore pointer events
   so the browser can't fire a hover animation against a stale pointer
   position — which otherwise causes a visible "jerk" on page navigation. */
nav.nav-mounting .flip-button-inner {
  transition: none !important;
}
nav.nav-mounting .flip-button-container {
  pointer-events: none;
}

/* Nav — link hover + active state ----------------------------------------- */
.nav-link {
  position: relative;
  display: inline-block;
}

.nav-link::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: -4px;
  width: 100%;
  height: 1px;
  background-color: currentColor;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.3s ease;
}

.nav-link:hover::after,
.nav-link-active::after {
  transform: scaleX(1);
}

.nav-link-active {
  color: #1CAEE5 !important;
}

.nav-link-active:hover {
  color: #1CAEE5 !important;
}

/* Selected Works — feature + preview rotating gallery -------------------- */
.gallery-stage {
  position: relative;
  width: 100%;
  aspect-ratio: 2.6 / 1;
  min-height: 300px;
}

.gallery-card {
  position: absolute;
  overflow: hidden;
  transition:
    left 0.9s cubic-bezier(0.65, 0, 0.35, 1),
    top 0.9s cubic-bezier(0.65, 0, 0.35, 1),
    width 0.9s cubic-bezier(0.65, 0, 0.35, 1),
    height 0.9s cubic-bezier(0.65, 0, 0.35, 1),
    opacity 0.7s ease;
}

/* Stashed — off-screen right, waiting its turn. No transition so cards can
   be teleported here from `exiting` without a visible slide across the page. */
.gallery-card.stashed {
  left: 110%;
  top: 10%;
  width: 32%;
  height: 80%;
  opacity: 0;
  transition: none;
}

/* Preview — small, right */
.gallery-card.preview {
  left: 66%;
  top: 10%;
  width: 32%;
  height: 80%;
  opacity: 1;
  z-index: 1;
}

/* Featured — large, left */
.gallery-card.featured {
  left: 0;
  top: 0;
  width: 62%;
  height: 100%;
  opacity: 1;
  z-index: 2;
}

/* Exiting — sliding out to the left */
.gallery-card.exiting {
  left: -110%;
  top: 0;
  width: 62%;
  height: 100%;
  opacity: 0;
  z-index: 0;
}

/* Stashed-left — off-screen left, waiting to slide in as featured during
   REVERSE navigation. Mirrors `.stashed` (no transition) so a card can be
   teleported here before its forward slide into the featured slot. */
.gallery-card.stashed-left {
  left: -110%;
  top: 0;
  width: 62%;
  height: 100%;
  opacity: 0;
  transition: none;
}

/* Exiting-right — preview sliding BACK to the right (reverse direction).
   Same end position as `.stashed` but WITH transition so the slide animates. */
.gallery-card.exiting-right {
  left: 110%;
  top: 10%;
  width: 32%;
  height: 80%;
  opacity: 0;
  z-index: 0;
}

/* Cursor + drag affordance — signals the carousel is interactive. */
.gallery-stage {
  cursor: grab;
  touch-action: pan-y;
}
.gallery-stage:active {
  cursor: grabbing;
}

/* Text inside cards scales with state — smooth font-size transitions so the
   overlay looks right at both the large featured size and the small preview. */
.gallery-card .card-text {
  padding: 1.25rem;
  transition: padding 0.9s cubic-bezier(0.65, 0, 0.35, 1);
}

.gallery-card .card-classification {
  font-size: 0.625rem;
  letter-spacing: 0.2em;
  transition: font-size 0.9s cubic-bezier(0.65, 0, 0.35, 1);
}

.gallery-card .card-title {
  font-size: 1.05rem;
  line-height: 1.1;
  transition: font-size 0.9s cubic-bezier(0.65, 0, 0.35, 1);
}

.gallery-card.featured .card-text {
  padding: 2.25rem;
}

.gallery-card.featured .card-classification {
  font-size: 0.75rem;
}

.gallery-card.featured .card-title {
  font-size: 2rem;
}

/* Mobile — collapse to a single full-width card slideshow. Preview/stashed
   cards live off-screen right; exiting cards slide off-screen left. */
@media (max-width: 767px) {
  .gallery-stage {
    aspect-ratio: 4 / 5;
    min-height: 360px;
    max-height: 520px;
  }

  .gallery-card.featured,
  .gallery-card.preview,
  .gallery-card.exiting,
  .gallery-card.exiting-right,
  .gallery-card.stashed,
  .gallery-card.stashed-left {
    top: 0;
    width: 100%;
    height: 100%;
  }

  .gallery-card.featured {
    left: 0;
    opacity: 1;
  }

  .gallery-card.preview {
    left: 105%;
    opacity: 0;
  }

  .gallery-card.exiting {
    left: -105%;
    opacity: 0;
  }

  .gallery-card.stashed {
    left: 105%;
    opacity: 0;
  }

  .gallery-card.stashed-left {
    left: -105%;
    opacity: 0;
  }

  .gallery-card.exiting-right {
    left: 105%;
    opacity: 0;
  }

  /* Use the larger featured text sizing at all times on mobile, since
     it's the only visible card. */
  .gallery-card .card-text {
    padding: 1.5rem;
  }
  .gallery-card .card-classification,
  .gallery-card.featured .card-classification {
    font-size: 0.75rem;
  }
  .gallery-card .card-title,
  .gallery-card.featured .card-title {
    font-size: 1.5rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  .gallery-card,
  .gallery-card .card-text,
  .gallery-card .card-title,
  .gallery-card .card-classification {
    transition: none;
  }
}

/* Individual project page — gallery + lightbox -------------------------- */

/* Uniform CSS Grid layout. Each thumb is a 4:3 tile with `object-fit: cover`
   so the grid is clean regardless of how many images a project has (3, 9,
   11, whatever). Clicking any thumb opens the FULL natural-aspect-ratio
   image in the lightbox — the crop only affects the thumbnail preview. */
.project-gallery {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1rem;
}

@media (min-width: 768px) {
  .project-gallery {
    grid-template-columns: repeat(3, 1fr);
    gap: 1.5rem;
  }
}

/* Each thumbnail in the project gallery. Acts as a button that opens the
   lightbox to the corresponding image. */
.gallery-thumb {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;     /* uniform tiles, clean grid */
  overflow: hidden;
  cursor: pointer;
  background: #1a1a1a;
  border: none;
  padding: 0;
  border-radius: 0;
}

.gallery-thumb img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;       /* crop-to-fit the 4:3 tile */
  object-position: center;
  transition: transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
}

.gallery-thumb::after {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(28, 174, 229, 0);
  transition: background 0.3s ease;
  pointer-events: none;
}

.gallery-thumb:hover img {
  transform: scale(1.06);
}

.gallery-thumb:hover::after {
  background: rgba(28, 174, 229, 0.12);
}

.gallery-thumb:focus-visible {
  outline: 2px solid #1CAEE5;
  outline-offset: 2px;
}

/* Lightbox modal — fixed full-viewport overlay that displays a single
   gallery image at full size with prev/next/close controls. */
.lightbox {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.96);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 9999;
  padding: 4rem 1rem;
}

.lightbox.is-open {
  display: flex;
}

.lightbox-image {
  max-width: 95vw;
  max-height: 85vh;
  object-fit: contain;
  display: block;
}

.lightbox-close,
.lightbox-prev,
.lightbox-next {
  position: absolute;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.18);
  color: white;
  width: 48px;
  height: 48px;
  border-radius: 9999px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
  backdrop-filter: blur(8px);
}

.lightbox-close { top: 1.5rem; right: 1.5rem; }
.lightbox-prev  { left: 1.5rem; top: 50%; transform: translateY(-50%); }
.lightbox-next  { right: 1.5rem; top: 50%; transform: translateY(-50%); }

.lightbox-close:hover,
.lightbox-prev:hover,
.lightbox-next:hover {
  background: #1CAEE5;
  border-color: #1CAEE5;
}

.lightbox-prev:hover { transform: translateY(-50%) scale(1.06); }
.lightbox-next:hover { transform: translateY(-50%) scale(1.06); }

.lightbox-counter {
  position: absolute;
  bottom: 1.5rem;
  left: 50%;
  transform: translateX(-50%);
  color: rgba(255, 255, 255, 0.7);
  font-size: 0.7rem;
  letter-spacing: 0.25em;
  text-transform: uppercase;
  font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
  font-weight: 600;
}

@media (max-width: 640px) {
  .lightbox-prev,
  .lightbox-next {
    width: 42px;
    height: 42px;
  }
  .lightbox-close { top: 1rem; right: 1rem; }
  .lightbox-prev  { left: 0.75rem; }
  .lightbox-next  { right: 0.75rem; }
}

/* Scroll reveal ------------------------------------------------------------
   Elements with .reveal fade/slide up into view as they scroll onto the
   screen. An IntersectionObserver in js/includes.js toggles .is-visible. */
.reveal {
  opacity: 0;
  transform: translateY(28px);
  transition:
    opacity 0.9s cubic-bezier(0.16, 1, 0.3, 1),
    transform 0.9s cubic-bezier(0.16, 1, 0.3, 1);
  will-change: opacity, transform;
}

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

/* Optional stagger — child-delayed reveal. Apply .reveal-stagger to a parent
   and its first 5 .reveal children will fan in at 120ms offsets. */
.reveal-stagger > .reveal:nth-child(1) { transition-delay: 0ms; }
.reveal-stagger > .reveal:nth-child(2) { transition-delay: 120ms; }
.reveal-stagger > .reveal:nth-child(3) { transition-delay: 240ms; }
.reveal-stagger > .reveal:nth-child(4) { transition-delay: 360ms; }
.reveal-stagger > .reveal:nth-child(5) { transition-delay: 480ms; }

@media (prefers-reduced-motion: reduce) {
  .reveal {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* Mobile nav — hamburger panel --------------------------------------------
   The hamburger button (.mobile-menu-toggle) lives in nav_bar.html and is
   shown only below md. Clicking it toggles .is-open on the .mobile-menu
   panel, which slides down from the top as a full-screen overlay. */
.mobile-menu {
  position: fixed;
  inset: 0;
  background: rgba(19, 19, 19, 0.97);
  -webkit-backdrop-filter: blur(20px);
  backdrop-filter: blur(20px);
  opacity: 0;
  pointer-events: none;
  transform: translateY(-100%);
  transition:
    opacity 0.35s ease,
    transform 0.45s cubic-bezier(0.65, 0, 0.35, 1);
  z-index: 40;
  padding: 110px 2rem 3rem;
  overflow-y: auto;
}

.mobile-menu.is-open {
  opacity: 1;
  pointer-events: auto;
  transform: translateY(0);
}

.mobile-menu a {
  display: block;
  padding: 0.75rem 0;
  color: rgba(255, 255, 255, 0.8);
  font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
  font-size: 1.875rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  transition: color 0.2s ease, padding-left 0.3s ease;
}

.mobile-menu a:hover,
.mobile-menu a.nav-link-active {
  color: #1CAEE5;
  padding-left: 0.5rem;
}

/* The hamburger's color scheme and the toggle button are plain Tailwind
   classes in nav_bar.html. We only need this extra rule so .nav-link::after
   underline doesn't show on the mobile menu links (they have borders). */
.mobile-menu .nav-link::after {
  display: none;
}

body.menu-open {
  overflow: hidden;
}

@media (min-width: 768px) {
  .mobile-menu,
  .mobile-menu-toggle {
    display: none !important;
  }
}

/* Values page CTA — ambient glow pulse + staggered entrance -----------------
   The .reveal parent handles the scroll-in fade; these rules add a slow
   breathing glow behind the text and a staggered rise on the heading/buttons
   once the section becomes visible. */

@keyframes cta-glow-pulse {
  0%, 100% {
    opacity: 0.7;
    transform: translate(-50%, 0) scale(1);
  }
  50% {
    opacity: 1;
    transform: translate(-50%, -2%) scale(1.05);
  }
}

@keyframes cta-glow-drift {
  0%, 100% {
    opacity: 0.6;
    transform: translate(0, 0) scale(1);
  }
  50% {
    opacity: 0.9;
    transform: translate(-3%, -2%) scale(1.08);
  }
}

.cta-glow {
  animation: cta-glow-pulse 9s ease-in-out infinite;
}

.cta-glow-2 {
  animation: cta-glow-drift 11s ease-in-out infinite;
}

/* Staggered content rise — starts when the parent .reveal becomes visible.
   Each child has its own transition-delay so they fan in one after another. */
.reveal .cta-eyebrow,
.reveal .cta-heading,
.reveal .cta-buttons {
  opacity: 0;
  transform: translateY(16px);
  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-visible .cta-eyebrow {
  opacity: 1;
  transform: translateY(0);
  transition-delay: 0.15s;
}

.reveal.is-visible .cta-heading {
  opacity: 1;
  transform: translateY(0);
  transition-delay: 0.30s;
}

.reveal.is-visible .cta-buttons {
  opacity: 1;
  transform: translateY(0);
  transition-delay: 0.50s;
}

/* Continuous float on the CTA heading — kicks in after the entrance finishes.
   A very subtle up-down bob, paired with a gradient shimmer on the accent
   word "project". The float draws the eye; the shimmer holds attention. */
@keyframes cta-heading-float {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-6px); }
}

.reveal.is-visible .cta-heading .cta-heading-text {
  display: inline-block;
  animation: cta-heading-float 2.8s ease-in-out infinite;
  animation-delay: 1.5s;      /* wait for the entrance to complete */
  will-change: transform;
}

/* Shimmer on the accent word "project" — a moving gradient that sweeps
   left-to-right across the text, highlighting the word every few seconds. */
/* "project" accent shimmer — a soft white highlight continuously
   glides across the brand-blue word in a single direction, creating
   a subtle shine that draws the eye without being distracting. */
@keyframes cta-heading-shimmer {
  0%   { background-position: -200% center; }
  50%  { background-position:  200% center; }
  100% { background-position: -200% center; }
}

.reveal.is-visible .cta-heading .cta-heading-accent {
  display: inline-block;
  padding-right: 0.08em;
  padding-bottom: 0.15em;
  margin-bottom: -0.15em;
  background: linear-gradient(
    100deg,
    #1CAEE5 0%,
    #1CAEE5 38%,
    #ffffff 50%,
    #1CAEE5 62%,
    #1CAEE5 100%
  );
  background-size: 200% auto;
  background-position: -200% center;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  animation: cta-heading-shimmer 5s ease-in-out infinite;
  animation-delay: 2.2s;
  padding-bottom: 0.15em;
  margin-bottom: -0.15em;
}

@media (prefers-reduced-motion: reduce) {
  .cta-glow,
  .cta-glow-2,
  .reveal.is-visible .cta-heading .cta-heading-text,
  .reveal.is-visible .cta-heading .cta-heading-accent {
    animation: none;
  }
  .reveal .cta-eyebrow,
  .reveal .cta-heading,
  .reveal .cta-buttons {
    opacity: 1;
    transform: none;
    transition: none;
  }
  .reveal.is-visible .cta-heading .cta-heading-accent {
    background: none;
    color: #1CAEE5;
    -webkit-text-fill-color: #1CAEE5;
    animation: none;
  }
}

/* Professionalism value — stat numbers turn white when the user hovers the
   RIGHT-hand image column. Uses CSS :has() to detect hover on the image
   trigger and apply the color change to the stat numbers in the opposite
   column. Scoped to .prof-grid so it only applies within the Professionalism
   section. Supported in Chrome 105+, Safari 15.4+, Firefox 121+. */
.prof-grid:has(.prof-image-hover-trigger:hover) .prof-stat-number {
  color: #ffffff;
}

/* Same hover trigger fades the giant decorative "PRO" watermark from its
   default light-blue tint to a soft white, in sync with the stat-number
   color change above. */
.prof-grid:has(.prof-image-hover-trigger:hover) .prof-pro-watermark {
  color: rgba(255, 255, 255, 0.03);
}

/* Honesty & Integrity value — Clear Communication + No Bullshit check icons
   turn white when the user hovers the LEFT-hand image column. Same :has()
   pattern as the Professionalism rule above. */
/* Same hover trigger fades the giant decorative "01" numeral from its
   default surface tint to a soft white, in sync with the check-icon
   color change below. */
.honesty-grid:has(.honesty-image-hover-trigger:hover) .honesty-numeral-watermark {
  color: rgba(255, 255, 255, 0.03);
}

.honesty-grid:has(.honesty-image-hover-trigger:hover) .honesty-check-icon {
  color: #ffffff;
}

/* Home page — "uncompromised" word smoothly slants into a faux-italic when
   the user hovers anywhere over the Expertise grid (text or LHS images).
   `font-style: italic` is a discrete CSS property that browsers cannot
   interpolate, so we use `transform: skewX()` instead — visually identical
   slant, fully animatable. inline-block is required for transforms to apply
   to inline text. transform-origin keeps the word anchored to its baseline
   so the surrounding sentence doesn't shift. */
.uncompromised-italic {
  display: inline-block;
  transform: skewX(0deg);
  transform-origin: 0 100%;
  font-weight: 400;
  font-variation-settings: "wght" 400;
  transition: transform 600ms cubic-bezier(0.22, 0.61, 0.36, 1),
              font-variation-settings 600ms cubic-bezier(0.22, 0.61, 0.36, 1),
              font-weight 600ms cubic-bezier(0.22, 0.61, 0.36, 1);
  will-change: transform, font-variation-settings;
}

.group\/expertise:hover .uncompromised-italic {
  transform: skewX(-12deg);
}

/* Bold-on-bold: when the user specifically hovers the Discover Services
   button, "uncompromised" thickens into bold (in addition to its slant
   from the broader expertise hover). Scoped via :has() so the bold
   effect doesn't fire from the rest of the grid. */
.group\/expertise:has(.discover-services-trigger:hover) .uncompromised-italic {
  font-weight: 700;
  font-variation-settings: "wght" 700;
}

/* Projects page — "Project Gallery." heading colour fill.
   "Gallery." is always brand-blue (hardcoded via text-[#1CAEE5]).
   "Project" uses a gradient with background-clip: text so the blue
   fills right-to-left on hover over "Gallery." — the colour boundary
   sweeps across the word rather than fading uniformly. */
.gallery-heading-text {
  display: inline-block;
  background: linear-gradient(
    to left,
    #1CAEE5 0%,
    #1CAEE5 50%,
    #ffffff 50%,
    #ffffff 100%
  );
  background-size: 200% 100%;
  background-position: 0% center;
  background-clip: text;
  -webkit-background-clip: text;
  color: transparent !important;
  -webkit-text-fill-color: transparent;
  transition: background-position 0.8s cubic-bezier(0.16, 1, 0.3, 1);
  will-change: background-position;
  /* Prevent the descender on "j" from clipping against the inline-block edge */
  padding-bottom: 0.15em;
  margin-bottom: -0.15em;
  /* Restore the natural word gap between "Project" and "Gallery." —
     inline-block can swallow the trailing space inside the span */
  margin-right: 0.25em;
}

.gallery-heading-accent {
  display: inline-block;
  cursor: default;
}

/* Scale the entire h1 as a single unit — both words expand together
   from a shared origin, so they don't drift apart. */
.gallery-h1 {
  display: inline-block;
  transition: transform 1.5s cubic-bezier(0.16, 1, 0.3, 1);
  transform-origin: right center;
  will-change: transform;
  cursor: default;
}

.gallery-h1:hover {
  transform: scale(1.05);
}

/* Hovering "Gallery." also triggers the right-to-left blue fill on "Project" */
h1:has(.gallery-heading-accent:hover) .gallery-heading-text {
  background-position: 100% center;
}

@media (prefers-reduced-motion: reduce) {
  .gallery-heading-text {
    background: none;
    color: #ffffff !important;
    -webkit-text-fill-color: #ffffff;
    transition: none;
  }
}

/* Contact page hero — full heading gradient shimmer.
   Applied to the entire <h1> "Let's Build Something Exceptional." line.
   A soft brand-blue highlight sweeps across the white text every 6s,
   creating an attention-catching shine across the whole heading.
   Hover anywhere on the heading to pause the animation and lift the
   text slightly for a premium interaction. */

@keyframes exceptional-shimmer {
  0%   { background-position: -150% center; }
  100% { background-position:  150% center; }
}

.exceptional-accent {
  display: inline-block;
  color: #1CAEE5;
  -webkit-text-fill-color: #1CAEE5;
  /* Padding so the descender on "g"/"p" doesn't clip against the inline-block edge */
  padding-bottom: 0.12em;
  margin-bottom: -0.12em;
  transition: transform 1.5s cubic-bezier(0.16, 1, 0.3, 1);
  transform-origin: left bottom;
  will-change: transform;
  cursor: default;
}

/* Shimmer only activates when hovering the phone/email tiles —
   at rest the word is solid brand blue with no animation. The
   gradient + animation are applied via the :has() selector so
   the shimmer starts on tile hover and stops when leaving. */
.contact-hero:has(.contact-tile:hover) .exceptional-accent {
  background: linear-gradient(
    100deg,
    #1CAEE5 0%,
    #1CAEE5 35%,
    #ffffff 50%,
    #1CAEE5 65%,
    #1CAEE5 100%
  );
  background-size: 250% auto;
  background-clip: text;
  -webkit-background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  animation: exceptional-shimmer 4s linear infinite;
  transform: scale(1.05);
}

/* The italic <em> inside still gets the gradient via background-clip
   inheritance, but we override the brand-blue color so it falls back
   gracefully if background-clip fails. */
.exceptional-accent em {
  font-style: italic;
  color: inherit;
  -webkit-text-fill-color: inherit;
}

.exceptional-accent:hover {
  /* No action on direct hover — shimmer only from tile hover */
}

@media (prefers-reduced-motion: reduce) {
  .exceptional-accent {
    animation: none;
    background: none;
    color: #1CAEE5;
    -webkit-text-fill-color: #1CAEE5;
    transition: none;
  }
  .exceptional-accent em {
    color: #1CAEE5;
    -webkit-text-fill-color: #1CAEE5;
  }
  .exceptional-accent:hover {
    transform: none;
  }
}

/* Brand-blue accent — used on hero accent words ("Joinery." on the home
   page, "Values." on the values page). Solid brand blue with a subtle
   scale-up on hover for a tactile feel — no sweep/shimmer animation. */
.accent-shimmer {
  display: inline-block;
  color: #1CAEE5;
  /* Padding so descenders ("y", "p", "g") don't clip */
  padding-bottom: 0.12em;
  margin-bottom: -0.12em;
  transition: transform 1.5s cubic-bezier(0.16, 1, 0.3, 1);
  will-change: transform;
  cursor: default;
}

/* Both direct hover on the word and indirect hover on either CTA button
   (View Portfolio / Get in Touch) trigger the same gentle scale-up. */
.accent-shimmer:hover,
.hero-content:has(.joinery-trigger:hover) .accent-shimmer {
  transform: scale(1.05);
}

@media (prefers-reduced-motion: reduce) {
  .accent-shimmer {
    transition: none;
  }
  .accent-shimmer:hover,
  .hero-content:has(.joinery-trigger:hover) .accent-shimmer {
    transform: none;
  }
}

/* Opt-in gradient sweep — used on the Values page hero "Values." word.
   Paints the text via background-clip so a soft deep-blue highlight
   slice can travel across it on hover. At rest the slice sits off the
   right edge so the word reads as solid brand blue (#1CAEE5), matching
   the plain .accent-shimmer base. The transform transition is repeated
   here because we need a single `transition` declaration that covers
   both transform AND background-position — declaring `transition`
   twice would have the second one win and lose the scale animation. */
.accent-shimmer-sweep {
  background: linear-gradient(
    100deg,
    #1CAEE5 0%,
    #1CAEE5 38%,
    #0078c4 50%,
    #005492 58%,
    #1CAEE5 70%,
    #1CAEE5 100%
  );
  background-size: 250% auto;
  background-position: 150% center;
  background-clip: text;
  -webkit-background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  transition: transform 0.5s cubic-bezier(0.16, 1, 0.3, 1),
              background-position 2.5s cubic-bezier(0.16, 1, 0.3, 1);
  will-change: transform, background-position;
}

.accent-shimmer-sweep:hover {
  background-position: -150% center;
}

@media (prefers-reduced-motion: reduce) {
  .accent-shimmer-sweep {
    background: none;
    color: #1CAEE5;
    -webkit-text-fill-color: #1CAEE5;
    transition: none;
  }
  .accent-shimmer-sweep:hover {
    background: none;
  }
}

/* Values page hero — "Values." faux-italic that smoothly slants when
   the user hovers anywhere over the .group/values-hero wrapper.
   `font-style: italic` is a discrete CSS property and can't be
   interpolated, so we fake the slant with `transform: skewX()` instead
   — visually identical, fully animatable. inline-block is required so
   the transform actually applies (it's ignored on plain inline). The
   transform-origin keeps the word anchored to its baseline so the
   line above doesn't visibly shift when the word slants.
   This rule is repeated below the .accent-shimmer transition because
   `transition` is a shorthand and would otherwise wipe out the scale
   transition inherited from .accent-shimmer. */
.values-italic-smooth {
  display: inline-block;
  transform: skewX(0deg);
  transform-origin: 0 100%;
  /* Single transition shorthand covers both the skew (driven by group
     hover) and the background-position sweep (driven by .accent-shimmer-sweep
     direct hover). Combining them here is required because `transition` is
     a single property — declaring it again in a sibling class would
     override one of the two behaviors. */
  transition: transform 0.3s cubic-bezier(0.22, 0.61, 0.36, 1),
              background-position 2.5s cubic-bezier(0.16, 1, 0.3, 1);
  will-change: transform, background-position;
}

/* Italic skew is driven by a JS-toggled class (.is-italic-active) on the
   <em>, set when the mouse is between the top 25% and bottom 75% of the
   #values-hero section. JS lets us define a precise vertical hit zone
   without an absolute overlay that would block text selection on the
   headline and paragraph. */
.values-italic-smooth.is-italic-active {
  transform: skewX(-9deg);
}

/* Direct hover on the word: dwell-guard for the gradient sweep. The
   400ms delay on background-position is read from this :hover rule
   when going INTO hover, so the sweep only fires after the cursor has
   stayed on the word for that long. The transform delay stays at 0
   so the italic skew (driven by the JS class above) is unaffected.
   On mouse-out the default-state transition takes over (no delay) so
   the sweep glides back immediately. */
.values-italic-smooth.accent-shimmer-sweep:hover {
  transition: transform 0.3s cubic-bezier(0.22, 0.61, 0.36, 1),
              background-position 2.5s cubic-bezier(0.16, 1, 0.3, 1) 400ms;
}

@media (prefers-reduced-motion: reduce) {
  .values-italic-smooth {
    transition: none;
  }
  .group\/values-hero:hover .values-italic-smooth {
    transform: none;
  }
}

/* Hero "Scroll to Explore" chevrons — both the home page and the values
   page use Tailwind's stock `animate-bounce`, but the default 1s cycle
   feels sluggish for an attention affordance. Override the duration
   globally so both pages get the same faster bounce.
   `animate-bounce` is only used for these scroll-to-explore chevrons
   site-wide, so this override has no other knock-on effects. */
.animate-bounce {
  animation-duration: 0.7s;
}

/* Call modal -------------------------------------------------------------
   Triggered on the contact page when the user clicks the phone number or
   the "Call Us" button. Frosted-glass backdrop + centered dialog with the
   phone number, business hours, and a Call Now CTA that fires the tel:
   protocol. Open/close behavior lives in the inline <script> on the
   contact page. */
.call-modal {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease;
}

.call-modal.is-open {
  opacity: 1;
  pointer-events: auto;
}

.call-modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(19, 19, 19, 0.7);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
}

.call-modal-dialog {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 26rem;
  padding: 2.5rem 2rem 2rem;
  background: #1f2020;
  border: 1px solid rgba(28, 174, 229, 0.25);
  border-radius: 8px;
  box-shadow:
    0 20px 60px -15px rgba(0, 0, 0, 0.7),
    0 0 0 1px rgba(255, 255, 255, 0.04);
  text-align: center;
  transform: translateY(20px) scale(0.95);
  transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}

.call-modal.is-open .call-modal-dialog {
  transform: translateY(0) scale(1);
}

.call-modal-close {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
  width: 36px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  color: rgba(255, 255, 255, 0.5);
  cursor: pointer;
  border-radius: 4px;
  transition: color 0.2s ease, background-color 0.2s ease;
}

.call-modal-close:hover {
  color: #ffffff;
  background: rgba(255, 255, 255, 0.05);
}

.call-modal-close .material-symbols-outlined {
  font-size: 20px;
}

.call-modal-icon {
  display: inline-block;
  font-size: 44px !important;
  color: #1CAEE5;
  margin-bottom: 1rem;
}

.call-modal-eyebrow {
  font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  color: #1CAEE5;
  margin-bottom: 0.5rem;
}

.call-modal-number {
  font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
  font-size: 2.25rem;
  font-weight: 800;
  color: #ffffff;
  letter-spacing: -0.02em;
  margin-bottom: 1.25rem;
}

/* Email address — smaller than the phone number so the longer string fits
   the dialog width without wrapping mid-address. */
.call-modal-email {
  font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
  font-size: 1.25rem;
  font-weight: 700;
  color: #ffffff;
  letter-spacing: -0.01em;
  margin-bottom: 1.25rem;
  word-break: break-all;
}

.call-modal-divider {
  width: 48px;
  height: 1px;
  background: rgba(28, 174, 229, 0.4);
  margin: 0 auto 1.25rem;
}

.call-modal-hours {
  margin-bottom: 1.75rem;
}

.call-modal-hours-label {
  font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  color: rgba(189, 200, 208, 0.7);
  margin-bottom: 0.5rem;
}

.call-modal-hours-value {
  color: rgba(255, 255, 255, 0.85);
  font-size: 0.9rem;
  line-height: 1.5;
}

.call-modal-cta {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.875rem 1.5rem;
  background: #1CAEE5;
  color: #ffffff;
  font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.15em;
  text-decoration: none;
  border-radius: 4px;
  transition: background-color 0.3s ease, color 0.3s ease, transform 0.3s ease;
}

.call-modal-cta:hover,
.call-modal-cta:focus-visible {
  background: #ffffff;
  color: #1CAEE5;
  transform: scale(1.02);
}

.call-modal-cta .material-symbols-outlined {
  font-size: 18px !important;
}

.call-modal-cancel {
  display: block;
  width: 100%;
  margin-top: 0.75rem;
  padding: 0.625rem;
  background: transparent;
  border: none;
  color: rgba(255, 255, 255, 0.5);
  font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.15em;
  cursor: pointer;
  transition: color 0.2s ease;
}

.call-modal-cancel:hover {
  color: #ffffff;
}

body.call-modal-open {
  overflow: hidden;
}
