/* ==========================================================================
   Work samples — inline links inside the What disclosures that open a
   full-screen modal over the page.

   Two parts:
     1. .sample-link  — the hyperlinked text inside a disclosure bullet.
     2. .ws-*         — the modal: scrim, chrome, and a media card that is
                        sized by JS to the asset's own aspect ratio.

   Layout model, because it is not obvious from the rules alone:
   the modal is a column — bar / stage / foot — and the CARD inside the
   stage is the only thing that carries the asset's shape. worksamples.js
   measures the space left after the bar and foot and gives the card the
   largest box of that aspect ratio which fits, then sets the dialog to
   the same width so the bar and foot line up with the media edges
   exactly. Nothing here guesses a size; the rules below only describe
   what the boxes look like once JS has sized them.

   The chrome sits ON the scrim, not on the card, so it is white type on
   a dimmed page while the card stays bright. That is what keeps the
   modal simple: one lit rectangle, everything else out of the way.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. The trigger
   --------------------------------------------------------------------------
   Same rule the rest of the page follows: colour is the entire
   affordance, no underline, no size or weight change. It is a <button>
   rather than an <a> because it opens something in place rather than
   going anywhere — the same reasoning as the copy-to-clipboard lines —
   so it needs the same full button reset to sit invisibly in its line.
   `font: inherit` is what lets it inherit the 15.5px of the bullet it
   lives in without a second type rule to keep in sync. */
.sample-link {
  font: inherit;
  color: var(--link);
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  cursor: pointer;
  text-align: left;
  /* An inline run of text that can wrap mid-phrase like any other. */
  display: inline;
  transition: color 0.2s var(--ease);
  -webkit-tap-highlight-color: transparent;
}

/* Sticky :hover on a touch screen latches onto the last-tapped element,
   so — as everywhere else on this page — the hover colour is gated on a
   real pointer. Focus-visible is keyboard state and stays unconditional. */
@media (hover: hover) and (pointer: fine) {
  .sample-link:hover {
    color: var(--link-hover);
  }
}

.sample-link:focus-visible {
  outline: 2px solid var(--link);
  outline-offset: 2px;
  border-radius: 3px;
}

/* --------------------------------------------------------------------------
   2. Overlay + scrim
   -------------------------------------------------------------------------- */

.ws-overlay {
  --ws-gutter: 28px;
  --ws-gap: 10px;
  position: fixed;
  inset: 0;
  z-index: 90;
  display: none;
  align-items: center;
  justify-content: center;
  padding: var(--ws-gutter);
}

.ws-overlay.is-open {
  display: flex;
}

.ws-scrim {
  position: absolute;
  inset: 0;
  /* "Fade slightly": enough to push the page back and let a near-white
     card read as lit, not so much that the visitor loses their place in
     the section they were reading. The blur is the same family as the
     gate veil, one step stronger. */
  background: rgba(22, 22, 24, 0.38);
  backdrop-filter: blur(7px) saturate(115%);
  -webkit-backdrop-filter: blur(7px) saturate(115%);
  opacity: 0;
  transition: opacity 0.26s var(--ease);
}

/* Where backdrop-filter is unsupported the tint is the only separation
   there is, so make it carry the weight on its own. */
@supports not ((backdrop-filter: blur(7px)) or (-webkit-backdrop-filter: blur(7px))) {
  .ws-scrim {
    background: rgba(22, 22, 24, 0.58);
  }
}

.ws-overlay.is-in .ws-scrim {
  opacity: 1;
}

/* --------------------------------------------------------------------------
   3. Dialog
   --------------------------------------------------------------------------
   Width is set by JS to the card's width, so the chrome and the media
   share an edge. Height is whatever the three rows add up to. */
.ws-dialog {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: var(--ws-gap);
  width: 100%;
  max-width: 100%;
  /* Height is the card's height plus the chrome, NOT the full band. On a
     desktop the two are the same thing, but on a portrait phone a 16:9
     card leaves most of the band empty, and a full-height dialog pins
     the title to the top of the screen and the slide controls to the
     bottom with a void between them and the slide they belong to.
     Letting the dialog shrink keeps title, media and controls together
     as one object wherever the shape lands. */
  height: auto;
  max-height: 100%;
  opacity: 0;
  transform: translateY(10px) scale(0.985);
  transition: opacity 0.28s var(--ease), transform 0.28s var(--ease);
}

