/* ==========================================================================
   DA INFOTECH — UI
   --------------------------------------------------------------------------
   A quiet, professional studio interface. The previous system shouted:
   3px ink borders, zero-blur offset shadows, 120px headlines. This one puts
   the work first and lets the layout carry the confidence instead.

   The identity survives in the palette rather than the mechanics. Indigo is
   the primary — a natural-dye colour that also happens to read as serious —
   with a single warm amber kept for accents that need to feel handmade.
   Gujarati and Devanagari stay as real typographic layers; Porbandar is in
   Gujarat and the script belongs on the page.

   Everything is 1px hairlines, soft layered shadows, small radii, and
   transitions short enough to feel mechanical rather than decorative.

    1. Tokens
    2. Reset & base
    3. Type
    4. Layout
    5. Primitives — button, label
    6. Surface patterns
    7. Marquee
    8. Header
    9. Hero
   10. Stats
   11. Services
   12. Digital solutions
   13. Selected work — the card grid
   14. Client reviews
   15. Career & documents — packs, CTA, chips
   16. About
   17. Process
   18. Contact & form
   19. Footer
   20. Reveal on scroll
   21. Responsive
   22. Motion preferences
   ========================================================================== */


/* ==========================================================================
   1. TOKENS
   ========================================================================== */

:root {

  /* ---- Ground & ink ---------------------------------------------------- */
  --bg:            #FFFFFF;
  --bg-subtle:     #F7F7F6;   /* alternating bands — warm neutral          */
  --bg-sunken:     #F1F1F0;   /* wells, insets                             */
  --bg-dark:       #101318;   /* contact & footer                          */
  --bg-dark-2:     #171B21;   /* raised surfaces on the dark ground        */
  --surface:       #FFFFFF;

  --text:          #14161A;
  --text-2:        #565A63;
  /* Was #888D96, which is 3.34:1 on white — under AA for the 11–13px text it
     is used on (stat labels, card meta, form hints). This is 4.77:1 and still
     reads as the third step down from --text-2. */
  --text-3:        #6E737C;
  --text-inv:      #F4F5F7;
  --text-inv-2:    #A3A9B3;

  --border:        #E7E8EB;
  --border-2:      #D5D7DC;
  --border-inv:    rgba(255, 255, 255, 0.13);

  /* ---- Accent ----------------------------------------------------------- */
  --accent:        #2B45C4;   /* indigo — primary                          */
  --accent-dark:   #21349B;
  --accent-light:  #5C74E8;   /* accent text on the dark ground            */
  --accent-soft:   #EFF1FD;
  --accent-line:   #C9D0F8;

  --warm:          #B9721E;   /* amber — sparing, for handmade marks       */
  --warm-soft:     #FBF3E8;
  --warm-light:    #E9A44E;
  /* --warm is 3.83:1 on white: fine for an icon or a rule, under AA as text.
     Anything that has to be READ in amber on a light ground uses this. */
  --warm-ink:      #96590F;

  --focus:         #4B62DC;

  /* ---- Type ------------------------------------------------------------- */
  --font-display: 'Plus Jakarta Sans', ui-sans-serif, system-ui, -apple-system,
                  'Segoe UI', sans-serif;
  --font-body:    'Inter', ui-sans-serif, system-ui, -apple-system,
                  'Segoe UI', sans-serif;

  /* A restrained scale. The top end is less than half the old one. */
  --fs-display: clamp(2.375rem, 1.55rem + 3.3vw, 4.25rem);   /* 38 → 68 */
  --fs-xxl:     clamp(1.875rem, 1.40rem + 1.9vw, 2.875rem);  /* 30 → 46 */
  --fs-xl:      clamp(1.5rem,   1.28rem + 0.9vw, 2rem);      /* 24 → 32 */
  --fs-lg:      clamp(1.125rem, 1.06rem + 0.28vw, 1.3125rem);/* 18 → 21 */
  --fs-md:      1rem;
  --fs-sm:      0.9375rem;
  --fs-xs:      0.8125rem;
  --fs-micro:   0.75rem;

  --lh-tight:   1.08;
  --lh-heading: 1.2;
  --lh-body:    1.65;

  --ls-display: -0.028em;
  --ls-heading: -0.018em;
  --ls-label:    0.08em;

  /* ---- Space ------------------------------------------------------------ */
  --s-1:  0.25rem;
  --s-2:  0.5rem;
  --s-3:  0.75rem;
  --s-4:  1rem;
  --s-5:  1.5rem;
  --s-6:  2rem;
  --s-7:  3rem;
  --s-8:  4rem;
  --s-9:  6rem;

  /* Vertical rhythm. Three values only — section padding, the gap under a
     section head, and the gap between repeated blocks. Everything spaced
     larger than --s-6 should use one of these rather than a raw step, or the
     page drifts out of rhythm one section at a time. */
  --band:      clamp(2.75rem, 1.9rem + 3.4vw, 4.5rem);  /* section padding  */
  --band-gap:  clamp(1.75rem, 1.4rem + 1.5vw, 2.5rem);  /* head → content   */
  --block-gap: clamp(2.25rem, 1.75rem + 2.2vw, 3.5rem); /* big block → block*/

  --wrap:   1200px;
  --pad:    clamp(1.25rem, 0.6rem + 2.6vw, 2.5rem);

  /* ---- Shape ------------------------------------------------------------ */
  --r-sm:   6px;
  --r-md:  10px;
  --r-lg:  14px;
  --r-xl:  20px;
  --r-pill: 999px;

  /* ---- Depth — soft and layered, never a hard offset --------------------- */
  --sh-sm: 0 1px 2px rgba(20, 22, 26, 0.05);
  --sh-md: 0 1px 2px rgba(20, 22, 26, 0.04),
           0 10px 24px -14px rgba(20, 22, 26, 0.18);
  --sh-lg: 0 2px 6px rgba(20, 22, 26, 0.04),
           0 24px 52px -26px rgba(20, 22, 26, 0.24);
  --sh-accent: 0 8px 22px -12px rgba(43, 69, 196, 0.55);

  /* ---- Motion ----------------------------------------------------------- */
  --t:      180ms cubic-bezier(0.22, 1, 0.36, 1);
  --t-slow: 320ms cubic-bezier(0.22, 1, 0.36, 1);

  /* Hover is asymmetric on purpose. A pointer arriving is a deliberate act, so
     the response is immediate; a pointer leaving is often incidental, so the
     release is slower and the element is not snatched away from under it. Equal
     in and out durations are what make an interface feel machine-timed. */
  --t-in:   140ms cubic-bezier(0.34, 1.2, 0.64, 1);
  --t-out:  260ms cubic-bezier(0.33, 0, 0.2, 1);

  /* Entrances get their own, longer curve — a near-expo ease-out, so movement
     arrives fast and settles slowly instead of gliding at a constant rate. */
  --t-reveal: 720ms cubic-bezier(0.16, 1, 0.3, 1);
  /* Opacity resolves before travel does. The element is legible while it is
     still settling, which is why a real entrance reads as arriving rather than
     as sliding — matched durations produce the second. */
  --t-fade:   460ms cubic-bezier(0.33, 0, 0.2, 1);
  /* The masked headline lines. Longer than a card, because the distance
     travelled is a whole line-height rather than a few pixels. */
  --t-line:   940ms cubic-bezier(0.16, 1, 0.3, 1);
}


/* ==========================================================================
   2. RESET & BASE
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }

* { margin: 0; padding: 0; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 5.5rem;   /* the sticky header                        */
}

body {
  background-color: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--fs-md);
  line-height: var(--lh-body);
  font-feature-settings: 'cv05' 1, 'ss01' 1;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

img, svg, video { display: block; max-width: 100%; }

ul, ol { list-style: none; }

a { color: inherit; }

button, input, textarea, select {
  font: inherit;
  color: inherit;
}

button { background: none; border: 0; cursor: pointer; }

:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 3px;
  border-radius: var(--r-sm);
}

::selection { background-color: var(--accent); color: #FFFFFF; }

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  top: -100%; left: var(--s-4);
  z-index: 200;
  padding: var(--s-3) var(--s-4);
  background-color: var(--accent);
  color: #FFFFFF;
  border-radius: var(--r-sm);
  font-size: var(--fs-sm);
  font-weight: 600;
  text-decoration: none;
}

.skip-link:focus { top: var(--s-4); }


/* ==========================================================================
   3. TYPE
   ========================================================================== */

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 700;
  line-height: var(--lh-heading);
  letter-spacing: var(--ls-heading);
  text-wrap: balance;
  /* A display word at a scaled-up root font can be wider than a phone. Break
     it rather than push the whole page sideways.

     `anywhere`, not `break-word`, and the difference is the entire point:
     both break identically once a line box is too narrow, but only `anywhere`
     reduces the element's min-content width. `.head__row` goes
     `align-items: flex-start` below 720px, which makes its children
     shrink-to-fit — and a shrink-to-fit box sized from min-content grows to
     the longest word, pushing the whole page sideways before the break rule
     ever gets a chance to apply. "Straightforward" at 30px did exactly that. */
  overflow-wrap: anywhere;
}

