/* THE TESTIMONIAL BLOCK , shared.
   Lifted out of index.html so a case page can carry it without a third hand-copy of the
   same 60 lines. index.html and orx.html still hold their own inline copies; migrating
   those onto this file is the component work (open item 162), and until that happens any
   change here has to be made in three places, which is exactly the problem 162 exists to
   end.

   Pair with assets/redaction.css (the ladder + the ten faces) and, for the motion,
   assets/js/redaction.js + assets/js/testimonial.js.

   Two things are parameterised so the block is not welded to one client:
     .says-fig[data-safe]   the face rectangle, as a clip-path inset. Per portrait, never
                            shared: two people are never framed the same way.
     .says-mark.tall        for a STACKED client lockup, which needs a height cap instead
                            of a width, or it towers over the credit above it.
*/

/* EVERY testimonial block carries data-testimonial="<person-slug>" on its .says-in.
   That is the handle: one attribute, greppable across the whole site, and the thing
   _tools/testimonials.py reads to list who is quoted, where, in which language and
   whether they have approved it. Never add a testimonial without one.
   The register with the sources and the approval state is _context/TESTIMONIALS.md. */

.says{padding:clamp(70px,9vw,120px) 0;border-top:1px solid var(--line)}
.says-in{display:grid;grid-template-columns:clamp(150px,15vw,190px) minmax(0,1fr);
  gap:clamp(28px,4vw,60px);align-items:start;margin-top:clamp(28px,3.4vw,44px)}
/* A SECOND testimonial in the same section. A rule and real air between them, not a
   card each: two people saying different things about different jobs need separating,
   but boxing them would turn a page that has no cards anywhere into one that has two. */
.says-in + .says-in{margin-top:clamp(48px,6vw,84px);
  padding-top:clamp(44px,5.4vw,76px);border-top:1px solid var(--line)}

/* THE ZIGZAG. With two testimonials stacked, two portraits in the same left column read
   as a table with a photo column. Alternating the side makes them two people rather than
   two rows, and it is the same move the deliverables sections make with .flip.
   :has(~ .says-in) means "a testimonial with another one after it", so it only fires when
   a section actually carries more than one. A case page with a single testimonial keeps
   the portrait on the left and needs no page specific rule. */
.says-in:has(~ .says-in){grid-template-columns:minmax(0,1fr) clamp(150px,15vw,190px)}
.says-in:has(~ .says-in) .says-fig{order:2}
/* the quote is set ragged-right, so when it sits on the left of the portrait the ragged
   edge should face the portrait, not the page margin */
.says-in:has(~ .says-in) .says-q,
.says-in:has(~ .says-in) .says-pre{margin-right:0}

/* height:auto is load-bearing. The img carries width/height ATTRIBUTES so the browser can
   reserve space before the file loads, but that makes both dimensions determined, and a
   determined height silently voids aspect-ratio. */
.says-fig img{width:100%;height:auto;aspect-ratio:1/1.618;object-fit:cover;display:block;
  filter:grayscale(1) contrast(1.06)}
.says-fig{position:relative;overflow:visible}
.says-fig .shot{display:block;position:relative;overflow:hidden}
/* THE FACE MASK. A second copy of the portrait, clipped to the face rectangle and stacked
   ABOVE the inversion squares, so wherever a square lands the face is redrawn clean.
   Inverted human faces are creepy, and this is the structural fix rather than trying to
   route five moving squares around a head. The inset comes from the markup. */
.says-fig .safe{position:absolute;inset:0;overflow:hidden;pointer-events:none;
  clip-path:inset(var(--safe,2% 7.63% 38% 7.63%))}
/* The squares carry TWO offsets that must not fight each other: left/top in percent, the
   composed layout position, and a translate in pixels, the live parallax and cursor lean.
   Different properties means the drift can be damped every frame without ever recomputing
   the composition. */
.says-fig .fsq{position:absolute;background:#fff;mix-blend-mode:difference;pointer-events:none;
  opacity:0;will-change:transform,opacity;
  transform:translate3d(var(--dx,0px),var(--dy,0px),0)}
html.lite .says-fig .fsq{display:none}
/* NO reduced-motion kill switch. Timo's own Windows has "Animation effects" off, which
   makes Chrome report prefers-reduced-motion:reduce, which blanked this on the machine the
   site is designed on. Reduced motion halves the amplitude in JS instead. */

/* THE LEAD-IN , a second, earlier quote from the same person.
   Sequence is the whole point: he noticed the work before he ever spoke to Timo, then
   hired him. Small-then-big reads as chronological without having to label it "first" and
   "then", and the rule between them is the beat. */
