/* ============================================================================
   TechMark — page composition
   Section-level layout built from the primitives in system.css. No new colours,
   sizes or durations are invented here; everything references a token.
   ========================================================================= */

/* -------------------------------------------------------------------- hero */
/* A photograph, not a gradient. The scrim is fixed dark in both themes because
   the image underneath is always a night shot — theme-swapping it would make
   the headline unreadable. */

/* The header overlays the photograph rather than sitting on a slab above it.
   Re-pointing the tokens on the header itself means brand, nav, buttons and
   icons all pick up the light-on-photo treatment without individual rules. */
.has-hero .site-header:not([data-scrolled]):not([data-menu]) {
  background: transparent;
  backdrop-filter: none;
  --fg: #F4F1EB;
  --fg-2: #DDD8CF;
  --fg-3: #C3BDB3;
  --accent: #63CADF;
  --line-2: rgb(244 241 235 / 0.36);
  --ink-700: rgb(255 255 255 / 0.12);
}

.hero {
  position: relative;
  display: flex;
  align-items: flex-end;
  min-block-size: min(88svh, 46rem);
  /* pull up under the sticky header, then pad it back */
  margin-block-start: calc(-1 * var(--header-h));
  padding-block: calc(var(--header-h) + var(--sp-10)) var(--sp-9);
  overflow: clip;
  isolation: isolate;
}
.hero__media { position: absolute; inset: 0; z-index: -1; }
.hero__img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  object-position: 50% 45%;
}
/* ------------------------------------------------- signature: the topology */
/* Eight nodes into one hub, drawn on arrival, then carrying packets. Sits above
   both hero scrims (z-index 3) but under the text, on the side the copy leaves
   empty — and mirrors for RTL because it is placed with logical inset. */
.hero__net {
  position: absolute;
  z-index: 3;
  /* Sits high and outboard: the copy occupies the lower inline-start quadrant,
     so the graphic takes the opposite one and bleeds off the edge rather than
     sitting politely inside the column. */
  inset-block-start: 40%;
  inset-inline-end: -7rem;
  inline-size: min(34rem, 42vw);
  aspect-ratio: 1;
  transform: translateY(-50%);
  /* the brand cyan literal, not var(--accent): the hero sits under a fixed dark
     scrim in BOTH themes, and --accent flips to the deep cyan in light theme.
     Same reasoning as .hero__kicker below. */
  color: #63CADF;
  pointer-events: none;
  display: none;
}
@media (min-width: 62rem) {
  .hero__net { display: block; }
}
[dir='rtl'] .hero__net { transform: translateY(-50%) scaleX(-1); }

.hero__net-links path {
  stroke-width: 1.25;
  opacity: 0.5;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  animation: net-draw 1.1s var(--ease-draw) forwards;
}
.hero__net-chord { opacity: 0.28 !important; stroke-dasharray: 1; }
/* Stagger the draw so the system assembles rather than appearing. */
.hero__net-links path:nth-child(1) { animation-delay: 0.15s }
.hero__net-links path:nth-child(2) { animation-delay: 0.24s }
.hero__net-links path:nth-child(3) { animation-delay: 0.33s }
.hero__net-links path:nth-child(4) { animation-delay: 0.42s }
.hero__net-links path:nth-child(5) { animation-delay: 0.51s }
.hero__net-links path:nth-child(6) { animation-delay: 0.60s }
.hero__net-links path:nth-child(7) { animation-delay: 0.69s }
.hero__net-links path:nth-child(8) { animation-delay: 0.78s }
.hero__net-links path:nth-child(9) { animation-delay: 0.95s }
.hero__net-links path:nth-child(10) { animation-delay: 1.05s }
.hero__net-links path:nth-child(11) { animation-delay: 1.15s }

.hero__net-nodes circle {
  fill: #63CADF;
  opacity: 0;
  transform-box: fill-box;
  transform-origin: center;
  animation: net-node 0.5s var(--ease-spring) forwards;
}
.hero__net-nodes circle:nth-child(1) { animation-delay: 0.75s }
.hero__net-nodes circle:nth-child(2) { animation-delay: 0.84s }
.hero__net-nodes circle:nth-child(3) { animation-delay: 0.93s }
.hero__net-nodes circle:nth-child(4) { animation-delay: 1.02s }
.hero__net-nodes circle:nth-child(5) { animation-delay: 1.11s }
.hero__net-nodes circle:nth-child(6) { animation-delay: 1.20s }
.hero__net-nodes circle:nth-child(7) { animation-delay: 1.29s }
.hero__net-nodes circle:nth-child(8) { animation-delay: 1.38s }