.ws-overlay.is-in .ws-dialog {
  opacity: 1;
  transform: none;
}

/* Closing is quicker than opening — an exit that takes as long as the
   entrance reads as lag rather than as motion. */
.ws-overlay.is-out .ws-scrim,
.ws-overlay.is-out .ws-dialog {
  transition-duration: 0.18s;
}

@media (prefers-reduced-motion: reduce) {
  .ws-scrim,
  .ws-dialog {
    transition: opacity 0.15s linear;
    transform: none;
  }

  .ws-overlay.is-in .ws-dialog {
    transform: none;
  }
}

/* --------------------------------------------------------------------------
   4. Bar and foot — white chrome on the scrim
   -------------------------------------------------------------------------- */

.ws-bar,
.ws-foot {
  flex: none;
  display: flex;
  align-items: center;
  color: rgba(255, 255, 255, 0.82);
}

.ws-bar {
  height: 34px;
  gap: 12px;
  justify-content: space-between;
}

.ws-title {
  font-size: 14px;
  line-height: 1.3;
  font-weight: 500;
  letter-spacing: -0.01em;
  color: #fff;
  /* One line, always: the bar's height is a fixed input to the card
     sizing, so it must not be allowed to wrap. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

.ws-kind {
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.55);
  white-space: nowrap;
  flex: none;
}

.ws-titlewrap {
  display: flex;
  align-items: baseline;
  gap: 10px;
  min-width: 0;
}

.ws-baractions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: none;
}

/* ---------- Buttons ---------- */

.ws-btn {
  font: inherit;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.13);
  color: #fff;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background 0.18s var(--ease), transform 0.18s var(--ease),
    opacity 0.18s var(--ease);
}

@media (hover: hover) and (pointer: fine) {
  .ws-btn:hover {
    background: rgba(255, 255, 255, 0.24);
  }
}

.ws-btn:active {
  transform: scale(0.94);
}

.ws-btn:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
}

.ws-btn[disabled] {
  opacity: 0.3;
  cursor: default;
  transform: none;
}

.ws-btn svg {
  display: block;
}

/* A variant of .ws-btn, and it has to sit AFTER it: both selectors are a
   single class, so source order is the whole of the cascade here and the
   round button's fixed 34px width would otherwise win and clip the label
   straight across the close button beside it.

   It appears only once there is a zoom to undo, and says what to do
   rather than reporting a magnification — this is the one route back to
   fit that needs no gesture, so it should read as an instruction. The
   wording is set in JS, because "click" is the wrong verb on a phone. */
.ws-zoomchip {
  width: auto;
  min-width: 34px;
  padding: 0 12px;
  border-radius: 17px;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.01em;
  white-space: nowrap;
}

.ws-zoomchip[hidden] {
  display: none;
}

/* ---------- Foot ---------- */

.ws-foot {
  height: 40px;
  gap: 14px;
  justify-content: center;
}

.ws-foot[hidden] {
  display: none;
}

.ws-count {
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
  color: rgba(255, 255, 255, 0.7);
  min-width: 66px;
  text-align: center;
}

/* ---------- Video transport ---------- */

.ws-transport {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
}

.ws-time {
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: rgba(255, 255, 255, 0.7);
  flex: none;
  min-width: 38px;
}

.ws-time.is-end {
  text-align: right;
}

/* The filled portion of the track is painted with a gradient the script
   rewrites as the video plays. Doing it that way rather than with a
   second element is what keeps this working identically in WebKit and
   Gecko, whose range pseudo-elements disagree about everything else. */
.ws-range {
  -webkit-appearance: none;
  appearance: none;
  flex: 1;
  min-width: 0;
  height: 20px;
  background: none;
  cursor: pointer;
  margin: 0;
}

.ws-range::-webkit-slider-runnable-track {
  height: 4px;
  border-radius: 2px;
  background: linear-gradient(
    to right,
    #fff 0%,
    #fff var(--ws-progress, 0%),
    rgba(255, 255, 255, 0.28) var(--ws-progress, 0%),
    rgba(255, 255, 255, 0.28) 100%
  );
}

.ws-range::-moz-range-track {
  height: 4px;
  border-radius: 2px;
  background: linear-gradient(
    to right,
    #fff 0%,
    #fff var(--ws-progress, 0%),
    rgba(255, 255, 255, 0.28) var(--ws-progress, 0%),
    rgba(255, 255, 255, 0.28) 100%
  );
}