.xxl {
  font-size: var(--fs-xxl);
  line-height: 1.12;
  letter-spacing: var(--ls-display);
}

.xl { font-size: var(--fs-xl); }

.lead {
  font-size: var(--fs-lg);
  line-height: 1.6;
  color: var(--text-2);
  text-wrap: pretty;
}

.muted { color: var(--text-2); }

p { text-wrap: pretty; }


/* ==========================================================================
   4. LAYOUT
   ========================================================================== */

.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--pad);
}

.band {
  padding-block: var(--band);
  border-bottom: 1px solid var(--border);
}

.band--paper-deep { background-color: var(--bg-subtle); }

.band--ink {
  background-color: var(--bg-dark);
  color: var(--text-inv);
  border-bottom-color: transparent;
}

.band--flush { border-bottom: 0; }

/* --- Section head ----------------------------------------------------------- */
.head { margin-bottom: var(--band-gap); }

.head__row {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--s-6);
  margin-top: var(--s-4);
}

.head__title { flex: 1 1 26rem; }

.head__lead {
  flex: 0 1 30rem;
  max-width: 34rem;
  font-size: var(--fs-md);
  line-height: 1.6;
}

/* `.band--ink .muted` sat here too and never matched anything — the only .muted
   on a dark band is .head__lead, which is already named. Re-add it if a .muted
   is ever placed inside .band--ink; without it the text inherits the band's
   pale colour and is unreadable. */
.band--ink .head__lead { color: var(--text-inv-2); }


/* ==========================================================================
   5. PRIMITIVES
   ========================================================================== */

/* --- Button ----------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  padding: 0.7rem 1.15rem;
  background-color: var(--surface);
  color: var(--text);
  border: 1px solid var(--border-2);
  border-radius: var(--r-md);
  box-shadow: var(--sh-sm);
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: -0.005em;
  text-decoration: none;
  white-space: nowrap;
  transition: background-color var(--t-out), border-color var(--t-out),
              box-shadow var(--t-out), transform var(--t-out), color var(--t-out);
}

.btn svg { width: 1em; height: 1em; flex: none; }

.btn:hover {
  background-color: var(--bg-subtle);
  border-color: var(--border-2);
  box-shadow: var(--sh-md);
  transform: translateY(-1px);
  transition: background-color var(--t-in), border-color var(--t-in),
              box-shadow var(--t-in), transform var(--t-in), color var(--t-in);
}

/* The press goes past the resting position, not back to it, and takes the
   shadow down with it — the button is being pushed into the page rather than
   returning to level. No duration: a press has to land on the same frame as
   the finger, and any easing at all reads as lag. */
.btn:active {
  transform: translateY(1px);
  box-shadow: var(--sh-sm);
  transition-duration: 0ms;
}

/* The one filled button. Used for the single next step on the page. */
.btn--go {
  background-color: var(--accent);
  color: #FFFFFF;
  border-color: var(--accent);
  box-shadow: var(--sh-accent);
}

.btn--go:hover {
  background-color: var(--accent-dark);
  border-color: var(--accent-dark);
  box-shadow: 0 12px 28px -12px rgba(43, 69, 196, 0.65);
}

.btn--lg { padding: 0.9rem 1.5rem; font-size: var(--fs-md); }
.btn--full { width: 100%; }

/* On the dark ground the outline button needs its own values. */
.band--ink .btn {
  background-color: transparent;
  color: var(--text-inv);
  border-color: var(--border-inv);
  box-shadow: none;
}

.band--ink .btn:hover { background-color: rgba(255, 255, 255, 0.07); }

.band--ink .btn--go {
  background-color: var(--accent);
  color: #FFFFFF;
  border-color: var(--accent);
}

.band--ink .btn--go:hover { background-color: var(--accent-light); border-color: var(--accent-light); }

/* --- Label — the small eyebrow above a section headline ---------------------- */
.label {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: 0.3rem 0.7rem;
  background-color: var(--bg-subtle);
  color: var(--text-2);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  font-size: var(--fs-micro);
  font-weight: 600;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  white-space: nowrap;
}

/* The old system had a dye colour per section. Keeping one tinted variant and
   collapsing the rest keeps the page from turning back into a paint chart. */
.label--marigold,
.label--madder,
.label--peacock {
  background-color: var(--accent-soft);
  color: var(--accent-dark);
  border-color: var(--accent-line);
}

.label--paper {
  background-color: var(--surface);
  color: var(--text-2);
  border-color: var(--border-2);
}

.band--ink .label {
  background-color: rgba(255, 255, 255, 0.06);
  color: var(--text-inv-2);
  border-color: var(--border-inv);
}

/* The .tag primitive lived here. Its only caller was the old work row; the
   card grid that replaced it carries its own .card__cat, so the primitive was
   a neutral pill nothing used. */


/* ==========================================================================
   6. SURFACE PATTERNS
   --------------------------------------------------------------------------
   The Ajrakh block print is gone as a literal motif — at this weight it read
   as wallpaper. What is left is a faint diagonal grid that gives placeholder
   surfaces some texture without competing with the content on top.
   ========================================================================== */

.pat-ajrakh {
  position: relative;
  background-color: var(--accent-soft);
  background-image:
    repeating-linear-gradient(45deg,
      rgba(43, 69, 196, 0.055) 0 1px, transparent 1px 14px),
    repeating-linear-gradient(-45deg,
      rgba(43, 69, 196, 0.055) 0 1px, transparent 1px 14px);
}


/* ==========================================================================
   7. MARQUEE
   --------------------------------------------------------------------------
   Reduced to a hairline strip. It reads as a quiet ticker rather than a
   painted band, so it can sit between sections without splitting the page.
   ========================================================================== */

.marquee {
  overflow: hidden;
  padding-block: var(--s-3);
  background-color: var(--bg-subtle);
  border-block: 1px solid var(--border);
  user-select: none;
}

.marquee__track {
  display: flex;
  width: max-content;
  animation: marquee-run 46s linear infinite;
}

.marquee--rev  .marquee__track { animation-direction: reverse; }
.marquee--fast .marquee__track { animation-duration: 32s; }

.marquee__group {
  display: flex;
  align-items: center;
  gap: var(--s-5);
  padding-inline: calc(var(--s-5) / 2);
  color: var(--text-2);
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* The separator is a dot, not a star. */
.marquee__star {
  color: transparent;
  position: relative;
  width: 4px;
}

.marquee__star::before {
  content: "";
  position: absolute;
  inset: 50% auto auto 0;
  width: 4px; height: 4px;
  transform: translateY(-50%);
  background-color: var(--accent-line);
  border-radius: 50%;
}

.marquee--go .marquee__star::before  { background-color: var(--accent-line); }
.marquee--red .marquee__star::before { background-color: var(--warm-light); }

.marquee:hover .marquee__track { animation-play-state: paused; }

@keyframes marquee-run {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}


/* ==========================================================================
   8. HEADER
   ========================================================================== */

.hdr {
  position: sticky;
  top: 0;
  z-index: 100;
  background-color: rgba(255, 255, 255, 0.82);
  backdrop-filter: saturate(180%) blur(14px);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  border-bottom: 1px solid var(--border);
  transition: box-shadow var(--t), background-color var(--t);
}

/* Set by the script once the page has moved off the top. At rest the header is
   part of the page; in motion it needs to sit above it, and a hairline alone
   does not do that over a photograph. Opacity goes up too — 0.82 white over a
   dark screenshot is not enough to keep the nav legible. */
.hdr.is-stuck {
  background-color: rgba(255, 255, 255, 0.92);
  box-shadow: 0 1px 2px rgba(20, 22, 26, 0.04), 0 8px 24px rgba(20, 22, 26, 0.07);
}

.hdr__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  min-height: 4.5rem;
}

/* --- Brand ------------------------------------------------------------------ */
.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  min-width: 0;   /* lets the name give way before the bar overflows */
  text-decoration: none;
  color: var(--text);
}

.brand__mark {
  display: grid;
  place-items: center;
  width: 2.125rem; height: 2.125rem;
  background-color: var(--accent);
  color: #FFFFFF;
  border-radius: var(--r-sm);
  box-shadow: var(--sh-accent);
  font-family: var(--font-display);
  font-size: 0.8125rem;
  font-weight: 800;
  letter-spacing: 0.02em;
  transition: transform var(--t);
}

.brand:hover .brand__mark { transform: rotate(-4deg) scale(1.04); }

.brand__name {
  min-width: 0;
  font-family: var(--font-display);
  font-size: 1.0625rem;
  font-weight: 700;
  letter-spacing: -0.015em;
  overflow-wrap: anywhere;
}

