/* ══════════════════════════════════════════════════════════════════
   ML CORPORATE SERVICES | Elementor house styles
   Archivo structure, Literata voice. Black / white / soft grey only,
   accent reserved for hover and active states (IMPLEMENTATION GUIDE 7).

   Selector policy, established by testing this build rather than by
   assumption: container elements do NOT output custom CSS classes, so
   sections are addressed by their Elementor element ID, and everything
   inside a loop is addressed through the widget classes and the
   .e-loop-item wrapper Elementor generates per post.
   ══════════════════════════════════════════════════════════════════ */

@font-face{
  font-family:"Archivo";
  src:url('fonts/archivo-var.woff2') format('woff2');
  font-weight:100 900;
  font-display:swap;
}
@font-face{
  font-family:"Literata";
  src:url('fonts/literata-var.woff2') format('woff2');
  font-weight:100 900;
  font-display:swap;
}
/* Literata Italic. The variable roman above carries no `ital` axis, so without
   this the italic lines (the footer positioning line, the Moments reflections)
   render as a synthesised oblique. Instantiated from the variable italic at the
   only values the site uses, weight 400 at optical size 16, which takes it from
   87KB to 19KB. Archivo italic is deliberately absent: nothing on the site uses
   it, verified across all 14 pages. Uploaded to the media library because a
   binary cannot be added through the Theme File Editor.

   CORRECTED 2026-08-17: `src` was a hardcoded absolute staging URL. The
   database search-replace at cutover only touches the database - this is a
   static theme file, so it shipped unchanged to live and every page there was
   making a cross-origin request to the staging host for this one file. Browsers
   enforce CORS on @font-face regardless of the response's own status, so it
   failed silently: no visible layout break, just italic text falling back to
   a synthesised oblique everywhere, on every page, since the day of cutover.
   Root-relative now, so it resolves against whatever domain actually served
   the page and needs no fixing the next time the domain changes. */
@font-face{
  font-family:"Literata";
  src:url('/wp-content/uploads/2026/07/literata-italic-400.woff2') format('woff2');
  font-style:italic;
  font-weight:400;
  font-display:swap;
}

/* THE PALETTE COMES FROM SITE SETTINGS, not from this file.

   The supreme law is that anything on the canvas must be editable in Elementor.
   Measured, 409 of the site's 437 widgets carry no Elementor styling at all -
   only a `_css_classes` hook into this stylesheet - so a colour changed in Site
   Settings did nothing and the law was unmet everywhere except the footer.

   Re-authoring 409 widgets is the wrong answer: it would scatter the palette
   across the page JSON and lose the reasoning that lives in these files. Binding
   the TOKENS is the right one. Each now reads its kit global first and keeps its
   own value as the fallback, so Site Settings drives the site, and if a global
   is ever deleted the page still renders exactly as it does today.

   The kit's globals were checked against these values before binding, not
   assumed. Five matched to the digit. --ml-grey did not - #595959 here against
   #4A4A4A in the kit - so it was held back until the kit was corrected rather
   than bound blind, which would have darkened it across seven stylesheets on
   push. The kit was then saved with ml_grey at #595959, sending the WHOLE 636-key
   settings object: sending a subset is what wiped the font globals to Roboto the
   last time. Verified after that save - kit CSS 1,609 bytes, every colour present,
   typography still Archivo and Literata. All six are now bound. */
