/* ==========================================================================
   COMPONENTS — reusable building blocks
   ========================================================================== */

/* ---- Scroll progress bar ------------------------------------------------- */
.scroll-progress {
  position: fixed;
  top: 0;
  left: 0;
  height: 3px;
  width: 0%;
  background: var(--ink);
  z-index: var(--z-progress);
  transition: width 0.1s linear;
}

/* ---- Custom cursor -------------------------------------------------------- */
.pixel-cursor {
  position: fixed;
  top: 0;
  left: 0;
  width: 24px;
  height: 24px;
  pointer-events: none;
  z-index: var(--z-cursor);
  will-change: left, top;
  display: none;
}
.has-custom-cursor .pixel-cursor {
  display: block;
}
.pixel-cursor.is-pressed {
  transform: scale(0.85);
}

/* Cascade reveal (fade-up + scale-in on scroll). Declared here, ahead of
   every hover:transform rule below, on purpose: `.reveal.is-visible` and
   e.g. `.u-hard:hover` both set `transform` at equal specificity (two
   classes vs. one class + one pseudo-class), so it's source order that
   decides the tie — if this block came after them, a hovered, already-
   revealed card would freeze at `.reveal.is-visible`'s translateY(0)
   scale(1) and never collapse into its shadow. */
.reveal {
  opacity: 0;
  transform: translateY(24px) scale(0.98);
  transition:
    opacity var(--dur-med) ease,
    transform var(--dur-med) ease;
}
.reveal.is-visible {
  opacity: 1;
  transform: translateY(0) scale(1);
}

/* Pixel-style arrow icon, replaces plain "→" text site-wide in buttons/CTAs. */
.btn-arrow {
  /* Global reset makes svg display:block by default, which would force
     this onto its own line inside plain-text contexts like
     .service-card__link / .cta-banner__text (not an issue inside .btn,
     a flex container, but needed everywhere else). */
  display: inline-block;
  width: 12px;
  height: 12px;
  fill: currentColor;
  flex-shrink: 0;
  margin-left: 6px;
  vertical-align: -1px;
}

/* ---- Buttons ---------------------------------------------------------------
   Hard-brutalist button: thick outline, zero-blur offset shadow, hover
   "presses" the shadow flat and nudges the button into its own shadow. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-1);
  font-family: var(--font-body);
  font-size: var(--fs-small);
  font-weight: 700;
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 18px 32px;
  min-height: 48px;
  border: var(--border-w) solid var(--ink);
  background: var(--ink);
  color: var(--ink-inverse);
  box-shadow: var(--shadow-hard);
  transition:
    transform var(--dur-fast) ease,
    box-shadow var(--dur-fast) ease,
    background var(--dur-fast) ease,
    color var(--dur-fast) ease;
  cursor: pointer;
  white-space: nowrap;
}
.btn:hover,
.btn:focus-visible {
  transform: translate(var(--shadow-off), var(--shadow-off));
  box-shadow: var(--shadow-collapse);
}
.btn--primary {
  background: var(--ink);
  color: var(--ink-inverse);
}
.btn--primary:hover,
.btn--primary:focus-visible {
  background: var(--bg-primary);
  color: var(--ink);
}
.btn--outline {
  background: transparent;
  color: var(--ink);
  box-shadow: none;
}
.btn--outline:hover,
.btn--outline:focus-visible {
  transform: none;
  background: var(--ink);
  color: var(--ink-inverse);
}
.btn--large {
  padding: 20px 40px;
  font-size: var(--fs-body);
}
.btn--full {
  width: 100%;
}

/* Inverted context (used inside black sections): shadow + hover flip colors. */
.btn--on-inverse {
  background: var(--ink-inverse);
  color: var(--ink);
  border-color: var(--ink-inverse);
  box-shadow: var(--shadow-hard-inverse);
}
.btn--on-inverse:hover,
.btn--on-inverse:focus-visible {
  background: var(--accent);
  color: var(--ink-inverse);
  border-color: var(--accent);
  /* Collapse to zero offset like every other hard-shadow hover — same
     accent tint, but the shadow used to keep its 6px offset while the
     button ALSO translated 6px, so it visibly dragged along with the
     button instead of staying put and getting absorbed under it. */
  box-shadow: var(--shadow-collapse-accent);
  transform: translate(var(--shadow-off), var(--shadow-off));
}
.btn--ghost-inverse {
  background: transparent;
  color: var(--ink-inverse);
  border: var(--border-w) solid var(--ink-inverse);
  box-shadow: none;
}
.btn--ghost-inverse:hover,
.btn--ghost-inverse:focus-visible {
  background: var(--ink-inverse);
  color: var(--ink);
  transform: none;
}