/* --- Nav -------------------------------------------------------------------- */
.nav__list {
  display: flex;
  align-items: center;
  flex-wrap: wrap;   /* only ever engages under heavy text scaling */
  gap: clamp(0.25rem, -0.2rem + 1.4vw, 0.75rem);
}

.nav__link {
  position: relative;
  display: block;
  padding: 0.45rem 0.7rem;
  border-radius: var(--r-sm);
  color: var(--text-2);
  font-size: var(--fs-sm);
  font-weight: 500;
  text-decoration: none;
  transition: color var(--t-out), background-color var(--t-out);
}

.nav__link:hover {
  color: var(--text);
  background-color: var(--bg-subtle);
  transition: color var(--t-in), background-color var(--t-in);
}

.nav__link.is-active { color: var(--accent); background-color: var(--accent-soft); }

.hdr__actions { display: flex; align-items: center; gap: var(--s-3); }

.hdr__cta { padding: 0.55rem 1rem; }

/* --- Mobile toggle ---------------------------------------------------------- */
.navtoggle {
  display: none;
  width: 2.5rem; height: 2.5rem;
  place-items: center;
  background-color: var(--surface);
  border: 1px solid var(--border-2);
  border-radius: var(--r-sm);
  box-shadow: var(--sh-sm);
  transition: background-color var(--t);
}

.navtoggle:hover { background-color: var(--bg-subtle); }

.navtoggle__box {
  display: grid;
  gap: 4px;
  width: 1rem;
}

.navtoggle__box span {
  height: 1.5px;
  background-color: var(--text);
  border-radius: 2px;
  transition: transform var(--t), opacity var(--t);
}

[aria-expanded="true"] .navtoggle__box span:nth-child(1) { transform: translateY(5.5px) rotate(45deg); }
[aria-expanded="true"] .navtoggle__box span:nth-child(2) { opacity: 0; }
[aria-expanded="true"] .navtoggle__box span:nth-child(3) { transform: translateY(-5.5px) rotate(-45deg); }


/* ==========================================================================
   9. HERO
   ========================================================================== */

/* The first section on the page, so its top padding is NOT --band: that token
   is sized to separate two sections, and there is nothing above the hero but
   the header, which already draws its own bottom rule. Left at --band it reads
   as a void between the nav and the hero tags. */
.hero {
  border-bottom: 0;
  padding-top: clamp(1.5rem, 1rem + 2vw, 2.75rem);
  padding-bottom: var(--block-gap);
}

.hero__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
  gap: clamp(2rem, 1rem + 4vw, 4.5rem);
  align-items: center;
}

.hero__tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin-bottom: var(--s-5);
}

.hero__title {
  font-size: var(--fs-display);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-display);
  font-weight: 800;
}

/* The clipping box. The padding/negative-margin pair widens the box by 0.14em
   on each side without moving anything: `overflow: hidden` cuts at the padding
   edge, so accents and descenders that sit outside a tight line box survive,
   while the negative margin gives the space back to the layout. Clipping at
   the content edge instead is what shears the tops off capitals. */
.hero__line {
  display: block;
  overflow: hidden;
  padding-block: 0.14em;
  margin-block: -0.14em;
}

.hero__line__in { display: block; }

/* No highlight boxes. The emphasis is the colour of the word itself. */
.hero__line--go  { color: var(--accent); }
.hero__line--ink { color: var(--text); }

.hero__lead {
  margin-top: var(--s-5);
  max-width: 34rem;
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  margin-top: var(--s-6);
}

/* Two short proof points under the CTAs. They earn their place by being
   checkable claims about how the work is run, not adjectives. */
.hero__points {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-5);
  margin-top: var(--s-5);
}

.hero__points li {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  color: var(--text-2);
  font-size: var(--fs-xs);
  font-weight: 500;
}

.hero__points svg {
  width: 0.9rem; height: 0.9rem;
  flex: none;
  color: var(--accent);
}

/* --- Panel ------------------------------------------------------------------- */
.hero__panel {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--s-5);
  min-height: 22rem;
  padding: var(--s-6);
  border: 1px solid var(--accent-line);
  border-radius: var(--r-xl);
  box-shadow: var(--sh-md);
  overflow: hidden;
}

/* --- Real screenshots in CSS device frames ---------------------------------
   Replaces the drawn SVG. Padding-right/bottom reserves the corner the phone
   overlaps into, so the phone never covers the window's content — it hangs off
   an area nothing else occupies. */
.hero__shots {
  position: relative;
  width: min(100%, 25rem);
  padding-right: 3.25rem;
  padding-bottom: 1.75rem;
}

.hero__win {
  display: block;
  overflow: hidden;
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: 0 14px 24px rgba(20, 22, 26, 0.10);
}

.hero__win-bar {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  padding: 0.45rem 0.6rem;
  background-color: var(--bg-subtle);
  border-bottom: 1px solid var(--border);
}

.hero__win-dots { display: flex; gap: 4px; }

.hero__win-dots span {
  width: 6px; height: 6px;
  background-color: var(--border-2);
  border-radius: 50%;
}

.hero__win-url {
  flex: 1 1 auto;
  height: 11px;
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
}

/* height: auto is load-bearing, not tidiness. The tag carries width="800"
   height="500" so the browser can reserve the box before the file arrives —
   but that height attribute is a presentational hint that BEATS aspect-ratio,
   so without this the frame renders a literal 500px tall and the composition
   collapses. The reset sets display/max-width on img but not height. */
.hero__win-img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  object-position: 50% 0;
}

.hero__phone {
  position: absolute;
  right: 0;
  bottom: 0;
  display: block;
  width: 5.75rem;
  padding: 3px;
  background-color: #14161A;
  border-radius: 0.85rem;
  box-shadow: 0 16px 26px rgba(20, 22, 26, 0.18);
}

.hero__phone-notch {
  position: absolute;
  top: 7px; left: 50%;
  transform: translateX(-50%);
  z-index: 2;
  width: 26%; height: 3px;
  background-color: #14161A;
  border-radius: var(--r-pill);
}

/* Same height:auto trap as .hero__win-img — height="1140" would win outright. */
.hero__phone-img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 9 / 19;
  object-fit: cover;
  object-position: 50% 0;
  border-radius: 0.7rem;
}

/* In normal flow under the art. Absolute positioning here used to drop them
   on top of whatever the illustration happened to be doing.

   This was two small chips floating on the pattern, which left the bottom of
   a 22rem panel reading as empty space. One card that spans the panel gives
   the illustration a base to sit on and carries twice the information in the
   same height. */
/* Tracks the art's width so the two edges line up. --s-5 is the panel's own
   gap, so this only has to space its own two rows. */
.hero__meta {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  width: min(100%, 25rem);
}

/* Always four across. Labels wrap before the grid does, so the row holds its
   shape down to the narrowest panel. */
.hero__tiles {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--s-2);
}

.hero__tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  padding: 0.6rem 0.25rem;
  background-color: rgba(255, 255, 255, 0.92);
  backdrop-filter: blur(6px);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--sh-sm);
  color: var(--text-2);
  font-size: var(--fs-micro);
  font-weight: 600;
  letter-spacing: 0.02em;
  text-align: center;
}

/* The service marks are drawn on a 40-unit grid at stroke 2.5; at 20px that
   lands on a 1.25px line, which is the weight the rest of the page's icons
   read at. */
.hero__tile-ico {
  width: 1.25rem; height: 1.25rem;
  color: var(--accent);
}

.hero__tile-ico svg { width: 100%; height: 100%; }

/* The provenance the two stickers used to carry — now one quiet line under
   the tiles rather than a chip competing with them. */
/* --text-3 NOT --text-2 is the obvious choice here and it fails AA. That token
   is tuned to 4.77:1 against WHITE, but this line sits on .hero__panel, whose
   ground is --accent-soft (#EFF1FD). The tint eats the margin and drops it to
   4.24:1 — under AA for 12px. --text-2 is 6.15:1 on the same ground.
   Any --text-3 text moved onto a tinted panel needs re-checking for this. */
.hero__origin {
  color: var(--text-2);
  font-size: var(--fs-micro);
  text-align: center;
}

.hero__origin-sep { margin: 0 0.3rem; color: var(--border-2); }
.hero__origin-warm { color: var(--warm-ink); font-weight: 600; }


/* ==========================================================================
   10. STATS
   ========================================================================== */

.stats {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  margin-top: var(--block-gap);
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-md);
  overflow: hidden;
}

.stat {
  padding: var(--s-5);
  border-right: 1px solid var(--border);
}

.stat:last-child { border-right: 0; }

.stat__num {
  font-family: var(--font-display);
  font-size: clamp(1.75rem, 1.4rem + 1.4vw, 2.5rem);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--accent);
}

/* Smaller than the numeral it follows, so "20+" does not read as a two-part
   number with the plus competing for the same weight. */
.stat__plus { font-size: 0.6em; vertical-align: 0.35em; }