/* --dim, not --dimmer: at 9.9px with .2em tracking the darker grey was barely legible */
.says-pre-lab{display:block;font-family:var(--mono);font-size:var(--t--3);letter-spacing:.2em;
  text-transform:uppercase;color:var(--dim);margin-bottom:var(--s-3)}
/* plain typographic quotes on the lead-in, not a second display mark: two big glyphs in
   one block would fight, and the hierarchy is the point , the display mark belongs to the
   quote that carries the section */
.says-pre{max-width:62ch;font-size:var(--t-0);line-height:1.62;color:var(--dim);
  padding-bottom:clamp(20px,2.4vw,32px);margin-bottom:clamp(24px,2.8vw,40px);
  border-bottom:1px solid var(--line)}
.says-pre::before{content:"“"}
.says-pre::after{content:"”"}

/* 23ch made a tall square block of nine short lines. The quote is the one piece of proof
   in the section and it was reading like a column, so it runs the full width of its own
   column at a slightly smaller size: fewer, longer lines. */
.says-q{font-family:'RedC',serif;font-weight:400;letter-spacing:-.015em;line-height:1.24;
  font-size:clamp(22px,2.4vw,34px);max-width:none;position:relative}
/* THE OPENING QUOTE MARK.
   A BLOCK above the text, not a mark hung in the left margin. Hanging it needs roughly
   -0.5em of negative offset, which at this size is about 46px against a grid gap that
   clamps down to 28px, and on a phone the grid collapses to one column where the glyph
   would sit outside .wrap entirely. Above the text it cannot overflow at any width.
   Redaction, because the quote itself is Redaction and the mark should read as the same
   piece of type rather than a dingbat borrowed from somewhere else. */
.says-q::before{content:"“";display:block;font-family:'RedC',serif;
  font-size:clamp(54px,5.6vw,92px);line-height:.72;color:var(--ink);opacity:.26;
  margin-bottom:clamp(2px,.5vw,8px)}
/* the highlighted word is split into one span per character by the wave, so without this a
   line break can land INSIDE it. German caught it first: "Er d enkt mit". */
.says-q .obsess{white-space:nowrap}

.says-cred{margin-top:clamp(24px,3vw,34px)}
.says-who{font-size:var(--t--1);font-weight:700}
.says-role{display:block;font-family:var(--mono);font-size:var(--t--3);letter-spacing:.14em;
  text-transform:uppercase;color:var(--dim);margin-top:6px;line-height:1.7}

/* the client mark, resting at 55% and coming to full on hover with a rule drawing across,
   the same motion the footer sweep uses, so the page keeps one idea of what a link is */
.says-mark{display:inline-block;margin-top:clamp(26px,3.4vw,40px);text-decoration:none;position:relative}
.says-mark img{width:clamp(140px,17vw,230px);height:auto;display:block;opacity:.55;transition:opacity .3s ease}
/* a STACKED lockup (mark over wordmark over strapline) matched on width becomes three
   times as tall as a horizontal one and swamps the credit. Height is the axis that
   matters for those. brightness(0) invert(1) is how the client marquee whitens marks
   already, so a brand-colour SVG needs no second asset. */
.says-mark.tall img{width:auto;height:clamp(46px,5.2vw,68px);filter:brightness(0) invert(1)}
.says-mark .u{position:absolute;left:0;right:0;bottom:-12px;height:1px;background:#fff;
  transform:scaleX(0);transform-origin:0 50%;transition:transform .45s cubic-bezier(.22,.72,.18,1)}
.says-mark .go{display:block;margin-top:var(--s-5);font-family:var(--mono);font-size:var(--t--3);
  letter-spacing:.18em;text-transform:uppercase;color:var(--dim);transition:color .3s ease}
.says-mark:hover img{opacity:1}
.says-mark:hover .u{transform:scaleX(1)}
.says-mark:hover .go{color:#fff}

@media(max-width:820px){
  /* one column, so the zigzag has nowhere to zig. Both the template and the order are
     reset, or the flipped block would put its portrait BELOW its own quote while the
     other one kept it above, which reads as a mistake rather than as a rhythm. */
  .says-in,
  .says-in:has(~ .says-in){grid-template-columns:1fr;gap:24px}
  .says-in:has(~ .says-in) .says-fig{order:0}
  .says-fig img{width:clamp(150px,40vw,200px)}
  .says-q{max-width:100%;font-size:clamp(22px,6.4vw,30px)}
  .says-mark img{width:clamp(130px,38vw,180px)}
}
