/* ---- Eight pieces, one picture ------------------------------------------
   The eight products interlock and drop onto a plate that carries the rules
   all of them obey. It is the same argument the page made in three flat
   columns, drawn instead, because "they fit together" is a claim a picture can
   make and a list cannot.

   WHY IT IS A FIXED-SIZE DRAWING. The silhouettes are clip-path: path(), and
   path() coordinates are absolute pixels. The alternative that scales
   (objectBoundingBox units) stretches a round knob into an oval the moment the
   column width moves. So the drawing has one size and every number here is in
   that size's pixels: four columns of 276 makes a 1104 stage, which fits inside
   the 1180 wrap. _lib/jigsaw.php holds the geometry and generates each path.

   Under the width that fits it, and in any browser without clip-path, the
   pieces are eight ordinary cards. Nothing in the argument depends on the
   picture: the words are the argument.
   ---------------------------------------------------------------------- */

.assembly-stage { margin: 0 auto; max-width: 940px; }
.piecegrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-4); }

.piece {
  display: block; padding: var(--s-5); text-decoration: none;
  background: rgba(255, 255, 255, .06);
  border: 1px solid var(--on-dark-line); border-radius: var(--r-sm);
}
.piece:hover { text-decoration: none; background: rgba(255, 255, 255, .1); }
.piece h3 { font-size: var(--t-16); color: var(--on-dark); margin-bottom: var(--s-2); }
.piece h3 em { font-style: normal; color: var(--pa, var(--accent-on-dark)); }
.piece .q { font-size: var(--t-15); line-height: 1.35; font-weight: 650; color: var(--on-dark); margin-bottom: var(--s-2); }
.piece .when {
  display: block; font: 700 var(--t-11)/1 var(--mono); letter-spacing: .09em;
  text-transform: uppercase; color: var(--pa, var(--accent-on-dark)); margin-bottom: var(--s-2);
}
.piece .does { font-size: var(--t-13); line-height: 1.55; color: var(--on-dark-soft); }
.piece .pgo { opacity: 0; transition: opacity .14s, transform .14s; }
.piece:hover .pgo, .piece:focus-visible .pgo { opacity: .8; transform: translateX(3px); }

.baseplate { margin-top: var(--s-5); padding: var(--s-5); border: 1px solid var(--on-dark-line); border-radius: var(--r-sm); }
.baseplate h3 { color: var(--on-dark); font-size: var(--t-15); }
.baseplate .rules { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr)); gap: var(--s-5); margin-top: var(--s-3); }
.baseplate .rule { font-size: var(--t-13); color: var(--on-dark-soft); line-height: 1.45; }
.baseplate .rule b {
  display: block; color: var(--accent-on-dark); font-weight: 650;
  font: 650 var(--t-12)/1 var(--mono); letter-spacing: .05em; text-transform: uppercase; margin-bottom: 4px;
}