:root{
  /* Fallback is #0a0a0a, the locked black - not #000000, which it was until
     2026-08-12. Where the kit global does not resolve (the Moments art panels
     were one such scope) the fallback is what paints, so a pure-black fallback
     quietly reintroduced the second black the lock exists to prevent. */
  --ml-ink:var(--e-global-color-ml_ink,#0a0a0a);
  --ml-paper:var(--e-global-color-ml_paper,#ffffff);
  --ml-charcoal:#0e0e0c;
  --ml-grey:var(--e-global-color-ml_grey,#595959);
  --ml-grey-light:var(--e-global-color-ml_greyl,#a5a5a5);

  /* Card metadata - the kicker and the date - sits on paper on some pages and on
     ink on others, and one value cannot serve both. --ml-grey-light is #a5a5a5:
     7:1 on ink and 2.46:1 on paper, which is below AA and measurably hard to read
     at 10.5px uppercase. So the meta colour is contextual, and both ends of it are
     existing brand tokens rather than a new grey: --ml-grey (#595959, 7:1 on paper)
     by default, re-lightened inside the ink sections below. */
  --ml-meta:var(--ml-grey);
  --ml-mist-ink:rgba(255,255,255,.82);
  --ml-line-ink:rgba(255,255,255,.18);
  --ml-gut:clamp(28px,5.4vw,116px);
  --ml-maxw:1680px;
}

/* ── Responsive hygiene, site-wide ──────────────────────────────────
   Applies to every page, not only the feeds. Deliberately targeted:
   `overflow-x:hidden` on the page would hide layout faults rather than
   fix them, so instead the things that actually break the viewport are
   made to behave - long unbroken strings (email addresses, URLs) and
   media that ignores its container. */

img,
video,
iframe,
table{
  max-width:100%;
}

.elementor-icon-list-text,
.elementor-icon-list-item > a,
.elementor-widget-text-editor a,
.elementor-widget-heading a{
  overflow-wrap:anywhere;
  word-break:break-word;
}

/* ── Feed section ───────────────────────────────────────────────── */

#ml-news.e-con{
  background-color:var(--ml-charcoal);
  color:var(--ml-paper);
  /* Top is lighter than the sides. The grid follows a paper hero that already
     closes on its own padding, and at 11vw the two stacked to 216px of dead ink
     before the first card. Perspectives opens its next section on 86px. */
  padding:clamp(64px,6vw,92px) var(--ml-gut) clamp(96px,11vw,168px);
  --width:100%;
  max-width:none;
}

#ml-news-head.e-con{
  max-width:var(--ml-maxw);
  margin:0 auto clamp(48px,5vw,78px);
  padding:0;
}

#ml-news-head .elementor-heading-title{
  font-family:"Archivo",Helvetica,Arial,sans-serif;
  font-weight:560;
  font-size:clamp(30px,3.1vw,50px);
  line-height:1.04;
  letter-spacing:-.035em;
  color:var(--ml-paper);
}

/* The head carries the page's identity now that this is Articles rather than a
   news feed: eyebrow, title, lead. The rule below sizes every heading in the
   head, so the eyebrow has to be pulled back out of it explicitly. Spacing
   follows the locked section-head rhythm - 26px eyebrow to title, 28px title
   to copy - which is what the other 39 section heads use. */
#ml-news-head .ml-eyebrow .elementor-heading-title{
  font-family:"Archivo",Helvetica,Arial,sans-serif;
  font-size:.72rem;
  font-weight:600;
  line-height:1;
  letter-spacing:.18em;
  text-transform:uppercase;
  color:rgba(255,255,255,.5);
}

#ml-news-head .ml-section-title .elementor-heading-title{margin-top:26px}

#ml-news-head .ml-lead{margin-top:28px;max-width:58ch}
#ml-news-head .ml-lead p{
  font-size:1.0625rem;
  line-height:1.7;
  color:rgba(255,255,255,.72);
}

#ml-news .elementor-widget-loop-grid{
  /* width:100% is load-bearing. Elementor gives a widget inside a flex container
     its kit content width, which is 1000px here, and max-width cannot stretch an
     element that is already narrower than the cap. Measured before this line:
     the head ran 78 to 1362 while the grid sat 220 to 1220, so the page title
     was 142px out of line with the first card at 1440, and at 600px a
     single-column card was 276px wide inside 535px of available space. */
  width:100%;
  max-width:var(--ml-maxw);
  margin:0 auto;
}

/* ── The card: one post inside a loop ───────────────────────────── */

/* No rule above the card. Separation is the column gap and the space over the
   image; a hairline on every card is line furniture, and twelve of them on a
   grid read as a form. */
.elementor-widget-loop-grid .e-loop-item{
  padding-top:4px;
}

.ml-kicker .elementor-heading-title{
  font-family:"Archivo",Helvetica,Arial,sans-serif;
  font-weight:600;
  font-size:.6562rem;
  letter-spacing:.24em;
  text-transform:uppercase;
  color:var(--ml-meta);
}

.ml-kicker a{
  color:inherit;
  text-decoration:none;
}

.ml-title{margin-top:16px}

.ml-title .elementor-heading-title,
.ml-title .elementor-heading-title a{
  font-family:"Archivo",Helvetica,Arial,sans-serif;
  font-weight:540;
  font-size:clamp(19px,1.5vw,25px);
  line-height:1.16;
  letter-spacing:-.03em;
  color:var(--ml-paper);
  text-decoration:none;
  transition:opacity .3s ease;
}

.ml-title .elementor-heading-title a:hover{opacity:.72}

.ml-excerpt{margin-top:14px}

.ml-excerpt,
.ml-excerpt p{
  font-family:"Literata",Georgia,serif;
  font-size:1rem;
  line-height:1.62;
  color:var(--ml-mist-ink);
}

/* Publication or organisation on a media card. Review 3 item M5 asks each media
   item to name where it appeared. It sits above the date and carries the weight,
   because on a press card the source is the more informative half of the meta;
   the date then follows tight beneath it rather than opening its own block. */
