/* ============================================================
   ART Mechatronics — base + shared components
   Every page loads: tokens.css then base.css then its own page css.
   Agents/pages MUST reuse these classes and must NOT redefine them.
   ============================================================ */

*,*::before,*::after { box-sizing: border-box; }
html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }
body {
  margin: 0; font-family: var(--font); color: var(--ink);
  background: var(--white); line-height: 1.6; font-size: var(--fs-body);
  -webkit-font-smoothing: antialiased;
}
img, svg { max-width: 100%; display: block; }
[hidden] { display: none !important; }
a { color: var(--blue); text-decoration: none; }
h1,h2,h3,h4 { font-family: var(--font-display); line-height: 1.08; color: var(--ink); margin: 0 0 .5em; font-weight: 800; letter-spacing: -.025em; }
h2 { font-size: var(--fs-h2); }
h3 { font-size: var(--fs-h3); }
p { margin: 0 0 1em; }

/* ---- layout helpers ---- */
.wrap { width: min(100% - 2.4rem, var(--wrap)); margin-inline: auto; }
.section { padding: var(--s-8) 0; }
.section--tint { background: var(--mist); }
.section--navy { background: linear-gradient(160deg,var(--blue),var(--navy-2)); color: #dfe8f5; }
.section--navy h2, .section--navy h3 { color: #fff; }
/* A plain link inside a navy section inherited the brand blue on a navy
   ground — measured at 3.02:1, under the 4.5:1 AA floor, and in practice you
   could barely see the email address on the careers page. Buttons keep their
   own styling; this is for links inside prose. Added 2026-08-22. */
.section--navy a:not(.btn) { color: #fff; text-decoration: underline; text-underline-offset: 3px; }
.section--navy a:not(.btn):hover { color: #bdd7f6; }
.eyebrow {
  display: inline-block; font-size: .78rem; font-weight: 700; letter-spacing: .16em;
  text-transform: uppercase; color: var(--blue); margin-bottom: var(--s-3);
}
.section--navy .eyebrow { color: #bdd7f6; }
/* Every dark hero shares one ground (--blue -> --navy-2), so every label on
   one shares a colour. Before this, .ct-hero .eyebrow had no rule of its own
   and fell back to the brand blue — which, once the grounds BECAME that blue,
   made "CONTACT" invisible. #bdd7f6 clears AA (4.7) on the lightest of them. */
.ct-hero .eyebrow, .mi-hero .eyebrow, .ab-hero .eyebrow,
.md-hero .eyebrow, .sv-hero .eyebrow,
.ab-hero__cols .eyebrow, .md-hero__inner .eyebrow,
.project-brief .eyebrow, .proc .eyebrow, .net-band .eyebrow { color: #bdd7f6; }
.lead { font-size: var(--fs-lead); color: var(--slate); max-width: 62ch; }
.section--navy .lead { color: #c2d3ea; }
.center { text-align: center; }
.center .lead { margin-inline: auto; }

/* ---- buttons ---- */
.btn {
  display: inline-flex; align-items: center; gap: .55rem; cursor: pointer;
  font: inherit; font-weight: 700; font-size: .98rem; line-height: 1;
  padding: .85rem 1.5rem; border-radius: var(--r); border: 1.5px solid transparent;
  transition: transform .18s var(--ease), box-shadow .18s var(--ease), background .18s, color .18s;
  text-align: center; white-space: nowrap;
}
.btn svg { width: 1.05em; height: 1.05em; }
.btn--primary { background: var(--blue); color: #fff; box-shadow: 0 8px 22px rgba(22,87,176,.32); }
.btn--primary:hover { background: var(--blue-bright); transform: translateY(-2px); box-shadow: 0 12px 28px rgba(46,134,230,.4); }
.btn--wa { background: #25D366; color: #073b1e; box-shadow: 0 8px 22px rgba(37,211,102,.32); }
.btn--wa:hover { background: #1fbe5a; transform: translateY(-2px); }
.btn--ghost { background: transparent; color: var(--blue); border-color: var(--line); }
.btn--ghost:hover { border-color: var(--blue); background: var(--blue-soft); }
.btn--light { background: #fff; color: var(--navy); }
.btn--light:hover { transform: translateY(-2px); box-shadow: var(--shadow); }
.btn--outline-light { background: transparent; color: #fff; border-color: rgba(255,255,255,.4); }
.btn--outline-light:hover { background: rgba(255,255,255,.12); border-color: #fff; }
/* BUTTONS ON THE BLUE GROUNDS (2026-08-16 repaint).
   The site's dark surfaces are now the same blue family as --blue itself, so
   a --blue button on them is nearly invisible and a --ghost button's blue
   text sits on blue. Any button inside a blue section therefore takes the
   light treatment instead: solid white for the primary action, white outline
   for the secondary. Scoped to the dark sections only — on white pages the
   blue buttons are unchanged. */
.proc .btn--primary,
.section--navy .btn--primary,
.project-brief .btn--primary,
.net-band .btn--primary,
.site-footer .btn--primary { background: #fff; color: var(--navy); box-shadow: none; }
.proc .btn--primary:hover,
.section--navy .btn--primary:hover,
.project-brief .btn--primary:hover,
.net-band .btn--primary:hover,
.site-footer .btn--primary:hover { background: #eaf2fd; color: var(--navy); transform: translateY(-2px); }
.proc .btn--ghost,
.section--navy .btn--ghost,
.project-brief .btn--ghost,
.net-band .btn--ghost,
.site-footer .btn--ghost { color: #fff; border-color: rgba(255,255,255,.45); background: transparent; }
.proc .btn--ghost:hover,
.section--navy .btn--ghost:hover,
.project-brief .btn--ghost:hover,
.net-band .btn--ghost:hover,
.site-footer .btn--ghost:hover { background: rgba(255,255,255,.12); border-color: #fff; color: #fff; }

.btn--lg { padding: 1.05rem 2rem; font-size: 1.05rem; }
.btn:active { transform: scale(.97); }

/* ---- cards ---- */
.card {
  background: #fff; border: 1px solid var(--line); border-radius: var(--r-lg);
  box-shadow: var(--shadow-sm); overflow: clip;
  transition: transform .22s var(--ease), box-shadow .22s var(--ease), border-color .22s;
}
.card:hover { transform: translateY(-4px); box-shadow: var(--shadow); border-color: #c9d6ea; }

/* pill / chip */
.chip {
  display: inline-flex; align-items: center; gap: .4rem; font-size: .78rem; font-weight: 700;
  letter-spacing: .04em; padding: .32rem .7rem; border-radius: 999px;
  background: var(--blue-soft); color: var(--blue); border: 1px solid #d4e4fb;
}

/* feature check list */
.checks { list-style: none; margin: 0; padding: 0; display: grid; gap: .7rem; }
.checks li { position: relative; padding-left: 1.9rem; color: var(--slate); }
.checks li::before {
  content: ""; position: absolute; left: 0; top: .15em; width: 1.25rem; height: 1.25rem;
  border-radius: 50%; background: var(--blue-soft) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%231657B0' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E") center/14px no-repeat;
}

/* ============ HEADER ============ */
.site-header {
  position: sticky; top: 0; z-index: 100; background: rgba(255,255,255,.9);
  backdrop-filter: saturate(160%) blur(10px); border-bottom: 1px solid var(--line);
}
.site-header__inner { display: flex; align-items: center; justify-content: space-between; gap: 1rem; height: 78px; }
.brand { display: flex; align-items: center; }
.brand img { height: 56px; width: auto; }
/* Tagline beside the logo (client, 2026-08-16). Separated by a hairline rule
   rather than a bullet so it reads as a lockup, not a nav item. It is the
   FIRST thing dropped when width gets tight: below 1100px the six nav links
   plus the quote button need every pixel. */
.brand-tag {
  margin: 0 auto 0 .95rem; padding-left: .95rem;
  border-left: 1px solid var(--line);
  color: var(--steel); font-size: .74rem; font-weight: 600; font-style: italic;
  letter-spacing: .07em; text-transform: uppercase; line-height: 1.3;
  /* 22ch breaks it as "Shaping the Future of / Industrial World" — two lines
     sit comfortably in the 78px bar; narrower ran to four and looked cramped. */
  max-width: 22ch;
}
/* The tagline is decorative (aria-hidden) and it competes with the nav for the
   same row. With seven nav items + Get a Quote (Partner With Us added
   2026-08-22) the nav needs ~817px, which at 1101px squeezed the logo from
   126px down to 52px and wrapped the tagline onto four lines. Drop the tagline
   sooner: below 1280px the logo keeps its full size and the nav keeps one line.
   Add another nav item and this number has to move again — measure, don't guess. */
@media (max-width: 1280px) { .brand-tag { display: none; } }
.nav { display: flex; align-items: center; gap: .4rem; }
.nav a {
  color: var(--slate); font-weight: 600; font-size: .95rem; padding: .5rem .8rem; border-radius: 8px;
  white-space: nowrap;
  transition: color .15s, background .15s;
}
.nav a:hover, .nav a[aria-current="page"] { color: var(--blue); background: var(--blue-soft); }
.nav .btn { margin-left: .4rem; padding: .6rem 1.1rem; }
.nav a.btn--primary { color:#fff; background:var(--blue); }
.nav a.btn--primary:hover { color:#fff; background:var(--blue-bright); }
.nav-toggle { display: none; min-width:44px; min-height:44px; align-items:center; justify-content:center; background: none; border: 0; cursor: pointer; padding: .4rem; color: var(--navy); }
.nav-toggle svg { width: 28px; height: 28px; }

/* ===== ABOUT US DROPDOWN (Anurag 2026-08-28) =====
   Hover or keyboard focus opens About ART / Infrastructure / Media under the
   "About Us" item. The ::after strip bridges the .5rem gap between item and
   panel so the pointer can travel down without the menu closing. On touch,
   browsers fire :hover on first tap when it reveals content, so the menu opens
   before the link navigates. The phone hamburger gets its own flat layout in
   the 64em block below — no hover, no double-tap. */
.nav-drop { position: relative; }
.nav-drop__cap { display: none; }
.nav-drop > a { display: inline-flex; align-items: center; gap: .28rem; }
.nav-drop > a svg { transition: transform .18s var(--ease); }
.nav-drop:hover > a svg, .nav-drop:focus-within > a svg { transform: rotate(180deg); }
.nav-drop.is-here > a { color: var(--blue); background: var(--blue-soft); }
.nav-drop::after { content: ""; position: absolute; left: 0; right: 0; top: 100%; height: .55rem; }
.nav-drop__menu {
  position: absolute; top: calc(100% + .5rem); left: 50%;
  transform: translate(-50%, -4px);
  min-width: 12.5rem; padding: .4rem; display: grid; gap: .15rem;
  background: #fff; border: 1px solid var(--line); border-radius: var(--r);
  box-shadow: var(--shadow); z-index: 40;
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity .18s var(--ease), transform .18s var(--ease), visibility 0s linear .18s;
}
.nav-drop:hover .nav-drop__menu, .nav-drop:focus-within .nav-drop__menu {
  opacity: 1; visibility: visible; pointer-events: auto;
  transform: translate(-50%, 0); transition-delay: 0s;
}
.nav-drop__menu a { display: block; }

/* 1024, not 900: with seven nav items the desktop bar no longer fits
   below ~1030px and the labels start wrapping. */
/* 64em, not 1024px — and they are the SAME 1024px for anyone on the default
   font size, so nothing changes for almost everyone. The nav's own width is in
   rem, so a reader who raises their browser's default font size gets a wider
   nav while a px breakpoint keeps insisting there is room: measured 176px of
   horizontal scroll at a 150% font size on a 1100px window. An em breakpoint
   is measured against the browser default, so the hamburger now appears when
   the nav actually outgrows the bar. Changed 2026-08-22. */
@media (max-width: 64em) {
  .nav-toggle { display: inline-flex; }
  .nav {
    position: fixed; inset: 78px 0 auto 0; flex-direction: column; align-items: stretch; gap: .2rem;
    background: #fff; padding: 1rem 1.2rem 1.4rem; border-bottom: 1px solid var(--line);
    box-shadow: var(--shadow); transform: translateY(-140%); visibility:hidden; pointer-events:none;
    transition: transform .3s var(--ease), visibility 0s linear .3s;
    max-height: calc(100vh - 78px); overflow-y: auto;
  }
  .nav[data-open="true"] { transform: translateY(0); visibility:visible; pointer-events:auto; transition-delay:0s; }
  .nav a { padding: .8rem .6rem; font-size: 1.05rem; border-bottom: 1px solid var(--mist); border-radius: 0; }
  .nav .btn { margin: .6rem 0 0; justify-content: center; }
  /* About Us group in the hamburger: hide the hover-only top link (it would
     duplicate "About ART" right under it), show a small caption instead, and
     lay the three pages flat and indented — everything stays one tap. */
  .nav-drop > a { display: none; }
  .nav-drop::after { display: none; }
  .nav-drop__cap {
    display: block; padding: .85rem .6rem .25rem; font-size: .74rem;
    font-weight: 800; letter-spacing: .08em; text-transform: uppercase;
    color: var(--slate); opacity: .75;
  }
  .nav-drop__menu {
    position: static; transform: none; opacity: 1; visibility: visible;
    pointer-events: auto; box-shadow: none; border: 0; border-radius: 0;
    padding: 0; min-width: 0; display: block;
  }
  .nav-drop__menu a { padding-left: 1.5rem; }
}

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

.skip-link {
  position:fixed; z-index:200; top:.75rem; left:.75rem; padding:.7rem 1rem;
  border-radius:var(--r-sm); background:#fff; color:var(--navy); font-weight:800;
  box-shadow:var(--shadow); transform:translateY(-160%); transition:transform .18s var(--ease);
}
.skip-link:focus { transform:translateY(0); }

/* ============ FOOTER ============ */
.site-footer { background: linear-gradient(160deg,var(--navy),var(--navy-2)); color: #b9c8de; padding: var(--s-8) 0 var(--s-5); }
.site-footer a { color: #cfe0f7; }
.site-footer a:hover { color: #fff; }
.footer-grid { display: grid; grid-template-columns: 1.4fr 1fr 1fr 1.2fr; gap: 2.4rem; }
.footer-grid img { height: 58px; width: auto; margin-bottom: 1rem; }
.footer-col { min-width: 0; }                 /* let the track shrink */
.footer-cat__act { min-width: 0; }
/* the only button on the site allowed to wrap: its label is a sentence */
.footer-cat__act .btn { white-space: normal; line-height: 1.3; }
.footer-col h2 { color: #fff; font-size: .8rem; letter-spacing: .14em; text-transform: uppercase; margin-bottom: 1rem; }
.footer-col ul { list-style: none; margin: 0; padding: 0; display: grid; gap: .6rem; font-size: .95rem; }
/* This grid's implicit track is `auto`, i.e. min-content — and the contact
   column holds two tokens with nowhere to break: www.artmechatronics.com and
   info@artmechatronics.com, both ~195px. So the track refused to go below
   195px and ALL SIX items rode out of the column together, spilling into the
   gutter from 900px down and pushing a real scrollbar at ~821px. Worse, the
   layout is rem-based while the 820px breakpoint is px, so a reader browsing
   at a larger default font size got 8px of sideways scroll at 1000px and 62px
   at the largest setting. Letting the tokens wrap fixes it at every root size,
   which moving the breakpoint could not. Measured 2026-08-22. */
.footer-col ul { grid-template-columns: minmax(0, 1fr); }
.footer-col ul li, .footer-col ul a { overflow-wrap: anywhere; }
/* Catalogue column — the portfolio block, collapsed to footer scale
   (Yash, 2026-08-07). Rotating ring is transform-only (M2) and stops for
   .lite and reduced motion (M4). */
.footer-cat__lead { color: #c9d5e7; font-size: .88rem; line-height: 1.55; margin: 0 0 1rem; max-width: 30ch; }
.footer-cat__act { display: grid; gap: .45rem; justify-items: start; }
/* the footer's pale link colour must not bleach a solid button (AA) */
.site-footer .footer-cat__act .btn--light { color: var(--navy); }
.site-footer .footer-cat__act .btn--light:hover { color: var(--navy); }
.footer-cat__act small { color: #c9d5e7; font-size: .74rem; }
.footer-flags { display: flex; gap: .5rem; margin-top: .8rem; font-size: .9rem; color: #c9d5e7; }
.footer-bottom {
  margin-top: var(--s-7); padding-top: var(--s-4); border-top: 1px solid rgba(255,255,255,.12);
  display: flex; flex-wrap: wrap; gap: .6rem 1.4rem; justify-content: space-between;
  font-size: .82rem; color: #8ea5c6;
}
@media (max-width: 820px){ .footer-grid { grid-template-columns: 1fr 1fr; gap: 1.8rem; } }
@media (max-width: 480px){ .footer-grid { grid-template-columns: 1fr; } }

/* ============ reveal-on-scroll ============ */
.reveal { opacity: 0; transform: translateY(22px); transition: opacity .7s var(--ease), transform .7s var(--ease); }
.reveal.in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  * { animation-duration: .001ms !important; animation-iteration-count: 1 !important; transition-duration: .001ms !important; }
  html { scroll-behavior: auto; }
  .reveal { opacity: 1; transform: none; }
}


/* utility */
.grid { display: grid; gap: 1.5rem; }
.mt-0{margin-top:0}.mb-0{margin-bottom:0}
.stack > * + * { margin-top: 1rem; }
.visually-hidden { position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0; }
:focus-visible { outline: 3px solid var(--blue-bright); outline-offset: 2px; border-radius: 4px; }

/* ============================================================
   Industry tile grid ("Industries We Serve")
   41 catalogue tiles: cover photo + name label on the image, with a
   caret that reveals the sub-industries beneath. Matches the layout
   Anurag approved in the ART Catalogue '26.
   ============================================================ */
.ig-head { text-align: center; margin: 0 0 .5rem; }
.ig-sub {
  text-align: center; color: var(--steel); max-width: 60ch;
  margin: 0 auto 2rem; font-size: var(--fs-small);
}
.ig-grid {
  display: grid; gap: .85rem;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
.ig-tile { position: relative; }
.ig-link {
  display: block; position: relative; overflow: hidden;
  border: 1px solid var(--line); border-radius: var(--r);
  background: var(--navy-2); text-decoration: none;
  transition: border-color .18s var(--ease), transform .18s var(--ease), box-shadow .18s var(--ease);
}
.ig-link:hover, .ig-link:focus-visible {
  border-color: var(--accent); transform: translateY(-2px);
  box-shadow: var(--shadow-sm); outline: none;
}
/* Covers are dark editorial stills, so the plate behind them is dark too —
   a light placeholder would flash white before the image paints. */
.ig-media { display: block; aspect-ratio: 4 / 3; background: var(--navy-2); }
.ig-media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ig-plate {
  display: block; width: 100%; height: 100%;
  background: linear-gradient(155deg, #1657b0 0%, #103f7e 55%, #0c3163 100%);
}
/* Awaiting its real cover photo — a faint diagonal sheen so the plate reads as
   an intentional placeholder rather than a failed image. */
.ig-plate::after {
  content: ""; display: block; width: 100%; height: 100%;
  background: linear-gradient(115deg, transparent 42%, rgba(255,255,255,.05) 50%, transparent 58%);
}
/* Name sits ON the photo, with a gradient so light images stay readable. */
.ig-name {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 1.6rem .6rem .5rem;
  font-size: .78rem; font-weight: 700; line-height: 1.2;
  color: var(--white); text-align: center; text-transform: uppercase;
  letter-spacing: .02em;
  /* A single long word cannot wrap, and .ig-link clips with overflow:hidden —
     so at 414px (iPhone XR/11/Plus) "CONFECTIONERY" was chopped to
     "CONFECTIONER", which reads as a different, complete word rather than as
     truncation. Hyphenate where the browser can, break the word where it
     cannot; either way nothing is silently hidden. Measured 2026-08-22. */
  hyphens: auto; overflow-wrap: break-word;
  /* The photo's own bottom strip is already dark, so this only needs to
     guarantee legibility, not manufacture contrast. */
  background: linear-gradient(to top, rgba(12,10,9,.82) 0%, rgba(12,10,9,.42) 60%, rgba(12,10,9,0) 100%);
}
.ig-caret {
  position: absolute; top: .4rem; right: .4rem; z-index: 2;
  width: 26px; height: 26px; border: 0; border-radius: 50%;
  background: var(--accent); color: var(--white);
  display: grid; place-items: center; cursor: pointer; padding: 0;
  box-shadow: 0 1px 4px rgba(0,0,0,.28);
  transition: transform .18s var(--ease), background .18s var(--ease);
}
.ig-caret svg { width: 12px; height: 12px; }
.ig-caret:hover { background: var(--blue-bright); }
.ig-caret[aria-expanded="true"] { transform: rotate(180deg); }
.ig-caret:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
.ig-drop {
  position: absolute; z-index: 5; top: calc(100% - .4rem); left: 0; right: 0;
  background: var(--white); border: 1px solid var(--line);
  border-radius: var(--r); box-shadow: var(--shadow);
  padding: .6rem; max-height: 260px; overflow-y: auto;
}
.ig-drop__h {
  margin: 0 0 .4rem; font-size: .62rem; font-weight: 700;
  letter-spacing: .09em; text-transform: uppercase; color: var(--steel);
}
.ig-drop ul { list-style: none; margin: 0; padding: 0; display: grid; gap: .1rem; }
/* Small accent dot before each entry, matching the pattern Anurag knows. */
.ig-dot {
  width: 5px; height: 5px; border-radius: 50%; background: var(--accent);
  flex: 0 0 5px;
}
.ig-drop a, .ig-drop__plain { display: flex; align-items: center; gap: .5rem; }
/* Sub-categories with no page of their own: shown, but not a dead link. */
.ig-drop__plain { padding: .32rem .4rem; font-size: .78rem; color: var(--steel); line-height: 1.3; }
.ig-drop a {
  padding: .32rem .4rem; border-radius: var(--r-sm);
  font-size: .78rem; color: var(--ink); text-decoration: none; line-height: 1.3;
}
.ig-drop a:hover, .ig-drop a:focus-visible {
  background: var(--blue-soft); color: var(--accent-ink); outline: none;
}
@media (max-width: 640px) {
  .ig-grid { grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); gap: .6rem; }
  /* .8rem = 12.8px — .68rem measured 10.9px on a phone, which is squint
     territory for what is effectively primary navigation */
  .ig-name { font-size: .8rem; padding: 1.3rem .4rem .4rem; }
}
@media (prefers-reduced-motion: reduce) {
  .ig-link, .ig-caret { transition: none; }
  .ig-link:hover { transform: none; }
}

/* ============================================================
   Markets band — large landscape flags on a full-bleed strip that
   travels left to right. Two tracks shift by one track width so the
   loop is seamless.
   ============================================================ */
.mk-section { text-align: center; overflow: hidden; }
.mk-sub { color: var(--steel); max-width: 62ch; margin: .6rem auto 0; font-size: var(--fs-small); }
/* break out of .wrap so the strip runs edge to edge */
.mk-marquee {
  margin: 2.6rem 0 0; width: 100%; overflow: hidden; display: flex;
  -webkit-mask-image: linear-gradient(to right, transparent, #000 6%, #000 94%, transparent);
          mask-image: linear-gradient(to right, transparent, #000 6%, #000 94%, transparent);
}
.mk-track {
  list-style: none; margin: 0;
  /* padding-right MUST equal the gap: it is the space across the seam between
     the two tracks. Any mismatch shows up as a stutter once per loop. */
  padding: 0 5rem 0 0;
  display: flex; align-items: center; gap: 5rem; flex: 0 0 auto;
  /* 25 flags is roughly four times the old track, so the duration scales
     with it — the on-screen speed stays the same, it just takes longer to lap. */
  animation: mk-scroll 150s linear infinite;
  /* keep the whole strip on its own compositor layer so each frame is a GPU
     transform rather than a repaint of twelve shadowed flags */
  will-change: transform;
  backface-visibility: hidden;
}
@keyframes mk-scroll {          /* travels left -> right */
  from { transform: translate3d(-100%, 0, 0); }
  to   { transform: translate3d(0, 0, 0); }
}
.mk-item { display: flex; flex-direction: column; align-items: center; justify-content: flex-end; gap: .8rem; }
.mk-flag {
  /* per-flag dimensions come from the generator (equal-area sizing); the scale
     var shrinks the whole set on small screens without touching the ratios */
  width: calc(var(--fw) * var(--mk-scale, 1));
  height: calc(var(--fh) * var(--mk-scale, 1));
  display: block;
  border-radius: 4px;
  box-shadow: 0 6px 20px rgba(11,44,94,.18), 0 0 0 1px rgba(16,35,63,.08);
}
.mk-name {
  font-weight: 700; font-size: .95rem; letter-spacing: .02em;
  color: var(--ink); white-space: nowrap;
}
@media (max-width: 720px) {
  .mk-marquee { --mk-scale: .68; }
  .mk-track { gap: 3rem; }
  .mk-name { font-size: .82rem; }
}
/* Motion here is decorative — hold it still if the visitor asked for less. */
@media (prefers-reduced-motion: reduce) {
  .mk-track { animation: none; }
  .mk-marquee { justify-content: center; flex-wrap: wrap;
    -webkit-mask-image: none; mask-image: none; }
  .mk-track { flex-wrap: wrap; justify-content: center; padding-right: 0; }
  .mk-track + .mk-track { display: none; }
}

/* ============================================================
   Machine-category grid — the second browse axis.
   Lives in base.css deliberately: it renders on the homepage
   (home.css) AND on the 10 category pages (catalog.css), so a
   page-specific stylesheet would leave one of them unstyled.
   ============================================================ */
.mc-sub { color: var(--steel); max-width: 62ch; margin: .5rem 0 0; }

.mc-grid {
  display: grid; gap: .9rem; margin-top: 2.2rem;
  grid-template-columns: repeat(5, 1fr);
}
.mc-tile {
  position: relative; display: flex; flex-direction: column;
  border-radius: var(--r-lg); overflow: hidden;
  border: 1px solid var(--line); background: var(--white);
  text-decoration: none; color: inherit;
  transition: transform .18s var(--ease), box-shadow .18s var(--ease), border-color .18s var(--ease);
}
.mc-tile:hover, .mc-tile:focus-visible {
  transform: translateY(-3px); border-color: var(--accent);
  box-shadow: var(--shadow-sm); outline: none;
}
.mc-tile__media { display: block; position: relative; aspect-ratio: 4 / 3; overflow: hidden;
  background: linear-gradient(150deg, var(--mc-from, var(--navy)), var(--mc-to, var(--blue))); }
.mc-tile__media img, .mc-tile__media picture { display: block; width: 100%; height: 100%; object-fit: cover; }
/* No tint overlay. It existed to unify ten product photos shot on different
   grounds; the covers are already composed on one ART navy, so multiplying a
   category gradient over them re-introduced exactly the rainbow the single-navy
   decision removed — heating read sepia, cleaning read green. The gradient
   stays only as the placeholder BEHIND a loading image. */
.mc-tile__body { display: block; padding: .85rem .9rem 1rem; }
.mc-tile__name { display: block; font-weight: 700; font-size: .92rem; line-height: 1.3; color: var(--ink); }
.mc-tile__count { display: block; margin-top: .25rem; font-size: .76rem; color: var(--steel); }

@media (max-width: 1100px) { .mc-grid { grid-template-columns: repeat(4, 1fr); } }
@media (max-width: 860px)  { .mc-grid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 620px)  { .mc-grid { grid-template-columns: repeat(2, 1fr); gap: .7rem; } }
@media (prefers-reduced-motion: reduce) {
  .mc-tile { transition: none; }
  .mc-tile:hover, .mc-tile:focus-visible { transform: none; }
}

/* --- machine-category tile: caret dropdown (mirrors the industry tile) --- */
.mc-tile-wrap { position: relative; display: block; }
.mc-caret {
  position: absolute; top: .5rem; right: .5rem; z-index: 2;
  width: 30px; height: 30px; border: 0; border-radius: 50%;
  display: grid; place-items: center; cursor: pointer;
  background: rgba(255,255,255,.92); color: var(--navy);
  box-shadow: 0 2px 8px rgba(11,44,94,.22);
  transition: transform .18s var(--ease), background .18s var(--ease);
}
.mc-caret svg { width: 13px; height: 13px; }
.mc-caret:hover { background: #fff; }
.mc-caret[aria-expanded="true"] { transform: rotate(180deg); background: var(--accent); color: #fff; }
.mc-drop {
  position: absolute; z-index: 5; left: .5rem; right: .5rem; top: calc(100% - .6rem);
  background: #fff; border: 1px solid var(--line); border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg, 0 20px 44px rgba(11,44,94,.18));
  padding: .7rem .55rem .55rem;
}
.mc-drop__h { margin: 0 .35rem .45rem; font-size: .72rem; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--steel); }
.mc-drop ul { list-style: none; margin: 0; padding: 0; }
.mc-drop li a {
  display: flex; align-items: center; gap: .5rem;
  padding: .38rem .35rem; border-radius: 7px;
  font-size: .82rem; color: var(--ink); text-decoration: none;
}
.mc-drop li a:hover { background: var(--blue-soft); color: var(--blue); }
.mc-drop li a em { margin-left: auto; font-style: normal; font-size: .72rem; color: var(--steel); }
.mc-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--accent); flex: 0 0 5px; }
.mc-drop__all {
  display: block; margin-top: .4rem; padding: .45rem .35rem 0;
  border-top: 1px solid var(--line);
  font-size: .78rem; font-weight: 700; color: var(--blue); text-decoration: none;
}
.mc-drop__all:hover { text-decoration: underline; }

/* --- category page: jump bar + grouped sections --- */
.cg-jump { display: flex; flex-wrap: wrap; gap: .45rem; margin-bottom: 2rem; }
.cg-jump a {
  display: inline-flex; align-items: center; gap: .4rem;
  padding: .4rem .75rem; border: 1px solid var(--line); border-radius: 999px;
  font-size: .82rem; font-weight: 600; color: var(--slate); text-decoration: none;
  background: var(--white);
}
.cg-jump a:hover { border-color: var(--accent); color: var(--blue); background: var(--blue-soft); }
.cg-jump a em { font-style: normal; font-size: .72rem; color: var(--steel); }

.cg-group { margin-bottom: 2.6rem; scroll-margin-top: 96px; }  /* clear the sticky header on anchor jump */
.cg-group__h {
  display: flex; align-items: baseline; gap: .6rem;
  margin: 0 0 1rem; padding-bottom: .5rem;
  border-bottom: 1px solid var(--line);
  font-size: 1.15rem;
}
.cg-group__h span { font-size: .8rem; font-weight: 600; color: var(--steel); }
.cg-ask { color: var(--steel); font-size: .92rem; }

@media (max-width: 620px) {
  .mc-drop { left: .35rem; right: .35rem; }
  .mc-drop li a { padding: .5rem .35rem; font-size: .86rem; }  /* bigger tap target */
}

/* M4 kill switch, global half. Set by js/layout.js before the header is
   injected. Removes the sticky bar's per-frame backdrop blur — the one
   backdrop-filter that runs on every page — and swaps the translucent ground
   for a solid one, so nothing loses contrast. */
.lite .site-header { background: #fff; backdrop-filter: none; }

/* The manual half of M4: a labelled toggle the visitor can throw themselves. */
.lite-toggle {
  font: inherit; font-size: .78rem; font-weight: 700; color: #cfe0f7;
  background: none; border: 1px solid rgba(255,255,255,.28);
  border-radius: var(--r-sm); padding: .35rem .7rem; min-height: 34px;
  cursor: pointer; transition: color .18s var(--ease), border-color .18s var(--ease);
}
.lite-toggle:hover { color: #fff; border-color: rgba(255,255,255,.6); }
.lite-toggle[aria-pressed="true"] { color: #fff; border-color: #fff; }

/* ============================================================
   MOBILE TAP TARGETS — 2026-08-01, from a whole-site audit at 375px.

   .ig-caret is a 26x26 button and .mc-caret 30x30. On the homepage alone that
   is 44 controls a thumb has to hit, all well under the 44px minimum. Both are
   real buttons that open a dropdown, so this is a usability defect, not a
   cosmetic one.

   The fix extends the HIT AREA without touching how they look: a transparent
   pseudo-element centred on the button, sized 44x44. The visual caret stays
   26/30px, so the design is unchanged and nothing reflows.

   It must be ::after on the pseudo-element rather than padding or min-height on
   the button, because .ig-caret[aria-expanded="true"] animates the BUTTON with
   transform: rotate(180deg) — growing the button box would move the rotation
   centre and make the caret wobble as it flips.
   ============================================================ */
@media (max-width: 900px) {
  .ig-caret, .mc-caret { position: relative; }
  .ig-caret { position: absolute; }        /* keep its own positioning context */
  .ig-caret::after, .mc-caret::after {
    content: "";
    position: absolute; top: 50%; left: 50%;
    width: 44px; height: 44px;
    transform: translate(-50%, -50%);
  }
}

/* ============================================================
   INDUSTRY DROPDOWN ON A PHONE — 2026-08-01.

   Opened on a 375px screen the panel was trapped inside one 164px grid column:
   ten navigation links at .78rem in rows 26px tall, and long names like
   "Silver Coated Flavoured Cardomom" wrapping to three lines. Those are real
   destinations — 88 industries each carry one — so they need thumb-sized rows
   and room to sit on one line.

   The panel now spans BOTH grid columns on a phone. `right: auto` with an
   explicit width is what does it: the base rule pins left:0/right:0 to the
   tile, so simply widening would be clipped to the tile's column.
   ============================================================ */
@media (max-width: 620px) {
  .ig-drop {
    right: auto;
    width: calc(200% + 0.9rem);       /* two columns plus the grid gap */
    max-height: 60vh;
    padding: .7rem;
  }
  /* The right-hand tiles must open LEFTWARDS or the panel runs off-screen.
     The grid class is .ig-grid — an earlier attempt used .industry-grid, which
     matched zero elements and let the right column overflow by 159px. */
  .ig-grid > .ig-tile:nth-child(even) .ig-drop { left: auto; right: 0; }

  .ig-drop a, .ig-drop__plain {
    min-height: 44px; padding: .4rem .5rem;
    font-size: .95rem; line-height: 1.35;
  }
  .ig-drop__h { font-size: .72rem; margin-bottom: .5rem; }
}

/* ============================================================
   WHY ART — numbered cards, two columns. Shared by the homepage and the
   About page (same fourteen catalogue points on both).
   ============================================================ */
.wa-cards {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: 1fr 1fr; gap: 1rem 1.2rem;
}
.wa-cards li {
  display: flex; gap: 1rem; align-items: flex-start;
  background: var(--white); border: 1px solid var(--line); border-radius: var(--r-lg);
  padding: 1.15rem 1.25rem;
}
.wa-cards li > span {
  flex: 0 0 auto; width: 44px; height: 44px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--blue-soft); color: var(--blue);
  font-family: var(--font-mono); font-size: .86rem; font-weight: 700;
}
.wa-cards p { margin: .35rem 0 0; font-size: .95rem; line-height: 1.55; color: var(--slate); }
.wa-cards p b { color: var(--navy); }
@media (max-width: 800px) { .wa-cards { grid-template-columns: 1fr; } }

/* Section heads, centred (client, 2026-08-16): the small label became the
   heading and the old descriptive heading became this subtitle beneath it. */
.sec-head__sub {
  margin: .55rem auto 0; max-width: 60ch;
  color: var(--slate); font-size: 1.02rem; line-height: 1.6;
}
.sec-head.center .sec-head__sub { margin-inline: auto; }
.section--navy .sec-head__sub, .net-band .sec-head__sub { color: #c8d6e9; }

/* ============================================================
   TILE POPUP (client, 2026-08-16). Desktop: a centred card. Phone: a
   full-screen sheet, because a tile can hold 23 sub-categories and each one
   needs a 44px tap target.
   ============================================================ */
.tile-modal {
  border: 0; padding: 0; background: transparent;
  max-width: min(92vw, 640px); width: 100%;
  color: var(--ink);
}
.tile-modal--wide { max-width: min(94vw, 860px); }
.tile-modal::backdrop { background: rgba(8, 27, 58, .62); }
.tile-modal__card {
  background: var(--white); border-radius: var(--r-lg);
  box-shadow: 0 28px 70px -20px rgba(8,27,58,.55);
  padding: 1.5rem 1.6rem 1.6rem;
  max-height: 86vh; display: flex; flex-direction: column;
}
.tile-modal__head { display: flex; align-items: flex-start; gap: 1rem; margin-bottom: 1rem; }
.tile-modal__title { margin: 0; font-size: 1.5rem; line-height: 1.2; color: var(--navy); }
.tile-modal__x {
  margin-left: auto; flex: 0 0 auto;
  width: 44px; height: 44px; border-radius: 50%;
  border: 1px solid var(--line); background: var(--white); color: var(--slate);
  display: grid; place-items: center; cursor: pointer;
  transition: background .18s var(--ease), color .18s var(--ease);
}
.tile-modal__x svg { width: 20px; height: 20px; }
.tile-modal__x:hover { background: var(--blue-soft); color: var(--navy); }
.tile-modal__x:focus-visible { outline: 3px solid var(--blue-bright); outline-offset: 2px; }
.tile-modal__body { overflow-y: auto; -webkit-overflow-scrolling: touch; }
.tile-modal__body ul { list-style: none; margin: 0; padding: 0; display: grid; gap: .15rem; }
.tile-modal--wide .tile-modal__body ul { grid-template-columns: 1fr 1fr; gap: .15rem 1.2rem; }
.tile-modal__body li a {
  display: flex; align-items: center; gap: .6rem;
  min-height: 44px; padding: .35rem .6rem;
  border-radius: var(--r-sm);
  color: var(--ink); font-size: .98rem; line-height: 1.3;
  transition: background .16s var(--ease), color .16s var(--ease);
}
.tile-modal__body li a:hover { background: var(--blue-soft); color: var(--blue); }
.tile-modal__body li a:focus-visible { outline: 3px solid var(--blue-bright); outline-offset: -1px; }
/* Client, 2026-08-18: the "View the full page" button is gone, so this list IS
   the way into a category. Every row was already a real link, but at rest it
   read as plain text, so nobody would know to tap it. The chevron is drawn with
   borders rather than a glyph so it needs no font and scales with the row. */
.tile-modal__body li a::after {
  content: ""; flex: 0 0 auto; margin-left: auto;
  width: 7px; height: 7px;
  border-right: 2px solid var(--steel); border-top: 2px solid var(--steel);
  transform: rotate(45deg);
  transition: transform .16s var(--ease), border-color .16s var(--ease);
}
.tile-modal__body li a:hover::after,
.tile-modal__body li a:focus-visible::after { border-color: var(--blue); transform: rotate(45deg) translate(1px, -1px); }
.tile-modal__body .ig-dot, .tile-modal__body .mc-dot {
  flex: 0 0 auto; width: 7px; height: 7px; border-radius: 50%; background: var(--accent);
}
.tile-modal__body li a em { margin-left: .5rem; font-style: normal; color: var(--steel); font-size: .86rem; }

@media (max-width: 640px) {
  /* full-screen sheet */
  .tile-modal, .tile-modal--wide { max-width: 100vw; width: 100vw; height: 100dvh; max-height: none; }
  .tile-modal__card { height: 100dvh; max-height: none; border-radius: 0; padding: 1.1rem 1.1rem 1.4rem; }
  .tile-modal--wide .tile-modal__body ul { grid-template-columns: 1fr; }
}
@media (prefers-reduced-motion: no-preference) {
  .tile-modal[open] { animation: tm-in .18s var(--ease); }
  @keyframes tm-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
}
.tile-modal__lead {
  /* No negative top margin: the body is the scroll container (overflow-y:
     auto), so pulling the first line upward pushed the top ~5px of the
     capitals past the clip edge — the founder saw the letters cut. */
  margin: 0 0 .85rem; font-size: .78rem; font-weight: 700;
  letter-spacing: .13em; text-transform: uppercase; color: var(--steel);
}

/* ============================================================
   CONTACT US BUTTON — a circle in the bottom-right corner that unfolds
   upward into one brand-coloured circle per destination.
   Founder, 2026-08-18: this replaces the share rail that used to sit on the
   left edge. Same five destinations, same unfolding, new corner and a
   "Contact Us" label that rides alongside the button on hover.
   Only transform/opacity animate, and the whole thing is fixed, so nothing
   on the page ever reflows because of it.
   ============================================================ */
.cfab {
  position: fixed; z-index: 60;
  right: 18px;
  /* env() keeps it clear of the iPhone home-gesture bar; the max() means it
     still gets its 18px on every browser that has no safe-area inset. */
  bottom: max(18px, env(safe-area-inset-bottom, 0px));
  display: flex; flex-direction: column; align-items: flex-end;
}
/* The panel is ABSOLUTE, anchored above the button, and it stays out of the
   widget's flow. Learned the hard way on the left rail: circles that are only
   transformed away still occupy layout, which left the visible tab 145px off
   centre. Out of flow, the widget IS the button, so the button is what the
   corner positions — and the circles fly up out of it. */
.cfab__panel {
  position: absolute; right: 0; bottom: calc(100% + 10px);
  display: flex; flex-direction: column; align-items: flex-end; gap: .5rem;
}
.cfab__row, .cfab__btn {
  position: relative;
  border: 0; cursor: pointer; text-decoration: none;
  display: grid; place-items: center;
  color: #fff;
  box-shadow: 0 6px 18px rgba(8, 27, 58, .28);
}
.cfab__ic { display: grid; place-items: center; }
.cfab__ic svg { width: 21px; height: 21px; }

/* --- the destinations --- */
.cfab__row {
  width: 46px; height: 46px; border-radius: 50%;
  /* collapsed: tucked into the button and unclickable, never merely invisible */
  transform: scale(.4) translateY(28px); opacity: 0; pointer-events: none;
  transition: transform .28s var(--ease), opacity .22s var(--ease);
}
.cfab--open .cfab__row { transform: none; opacity: 1; pointer-events: auto; }
/* staggered so they read as one movement rather than five (30-40ms apart) */
.cfab--open .cfab__row:nth-child(1) { transition-delay: .00s; }
.cfab--open .cfab__row:nth-child(2) { transition-delay: .04s; }
.cfab--open .cfab__row:nth-child(3) { transition-delay: .08s; }
.cfab--open .cfab__row:nth-child(4) { transition-delay: .12s; }
.cfab--open .cfab__row:nth-child(5) { transition-delay: .16s; }

.cfab__row--linkedin  { background: #0A66C2; }
.cfab__row--instagram { background: linear-gradient(45deg,#F58529,#DD2A7B 50%,#8134AF); }
.cfab__row--facebook  { background: #1877F2; }
.cfab__row--line      { background: #06C755; }
.cfab__row--whatsapp  { background: #25D366; }
/* Dialer green (Yash, 2026-08-28): deeper than the three chat greens
   (WhatsApp #25D366, LINE #06C755, WeChat #07C160) so Call reads as the
   phone button, not another messenger. Same green in the region picker. */
.cfab__row--call      { background: #00A24C; }
.cfab__row:hover, .cfab__row:focus-visible { filter: brightness(1.12); }
.cfab__row:focus-visible, .cfab__btn:focus-visible { outline: 3px solid var(--blue-bright); outline-offset: 3px; }

/* --- the name / "Contact Us" label, to the LEFT of its circle --- */
.cfab__tip {
  position: absolute; right: calc(100% + 10px); top: 50%;
  transform: translate(6px, -50%);
  background: var(--navy); color: #fff;
  padding: .32rem .6rem; border-radius: var(--r-sm);
  font-size: .78rem; font-weight: 700; white-space: nowrap;
  opacity: 0; pointer-events: none;
  transition: opacity .18s var(--ease), transform .18s var(--ease);
}
.cfab__row:hover .cfab__tip, .cfab__row:focus-visible .cfab__tip,
.cfab__btn:hover .cfab__tip--btn, .cfab__btn:focus-visible .cfab__tip--btn { opacity: 1; transform: translate(0, -50%); }

/* --- the button itself --- */
.cfab__btn {
  width: 56px; height: 56px; border-radius: 50%;
  background: var(--blue);
  transition: background .18s var(--ease), transform .18s var(--ease);
}
.cfab__btn:hover { background: var(--blue-bright); }
.cfab__btn:active { transform: scale(.96); }
.cfab__chat { width: 26px; height: 26px; }
.cfab__closeic {
  position: absolute; width: 22px; height: 22px;
  opacity: 0; transform: rotate(-90deg);
  transition: opacity .2s var(--ease), transform .2s var(--ease);
}
.cfab__chat { transition: opacity .2s var(--ease), transform .2s var(--ease); }
.cfab--open .cfab__chat { opacity: 0; transform: rotate(90deg); }
.cfab--open .cfab__closeic { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .cfab__row, .cfab__tip, .cfab__chat, .cfab__closeic, .cfab__btn { transition: none; }
}

/* Touch: hover means nothing, so only the tap toggle opens it, and the labels
   are dropped — a tooltip that needs a hover is dead weight on a phone. */
@media (max-width: 900px) {
  .cfab { right: 14px; bottom: max(14px, env(safe-area-inset-bottom, 0px)); }
  .cfab__btn { width: 52px; height: 52px; }
  .cfab__row { width: 44px; height: 44px; }
  .cfab__tip { display: none; }
}

/* ============================================================
   WECHAT — the QR panel opened from the Contact Us button, and the block on
   the Contact page. WeChat has no profile URL a browser can open, so the code
   itself is the contact detail; everything here exists to make it scannable.
   ============================================================ */
.cfab__row--wechat { background: #07C160; }

.qrmodal { border: 0; padding: 0; background: transparent; max-width: 92vw; }
.qrmodal::backdrop { background: rgba(8, 27, 58, .62); }
.qrmodal__card {
  position: relative; background: #fff; border-radius: var(--r-lg);
  padding: 1.6rem 1.5rem 1.5rem; text-align: center; width: min(92vw, 340px);
  box-shadow: 0 24px 60px rgba(8, 27, 58, .3);
}
.qrmodal__x {
  position: absolute; top: .6rem; right: .6rem;
  width: 38px; height: 38px; border-radius: 50%;
  border: 1px solid var(--line); background: #fff; color: var(--steel);
  display: grid; place-items: center; cursor: pointer;
}
.qrmodal__x svg { width: 18px; height: 18px; }
.qrmodal__x:hover { background: var(--blue-soft); color: var(--navy); }
.qrmodal__x:focus-visible { outline: 3px solid var(--blue-bright); outline-offset: 2px; }
.qrmodal__h { margin: 0 0 .9rem; font-size: 1.15rem; color: var(--navy); }
/* A QR has to stay crisp and square or it stops scanning — never let it be
   stretched by a flexible parent, and keep it on a white ground. */
.qrmodal__qr {
  display: block; width: 260px; max-width: 100%; height: auto; aspect-ratio: 1;
  margin: 0 auto; background: #fff; border: 1px solid var(--line); border-radius: var(--r-sm);
  image-rendering: crisp-edges;
}
.qrmodal__note { margin: .9rem 0 0; color: var(--steel); font-size: .88rem; line-height: 1.5; }
.qrmodal__go { margin-top: 1rem; display: inline-block; }

/* the block on the Contact page */
.wechat-block { display: flex; gap: 1.2rem; align-items: center; flex-wrap: wrap; }
.wechat-block img {
  width: 168px; height: 168px; flex: 0 0 auto; background: #fff;
  border: 1px solid var(--line); border-radius: var(--r-sm); image-rendering: crisp-edges;
}
.wechat-block__tx { min-width: 0; }
.wechat-block__tx h3 { margin: 0 0 .3rem; font-size: 1rem; color: var(--navy); }
.wechat-block__tx p { margin: 0; color: var(--steel); font-size: .9rem; line-height: 1.55; max-width: 42ch; }

/* ============================================================
   /projects — the gallery viewer and the masonry grid.
   These live in base.css, NOT home.css: shell() does not load home.css, so the
   projects page had none of these styles and rendered as raw images at their
   attribute size. Anything the projects page needs belongs here.
   ============================================================ */
/* --- the gallery --- */
.gal { border: 0; padding: 0; background: transparent; width: 100vw; max-width: 100vw; height: 100dvh; max-height: 100dvh; }
.gal::backdrop { background: rgba(6, 20, 42, .93); }
.gal__stage { position: relative; width: 100%; height: 100%; display: grid; place-items: center; }
.gal__img {
  max-width: min(94vw, 1400px); max-height: 78dvh; width: auto; height: auto;
  object-fit: contain; border-radius: var(--r); background: #0b1c33;
}
.gal__x, .gal__nav {
  position: absolute; width: 48px; height: 48px; border-radius: 50%;
  border: 1px solid rgba(255,255,255,.35); background: rgba(10,25,48,.55); color: #fff;
  display: grid; place-items: center; cursor: pointer;
  transition: background .18s var(--ease), border-color .18s var(--ease);
}
.gal__x svg, .gal__nav svg { width: 22px; height: 22px; }
.gal__x:hover, .gal__nav:hover { background: var(--blue); border-color: #fff; }
.gal__x:focus-visible, .gal__nav:focus-visible { outline: 3px solid #fff; outline-offset: 2px; }
.gal__x { top: 18px; right: 18px; }
.gal__nav--prev { left: 18px; top: 50%; transform: translateY(-50%); }
.gal__nav--next { right: 18px; top: 50%; transform: translateY(-50%); }
.gal__bar {
  position: absolute; left: 0; right: 0; bottom: 22px;
  display: flex; align-items: baseline; justify-content: center; gap: 1.2rem;
  padding: 0 1.2rem; color: #fff; flex-wrap: wrap;
}
.gal__tx { display: grid; gap: .15rem; text-align: center; }
.gal__t { font-size: 1.02rem; }
.gal__c { font-size: .76rem; letter-spacing: .1em; text-transform: uppercase; color: #bdd7f6; }
.gal__n { font-family: var(--font-mono); font-size: .8rem; color: #bdd7f6; }

@media (max-width: 620px) {
  .gal__img { max-width: 96vw; max-height: 68dvh; }
  .gal__nav { width: 44px; height: 44px; }
  .gal__nav--prev { left: 8px; } .gal__nav--next { right: 8px; }
  .gal__bar { bottom: 14px; gap: .5rem; }
}

/* --- /projects: masonry, filters (client, 2026-08-21) --- */
.pj-head { padding-bottom: 0; }
.pj-filters { display: flex; flex-wrap: wrap; gap: .5rem; justify-content: center; margin-top: 1.4rem; }
@media (max-width: 640px) {
  /* stacked, the 8 pills ran 200px tall and pushed the first photograph below
     the fold on a page whose whole job is showing photographs. One swipeable
     row costs nothing: the pills keep their 44px height and stay reachable. */
  .pj-filters {
    flex-wrap: nowrap; justify-content: flex-start; overflow-x: auto;
    -webkit-overflow-scrolling: touch; scrollbar-width: none;
    margin-inline: -1.2rem; padding-inline: 1.2rem;
  }
  .pj-filters::-webkit-scrollbar { display: none; }
  .pj-filter { flex: 0 0 auto; }
}
.pj-filter {
  min-height: 44px; padding: .55rem 1rem; border-radius: 99px; cursor: pointer;
  border: 1px solid var(--line); background: var(--white); color: var(--steel);
  font: inherit; font-size: .86rem; font-weight: 700;
  transition: background .16s var(--ease), color .16s var(--ease), border-color .16s var(--ease);
}
.pj-filter:hover { border-color: var(--blue); color: var(--blue); }
.pj-filter.is-on { background: var(--blue); border-color: var(--blue); color: #fff; }
.pj-filter:focus-visible { outline: 3px solid var(--blue-bright); outline-offset: 2px; }

.pj-sec { padding-top: 1.8rem; }
/* CSS columns, not grid: the photographs are portrait AND landscape, and columns
   let each keep its own height instead of being cropped into a uniform cell —
   which is the whole point of showing them properly. */
.pj-grid { list-style: none; margin: 0; padding: 0; columns: 4; column-gap: 1.1rem; }
.pj-item { break-inside: avoid; margin: 0 0 1.1rem; }
.pj-hit {
  display: block; width: 100%; padding: 0; border: 1px solid var(--line); cursor: pointer;
  background: var(--white); border-radius: var(--r-lg); overflow: hidden; text-align: left;
  font: inherit; color: inherit;
  transition: box-shadow .18s var(--ease), border-color .18s var(--ease), transform .18s var(--ease);
}
.pj-hit:hover { box-shadow: var(--shadow-sm); border-color: var(--accent); transform: translateY(-2px); }
.pj-hit:focus-visible { outline: 3px solid var(--blue-bright); outline-offset: 2px; }
/* the ratio is declared inline per photo, so the column does not reflow as
   images arrive — height:auto keeps the width/height attributes from fixing it */
.pj-hit img { display: block; width: 100%; height: auto; }
/* the tile is the photograph and nothing else (client, 2026-08-21) */
.pj-hit { line-height: 0; }
.pj-empty { text-align: center; color: var(--steel); padding: 2rem 0; }

@media (max-width: 1100px) { .pj-grid { columns: 3; } }
@media (max-width: 780px)  { .pj-grid { columns: 2; } }
@media (max-width: 460px)  { .pj-grid { columns: 1; } }