.stat__label {
  margin-top: var(--s-2);
  color: var(--text-3);
  font-size: var(--fs-xs);
  font-weight: 500;
  line-height: 1.45;
}


/* ==========================================================================
   11. SERVICES
   ========================================================================== */

/* Three across, not four. At four the copy in each card wrapped to five or six
   lines on a laptop and the row read as a list of headings with debris under
   them. Three gives every card a readable measure at every width above the
   tablet breakpoint. */
.svc-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s-5);
}

.svc {
  display: flex;
  flex-direction: column;
  padding: var(--s-5);
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-sm);
  /* Declared here, the slow curve owns the return trip; the :hover rule below
     overrides it for the trip out. */
  transition: transform var(--t-out), box-shadow var(--t-out), border-color var(--t-out);
}

.svc:hover {
  transform: translateY(-3px);
  border-color: var(--accent-line);
  box-shadow: var(--sh-lg);
  transition: transform var(--t-in), box-shadow var(--t-in), border-color var(--t-in);
}

/* The card used to carry a counter opposite the icon. It numbered a set whose
   order carries no meaning, and it was the loudest thing in the row. */
.svc__mark {
  display: grid;
  place-items: center;
  width: 2.75rem; height: 2.75rem;
  margin-bottom: var(--s-5);
  padding: 0.6rem;
  background-color: var(--accent-soft);
  color: var(--accent);
  border-radius: var(--r-md);
  transition: background-color var(--t), color var(--t);
}

.svc__mark svg { width: 100%; height: 100%; }

.svc:hover .svc__mark { background-color: var(--accent); color: #FFFFFF; }

.svc__title {
  font-size: var(--fs-lg);
  line-height: 1.25;
  margin-bottom: var(--s-3);
}

.svc__title a { text-decoration: none; }
.svc__title a:hover { color: var(--accent); }

/* The card is the hit area; the title link just carries the destination. */
.svc__title a::after { content: ""; position: absolute; inset: 0; }
.svc { position: relative; }

.svc__text {
  flex: 1 1 auto;
  color: var(--text-2);
  font-size: var(--fs-sm);
}

.svc__go {
  margin-top: var(--s-5);
  color: var(--accent);
  font-size: var(--fs-xs);
  font-weight: 600;
  transition: transform var(--t-out);
}

/* Slower than the card that carries it, and starting a beat later. One
   interaction moving at two speeds is what gives it a sense of weight — when
   every part of a hover fires at the same duration, the whole card reads as a
   single flat image being nudged. */
.svc:hover .svc__go {
  transform: translateX(3px);
  transition: transform var(--t-slow) 30ms;
}


/* ==========================================================================
   12. DIGITAL SOLUTIONS
   --------------------------------------------------------------------------
   Deliberately not another .svc card. Services above are things that get
   built, so they lead with an icon over a heading; these are things that get
   run, so they sit as a flat two-column row — icon beside the text, no lift.
   Two card treatments on one page is a system; three is a scrapbook.
   ========================================================================== */

.sol-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s-4);
}

.sol {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  gap: var(--s-4);
  padding: var(--s-5);
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  transition: border-color var(--t), box-shadow var(--t);
}

.sol:hover {
  border-color: var(--accent-line);
  box-shadow: var(--sh-md);
}

.sol__mark {
  display: grid;
  place-items: center;
  width: 2.5rem; height: 2.5rem;
  padding: 0.5rem;
  background-color: var(--bg-subtle);
  color: var(--accent);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  transition: background-color var(--t), border-color var(--t);
}

.sol__mark svg { width: 100%; height: 100%; }

.sol:hover .sol__mark {
  background-color: var(--accent-soft);
  border-color: var(--accent-line);
}

.sol__body { display: flex; flex-direction: column; gap: var(--s-2); min-width: 0; }

.sol__title { font-size: var(--fs-md); line-height: 1.3; }

.sol__text { color: var(--text-2); font-size: var(--fs-sm); }


/* ==========================================================================
   13. SELECTED WORK — the card grid
   --------------------------------------------------------------------------
   This was an alternating full-width row per project: a 50/50 split with the
   preview and the copy trading sides. Three of them ran to most of a screen
   each, which read as a case-study page rather than a portfolio, and the
   fourth project had nowhere to go.

   Now it is a three-up grid of equal cards. Two things keep the row even:
   every visual carries the same 16:10 ratio whatever is inside it, and the
   body is a flex column whose text takes the slack, so the foot rule and the
   action sit on the same line across all three cards regardless of how long
   the description runs.
   ========================================================================== */

.work-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s-5);
}

.card {
  position: relative;
  display: flex;
  flex-direction: column;
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-sm);
  overflow: hidden;
  transition: transform var(--t-out), box-shadow var(--t-out), border-color var(--t-out);
}

.card:hover {
  transform: translateY(-4px);
  border-color: var(--accent-line);
  box-shadow: var(--sh-lg);
  transition: transform var(--t-in), box-shadow var(--t-in), border-color var(--t-in);
}

/* A <span>, so it needs blockifying before aspect-ratio and overflow apply.
   Removing the display here collapses it to 0×0 and its children escape the
   card entirely. */
.card__visual {
  display: flex;
  flex-direction: column;
  position: relative;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  border-bottom: 1px solid var(--border);
}

/* --- Website cards: browser chrome over the page area ------------------------ */
.card__chrome {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  flex: none;
  padding: 0.45rem 0.55rem;
  background-color: var(--bg-subtle);
  border-bottom: 1px solid var(--border);
}

.card__dots { display: flex; gap: 5px; flex: none; }

.card__dots span {
  width: 7px; height: 7px;
  background-color: var(--border-2);
  border-radius: 50%;
}

.card__url {
  flex: 1 1 auto;
  min-width: 0;
  padding: 0.15rem 0.55rem;
  background-color: var(--surface);
  color: var(--text-3);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  font-size: 0.6875rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.card__stage { display: block; position: relative; flex: 1 1 auto; overflow: hidden; }

/* The slowest thing in the card, by a wide margin: the frame lifts in 140ms
   and the image behind it is still easing four times longer. 2.8% is under
   the threshold where a screenshot starts to look soft — it is meant to be
   noticed at the edges of the crop, not watched. Both this element and its
   parent clip, so nothing escapes the card. */
.card__stage { transition: transform 620ms cubic-bezier(0.16, 1, 0.3, 1); }

.card:hover .card__stage { transform: scale(1.028); }

/* --- App cards: a phone centred in the same 16:10 box ------------------------ */
.card__visual--app { display: grid; place-items: center; padding: var(--s-4); }

.card__device {
  display: block;
  position: relative;
  height: 100%;
  aspect-ratio: 9 / 19;
  padding: 3px;
  background-color: var(--text);
  border-radius: 12px;
  box-shadow: var(--sh-lg);
  transition: transform var(--t);
}

.card:hover .card__device { transform: translateY(-4px); }

/* Over the white screen, so it reads as a punch-hole. A pale notch cut into
   the bezel at this size is simply not visible. */
.card__notch {
  position: absolute;
  top: 7px; left: 50%;
  transform: translateX(-50%);
  z-index: 3;
  width: 26%; height: 3px;
  background-color: var(--text);
  border-radius: var(--r-pill);
}

.card__screen {
  display: block;
  position: relative;
  height: 100%;
  overflow: hidden;
  background-color: var(--bg);
  border-radius: 9px;
}

/* --- Placeholder and the real capture that replaces it ----------------------- */
.card__img,
.card__ph { position: absolute; inset: 0; }

.card__img {
  z-index: 2;
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: 50% 0;
}

/* A second page from the same site, cross-faded in on hover. Sits above
   .card__img inside .card__stage — the stage carries a transform and is
   therefore its own stacking context, so this 3 never competes with the
   .card__mini 3 outside it. */
.card__img--alt {
  z-index: 3;
  opacity: 0;
  transition: opacity 420ms cubic-bezier(0.16, 1, 0.3, 1);
}

.card:hover .card__img--alt,
.card:focus-within .card__img--alt { opacity: 1; }

.card__ph {
  z-index: 1;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: var(--s-2);
  padding-inline: var(--s-3);
}

/* --- The phone on a website card -------------------------------------------
   In .card__visual rather than .card__stage, so the stage's hover scale does
   not drag it. z-index 3 is deliberate and load-bearing: .card__title a::after
   is 4 and covers the card to make it clickable. Raise this to 4 or above and
   the corner stops responding to clicks — which looks like nothing at all is
   wrong, and is the half of the card people aim at. */
.card__mini {
  position: absolute;
  right: 5%;
  bottom: 0;
  z-index: 3;
  display: block;
  width: 20%;
  padding: 2px;
  background-color: var(--text);
  border-radius: 8px 8px 0 0;
  box-shadow: var(--sh-lg);
  transition: transform var(--t);
}

.card:hover .card__mini { transform: translateY(-4px); }

.card__mini-notch {
  position: absolute;
  top: 4px; left: 50%;
  transform: translateX(-50%);
  z-index: 2;
  width: 26%; height: 2px;
  background-color: var(--text);
  border-radius: var(--r-pill);
}

/* height: auto or the height="1140" attribute wins over aspect-ratio and the
   phone renders full-length down the card. Same trap as the hero shots. */
.card__mini-img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 9 / 19;
  object-fit: cover;
  object-position: 50% 0;
  border-radius: 7px 7px 0 0;
}

