/* TAMAX motion layer
   ============================================================
   Everything here is decorative. The page is complete, readable and usable
   with this file removed, and every rule that moves is either gated behind
   `prefers-reduced-motion: no-preference` or reverts in the reduced-motion
   block at the end. Only transform, opacity, filter and custom properties are
   animated, so the compositor does the work rather than the main thread.

   Load order matters: this file comes after sections.css so it can raise the
   existing reveal and card rules without !important.
   ============================================================ */

:root {
  /* Driven from motion.js: the atmosphere tint follows the section in view. */
  --tx-atmos-a: rgba(11, 123, 208, 0.22);
  --tx-atmos-b: rgba(41, 184, 238, 0.14);
  --tx-atmos-y: 0%;
  --tx-scroll: 0;
  --tx-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --tx-ease-inout: cubic-bezier(0.65, 0, 0.35, 1);
}

/* ============================================================
   0. The scene.
   scene.js paints one fixed canvas behind the whole page. It sits above the
   atmosphere and below every section band, so the bands read as glass held
   in front of a room rather than as pages over a background.
   ============================================================ */

.tx-scene {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: -1;
  display: block;
  pointer-events: none;
  opacity: 0;
  transition: opacity 1.6s var(--tx-ease-out);
  contain: strict;
}

.tx-scene.is-ready {
  opacity: 1;
}

/* With the scene running, the hero has real depth behind it and its own flat
   decorations would fight it. The aurora steps back, and the flat SVG core is
   retired for the session: the mark in the scene is the same geometry, built
   from the same numbers, so showing both would be showing the logo twice. It
   stays in the markup, and returns untouched whenever the scene does not run. */
.tx-scene-on .tx-hero__aurora {
  opacity: 0.45;
}

.tx-scene-on .tx-hero__visual {
  opacity: 0;
  visibility: hidden;
  transition: opacity 900ms var(--tx-ease-out), visibility 0s linear 900ms;
}

/* The copy column takes the width the visual leaves behind, so the hero does
   not end up with a hole in it where the SVG used to be. */
@media (min-width: 62em) {
  .tx-scene-on .tx-hero__grid {
    grid-template-columns: minmax(0, 1fr) minmax(0, 0.92fr);
  }
}

/* The bands open up: the room behind them should be legible as depth, while
   the copy keeps the contrast it had. Text sits on the band, not on the
   scene, so this only changes what happens between the lines. */
.tx-scene-on .tx-section--light {
  background: rgba(8, 27, 50, 0.8);
}

.tx-scene-on .tx-section--dark {
  background:
    radial-gradient(120% 90% at 85% 0%, rgba(11, 123, 208, 0.3) 0%, rgba(11, 123, 208, 0) 60%),
    linear-gradient(160deg, rgba(7, 42, 84, 0.8) 0%, rgba(4, 16, 31, 0.82) 100%);
}

.tx-scene-on .tx-section--apps {
  background: linear-gradient(180deg, rgba(8, 27, 50, 0.78) 0%, rgba(4, 16, 31, 0.84) 100%);
}

/* The hero is the one band that gets out of the way completely. */
.tx-scene-on .tx-hero {
  background: radial-gradient(86% 66% at 76% 14%, rgba(11, 123, 208, 0.26) 0%, rgba(11, 123, 208, 0) 62%);
}

@media (prefers-reduced-motion: reduce) {
  .tx-scene {
    display: none;
  }
}

/* ============================================================
   0b. Holographic glass.
   The cards stop being filled rectangles and become panes: a blurred sample
   of the room behind them, a lit top edge, and a highlight that follows the
   pointer. Blur is the expensive part, so it is only asked for where a card
   actually sits over the scene, and never on a coarse pointer at phone size.
   ============================================================ */

.tx-eco-panel,
.tx-cube,
.tx-card,
.tx-note,
.tx-layers,
.tx-toc,
.tx-form--contact {
  position: relative;
  border-color: rgba(148, 195, 226, 0.24);
}