/* ---- Kicker / pixel tag ---------------------------------------------------- */
.kicker {
  display: inline-block;
  font-family: var(--font-body);
  font-size: var(--fs-micro);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--grey-mid);
  border: var(--border-w-thin) solid var(--ink);
  padding: 6px 12px;
  margin-bottom: var(--sp-2);
}
.section--inverse .kicker {
  color: var(--ink-inverse);
  border-color: var(--ink-inverse);
}

/* ---- Promo badge (hero, sits above the mascot in .hero__mascot-wrap) --------
   Static/visible without JS (progressive enhancement, same convention as
   .tw-target); JS delays it in with a fade+scale-in ~800ms after load and
   wires up the click-to-contact behavior (initPromoBadge() in script.js).
   Entrance state is gated on .js so a no-JS visitor — who never gets
   .is-shown — simply sees it in place from the start. Permanent: no dismiss
   control, always shown every visit.
   A fixed -2deg rotation gives it a "stuck-on sticker" read against the
   otherwise strictly axis-aligned pixel-brutalism layout; it's baked into
   every transform value below (rest/shown/hover) so it's constant across
   states — only position/scale animate. */
.promo-badge {
  position: relative;
  display: inline-flex;
  align-items: flex-start;
  gap: var(--sp-2);
  max-width: 100%;
  padding: 16px 20px;
  border: var(--border-w) solid var(--ink);
  background: var(--bg-primary);
  box-shadow: var(--shadow-hard-accent);
  cursor: pointer;
  transform: rotate(-2deg);
  transition:
    transform var(--dur-fast) ease,
    box-shadow var(--dur-fast) ease,
    opacity var(--dur-med) ease,
    transform var(--dur-med) ease;
}
/* Pre-show state: only applied once JS is confirmed present. */
.js .promo-badge {
  opacity: 0;
  transform: translateY(-8px) scale(0.92) rotate(-2deg);
  pointer-events: none;
}
.promo-badge.is-shown {
  opacity: 1;
  transform: rotate(-2deg);
  pointer-events: auto;
}
/* Same specificity as the two rules above (two classes) — declared last so
   source order lets a hovered, already-shown badge collapse into its shadow
   instead of freezing at .is-shown's resting transform. */
.promo-badge:hover,
.promo-badge:focus-visible {
  transform: translate(var(--shadow-off), var(--shadow-off)) rotate(-2deg);
  box-shadow: var(--shadow-collapse-accent);
}
.promo-badge__dot {
  width: 6px;
  height: 6px;
  flex-shrink: 0;
  margin-top: 5px;
  background: var(--accent);
  animation: promo-dot-blink 1s ease-in-out infinite;
}
.promo-badge__body {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.promo-badge__line1 {
  font-family: var(--font-body);
  font-size: var(--fs-body);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink);
}
.promo-badge__price {
  color: var(--accent);
}
.promo-badge__line2 {
  font-family: var(--font-body);
  font-size: var(--fs-small);
  color: var(--grey-mid);
}

/* ---- Hard outline + shadow card utility ------------------------------------ */
.u-hard {
  border: var(--border-w) solid var(--ink);
  box-shadow: var(--shadow-hard);
  background: var(--bg-primary);
  transition:
    transform var(--dur-fast) ease,
    box-shadow var(--dur-fast) ease;
}
.u-hard:hover {
  transform: translate(var(--shadow-off), var(--shadow-off));
  box-shadow: var(--shadow-collapse);
}
.u-hard--inverse {
  background: var(--ink);
  color: var(--ink-inverse);
  border-color: var(--ink-inverse);
  box-shadow: var(--shadow-hard-inverse);
}
.u-hard--inverse:hover {
  box-shadow: var(--shadow-collapse-inverse);
}

/* ---- Dividers ---------------------------------------------------------------- */
.divider {
  width: 100%;
}
.divider--rule {
  height: var(--border-w);
  background: var(--ink);
  border: 0;
}
.divider--zigzag {
  height: 16px;
}
.divider--zigzag svg {
  width: 100%;
  height: 100%;
  display: block;
}