.card__mono {
  font-family: var(--font-display);
  font-size: clamp(1.25rem, 0.9rem + 1.4vw, 1.875rem);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--accent);
  opacity: 0.75;
}

.card__mono--sm { font-size: 0.9375rem; opacity: 0.9; }

/* --- Body -------------------------------------------------------------------- */
.card__body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--s-3);
  padding: var(--s-5);
  flex: 1 1 auto;
}

.card__cat {
  padding: 0.2rem 0.6rem;
  background-color: var(--accent-soft);
  color: var(--accent-dark);
  border-radius: var(--r-pill);
  font-size: var(--fs-micro);
  font-weight: 600;
  letter-spacing: 0.02em;
}

.card__title { font-size: var(--fs-lg); line-height: 1.25; }

.card__title a { text-decoration: none; }
.card__title a:hover { color: var(--accent); }

/* The card is the hit area; the title link carries the destination.
   The z-index is not optional. Everything inside .card__visual — the
   placeholder (1), a real screenshot (2), the phone notch (3) — is explicitly
   layered, and the card is not a stacking context, so an overlay left at
   `auto` sits UNDER all of it. Without this, the whole image half of every
   work card silently stops being clickable, which is the half people aim at. */
.card__title a::after { content: ""; position: absolute; inset: 0; z-index: 4; }

/* Takes the slack, which is what keeps the foot rules aligned across a row. */
.card__text { flex: 1 1 auto; color: var(--text-2); font-size: var(--fs-sm); }

.card__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  width: 100%;
  margin-top: var(--s-1);
  padding-top: var(--s-4);
  border-top: 1px solid var(--border);
}

.card__stack {
  min-width: 0;
  color: var(--text-3);
  font-size: var(--fs-micro);
  overflow-wrap: anywhere;
}

.card__go {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  flex: none;
  color: var(--accent);
  font-size: var(--fs-xs);
  font-weight: 600;
  transition: transform var(--t-out);
}

.card__go svg { width: 0.85em; height: 0.85em; flex: none; }

.card:hover .card__go {
  transform: translateX(3px);
  transition: transform var(--t-slow) 30ms;
}


/* ==========================================================================
   14. CLIENT REVIEWS
   --------------------------------------------------------------------------
   The markup for this is commented out in index.html until real, permitted
   client quotes exist. The styles stay live so that uncommenting the section
   is the only step — see the instructions in that comment block.

   The stars are clip-path on a plain box rather than a glyph or an SVG each:
   one shape, two colours, and `.is-off` is the whole of the empty state. A
   card must never show five filled stars because five was the default.
   ========================================================================== */

.rev-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s-5);
}

.rev {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  padding: var(--s-6) var(--s-5);
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-sm);
  transition: border-color var(--t), box-shadow var(--t);
}

.rev:hover { border-color: var(--accent-line); box-shadow: var(--sh-md); }

.rev__stars { display: flex; gap: 3px; }

.rev__star {
  width: 1rem; height: 1rem;
  background-color: var(--warm-light);
  clip-path: polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%,
                     50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%);
}

.rev__star.is-off { background-color: var(--border-2); }

.rev__quote { flex: 1 1 auto; color: var(--text-2); font-size: var(--fs-sm); }

.rev__who {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding-top: var(--s-4);
  border-top: 1px solid var(--border);
}

.rev__avatar {
  display: grid;
  place-items: center;
  width: 2.5rem; height: 2.5rem;
  flex: none;
  background-color: var(--accent-soft);
  color: var(--accent-dark);
  border-radius: 50%;
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: 700;
}

.rev__name { display: block; font-size: var(--fs-sm); font-weight: 600; }

.rev__meta { display: block; color: var(--text-3); font-size: var(--fs-micro); }


/* ==========================================================================
   15. CAREER & DOCUMENTS — packs, note, CTA, chips
   ========================================================================== */

.pack-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s-5);
}

.pack {
  display: flex;
  flex-direction: column;
  padding: var(--s-6) var(--s-5);
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-sm);
  transition: border-color var(--t), box-shadow var(--t);
}

.pack:hover { border-color: var(--border-2); box-shadow: var(--sh-md); }

/* Warm rather than indigo. This band sells to a different visitor than the
   one reading the services grid, and the icon well is the cheapest way to
   say so without introducing a fourth hue. */
.pack__mark {
  display: grid;
  place-items: center;
  width: 2.75rem; height: 2.75rem;
  margin-bottom: var(--s-5);
  padding: 0.6rem;
  background-color: var(--warm-soft);
  color: var(--warm);
  border-radius: var(--r-md);
}

.pack__mark svg { width: 100%; height: 100%; }

.pack__title { font-size: var(--fs-lg); line-height: 1.25; margin-bottom: var(--s-3); }

.pack__text { color: var(--text-2); font-size: var(--fs-sm); }

.pack__list {
  display: grid;
  gap: var(--s-2);
  margin-top: var(--s-4);
  padding-top: var(--s-4);
  border-top: 1px solid var(--border);
}

.pack__list li {
  position: relative;
  padding-left: 1.1rem;
  color: var(--text-2);
  font-size: var(--fs-xs);
}

.pack__list li::before {
  content: "";
  position: absolute;
  left: 0; top: 0.55em;
  width: 5px; height: 5px;
  background-color: var(--accent-line);
  border-radius: 50%;
}

/* The documents card carries a condition, and the condition is the point. */
.pack__note {
  display: flex;
  align-items: flex-start;
  gap: var(--s-3);
  margin-top: var(--s-4);
  padding: var(--s-4);
  background-color: var(--warm-soft);
  border: 1px solid rgba(185, 114, 30, 0.22);
  border-radius: var(--r-md);
  font-size: var(--fs-xs);
  color: var(--text-2);
}

.pack__note strong { color: var(--text); font-weight: 600; }

.pack__note-bar {
  flex: none;
  width: 3px;
  align-self: stretch;
  background-color: var(--warm);
  border-radius: var(--r-pill);
}

/* --- A qualifying note under a grid ------------------------------------------ */
.section-note {
  max-width: 62ch;
  margin-top: var(--s-6);
  padding: var(--s-4) var(--s-5);
  background-color: var(--bg-subtle);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--r-md);
  color: var(--text-2);
  font-size: var(--fs-sm);
}

.section-note strong { color: var(--text); font-weight: 600; }

/* --- CTA panel ---------------------------------------------------------------- */
.cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-5);
  margin-top: var(--block-gap);
  padding: clamp(1.5rem, 1rem + 2vw, 2.5rem);
  background-color: var(--bg-dark);
  color: var(--text-inv);
  border-radius: var(--r-xl);
  box-shadow: var(--sh-lg);
}

.cta__body { flex: 1 1 24rem; min-width: 0; }

.cta__title {
  font-family: var(--font-display);
  font-size: var(--fs-xl);
  line-height: var(--lh-heading);
  letter-spacing: var(--ls-heading);
}

.cta__text {
  margin-top: var(--s-3);
  max-width: 46rem;
  color: var(--text-inv-2);
  font-size: var(--fs-sm);
}

.cta .btn { flex: none; }

/* --- More services: four grouped lists ---------------------------------------- */
.chips {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--s-5);
}

.chips__col {
  padding: var(--s-5);
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-sm);
}

/* Flex, not an inline icon: the heading wraps to two lines at narrow widths
   ("Security & Infrastructure"), and inline would drop the icon onto the
   second line with the overflow. align-items: center holds it against the
   first line whatever the text does. */
.chips__h {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-bottom: var(--s-4);
  padding-bottom: var(--s-3);
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-md);
}

.chips__ico {
  flex: 0 0 auto;              /* never let the heading text squeeze it */
  display: grid;
  place-items: center;
  width: 1.75rem; height: 1.75rem;
  padding: 0.3rem;
  background-color: var(--accent-soft);
  color: var(--accent);
  border-radius: var(--r-sm);
}

.chips__ico svg { width: 100%; height: 100%; }

.chips__list { display: grid; gap: var(--s-2); }

.chips__list li {
  position: relative;
  padding-left: 1.15rem;
  color: var(--text-2);
  font-size: var(--fs-sm);
}

.chips__list li::before {
  content: "";
  position: absolute;
  left: 0; top: 0.72em;
  width: 7px; height: 2px;
  background-color: var(--accent);
  border-radius: 2px;
}