@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  @media (min-width: 48em) {
    .tx-scene-on .tx-eco-panel,
    .tx-scene-on .tx-cube,
    .tx-scene-on .tx-layers,
    .tx-scene-on .tx-card {
      background: linear-gradient(
        155deg,
        rgba(148, 205, 245, 0.09) 0%,
        rgba(10, 35, 64, 0.42) 46%,
        rgba(6, 22, 42, 0.5) 100%
      );
      -webkit-backdrop-filter: blur(14px) saturate(130%);
      backdrop-filter: blur(14px) saturate(130%);
      box-shadow: inset 0 1px 0 rgba(196, 233, 255, 0.14), 0 18px 44px rgba(2, 12, 26, 0.34);
    }

    .tx-scene-on .tx-eco-panel--accent {
      background: linear-gradient(
        155deg,
        rgba(41, 184, 238, 0.16) 0%,
        rgba(10, 42, 78, 0.44) 48%,
        rgba(6, 22, 42, 0.5) 100%
      );
    }
  }
}

/* A lit top edge: the one detail that reads as glass rather than as a tint. */
.tx-eco-panel,
.tx-cube {
  --tx-glass-edge: linear-gradient(
    90deg,
    transparent 0%,
    rgba(134, 224, 255, 0.5) 22%,
    rgba(196, 240, 255, 0.75) 50%,
    rgba(134, 224, 255, 0.5) 78%,
    transparent 100%
  );
}

.tx-eco-panel > .tx-glass-edge,
.tx-cube > .tx-glass-edge {
  position: absolute;
  top: 0;
  left: 12%;
  right: 12%;
  height: 1px;
  background: var(--tx-glass-edge);
  opacity: 0.5;
  transition: opacity var(--tx-dur) var(--tx-ease), left var(--tx-dur-slow) var(--tx-ease),
    right var(--tx-dur-slow) var(--tx-ease);
  pointer-events: none;
}

.tx-eco-panel:hover > .tx-glass-edge,
.tx-cube:hover > .tx-glass-edge,
.tx-eco-panel:focus-within > .tx-glass-edge,
.tx-cube:focus-within > .tx-glass-edge {
  opacity: 1;
  left: 4%;
  right: 4%;
}

/* ============================================================
   1. Atmosphere: one fixed, continuously drifting light field behind
   every section, so scrolling reads as travelling through a single space
   instead of stepping between separate blocks.
   ============================================================ */

.tx-atmos {
  position: fixed;
  inset: -20vmax;
  z-index: -2;
  pointer-events: none;
  opacity: 0;
  transition: opacity 1.2s var(--tx-ease-out);
  contain: strict;
}

.js .tx-atmos.is-ready {
  opacity: 1;
}

.tx-atmos__orb {
  position: absolute;
  border-radius: 50%;
  filter: blur(60px);
  will-change: transform;
  /* The tint transition is what makes a dark-to-light section change feel
     like a lighting change rather than a background swap. */
  transition: background 1.4s var(--tx-ease-out);
}

.tx-atmos__orb--a {
  top: 6%;
  left: 4%;
  width: 46vmax;
  height: 46vmax;
  background: radial-gradient(circle at 50% 50%, var(--tx-atmos-a) 0%, transparent 68%);
}

.tx-atmos__orb--b {
  bottom: 2%;
  right: 0%;
  width: 52vmax;
  height: 52vmax;
  background: radial-gradient(circle at 50% 50%, var(--tx-atmos-b) 0%, transparent 66%);
}

.tx-atmos__orb--c {
  top: 42%;
  left: 46%;
  width: 34vmax;
  height: 34vmax;
  background: radial-gradient(circle at 50% 50%, rgba(86, 217, 249, 0.1) 0%, transparent 70%);
}

@media (prefers-reduced-motion: no-preference) {
  .tx-atmos__orb--a {
    animation: tx-drift-a 34s var(--tx-ease-inout) infinite alternate;
  }
  .tx-atmos__orb--b {
    animation: tx-drift-b 44s var(--tx-ease-inout) infinite alternate;
  }
  .tx-atmos__orb--c {
    animation: tx-drift-c 28s var(--tx-ease-inout) infinite alternate;
  }
}

@keyframes tx-drift-a {
  from {
    transform: translate3d(0, 0, 0) scale(1);
  }
  to {
    transform: translate3d(12vmax, 8vmax, 0) scale(1.18);
  }
}

@keyframes tx-drift-b {
  from {
    transform: translate3d(0, 0, 0) scale(1.1);
  }
  to {
    transform: translate3d(-14vmax, -10vmax, 0) scale(0.92);
  }
}

