:root {
    --accent: #3b8ef3;

    /* One corner-radius scale for the whole site. Phones get the smaller
       step: the same radius eats a far bigger share of a narrow card, so a
       value that reads as a soft corner on desktop reads as a blob there. */
    --r-lg: 14px;     /* wide panels, media blocks */
    --r-md: 12px;     /* cards */
    --r-sm: 10px;     /* small inline media, thumbnails */
    --r-accent: 36px; /* the one oversized corner on .card-visit */
}
@media (min-width: 768px) {
    :root {
        --r-lg: 20px;
        --r-md: 14px;
        --r-sm: 12px;
        --r-accent: 60px;
    }
}

body {
    font-family: "Inter", serif;
    font-size: 16px;
    font-weight: 400;
    color: #454545;
    line-height: 1.4;
    overflow-x: hidden;
}

h1, h2, h3, h4, h5, h6 {
    font-family: "Lora", serif;
}

/* -- Process Slider -- */
.process-slider-wrapper,
.who-we-are {
    h3 {font-family: "Inter", serif;}
}

/* -- Visit Us -- */
@keyframes float {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-30px); }
}
.animate-float {
  animation: float 5s ease-in-out infinite;
}
.delay-2000 {
  animation-delay: 2s;
}
.card-visit {
    border-radius: var(--r-lg) var(--r-accent) var(--r-lg) var(--r-lg);
}

/* -- FAQs/What We Do -- */
.heading-faq h2 {
    background: url('assets/shape-brush.svg') left bottom no-repeat;
}
.bg-overlay {
    background: linear-gradient(to right, rgba(0,0,0,0.8), rgba(0,0,0,0.6));
}
@keyframes bgZoom {
  0%   { transform: scale(1) translateY(0); }
  50%  { transform: scale(1.1) translateY(-10px); }
  100% { transform: scale(1) translateY(0); }
}

.bg-zoom {
  animation: bgZoom 12s ease-in-out infinite;
}

/* -- Blog -- */
.blog-slider {
    .slick-slide {
        opacity: 0.2;
        transform: scale(1);
        transition: all 0.4s ease;
    }
    .slick-center {
        opacity: 1 !important;
        transform: scale(1);
    }
    .slick-dots li {
        margin: 0;
        button:before {font-size: 10px;}
    }
    .slick-dots {
        bottom: -50px;
    }
    .slick-slide:has(+ .slick-center) {
        opacity: 1;
    }
    .slick-prev, .slick-next {
        z-index: 10;
    }
    .slick-prev:before,
    .slick-next:before {
        color: black;
        font-size: 20px;
    }
}
#blogPrev {margin-left: -75px;}
#blogNext {margin-right: -75px;}

/* -- Footer Section -- */
@keyframes gradientFlow {
    0%   { background-position: 0% center; }
    100% { background-position: 250% center; }
}