.ml-pub{margin-top:18px}

.ml-pub .elementor-heading-title{
  font-family:"Archivo",Helvetica,Arial,sans-serif;
  font-weight:600;
  font-size:.6562rem;
  letter-spacing:.2em;
  text-transform:uppercase;
  color:var(--ml-ink);
}

/* A card that carries a publication has the date as its second meta line. */
.ml-pub + .ml-date{margin-top:4px}

/* ML's own LinkedIn updates carry no publication tag - naming the channel you
   published yourself on tells the reader nothing, and three cards reading
   LINKEDIN in a row read as furniture. Measured 2026-08-12: Elementor omits the
   widget ENTIRELY when the dynamic tag resolves empty, so no space is left
   behind and nothing here is load-bearing. Kept as a safety net in case a future
   Elementor renders the empty shell instead. */
.ml-pub:has(.elementor-heading-title:empty){display:none}

.ml-date{margin-top:18px}

.ml-date .elementor-heading-title{
  font-family:"Archivo",Helvetica,Arial,sans-serif;
  font-weight:600;
  font-size:.6562rem;
  letter-spacing:.2em;
  text-transform:uppercase;
  color:var(--ml-meta);
}

.ml-go{margin-top:20px}

/* CORRECTED 2026-08-17: was a bottom-only underline (border-bottom, 0 on the
   other three sides). Closed to a full outline on Cephas's instruction - every
   section already sets its own `color` (and, where it needs to differ, its
   own border colour) for this class, so this one change carries to every
   .ml-go instance site-wide rather than needing a per-section edit. Sections
   that set `border-bottom-color` specifically were renamed to `border-color`
   alongside this change; #pub-featured already used `border-color` for a
   fix of its own (a white-on-white repeat of the same #media-av bug fixed
   the same day) and needed no change at all - it was already writing to a
   property this rule had never used until now. */
.ml-go .elementor-button{
  font-family:"Literata",Georgia,serif;
  font-weight:520;
  font-size:.9375rem;
  color:var(--ml-paper);
  background:none;
  border:1px solid rgba(255,255,255,.5);
  border-radius:2px;
  padding:9px 20px;
  transition:border-color .3s ease,opacity .3s ease;
}

.ml-go .elementor-button:hover,
.ml-go .elementor-button:focus{
  background:none;
  color:var(--ml-paper);
  border-color:var(--ml-paper);
  opacity:.78;
}

/* ── Responsive ─────────────────────────────────────────────────────
   Built in from the start, not retrofitted. Type and section padding
   scale fluidly through clamp() above; the rules below handle the
   structural steps. Column counts are set on the Loop Grid widget
   itself (3 / 2 / 1) so an editor can change them in the UI.
   Checked at 1440, 1024, 768 and 390. */

@media (max-width:1024px){
  #ml-news-head.e-con{margin-bottom:clamp(38px,5vw,58px)}
  .ml-title .elementor-heading-title,
  .ml-title .elementor-heading-title a{font-size:clamp(19px,2.4vw,23px)}
}

@media (max-width:782px){
  .elementor-widget-loop-grid .e-loop-item{padding-top:22px}
  .ml-excerpt,
  .ml-excerpt p{font-size:.9688rem}
}

@media (max-width:430px){
  #ml-news.e-con{padding-left:24px;padding-right:24px}
  .ml-title .elementor-heading-title,
  .ml-title .elementor-heading-title a{font-size:21px;line-height:1.2}
}

/* Fade only (IMPLEMENTATION GUIDE 13): no movement. */
@media (prefers-reduced-motion:no-preference){
  .elementor-widget-loop-grid .e-loop-item{
    animation:ml-fade .85s cubic-bezier(.22,.61,.21,1) both;
  }
  @keyframes ml-fade{from{opacity:.001}to{opacity:1}}
}

/* Nothing is removed on small screens (IMPLEMENTATION GUIDE 14). */
@media (max-width:782px){
  .ml-excerpt,
  .ml-date,
  .ml-kicker{display:block}
}

/* ── News / Media cards ──────────────────────────────────────────────
   The cards carried no image because all nine posts were imported with
   featured_media=0, not because the widget was wrong - swapping in a
   different post widget would have changed the container and left the
   contents just as empty. Each post now points at its own photograph.

   Lydie asked for "exactly the same dimensions, spacing and visual
   treatment" across every card, so the image is locked to one ratio and
   the cards stretch to a common height rather than following their
   headline length. */

.ml-card-img img{
  width:100%;
  aspect-ratio:3 / 2;
  object-fit:cover;
  object-position:50% 12%;
  display:block;
  border-radius:2px;
  margin-bottom:26px;
}