@keyframes tx-drift-c {
  from {
    transform: translate3d(-6vmax, 4vmax, 0);
  }
  to {
    transform: translate3d(8vmax, -6vmax, 0);
  }
}

/* Phones get the field without the blur cost: fewer, softer, cheaper. */
@media (max-width: 47.99em) {
  .tx-atmos__orb {
    filter: blur(40px);
  }
  .tx-atmos__orb--c {
    display: none;
  }
}

/* The bands let the field through.
   The section backgrounds in base.css and sections.css are opaque, which
   would hide the atmosphere completely. These rules restate the same
   gradients at 88% so the light behind them reads through: the composited
   colour over the page background is within a point of the original, and
   where an orb passes, the band brightens. Keep the colour stops in step
   with base.css (--tx-navy #072a54, --tx-midnight #04101f, --tx-bg #081b32)
   if those tokens ever change. */

.tx-section--light {
  background: var(--tx-bg); /* fallback for browsers without the layer below */
  background: rgba(8, 27, 50, 0.88);
}

.tx-section--dark {
  background:
    radial-gradient(120% 90% at 85% 0%, rgba(11, 123, 208, 0.35) 0%, rgba(11, 123, 208, 0) 60%),
    linear-gradient(160deg, rgba(7, 42, 84, 0.88) 0%, rgba(4, 16, 31, 0.88) 100%);
}

.tx-section--apps {
  background: linear-gradient(180deg, rgba(8, 27, 50, 0.88) 0%, rgba(4, 16, 31, 0.88) 100%);
}

/* ============================================================
   2. Scroll beam: reading progress as a hairline of brand gradient under
   the header. Native scroll-timeline where it exists, JS variable elsewhere.
   ============================================================ */

.tx-beam {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  z-index: 60;
  pointer-events: none;
  transform-origin: 0 50%;
  transform: scaleX(var(--tx-scroll, 0));
  background: linear-gradient(90deg, var(--tx-deep) 0%, var(--tx-blue) 38%, var(--tx-sky) 100%);
  opacity: 0;
  transition: opacity var(--tx-dur) var(--tx-ease);
}

.tx-beam.is-active {
  opacity: 0.9;
}

@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    .tx-beam {
      transform: scaleX(0);
      animation: tx-beam-grow linear both;
      animation-timeline: scroll(root block);
    }
  }
}

@keyframes tx-beam-grow {
  to {
    transform: scaleX(1);
  }
}

/* ============================================================
   3. Hero entrance: the page's one piece of theatre. Copy arrives in a
   single choreographed sweep, the visual settles in behind it, and the
   whole thing is over in well under a second and a half.
   ============================================================ */

.js .tx-hero__eyebrow,
.js .tx-hero__sub,
.js .tx-hero__lead,
.js .tx-hero__actions,
.js .tx-trust {
  opacity: 0;
  transform: translate3d(0, 22px, 0);
  transition: opacity 900ms var(--tx-ease-out), transform 900ms var(--tx-ease-out);
  transition-delay: var(--tx-in-delay, 0ms);
}

.js .tx-hero.is-ready .tx-hero__eyebrow,
.js .tx-hero.is-ready .tx-hero__sub,
.js .tx-hero.is-ready .tx-hero__lead,
.js .tx-hero.is-ready .tx-hero__actions,
.js .tx-hero.is-ready .tx-trust {
  opacity: 1;
  transform: none;
}

.js .tx-hero__eyebrow {
  --tx-in-delay: 80ms;
}
.js .tx-hero__sub {
  --tx-in-delay: 520ms;
}
.js .tx-hero__lead {
  --tx-in-delay: 620ms;
}
.js .tx-hero__actions {
  --tx-in-delay: 720ms;
}
.js .tx-trust {
  --tx-in-delay: 820ms;
}

/* The hero copy is held for a fifth of a second so the finished headline
   never flashes before motion.js has split it into words. This is a plain
   animation with a fill, not a class: it releases on its own, so no failure
   anywhere in the module can leave the hero hidden. Scripting-off browsers
   cancel it through no-js.css. */
.tx-hero__copy,
.tx-hero__visual {
  animation: tx-hero-hold 240ms steps(1, end) both;
}

