/* ==========================================================================
   Core values - the rolling list
   Markup: about.html, section#yp-values. This file owns the whole section.

   A port of the client's 21st.dev "rolling-list" to plain HTML and CSS. No
   React, no Tailwind, no next/image: every utility is resolved to the value it
   compiles to and written out. The resolutions used below are theirs, not
   approximations of theirs:

     text-5xl 48px/1      text-7xl 72px/1     font-black 900
     tracking-tighter -0.05em                 tracking-widest 0.1em
     text-xs 12px/16px    py-6 24px 0         top-8 32px
     h-[60px] 60          h-20 80             h-32 128        w-48 192
     rounded-lg 8         shadow-2xl 0 25px 50px -12px rgb(0 0 0 / .25)
     duration-500 500ms   ease-out cubic-bezier(0, 0, .2, 1)
     ease-[cubic-bezier(.76,0,.24,1)]         -translate-y-1/2 translateY(-50%)
     scale-95 .95         rotate-3 3deg       translate-x-4 16px
     grayscale grayscale(1)                   bg-blue-600/15 + mix-blend-overlay

   FOUR animated layers hang off one hover, and the component is the count, not
   any single move. Drop one and the row reads as dead:
     1  the title stack rolls          500ms cubic-bezier(.76, 0, .24, 1)
     2  the category label fades out   300ms cubic-bezier(.4, 0, .2, 1)
     3  the photograph is dealt in     500ms ease-out
     4  the photograph loses its grey  500ms ease-out, its own transition

   Layer 3 is the one the previous build dropped and the client rejected. It is
   a photograph, at maximum figure/ground contrast against the page. A card of
   body copy on Shell over a Shell section is beige on beige, which is why
   hovering appeared to do nothing.

   FIVE departures from the reference, each forced by content or by the host
   page rather than by taste, and each documented at the rule that makes it:
     a  titles are two words, not one, so each word takes its own line
     b  the category slot carries a sentence, not a filing tag, so it takes the
        row's top-right band instead of a slot beside the title, and stays put
        at the widths where there is no plate to hand the zone to
     c  the accent is a value on the brand's own ink-to-sand ramp, because this
        palette has no hue to spend
     d  that slot is set in the brand serif, where theirs is the default
        grotesque, and it is the section's only 12px caps
     e  the section is hosted in this site's own furniture - the .yp-shead
        name-over-label pair and the .yp-sec / .yp-wrap rhythm - not their
        centred "Process" eyebrow inside a px-4 py-12 box
   Departures d and e, and one more the markup makes on its own - the five
   paragraphs of supporting copy the client supplied are not in these rows -
   are argued in full in the fragment, which is where they belong: the list
   there is the whole list, and it is six items, not five.
   ========================================================================== */