.hero__net-hub {
  fill: none;
  stroke: #63CADF;
  stroke-width: 1.5;
  opacity: 0;
  transform-box: fill-box;
  transform-origin: center;
  animation: net-node 0.6s var(--ease-spring) 0.05s forwards, net-breathe 4.5s var(--ease-inout) 1.6s infinite;
}

/* A short dash travelling the path reads as a packet in flight, and needs no
   offset-path support: it is the same dasharray trick running the other way. */
.hero__net-pulses path {
  stroke-width: 2.5;
  stroke-dasharray: 0.05 0.95;
  stroke-dashoffset: 1;
  opacity: 0;
  animation: net-pulse 3.6s var(--ease-inout) infinite;
}
.hero__net-pulses path:nth-child(1) { animation-delay: 1.9s }
.hero__net-pulses path:nth-child(2) { animation-delay: 3.1s }
.hero__net-pulses path:nth-child(3) { animation-delay: 4.3s }

@keyframes net-draw   { to { stroke-dashoffset: 0 } }
@keyframes net-node   { from { opacity: 0; transform: scale(0.2) } to { opacity: 1; transform: scale(1) } }
@keyframes net-breathe{ 0%,100% { opacity: 0.85; r: 13 } 50% { opacity: 1; r: 15 } }
@keyframes net-pulse  { 0% { stroke-dashoffset: 1; opacity: 0 } 12% { opacity: 1 } 60%,100% { stroke-dashoffset: -0.05; opacity: 0 } }

@media (prefers-reduced-motion: reduce) {
  /* The assembled state is the meaningful one — keep it, drop the assembling. */
  .hero__net-links path { stroke-dashoffset: 0; animation: none; }
  .hero__net-nodes circle, .hero__net-hub { opacity: 1; animation: none; }
  .hero__net-pulses { display: none; }
}

.hero__media::after {
  content: '';
  position: absolute;
  inset: 0;
  /* The top band is deliberately darker than the middle: the header sits there,
     and the brand mark is the slate + cyan from the logo file rather than a
     theme colour, so it needs a dependable backdrop on bright photographs
     (the contact-centre shot) as well as dark ones (the villa at night). */
  background:
    linear-gradient(to top,
      rgb(6 8 10 / 0.95) 0%,
      rgb(6 8 10 / 0.88) 30%,
      rgb(6 8 10 / 0.70) 52%,
      rgb(6 8 10 / 0.38) 72%,
      rgb(6 8 10 / 0.34) 84%,
      rgb(6 8 10 / 0.68) 100%);
}
/* A second scrim running from the text edge inward. The vertical gradient alone
   could not carry the kicker, which sits highest in the text block and is small
   cyan type — measured 1.05:1 over the lit villa windows. This is direction-
   aware: it hugs whichever edge the text is set against. */
.hero__media::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(to right, rgb(6 8 10 / 0.62) 0%, rgb(6 8 10 / 0.30) 38%, transparent 64%);
}
[dir='rtl'] .hero__media::before {
  background: linear-gradient(to left, rgb(6 8 10 / 0.62) 0%, rgb(6 8 10 / 0.30) 38%, transparent 64%);
}
.hero__media::after { z-index: 2; }

.hero__inner { position: relative; color: #F4F1EB; }
.hero__kicker {
  font-size: var(--step--1);
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #63CADF;
  margin-block-end: var(--sp-5);
}
[lang='ar'] .hero__kicker { letter-spacing: 0; text-transform: none; font-size: var(--step-0); }

.hero__title {
  font-size: var(--step-5);
  max-inline-size: 46ch;
  color: #FBF9F5;
}
.hero__lead {
  margin-block-start: var(--sp-6);
  max-inline-size: 54ch;
  color: #D6D1C8;
}
.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  margin-block-start: var(--sp-7);
}
/* The hero always sits on a night photograph, so its buttons keep the bright
   cyan in both themes — the light theme's darker accent would go muddy here. */
