/* ============================================================
   Visitech — Section skeleton + backdrops
   The ONE contract every container block renders against: hero today,
   every section that follows. Stands on tokens.css.
   Load order: tokens → base → components → sections → chrome.

   Five axes, identical attribute names on every container block
   (see visitech-core/inc/section-skeleton.php):

     width      column width          content | wide | narrow | full
     air        vertical air          none | s | m | l | xl
     justify    justification         left | center | right
     backdrop   the background        none | subtle | strip | mist | mist-fade | aurora
                                      | dmd-echo | cinematic | deep | night | ink
                                      | dmd | map
     tone       text colour scheme    auto | light | dark   (auto = from backdrop)

   Geometry axes are CLASSES that set custom properties; atoms read the
   properties and never know which section they sit in.
   ============================================================ */

/* ------------------------------------------------------------
   1 · The band
   ------------------------------------------------------------ */
.vt-section {
  /* defaults — every axis class below only overrides what it owns */
  --vt-column: var(--layout-column);
  --vt-air: 96px;
  --vt-air-narrow: 64px;
  --vt-align: flex-start;
  --vt-text-align: left;
  --vt-fg: var(--color-text);
  --vt-fg-muted: var(--color-text-muted);
  --vt-fg-subtle: var(--color-text-subtle);
  /* the gradient an emphasised word wears — brand wash on a light surface */
  --vt-title-accent: var(--gradient-brand);

  position: relative;
  isolation: isolate;
  color: var(--vt-fg);

  /* named container so descendants can query the BAND width.
     A block can never query its own container, so all container queries in
     block CSS target `.vt-section__inner` or deeper. */
  container-type: inline-size;
  container-name: vt-section;
}

/* A band is FLUSH: its vertical air is its spacing. Core's flow block-gap
   (`:root :where(.is-layout-flow) > *`, 24px by default) would otherwise put
   a strip of page background between two full-bleed backdrops — measured on
   every section after the first on the LLS1000 page (2026-08-22). Scoped to
   .vt-section on purpose: prose in a flow container (a news post) keeps its
   paragraph rhythm. (0,2,0) beats core's (0,1,0) whatever the sheet order. */
.is-layout-flow > .vt-section,
.is-layout-constrained > .vt-section {
  margin-block-start: 0;
  margin-block-end: 0;
}

/* The column. Band = column + 2 x gutter, so the gutter can change without
   moving the column (see --layout-column in tokens.css). */
.vt-section__inner {
  position: relative;
  z-index: 2;                 /* above every backdrop layer */
  width: 100%;
  max-width: calc(var(--vt-column) + 2 * var(--layout-gutter));
  margin-inline: auto;
  padding-inline: var(--layout-gutter);
  padding-block: var(--vt-air);
  text-align: var(--vt-text-align);
}

/* ------------------------------------------------------------
   2 · Axis: width
   ------------------------------------------------------------ */
.vt-section--w-content { --vt-column: var(--layout-column); }
.vt-section--w-wide    { --vt-column: var(--layout-column-wide); }
.vt-section--w-narrow  { --vt-column: var(--layout-column-narrow); }
.vt-section--w-full > .vt-section__inner { max-width: none; }

/* ------------------------------------------------------------
   3 · Axis: air
   Values are the designer's own rhythm: 96 (home sections), 120 (product
   sections), 88 (hero inner), collapsing at the 980px breakpoint the
   mockups use.
   ------------------------------------------------------------ */
.vt-section--air-none { --vt-air: 0;     --vt-air-narrow: 0; }
.vt-section--air-s    { --vt-air: 64px;  --vt-air-narrow: 48px; }
.vt-section--air-m    { --vt-air: 88px;  --vt-air-narrow: 64px; }
.vt-section--air-l    { --vt-air: 96px;  --vt-air-narrow: 64px; }
.vt-section--air-xl   { --vt-air: 120px; --vt-air-narrow: 80px; }

@container vt-section (max-width: 980px) {
  .vt-section__inner { padding-block: var(--vt-air-narrow); }
}

