/* ============================================================
   PCG — Case showcase
   Real captures of a real build, presented in a CSS-drawn onyx
   device rig. As the case travels through the viewport the
   browser frame pages through the site — home, expertise, team,
   contact — and the phone follows it inside.

   Rules of the house:
   · transform + opacity only — never animate layout
   · the ENTRANCE is the site's own reveal engine ([data-rise],
     [data-stagger] in js/pcg.js + css/motion.css). This file never
     invents a second one and never hides copy behind a class of
     its own, so a blocked script leaves every word on screen.
   · one @media (prefers-reduced-motion) alternative for each move
   · the phone never covers the capture it sits beside: the two
     devices share a row, they do not share pixels
   ============================================================ */

.cs{
  --cs-hair:rgba(201,161,74,.34);
  --cs-hair-soft:rgba(201,161,74,.2);
  --cs-bezel:linear-gradient(180deg,#1c1c21,#101013);
  /* One number owns the rig's geometry: the phone takes the left
     column, the browser the right, and they can never overlap
     because the browser's inset is the wider of the two. */
  --cs-phone-w:21%;
  --cs-browser-x:25%;
}

/* ------------------------------------------------------------
   1. Nameplate + title
   The client's own name sets the section, in the display face —
   not a tracked label. It is the subject, so it reads like one.
   ------------------------------------------------------------ */
.cs-head{max-width:min(100%,780px)}

.cs-name{display:flex;align-items:center;gap:clamp(12px,1.2vw,18px);flex-wrap:wrap}
.cs-name__word{
  /* The client's own name is the section's subject, so it reads like one.
     At 700 it no longer needs the positive tracking that was propping up
     a 500 in the old display face. */
  font-family:var(--ff-display);font-weight:700;
  font-size:clamp(20px,1.8vw,26px);line-height:1;
  letter-spacing:0;color:var(--ink);
}
.cs-name__rule{
  display:block;flex:0 0 auto;
  width:clamp(30px,4vw,68px);height:1px;
  background:linear-gradient(90deg,var(--goud),rgba(201,161,74,0));
}
.cs-name__meta{font-size:var(--fs-fine);letter-spacing:.03em;color:var(--muted)}

.cs-title{margin-top:clamp(18px,1.8vw,26px);max-width:15ch}
/* font-style:normal is written explicitly rather than omitted, so the rule
   states the constraint at the point where someone would re-add the
   italic. Switzer has no italic; an italic here would be a synthesised
   oblique on a display-size glyph, and this selector (0,1,1) outranks the
   house guard `em{font-style:normal}` (0,0,1) AND loads after it, so the
   guard could not save it. Emphasis steps up in weight instead. */
.cs-title em{font-style:normal;font-weight:900;color:var(--gold-text)}

.cs-lead{
  margin-top:clamp(16px,1.6vw,22px);max-width:52ch;
  font-size:var(--fs-lead);line-height:1.6;color:var(--muted);
}

/* ------------------------------------------------------------
   2. Stage — device rig beside the story
   ------------------------------------------------------------ */
.cs-stage{
  margin-top:clamp(46px,5.5vw,88px);
  display:grid;align-items:center;
  gap:clamp(34px,4.4vw,74px);
  grid-template-columns:minmax(0,1.12fr) minmax(0,.88fr);
}
/* No overflow/clip on this column on purpose: the rig lifts 5px on hover and
   its focus ring sits 10px outside the link, and both have to stay visible.
   (This is also why the markup asks for a bare data-rise entrance rather than
   data-rise="media" — that one keeps a clip-path on the element for good.) */
.cs-viz,.cs-story{min-width:0}

.cs-stack{
  position:relative;display:block;color:inherit;
  /* The phone hangs a little below the browser — enough to read as a
     second object, not so much that it drags the caption off the rig. */
  padding-bottom:clamp(18px,2.6vw,38px);
  transition:transform var(--dur-3) var(--ease);
}
/* A warm pool of light behind the rig. Onyx-on-onyx would leave a dark
   site's screenshot dissolving into the section; this lifts it off the
   page without a border or a fake drop shadow doing the work. */
.cs-stack::before{
  content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  background:radial-gradient(68% 58% at 54% 46%,rgba(201,161,74,.115),transparent 72%);
}
@media (hover:hover){
  .cs-stack:hover{transform:translateY(-5px)}
}
.cs-stack:focus-visible{outline:2px solid var(--champagne);outline-offset:10px;border-radius:16px}

/* ------------------------------------------------------------
   3. The browser rig — drawn, never photographed
   ------------------------------------------------------------ */
.cs-browser{
  position:relative;z-index:1;
  width:calc(100% - var(--cs-browser-x));margin-left:var(--cs-browser-x);
  border:1px solid var(--cs-hair);border-radius:13px;overflow:hidden;
  background:var(--cs-bezel);
  box-shadow:
    0 0 0 1px rgba(0,0,0,.7),
    0 46px 92px -48px rgba(0,0,0,.96),
    inset 0 1px 0 rgba(255,255,255,.06);
  transition:border-color var(--dur-3) var(--ease);
}
@media (hover:hover){
  .cs-stack:hover .cs-browser{border-color:rgba(201,161,74,.5)}
}
.cs-browser__bar{
  display:flex;align-items:center;gap:clamp(9px,.9vw,14px);
  height:clamp(32px,2.7vw,40px);
  padding-inline:clamp(11px,1vw,16px);
  border-bottom:1px solid var(--cs-hair-soft);
  background:linear-gradient(180deg,rgba(255,255,255,.05),rgba(255,255,255,.012));
}
.cs-dots{display:inline-flex;gap:6px;flex:none}
.cs-dots i{display:block;width:6px;height:6px;border-radius:50%;background:rgba(230,207,143,.24)}
.cs-url{
  display:inline-block;flex:0 1 auto;min-width:0;
  font-family:var(--ff-body);font-size:var(--fs-fine);
  letter-spacing:.06em;color:rgba(230,207,143,.78);
  border:1px solid var(--cs-hair-soft);border-radius:999px;
  padding:.06em .95em;line-height:1.5;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}

/* One tick per page — the browser's own progress through the site,
   cut as GRADUATIONS rather than dashes so the rig indexes in the same
   language as the price scale (see "the .scale primitive" in
   css/instrument-live.css) and the process rail (css/motion.css): a
   major graduation per shot, a taller needle on the shot on screen.
   Same two tokens, same 9px-to-5px relationship the rail uses.

   Honest by that file's rule: the axis is the rig's own shot list,
   every mark is a real capture that exists in the markup, and .cs-cap
   prints the page's name in words directly underneath. It stays
   aria-hidden precisely because that caption already carries it.

   CONTRAST, measured against the bezel bar where these actually sit —
   --cs-bezel's #1c1c21 under the bar's rgba(255,255,255,.03) wash
   = #232328. The bezel is a fixed literal, not a skin token, so these
   ratios hold on every skin the rig can be placed on:
     --needle     #E6CF8F ... 10.21:1  the shot on screen
     --tick-major #96742C .... 3.60:1  a graduation marking a real shot
   Both clear 1.4.11's 3:1 for a non-text graphic. The old dim tick,
   rgba(230,207,143,.22), flattened to #4E493F and measured 1.75:1 —
   well under 3:1 — so this is also a fix. */
.cs-ticks{display:inline-flex;align-items:center;gap:clamp(7px,.7vw,11px);flex:none;margin-left:auto}
.cs-ticks i{
  display:block;width:1px;height:9px;
  background:var(--tick-major);
  /* Every tick is struck at the needle's full 9px and SHORTENED to the
     5px graduation height by a transform. Height is a layout property
     and the rule at the head of this file bans animating one; scaleY
     keeps the change on the compositor. */
  transform-origin:center;transform:scaleY(.556);
  transition:background-color var(--dur-2) var(--ease),transform var(--dur-2) var(--ease);
}
.cs-ticks i.is-current{background:var(--needle);transform:scaleY(1)}

/* ------------------------------------------------------------
   4. Screens — one rule for both devices
   Each capture is absolutely placed inside a fixed-ratio window,
   so its own dimensions can never shift the layout. The pages of
   one device are stacked in that window and cross-faded; the
   first page carries `is-current` in the MARKUP, so with no JS at
   all the frame still shows the site's homepage.
   ------------------------------------------------------------ */
.cs-screen{position:relative;overflow:hidden;background:#0c0b0d}
.cs-screen--wide{aspect-ratio:1440/900}
.cs-screen--tall{aspect-ratio:390/844;border-radius:19px}

/* Empty frame: a quiet drafting texture, so a missing capture
   degrades to a considered surface instead of a broken icon. */
.cs-screen::before{
  content:"";position:absolute;inset:0;
  background:
    repeating-linear-gradient(90deg,rgba(201,161,74,.05) 0 1px,transparent 1px 46px),
    repeating-linear-gradient(180deg,rgba(201,161,74,.035) 0 1px,transparent 1px 46px),
    linear-gradient(180deg,#17161b,#0a090b 72%);
}
.cs-screen::after{
  content:"";position:absolute;left:50%;top:50%;
  width:15px;height:15px;margin:-7.5px 0 0 -7.5px;
  border:1px solid rgba(201,161,74,.55);
  transform:rotate(45deg);
  opacity:0;transition:opacity var(--dur-2) var(--ease);
}
.cs-screen.is-empty::after{opacity:1}

/* A 2D transform on purpose: the incoming page settles out of a 1.2%
   zoom, which reads as a page arriving rather than a slide dissolving.
   No compositor layer is forced — six full-width captures promoted at
   once would cost more than the effect is worth.

   The two rules below are what keeps a page change from flashing dark.
   A plain crossfade dips: at the halfway point both captures sit near
   50% and the empty frame behind them shows through. So the incoming
   page is lifted above the outgoing one (z-index, applied the instant
   the class lands) and the outgoing one holds full opacity until the
   incoming has arrived — the .34s delay lives on the base rule, which
   is the rule that runs when a page LOSES is-current. Scrolling up
   behaves exactly like scrolling down, which a DOM-order crossfade
   does not. */
.cs-shot{
  position:absolute;left:0;top:0;width:100%;height:auto;z-index:0;
  opacity:0;transform:scale(1.012);
  transition:opacity .5s var(--ease) .34s,transform .9s var(--ease);
}
.cs-shot.is-current{
  opacity:1;transform:none;z-index:1;
  transition:opacity .55s var(--ease),transform .9s var(--ease);
}
.cs-screen.is-empty .cs-shot,
.cs-shot.is-broken{opacity:0}

/* ------------------------------------------------------------
   5. The phone — its own column, never a lid on the capture
   ------------------------------------------------------------ */
.cs-phone{
  position:absolute;left:0;bottom:0;z-index:2;
  width:var(--cs-phone-w);
  padding:5px;border-radius:24px;
  background:linear-gradient(168deg,#232329,#0d0d10);
  border:1px solid rgba(201,161,74,.38);
  box-shadow:
    0 0 0 1px rgba(0,0,0,.8),
    0 30px 56px -18px rgba(0,0,0,1),
    inset 0 1px 0 rgba(255,255,255,.08);
}
.cs-phone__speaker{
  position:absolute;left:50%;top:11px;z-index:3;
  width:30%;max-width:42px;height:4px;border-radius:3px;
  background:rgba(230,207,143,.2);
  transform:translateX(-50%);
}

/* A dozen pixels of parallax between the two devices as the case
   passes. --drift (0 → 1) is written by JS on desktop only; unset,
   it resolves to the midpoint and nothing moves. */
@media (min-width:721px){
  .cs-phone{transform:translateY(calc((var(--drift,.5) - .5) * -24px))}
}

/* ------------------------------------------------------------
   6. Page caption — which page the rig is showing, in words
   Sits under the browser, aligned to it. Decorative (aria-hidden):
   the link's own name says what the rig is.
   ------------------------------------------------------------ */
.cs-caps{
  position:relative;margin-top:clamp(12px,1.3vw,18px);
  margin-left:var(--cs-browser-x);min-height:1.7em;
}
.cs-cap{
  position:absolute;left:0;top:0;
  font-size:var(--fs-fine);letter-spacing:.14em;text-transform:uppercase;
  color:var(--muted);white-space:nowrap;
  opacity:0;transform:translateY(5px);
  transition:opacity .42s var(--ease),transform .5s var(--ease);
}
.cs-cap.is-current{opacity:1;transform:none}

/* ------------------------------------------------------------
   7. Claims — a read, not a card grid
   ------------------------------------------------------------ */
.cs-claims{list-style:none;margin:0;padding:0}
.cs-claim{
  position:relative;
  padding-left:clamp(20px,1.9vw,30px);
  padding-block:clamp(20px,2.1vw,30px);
}
.cs-claim:first-child{padding-top:0}
.cs-claim+.cs-claim{border-top:1px solid var(--rule-soft)}

.cs-claim__mark{
  position:absolute;left:0;width:1px;background:var(--rule);
  top:clamp(20px,2.1vw,30px);bottom:clamp(20px,2.1vw,30px);
}
.cs-claim:first-child .cs-claim__mark{top:0}
.cs-claim__mark::after{
  content:"";position:absolute;inset:0;
  background:linear-gradient(180deg,var(--champagne),var(--goud));
  transform:scaleY(0);transform-origin:top;
  transition:transform var(--dur-4) var(--ease);
}
.cs-claim.is-active .cs-claim__mark::after{transform:scaleY(1)}

.cs-claim__key{
  font-family:var(--ff-display);font-weight:600;
  font-size:clamp(21px,1.95vw,28px);line-height:1.16;
  letter-spacing:-.014em;color:var(--ink);
}
.cs-claim__body{
  margin-top:.62em;max-width:42ch;
  font-size:var(--fs-body);line-height:1.62;color:var(--muted);
}
/* The case link is the one thing here a thumb has to hit, so it gets
   a 44px target without moving its underline. */
.cs-foot{margin-top:clamp(14px,1.8vw,24px);padding-left:clamp(20px,1.9vw,30px)}
.cs-foot .link-underline{padding-block:11px}
.cs-foot .link-underline::after{bottom:8px}

/* ------------------------------------------------------------
   8. Responsive
   ------------------------------------------------------------ */
@media (max-width:1023px){
  .cs{--cs-phone-w:23%;--cs-browser-x:27%}
  .cs-stage{grid-template-columns:1fr;gap:clamp(38px,6vw,58px);align-items:stretch}
  .cs-claim__body{max-width:56ch}
}

@media (max-width:720px){
  /* The phone leads and the desktop capture becomes a wide band
     underneath it — stacked on purpose, not a squashed two-column. */
  .cs-stack{
    display:flex;flex-direction:column;align-items:center;
    gap:clamp(26px,6vw,36px);padding-bottom:0;
  }
  /* relative, not static: the speaker notch is positioned against the
     phone, and must not fall through to the stack. */
  .cs-phone{
    position:relative;left:auto;bottom:auto;
    width:min(238px,63vw);padding:7px;border-radius:30px;
  }
  .cs-phone__speaker{top:13px}
  .cs-browser{width:100%;margin-left:0;border-radius:11px}
  .cs-caps{margin-left:0;text-align:center}
  .cs-cap{left:0;right:0;white-space:normal}
  .cs-title,.cs-lead{max-width:none}
  .cs-claim__body{max-width:none}
}

@media (max-width:400px){
  .cs-phone{width:min(226px,68vw)}
  .cs-url{letter-spacing:.03em}
  /* Tighter, but never below 6px: at 1px per graduation a 4px gap
     stops reading as a scale and starts reading as a dotted line. */
  .cs-ticks{gap:6px}
}

/* ------------------------------------------------------------
   9. Reduced motion — nothing fades, nothing drifts
   The rig holds the page it starts on (the homepage, marked
   is-current in the markup); js/pcg-showcase.js does not sequence
   under this preference, so nothing changes under the visitor.
   ------------------------------------------------------------ */
@media (prefers-reduced-motion:reduce){
  .cs-shot{transform:none !important;transition:none !important}
  .cs-cap{transform:none !important;transition:none !important}
  /* Only the TRANSITION goes. The transform stays, and it has to: it
     is what makes an inactive graduation 5px and the needle 9px, which
     is a static fact about which shot is on screen rather than a
     movement. The old rule zeroed the transform too, which was safe
     when the transform was a decorative scaleY(1.7) on the current
     dash and would now flatten every mark to the same height and
     delete the only marker in the group. */
  .cs-ticks i{transition:none !important}
  .cs-phone{transform:none !important}
  .cs-stack{transition:none}
  .cs-stack:hover{transform:none}
  .cs-browser{transition:none}
  .cs-claim__mark::after{transform:scaleY(1);transition:none}
}