.hero__actions .btn { --btn-bg: #63CADF; --btn-fg: #080A0C; }
.hero__actions .btn--quiet { --btn-bg: transparent; --btn-fg: #F4F1EB; --btn-line: rgb(244 241 235 / 0.34); }
.hero__actions .btn--quiet:hover { --btn-line: #63CADF; --btn-fg: #63CADF; }

.br-lg { display: none; }
@media (min-width: 60rem) { .br-lg { display: inline; } }

/* ------------------------------------------------------------------- fears */
/* Q&A, not cards. Naming the anxiety is the whole point of the section. */

.fears {
  margin-block-start: var(--sp-9);
  border-block-end: 1px solid var(--line);
}
.fears__item {
  display: grid;
  gap: var(--sp-3) var(--sp-8);
  padding-block: var(--sp-7);
  border-block-start: 1px solid var(--line);
}
@media (min-width: 56rem) {
  .fears__item { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); }
}
.fears__q {
  font-size: var(--step-2);
  font-weight: 500;
  line-height: 1.3;
  letter-spacing: -0.02em;
  text-wrap: balance;
}
[lang='ar'] .fears__q { letter-spacing: 0; line-height: 1.5; }
.fears__a { margin: 0; color: var(--fg-2); max-inline-size: var(--measure); }

/* ------------------------------------------------------------------- index */
/* A contents page, not a feature wall. Hovering a row previews where it goes. */

.index {
  list-style: none;
  padding: 0;
  margin-block-start: var(--sp-8);
  border-block-start: 1px solid var(--line);
}
.index__row { position: relative; border-block-end: 1px solid var(--line); }

.index__link {
  display: grid;
  grid-template-columns: 2.75rem minmax(0, 1fr) 1.25rem;
  grid-template-areas: 'n name arrow' '. out .';
  column-gap: var(--sp-4);
  row-gap: var(--sp-1);
  align-items: baseline;
  padding-block: var(--sp-5);
  color: var(--fg);
  text-decoration: none;
}
@media (min-width: 60rem) {
  .index__link {
    /* fixed name track so the outcome column lines up across every row —
       each row is its own grid, so an auto track would stagger them */
    grid-template-columns: 3.5rem minmax(0, 22rem) minmax(0, 1fr) 1.25rem;
    grid-template-areas: 'n name out arrow';
    column-gap: var(--sp-6);
    padding-block: var(--sp-5);
  }
}
.index__n {
  grid-area: n;
  font-size: var(--step--1);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  color: var(--accent);
}
.index__name {
  grid-area: name;
  font-size: var(--step-2);
  font-weight: 500;
  letter-spacing: -0.02em;
  transition: color var(--t-fast);
}
[lang='ar'] .index__name { letter-spacing: 0; }
.index__out { grid-area: out; color: var(--fg-2); font-size: var(--step-0); }
.index__arrow {
  grid-area: arrow;
  align-self: center;
  color: var(--fg-3);
  transition: transform var(--t-fast), color var(--t-fast);
}
[dir='rtl'] .index__arrow { transform: scaleX(-1); }

.index__link:hover .index__name { color: var(--accent); }
.index__link:hover .index__arrow { color: var(--accent); transform: translateX(4px); }
[dir='rtl'] .index__link:hover .index__arrow { transform: scaleX(-1) translateX(4px); }

/* Destination preview — desktop pointer only, purely additive.
   It is a background-image on a display:none element, which browsers do not
   fetch. Nothing downloads until a row is actually hovered, so eight previews
   cost zero bytes on load. `display` cannot be transitioned, hence the
   keyframe. */
.index__peek { display: none; }
@media (min-width: 68rem) and (hover: hover) and (prefers-reduced-motion: no-preference) {
  .index__peek {
    position: absolute;
    inset-block-start: 50%;
    inset-inline-end: 3rem;
    inline-size: 13rem;
    aspect-ratio: 3 / 2;
    z-index: 2;
    border-radius: var(--r-sm);
    background-size: cover;
    background-position: center;
    box-shadow: var(--shadow-lift);
    pointer-events: none;
    transform: translateY(-50%);
  }
  .index__row:hover .index__peek,
  .index__row:focus-within .index__peek {
    display: block;
    animation: peek-in var(--t) var(--ease) both;
  }
  /* a phone screenshot needs a phone-shaped frame, not a 3:2 crop */
  .index__peek--tall { inline-size: 8rem; aspect-ratio: 9 / 16; }
}
@keyframes peek-in {
  from { opacity: 0; transform: translateY(-50%) scale(0.96); }
  to   { opacity: 1; transform: translateY(-50%) scale(1); }
}

/* ----------------------------------------------------------------- product */

.product { align-items: center; }
.shots {
  position: relative;
  display: flex;
  justify-content: center;
  gap: var(--sp-4);
  padding-block: var(--sp-4);
}
.shots img {
  inline-size: 100%;
  border-radius: var(--r-md);
  border: 1px solid var(--line-2);
  box-shadow: var(--shadow-lift);
  background: var(--ink-700);
}
.shots__a, .shots__b { inline-size: min(44%, 15rem); }
.shots__a { transform: translateY(var(--sp-5)); }
@media (min-width: 56rem) {
  .shots__a { transform: translateY(var(--sp-7)) rotate(-1.2deg); }
  .shots__b { transform: rotate(1.2deg); }
}

/* ------------------------------------------------------------------- proof */

.proof { position: relative; padding-block: var(--sp-12); overflow: clip; isolation: isolate; }
.proof__media { position: absolute; inset: 0; z-index: -1; }
.proof__img { inline-size: 100%; block-size: 100%; object-fit: cover; }
/* Two scrims, same reasoning as the hero (F-007): the vertical one sets the
   overall mood, the horizontal one guarantees the text column has a dark
   backdrop whatever photograph sits here. The proof image is a placeholder due
   to be swapped, so the legibility must not depend on which picture it is. */
.proof__media::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(to right, rgb(6 8 10 / 0.74) 0%, rgb(6 8 10 / 0.46) 42%, transparent 70%);
}
[dir='rtl'] .proof__media::before {
  background: linear-gradient(to left, rgb(6 8 10 / 0.74) 0%, rgb(6 8 10 / 0.46) 42%, transparent 70%);
}
.proof__media::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  background: linear-gradient(to top, rgb(6 8 10 / 0.9), rgb(6 8 10 / 0.62));
}
.proof__card {
  max-inline-size: 44rem;
  color: #F4F1EB;
}
.proof__card .lead { color: #D6D1C8; }
.proof__card .eyebrow { color: #B9B2A8; }
.proof__card .tlink { color: #63CADF; border-block-end-color: rgb(99 202 223 / 0.4); }

/* ------------------------------------------------------- page head + pillars */

.page-head { padding-block-start: var(--sp-10); }
.page-head .h1 { max-inline-size: 18ch; }
[lang='ar'] .page-head .h1 { max-inline-size: 22ch; }

/* A photograph and a group of services, alternating sides down the page. */
.pillar { display: grid; gap: var(--sp-6); align-items: center; }
@media (min-width: 56rem) {
  .pillar { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: var(--sp-9); }
  .pillar--flip > figure { order: 2; }
}
.pillar .figure img { aspect-ratio: 4 / 3; }
.pillar .index--compact { margin-block-start: var(--sp-5); border-block-start: 1px solid var(--line); }

.statement {
  font-size: var(--step-1);
  line-height: 1.55;
  color: var(--fg-2);
  max-inline-size: var(--measure);
  margin-block-start: var(--sp-4);
}
[lang='ar'] .statement { line-height: 1.85; }
.statement strong { color: var(--fg); font-weight: 500; }

/* ============================================================ SERVICE PAGES */
/* One rhythm across all eight. Only the photographs change. */

.hero--service { min-block-size: min(66svh, 34rem); }
.hero__back { margin-block-end: var(--sp-5); }
.hero__back a {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--step--1);
  color: #C7C1B8;
  text-decoration: none;
  transition: color var(--t-fast);
}
.hero__back a::before { content: '\2190'; }
[dir='rtl'] .hero__back a::before { content: '\2192'; }
.hero__back a:hover { color: #63CADF; }

/* the problem and the answer to it, side by side */
.pitch .statement {
  font-size: var(--step-1);
  line-height: 1.55;
  color: var(--fg-2);
  max-inline-size: 52ch;
}
[lang='ar'] .pitch .statement { line-height: 1.85; }
.pitch .statement--answer { color: var(--fg); }
.band-figure { margin-block-start: var(--sp-9); }

/* benefits — three statements under a rule. No cards, no icons. */
.benefits {
  display: grid;
  gap: var(--sp-7);
  margin-block-start: var(--sp-7);
}
@media (min-width: 56rem) {
  .benefits { grid-template-columns: repeat(3, 1fr); gap: var(--sp-8); }
}
.benefit { padding-block-start: var(--sp-5); border-block-start: 1px solid var(--accent-line); }
.benefit__t { font-size: var(--step-1); font-weight: 500; letter-spacing: -0.015em; }
[lang='ar'] .benefit__t { letter-spacing: 0; }
.benefit__d { margin-block-start: var(--sp-3); color: var(--fg-2); }

/* equipment & stack */
.stack-block { align-items: start; margin-block-start: var(--sp-2); }
.kv { display: grid; gap: 0; margin: 0; }
.kv > div {
  display: grid;
  gap: var(--sp-1) var(--sp-6);
  padding-block: var(--sp-5);
  border-block-start: 1px solid var(--line);
}
.kv > div:last-child { border-block-end: 1px solid var(--line); }
@media (min-width: 40rem) { .kv > div { grid-template-columns: minmax(0, 12rem) minmax(0, 1fr); } }
.kv dt { font-weight: 500; }
.kv dd { margin: 0; color: var(--fg-2); }

/* "built with" marks — monochrome, so a vendor palette never fights ours */
.toolmarks {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-6);
  list-style: none;
  padding: 0;
  margin-block-start: var(--sp-6);
}
.toolmarks:empty { display: none; }
.toolmark img {
  inline-size: 1.75rem;
  block-size: 1.75rem;
  opacity: 0.55;
  transition: opacity var(--t-fast);
}
.toolmark:hover img { opacity: 1; }
/* the marks are SVG with fill="currentColor", so they take the text colour */
.toolmarks { color: var(--fg-2); }

/* in the field */
.cases {
  display: grid;
  gap: var(--sp-8);
  margin-block-start: var(--sp-7);
}
@media (min-width: 56rem) { .cases { grid-template-columns: repeat(2, 1fr); gap: var(--sp-9); } }
.case__t {
  margin-block-start: var(--sp-5);
  font-size: var(--step-1);
  font-weight: 500;
  letter-spacing: -0.015em;
}
[lang='ar'] .case__t { letter-spacing: 0; }
.case__d { margin-block-start: var(--sp-3); color: var(--fg-2); max-inline-size: 46ch; }

/* related services — the index list stripped back to names */
.index--compact { margin-block-start: var(--sp-6); }
.index--compact .index__link {
  grid-template-columns: minmax(0, 1fr) 1.25rem;
  grid-template-areas: 'name arrow';
  padding-block: var(--sp-4);
}
@media (min-width: 60rem) {
  .index--compact .index__link {
    grid-template-columns: minmax(0, 1fr) 1.25rem;
    grid-template-areas: 'name arrow';
    padding-block: var(--sp-4);
  }
}
.index--compact .index__name { font-size: var(--step-1); }

/* ============================================================== BAIT PAGE */
/* A hairline frame, not a drawn phone bezel — the screenshot is the product,
   and a fake chrome illustration would be decoration. */

.phone { display: block; }
.phone img {
  inline-size: 100%;
  border-radius: var(--r-md);
  border: 1px solid var(--line-2);
  background: var(--ink-700);
  box-shadow: var(--shadow-lift);
}

.shot-row {
  display: flex;
  justify-content: flex-start;
  gap: var(--sp-4);
  margin-block-start: var(--sp-9);
  overflow: visible;
}
.shot-row .phone { inline-size: min(44%, 15rem); }
@media (min-width: 56rem) { .shot-row .phone { inline-size: 15rem; } }
.phone--hide-sm { display: none; }
@media (min-width: 56rem) { .phone--hide-sm { display: block; } }

.feature-set {
  display: grid;
  gap: var(--sp-9) var(--sp-8);
  margin-block-start: var(--sp-9);
  grid-template-columns: 1fr;
}
@media (min-width: 44rem) { .feature-set { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 72rem) { .feature-set { grid-template-columns: repeat(3, 1fr); } }
.feature .phone { max-inline-size: 18rem; margin-inline: auto; }
.feature__t {
  margin-block-start: var(--sp-6);
  font-size: var(--step-1);
  font-weight: 500;
  letter-spacing: -0.015em;
}
[lang='ar'] .feature__t { letter-spacing: 0; }
.feature__d { margin-block-start: var(--sp-3); color: var(--fg-2); }

/* the four-state proof grid */
.quad {
  display: grid;
  gap: var(--sp-6);
  margin-block-start: var(--sp-8);
  grid-template-columns: repeat(2, 1fr);
}
@media (min-width: 56rem) { .quad { grid-template-columns: repeat(4, 1fr); gap: var(--sp-7); } }
.quad figure { margin: 0; }
.quad figcaption {
  margin-block-start: var(--sp-3);
  font-size: var(--step--1);
  color: var(--fg-3);
  text-align: center;
}
.kv dt { font-variant-numeric: tabular-nums; }

/* ========================================================== COMPANY PAGE */

.mark { align-items: center; }
.mark__art { display: grid; place-items: center; padding: var(--sp-6); }
.logo-lockup { inline-size: min(100%, 22rem); block-size: auto; color: var(--fg-2); }

/* ========================================================== CONTACT PAGE */

.contact-grid { align-items: start; gap: var(--sp-9); }

.channels { list-style: none; padding: 0; margin-block-start: var(--sp-2); }
.channels > li { padding-block: var(--sp-5); border-block-start: 1px solid var(--line); }
.channels > li:last-child { border-block-end: 1px solid var(--line); }
.channels a { text-decoration: none; color: inherit; display: block; padding-block: var(--sp-1); }
.channels__k {
  display: block;
  font-size: var(--step--1);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--fg-3);
}
[lang='ar'] .channels__k { letter-spacing: 0; text-transform: none; }
.channels__v {
  display: block;
  margin-block-start: var(--sp-1);
  font-size: var(--step-2);
  font-weight: 500;
  letter-spacing: -0.02em;
  transition: color var(--t-fast);
}
[lang='ar'] .channels__v { letter-spacing: 0; }
.channels a:hover .channels__v { color: var(--accent); }
.channels__n { margin-block-start: var(--sp-2); color: var(--fg-3); font-size: var(--step--1); }

/* ------------------------------------------------------------------ form */

.form {
  display: grid;
  gap: var(--sp-5);
  margin-block-start: var(--sp-5);
}
@media (min-width: 40rem) { .form { grid-template-columns: repeat(2, 1fr); } }
.field { display: grid; gap: var(--sp-2); align-content: start; }
.field--wide { grid-column: 1 / -1; }
.field label { font-size: var(--step--1); color: var(--fg-2); }
.field input,
.field select,
.field textarea {
  inline-size: 100%;
  padding: 0.75em 0.9em;
  background: var(--ink-800);
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  color: var(--fg);
  transition: border-color var(--t-fast), background var(--t-fast);
}
.field textarea { resize: vertical; min-block-size: 7rem; }
.field input:hover, .field select:hover, .field textarea:hover { border-color: var(--fg-3); }
.field input:focus, .field select:focus, .field textarea:focus { border-color: var(--accent); }
.field [aria-invalid='true'] { border-color: #E4674F; }
.field__hint { font-size: var(--step--1); color: var(--fg-3); }
.field__err { font-size: var(--step--1); color: #E4674F; min-block-size: 0; }
.field__err:empty { display: none; }

.form__foot { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-4) var(--sp-5); }

/* honeypot — off-canvas rather than display:none, which some bots skip */
.hp {
  position: absolute;
  inline-size: 1px; block-size: 1px;
  overflow: hidden; clip-path: inset(50%);
  white-space: nowrap;
}

/* ---------------------------------------------------------------- cta band */

.cta-band { border-block-start: 1px solid var(--line); }
.cta-band__inner { max-inline-size: 46rem; }
.cta-band__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3) var(--sp-5);
  margin-block-start: var(--sp-7);
}


/* ------------------------------------------------ capability index: hover */
/* Three things move on different curves rather than one thing fading: the row
   lifts on a glide, the name tracks toward the reader, and the arrow springs.
   Differentiated timing is what separates choreography from a hover state. */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .index__link {
    transition: padding-inline-start var(--t-glide), background var(--t);
  }
  .index__name, .index__arrow {
    transition: transform 0.34s var(--ease-spring), color var(--t-swift);
  }
  .index__row:hover .index__link,
  .index__row:focus-within .index__link {
    padding-inline-start: var(--sp-4);
    background: linear-gradient(to right, var(--accent-wash), transparent 42%);
  }
  [dir='rtl'] .index__row:hover .index__link,
  [dir='rtl'] .index__row:focus-within .index__link {
    background: linear-gradient(to left, var(--accent-wash), transparent 42%);
  }
  .index__row:hover .index__name { transform: translateX(0.18rem); }
  .index__row:hover .index__arrow { transform: translateX(0.42rem); }
  [dir='rtl'] .index__row:hover .index__name { transform: translateX(-0.18rem); }
  [dir='rtl'] .index__row:hover .index__arrow { transform: translateX(-0.42rem); }
}