/* ------------------------------------------------------------
   4 · Axis: justify
   `align` is WordPress's own block-support attribute name, so the skeleton's
   justification axis is called `justify`.
   ------------------------------------------------------------ */
.vt-section--j-left   { --vt-align: flex-start; --vt-text-align: left; }
.vt-section--j-center { --vt-align: center;     --vt-text-align: center; }
.vt-section--j-right  { --vt-align: flex-end;   --vt-text-align: right; }

/* ------------------------------------------------------------
   5 · Axis: tone — the only thing atoms need to know about the surface
   ------------------------------------------------------------ */
.vt-section.is-dark {
  --vt-fg: #ffffff;
  --vt-fg-muted:  color-mix(in srgb, #ffffff 82%, transparent);
  --vt-fg-subtle: color-mix(in srgb, #ffffff 78%, transparent);
  /* electric periwinkle reads on a dark ground where the brand wash goes muddy */
  --vt-title-accent: var(--gradient-electric);
}

/* ------------------------------------------------------------
   6 · Axis: backdrop
   Flat surfaces are a background on the band itself; layered ones render a
   .vt-backdrop element (canvas / glows / scrim) BEHIND .vt-section__inner.
   No token carries an opacity: every translucent value is mixed here, at
   the use site, from a solid token.
   ------------------------------------------------------------ */
.vt-section--bd-subtle    { background: var(--color-bg-subtle); }
/* The homepage news band — the mockup's `.eventnews` (styles/home.css), whose
   comment is the design rule: "defined light-grey gradient — stays grey (the
   offerings section below is white) so the band reads as a distinct strip
   (Petter 18/6)". `subtle` (#faf9fb) is too faint to read as a strip, so this is
   its own value rather than a restyle of that one. The two stops are the
   mockup's own and no token carries them; the hairlines are the divider token.
   Added 2026-09-28 for visitech/news-teaser. */
.vt-section--bd-strip {
  background: linear-gradient(180deg, #e7e5ee 0%, #f5f3f9 100%);
  border-block: 1px solid var(--color-divider);
}
.vt-section--bd-mist      { background: var(--color-light-purple-10); }
/* the product/page hero surface: mist at the top, white by 60% — so the band
   below it (white) joins without an edge. The token already existed. */
.vt-section--bd-mist-fade { background: var(--gradient-surface-purple); }
.vt-section--bd-cinematic { background: var(--gradient-cinematic); }
/* The mockup's DARK page hero — styles/hero.css `.uhero--dark`, the variant it
   reserves for brand / category / company / news, where `.uhero--light` is the
   product pages'. Three layers, and the mockup paints them in two places for a
   reason that does not survive the port: the deep navy bloom is the element's
   own `background` and the two soft glows sit on a `::before`, because
   `.uhero--dark` is a variant class written over a base that already had a
   background. Nothing here animates and nothing composites separately, so the
   three become one stack — first listed paints on top, which is the same order
   a pseudo-element over a background produces. Verified identical.

   Both glow colours ARE tokens at the mockup's own values (#6e8bff electric
   periwinkle, #a068b1 mystic lavender); only the alpha is mixed here, per the
   rule that no token carries an opacity. */
.vt-section--bd-deep {
  background:
    radial-gradient(58% 60% at 30% 46%, color-mix(in srgb, var(--color-electric-blue) 20%, transparent), transparent 70%),
    radial-gradient(46% 48% at 78% 64%, color-mix(in srgb, var(--color-light-purple) 16%, transparent), transparent 72%),
    var(--gradient-feature-dark);
}
.vt-section--bd-night     { background: var(--gradient-night); }
.vt-section--bd-ink       { background: var(--color-ink); }
.vt-section--bd-map       { background: var(--color-ink); }   /* the map itself is a layer */
.vt-section--bd-aurora    { background: var(--color-light-purple-10); }
.vt-section--bd-dmd       { background: var(--gradient-cinematic); }   /* base + fallback behind the canvas */
.vt-section--bd-dmd-echo  { background: var(--color-light-purple-10); }
/* the film hero's ground — the mockup's own #0d0a18 behind the video
   (styles/home.css `.hero--video`), which is this token exactly. It is what
   shows before the film buffers, wherever the film is not played at all, and
   under a film that does not cover the band. */
.vt-section--bd-video     { background: var(--color-ink); }
/* the photograph hero's ground — the same ink the film sits on, for the same
   three reasons: it shows before the picture decodes, wherever no picture is
   chosen, and under a picture that does not cover the band. */
.vt-section--bd-image     { background: var(--color-ink); }

.vt-backdrop {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;           /* clips the aurora blobs; the band itself stays unclipped */
  pointer-events: none;
}

/* -- the WebGL micromirror field (assets/js/dmd-field.js) -- */
.vt-backdrop__canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}
/* reduced motion / no-WebGL / phones: the canvas never initialises and a
   static glow stands in. Set by the script, mirrored by editor.css. */
.vt-section--bd-dmd.is-static .vt-backdrop__canvas,
.vt-section--bd-dmd-echo.is-static .vt-backdrop__canvas { display: none; }
.vt-section--bd-dmd.is-static .vt-backdrop__canvas-fallback {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(58% 58% at 72% 42%, color-mix(in srgb, var(--color-electric-blue) 30%, transparent), transparent 70%),
    radial-gradient(46% 46% at 30% 78%, color-mix(in srgb, var(--color-light-purple) 22%, transparent), transparent 72%);
}

/* -- the film (assets/js/backdrop-video.js) --
   No `display: none` twin of the canvas's `.is-static` rule below, because the
   film needs none: the server renders the <video> WITHOUT `autoplay`, so an
   unplayed film IS its poster frame, in place and correctly sized. Reduced
   motion, phones and metered connections are decided in the script for that
   reason — a CSS rule can hide a video, it cannot stop it playing. */
.vt-backdrop__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  display: block;
}

/* -- the photograph (Section\image_layer()) --
   An <img> rather than a background-image, so a phone gets a phone-sized file
   off the srcset. Everything a background would have given for free is here. */
.vt-backdrop__image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  display: block;
}