/* ---- Header ------------------------------------------------------------------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: var(--z-header);
  height: var(--header-h);
  background: var(--bg-primary);
  border-bottom: var(--border-w) solid var(--ink);
  transition: height var(--dur-med) ease;
}
.site-header.is-compact {
  height: var(--header-h-compressed);
}

.header__inner {
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
}
/* Header CTA needs a shorter vertical footprint than other large buttons —
   the default .btn padding (plus min-height and hard shadow) is taller than
   the 56px compressed header, so the shadow used to hang below the border.
   40px still only left ~2px clearance for the 6px shadow once rounding is
   accounted for; 36px gives a clean 4px+ gap in both header states. */
.header__inner .btn {
  padding: 8px 22px;
  min-height: 36px;
}

.logo {
  font-family: var(--font-display);
  font-size: 20px;
  letter-spacing: 0.03em;
  font-weight: 700;
  white-space: nowrap;
}

.nav {
  display: flex;
  gap: var(--sp-4);
}
.nav__link {
  font-family: var(--font-body);
  font-size: var(--fs-small);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 4px 2px;
  border-bottom: var(--border-w-thin) solid transparent;
  transition: border-color var(--dur-fast) ease;
}
.nav__link:hover {
  border-color: var(--ink);
}

.hamburger {
  display: none;
  width: 48px;
  height: 48px;
  border: var(--border-w-thin) solid var(--ink);
  background: var(--bg-primary);
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.hamburger__bars {
  position: relative;
  width: 20px;
  height: 14px;
}
.hamburger__bars span {
  position: absolute;
  left: 0;
  right: 0;
  height: 2px;
  background: var(--ink);
  transition:
    transform var(--dur-fast) ease,
    opacity var(--dur-fast) ease,
    top var(--dur-fast) ease;
}
.hamburger__bars span:nth-child(1) {
  top: 0;
}
.hamburger__bars span:nth-child(2) {
  top: 6px;
}
.hamburger__bars span:nth-child(3) {
  top: 12px;
}
.hamburger[aria-expanded="true"] .hamburger__bars span:nth-child(1) {
  top: 6px;
  transform: rotate(45deg);
}
.hamburger[aria-expanded="true"] .hamburger__bars span:nth-child(2) {
  opacity: 0;
}
.hamburger[aria-expanded="true"] .hamburger__bars span:nth-child(3) {
  top: 6px;
  transform: rotate(-45deg);
}

.mobile-nav {
  display: none;
  position: fixed;
  inset: 0;
  /* Match the header's current (expanded) height so the panel starts
     flush under the hamburger instead of overlapping its bottom edge.
     The sibling rule below re-matches it once the header compresses on
     scroll — see .site-header.is-compact + .mobile-nav. */
  top: var(--header-h);
  background: var(--bg-inverse);
  color: var(--ink-inverse);
  z-index: var(--z-overlay);
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-4);
  /* -100% alone only clears the panel's own height, leaving a residual
     band equal to its `top` offset still visible/clickable above the
     viewport edge — overshoot by --header-h so it always fully clears. */
  transform: translateY(calc(-100% - var(--header-h)));
  transition: transform var(--dur-med) ease;
}
.site-header.is-compact ~ .mobile-nav {
  top: var(--header-h-compressed);
}
.mobile-nav.is-open {
  transform: translateY(0);
}
.mobile-nav__link {
  font-family: var(--font-display);
  font-size: 32px;
  text-transform: uppercase;
  transition: color var(--dur-fast) ease;
}
.mobile-nav__link:hover,
.mobile-nav__link:focus-visible {
  color: var(--accent);
}

/* ---- Mascot (BYTE) -------------------------------------------------------------
   Shared base + pose modifiers. Body parts use currentColor so the same
   markup can sit on white or black sections; eyes/light always use accent. */
.mascot {
  width: 100%;
  height: auto;
  color: var(--ink);
  overflow: visible;
}
.section--inverse .mascot,
.mascot--on-inverse {
  color: var(--ink-inverse);
}

.byte-eye,
.byte-light {
  fill: var(--accent);
}

.byte-arm-l {
  transform-box: fill-box;
  transform-origin: 100% 50%;
}
.byte-arm-r {
  transform-box: fill-box;
  transform-origin: 0% 50%;
}
.byte-head-group {
  transform-box: fill-box;
  transform-origin: 50% 100%;
}