/* ==========================================================================
   16. ABOUT
   ========================================================================== */

.about {
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
  gap: clamp(2rem, 1rem + 4vw, 4.5rem);
  align-items: center;
}

/* No forced 1:1. A square derived from the width clips its own contents the
   moment the text grows — larger root font, longer wordmark — and the ratio
   was decoration. A min-height keeps the stamp proportion without the trap. */
.about__stamp {
  position: relative;   /* containing block for .about__shot */
  display: grid;
  /* An implicit `auto` track sizes to the item's max-content and refuses to
     shrink, so the card's `max-width: 100%` resolved against the card's own
     272px and never clamped. `minmax(0, 1fr)` gives the track a real floor. */
  grid-template-columns: minmax(0, 1fr);
  place-items: center;
  width: 100%;
  min-height: 18rem;
  padding: var(--s-6);
  border: 1px solid var(--accent-line);
  border-radius: var(--r-xl);
  box-shadow: var(--sh-md);
  overflow: hidden;
}

/* Fills the panel edge to edge, under the frosted card. The panel already
   clips and rounds, so nothing escapes the corners. Slightly dimmed so the
   white card keeps its contrast whatever screenshot sits here. */
.about__shot {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: 50% 0;
  filter: saturate(0.9) brightness(0.96);
}

.about__stamp-inner {
  position: relative;
  z-index: 1;           /* over .about__shot */
  display: grid;
  justify-items: center;
  gap: var(--s-2);
  /* `width: min(100%, 17rem)` was circular inside a shrink-to-fit parent, so
     the 17rem always won — and at a 24px root that is 408px, wider than a
     small phone. An explicit width with a percentage cap resolves. */
  width: 17rem;
  max-width: 100%;
  padding: var(--s-6) var(--s-5);
  background-color: rgba(255, 255, 255, 0.92);
  backdrop-filter: blur(6px);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-sm);
  text-align: center;
}

/* The DA INFOTECH lockup at the centre of the stamp — the wordmark itself,
   not a chip. "Porbandar" above it is the .label. */
.about__est {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 1.2rem + 1.2vw, 2rem);
  font-weight: 800;
  line-height: 1.05;
  letter-spacing: -0.03em;
  color: var(--text);
  text-transform: uppercase;
  overflow-wrap: break-word;
}

.about__loc {
  color: var(--accent);
  font-size: var(--fs-sm);
  font-weight: 600;
}

.about__h    { margin-top: var(--s-5); }
.about__text { margin-top: var(--s-5); }

/* Academic background. Generic on purpose — the capability is the claim, and
   no degree name, institution or "student" framing belongs on a services
   page. Read the note above this section in index.html before rewording it. */
.about__note {
  margin-top: var(--s-6);
  padding: var(--s-5);
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--warm);
  border-radius: var(--r-md);
  box-shadow: var(--sh-sm);
}

.about__note-t { font-size: var(--fs-md); margin-bottom: var(--s-2); }

.about__note p { color: var(--text-2); font-size: var(--fs-sm); }

/* --- Why grid ----------------------------------------------------------------
   Its own band now, six items rather than three, so it takes the section grid
   rather than the two-up it used inside the About column. */
.why-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s-4);
}

.why {
  padding: var(--s-5);
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--sh-sm);
}

/* Same well as .svc__mark, one step down: these cards are half the size of a
   service card, so a 2.75rem mark would dominate them. No hover swap either —
   .why is not a link and nothing here is clickable. */
.why__mark {
  display: grid;
  place-items: center;
  width: 2.25rem; height: 2.25rem;
  margin-bottom: var(--s-4);
  padding: 0.5rem;
  background-color: var(--accent-soft);
  color: var(--accent);
  border-radius: var(--r-sm);
}

.why__mark svg { width: 100%; height: 100%; }

.why__title { font-size: var(--fs-md); margin-bottom: var(--s-2); }

.why__text { font-size: var(--fs-sm); color: var(--text-2); }


/* ==========================================================================
   17. PROCESS
   ========================================================================== */

.steps {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-md);
  overflow: hidden;
}

.step {
  padding: var(--s-5);
  border-right: 1px solid var(--border);
}

.step:last-child { border-right: 0; }

/* Number and icon share one row: the numeral carries the sequence, the icon
   carries the meaning. The margin that used to sit on .step__num moves here,
   or the two would space independently and the titles would stop aligning
   across the four cells. */
.step__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  margin-bottom: var(--s-4);
}

.step__num {
  display: inline-grid;
  place-items: center;
  width: 1.75rem; height: 1.75rem;
  background-color: var(--accent-soft);
  color: var(--accent-dark);
  border-radius: var(--r-sm);
  font-family: var(--font-display);
  font-size: var(--fs-micro);
  font-weight: 700;
}

/* Lighter than the numeral on purpose — it is the supporting mark, not a
   second focal point competing with it. */
.step__ico {
  display: grid;
  place-items: center;
  width: 1.75rem; height: 1.75rem;
  color: var(--accent-line);
}

.step__ico svg { width: 100%; height: 100%; }

.step__title { font-size: var(--fs-md); margin-bottom: var(--s-2); }

.step__text { font-size: var(--fs-sm); color: var(--text-2); }


/* ==========================================================================
   18. CONTACT & FORM
   ========================================================================== */

.contact {
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
  gap: clamp(2rem, 1rem + 3vw, 4rem);
  align-items: start;
}

.contact__list { display: grid; gap: var(--s-3); }

.contact__item {
  display: block;
  padding: var(--s-4) var(--s-5);
  background-color: var(--bg-dark-2);
  color: var(--text-inv);
  border: 1px solid var(--border-inv);
  border-radius: var(--r-md);
  text-decoration: none;
  transition: background-color var(--t), border-color var(--t), transform var(--t);
}

.contact__item:hover {
  background-color: #1E232B;
  border-color: rgba(255, 255, 255, 0.22);
  transform: translateY(-2px);
}

.contact__k {
  display: block;
  color: var(--text-inv-2);
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
}

.contact__v {
  font-family: var(--font-display);
  font-size: var(--fs-md);
  font-weight: 600;
  margin-top: 0.2rem;
  /* The email is a single unbreakable token wider than the tile below ~260px. */
  overflow-wrap: anywhere;
}

/* The address is three lines of prose, not a value to be read at heading size. */
.contact__v--addr {
  font-size: var(--fs-sm);
  line-height: 1.45;
}

/* --- Form -------------------------------------------------------------------- */
/* A light panel on the dark band. It must reclaim the ink explicitly, or every
   label — and every character typed — inherits the band's pale colour. */
.form {
  padding: var(--s-6);
  background-color: var(--surface);
  color: var(--text);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-lg);
}

.form__row { display: grid; gap: var(--s-4); }