.yp-val {
  /* The reference's mobile step. Both sizes are hard, not fluid: their whole
     proportion is 48px below 768 and 72px from 768 up, and a clamp that only
     reaches 72px at 1125px renders a different component through the entire
     useful range. */
  --yp-val-fs: 48px;
  --yp-val-line: 60px;              /* their h-[60px], ratio 1.25 to the type */

  /* Departure (a). Their titles are single words - DISCOVER, DESIGN - and
     every measurement in the component assumes one line that fits. Ours are
     two: SEAMLESS RELIABILITY sets about 950px on one line at 72px, which no
     measure with this component's density holds, and a title left to wrap
     inside an overflow-hidden window is a title with its second half cut off.
     So each word takes its own line and the line box is two lines deep. The
     mechanic is untouched: the stack is still exactly two boxes and still
     travels exactly one, at either breakpoint, off the same -50%. */
  --yp-val-box: calc(var(--yp-val-line) * 2);

  --yp-val-pad: 24px;               /* their py-6, fixed at every viewport */
  --yp-val-card-w: 192px;           /* their w-48 */
  --yp-val-card-h: 128px;           /* their h-32 */

  /* Their two curves. The roll is a symmetric in-out quart - slow, hard, slow
     - and it is the character of the whole component. The reveal is Tailwind's
     ease-out, which leaves at full speed and decelerates. They are different
     curves on purpose; running both on one easing flattens the sequence. */
  --yp-val-roll: cubic-bezier(0.76, 0, 0.24, 1);
  --yp-val-out: cubic-bezier(0, 0, 0.2, 1);

  /* Departure (c). Theirs is text-blue-500 and it appears twice - the italic
     title and a 15% wash over the photograph - which is what makes it read as
     a system rather than a one-off. This palette is White, Shell, Sand and
     Ink: there is no hue here to spend, and inventing one would put the only
     colour on the site inside a hover state.

     So the accent is a VALUE, taken from the brand's own ramp: 52% ink into
     Sand. Mixed rather than made translucent on purpose - ink at 56% alpha
     over Shell composites to a flat grey, while the same value mixed with
     Sand in oklab keeps the warmth, which is the whole difference between an
     accent and a disabled state. It runs in both of the reference's two
     places, the italic title and the wash over the photograph, so the system
     still reads. Their state 2 is also lighter than their state 1 (blue-500
     against neutral-900), so the direction matches as well as the count.

     The alpha version is the fallback for engines without color-mix; it is
     the same value, only cooler. Because it is translucent and the opaque mix
     is not, the wash over the photograph has to be told how much of itself to
     show on each path, or the fallback multiplies twice (0.56 x 0.28 = 0.157)
     and layer 3 silently drops below the coverage it needs to exist. */
  --yp-val-accent: rgb(var(--yp-ink-rgb) / 0.56);
  --yp-val-tint-o: 0.5;             /* 0.56 alpha x 0.5 = the 0.28 below */

  /* Their max-w-2xl is 672px, and the density of the whole thing comes from
     72px type inside that measure. 672 cannot be kept literally. Measured in
     the browser, UNWAVERING sets 579px at 72px in this face, and the plate
     needs 192px of clear right-hand zone plus a gutter: 579 + 40 + 192 = 811.
     840 is the first round number that clears it, and it keeps the ratio of
     type to measure at 11.7 against their 9.3. The site's own 1460px wrap
     would put the right-hand layers a foot away from the words they belong
     to, which is the density the reference's proportion exists to prevent. */
  --yp-val-measure: 840px;

  max-width: var(--yp-val-measure);
  margin: 0;
  /* All three !important, and all three measured rather than assumed. Divi's
     '#left-area ol, .entry-content ol, .et-l--body ol' block is specificity
     (0,1,1) and beats a bare .yp-val on every property it names: it draws
     decimal markers straight through the design, lands a padding-bottom of 1em
     that shows up as a stray gap under the last row's rule, and sets
     line-height: 26px. That last one was the easy one to miss: it is the third
     property in the same declaration block, and it is inert today only because
     the title pins 1 and the tag pins 16px. Pinned to normal here so a row that
     ever gains a line of text takes typography.css's own 1.62 on li, which is
     the site's body leading, rather than a number Divi picked for a
     numbered list. */
  padding: 0 !important;
  list-style: none !important;
  line-height: normal !important;
}