.mascot--wave .byte-arm-r {
  animation: byte-wave 0.6s ease-in-out infinite;
}
.mascot--wave .byte-eye {
  animation: byte-blink 3.8s ease-in-out infinite;
}
.mascot--idle {
  animation: byte-idle-sway 2s ease-in-out infinite;
}
.mascot--idle .byte-eye {
  animation: byte-blink 4s ease-in-out infinite;
}
/* Both arms stay in their default symmetric resting position (pointing
   outward to the sides) — no raised/one-armed gesture. */
.mascot--point {
  animation: byte-idle-sway 2.4s ease-in-out infinite;
}
.mascot--point .byte-eye {
  animation: byte-blink 3.4s ease-in-out infinite;
}
.mascot--walk {
  animation: byte-walk-bob 0.4s steps(2, end) infinite;
}
.mascot--walk .byte-eye {
  animation: byte-blink 3.2s ease-in-out infinite;
}
.mascot--trophy {
  animation: byte-idle-sway 2.2s ease-in-out infinite;
}
.mascot--trophy .byte-eye {
  animation: byte-blink 2.8s ease-in-out infinite;
}
.mascot--typing .byte-arm-l {
  animation: byte-type-l 0.5s ease-in-out infinite;
}
.mascot--typing .byte-arm-r {
  animation: byte-type-r 0.5s ease-in-out infinite 0.25s;
}
.mascot--typing .byte-eye {
  animation: byte-blink 2.6s ease-in-out infinite;
}
/* Sleep pose is a standalone flat-art SVG (own <path>s, not the shared
   rect-grid mascot markup) — see index.html footer__mascot. */
.mascot--sleep .byte-zzz {
  animation: byte-zzz-float 3s ease-in infinite;
}
.mascot--sleep .byte-zzz:nth-child(2) {
  animation-delay: 1s;
}
.mascot--sleep .byte-zzz:nth-child(3) {
  animation-delay: 2s;
}

/* ---- Scroll indicator (hero) -------------------------------------------------- */
.scroll-indicator {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-1);
  color: var(--grey-mid);
}
.scroll-indicator__arrow {
  width: 16px;
  height: 16px;
  animation: byte-bounce 1.4s ease-in-out infinite;
}
.scroll-indicator__label {
  font-size: var(--fs-nano);
  text-transform: uppercase;
  letter-spacing: 0.2em;
}

/* ---- Compare cards (problem/solution) ------------------------------------------ */
.compare-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-4);
}
.compare-card {
  padding: var(--sp-4);
}
/* No hover-shadow effect on these — border/background only, by design. */
.compare-card.u-hard {
  box-shadow: none;
}
.compare-card.u-hard:hover {
  transform: none;
  box-shadow: none;
}
.compare-card__head {
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  margin-bottom: var(--sp-3);
  display: flex;
  align-items: center;
  gap: var(--sp-1);
}
.compare-card__icon {
  width: 20px;
  height: 20px;
  fill: currentColor;
  flex-shrink: 0;
}
.compare-list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.compare-list li {
  display: flex;
  gap: var(--sp-1);
  font-size: var(--fs-small);
  line-height: var(--lh-body);
}
.compare-list li::before {
  content: "";
  flex-shrink: 0;
  width: 10px;
  height: 10px;
  margin-top: 4px;
  background: currentColor;
}

/* ---- Service cards --------------------------------------------------------------- */
.service-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-3);
}
.service-card {
  position: relative;
  padding: var(--sp-4);
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.service-card__icon {
  width: 40px;
  height: 40px;
  transition: transform var(--dur-fast) ease;
}
.service-card:hover .service-card__icon {
  transform: rotate(5deg) scale(1.08);
}
.service-card__title {
  font-family: var(--font-display);
  font-size: 18px;
  line-height: 1.25;
}
.service-card__copy {
  font-size: var(--fs-small);
  color: var(--grey-mid);
  flex: 1;
}
.service-card__link {
  font-size: var(--fs-micro);
  text-decoration: underline;
  text-underline-offset: 3px;
  font-weight: 700;
  align-self: flex-start;
}
.service-card:focus-visible {
  outline-offset: 6px;
}

.cta-banner {
  margin-top: var(--sp-6);
  padding: var(--sp-4) var(--sp-3);
  background: var(--ink);
  color: var(--ink-inverse);
  border: var(--border-w) solid var(--ink-inverse);
  box-shadow: var(--shadow-hard-inverse);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3);
  text-align: center;
  transition:
    transform var(--dur-fast) ease,
    box-shadow var(--dur-fast) ease;
}
.cta-banner:hover,
.cta-banner:focus-visible {
  transform: translate(var(--shadow-off), var(--shadow-off));
  box-shadow: var(--shadow-collapse-inverse);
}
.cta-banner__text {
  font-family: var(--font-display);
  font-size: 20px;
}