/* Why `50% 12%` rather than the default centre.

   The frame is 3:2. `cover` crops the long axis, so for anything WIDER than 3:2
   the vertical position is inert and this changes nothing; it bites only on
   images TALLER than 3:2, which are exactly the ones that were cropping badly.

   Worked, not guessed, on Lydie's portrait (PICTURE-1.jpg, 1281x1920, ratio
   0.67). At a 299px card the image scales by 0.2334, so 1067 source pixels fall
   outside the frame and the position is a percentage of THAT. Her hair begins at
   roughly y=186. Centred (50%) started the band at y=533, below her eyes. 20%
   started it at 213, still 27px into her hair. 12% starts it at 128, clearing
   the top of her head by about 58 source pixels.

   Note the x value is inert here: this image is narrower than 3:2, so `cover`
   scales it to the frame WIDTH and crops only vertically. Nothing horizontal is
   hidden, so no x adjustment can change what is shown. */

/* Post 401 has no photograph. Its only image, featured and in body, was the ML
   monogram as BLACK artwork on transparency, which on a dark card rendered as a
   near-invisible cropped ring. Nothing there could be fixed by cropping, because
   there was nothing to crop.

   Now the white mark (media 1928) shown COMPLETE on the card field, so it reads
   as a deliberate branded tile rather than a broken photograph, and sits in the
   dark row instead of punching a white hole through it.

   This is a holding pattern, not a fix. The real answer is a photograph from ML,
   which is folded into the media-content ask. If that post ever gets a real
   featured image, DELETE this rule and revert the featured image. */
/* Post 401 carried object-fit:contain with padding because its cover was a
   monogram that could not be cropped. It now has a real 3:2 cover like every
   other card, so it fills the frame the same way. */

.ml-card-img{line-height:0}

/* Equal height regardless of how long a headline runs.

   NOTE FOR WHOEVER EDITS THIS NEXT: the card container carries
   _css_classes "ml-card" in the template, but Elementor containers in this
   build DO NOT OUTPUT custom classes - only widgets do. So .ml-card matches
   nothing in the DOM and styling it silently does nothing. The container is
   addressed by its element id instead, which is the documented pattern for
   this build. .ml-card-img works because it sits on a WIDGET. */
.elementor-loop-container .e-loop-item{
  display:flex;
  align-items:stretch;
}

.elementor-loop-container .elementor-element-0a53ac4{
  display:flex;
  flex-direction:column;
  width:100%;
  height:100%;
}

/* The DATE, not the link, takes the free space, so the last two rows of every
   card land on the same line together.

   The auto margin used to sit on .ml-go. That pinned the Read link to the
   bottom and left the date floating directly under whatever the headline
   happened to be: measured on the home news row, card one's date sat 25px
   below its neighbours' because its headline ran three lines against their
   two. Moving the auto margin up one row pushes the date AND the link down as
   a pair, so both align exactly whatever the headline length.

   Reserving a fixed three-line height on the title was the other candidate and
   it was tried first. It was 2px out - the clamp() headline size does not
   divide into a whole number of lines at every viewport - and an em-based
   reservation cannot be made exact at every width. Letting flex distribute the
   space is exact by construction, at any font size, on any viewport. */
.elementor-loop-container .elementor-element-0a53ac4 .ml-date{
  margin-top:auto;
  padding-top:18px;
}

/* Cards must be the same height, not merely the same height within a row.
   A CSS grid row sizes to its tallest cell, so a long headline made row one
   26px taller than rows two and three. Clamping the headline to three lines
   fixes every card to one height and keeps the truncation deliberate. */
.elementor-loop-container .ml-title .elementor-heading-title{
  display:-webkit-box;
  -webkit-line-clamp:3;
  -webkit-box-orient:vertical;
  overflow:hidden;
}


.elementor-loop-container .ml-excerpt{
  display:-webkit-box;
  -webkit-line-clamp:2;
  -webkit-box-orient:vertical;
  overflow:hidden;
}


/* The ink sections. Card metadata inside them takes the light grey back, because
   there #a5a5a5 is the legible value and #595959 would fail the other way. */
/* Measured on rendered backgrounds 2026-08-12, not assumed from the file:
   #s-media, #media-av, #s-moment-1 and #s-moment-4 are PAPER (the moments
   alternate, and s-media was inverted to paper earlier today to fix the tonal
   break), so they are deliberately absent and keep the darker default. */
#s-latest,
#persp-latest,
#media-selected,
#ml-news,
#pub-library,
#s-moment-2, #s-moment-3{
  --ml-meta:var(--ml-grey-light);
}