@keyframes tx-hero-hold {
  0% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}

/* Word-level reveal for the headline. motion.js wraps each word in a span
   inside a clipping line, so the words rise out of the line above them.
   The accessible text is untouched: the spans carry the same characters. */
.tx-line {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  /* Descenders would be clipped by overflow alone. */
  padding-bottom: 0.12em;
  margin-bottom: -0.12em;
}

.js .tx-word {
  display: inline-block;
  transform: translate3d(0, 108%, 0) rotate(2.5deg);
  opacity: 0;
  transition: transform 1s var(--tx-ease-out), opacity 700ms var(--tx-ease-out);
  transition-delay: calc(var(--i, 0) * 90ms + 140ms);
}

.js .tx-hero.is-ready .tx-word {
  transform: none;
  opacity: 1;
}

/* The accent word carries a slow gradient sweep once it has landed. */
@media (prefers-reduced-motion: no-preference) {
  .tx-hero.is-ready .tx-accent-text {
    background: linear-gradient(
      100deg,
      var(--tx-cyan) 0%,
      var(--tx-sky) 30%,
      #ffffff 46%,
      var(--tx-sky) 62%,
      var(--tx-cyan) 100%
    );
    background-size: 280% 100%;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    animation: tx-sheen 7s linear 1.8s infinite;
  }
}

@keyframes tx-sheen {
  0% {
    background-position: 140% 0;
  }
  45%,
  100% {
    background-position: -140% 0;
  }
}

/* The hero visual scales up out of its own glow. */
.js .tx-hero__visual {
  opacity: 0;
  transform: scale(0.9) translate3d(0, 14px, 0);
  transition: opacity 1.4s var(--tx-ease-out), transform 1.6s var(--tx-ease-out);
  transition-delay: 260ms;
}

.js .tx-hero.is-ready .tx-hero__visual {
  opacity: 1;
  transform: none;
}

.js .tx-hero__net,
.js .tx-hero__aurora {
  opacity: 0;
  transition: opacity 1.8s var(--tx-ease-out);
  transition-delay: 420ms;
}

.js .tx-hero.is-ready .tx-hero__net {
  opacity: 1;
}

.js .tx-hero.is-ready .tx-hero__aurora {
  opacity: 1;
}

/* A scanline sweeps the hero once on arrival: one pass, then gone. */
.tx-hero__sweep {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: 0;
  background: linear-gradient(
    180deg,
    transparent 0%,
    rgba(86, 217, 249, 0.06) 44%,
    rgba(86, 217, 249, 0.16) 50%,
    rgba(86, 217, 249, 0.06) 56%,
    transparent 100%
  );
}

@media (prefers-reduced-motion: no-preference) {
  .tx-hero.is-ready .tx-hero__sweep {
    animation: tx-hero-sweep 2.4s var(--tx-ease-inout) 260ms 1;
  }
}

@keyframes tx-hero-sweep {
  0% {
    opacity: 0;
    transform: translate3d(0, -100%, 0);
  }
  30% {
    opacity: 1;
  }
  100% {
    opacity: 0;
    transform: translate3d(0, 100%, 0);
  }
}

/* ============================================================
   4. Reveals with depth. The base rule in base.css stays as the floor;
   these variants add distance, scale and a short focus pull, and take
   their stagger from the index motion.js writes into --i.
   ============================================================ */

@media (prefers-reduced-motion: no-preference) {
  .js .tx-reveal {
    transform: translate3d(0, 32px, 0) scale(0.985);
    transition: opacity 900ms var(--tx-ease-out), transform 1s var(--tx-ease-out),
      filter 900ms var(--tx-ease-out);
  }

  /* --i beats the nth-child ladder in base.css because it lands later. */
  .js .tx-reveal[style*="--i"] {
    transition-delay: calc(var(--i, 0) * 85ms);
  }

  .js .tx-reveal.is-visible {
    transform: none;
    filter: none;
  }

  .js [data-reveal="left"] {
    transform: translate3d(-40px, 12px, 0);
  }
  .js [data-reveal="right"] {
    transform: translate3d(40px, 12px, 0);
  }
  .js [data-reveal="scale"] {
    transform: scale(0.94);
  }
  .js [data-reveal="rise"] {
    transform: translate3d(0, 56px, 0);
  }
  .js [data-reveal].is-visible {
    transform: none;
  }
}