/* ---- Process timeline --------------------------------------------------------------- */
.timeline {
  position: relative;
  display: flex;
  gap: var(--sp-3);
  padding-top: var(--sp-8);
}
.timeline__track {
  position: absolute;
  top: 24px;
  left: 0;
  right: 0;
  height: 0;
  border-top: 3px dashed var(--ink);
}
.timeline__mascot-track {
  position: absolute;
  top: -10px;
  left: 0;
  width: 56px;
  will-change: transform;
}
.timeline__step {
  position: relative;
  flex: 1;
  padding: var(--sp-3);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.timeline__number {
  font-family: var(--font-display);
  font-size: 28px;
  width: 56px;
  height: 56px;
  border: var(--border-w) solid var(--ink);
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-primary);
}
.timeline__title {
  font-family: var(--font-display);
  font-size: 16px;
}
.timeline__copy {
  font-size: var(--fs-small);
  color: var(--grey-mid);
}

.process__emphasis {
  margin-top: var(--sp-6);
  text-align: center;
  padding: var(--sp-4);
  border: var(--border-w) solid var(--ink);
  font-family: var(--font-display);
  font-size: 28px;
  max-width: 640px;
  margin-inline: auto;
}

/* ---- Case studies --------------------------------------------------------------------- */
/* Always a single row: 3 equal-width flex items on desktop, a swipeable
   slider (06-responsive.css) once 3 cards no longer fit — never wraps
   into a multi-row grid. */
.case-slider {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}
.case-slider__arrow {
  display: none;
  flex-shrink: 0;
  width: 48px;
  height: 48px;
  align-items: center;
  justify-content: center;
  border: var(--border-w-thin) solid var(--ink);
  background: var(--bg-primary);
  cursor: pointer;
  transition: border-color var(--dur-fast) ease;
}
.case-slider__arrow:hover,
.case-slider__arrow:focus-visible {
  border-color: var(--accent);
}
.case-slider__arrow[disabled] {
  opacity: 0.3;
  cursor: default;
  pointer-events: none;
}
.case-slider__arrow-icon {
  width: 14px;
  height: 14px;
}
.case-grid {
  display: flex;
  flex: 1;
  min-width: 0;
  gap: var(--sp-3);
}
.case-card {
  display: flex;
  flex-direction: column;
  flex: 1 1 0;
  min-width: 0;
  cursor: pointer;
}
.case-card:focus-visible {
  outline-offset: 6px;
}
.case-card__frame {
  border-bottom: var(--border-w) solid var(--ink);
  padding: 10px 10px 0;
}
.case-card__bezel {
  border: var(--border-w-thin) solid var(--ink);
  background: var(--bg-primary);
}
.case-card__bezel-bar {
  display: flex;
  gap: 4px;
  padding: 5px 6px;
  border-bottom: var(--border-w-thin) solid var(--ink);
}
.case-card__bezel-bar span {
  width: 6px;
  height: 6px;
  background: var(--ink);
}
.case-card__preview {
  position: relative;
  aspect-ratio: 16 / 10;
  overflow: hidden;
}
.case-card__preview img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.case-card__preview-label {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-nano);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--grey-mid);
  background: repeating-linear-gradient(
    45deg,
    var(--bg-primary),
    var(--bg-primary) 8px,
    #f0f0f0 8px,
    #f0f0f0 16px
  );
}
.case-card__body {
  padding: var(--sp-3);
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  flex: 1;
}
.case-card__tag {
  font-size: var(--fs-nano);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--grey-mid);
}
.case-card__name {
  font-family: var(--font-display);
  font-size: 18px;
}
.case-card__metric {
  font-family: var(--font-display);
  font-size: 32px;
  color: var(--ink);
  margin-top: var(--sp-1);
}
.case-card__quote {
  font-size: var(--fs-small);
  color: var(--grey-mid);
  border-left: var(--border-w-thin) solid var(--ink);
  padding-left: var(--sp-2);
  margin-top: var(--sp-2);
}
.case-card__author {
  font-size: var(--fs-micro);
  margin-top: var(--sp-1);
  font-weight: 700;
}