/* -- scrim: legibility wash over a moving/photographic backdrop --
   auto = the mockup's localized "pool" behind a left-aligned text column;
   the right and top stay clear so the field still reads.
   strong = the whole-frame wash the video hero needs. */
.vt-backdrop__scrim { position: absolute; inset: 0; }

.vt-backdrop__scrim--auto {
  background:
    radial-gradient(58% 68% at 24% 52%,
      color-mix(in srgb, var(--color-ink) 62%, transparent) 0%,
      color-mix(in srgb, var(--color-ink) 30%, transparent) 45%,
      transparent 75%),
    linear-gradient(90deg,
      color-mix(in srgb, var(--color-ink) 86%, transparent) 0%,
      color-mix(in srgb, var(--color-ink) 60%, transparent) 42%,
      color-mix(in srgb, var(--color-ink) 24%, transparent) 66%,
      transparent 86%),
    linear-gradient(0deg,
      color-mix(in srgb, var(--color-ink) 66%, transparent) 0%,
      transparent 46%);
}
/* centred text needs the pool centred too, or the right half loses contrast */
.vt-section--j-center .vt-backdrop__scrim--auto {
  background:
    radial-gradient(64% 72% at 50% 52%,
      color-mix(in srgb, var(--color-ink) 62%, transparent) 0%,
      color-mix(in srgb, var(--color-ink) 34%, transparent) 48%,
      transparent 78%),
    linear-gradient(0deg,
      color-mix(in srgb, var(--color-ink) 66%, transparent) 0%,
      transparent 46%);
}
/* and right-aligned text needs it mirrored — the pool follows the column, or
   the text sits on the clear side of the field and loses its contrast. Same
   stops as the left version, reflected: 24% -> 76%, 90deg -> 270deg. */