.ws-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #fff;
  border: 0;
  margin-top: -4px;
  transition: transform 0.15s var(--ease);
}

.ws-range::-moz-range-thumb {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #fff;
  border: 0;
}

.ws-range:active::-webkit-slider-thumb {
  transform: scale(1.25);
}

.ws-range:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 4px;
  border-radius: 4px;
}

/* --------------------------------------------------------------------------
   5. Stage and card
   -------------------------------------------------------------------------- */

.ws-stage {
  /* The card's height is written by JS, so the stage simply takes it. */
  flex: 0 0 auto;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* The one lit rectangle. Its width and height are written by JS; the
   aspect-ratio here is a fallback so the box is never shapeless in the
   frame before the first measure. */
.ws-card {
  position: relative;
  aspect-ratio: var(--ws-ar, 16 / 9);
  max-width: 100%;
  max-height: 100%;
  overflow: hidden;
  border-radius: 14px;
  background: var(--bg-elevated);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.14), 0 24px 60px rgba(0, 0, 0, 0.28);
}

/* ---------- Document: scrolls vertically ---------- */

.ws-scroll {
  width: 100%;
  height: 100%;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  /* Keeps a fling inside the document from chaining to the page behind
     it once the end is reached. */
  overscroll-behavior: contain;
  /* Lets the browser pan in both axes with a finger while reserving the
     two-finger gesture for the pinch handler — without this, iOS zooms
     the whole modal instead of the document inside it. */
  touch-action: pan-x pan-y;
}

/* Zoom on a document is width, not transform: the image is told to be N
   times the container's width and the container scrolls in the second
   axis to match. The page stays sharp at every step, because the browser
   is resampling the source rather than magnifying a rendered layer — the
   whole reason a 1-pager gets this treatment and a slide does not. */
.ws-doc {
  display: block;
  width: calc(var(--ws-zoom, 1) * 100%);
  max-width: none;
  height: auto;
}

.ws-scroll.is-zoomed {
  overflow-x: auto;
}

@media (hover: hover) and (pointer: fine) {
  .ws-scroll {
    cursor: zoom-in;
  }

  .ws-scroll.is-zoomed {
    cursor: grab;
  }

  .ws-scroll.is-panning {
    cursor: grabbing;
  }
}

/* ---------- Page: a real document, in a frame of its own ----------
   The three messaging and competitive documents were written as web
   pages, so they are shown as web pages rather than as tall JPGs of web
   pages. The frame gives them a reading column and the full height of
   the band; everything about how they look is their own stylesheet's
   business, which is the reason for the iframe — see buildPage().

   No zoom control here, deliberately: this is live text at its natural
   size in a column that reflows, not a picture of text at a fixed one. */
.ws-page {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
  background: var(--bg-elevated);
}

/* ---------- Deck: advances horizontally ---------- */

.ws-track {
  display: flex;
  width: 100%;
  height: 100%;
  transform: translate3d(calc(var(--ws-i, 0) * -100%), 0, 0);
  transition: transform 0.42s var(--ease);
}

/* While a finger is down the track follows it 1:1 — easing a value that
   is rewritten every frame is what makes a drag feel like it is lagging
   behind the finger. */
.ws-track.is-dragging {
  transition: none;
}

@media (prefers-reduced-motion: reduce) {
  .ws-track {
    transition: none;
  }
}

.ws-slide {
  flex: 0 0 100%;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  /* Every gesture over a slide is ours — swipe, pinch, and pan — so the
     browser is told to keep its hands off all of them. */
  touch-action: none;
}

/* The zoom layer. It sits between the slide (the fixed frame that clips)
   and the image, because both of its neighbours are already spoken for:
   the track above carries the sideways move between slides, and putting
   a second transform on the image itself would mean the two fighting
   over one property. */
.ws-zoomer {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  transform: translate(var(--ws-px, 0px), var(--ws-py, 0px)) scale(var(--ws-z, 1));
  transition: transform 0.26s var(--ease);
  will-change: transform;
}

/* While a pinch or a drag is running the transform is rewritten every
   frame, and easing a value that changes every frame is what makes a
   gesture feel like it is lagging behind the finger. */
.ws-card.is-live .ws-zoomer {
  transition: none;
}

@media (prefers-reduced-motion: reduce) {
  .ws-zoomer {
    transition: none;
  }
}