@media (min-width: 640px) {
  .form__row--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.field { display: grid; gap: var(--s-2); margin-bottom: var(--s-4); }

.field__label {
  color: var(--text-2);
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.02em;
}

.field__req { color: var(--warm-ink); }

.field__opt {
  color: var(--text-3);
  font-weight: 500;
  text-transform: none;
  letter-spacing: 0;
}

/* Empty until the script fills it, and it collapses when empty — an error slot
   that always reserves a line puts a ragged gap under every field. */
.field__err {
  color: #B3261E;
  font-size: var(--fs-xs);
  font-weight: 500;
}

.field__err:empty { display: none; }

.field .is-invalid {
  border-color: #B3261E;
  background-color: #FEF6F5;
}

.field .is-invalid:focus {
  border-color: #B3261E;
  box-shadow: 0 0 0 3px rgba(179, 38, 30, 0.14);
}

/* Honeypot. Off-screen rather than display:none — a few bots skip anything
   the CSS hides outright, and this has to look fillable to them. It carries
   aria-hidden and tabindex="-1" in the markup, so nobody using a keyboard or
   a screen reader can reach it. */
.hp {
  position: absolute;
  left: -9999px;
  width: 1px; height: 1px;
  overflow: hidden;
}

.field input,
.field textarea,
.field select {
  width: 100%;
  padding: 0.7rem 0.85rem;
  background-color: var(--bg);
  color: var(--text);
  border: 1px solid var(--border-2);
  border-radius: var(--r-md);
  font-size: var(--fs-sm);
  transition: border-color var(--t), box-shadow var(--t);
}

.field input::placeholder,
.field textarea::placeholder { color: var(--text-3); }

.field input:focus,
.field textarea:focus,
.field select:focus {
  outline: 0;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.field textarea { resize: vertical; min-height: 8rem; }

.form .btn--go { margin-top: var(--s-2); }

/* The one place the form speaks: idle guidance, sending, success, failure.
   It is a live region, so whatever lands here is announced — which is why the
   script writes a sentence rather than a status word. */
.form__note {
  margin-top: var(--s-4);
  color: var(--text-3);
  font-size: var(--fs-xs);
}

.form__note--good {
  padding: var(--s-4);
  background-color: #F0FAF3;
  color: #14603A;
  border: 1px solid #BFE6CD;
  border-radius: var(--r-md);
  font-size: var(--fs-sm);
  font-weight: 500;
}

.form__note--bad {
  padding: var(--s-4);
  background-color: #FEF6F5;
  color: #8C1D18;
  border: 1px solid #F3C9C5;
  border-radius: var(--r-md);
  font-size: var(--fs-sm);
  font-weight: 500;
}

/* While the request is in flight. Without this the button still looks armed
   and a second click is the visitor's obvious next move. */
.btn[disabled] {
  opacity: 0.65;
  cursor: progress;
  transform: none;
}


/* ==========================================================================
   19. FOOTER
   ========================================================================== */

.ftr {
  background-color: var(--bg-dark);
  color: var(--text-inv-2);
  padding-block: var(--s-7) var(--s-6);
}

.ftr__top {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
  gap: var(--band-gap);
  padding-bottom: var(--band-gap);
}

.ftr .brand { color: var(--text-inv); }

.ftr__tagline {
  margin-top: var(--s-4);
  max-width: 24rem;
  font-size: var(--fs-sm);
  color: var(--text-inv-2);
}

/* --- Social ------------------------------------------------------------------
   Three channels the business actually answers on. Every icon here must lead
   somewhere staffed; an unmaintained profile in a footer is worse than a gap. */
.ftr__social {
  display: flex;
  gap: var(--s-3);
  margin-top: var(--s-5);
}

.ftr__social a {
  display: grid;
  place-items: center;
  width: 2.5rem; height: 2.5rem;
  padding: 0.6rem;
  color: var(--text-inv-2);
  border: 1px solid var(--border-inv);
  border-radius: var(--r-md);
  transition: background-color var(--t), color var(--t), border-color var(--t);
}

.ftr__social a:hover {
  background-color: var(--bg-dark-2);
  color: var(--text-inv);
  border-color: rgba(255, 255, 255, 0.24);
}

.ftr__social svg { width: 100%; height: 100%; }

.ftr__cols {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--s-5);
}

.ftr__h {
  margin-bottom: var(--s-4);
  color: var(--text-inv);
  font-family: var(--font-display);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
}

/* The link, not the list item, carries the vertical padding. As plain inline
   text these were 19px tall — under the 24px minimum pointer target, and the
   hardest thing on the page to hit on a phone. The padding comes out of the
   item's margin, so the footer keeps the same rhythm. */
.ftr__cols li { margin-bottom: var(--s-1); }

.ftr__cols a { display: inline-block; padding-block: 0.3rem; }

.ftr__cols a,
.ftr__static {
  color: var(--text-inv-2);
  font-size: var(--fs-sm);
  text-decoration: none;
  transition: color var(--t);
  /* The email address is one unbreakable 171px token in a ~132px column — it
     was pushing the whole page into horizontal scroll on a small phone. */
  overflow-wrap: anywhere;
}

.ftr__cols a:hover { color: var(--text-inv); }

.ftr__bottom {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  padding-top: var(--s-5);
  border-top: 1px solid var(--border-inv);
  font-size: var(--fs-xs);
}

.ftr__made { color: var(--warm-light); }


/* ==========================================================================
   20. REVEAL ON SCROLL
   --------------------------------------------------------------------------
   Opacity and transform only. Both are composited, so this never triggers
   layout or paint and stays smooth on a mid-range phone.

   The hidden state is scoped to `.js`, a class the inline script in <head>
   adds before first paint. With scripting off the class is never added, so
   every element renders at its final state and the page stays complete —
   nothing here can hide content from a visitor who never gets the script.
   ========================================================================== */

/* 14px, not the 22 this started at. The distance is the part that gives a
   canned entrance away: far enough to be watched and the element reads as
   arriving from off-screen, which nothing on a page actually does. Short
   enough and the movement is felt rather than followed, which is the whole
   effect. Opacity runs on its own shorter curve — see --t-fade. */
/* The entrance moves elements with `translate`, not `transform`, and the
   reason is a collision rather than a preference. Cards, service tiles and
   contact rows all carry [data-reveal] AND a :hover lift. Written as
   `transform`, the revealed state — .js [data-reveal].is-revealed, three
   selectors — outranks .card:hover at two, so the moment a card finished
   revealing its hover lift was overridden to none and silently stopped
   working. `translate` is its own property: it composes with whatever
   `transform` the hover sets instead of replacing it, so both survive without
   either rule needing to know about the other.

   Older engines without independent transform properties ignore the translate
   and still run the fade, which is the correct thing to lose. */
.js [data-reveal] {
  opacity: 0;
  translate: 0 14px;
}

/* The entrance runs as an animation, not a transition, and that is a
   correctness fix rather than a style choice. Cards and tiles carry
   [data-reveal] and a :hover lift at the same time, and `transition` is a
   shorthand: whichever of the two rules lost the cascade had its entire
   transition list erased. Written as a transition the entrance won, and every
   hover on a revealed card changed state instantly with no easing at all.

   `animation` and `transition` are separate properties that never overwrite
   one another, so the entrance and the hover can both be declared without
   either having to know the other exists. It also suits the job: an entrance
   is a one-shot with a known start and end, which is what keyframes describe
   and what a transition has to be tricked into. */
.js [data-reveal].is-revealed {
  animation: reveal-in var(--t-reveal) both;
}

/* Opacity is finished at 64% — the same 460/720 split the two curves used to
   express — so the element is readable while it is still settling the last few
   pixels. Arriving, not sliding. */
@keyframes reveal-in {
  0%   { opacity: 0; translate: 0 14px; }
  64%  { opacity: 1; }
  100% { opacity: 1; translate: none; }
}

/* The headline opts out of the shared entrance entirely — both halves of it.
   The translate would move the same text the lines are already moving, and the
   fade would undo the point of a mask: the effect is text being uncovered by a
   fixed edge, which only reads if the text is at full opacity when it crosses
   that edge. Fading it as well is the doubled-up tell.

   Pinning opacity here also settles a collision with the group stagger. This
   h1 is the second [data-reveal] inside .hero__grid, so the observer stamps an
   inline 90ms transition-delay on it — but transition-delay is not inherited,
   so the lines below would ignore it and start rising 90ms before the element
   they sit in became visible. With nothing left on the h1 to transition, that
   inline delay now has nothing to act on and the lines own the timing. */
.js .hero__title[data-reveal],
.js .hero__title[data-reveal].is-revealed {
  opacity: 1;
  translate: none;
  animation: none;
}

.js .hero__title[data-reveal] .hero__line__in {
  /* 120%, not 100%: the clipping box is 0.14em taller than the line on each
     side, so a child displaced by exactly its own height still shows a sliver
     inside that slack. */
  transform: translate3d(0, 120%, 0);
  transition: transform var(--t-line);
}

.js .hero__title.is-revealed .hero__line__in { transform: none; }

/* Read in the order the eye reads them. The gap is deliberately shorter than
   the 90ms used between cards — these are three parts of one sentence, not
   three separate objects, and spacing them further apart breaks the phrase
   into a list. */
.js .hero__title[data-reveal] .hero__line:nth-child(2) .hero__line__in { transition-delay: 70ms; }
.js .hero__title[data-reveal] .hero__line:nth-child(3) .hero__line__in { transition-delay: 140ms; }

/* The two hero devices drift, slowly and barely — 8px over 3.6s. Enough to
   stop the panel reading as a flat placeholder, small enough that it never
   competes with the headline beside it. */
.hero__win   { animation: hero-drift 4.4s ease-in-out infinite alternate; }
.hero__phone { animation: hero-drift 3.6s ease-in-out 0.4s infinite alternate; }

@keyframes hero-drift {
  from { transform: translateY(0); }
  to   { transform: translateY(-8px); }
}


/* ==========================================================================
   21. RESPONSIVE
   ========================================================================== */

@media (max-width: 1024px) {
  .hero__grid { grid-template-columns: minmax(0, 1fr); }
  /* The panel used to be order:-1, so a decorative placeholder owned the whole
     first screen and pushed the h1 to y=595 and both CTAs below the fold. */
  .hero__panel { min-height: 14rem; }

  /* Every three-up grid on the page steps to two here, and to one at 720. */
  .svc-grid,
  .sol-grid,
  .work-grid,
  .rev-grid,
  .pack-grid,
  .chips,
  .why-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  .about { grid-template-columns: minmax(0, 1fr); }
  /* Capped but never centred, this sat hard left with 371px of dead space
     beside it at tablet width. Dropping the forced square as well — once the
     box is content-sized, a 1:1 ratio clips the wordmark instead of framing
     it. The square is a desktop luxury. */
  /* `margin-inline: auto` centred it but forced shrink-to-fit, which made every
     percentage inside it circular. `justify-self` centres without that. */
  .about__stamp { max-width: 22rem; justify-self: center; }

  .steps { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .step:nth-child(2) { border-right: 0; }
  .step:nth-child(1), .step:nth-child(2) { border-bottom: 1px solid var(--border); }

  .contact { grid-template-columns: minmax(0, 1fr); }
  .ftr__top { grid-template-columns: minmax(0, 1fr); gap: var(--s-7); }

  /* --- Nav becomes a drawer ---------------------------------------------- */
  .hdr { position: sticky; }
  .hdr__bar { position: relative; }

  .navtoggle { display: grid; }

  .nav {
    position: absolute;
    top: calc(100% + 0.75rem); left: 0; right: 0;
    display: none;
    padding: var(--s-3);
    background-color: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    box-shadow: var(--sh-lg);
  }

  /* The drawer used to appear with a hard display:none -> block, which on a
     phone reads as a glitch rather than a panel opening. An animation (not a
     transition) is what works here: a transition cannot run on the same frame
     display changes, an animation triggered by the class can. Opening only —
     closing removes display, so there is nothing left to animate against. */
  .nav.is-open {
    display: block;
    animation: nav-open 260ms cubic-bezier(0.16, 1, 0.3, 1) both;
    transform-origin: top center;
  }

  @keyframes nav-open {
    from { opacity: 0; transform: translateY(-10px) scale(0.98); }
    to   { opacity: 1; transform: none; }
  }

  /* Links arrive just behind the panel, 40ms apart, capped so a long menu
     never turns into a queue. Delay is on the animation, not a transition,
     so it costs nothing once finished. */
  .nav.is-open .nav__item {
    animation: nav-item-in 300ms cubic-bezier(0.16, 1, 0.3, 1) both;
  }

  .nav.is-open .nav__item:nth-child(1) { animation-delay: 60ms; }
  .nav.is-open .nav__item:nth-child(2) { animation-delay: 100ms; }
  .nav.is-open .nav__item:nth-child(3) { animation-delay: 140ms; }
  .nav.is-open .nav__item:nth-child(4) { animation-delay: 180ms; }
  .nav.is-open .nav__item:nth-child(5) { animation-delay: 220ms; }
  .nav.is-open .nav__item:nth-child(n + 6) { animation-delay: 260ms; }

  @keyframes nav-item-in {
    from { opacity: 0; transform: translateY(-6px); }
    to   { opacity: 1; transform: none; }
  }

  .nav__list { flex-direction: column; align-items: stretch; gap: 2px; }

  .nav__link { padding: var(--s-3) var(--s-4); font-size: var(--fs-md); }

  .hdr__cta { display: none; }
}

@media (max-width: 720px) {
  .stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .stat { border-bottom: 1px solid var(--border); }
  .stat:nth-child(2n) { border-right: 0; }
  .stat:nth-child(3), .stat:nth-child(4) { border-bottom: 0; }

  .svc-grid,
  .sol-grid,
  .work-grid,
  .rev-grid,
  .pack-grid,
  .chips,
  .why-grid { grid-template-columns: minmax(0, 1fr); }

  /* A single card at full width would run a 16:10 preview to nearly half the
     viewport height. Cap the card, not the grid, so it stays left-aligned
     with everything else in the band. */
  .card { max-width: 32rem; }

  .steps { grid-template-columns: minmax(0, 1fr); }
  .step { border-right: 0; border-bottom: 1px solid var(--border); }
  .step:last-child { border-bottom: 0; }

  /* Same flex-basis trap as .head__row below: in a column the 24rem basis on
     .cta__body became a 384px minimum HEIGHT, so the panel held ~215px of dead
     black between the paragraph and its own button. */
  .cta { flex-direction: column; align-items: flex-start; }
  .cta__body { flex: 0 1 auto; }

  /* Once this is a column, flex-basis sizes the HEIGHT, not the width — the
     26rem/30rem bases below were forcing every section head to 912px tall and
     putting a screen and a half of blank space between a heading and its own
     paragraph. Reset the basis with the direction. */
  .head__row { flex-direction: column; align-items: flex-start; gap: var(--s-4); }
  .head__title,
  .head__lead { flex: 0 1 auto; }

  /* Nowrap is fine on a wide label and an overflow trap on a narrow one. */
  .label { white-space: normal; }

  .ftr__cols { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-6); }

  .ftr__bottom { flex-direction: column; align-items: flex-start; gap: var(--s-2); }

  .form { padding: var(--s-5); }
}

/* Small phones. Everything from 720px down to 320px used to share one set of
   rules, which is where the layout came apart — this is the floor. */
@media (max-width: 480px) {
  /* Two shrink-wrapped buttons of different widths read as unfinished; below
     this width they cannot sit side by side anyway. */
  .hero__actions { flex-direction: column; align-items: stretch; }
  .hero__actions .btn { width: 100%; }

  .stat { padding: var(--s-4); }

  /* One column: the email fits on a line instead of breaking mid-address. */
  .ftr__cols { grid-template-columns: minmax(0, 1fr); gap: var(--s-5); }

  /* A shrink-wrapped button in a stacked panel reads as unfinished. */
  .cta .btn { width: 100%; }

  .form { padding: var(--s-4); }
  .about__stamp { padding: var(--s-5); }
  .about__note { padding: var(--s-4); }
  .hero__panel { padding: var(--s-5); }

  /* Four tiles need ~296px of panel; a 360px phone leaves 272, which pushes
     "Websites" past its tile. Two-up rather than a shrunken four-up. */
  .hero__tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .contact__item { padding: var(--s-4); }
  .card__body { padding: var(--s-4); }
  .card__ph { padding-inline: var(--s-3); }
  .pack,
  .rev { padding: var(--s-5) var(--s-4); }
  .section-note { padding: var(--s-4); }
}

/* Touch: nothing to hover, so the lift states are removed rather than left
   stuck on after a tap. */
/* --- Touch targets ----------------------------------------------------------
   `pointer: coarse` not `max-width`: the constraint is the finger, not the
   screen. A touch laptop at 1400px needs these; a 400px browser window on a
   desktop does not.

   Everything here was measured under 44px on a real device profile and is a
   thing a finger has to hit. The 23px links inside .svc and .card are NOT
   here on purpose — their ::after covers the whole card, so the card is the
   target and it is far bigger than 44px.

   min-height + inline-flex rather than padding: padding would move the text
   and shift the layout at that width. This grows the hit box only. */
@media (pointer: coarse) {
  .brand {
    min-height: 44px;
    align-items: center;
  }

  .navtoggle {
    width: 2.75rem; height: 2.75rem;    /* 40px -> 44px */
  }

  .nav__link {
    display: flex;
    align-items: center;
    min-height: 44px;
  }

  .ftr__social a {
    width: 2.75rem; height: 2.75rem;    /* 40px -> 44px */
  }

  /* Footer nav links sit in a tight column; the extra block padding is what
     stops a mis-tap landing on the row above or below. */
  .ftr__cols a {
    display: flex;
    align-items: center;
    min-height: 44px;
    padding-block: 0;
  }
}

@media (hover: none) {
  .btn:hover,
  .svc:hover,
  .card:hover,
  .contact__item:hover { transform: none; }

  .card:hover .card__device { transform: none; }
  .card:hover .card__go { transform: none; }
  .card:hover .card__stage { transform: none; }
  .svc:hover .svc__go { transform: none; }
}


/* ==========================================================================
   22. MOTION PREFERENCES
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }

  .marquee__track { animation: none; transform: none; }

  .hero__win,
  .hero__phone { animation: none; transform: none; }

  /* Show the content outright rather than racing it in at 0.01ms. Both states
     are pinned, so an element is visible whether or not the observer ever
     reaches it. */
  /* animation:none as well as the pinned values. The entrance is a keyframe
     animation with `both` fill, so left running it would hold the 0% frame —
     opacity 0 — through its delay and briefly hide content that this mode
     exists to keep still and visible. */
  .js [data-reveal],
  .js [data-reveal].is-revealed {
    opacity: 1;
    translate: none;
    transform: none;
    transition: none;
    animation: none;
  }

  /* The masked headline is a second hidden state and needs pinning the same
     way. Left out, the lines sit a full line-height below their clipping boxes
     and the hero renders as three empty rows. */
  .js .hero__title[data-reveal] .hero__line__in,
  .js .hero__title.is-revealed .hero__line__in {
    transform: none;
    transition: none;
    transition-delay: 0ms;
  }

  .card:hover .card__stage { transform: none; }

  /* Same trap as [data-reveal] above, and easy to miss because the drawer is
     only reachable on a phone. The `*` rule kills animation DURATION but not
     animation-DELAY, and these keyframes fill `both` — so the staggered items
     would hold their 0% frame (opacity 0) for up to 260ms and the menu would
     open empty for a user who asked for less motion, not more. */
  .nav.is-open,
  .nav.is-open .nav__item {
    animation: none;
    animation-delay: 0ms;
    opacity: 1;
    transform: none;
  }
}
