﻿/* ==========================================================================
   Mouldshop home HERO — bespoke skin over Swiffy Slider (M2 / TASK-0007, DEC-0005).
   Engine = swiffy-slider core CSS (loaded separately); this file is 100% our skin.
   Built to _reference/Designs/Mouldshop_Web-26_home.jpg.
   ========================================================================== */

/* THE 32px WAS NEVER OURS, and `padding: 0` here had been failing silently for packets.
   Vendor swift.css applies `padding: var(--swift-content-padding)` to the direct child of a
   coloured grid column:

     [data-dw-colorscheme]:not([data-dw-colorscheme=""])[data-swift-gridcolumn]:not(:has(.card))
       > :not(swift-video-player, .card, figure, script, :has(.card))

   — (0,5,0) against this rule's (0,1,0), so ours lost every time.

   ONLY THE SIDES WERE ASKED FOR. The first attempt zeroed the token and took all four edges
   with it, which butted the banner straight against the dispatch bar above and the row
   below. `--swift-content-padding` is one value feeding one shorthand, so it cannot be split
   — zeroing it is all or nothing.

   So the token goes to zero HERE and the block axis is restored ON THE INNER element, where
   no vendor rule competes and a plain (0,1,0) selector is enough. Same rendered result as
   the vendor's 32px top and bottom, with nothing to outbid: no !important, no repeated class
   to inflate specificity, no fight with a selector we do not control. */
.ms-hero {
  --swift-content-padding: 0;
  /* TASK-0078 / DEC-0054: no background on a component root. The band's colour
     is the ROW's colour scheme. This hard-coded white was painting over whatever
     scheme an author chose for the row the hero sits in. */
  padding: 0;
  margin: 0;
}

/* 1200 centred container, edge-to-edge banner (no side padding) — matches design 1200×333 */
.ms-hero-inner {
  /* TASK-0078 / DEC-0054: no max-width and no inline centring on a component root.
     This used to cap at var(--ms-page-width), which DUPLICATED the container ladder
     inside a container that is already on it -- measured 1288 inside 1288 at 1440.
     The row's container owns the width now; this element fills it. */
  margin-block: 0;
  /* The vendor's 32px, put back on the TOP only. The bottom is zero because the space below
     the banner was doubled: this block was paying for it and so was the slider underneath,
     whose own top padding plus the track's headroom already separates the two. One gap, and
     the component below owns it. */
  padding-top: 2rem;
  padding-bottom: 0;
  padding-inline: 0;
}

/* ONE SLIDE PER VIEW IS NO LONGER HARDCODED HERE — TASK-0017 banner half.
   This used to be `.ms-hero-slider .slider-container > * { flex-basis: 100%; max-width: 100% }`,
   which was correct while the count was fixed. The lifted `SlidesPerPage` field now drives it
   through swiffy's own `--swiffy-slider-item-count`, and this rule would have beaten it on
   order (mouldshop-hero.css loads after swiffy-slider.min.css, equal specificity) — turning a
   new control into one that silently does nothing. One per view is swiffy's own default with
   no `slider-item-show*` class, which is exactly what the field emits at "1", so removing this
   changes nothing at the default and makes 2..6 actually work. */