.vt-section--j-right .vt-backdrop__scrim--auto {
  background:
    radial-gradient(58% 68% at 76% 52%,
      color-mix(in srgb, var(--color-ink) 62%, transparent) 0%,
      color-mix(in srgb, var(--color-ink) 30%, transparent) 45%,
      transparent 75%),
    linear-gradient(270deg,
      color-mix(in srgb, var(--color-ink) 86%, transparent) 0%,
      color-mix(in srgb, var(--color-ink) 60%, transparent) 42%,
      color-mix(in srgb, var(--color-ink) 24%, transparent) 66%,
      transparent 86%),
    linear-gradient(0deg,
      color-mix(in srgb, var(--color-ink) 66%, transparent) 0%,
      transparent 46%);
}
.vt-backdrop__scrim--strong {
  background:
    linear-gradient(90deg,
      color-mix(in srgb, var(--color-ink) 85%, transparent) 0%,
      color-mix(in srgb, var(--color-ink) 55%, transparent) 42%,
      color-mix(in srgb, var(--color-ink) 22%, transparent) 100%),
    linear-gradient(0deg,
      color-mix(in srgb, var(--color-ink) 72%, transparent) 0%,
      transparent 52%);
}
/* the light field needs a hair-thin veil, not a wash, to keep body text crisp */
.vt-backdrop__scrim--veil {
  background: color-mix(in srgb, var(--color-light-purple-10) 22%, transparent);
}
/* film: the mockup's `.hero__video-overlay` (styles/home.css) to the value —
   a whole-frame diagonal wash with a deep-purple middle, not the localized pool
   `auto` pours behind a text column. Both colours are tokens at the mockup's own
   hexes (#2a2a32 anthracite, #302c80 deep purple); only the alpha is mixed here,
   per the rule that no token carries an opacity. */
.vt-backdrop__scrim--film {
  background: linear-gradient(160deg,
    color-mix(in srgb, var(--color-purple-black) 60%, transparent) 0%,
    color-mix(in srgb, var(--color-dark-blue) 42%, transparent) 55%,
    color-mix(in srgb, var(--color-purple-black) 72%, transparent) 100%);
}

/* photograph: the mockup's `.uhero--photo` (styles/contact.css) to the value —
   a SIDE wash, not the film's corner-to-corner one. 100deg keeps the ink under a
   left-aligned text column and lets the right of the picture come through, which
   is the whole reason a photograph is there. The colour is --color-ink, the
   mockup's own #0d0a18, with only the alphas mixed here per the rule that no
   token carries an opacity. */
.vt-backdrop__scrim--photo {
  background: linear-gradient(100deg,
    color-mix(in srgb, var(--color-ink) 90%, transparent) 0%,
    color-mix(in srgb, var(--color-ink) 62%, transparent) 42%,
    color-mix(in srgb, var(--color-ink) 28%, transparent) 100%);
}
/* The wash follows the column, exactly as `--auto` does: a centred or
   right-aligned heading over the clear side of a photograph loses its contrast,
   and the fix is the same gradient reflected rather than a second scrim value an
   editor has to remember to pick. Same stops, mirrored: 100deg -> 260deg. */
.vt-section--j-center .vt-backdrop__scrim--photo {
  background:
    radial-gradient(70% 78% at 50% 52%,
      color-mix(in srgb, var(--color-ink) 78%, transparent) 0%,
      color-mix(in srgb, var(--color-ink) 46%, transparent) 52%,
      color-mix(in srgb, var(--color-ink) 28%, transparent) 100%);
}
.vt-section--j-right .vt-backdrop__scrim--photo {
  background: linear-gradient(260deg,
    color-mix(in srgb, var(--color-ink) 90%, transparent) 0%,
    color-mix(in srgb, var(--color-ink) 62%, transparent) 42%,
    color-mix(in srgb, var(--color-ink) 28%, transparent) 100%);
}