/* ============================================================
   5. Section seams. A soft horizon where two bands meet, lit from the
   brand gradient, so dark and light sections dissolve into each other.
   ============================================================ */

.tx-seam {
  position: absolute;
  top: -1px;
  left: 0;
  right: 0;
  height: clamp(3.5rem, 8vw, 7rem);
  z-index: 0;
  pointer-events: none;
  background: linear-gradient(
    180deg,
    rgba(41, 184, 238, 0.09) 0%,
    rgba(41, 184, 238, 0.028) 38%,
    transparent 100%
  );
  transform: translateZ(0);
}

.tx-seam::after {
  content: "";
  position: absolute;
  top: 0;
  left: 8%;
  right: 8%;
  height: 1px;
  background: linear-gradient(
    90deg,
    transparent 0%,
    rgba(41, 184, 238, 0.5) 30%,
    rgba(86, 217, 249, 0.75) 50%,
    rgba(41, 184, 238, 0.5) 70%,
    transparent 100%
  );
  opacity: 0.5;
  filter: blur(0.2px);
}

/* Where the browser can drive an animation from scroll position, the seam
   lights up as it crosses the viewport and dims again on the way out. No
   JavaScript, no scroll listener, no main-thread work. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .tx-seam::after {
      animation: tx-seam-light linear both;
      animation-timeline: view();
      animation-range: entry 10% cover 45%;
    }
  }
}

@keyframes tx-seam-light {
  from {
    opacity: 0.18;
    transform: scaleX(0.55);
  }
  to {
    opacity: 1;
    transform: scaleX(1);
  }
}

/* ============================================================
   6. Cards that answer the cursor. motion.js publishes the pointer
   position on the card as --mx/--my; everything else is CSS.
   ============================================================ */

@media (hover: hover) and (pointer: fine) {
  .tx-eco-panel,
  .tx-cube {
    transform-style: preserve-3d;
  }

  /* Spotlight: a soft light that tracks the cursor across the surface. */
  .tx-eco-panel::before,
  .tx-cube::before {
    background:
      radial-gradient(
        22rem 22rem at var(--mx, 50%) var(--my, 0%),
        rgba(86, 217, 249, 0.16) 0%,
        rgba(41, 184, 238, 0.06) 42%,
        transparent 72%
      ),
      radial-gradient(80% 62% at 0% 0%, rgba(41, 184, 238, 0.14) 0%, rgba(41, 184, 238, 0) 62%);
    transition: opacity var(--tx-dur) var(--tx-ease);
  }

  .tx-eco-panel:hover::before,
  .tx-cube:hover::before {
    opacity: 1;
  }

  /* Tilt. Small angles on purpose: the card should feel physical, not
     theatrical. The lift is separate so hover keeps its existing rise. */
  @media (prefers-reduced-motion: no-preference) {
    .tx-eco-panel[data-tilt],
    .tx-cube[data-tilt] {
      transition: transform 320ms var(--tx-ease-out), box-shadow var(--tx-dur) var(--tx-ease),
        border-color var(--tx-dur) var(--tx-ease);
    }

    .tx-eco-panel[data-tilt].is-tilting,
    .tx-cube[data-tilt].is-tilting {
      transform: perspective(900px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg))
        translate3d(0, -4px, 0);
      transition-duration: 120ms;
    }

    /* The cube's icon and title ride slightly above the surface. */
    .tx-cube[data-tilt].is-tilting .tx-cube__icon {
      transform: translate3d(0, 0, 26px);
    }

    .tx-eco-panel[data-tilt].is-tilting .tx-eco-panel__icon {
      transform: translate3d(0, 0, 18px);
    }
  }

  .tx-eco-panel__icon,
  .tx-cube__icon {
    transition: transform 320ms var(--tx-ease-out), box-shadow var(--tx-dur) var(--tx-ease);
  }

  /* Cursor-aware edge light: the gradient border brightens on the side the
     pointer is on, which is what sells the surface as glass. */
  .tx-eco-panel::after,
  .tx-cube::after {
    background: radial-gradient(
      16rem 16rem at var(--mx, 50%) var(--my, 50%),
      rgba(86, 217, 249, 0.95) 0%,
      rgba(41, 184, 238, 0.42) 38%,
      transparent 70%
    );
  }
}