@supports (color: color-mix(in oklab, #000, #fff)) {
  .yp-val {
    --yp-val-accent: color-mix(in oklab, var(--yp-ink) 52%, var(--yp-sand));
    --yp-val-tint-o: 0.28;          /* opaque source, so this is the coverage */
  }
}

/* Their list has no rule above the first row; only bottom edges draw. */
.yp-val-row {
  list-style: none !important;
  position: relative;
  /* A column rather than a block, so the promise line can be moved above the
     title at the width where it takes the reference's slot without being
     moved above it in the DOM, where the title has to lead. */
  display: flex;
  flex-direction: column;
  padding: var(--yp-val-pad) 0;
  /* Their border-neutral-200 is a ten percent step off white. Ink needs a
     touch more alpha than that to make the same step off a warm beige. */
  border-bottom: 1px solid rgb(var(--yp-ink-rgb) / 0.12);
}

/* The row has no handler and no link. The cursor is the entire affordance
   telling a visitor that the row answers the pointer, which is the only reason
   the four layers ever get seen - so it is only offered where a pointer
   exists. On touch there is no hover to spend, and a pointer cursor there
   promises an interaction the visitor cannot reach. */
@media (hover: hover) {
  .yp-val-row { cursor: pointer; }
}

/* ---- 1. the roll --------------------------------------------------------- */

/* The clip is the mechanic. It has to be exact: window height, line box height
   and stack travel are all driven off --yp-val-line so they cannot drift
   apart at a breakpoint. */
.yp-val-window {
  position: relative;
  overflow: hidden;
  height: var(--yp-val-box);
  /* A flex item may not shrink out of the height the clip depends on. */
  flex: none;
}

/* transform only, never transition-all: the stack has nothing else to animate
   and a blanket transition would pick up the reveal's properties too. */
.yp-val-stack {
  transition: transform 0.5s var(--yp-val-roll);
}

/* -50% of a two-box stack is exactly one box, at either breakpoint, without
   naming a pixel value. */
.yp-val-row:hover .yp-val-stack {
  transform: translateY(-50%);
}

/* Vertical centring is done once, by the flex box. Their h2 carries
   line-height 1 and the box does the rest; pinning the line-height to the box
   as well centres the ink twice and fights itself. */
.yp-val-line {
  display: flex;
  align-items: center;
  height: var(--yp-val-box);
}

.yp-val-title {
  margin: 0;
  /* Divi puts padding-bottom: 10px on every h1-h6. Inside a box measured to
     the pixel that 10px is the whole overflow, and the second copy shows
     under the first. */
  padding: 0 !important;
  /* !important because typography.css sets the family on every h1-h6 with it;
     the value is the same today, but a plain declaration here is not the one
     that wins and the block would silently stop owning its own face. */
  font-family: var(--yp-serif) !important;
  font-size: var(--yp-val-fs);
  /* Their font-black is 900. Newsreader is a 200-800 variable face, so 900 is
     not reachable and the missing 100 units have to be paid for somewhere
     else. They are paid here, on the optical size axis: opsz is normally set
     to the rendering size, and a 72px display cut is drawn thin and fine.
     Pinning opsz to the small-text end of the axis gives the sturdy stems and
     blunt serifs that cut is designed for, at 72px, which is most of what the
     900 was buying. font-weight is still declared for the fallback stack,
     which has no axes, and !important because typography.css sets a weight on
     every heading with it - without that this reads as body copy at 72px. */
  font-weight: 800 !important;
  font-variation-settings: "opsz" 10, "wght" 800;
  /* !important throughout: typography.css sets line-height 1.28 and
     letter-spacing 0.09em on h3, and both would break the box. */
  line-height: 1 !important;
  /* Their tracking-tighter in full. At 72px this is 3.6px off every gap, and
     the compression is what fuses the caps into one mass; taking 70% of it
     takes most of the effect. */
  letter-spacing: -0.05em !important;
  text-transform: uppercase;
  color: var(--yp-ink);
}

/* Departure (a) again. Blocks rather than <br>, so the space between the two
   words survives in the accessible name. */
.yp-val-title span {
  display: block;
}

/* Their state 2 changes TWO properties, slant AND hue. Italic alone swaps a
   72px word for the same 72px word and reads as a jitter, not a state.
   font-style needs !important because typography.css forces normal on every
   heading; without it the second copy is identical to the first. */
.yp-val-title-alt {
  font-style: italic !important;
  color: var(--yp-val-accent);
}

/* ---- 2. the category label ----------------------------------------------- */

/* Departure (b). Theirs is a one-word taxonomy tag - "Research" - which can
   fade out on hover because nothing is lost when it goes, and which is
   absolutely positioned beside the title because one word always fits there.
   Ours is the value's promise line: a sentence, 383px of it at the longest,
   which will not share a line with a 579px word inside any measure that keeps
   the reference's density.

   So it takes the row's top-right band rather than a slot beside the title:
   same corner, same 32px from the top, same typography, same 300ms fade, and
   still clear above the plate's centreline. What it cannot do is overlap the
   words, which is what the previous build's wrapped three-line version did.

   Below 880px, and on any screen with no pointer at any width, it is a plain
   line under the title. There is no plate at those widths (see the reveal),
   and a label that fades with nothing arriving to replace it is just content
   being taken away. */
.yp-val-tag {
  display: block;
  margin: 16px 0 0;
  /* Departure (d). Theirs is the page's default grotesque, at a size where a
     grotesque is the safer choice. This is the brand serif because there is no
     second face on this site to switch to, and 12px tracked caps in Newsreader
     hold their shape - the tracking below is doing the legibility work a
     grotesque would otherwise do. */
  font-family: var(--yp-serif);
  font-size: 12px;
  line-height: 16px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  /* Their neutral-400 is the lightest of three greys, and ink at 0.4 over
     Shell is exactly that step - 2.61:1. Theirs can afford it because it
     carries the word "Research" and nothing is lost when it goes. This one
     carries the client's promise, and below 880px it is the only supporting
     copy on the row, so it has to pass AA at 12px: 4.5:1, not the 3:1 that
     18.66px bold would have bought. 0.66 measures 5.94:1 on Shell and 5.69:1
     on Sand, so the section can move ground without re-failing. The three
     ranks of grey survive - the title is still 17.77:1 above it. */
  color: rgb(var(--yp-ink-rgb) / 0.66);
}

/* ---- 3 + 4. the reveal --------------------------------------------------- */

/* A fixed 192x128 plate. Fixed is the point: theirs is exactly as tall as
   their row, so it fills the row by construction and can never spill onto a
   neighbour and hand the hover to the wrong one mid-transition. Ours is 128px
   inside a taller row, which keeps that guarantee with room to spare.
   Switched on at 880px, where the measure can hold it clear of the words. */
.yp-val-reveal {
  display: none;
  position: absolute;
  right: 0;
  top: 50%;
  /* Their z-20. Deliberately generous: this sits over a 72px title and needs
     headroom against anything else the page puts nearby. */
  z-index: 20;
  width: var(--yp-val-card-w);
  height: var(--yp-val-card-h);
  /* The radius only reads because the clip crops the photograph to it. The
     two are a pair; the previous build dropped both. */
  overflow: hidden;
  border-radius: 8px;
  /* Their shadow-2xl, blur and spread and alpha exactly. The colour is Ink
     rather than their neutral black because this project allows no literal
     colours, and warm black is what sits under everything else on the page. */
  box-shadow: 0 25px 50px -12px rgb(var(--yp-ink-rgb) / 0.25);
  /* Keeps the tint's blend from reaching the page behind the card. */
  isolation: isolate;
  /* The card must never steal the hover from the row that summoned it. */
  pointer-events: none;
  /* Four simultaneous offsets at rest. The 3deg is what makes it read as a
     card being dealt rather than a box being faded in. */
  opacity: 0;
  transform: translate(16px, -50%) rotate(3deg) scale(0.95);
  transition: opacity 0.5s var(--yp-val-out),
              transform 0.5s var(--yp-val-out);
}

/* Settles perfectly square. translateY(-50%) is the one value that persists
   through both states. */
.yp-val-row:hover .yp-val-reveal {
  opacity: 1;
  transform: translate(0, -50%) rotate(0deg) scale(1);
}

/* The media is the surface: no panel colour, no border. The originals are
   1200x1800 portraits and the plate is 3:2, so the 44% band each row shows is
   cut into a derivative at build time rather than at paint time - the src is
   already 3:2. cover stays because it is the correct behaviour if anything
   ever hands this element a differently-shaped file. */
.yp-val-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Their second, nested animation. It runs concurrently with the card's
     geometry and it is half the drama of the reveal: the parked card is
     monochrome, so the colour arriving is the payoff. A port with one
     animation is running half the motion. */
  filter: grayscale(1);
  transition: filter 0.5s var(--yp-val-out);
}