/* -- aurora: two slow drifting glows -- */
.vt-backdrop__glow {
  position: absolute;
  border-radius: 50%;
  filter: blur(70px);
  will-change: transform;
}
.vt-backdrop__glow--a {
  width: 55%; height: 170%; left: -8%; top: -35%;
  background: radial-gradient(circle, color-mix(in srgb, var(--color-light-purple) 28%, transparent), transparent 70%);
  animation: vt-aurora-a 22s ease-in-out infinite alternate;
}
.vt-backdrop__glow--b {
  width: 50%; height: 160%; right: -6%; bottom: -35%;
  background: radial-gradient(circle, color-mix(in srgb, var(--color-light-blue) 20%, transparent), transparent 70%);
  animation: vt-aurora-b 27s ease-in-out infinite alternate;
}
@keyframes vt-aurora-a { from { transform: translate(0, 0) scale(1); }  to { transform: translate(18%, 12%) scale(1.22); } }
@keyframes vt-aurora-b { from { transform: translate(0, 0) scale(1); }  to { transform: translate(-16%, -10%) scale(1.18); } }

/* -- world map: the faint continents, and one pulsing marker per office --
   Ported from the mockup's .worldmap + .geo__nodes (styles/lithography.css,
   the lithography page's #why band). The markers come from Visitech →
   Settings → Locations (each office's "Map marker" position); the plugin's
   render_backdrop() writes one .vt-map-node per office that has one.
   Pure CSS — transform/opacity animations only, no script. */
.vt-backdrop__map {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 60%;                          /* the whole map clears the band (mockup, Petter 18/6) */
  aspect-ratio: 2000 / 1001;
  container-type: inline-size;         /* cqw → ring size scales with the map width */
  --reach: 16cqw;                      /* max ring diameter */
  --ping-dur: 6s;                      /* one emission cycle */
  --ring-opacity: 0.5;                 /* peak ring brightness */
}
.vt-backdrop__map-img {
  width: 100%;
  height: 100%;
  display: block;
  opacity: 0.17;                       /* the mockup's "dots" variant — a touch above the plain 0.13 */
}
.vt-map-node { position: absolute; left: var(--x); top: var(--y); width: 0; height: 0; }
.vt-map-node > * { position: absolute; left: 0; top: 0; }
.vt-map-node__ring {
  width: var(--reach);
  aspect-ratio: 1;
  border-radius: 50%;
  border: 1.5px solid color-mix(in srgb, var(--color-light-purple) 85%, transparent);
  transform: translate(-50%, -50%) scale(0.06);
  opacity: 0;
  animation: vt-map-radar var(--ping-dur) cubic-bezier(0.16, 0.7, 0.3, 1) infinite;
  animation-delay: calc(var(--ping-dur) * var(--ring-frac) + var(--phase));
}
@keyframes vt-map-radar {
  0%   { transform: translate(-50%, -50%) scale(0.06); opacity: 0; }
  8%   { opacity: var(--ring-opacity); }
  100% { transform: translate(-50%, -50%) scale(1); opacity: 0; }
}
.vt-map-node__core {
  width: 13px;
  height: 13px;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  background: var(--color-light-purple);
  box-shadow:
    0 0 0 4px color-mix(in srgb, var(--color-light-purple) 22%, transparent),
    0 0 14px 3px color-mix(in srgb, var(--color-light-purple) 75%, transparent);
  animation: vt-map-core var(--ping-dur) ease-in-out infinite;
  animation-delay: var(--phase);
}
@keyframes vt-map-core {
  0%, 100% {
    box-shadow:
      0 0 0 3px color-mix(in srgb, var(--color-light-purple) 20%, transparent),
      0 0 12px 1px color-mix(in srgb, var(--color-light-purple) 65%, transparent);
  }
  50% {
    box-shadow:
      0 0 0 7px color-mix(in srgb, var(--color-light-purple) 10%, transparent),
      0 0 20px 4px color-mix(in srgb, var(--color-light-purple) 95%, transparent);
  }
}

@media (prefers-reduced-motion: reduce) {
  .vt-backdrop__glow { animation: none; }
  /* markers stay, mid-emission and still — presence without motion */
  .vt-map-node__ring {
    animation: none;
    opacity: calc(var(--ring-opacity) * 0.45);
    transform: translate(-50%, -50%) scale(0.85);
  }
  .vt-map-node__core { animation: none; }
}