/* Labs cubes: a sheen crosses the card on hover, once. */
.tx-cube__sheen {
  position: absolute;
  inset: -30% -60%;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  background: linear-gradient(
    100deg,
    transparent 38%,
    rgba(255, 255, 255, 0.1) 48%,
    rgba(134, 231, 255, 0.16) 52%,
    transparent 62%
  );
  transform: translate3d(-60%, 0, 0) rotate(8deg);
}

@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .tx-cube:hover .tx-cube__sheen,
  .tx-cube:focus-within .tx-cube__sheen {
    opacity: 1;
    transform: translate3d(60%, 0, 0) rotate(8deg);
    transition: transform 900ms var(--tx-ease-out), opacity 300ms linear;
  }

  /* Platform badges cascade instead of all lifting together. */
  .tx-cube .tx-platforms li {
    transition: transform 420ms var(--tx-ease-out), border-color var(--tx-dur) var(--tx-ease),
      background-color var(--tx-dur) var(--tx-ease);
  }

  .tx-cube:hover .tx-platforms li {
    transform: translateY(-3px);
    border-color: rgba(41, 184, 238, 0.45);
  }

  .tx-cube:hover .tx-platforms li:nth-child(2) {
    transition-delay: 60ms;
  }
  .tx-cube:hover .tx-platforms li:nth-child(3) {
    transition-delay: 120ms;
  }
}

/* ============================================================
   7. The cybersecurity section, alive.
   The lifecycle is a cycle, so it behaves like one: a marker walks the four
   stages continuously while the block is on screen, and stops when it is not.
   ============================================================ */

.tx-layer {
  position: relative;
  overflow: hidden;
  transition: background-color 600ms var(--tx-ease-out), border-color 600ms var(--tx-ease-out),
    transform 600ms var(--tx-ease-out);
}

.tx-layer__index {
  transition: color 500ms var(--tx-ease-out), background-color 500ms var(--tx-ease-out),
    box-shadow 500ms var(--tx-ease-out), transform 500ms var(--tx-ease-out);
}

.tx-layers.is-cycling .tx-layer.is-active {
  border-color: rgba(86, 217, 249, 0.55);
  transform: translateX(4px);
}

.tx-layers.is-cycling .tx-layer.is-active .tx-layer__index {
  color: var(--tx-midnight);
  background: linear-gradient(135deg, var(--tx-sky) 0%, var(--tx-cyan) 100%);
  box-shadow: 0 0 0 4px rgba(86, 217, 249, 0.16), 0 6px 18px rgba(41, 184, 238, 0.34);
  transform: scale(1.06);
}

/* The scan itself: a band of light that crosses the active stage. */
.tx-layer::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  background: linear-gradient(
    90deg,
    transparent 0%,
    rgba(86, 217, 249, 0.14) 45%,
    rgba(86, 217, 249, 0.22) 50%,
    rgba(86, 217, 249, 0.14) 55%,
    transparent 100%
  );
}

@media (prefers-reduced-motion: no-preference) {
  .tx-layers.is-cycling .tx-layer.is-active::after {
    animation: tx-scan 1.5s var(--tx-ease-inout) 1;
  }
}

@keyframes tx-scan {
  0% {
    opacity: 0;
    transform: translate3d(-100%, 0, 0);
  }
  25% {
    opacity: 1;
  }
  100% {
    opacity: 0;
    transform: translate3d(100%, 0, 0);
  }
}

/* The cycle note brightens for one lap, which is the only place the page
   tells you the loop restarted. The glyph itself already spins in
   sections.css, so this touches colour and nothing else. */
.tx-layers__cycle {
  transition: color 1.2s var(--tx-ease-out), opacity 1.2s var(--tx-ease-out);
}

.tx-layers.is-cycling.is-looping .tx-layers__cycle {
  color: var(--tx-sky);
  opacity: 1;
}

/* The shield keeps a slow breath while it is in view. */
@media (prefers-reduced-motion: no-preference) {
  .tx-shield-viz-wrap.is-live .tx-shield-viz {
    animation: tx-breathe 6s var(--tx-ease-inout) infinite;
  }
}