/* The drawing, only where there is room to draw it. */
@supports (clip-path: path("M0 0 H1 Z")) {
  @media (min-width: 1180px) and (prefers-reduced-motion: no-preference) {
    /* The pieces fly in from outside the stage, which for the length of the
       animation makes the document wider than the viewport. Clip the thing that
       overflows rather than the whole page. */
    .assembly { overflow-x: clip; }

    .assembly-stage { max-width: none; width: 1104px; }
    .piecegrid {
      grid-template-columns: repeat(4, 276px);
      gap: 0;
      justify-content: center;
      /* Depth, so the end-over-end part of the tumble reads as a piece turning
         in space rather than a rectangle squashing. One perspective on the grid
         and not on each piece: they tumble as though seen from the same place,
         which is what makes them look like eight pieces in one room. */
      perspective: 1600px; perspective-origin: 50% 45%;
    }
    .piece {
      width: 364px; height: 358px; margin: -44px;
      /* Room for the holes, which is the opposite of what it first looks like.
         A knob is drawn outside the tile and eats no text space. A HOLE is cut
         into the piece, and with a radius of 29 whose centre sits 13 past the
         edge it reaches 42 inside it, from a tile edge that is already 44 in
         from the box. So text has to start 86 from the box on the two sides
         that can carry a hole, which is the left and the top, and only needs to
         clear the tile edge on the other two. */
      padding: 92px 52px 52px 92px;
      border: 0; border-radius: 0;
      clip-path: var(--clip);
      /* The piece is the product's own colour, lit from above: a hairline of
         light along the top of the tile, then a wash that fades out by the
         middle and turns fractionally darker where the face falls away at the
         bottom. The top edge is at y=44 because that is where the tile starts
         inside the box, and the box has a fixed size so the number can be
         written down. */
      background:
        linear-gradient(rgba(255, 255, 255, .30), rgba(255, 255, 255, .30))
          0 44px / 100% 2px no-repeat,
        linear-gradient(178deg,
          rgba(255, 255, 255, .15) 0,
          rgba(255, 255, 255, .05) 15%,
          rgba(255, 255, 255, 0) 46%,
          rgba(0, 0, 0, .10) 100%),
        var(--pd);
      color: var(--on-accent);
      /* THE SIDE OF THE BOARD. Chained drop-shadows, and chained is the trick:
         each one shadows the result of the one before it, so three 3px drops
         with no blur stack into a wall rather than three copies of one band.
         Each is mixed darker than the last, so the wall falls away from the
         light the way a cut edge does instead of reading as a flat skirt.
         Drop-shadows and not box-shadows: a box-shadow traces the element's
         rectangle and would draw a frame straight through the knobs, while a
         drop-shadow follows the cut, which is what puts a wall under every knob
         and inside every hole.
         Then two real shadows: a tight dark one for where the piece meets what
         it sits on, and a wide soft one for the light in the room. */
      filter:
        drop-shadow(0 3px 0 color-mix(in srgb, var(--pd) 72%, #04101c))
        drop-shadow(0 3px 0 color-mix(in srgb, var(--pd) 56%, #04101c))
        drop-shadow(0 3px 0 color-mix(in srgb, var(--pd) 42%, #04101c))
        drop-shadow(0 2px 2px rgba(6, 16, 30, .5))
        drop-shadow(0 18px 20px rgba(6, 16, 30, .34));
      /* THE SETTLED STATE IS SPELLED OUT, and it has to be. Leaving the piece at
         transform: none and letting the scattered state override it silently
         kills the tumble: with one end none, the browser interpolates the two as
         matrices, and a matrix has no memory of how many turns went into it, so
         380deg decomposes to 20deg and the piece rocks instead of rotating. Both
         ends need the same list of functions to interpolate one for one. */
      transform: translate(0px, 0px) rotateX(0deg) rotateY(0deg) rotate(0deg) scale(1);
      transition: background .18s;
    }
    /* --on-accent, not white. A settled piece is filled with the product's own
       colour, and in dark mode that colour is a pale pastel: white text on it
       measured 1.47:1 against a required 4.5:1, and thirty-two labels across
       the eight pieces were effectively invisible. --on-accent already flips
       with the theme for exactly this reason, which is how the primary button
       stays readable, and the pieces were the one consumer that never used it.
       Nothing here re-states a colour value: the palette is generated. */
    .piece h3 { color: var(--on-accent); font-size: var(--t-17); margin-bottom: var(--s-2); }
    /* "Cross" reads quieter than the product name it prefixes, and it does
       that on weight. Doing it on opacity put a 17px word at 2.38:1 on the
       darker fills: a held-back colour is a contrast failure wearing a
       design decision. */
    .piece h3 em { color: var(--on-accent); font-weight: 400; }
    .piece .q { color: var(--on-accent); font-size: var(--t-17); line-height: 1.3; margin-bottom: var(--s-2); }
    /* Full strength. This label was held back to 72% white to sit quieter
       than the question above it, which put an 11px line at 2.4:1 on the
       darker fills. De-emphasis has to come from size and letterspacing, both
       of which it already has, rather than from taking the contrast below the
       floor. */
    .piece .when { color: var(--on-accent); }
    .piece .does { display: none; }        /* no room for it between the knobs */

    /* KEYFRAMES, NOT A TRANSITION, because of the last fifth of the flight. A
       transition has one curve, so the best it can do at the end is ease out. A
       piece going into a hole does not ease out: it arrives, sits proud of the
       seat for an instant, then drops the last few pixels and stops dead. Three
       movements, so keyframes.
       backwards rather than both: during its stagger a piece holds the 0% state,
       and once finished it obeys the stylesheet again, which is what lets the
       hover work afterwards. */
    .piecegrid.assembling .piece {
      animation: piece-home 1900ms cubic-bezier(.26, .58, .32, .99) backwards;
      animation-delay: var(--wait, 0ms);
    }
    .assembly-stage.assembling .baseplate {
      animation: plate-home 760ms ease-out backwards;
    }
  }
}

@keyframes piece-home {
  0% {
    transform:
      translate(var(--fx), var(--fy))
      rotateX(var(--frx, 0deg)) rotateY(var(--fry, 0deg)) rotate(var(--fr))
      scale(.72);
    opacity: 0;
  }
  58% { opacity: 1; }
  84% { transform: translate(0px, -3px) rotateX(0deg) rotateY(0deg) rotate(0deg) scale(1.04); opacity: 1; }
  93% { transform: translate(0px, 1px) rotateX(0deg) rotateY(0deg) rotate(0deg) scale(.995); }
  100% { transform: translate(0px, 0px) rotateX(0deg) rotateY(0deg) rotate(0deg) scale(1); }
}
/* The plate lands first, so there is something for the pieces to drop into. */
@keyframes plate-home {
  from { transform: translateY(56px); opacity: 0; }
  to   { transform: translateY(0); opacity: 1; }
}