/* ---- FAQ accordion -------------------------------------------------------------------- */
.accordion__item {
  border-bottom: var(--border-w-thin) solid var(--grey-line);
}
.accordion__item:first-child {
  border-top: var(--border-w) solid var(--ink);
}
.accordion__trigger {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-3) 0;
  background: transparent;
  border: none;
  text-align: left;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--fs-body);
  cursor: pointer;
}
.accordion__icon {
  flex-shrink: 0;
  position: relative;
  width: 20px;
  height: 20px;
  border: var(--border-w-thin) solid var(--ink);
}
.accordion__icon::before,
.accordion__icon::after {
  content: "";
  position: absolute;
  background: var(--ink);
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
.accordion__icon::before {
  width: 10px;
  height: 2px;
}
.accordion__icon::after {
  width: 2px;
  height: 10px;
  transition: transform var(--dur-fast) ease;
}
.accordion__trigger[aria-expanded="true"] .accordion__icon::after {
  transform: translate(-50%, -50%) rotate(90deg) scale(0);
}
.accordion__panel {
  overflow: hidden;
  max-height: 0;
  transition: max-height var(--dur-med) ease;
}
.accordion__panel-inner {
  padding-bottom: var(--sp-3);
  font-size: var(--fs-small);
  color: var(--grey-mid);
  max-width: 720px;
}

/* ---- Contact form --------------------------------------------------------------------- */
.form {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}
.form__fields {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}
.form__field {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}
.form__label {
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-inverse);
}
.form__label .is-optional {
  color: var(--grey-placeholder);
  text-transform: none;
  letter-spacing: 0;
}
.form__input,
.form__textarea {
  width: 100%;
  background: var(--bg-inverse);
  color: var(--ink-inverse);
  border: var(--border-w-thin) solid var(--ink-inverse);
  padding: 14px 16px;
  font-family: var(--font-body);
  font-size: var(--fs-body);
  min-height: 48px;
  transition:
    border-color var(--dur-fast) ease,
    box-shadow var(--dur-fast) ease;
}
.form__textarea {
  min-height: 96px;
  resize: vertical;
}
.form__input::placeholder,
.form__textarea::placeholder {
  color: var(--grey-mid-inverse);
}
.form__input:focus,
.form__textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 4px 4px 0 0 var(--accent);
}
.form__input.has-error,
.form__textarea.has-error {
  border-color: var(--accent);
}
.form__error {
  font-size: var(--fs-micro);
  color: var(--accent);
  min-height: 16px;
}

.form__chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1);
}
.chip {
  padding: 8px 14px;
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  border: var(--border-w-thin) solid var(--ink-inverse);
  background: transparent;
  color: var(--ink-inverse);
  cursor: pointer;
  transition:
    background var(--dur-fast) ease,
    color var(--dur-fast) ease;
}
.chip:hover {
  border-color: var(--accent);
}
.chip[aria-pressed="true"] {
  background: var(--ink-inverse);
  color: var(--ink);
}

/* ---- Checkbox (pixel-style) ------------------------------------------------ */
input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  margin: 0;
  border: var(--border-w-thin) solid var(--ink);
  background: var(--bg-primary);
  position: relative;
  cursor: pointer;
  transition:
    background-color var(--dur-fast) ease,
    border-color var(--dur-fast) ease;
}
.section--inverse input[type="checkbox"] {
  border-color: var(--ink-inverse);
  background: var(--bg-inverse);
}
input[type="checkbox"]:checked {
  background: var(--accent);
  border-color: var(--accent);
}
input[type="checkbox"]:checked::after {
  content: "";
  position: absolute;
  inset: 3px;
  background: var(--ink-inverse);
}
input[type="checkbox"]:focus-visible {
  outline: var(--border-w) solid var(--accent);
  outline-offset: 3px;
}

.form__consent {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  font-size: var(--fs-micro);
  color: var(--grey-mid-inverse);
}
.form__consent a {
  text-decoration: underline;
  color: var(--ink-inverse);
}