@media (hover: hover) and (pointer: fine) {
  .ws-slide {
    cursor: zoom-in;
  }

  .ws-card.is-zoomed .ws-slide {
    cursor: grab;
  }

  .ws-card.is-zoomed .ws-track.is-dragging .ws-slide {
    cursor: grabbing;
  }
}

.ws-slide img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  /* A slide is a picture of a document; dragging a copy of it out of the
     modal is not what the arrow keys are for. */
  user-select: none;
  -webkit-user-select: none;
  -webkit-user-drag: none;
}

/* ---------- Video ---------- */

.ws-video {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  background: var(--bg-elevated);
  cursor: pointer;
}

/* A play affordance over the poster, and the same mark again whenever
   the video is paused mid-way. It fades rather than pops so that a
   tap-to-pause does not flash. */
.ws-playmark {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.22s var(--ease);
}

.ws-card.is-paused .ws-playmark {
  opacity: 1;
}

.ws-playmark span {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 64px;
  height: 64px;
  border-radius: 50%;
  background: rgba(22, 22, 24, 0.55);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  color: #fff;
  transform: scale(1);
  transition: transform 0.22s var(--ease);
}

.ws-card.is-paused .ws-playmark span {
  transform: scale(1);
}

.ws-card:not(.is-paused) .ws-playmark span {
  transform: scale(0.86);
}

/* --------------------------------------------------------------------------
   6. Narrow screens
   --------------------------------------------------------------------------
   The gutter shrinks rather than the chrome: on a phone every pixel the
   frame gives back goes straight into the media, which is the whole
   point of the thing. */
@media (max-width: 640px) {
  .ws-overlay {
    --ws-gutter: 12px;
    --ws-gap: 8px;
  }

  .ws-title {
    font-size: 13px;
  }

  .ws-kind {
    display: none;
  }

  .ws-foot {
    height: 44px;
  }

  .ws-card {
    border-radius: 12px;
  }
}

/* --------------------------------------------------------------------------
   6b. Zoomed on a phone: the frame gives way
   --------------------------------------------------------------------------
   Fitted to its own shape, a 16:9 slide on a portrait phone is about
   390 x 219 with scrim above and below it — so zooming in is reading
   detail through a letterbox. Once there is any zoom at all on a touch
   or narrow viewport, worksamples.js puts `is-fullframe` on the overlay
   and sizes the card to the whole band instead of to the asset's ratio;
   these rules are the other half of that. The gutter goes, and with the
   card meeting the edges of the screen the rounded corners go with it —
   a radius against nothing reads as a mistake rather than as a card.

   The bar and the foot stay. They are the way back: the unzoom pill
   lives in one and the slide counter in the other, and a full-bleed
   frame with no way out of it is a trap rather than a feature. */
@media (max-width: 760px), (pointer: coarse) {
  .ws-overlay.is-fullframe {
    --ws-gutter: 0px;
    --ws-gap: 6px;
  }

  .ws-overlay.is-fullframe .ws-bar {
    padding: 0 10px;
  }

  .ws-overlay.is-fullframe .ws-card {
    border-radius: 0;
    /* Flush to the screen edges, so the lift a card needs to read as a
       card is no longer doing anything except darkening the border. */
    box-shadow: none;
    /* And no ground of its own. Fitted, the card IS the slide and a lit
       rectangle is exactly right. Full-bleed it is a 390-wide portrait
       box holding a 16:9 slide, so a white card would put two bands of
       empty paper above and below the thing being looked at. Transparent,
       the scrim shows through and the only lit rectangle on the screen is
       still the slide itself — the same rule, held at both sizes. */
    background: transparent;
  }
}

/* A landscape phone has almost no vertical room; drop the bar's height
   and let the card have it. */
@media (max-height: 460px) and (orientation: landscape) {
  .ws-overlay {
    --ws-gutter: 10px;
    --ws-gap: 6px;
  }

  .ws-bar {
    height: 28px;
  }

  .ws-foot {
    height: 34px;
  }
}

/* --------------------------------------------------------------------------
   7. Page behind the modal
   --------------------------------------------------------------------------
   position: fixed rather than overflow: hidden, for the same reason the
   gate does it that way: overflow alone does not stop iOS Safari from
   scrolling the document underneath. The scroll position is saved and
   restored by the script. */
html.ws-open,
html.ws-open body {
  overflow: hidden;
}

html.ws-open body {
  position: fixed;
  width: 100%;
}