/* ------------------------------------------------ a day in this house */
/* An editorial timeline, not a feature grid. The spine draws itself downward
   as each moment reveals — it reuses the .is-in class the existing
   IntersectionObserver already sets, so it costs no new JavaScript and it
   degrades to a plain drawn line if the observer never fires. */

.scenes__list { list-style: none; margin: 0; padding: 0; margin-block-start: var(--sp-9); }

.scene {
  position: relative;
  display: grid;
  grid-template-columns: 5.5rem 1fr;
  gap: 0 var(--sp-6);
  padding-block-end: var(--sp-9);
  padding-inline-start: var(--sp-6);
}
.scene:last-child { padding-block-end: 0; }

/* the spine: one segment per moment, scaled from the top on reveal */
.scene::before {
  content: '';
  position: absolute;
  inset-block: 0.55rem 0;
  inset-inline-start: 0;
  inline-size: 1px;
  background: var(--accent-line);
  transform: scaleY(0);
  transform-origin: top;
  transition: transform 0.7s var(--ease-glide);
  transition-delay: 0.1s;
}
.scene.is-in::before { transform: scaleY(1); }
.scene:last-child::before { display: none; }

/* the node */
.scene::after {
  content: '';
  position: absolute;
  inset-block-start: 0.5rem;
  inset-inline-start: -3px;
  inline-size: 7px;
  block-size: 7px;
  border-radius: var(--r-full);
  background: var(--accent);
  transform: scale(0);
  transition: transform 0.45s var(--ease-spring);
}
.scene.is-in::after { transform: scale(1); }