@keyframes tx-breathe {
  0%,
  100% {
    transform: scale(1);
    filter: drop-shadow(0 0 12px rgba(41, 184, 238, 0.25));
  }
  50% {
    transform: scale(1.035);
    filter: drop-shadow(0 0 24px rgba(86, 217, 249, 0.4));
  }
}

/* Cyber service cards get a data-grid wash that only appears on hover. */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .tx-cyber-grid .tx-eco-panel__benefit {
    position: relative;
    transition: transform 420ms var(--tx-ease-out), color 420ms var(--tx-ease-out);
  }

  .tx-cyber-grid .tx-eco-panel:hover .tx-eco-panel__benefit {
    transform: translateX(3px);
    color: var(--tx-sky);
  }
}

/* ------------------------------------------------------------------
   The data grid drifts. Both dark bands already carry a masked grid; giving
   it one slow vertical pass makes the surface feel like a live readout
   instead of wallpaper. The travel equals one tile, so it never seams.
   ------------------------------------------------------------------ */

@media (prefers-reduced-motion: no-preference) {
  .tx-section--dark::before {
    inset: -64px 0;
    animation: tx-grid-drift-56 26s linear infinite;
  }

  .tx-section--apps::before {
    inset: -72px 0;
    animation: tx-grid-drift-64 32s linear infinite;
  }
}

@keyframes tx-grid-drift-56 {
  from {
    transform: translate3d(0, 0, 0);
  }
  to {
    transform: translate3d(0, 56px, 0);
  }
}

@keyframes tx-grid-drift-64 {
  from {
    transform: translate3d(0, 0, 0);
  }
  to {
    transform: translate3d(0, 64px, 0);
  }
}

/* ------------------------------------------------------------------
   Radar. A single slow sweep behind the lifecycle panel, at the edge of
   visible, so the block reads as something that is watching rather than
   something that is drawn.
   ------------------------------------------------------------------ */

.tx-layers {
  position: relative;
  isolation: isolate;
  overflow: hidden;
}

.tx-layers::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 160%;
  aspect-ratio: 1;
  z-index: -1;
  translate: -50% -50%;
  pointer-events: none;
  opacity: 0;
  background: conic-gradient(
    from 0deg,
    rgba(86, 217, 249, 0.16) 0deg,
    rgba(86, 217, 249, 0.05) 24deg,
    transparent 60deg,
    transparent 360deg
  );
  mask-image: radial-gradient(circle at 50% 50%, #000 0%, transparent 72%);
  -webkit-mask-image: radial-gradient(circle at 50% 50%, #000 0%, transparent 72%);
  transition: opacity 1.2s var(--tx-ease-out);
}

@media (prefers-reduced-motion: no-preference) {
  .tx-layers.is-cycling::before {
    opacity: 1;
    animation: tx-radar 9.6s linear infinite;
  }
}

@keyframes tx-radar {
  from {
    rotate: 0deg;
  }
  to {
    rotate: 360deg;
  }
}

/* ============================================================
   8. Micro-interactions: buttons, navigation, links, form.
   ============================================================ */

/* Magnetic pull. motion.js writes --tx-pull-x/y from the cursor offset; the
   button returns to rest with a spring-ish ease when the pointer leaves. */
@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
  .tx-btn[data-magnetic] {
    transition: transform 480ms var(--tx-ease-out), box-shadow var(--tx-dur) var(--tx-ease),
      background-color var(--tx-dur-fast) var(--tx-ease), color var(--tx-dur-fast) var(--tx-ease);
  }

  .tx-btn[data-magnetic].is-pulled {
    transform: translate3d(var(--tx-pull-x, 0px), var(--tx-pull-y, 0px), 0) scale(1.03);
    transition-duration: 140ms;
  }

  .tx-btn[data-magnetic]:active {
    transform: translate3d(var(--tx-pull-x, 0px), var(--tx-pull-y, 0px), 0) scale(0.97);
    transition-duration: 90ms;
  }
}

/* Navigation: one indicator slides between links rather than each link
   drawing its own underline. */
.tx-nav__list {
  position: relative;
}