.contact-row:hover { color: #fff; transform: translateX(-4px); }
.contact-row:hover .contact-icon-wrap {
    background: rgba(59,142,243,0.15);
    border-color: var(--accent);
    box-shadow: 0 0 18px rgba(59,142,243,0.35);
}
.footer-divider {
    height: 1px;
    background: linear-gradient(90deg, transparent, rgba(255,255,255,0.5) 30%, rgba(59,142,243,0.25) 50%, rgba(255,255,255,0.5) 70%, transparent);
}
.gsap-fade { opacity: 0; }
.gsap-left { opacity: 0; transform: translateX(-40px); }
.gsap-up { opacity: 0; transform: translateY(30px); }

/* The starting positions above, and the opacity aos.css puts on [data-aos],
   are only safe while the library that animates them out of that state has
   actually loaded. GSAP and AOS both come from a CDN, so custom.js sets this
   class on <html> when either one is missing and the content is shown as-is
   rather than staying invisible forever. */
.no-aos [data-aos],
.no-gsap .gsap-fade,
.no-gsap .gsap-left,
.no-gsap .gsap-up {
    opacity: 1 !important;
    transform: none !important;
}

/* Marquee animation */
@keyframes marquee {
    0% {
        transform: translateX(0%);
    }
    100% {
        transform: translateX(-50%);
    }
}
.marquee {
    display: flex;
    width: max-content;
    animation: marquee 20s linear infinite;
}
.fade-mask {
    -webkit-mask-image: linear-gradient(to right, transparent, black 10%, black 90%, transparent);
    mask-image: linear-gradient(to right, transparent, black 10%, black 90%, transparent);
}
.marquee:hover {
  animation-play-state: paused;
}
.logos-section {
    padding: 2rem 0;
}
@media (min-width: 1024px) {
    .logos-section {
        padding: 3rem 0;
    }
}
.client-logo {
    width: auto;
    filter: grayscale(1);
    opacity: .7;
}
/* -- ===== Header ==== -- */
.glass {
    background: rgba(255, 255, 255, 0.08);
    backdrop-filter: blur(10px);
    border: 1px solid rgba(255, 255, 255, 0.15);
}
.toggle-header {
    max-width: 100%;
    padding: 0;
    transition: all .3s;
    box-shadow: 0 1px 3px rgba(0,0,0,0.10);
    top: 0;
    .glass {
        background: white;
        border-radius: 0;
        border-top: 0;
        border-left: 0;
        border-right: 0;
    }
    .logo-main, #menuBtn, .nav-links a, button, #mobileMenu a, a[aria-label="Chat on WhatsApp"] {color: #333;}
    #mobileMenu {border-top: 1px solid #ddd;}
    svg path {fill: #333;}
    a[aria-label="Chat on WhatsApp"] {border-color: #d1d5db;}
}
.heading-hero {
    line-height: 1;
    font-weight: bold;
}
@keyframes borderFlow {
  0% { background-position: 0% 50%; }
  100% { background-position: 200% 50%; }
}
.animate-borderFlow {
  animation: borderFlow 3s linear infinite;
}
.toggle-header {
    #mobileMenu {
        margin-top: 0;
        padding: 0;
        background: white;
        &.opacity-100 {padding: 24px;}
    }
}
.process-slider .slick-slide {
  height: auto;
}
.process-slider .slick-track {
  display: flex !important;
}
.process-slider {
    .slick-slide > div {
        height: 100%;
    }
    .slick-dots {
        bottom: -50px;
        li {
            margin: 0;
            button {
                &:before {font-size: 10px;}
            }
        }
    }
}

@keyframes scroll {
  0% {
    transform: translateY(0);
    opacity: 1;
  }
  70% {
    transform: translateY(20px);
    opacity: 0.4;
  }
  100% {
    transform: translateY(0);
    opacity: 0;
  }
}
.animate-scroll {
  animation: scroll 1.8s infinite;
}
.nav-links {
    a {transition: all .3s;}
    a:hover,
    a.active {
        color: #00d3f3;
    }
}

/* -- Cube Animation -- */
.scene {
    position: relative;
    width: 100%;
    max-width: 540px;
    aspect-ratio: 540 / 460;
    /* Size the labels off the cube's own rendered width, not the viewport -
       the cube's actual room to breathe depends on the grid column it sits
       in, which isn't 1:1 with viewport width. */
    container-type: inline-size;
}
canvas {
    display: block;
    width: 100% !important;
    height: 100% !important;
}
.corner-label {
    position: absolute;
    /* Scales smoothly with the cube's width: wide enough to hold a full
       label on one line when there's room, small enough to wrap to two
       clean lines (never overlapping a neighbor) once the cube is tight. */
    font-size: clamp(11px, 3cqi, 16px);
    font-weight: 500;
    max-width: clamp(70px, 28cqi, 160px);
    text-align: center;
    line-height: 1.25;
    padding: 2px clamp(4px, 1.5cqi, 8px);
    pointer-events: none;
}
@media (min-width: 768px) {
    .wrap-cube {
        > div:nth-child(1) {
            order: 2;
            text-align: right;
            h2 {padding-right: 0;}
        }
        > div:nth-child(2) {
            order: 1;
        }
    }
}

/* -- Project Cards -- */
/* On touch devices there's no hover, so the title/category caption
   (normally revealed via group-hover) must stay visible, or the project
   is unidentifiable on mobile/tablet. */
@media (hover: none) {
    .project-card .group-hover\:opacity-100 {
        opacity: 1;
    }
}

/* -- Mobile card sliders (Services / Projects) -- */
/* Swipeable on small screens instead of a long vertical stack; JS
   unslicks back to the normal grid/list from md/sm up. Only one slide
   is ever shown at a time here, so (unlike the process/blog sliders)
   slides don't need forced equal heights - adaptiveHeight already
   resizes the list to fit whichever slide is current, and forcing
   height:100% on a slide's children fights that when a slide has more
   than one direct child (e.g. services' text + image columns). */
/* Slick sets display:block on every slide, which stops the grid `gap-10`
   from applying - so once the mobile slider initialises, a service's image
   sits flush against the last line of its copy. Put that spacing back as a
   margin, only while slicked (the class is dropped again from md up, where
   the real two-column grid and its gap take over). */
#servicesSlider.slick-initialized .slick-slide > * + * {
    margin-top: 2.5rem;
}
.mobile-slider.slick-initialized {
    .slick-slide {height: auto;}
    .slick-dots {
        position: static;
        margin-top: 20px;
        li {margin: 0;}
        button:before {font-size: 10px;}
    }
}
/* Hero animated background (canvas) */
.hero-canvas {
    display: block;
    width: 100%;
    height: 100%;
    pointer-events: none;
    background-color: #05060d;
}
/* Painted behind the canvas so the hero is never a blank box
   before the first frame, or if canvas is unavailable. */
.hero-canvas-fallback {
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background:
        radial-gradient(60% 55% at 62% 50%, rgba(59,142,243,0.22), transparent 70%),
        radial-gradient(45% 45% at 80% 25%, rgba(244,114,182,0.14), transparent 70%),
        #05060d;
}
.hero, .page-hero {
    background-color: #05060d;
}

/* -- Site Loader -- */
/* Plain CSS (not a Tailwind utility class) on purpose: this stylesheet is
   a blocking <link>, so the overlay is correctly positioned and opaque on
   the very first paint, before the Tailwind CDN script has had a chance
   to scan the DOM and inject its own rules. */
.site-loader {
    position: fixed;
    inset: 0;
    z-index: 9999;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    background: #05060d;
    transition: opacity .5s ease, visibility .5s ease;
}
.site-loader.is-hidden {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
}
/* The wait is the Pahadian name signing itself, once, in about 3.6
   seconds. The letterforms are Great Vibes (SIL Open Font License)
   converted to outlines and inlined, so the loader never waits on a
   webfont before it can draw - and hand-built SVG rather than a Lottie
   for the same reason: a player would have to download and execute
   before painting a frame, which is exactly what a loading screen
   cannot wait for. */
.site-loader-sign {
    position: relative;
    width: clamp(250px, 66vw, 430px);
    line-height: 0;
}
/* A static pool of light rather than a drop-shadow filter on the SVG: a
   filter would have to be re-applied on every frame of the writing, on
   top of re-rasterising the mask, which is the one thing that makes this
   stutter on a slow phone. */
.site-loader-sign::before {
    content: "";
    position: absolute;
    inset: -30% -10%;
    background: radial-gradient(closest-side, rgba(0, 212, 255, 0.16), rgba(0, 212, 255, 0) 100%);
    pointer-events: none;
}
.site-loader-sign svg {
    position: relative;
    display: block;
    width: 100%;
    height: auto;
    overflow: visible;
}
/* Paint - the ink gradient and the pen's white stroke - lives on the
   elements as presentation attributes, NOT here. A url(#id) written in an
   external stylesheet resolves against the stylesheet's own URL, so it
   would look for style.css#siteLoaderInkGrad and find nothing; an
   unresolvable paint server means the glyphs never paint. Keeping only the
   animation here also means a failure to load this file degrades to a
   static signature rather than a blank screen.

   Each pen stroke carries its own geometry and timing inline, in --l
   (its measured length, rounded up), --d (how long it takes to write),
   --t (when it starts) and --e (its speed curve). The dasharray is that
   measured length rather than pathLength, which some renderers handle
   unreliably - so if a pen path is edited, its --l has to be re-measured
   with getTotalLength() or the stroke will start part-drawn.

   The default curve eases in and out because a hand accelerates off the
   page and settles again at the end of every stroke; the long sweeps
   (the P, the h, the d's ascender, the n) override it with a flatter
   curve that holds speed through the middle. Both are what make this
   read as writing instead of a bar wiping across the letters. The last
   stroke lands at 3.605s - custom.js waits that long before it may
   uncover the page. */
.site-loader-pen > path {
    stroke-dasharray: var(--l);
    stroke-dashoffset: var(--l);
    animation: siteLoaderWrite var(--d) var(--e, cubic-bezier(.45, .06, .55, .95)) var(--t) forwards;
}
@keyframes siteLoaderWrite { to { stroke-dashoffset: 0; } }

@media (prefers-reduced-motion: reduce) {
    /* No writing motion: the finished signature is simply present. */
    .site-loader-pen > path { animation: none; stroke-dashoffset: 0; }
}
body.is-loading {
    overflow: hidden;
}

/* The eight service sections on /services are link targets (/services#seo
   and so on, from the home page service list and the schema @ids). The header
   is fixed, so a plain jump would hide the section's heading under it. */
.service-block {
    scroll-margin-top: 7rem;
}

/* ===========================================================================
   "Start your project" waiting character (.pc)

   Four complete renders of the same figure, stacked and cross-faded. They were
   rebuilt onto one aligned 640x960 canvas (see index.html), so every pose
   shares the offsets below and only its opacity changes.

     .pc          the layout box, sized to the idle pose's own bounding box
     .pc-pose     one render each; .is-on is the visible one

   He stands still: no breathing, no bob, no drift, and he does not turn toward
   the pointer. Changing pose is the entire interaction, so every animation here
   is a plain CSS transition - the cross-fade and the glow - and custom.js only
   ever moves the .is-on class from one image to another.
   =========================================================================== */

/* The idle pose occupies x 139-518, y 7-953 of the 640x960 canvas, and .pc is
   sized to exactly that box so the transparent margin never becomes layout
   space. The other poses are deliberately allowed to overflow it: the
   thumbs-up elbow reaches about 34 canvas px past the left edge, which lands
   harmlessly inside the gutter below. */
.pc {
    --pc-w: 112px;
    /* Breathing room either side of him, on top of the card padding on the left
       and the flex gap on the right. It also gives the poses that reach wider
       than the idle box - the thumbs-up elbow especially - somewhere to go
       without crowding the card edge or the heading. */
    --pc-gutter: 14px;
    position: relative;
    width: var(--pc-w);
    aspect-ratio: 380 / 947;
    flex-shrink: 0;
    margin-inline: var(--pc-gutter);
}
@media (min-width: 640px)  { .pc { --pc-w: 126px; --pc-gutter: 20px; } }
@media (min-width: 768px)  { .pc { --pc-w: 140px; --pc-gutter: 26px; } }
/* From here the card has stopped growing (max-w-[1100px]) but the row still has
   space to spare, so the gutter takes a share of it rather than letting it all
   pool in the empty stretch to the right of the paragraph. */
@media (min-width: 1024px) { .pc { --pc-w: 156px; --pc-gutter: 48px; } }
@media (min-width: 1280px) { .pc { --pc-w: 156px; --pc-gutter: 64px; } }

.pc-pose {
    position: absolute;
    left: -36.579%;      /* -139 / 380 */
    top: -0.739%;        /*   -7 / 947 */
    width: 168.421%;     /*  640 / 380 */
    height: auto;
    max-width: none;     /* Tailwind preflight sets img { max-width: 100% } */
    opacity: 0;
    pointer-events: none;
    user-select: none;
    -webkit-user-drag: none;
    /* Outgoing pose, deliberately slower than the incoming one below.
       Two things are being avoided here. Crossing both at the same rate leaves
       each at 0.5 halfway through, and the card shows faintly through him. But
       fading too fast reads as a hard cut rather than a change of stance - so
       the new pose arrives quickly and the old one lingers underneath it,
       keeping coverage solid the whole way across. */
    transition: opacity 0.34s cubic-bezier(0.33, 0, 0.2, 1);
}
.pc-pose.is-on {
    opacity: 1;
    transition: opacity 0.26s cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* Ambient warmth behind him. His own pixels are never filtered or tinted, so
   the artwork stays exactly as delivered. */
.pc-glow {
    position: absolute;
    left: 50%;
    top: 3%;
    margin-left: -95%;
    width: 190%;
    height: 46%;
    border-radius: 50%;
    background: radial-gradient(closest-side, rgba(20, 184, 166, 0.28), rgba(20, 184, 166, 0));
    opacity: 0;
    transition: opacity 0.45s ease;
    pointer-events: none;
}
.pc.is-attentive .pc-glow { opacity: 0.5; }
.pc.is-excited   .pc-glow { opacity: 1; }

/* "Get in touch" - lifts to meet the thumbs-up. */
.pc-trigger {
    box-shadow: 0 6px 16px -10px rgba(13, 148, 136, 0.9);
    transition: background-color 0.25s ease,
                box-shadow 0.35s ease,
                transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.pc-trigger:hover,
.pc-trigger:focus-visible {
    transform: translateY(-3px);
    box-shadow: 0 16px 28px -12px rgba(13, 148, 136, 0.85);
}
.pc-trigger:active { transform: translateY(-1px); }

/* Under reduced motion custom.js stops the idle watch-check cycle, so he simply
   stands there until the visitor does something. The pose still changes on hover
   - that is a state change rather than motion, and it is the whole point of the
   interaction - but it cross-fades without anything moving. */
@media (prefers-reduced-motion: reduce) {
    .pc-glow { transition: opacity 0.6s ease; }
    .pc-trigger,
    .pc-trigger:hover,
    .pc-trigger:focus-visible,
    .pc-trigger:active { transform: none; }
}