.scene__when {
  font-variant-numeric: tabular-nums;
  font-size: var(--step--1);
  letter-spacing: 0.1em;
  color: var(--accent);
  padding-block-start: 0.1rem;
}
[lang='ar'] .scene__when { letter-spacing: 0; }

.scene__t { font-size: var(--step-2); line-height: 1.25; margin: 0; }
.scene__d {
  color: var(--fg-2);
  max-inline-size: 54ch;
  margin-block-start: var(--sp-3);
}

.scene__media {
  grid-column: 2;
  margin: 0;
  margin-block-start: var(--sp-5);
  max-inline-size: 26rem;
  border-radius: var(--r-md);
  overflow: hidden;
}
.scene__media img { display: block; inline-size: 100%; block-size: auto; aspect-ratio: 3 / 2; object-fit: cover; }

/* The moment with no photograph carries the weight instead. This is the claim
   nobody else can make, so it is set as a statement rather than an entry. */
.scene--statement .scene__t { font-size: var(--step-3); color: var(--fg); max-inline-size: 20ch; }
.scene--statement .scene__d { color: var(--fg); }
.scene--statement::after { inline-size: 11px; block-size: 11px; inset-inline-start: -5px; }

@media (max-width: 46rem) {
  .scene { grid-template-columns: 1fr; gap: var(--sp-2); padding-block-end: var(--sp-8); }
  .scene__media { grid-column: 1; max-inline-size: none; }
}

@media (prefers-reduced-motion: reduce) {
  .scene::before { transform: scaleY(1); transition: none; }
  .scene::after { transform: scale(1); transition: none; }
}



/* A wordmark where no licensed mark exists. Set to the same optical weight and
   height as the glyphs beside it so the row reads as one deliberate line rather
   than icons plus leftovers — most of the equipment this firm deploys
   (Hikvision, Dahua, Yeastar, 3CX, KNX, Modbus…) has no CC0 logo at all. */
.toolmark--name {
  font-size: var(--step--1);
  font-weight: 500;
  letter-spacing: 0.06em;
  color: var(--fg-2);
  opacity: 0.62;
  line-height: 1.75rem;
  transition: opacity var(--t-fast);
  white-space: nowrap;
}
.toolmark--name:hover { opacity: 1; }
[lang='ar'] .toolmark--name { letter-spacing: 0; }