.form__success {
  display: none;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--sp-2);
  padding: var(--sp-8) var(--sp-3);
}
.form.is-submitted .form__fields,
.form.is-submitted .form__submit-row {
  display: none;
}
.form.is-submitted .form__success {
  display: flex;
}
.form__success-icon {
  width: 48px;
  height: 48px;
  color: var(--accent);
}
.form__success-title {
  font-family: var(--font-display);
  font-size: 22px;
  color: var(--ink-inverse);
}
.form__success-copy {
  color: var(--grey-mid-inverse);
  font-size: var(--fs-small);
}

/* ---- Contact aside (direct channels) --------------------------------------------------- */
.contact-buttons {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.contact-button {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 4px var(--sp-2);
  padding: 16px 20px;
  min-height: 48px;
  border: var(--border-w-thin) solid var(--ink-inverse);
  color: var(--ink-inverse);
  transition:
    background var(--dur-fast) ease,
    color var(--dur-fast) ease;
}
.contact-button__left {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-weight: 700;
  text-transform: uppercase;
  font-size: var(--fs-small);
}
.contact-button__icon {
  width: 22px;
  height: 22px;
  flex-shrink: 0;
}
.contact-button__value {
  font-size: var(--fs-small);
  min-width: 0;
  overflow-wrap: anywhere;
  word-break: break-word;
}
.contact-button:hover,
.contact-button:focus-visible {
  background: var(--ink-inverse);
  color: var(--ink);
}

.contact__mascot-caption {
  margin-top: var(--sp-2);
  font-size: var(--fs-micro);
  color: var(--grey-mid-inverse);
  text-align: center;
}

/* ---- Footer --------------------------------------------------------------------------- */
.footer__col-title {
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--grey-mid);
  margin-bottom: var(--sp-2);
}
.footer__nav {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}
.footer__nav a:hover {
  text-decoration: underline;
}
.footer__tagline {
  font-size: var(--fs-small);
  color: var(--grey-mid);
  margin-top: var(--sp-1);
  max-width: 32ch;
}
.footer__legal {
  font-size: var(--fs-small);
  color: var(--grey-mid);
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.footer__bottom {
  border-top: 1px solid var(--grey-line);
  margin-top: var(--sp-6);
  padding-top: var(--sp-3);
  display: flex;
  justify-content: space-between;
  gap: var(--sp-2);
  font-size: var(--fs-micro);
  color: var(--grey-mid);
}

/* ---- Service detail modal --------------------------------------------------------------
   One reusable template (index.html) populated per-service by initServiceModal() in
   script.js. Fade + scale-in, respects the global reduced-motion transition override. */
.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  background: rgb(10 10 10 / 78%);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-4);
  opacity: 0;
  visibility: hidden;
  transition:
    opacity var(--dur-med) ease,
    visibility 0s linear var(--dur-med);
}
.modal-backdrop.is-open {
  opacity: 1;
  visibility: visible;
  transition:
    opacity var(--dur-med) ease,
    visibility 0s linear;
}
.modal {
  position: relative;
  width: 100%;
  max-width: 640px;
  max-height: calc(100vh - var(--sp-8));
  overflow: visible;
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  background: var(--bg-primary);
  color: var(--ink);
  border: var(--border-w) solid var(--ink);
  box-shadow: var(--shadow-hard);
  padding: var(--sp-6);
  transform: scale(0.9);
  transition: transform var(--dur-med) ease;
}
.modal-backdrop.is-open .modal {
  transform: scale(1);
}
/* The modal body is split into a scrolling region (.modal__scroll) and a
   fixed, non-scrolling CTA footer (.modal__cta) so the CTA's hover
   transform/shadow never touches the scroll container's own box — a
   translate()d descendant contributes to its scroll ancestor's scrollable
   overflow per spec (box-shadow alone does not), so a full-width hover-
   translating button living directly inside an overflow:auto box grows that
   box's scrollHeight/scrollWidth by the translate offset on every hover. */
.modal__scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden; /* safety net — a modal should never need horizontal scroll */
}
/* scrollbar-width/-color (unlike ::-webkit-scrollbar, which is unscoped in
   02-base.css and so already covers this scroll container) isn't inherited
   by nested scroll containers — Firefox needs it restated per-element. Same
   forced-colors guard as the page-level rule. */