.tx-nav__marker {
  position: absolute;
  left: 0;
  bottom: -0.35rem;
  height: 2px;
  width: var(--w, 0px);
  border-radius: 2px;
  background: linear-gradient(90deg, var(--tx-blue) 0%, var(--tx-sky) 100%);
  transform: translate3d(var(--x, 0px), 0, 0);
  opacity: 0;
  pointer-events: none;
  transition: transform 420ms var(--tx-ease-out), width 420ms var(--tx-ease-out),
    opacity 220ms linear;
}

.tx-nav__list.is-marking .tx-nav__marker {
  opacity: 1;
}

/* Header: glass tightens as the page leaves the hero. */
.tx-header {
  transition: backdrop-filter 400ms var(--tx-ease-out), background-color 400ms var(--tx-ease-out),
    box-shadow 400ms var(--tx-ease-out), border-color 400ms var(--tx-ease-out);
}

.tx-header.is-stuck {
  box-shadow: 0 10px 40px rgba(4, 16, 31, 0.45);
}

/* Form fields: focus lights the field rather than only outlining it. */
.tx-input,
.tx-select,
.tx-textarea {
  transition: border-color var(--tx-dur) var(--tx-ease), box-shadow var(--tx-dur) var(--tx-ease),
    background-color var(--tx-dur) var(--tx-ease), transform var(--tx-dur) var(--tx-ease);
}

.tx-field:focus-within .tx-input,
.tx-field:focus-within .tx-select,
.tx-field:focus-within .tx-textarea {
  box-shadow: 0 0 0 1px rgba(41, 184, 238, 0.4), 0 8px 28px rgba(11, 123, 208, 0.18);
}

@media (prefers-reduced-motion: no-preference) {
  .tx-field label {
    transition: color 260ms var(--tx-ease-out), transform 260ms var(--tx-ease-out);
    transform-origin: left center;
  }
  .tx-field:focus-within label {
    color: var(--tx-sky);
    transform: translateX(2px);
  }
}

/* While the request is in flight the submit button runs a light bar. */
@media (prefers-reduced-motion: no-preference) {
  .tx-form--contact.is-sending [data-form-submit] {
    background-size: 220% 100%;
    background-image: linear-gradient(
      100deg,
      var(--tx-blue) 0%,
      var(--tx-sky) 45%,
      var(--tx-blue) 90%
    );
    animation: tx-sending 1.4s linear infinite;
  }
}

@keyframes tx-sending {
  from {
    background-position: 140% 0;
  }
  to {
    background-position: -140% 0;
  }
}

/* Arrow links lead with the arrow on hover. */
@media (prefers-reduced-motion: no-preference) {
  .tx-link-arrow svg,
  .tx-btn svg {
    transition: transform 320ms var(--tx-ease-out);
  }
  .tx-link-arrow:hover svg {
    transform: translateX(4px);
  }
}

/* ============================================================
   9. Parallax hooks. Depth comes from elements moving at different
   rates; motion.js only writes the offset, CSS decides how it is applied.
   ============================================================ */

[data-parallax] {
  will-change: transform;
  transform: translate3d(0, var(--tx-py, 0px), 0);
}

@media (prefers-reduced-motion: reduce) {
  [data-parallax] {
    transform: none;
  }
}

/* ============================================================
   10. Reduced motion: everything decorative stops. Content that the
   reveal system hides must still end up visible, which the JS guarantees
   by marking it visible immediately; these rules remove the transforms.
   ============================================================ */

@media (prefers-reduced-motion: reduce) {
  .tx-hero__copy,
  .tx-hero__visual {
    animation: none;
  }

  .tx-atmos__orb,
  .tx-hero__sweep,
  .tx-cube__sheen,
  .tx-layer::after {
    animation: none !important;
    transition: none !important;
  }

  .tx-hero__sweep,
  .tx-cube__sheen {
    display: none;
  }

  .js .tx-word,
  .js .tx-hero__eyebrow,
  .js .tx-hero__sub,
  .js .tx-hero__lead,
  .js .tx-hero__actions,
  .js .tx-trust,
  .js .tx-hero__visual,
  .js .tx-hero__net,
  .js .tx-hero__aurora {
    opacity: 1;
    transform: none;
    transition: none;
  }

  .tx-beam {
    display: none;
  }

  .tx-nav__marker {
    transition: none;
  }
}