.yp-val-row:hover .yp-val-img {
  filter: grayscale(0);
}

/* Focal points, measured off each frame: the value is where the face sits in
   the 1200x1800 original once cover has scaled it to 192x288 and the plate is
   showing 128 of those 288 pixels. These are the numbers the -plate-384 and
   -plate-576 derivatives were cut at, so against those files they compute to
   nothing - a 3:2 source in a 3:2 box has no overflow to position. They are
   kept as the record of the cut, and because they are what makes a full-size
   original render correctly if one is ever put back in the src. Named for the
   photograph, not for the row, so reordering the list cannot mis-crop
   anybody. */
.yp-val-img-a { object-position: 50% 20%; }
.yp-val-img-b { object-position: 50% 24%; }
.yp-val-img-c { object-position: 50% 8%; }
.yp-val-img-d { object-position: 50% 8%; }
.yp-val-img-e { object-position: 50% 8%; }

/* Their third layer: a static wash in the accent colour, no hover change,
   tying the photograph to the hue the hover title uses. Their blend mode, and
   their job. The alpha is 28% against their 15% because their blue carries
   chroma to spare and a near-neutral stone has to be laid on thicker to do
   the same amount of work; below about 20% it is indistinguishable from no
   layer at all, and a layer that does nothing is not a layer. */
.yp-val-tint {
  position: absolute;
  inset: 0;
  background: var(--yp-val-accent);
  mix-blend-mode: overlay;
  opacity: var(--yp-val-tint-o);
}

