/*
Theme Name: ECONS International
Theme URI: https://econs-international.com
Author: ECONS International
Description: Custom theme for ECONS International. The visual design is the site's own compiled design system (enqueued from assets/css/econs-design.css); this file holds only the WordPress compatibility layer so the theme works cleanly with the block editor, admin bar and accessibility needs.
Version: 1.8.2
Requires at least: 6.4
Requires PHP: 8.0
License: Proprietary
Text Domain: econs
*/

/* ---------------------------------------------------------------------------
 * WordPress compatibility layer only. Everything visual comes from the site's
 * compiled design system, loaded before this file.
 * ------------------------------------------------------------------------- */

/* Skip link — visible only when focused. */
.e-skip {
  position: absolute;
  left: -9999px;
}
.e-skip:focus {
  left: 1rem;
  top: 1rem;
  z-index: 1000;
  background: var(--e-accent, #3db5e6);
  color: var(--e-bg, #0a0d11);
  padding: 0.6rem 1rem;
  border-radius: 0.35rem;
}

.screen-reader-text {
  position: absolute !important;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* The sticky site header must sit below the WordPress admin bar when the
   editor is viewing the front end while logged in. */
.admin-bar .e-site-header {
  top: 32px;
}
@media screen and (max-width: 782px) {
  .admin-bar .e-site-header {
    top: 46px;
  }
}

/* Block editor content should use the same measure and rhythm as the front
   end so what an editor sees is what visitors get. */
.e-main .entry-content > * + * {
  margin-block-start: var(--wp--preset--spacing--40, 1.5rem);
}

/* Mobile navigation panel. The design system shows the desktop nav from the md
   breakpoint up and a hamburger below it; WordPress needs a container for the
   panel that the toggle reveals, so that shell lives here. The links inside use
   the design system's own .e-nav-link class. */
.e-nav-mobile {
  padding-block: 0.75rem;
  border-top: 1px solid var(--e-hairline, #26303a);
}
.e-nav-mobile[hidden] {
  display: none;
}
.e-nav-mobile .e-nav-list {
  flex-direction: column;
  gap: 0.15rem;
  align-items: flex-start;
}
.e-nav-mobile .e-nav-link {
  display: block;
  padding-block: 0.6rem;
}

/* WordPress renders every list block as .wp-block-list and gives it markers and
   padding. The design system's lists are unmarked flex/grid rows carrying their
   own utility classes, so the block default is removed rather than fought. */
.e-main .wp-block-list {
  padding-inline-start: 0;
  list-style: none;
}
.e-main .wp-block-list > li {
  margin-block: 0;
}

/* Block wrappers should never add spacing of their own: the design system sets
   every gap with explicit utilities. */
.e-main .wp-block-group > *:first-child {
  margin-block-start: 0;
}
.e-main .wp-block-group > *:last-child {
  margin-block-end: 0;
}

/* The scrolling progress rule is a scaled bar, so give it the shell it needs. */
.e-scroll-rule {
  transform: scaleX(0);
  transform-origin: left center;
  display: block;
  height: 2px;
}

/* ---------------------------------------------------------------------------
 * NOTE: paragraphs and headings must NOT have their bottom margin forced to
 * zero here.
 *
 * The design system sets every vertical gap with an explicit margin utility
 * (mb-4, mb-8, mt-6 …). A rule such as `.e-main p { margin-block-end: 0 }`
 * carries specificity (0,1,1) and therefore BEATS the single-class utilities at
 * (0,1,0) — silently cancelling every mb-* utility on headings and paragraphs
 * across the site. The design system's own reset already sets `p { margin: 0 }`,
 * so no compensation is needed.
 * ------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------
 * Page colour — deliberately UNLAYERED.
 *
 * The design system paints html and body from inside @layer base. Because
 * unlayered declarations outrank every layer, anything WordPress emits unlayered
 * (theme.json global styles, block library defaults) would silently win and pin
 * the page to one palette. These two lines restate the design system's intent
 * with the same unlayered priority, so the light/dark toggle always works.
 * ------------------------------------------------------------------------- */
html,
body {
  background-color: var(--e-bg);
  color: var(--e-ink);
}

/* ---------------------------------------------------------------------------
 * Neutralise WordPress's own block layout rhythm.
 *
 * WordPress adds `is-layout-flow` to Group blocks and applies a block-gap
 * margin between their children. The design system sets every gap explicitly
 * with utilities (gap-*, mt-*), so WordPress's margin stacks on top and breaks
 * any layout whose children must line up — most visibly the card grids, where
 * it pushed every card after the first down and left the arrows and text at
 * inconsistent heights.
 *
 * The zeroing is done through the VARIABLE, not with an overriding rule. That
 * matters: a rule like `.e-main .is-layout-flow > * + *` carries specificity
 * (0,2,0) and would beat the design system's own `mt-*` utilities at (0,1,0),
 * deleting spacing the design intends. Setting the variable instead lets
 * WordPress's margin compute to zero while the design's utilities still win on
 * source order — so the layout matches the live site exactly.
 * ------------------------------------------------------------------------- */
.e-main {
  --wp--style--block-gap: 0;
}

/* ---------------------------------------------------------------------------
 * Dropdowns inside a form field.
 *
 * The design system styles `.e-field input` and `.e-field textarea` but has no
 * rule for `.e-field select`, so a <select> falls back to the browser reset and
 * renders unstyled next to its neighbours. These declarations mirror the input
 * rule exactly, using the design system's own custom properties, so a select is
 * visually identical to the text fields beside it.
 *
 * `appearance` is deliberately left alone: the site sets color-scheme, so the
 * native dropdown arrow already matches the light and dark palettes.
 * ------------------------------------------------------------------------- */
.e-field select {
  border: 1px solid var(--e-hairline);
  background-color: var(--e-panel);
  width: 100%;
  color: var(--e-ink);
  border-radius: 0.35rem;
  outline: none;
  padding: 0.8rem 0.95rem;
  font-size: 0.95rem;
  transition: border-color 0.25s;
}
.e-field select:focus {
  border-color: var(--e-accent);
}

/* ---------------------------------------------------------------------------
 * Portrait video player: align it to the right of its column, but only where
 * the two-column layout exists.
 *
 * The section becomes a two-column grid at lg (1024px). Below that it is a
 * single column, and a right-aligned 416px block there would sit with an odd
 * gap on its left while everything else is left-aligned. The breakpoint value
 * matches the design system's own lg breakpoint (64rem).
 * ------------------------------------------------------------------------- */
/* A portrait player keeps its width inside a flex column. The inline
   max-width caps it at 416px, but a flex item that also carries an auto
   inline-start margin is not stretched, so without an explicit width it would
   shrink to its content. */
.e-main .e-video-portrait {
  width: 100%;
}

@media (min-width: 1024px) {
  .e-main .e-video-portrait {
    margin-inline-start: auto;
  }
}

/* ---------------------------------------------------------------------------
 * A media frame that follows the image's own proportions.
 *
 * .e-media-wide fixes a 3:2 frame and fills it with object-fit: cover, which is
 * right for photographs but crops graphics — an exhibition banner would lose its
 * logo and booth number off the sides. This variant lets the frame take the
 * image's own shape, so nothing is cut off.
 * ------------------------------------------------------------------------- */
.e-main .e-media-natural {
  aspect-ratio: auto;
}
.e-main .e-media-natural img {
  height: auto;
}

/* Wide and full width block alignments inside the constrained container. */
.e-main .alignwide {
  margin-inline: calc(-1 * clamp(1rem, 6vw, 6rem));
  max-width: none;
  width: auto;
}
.e-main .alignfull {
  margin-inline: calc(-1 * clamp(1rem, 4vw, 2rem));
  max-width: none;
  width: auto;
}

/* Buttons that WordPress renders should not inherit the browser default. */
.e-main .wp-block-button__link {
  text-decoration: none;
}

/* Images inside block content behave. */
.e-main img {
  max-width: 100%;
  height: auto;
}

/* ---------------------------------------------------------------------------
 * Page heroes — let the photograph actually read.
 *
 * The design system fades the hero photo to opacity .5-.68 and lays a near-solid
 * wash across the left two-thirds, so the image is barely visible. Raising the
 * photo and pulling the wash back to a left-hand gradient gives the approved
 * look: a clear landscape photograph with the headline over a dark corner.
 *
 * SCOPED TO ONE PAGE for approval (/machines/, body class page-id-2038).
 * Remove the .page-id-2038 prefix to apply it to every hero.
 * ------------------------------------------------------------------------- */
.page-id-2038 .e-main .e-hero-media {
  opacity: 0.90;
  filter: brightness(1.04) saturate(1.05);
}
.page-id-2038 .e-main .e-hero-scrim {
  background-image:
    linear-gradient(to right, rgba(12, 17, 22, 0.88) 0%, rgba(12, 17, 22, 0.50) 34%, rgba(12, 17, 22, 0.06) 68%, transparent 100%),
    linear-gradient(to bottom, transparent 62%, rgba(12, 17, 22, 0.62) 100%);
}
/* A machine render sits on the fixed dark stage, so the stage class survives
   whichever theme the visitor has chosen. */
.e-stage {
  background-color: #0d1116;
}

/* ---------------------------------------------------------------------------
 * Location cards — the country photo IS the card.
 *
 * The photo fills the card at full strength so the country is genuinely
 * recognisable, and the text sits along the foot over a dark gradient scrim.
 * Only the foot is darkened, so the upper two-thirds of the photo reads clearly.
 *
 * The text colours here are fixed light values rather than theme tokens: the
 * scrim makes the card dark in BOTH the light and the dark theme, so the text
 * must be light in both.
 *
 * The image is set per card via an inline background-image.
 * ------------------------------------------------------------------------- */
.e-main .e-loc-card {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  min-height: 20rem;
  background-size: cover;
  background-repeat: no-repeat;
  background-position: center 32%;
  overflow: hidden;
}
.e-main .e-loc-card::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    rgba(6, 10, 16, 0) 30%,
    rgba(6, 10, 16, 0.22) 55%,
    rgba(6, 10, 16, 0.74) 80%,
    rgba(6, 10, 16, 0.92) 100%
  );
  pointer-events: none;
}
.e-main .e-loc-card > * {
  position: relative;
  z-index: 1;
  /* a light shadow keeps the country line and the city readable wherever the
     photo happens to be pale, without darkening more of the image */
  text-shadow: 0 1px 5px rgba(4, 8, 13, 0.85), 0 0 2px rgba(4, 8, 13, 0.6);
}
/* the scrim is dark whatever the theme, so the card's text is always light */
.e-main .e-loc-card .text-e-ink {
  color: #ffffff;
}
.e-main .e-loc-card .text-e-muted {
  color: #c9d4de;
}
/* The two supporting tiles in "Where we work" use the same treatment as the
   location cards — full-strength photo, dark scrim, light caption at the foot —
   at a shorter height, since they sit in a two-up grid. */
.e-main .e-loc-tile {
  min-height: 14rem;
}

/* The country line sits above the densest part of the scrim, so it carries its
   own small dark chip. That keeps the accent colour — which the design uses to
   mark the country — while guaranteeing it reads over any part of the photo. */
.e-main .e-loc-card .text-e-accent {
  display: inline-block;
  padding: 3px 9px;
  border-radius: 4px;
  background: rgba(6, 10, 16, 0.74);
  color: #7fd8ff;
  text-shadow: none;
}

/* ---------------------------------------------------------------------------
 * Scroll hero — a light scrim over the video.
 *
 * The brand sting the hero now carries is already very dark by design (its own
 * blacks sit at zero), so a heavy scrim would crush it to nothing. This keeps
 * only enough tint on the left — where the copy sits — to guarantee contrast,
 * and leaves the rest of the frame alone so the blueprint, laser and machinery
 * still read.
 *
 * It sits inside the media layer, which precedes the chapters in the DOM, so
 * the copy is never covered.
 * ------------------------------------------------------------------------- */
.scroll-scrub__layer::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background-image:
    linear-gradient(to right, rgba(10, 13, 17, 0.40) 0%, rgba(10, 13, 17, 0.20) 38%, rgba(10, 13, 17, 0.04) 70%, transparent 100%),
    linear-gradient(to bottom, transparent 60%, rgba(10, 13, 17, 0.38) 100%);
}

/* ---------------------------------------------------------------------------
 * Tablet header (768-1023px) — keep it compact.
 *
 * The primary nav carries eleven long labels, including "Reliability &
 * Performance Transformation". At 768px that set cannot sit on one line even at
 * 7px type, so it must wrap; the goal is to make that wrap tight and to trim
 * everything else around it.
 *
 * The header is position:fixed and floats over the hero, so every pixel saved
 * here is a pixel of video the visitor actually sees.
 * ------------------------------------------------------------------------- */
@media (min-width: 768px) and (max-width: 1023px) {
  .e-nav {
    gap: 0.45rem;
    padding-block: 0.45rem;
  }
  .e-nav-top {
    min-height: 2.4rem;
  }
  .e-brand-logo {
    height: 1.8rem;
  }
  .e-nav-list {
    gap: 0.2rem 1.05rem;
  }
  .e-nav-link {
    font-size: 0.63rem;
    letter-spacing: 0.02em;
    padding: 0.3rem 0;
  }
  .e-header-cta {
    padding: 0.42rem 0.75rem;
    font-size: 0.64rem;
  }
}

/* ---------------------------------------------------------------------------
 * Service tiles — make the six cells follow the theme.
 *
 * The design system hardcodes these cells as a light "paper" panel inside the
 * dark band (background #f4f7f9, heading #0d1821, body #51626f). That reads
 * well in light mode but leaves six white boxes glowing in dark mode.
 *
 * The dark values below are the design's own dark tokens for this band
 * (#141c24 panel, #eef3f6 ink, #9dafbc muted, #3db5e6 accent), so the cells sit
 * on the band rather than floating on it. The light block re-asserts the
 * original design values exactly, so light mode is unchanged.
 * ------------------------------------------------------------------------- */
.e-main .e-band-dark .e-service-cell,
.e-band-dark .e-main .e-service-cell {
  background-color: #141c24;
}
.e-main .e-band-dark .e-service-cell:hover,
.e-band-dark .e-main .e-service-cell:hover {
  background-color: #1a232c;
}
.e-main .e-band-dark .e-service-cell h2,
.e-band-dark .e-main .e-service-cell h2 {
  color: #eef3f6;
}
.e-main .e-band-dark .e-service-cell p,
.e-band-dark .e-main .e-service-cell p {
  color: #9dafbc;
}
.e-main .e-band-dark .e-service-cell .e-icon-tile,
.e-band-dark .e-main .e-service-cell .e-icon-tile {
  color: #3db5e6;
  background: #3db5e629;
  border-color: #3db5e673;
}

/* light mode keeps the design's original paper panel.
   These carry the same three classes as the dark block plus the theme
   attribute, so they out-rank it and light mode renders exactly as designed. */
[data-theme="light"] .e-main .e-band-dark .e-service-cell,
[data-theme="light"] .e-band-dark .e-main .e-service-cell {
  background-color: #f4f7f9;
}
[data-theme="light"] .e-main .e-band-dark .e-service-cell:hover,
[data-theme="light"] .e-band-dark .e-main .e-service-cell:hover {
  background-color: #ffffff;
}
[data-theme="light"] .e-main .e-band-dark .e-service-cell h2,
[data-theme="light"] .e-band-dark .e-main .e-service-cell h2 {
  color: #0d1821;
}
[data-theme="light"] .e-main .e-band-dark .e-service-cell p,
[data-theme="light"] .e-band-dark .e-main .e-service-cell p {
  color: #51626f;
}
[data-theme="light"] .e-main .e-band-dark .e-service-cell .e-icon-tile,
[data-theme="light"] .e-band-dark .e-main .e-service-cell .e-icon-tile {
  color: #1d71b8;
  background: #1d71b824;
  border-color: #1d71b873;
}

/* ---------------------------------------------------------------------------
 * News & Events — category chips.
 *
 * The chips carry a small icon and one of them is the active "All news" state,
 * so the row reads like the filter it is rather than a plain list.
 * ------------------------------------------------------------------------- */
.e-main .e-chip-icon {
  display: inline-flex;
  align-items: center;
  margin-right: 0.45rem;
  vertical-align: -0.15em;
  color: var(--color-e-accent);
}
.e-main .e-chip-active {
  border-color: var(--color-e-accent);
  color: var(--color-e-ink);
  background: color-mix(in srgb, var(--color-e-accent) 14%, transparent);
}

/* ---------------------------------------------------------------------------
 * Countdown — readable in BOTH themes.
 *
 * The countdown sits inside an e-band-dark section, which is dark whichever
 * theme the visitor has chosen. But its figures and labels take their colour
 * from the global --color-e-ink / --color-e-muted tokens, which flip to dark
 * values in light mode — so in light mode the digits were painted dark onto a
 * dark band and effectively disappeared.
 *
 * Scoping them to the band fixes it: the band's own light-on-dark values apply
 * in both themes, and the accent caption keeps following the theme.
 * ------------------------------------------------------------------------- */
.e-band-dark .e-countdown-figure {
  color: #eef3f6;
}
.e-band-dark .e-countdown-label {
  color: #9dafbc;
}

/* ---------------------------------------------------------------------------
 * Homepage reliability band — a left-heavy scrim.
 *
 * The landscape photograph runs full width behind this band. A flat overlay
 * either hides the photo or fails to settle the copy, and this one has bright
 * areas (a white hard hat, lit structures) directly under the paragraph. So the
 * scrim is graded left to right: solid enough over the text column to hold the
 * white copy on any frame, clearing to nearly nothing on the right so the
 * photograph still reads.
 * ------------------------------------------------------------------------- */
.e-main .e-reliability-band .e-band-scrim {
  background-image:
    linear-gradient(
      to right,
      rgba(10, 13, 17, 0.93) 0%,
      rgba(10, 13, 17, 0.86) 30%,
      rgba(10, 13, 17, 0.62) 52%,
      rgba(10, 13, 17, 0.22) 76%,
      rgba(10, 13, 17, 0.06) 100%
    ),
    linear-gradient(to bottom, rgba(10, 13, 17, 0.30) 0%, rgba(10, 13, 17, 0) 34%);
}
/* the supporting copy sits on a photograph here rather than on a flat panel,
   so it is lifted from the muted grey to a near-white for a crisper read */
.e-main .e-reliability-band .text-e-muted {
  color: #d5dee6;
}

/* The photograph must actually fill the band.
   The compat layer's `img { max-width:100%; height:auto }` above is unlayered,
   so it out-ranks the layered `h-full` / `w-full` utilities and left the photo
   rendering at its natural size inside the column instead of covering the
   section. These declarations are unlayered too, and more specific. */
.e-main .e-reliability-band img.e-hero-media {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  object-fit: cover;
  object-position: 50% 42%;
}
/* keep the copy above the photograph */
.e-main .e-reliability-band > .e-container {
  position: relative;
  z-index: 1;
}

/* ---------------------------------------------------------------------------
 * Phone: the copy runs the full width, so a left-to-right scrim would leave
 * the right-hand end of every line over the bright part of the photo. The scrim
 * is graded top to bottom instead, and the band gets its own padding rhythm.
 * ------------------------------------------------------------------------- */
@media (max-width: 767px) {
  .e-main .e-reliability-band {
    padding-block: 3.5rem 2.5rem;
  }
  /* On a phone this section is ~1,470px tall because the five stat cells
     stack. Covering that with a 16:9 photo crops it to a thin vertical sliver
     and the operator disappears entirely. Instead the photo becomes a top
     strip roughly the height of the copy, and the stat cells (which are opaque
     panels) sit over the section background below it. */
  .e-main .e-reliability-band img.e-hero-media {
    height: 30rem;
    inset: 0 0 auto 0;
    object-position: 62% 38%;
  }
  /* the copy sits within that strip, above the photo */
  .e-main .e-reliability-band > .e-container {
    padding-top: 1rem;
  }
  .e-main .e-reliability-band .e-band-scrim {
    height: 30rem;
    inset: 0 0 auto 0;
    background-image: linear-gradient(
      to bottom,
      rgba(10, 13, 17, 0.90) 0%,
      rgba(10, 13, 17, 0.86) 46%,
      rgba(10, 13, 17, 0.90) 78%,
      rgba(10, 13, 17, 0.97) 100%
    );
  }
}

/* Tablet: still two columns of copy, but less room than desktop, so the
   left-hand scrim is carried further across before it clears. */
@media (min-width: 768px) and (max-width: 1023px) {
  .e-main .e-reliability-band .e-band-scrim {
    background-image: linear-gradient(
      to right,
      rgba(10, 13, 17, 0.93) 0%,
      rgba(10, 13, 17, 0.86) 42%,
      rgba(10, 13, 17, 0.58) 72%,
      rgba(10, 13, 17, 0.28) 100%
    );
  }
}

/* ---------------------------------------------------------------------------
 * Scroll reveal — the design's own e-rise, on the brand easing.
 *
 * The design already defines @keyframes e-rise (opacity 0 -> 1 with a 14px
 * lift) and runs it at .55s cubic-bezier(.2,.7,.2,1). Here the same keyframes
 * are driven from the scroll instead, at the brand's own easing curve
 * cubic-bezier(.65,0,.35,1), so the motion belongs to the design language
 * rather than being bolted on.
 *
 * Safety: the hidden state is applied ONLY once a script has flagged the
 * document with .e-reveal-ready. If JS is unavailable, blocked, or the
 * observer never runs, nothing is hidden and every section renders normally.
 * Reduced-motion visitors never see the transition at all.
 *
 * Scoped by class, not by position: the script marks the top-level sections
 * wherever they sit in the tree (inner pages nest theirs deeper than the front
 * page does), and only marked sections are ever affected.
 * ------------------------------------------------------------------------- */
.e-main section.e-reveal:not(.scroll-scrub) {
  opacity: 0;
}
.e-main section.e-reveal.is-revealed:not(.scroll-scrub) {
  animation: 0.62s cubic-bezier(0.65, 0, 0.35, 1) both e-rise;
}
@media (prefers-reduced-motion: reduce) {
  .e-main section.e-reveal:not(.scroll-scrub) {
    opacity: 1;
    animation: none !important;
  }
}

/* ---------------------------------------------------------------------------
 * Required fields.
 *
 * A red asterisk marks each obligatory field. It is aria-hidden, so assistive
 * tech announces the requirement through aria-required rather than reading
 * "asterisk" after every label.
 *
 * A field that fails shows the marker colour as a border. aria-invalid is
 * driven by form-a11y.js; :user-invalid covers the same state natively in
 * browsers that support it, and :invalid is deliberately not used because it
 * fires on empty untouched fields too.
 * ------------------------------------------------------------------------- */
.e-main .e-required {
  color: #e5484d;
  margin-left: 0.15rem;
  font-weight: 700;
}
.e-main .e-field input[aria-invalid="true"],
.e-main .e-field textarea[aria-invalid="true"],
.e-main .e-field input:user-invalid,
.e-main .e-field textarea:user-invalid {
  border-color: #e5484d;
}