@media not (forced-colors: active) {
  .modal__scroll {
    scrollbar-width: thin;
    scrollbar-color: var(--accent) var(--bg-primary);
  }
}
.modal__close {
  position: absolute;
  top: var(--sp-3);
  right: var(--sp-3);
  width: 48px;
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: var(--border-w-thin) solid var(--ink);
  background: var(--bg-primary);
  box-shadow: var(--shadow-hard);
  cursor: pointer;
  transition:
    transform var(--dur-fast) ease,
    box-shadow var(--dur-fast) ease;
}
.modal__close:hover,
.modal__close:focus-visible {
  transform: translate(var(--shadow-off), var(--shadow-off));
  box-shadow: var(--shadow-collapse);
}
.modal__close-icon {
  width: 16px;
  height: 16px;
}
.modal__icon {
  width: 72px;
  height: 72px;
  margin-bottom: var(--sp-2);
}
.modal__icon svg {
  width: 100%;
  height: 100%;
}
.modal__title {
  font-size: var(--fs-h3);
  margin-bottom: var(--sp-2);
  padding-right: var(--sp-6);
  overflow-wrap: break-word;
  word-break: break-word;
}
.modal__desc {
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--ink);
  max-width: 56ch;
  margin-bottom: var(--sp-4);
}
.modal__pricing-kicker {
  margin-bottom: var(--sp-2);
}
.modal__pricing-kicker[hidden] {
  display: none;
}
.modal__pricing-list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  margin-bottom: var(--sp-3);
}
.modal__pricing-list[hidden] {
  display: none;
}
.modal__pricing-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-2);
  padding: var(--sp-2);
  border: var(--border-w-thin) solid var(--ink);
}
.modal__pricing-name-wrap {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-width: 0;
}
.modal__pricing-name {
  font-size: var(--fs-small);
  overflow-wrap: break-word;
  word-break: break-word;
}
.modal__pricing-note {
  font-size: var(--fs-nano);
  color: var(--grey-mid);
  margin-top: 2px;
}
.modal__pricing-price {
  font-size: var(--fs-small);
  font-weight: 700;
  flex-shrink: 0;
  white-space: nowrap;
}
.modal__price-block {
  margin-bottom: var(--sp-3);
}
.modal__disclaimer {
  font-size: var(--fs-small);
  color: var(--grey-mid);
}
/* .modal__cta sits outside .modal__scroll (see above) as a fixed footer —
   width:100% is what makes it a full-bleed hover target, which is exactly
   why it must never live inside the scrolling box. flex-shrink:0 keeps it
   from being squeezed by the scroll region's flex:1. */
.modal__cta {
  width: 100%;
  flex-shrink: 0;
}

/* ---- Case-study modal ---------------------------------------------------------------
   Wider, gallery-led variant of the service modal above — same .modal-backdrop/.modal
   base (backdrop dim, fade+scale-in, hard border/shadow, .modal__scroll body +
   .modal__cta footer split), just a width override. Populated per-case by
   initCaseModal() in script.js. */
.modal--case {
  max-width: 960px;
}

.case-modal-gallery {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-bottom: var(--sp-2);
}
.case-modal-gallery__viewport {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  border: var(--border-w-thin) solid var(--ink);
}
.case-modal-gallery__track {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.case-modal-gallery__track::-webkit-scrollbar {
  display: none;
}
.case-modal-gallery__slide {
  flex: 0 0 100%;
  scroll-snap-align: start;
  aspect-ratio: 16 / 10;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-nano);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--grey-mid);
  background: repeating-linear-gradient(
    45deg,
    var(--bg-primary),
    var(--bg-primary) 8px,
    #f0f0f0 8px,
    #f0f0f0 16px
  );
}
.case-modal-gallery__slide img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* .case-modal-gallery__arrow reuses .case-slider__arrow's sizing/hover/
   disabled styling as-is (same 48px touch target) — but .case-slider__arrow
   defaults to display:none (it's only shown ≤1024px, when .case-slider
   becomes a tablet/mobile slider); this gallery is a single-image carousel
   at every width, so its arrows need to always be visible. */
.case-modal-gallery__arrow {
  display: flex;
}
.case-modal-gallery__counter {
  text-align: center;
  font-size: var(--fs-micro);
  color: var(--grey-mid);
  margin-bottom: var(--sp-4);
}

.case-modal__tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1);
  margin-bottom: var(--sp-4);
}
.case-modal__tags[hidden] {
  display: none;
}
.case-modal__tags .kicker {
  margin-bottom: 0;
}

.case-modal__price {
  margin-bottom: 0;
}
.case-modal__price-line {
  font-size: var(--fs-body);
  font-weight: 700;
  margin-top: var(--sp-1);
}