/* ---- the one breakpoint -------------------------------------------------- */

/* Theirs, and it does the same job here: 48px type in a 60px line below,
   72px in an 80px line above. */
@media (min-width: 768px) {
  .yp-val {
    --yp-val-fs: 72px;
    --yp-val-line: 80px;            /* their h-20, ratio 1.111 to the type */
  }
}

/* The hover system - the label in their slot, its fade, and the plate it
   hands the zone to - switches on together at 880px, because that is the
   first width where the measure holds a 579px word, a 40px gutter and a
   192px plate at once. Theirs manages it from 768px on single words like
   DISCOVER; two-word titles cost the extra 112px. Splitting the pair would
   be worse than moving it: a label that fades with no plate arriving is
   content being removed for nothing.

   It also needs (hover: hover). On a touch screen the first tap sets :hover
   and it sticks until the visitor taps elsewhere, so without this the promise
   line would fade out on an iPad and stay gone - real content hidden behind a
   mouse-only mechanic. The plate goes with it rather than being left on its
   own: there is no hover to deal it in, and a card that can only ever be
   summoned by a pointer is not content a tablet is missing. Below the guard
   the row is the same static, fully readable pair the phone gets. */
@media (min-width: 880px) and (hover: hover) {
  .yp-val-tag {
    /* Their top-right corner, off the title's line rather than beside it. The
       row is a column, so -1 lifts it above the window without lifting it
       above the title in the DOM. 8px of margin puts its cap line at their
       top-8, 32px down from the row's edge. */
    order: -1;
    margin: 8px 0 12px;
    text-align: right;
    /* A right-anchored line, never a text block: if it ever cannot fit it has
       to overflow visibly here rather than quietly become three lines. */
    white-space: nowrap;
    /* 300ms against the plate's 500ms, so the label is gone before the
       photograph lands and the handoff is sequenced rather than simultaneous.
       Tailwind's default curve, which is not the same as their ease-out. */
    transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1);
    /* It fades rather than moves, so it must not sit in the pointer's way. */
    pointer-events: none;
  }

  .yp-val-row:hover .yp-val-tag {
    opacity: 0;
  }

  .yp-val-reveal {
    display: block;
  }
}

/* Their 48px floor assumes an eight-character word: DISCOVER sets 210px and
   fits any phone. UNWAVERING sets 386px at 48px and a 360px phone has 328px
   of measure, so the floor has to step once more. A third hard step, not a
   clamp: the type still lands on a known size at a known width instead of
   rendering 28px on a phone the way a viewport clamp does. */
@media (max-width: 460px) {
  .yp-val {
    --yp-val-fs: 40px;
    --yp-val-line: 50px;
  }
}

/* And once more, because 40px does not reach the bottom of the range. The
   window clips horizontally as well as vertically - that is the same
   overflow:hidden the roll is built on - and a single word cannot wrap, so a
   title wider than the measure is not a wrapped title, it is a cut one, with
   no ellipsis and no scrollbar to show it happened. UNWAVERING sets 578.6px at
   72px, so 321px at 40px, against 288px of measure on a 320px phone: the row
   reads UNWAVERIN. 32px puts it at 257px inside that 288, and holds the same
   1.25 box-to-type ratio the 48px step uses.

   The step is at 380 rather than 360 because 360px clears 40px type by 6.6px,
   which is not clearance. Newsreader may not have arrived, and the fallback
   stack is not metric-compatible with it, so the margin at 360-375px belongs
   to whichever face actually loaded. Below the 460px block, not above it: both
   queries match at 380px and the later one has to be the one that wins. */
@media (max-width: 380px) {
  .yp-val {
    --yp-val-fs: 32px;
    --yp-val-line: 40px;
  }
}

/* ---- motion -------------------------------------------------------------- */

/* Nothing here is gated on JS or on a class, so reduced motion only has to
   remove the travel. Every state still changes, instantly: the stack still
   swaps, the photograph still arrives and still finds its colour. Fading a
   card in from square is not vestibular motion; flying it in from a 3deg tilt
   is, so the resting transform loses everything except its centring. */
@media (prefers-reduced-motion: reduce) {
  .yp-val-stack,
  .yp-val-tag,
  .yp-val-reveal,
  .yp-val-img {
    transition: none;
  }

  .yp-val-reveal {
    transform: translateY(-50%);
  }
}