.ms-hero-slide { list-style: none; }
.ms-hero-card {
  position: relative;
  display: flex;
  align-items: center;
  min-height: 333px;         /* floor for gradient (image-less) slides; media slides size to the image */
  border-radius: 0;          /* square corners (operator) */
  overflow: hidden;
  padding: 2.5rem clamp(1.5rem, 4vw, 4rem);
  color: #fff;
  background:
    radial-gradient(120% 140% at 85% 20%, rgba(108, 178, 222, .35), transparent 55%),
    linear-gradient(120deg, var(--ms-navy-deep, #001D52) 0%, var(--ms-navy, #0A2979) 60%, #0d2f6b 100%);
}
/* subtle per-slide hue shift so the 3 demo slides read distinct */
.ms-hero-slide--b .ms-hero-card { background:
    radial-gradient(120% 140% at 80% 80%, rgba(108, 178, 222, .30), transparent 55%),
    linear-gradient(120deg, #06184a 0%, var(--ms-navy, #0A2979) 70%, #123a82 100%); }
.ms-hero-slide--c .ms-hero-card { background:
    radial-gradient(130% 150% at 20% 30%, rgba(108, 178, 222, .28), transparent 55%),
    linear-gradient(120deg, var(--ms-navy, #0A2979) 0%, #0b2f73 65%, var(--ms-navy-deep, #001D52) 100%); }

/* editor image IS the banner — the <img> in flow sets the card height (operator: container = image).
   Scrim + copy overlay on top. */
.ms-hero-card--media { display: block; min-height: 0; padding: 0; }
.ms-hero-bg-img {
  display: block;
  width: 100%;
  height: auto;
  position: relative;
  z-index: 0;
}
/* copy overlays the image, vertically centred, left-aligned */
.ms-hero-card--media .ms-hero-copy {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  max-width: 60%;
  padding: 2.5rem clamp(1.5rem, 4vw, 4rem);
}

.ms-hero-copy { position: relative; z-index: 2; max-width: 56%; }
.ms-hero-eyebrow {
  display: inline-block;
  font: 700 12px/1 var(--ms-font-head);
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--ms-blue, #6CB2DE);
  margin-bottom: 1rem;
}
/* ==========================================================================
   TASK-0023 — THE TEXT OVERLAY. Ruling 8 is the source; there is no reference for this, so
   invariant 10 does not apply and invariant 1 is suspended (TASKS.md says so explicitly).

   THE VOCABULARY IS CLOSED, AND THAT IS THE POINT. Two weights, two colours, two positions.
   Ruling 8: it is what stops slide 3 drifting from slide 1. Everything below keys off the
   class the template emits from the item field; there is no free-form styling hook.

   INVARIANT 12 — every colour is a custom property with a PLACEHOLDER value, declared once in
   the block directly below, and no rule past it contains a colour literal.

   ACCENT IS LARGER AND SITS BELOW THE HEADLINE. Both are `display:block` inside the same h2,
   so the order is document order and the size difference is one token. It is NOT a different
   tag and it is NOT a per-slide setting — ruling 8 fixes it.

   VERTICAL IS ALWAYS MIDDLE. Ruling 8 says it is not a field, so it is not one.
   ========================================================================== */

:root {
  /* Project placeholders: light #ffffff, dark #000000 (invariant 12). Swap this block. */
  --ms-hero-ink-light:   #ffffff;
  --ms-hero-ink-dark:    #000000;

  /* The two weights of the closed vocabulary. */
  --ms-hero-weight-light: 300;
  --ms-hero-weight-bold:  800;

  /* Accent is larger than the headline by a fixed ratio, not by a per-slide value. */
  --ms-hero-title-size:  clamp(2rem, 4.6vw, 3.6rem);
  --ms-hero-accent-size: clamp(2.6rem, 6vw, 4.7rem);

  /* Per-word animation. Duration is per word; the OFFSETS are rolled in JS, which is what
     makes the order random rather than a stagger. */
  --ms-hero-word-duration: .62s;
  --ms-hero-word-ease:     cubic-bezier(.16, .84, .44, 1);
  --ms-hero-word-rise:     .55em;
}

.ms-hero-title { margin: 0 0 1rem; font-family: var(--ms-font-head); line-height: .98; }
.ms-hero-title-main,
.ms-hero-title-accent { display: block; text-transform: uppercase; letter-spacing: .01em; }
.ms-hero-title-main   { font-size: var(--ms-hero-title-size); }
.ms-hero-title-accent { font-size: var(--ms-hero-accent-size); }

/* The closed vocabularies, one rule each. Nothing else styles weight or colour on the title. */
.ms-hero-w--light { font-weight: var(--ms-hero-weight-light); }
.ms-hero-w--bold  { font-weight: var(--ms-hero-weight-bold); }
.ms-hero-c--light { color: var(--ms-hero-ink-light); }
.ms-hero-c--dark  { color: var(--ms-hero-ink-dark); }

/* --- position: left / right. Vertical stays middle in both. --------------------------------
   The card is already a flex row with align-items:center, and the media variant already
   absolutely positions the copy with justify-content:center — so BOTH cases are already
   vertically centred and this only moves the horizontal edge. That is why the overlay does not
   force a layout change, which the packet said to stop and report on. */
.ms-hero-copy--left  { margin-right: auto; text-align: left; }
.ms-hero-copy--right { margin-left: auto;  text-align: right; }
.ms-hero-card--media .ms-hero-copy--right { align-items: flex-end; }
.ms-hero-card--media .ms-hero-copy--left  { align-items: flex-start; }
/* the decorative figure lives on the right; with right-positioned copy it would collide */
.ms-hero-copy--right ~ .ms-hero-figure { display: none; }

/* --- per-word animation ---------------------------------------------------------------------
   Each word is its own inline-block so it can transform independently. The words start hidden
   and are revealed by the `is-animating` class the script adds ONCE per slide, per page load —
   ruling 8 forbids a replay on slide return.

   `--ms-word-delay` is set per word, in JS, from a FULLY RANDOM permutation re-rolled on each
   slide's first entry. Chat flagged that a headline can assemble out of reading order and the
   operator confirmed "full" — this is not to be softened into a stagger. Invariant 16.

   THE NO-JS AND REDUCED-MOTION PATHS BOTH SHOW THE TEXT. A word is only hidden while an
   ancestor carries `is-animating`, so if the script never runs the copy is simply visible —
   text that depends on JS to appear is a worse failure than no animation. */
.ms-hero-word { display: inline-block; will-change: transform, opacity; }

[data-ms-overlay].is-animating .ms-hero-word {
  opacity: 0;
  transform: translateY(var(--ms-hero-word-rise));
  animation: ms-hero-word-in var(--ms-hero-word-duration) var(--ms-hero-word-ease) forwards;
  animation-delay: var(--ms-word-delay, 0ms);
}

@keyframes ms-hero-word-in {
  from { opacity: 0; transform: translateY(var(--ms-hero-word-rise)); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  /* Not "no delay" — no animation at all, and the words are plainly visible. */
  [data-ms-overlay].is-animating .ms-hero-word {
    opacity: 1;
    transform: none;
    animation: none;
  }
}
/* --- CROSSFADE (operator, 2026-09-24: "Crossfade") -----------------------------------------
   Animation = Fade in -> `ms-hero-crossfade`, driven by Custom/mouldshop-hero-crossfade.js.
   EVERYTHING HERE IS GATED ON `.is-xf-ready`, which only that script adds: without JS the hero
   is the ordinary swiffy strip with the first slide showing. Once ready, all slides share one
   grid cell; the active one sits on top and fades 0 -> 1 while the previous one (`.is-leaving`)
   holds at full opacity underneath, so the picture never dips through the background. The grid
   row takes the tallest slide, so banners of unequal height do not make the block jump. */
.ms-hero-crossfade { --ms-hero-xfade: .9s; }
.ms-hero-crossfade.is-xf-ready .slider-container {
  grid-auto-flow: row;
  grid-template-columns: 100%;
  grid-auto-columns: 100%;
  grid-auto-rows: auto;
  overflow: hidden;
  scroll-snap-type: none;
  cursor: auto;
}
.ms-hero-crossfade.is-xf-ready .slider-container > * {
  grid-area: 1 / 1;
  opacity: 0;
  visibility: hidden;
  z-index: 0;
  pointer-events: none;
}
.ms-hero-crossfade.is-xf-ready .slider-container > .is-leaving {
  opacity: 1;
  visibility: visible;
  z-index: 1;
}
.ms-hero-crossfade.is-xf-ready .slider-container > .is-active {
  opacity: 1;
  visibility: visible;
  z-index: 2;
  pointer-events: auto;
  transition: opacity var(--ms-hero-xfade) var(--ms-card-hover-curve, cubic-bezier(0.78, 0, 0.22, 1));   /* the card hover curve (operator) */
}
@media (prefers-reduced-motion: reduce) {
  .ms-hero-crossfade.is-xf-ready .slider-container > .is-active { transition: none; }
}

.ms-hero-body { margin: 0 0 1.6rem; max-width: 40ch; font: 400 1.0625rem/1.55 var(--ms-font-body); color: #d6e2f5; }
.ms-hero-cta {
  display: inline-flex; align-items: center; gap: .55rem;
  padding: .7rem 1.5rem;
  border-radius: 999px;
  background: var(--ms-blue, #6CB2DE);
  color: var(--ms-navy-deep, #001D52);
  font: 700 .95rem var(--ms-font-head);
  text-decoration: none;
  transition: background .2s ease, transform .2s ease;
}
.ms-hero-cta:hover { background: #fff; transform: translateX(2px); }

/* decorative feature panel (placeholder for the editorial product image) */
.ms-hero-figure { position: absolute; inset: 0 0 0 auto; width: 46%; z-index: 1; pointer-events: none; }
.ms-hero-figure-glow {
  position: absolute; top: 50%; right: 8%; width: 60%; aspect-ratio: 1;
  transform: translateY(-50%);
  background: radial-gradient(circle, rgba(108, 178, 222, .45), transparent 65%);
  filter: blur(8px);
}
.ms-hero-figure-mark {
  position: absolute; top: 50%; right: 12%; transform: translateY(-50%);
  font: 800 11rem/1 var(--ms-font-head);
  color: rgba(255, 255, 255, .06);
  user-select: none;
}
/* editor-supplied feature image (replaces the placeholder mark when set) */
.ms-hero-figure-img {
  position: absolute; top: 50%; right: 6%; transform: translateY(-50%);
  max-width: 86%; max-height: 86%;
  object-fit: contain;
}

/* ── Swiffy skin (our colours over the engine) ───────────────────────────────
   Nav = plain side chevrons, ALWAYS visible, no box/field behind them (operator).
   Dots = small, round, overlaid at the banner bottom (operator). */
.ms-hero-slider .slider-nav {
  visibility: visible;            /* always show (swiffy hides by default) */
  opacity: .85;
  background: transparent;        /* kill the field box */
  filter: none;                   /* kill the drop-shadow halo */
  --swiffy-slider-nav-light: #fff;
}
.ms-hero-slider .slider-nav:hover { opacity: 1; }
.ms-hero-slider .slider-nav::after { width: 2.1rem; height: 2.1rem; }
.ms-hero-slider .slider-nav::before { display: none; }   /* drop the big hit-field box */

/* CONTROLS ABOVE THE TEXT OVERLAY (operator, 2026-09-24: "dots indicator doesn't work").
   The per-slide overlay (mouldshop-slide-overlay.css) is absolute, full width, bottom-anchored,
   z-index 2 — and no ancestor inside the slider makes a stacking context, so it is painted over
   swiffy's dots and arrows, which carry no z-index at all. Every click on a dot landed on the
   scrim. 3 puts both controls back on top. */
.ms-hero-slider .slider-indicators,
.ms-hero-slider .slider-nav { z-index: 3; }

.ms-hero-slider .slider-indicators { bottom: 0; margin-bottom: .8rem; }
.ms-hero-slider .slider-indicators > * {
  width: .45rem; height: .45rem;
  border-width: .35rem;
  background-color: #fff;
  opacity: .45;
}
.ms-hero-slider .slider-indicators > *.active { opacity: 1; background-color: #fff; }

/* MS-SWITCH-BANNER: the banner image switch, 767.98 / 768 (DEC-0094). TASK-0152b moved this rule here from the retired phone step. */
@media (max-width: 767.98px) {
  .ms-hero-card { min-height: 300px; padding: 2rem 1.5rem; }
  .ms-hero-copy,
  .ms-hero-card--media .ms-hero-copy { max-width: 100%; }
  .ms-hero-figure { display: none; }
  .ms-hero-body { color: #d6e2f5; }
}

/* TASK-0151: an IMAGE slide below the switch point is the image and nothing else. The ≤767.98 rule
   above sets .ms-hero-card's min-height and padding at the same specificity as, and later than,
   .ms-hero-card--media's reset (line 83), so on phones an image slide got the gradient-slide floor
   and padding back: a navy box with the banner inset in it (operator, 2026-09-27). Here the
   reset wins again - no floor, no padding, no ground - and the card takes the image's height
   (TASK-0149's square when a mobile image is set). (0,3,0) also beats the per-slide
   .ms-hero-slide--b / --c backgrounds at (0,2,0). Gradient slides (no image) keep their look.
   MS-SWITCH: the site's mobile switch point; the full list of places is in WORKLOG (TASK-0149). */
@media (max-width: 989px) {
  .ms-hero-slide .ms-hero-card.ms-hero-card--media {
    min-height: 0;
    padding: 0;
    background: none;
  }
}
