/* ==========================================================================
   Self-solving logo puzzle — loader / launcher
   Pairs with js/logo-puzzle.js. Markup is generated; only the host element
   is authored (see loader.html).

   Built to look like the object: a moulded frame, a recessed tray floor, and
   24 loose pieces that hold their seams all the way through the solve, then
   merge into one mark. The 25th cell stays empty and keeps its colour — a
   sliding puzzle needs its hole, so the logo is never quite whole.

   The logo is a flat 5x5 grid, so every piece is one solid colour. That means
   scaling a piece down to open its seams looks identical to cropping it, which
   is why the seams cost nothing but a transform.
   ========================================================================== */

.tbyp {
  --tbyp-frame: #2c2c2c; /* the case */
  --tbyp-frame-w: 3.4%; /* how much of the object is frame */
  /* Lighter than a black piece on purpose: on a near-black floor the black
     pieces lose their edges entirely and stop reading as pieces. */
  --tbyp-tray: #565656; /* the floor, seen between the pieces */
  /* The hole shows the case: you are looking through to the frame. Close to
     #000, so what separates it from a black piece is structure rather than
     tone — a piece has a rim, rounded corners and a drop shadow; the hole has
     none of those and darkens at its edges. */
  --tbyp-hole: var(--tbyp-frame);
  --tbyp-recess: inset 0 0 0 1px rgba(0, 0, 0, 0.45),
    inset 0 3px 7px rgba(0, 0, 0, 0.5);
  --tbyp-seam: 0.94; /* piece scale, and so the width of every seam */
  --tbyp-radius: 6%;
  /* Drop shadow plus a rim, so a black piece still shows its border. */
  --tbyp-shadow: 0 1px 2px rgba(0, 0, 0, 0.5),
    inset 0 0 0 1px rgba(255, 255, 255, 0.13);
  /* The same two shadows turned transparent, rather than `none`: a shadow list
     only interpolates against a list of equal length, so transitioning to
     `none` would snap the borders off instead of fading them. */
  --tbyp-shadow-off: 0 1px 2px rgba(0, 0, 0, 0),
    inset 0 0 0 1px rgba(255, 255, 255, 0);
  --tbyp-step: 350ms; /* per-move slide, set per move by the script */
  --tbyp-merge: 560ms; /* seams closing once the puzzle lands */
  --tbyp-ease: cubic-bezier(0.4, 0, 0.2, 1); /* a pushed piece: quick pickup, firm stop */

  position: relative;
  width: 100%;
  aspect-ratio: 1 / 1;
  border-radius: 2.5%;
  background: var(--tbyp-frame);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.07);
}

/* Inset with percentages so the frame stays proportional at any size —
   border-width cannot take a percentage, inset can. */
.tbyp__board {
  position: absolute;
  inset: var(--tbyp-frame-w);
  overflow: hidden;
  background: var(--tbyp-tray);
  /* Inset shadows paint above the background but under the children, so this
     reads as a recess in the seams and the hole without touching the pieces. */
  box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.45);
}

/* The missing piece. It sits under the pieces and, for the length of a slide,
   spans both the cell being vacated and the cell being filled — so a moving
   piece never uncovers bare tray behind itself. */
.tbyp__socket {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 0;
  width: 20%;
  height: 20%;
  transform-origin: 0 0;
}

/* Separate from the socket because the socket is what gets stretched across
   two cells; this is what gets styled. Bare floor, so it fills its cell. */
.tbyp__socket-face {
  position: absolute;
  inset: 0;
  background-color: var(--tbyp-hole);
  box-shadow: var(--tbyp-recess);
}

/* A piece is exactly one cell, so it can be placed with percentage
   translation and needs no pixel maths — it survives any resize for free. */
.tbyp__tile {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 1;
  width: 20%;
  height: 20%;
  will-change: transform;
  transition: transform var(--tbyp-step) var(--tbyp-ease);
}

.tbyp__face {
  position: absolute;
  inset: 0;
  background-image: var(--tbyp-logo); /* absolute URL, set by the script */
  background-size: 500% 500%;
  background-repeat: no-repeat;
  border-radius: var(--tbyp-radius);
  box-shadow: var(--tbyp-shadow);
  /* Held for the whole solve; released only on is-landed. */
  transform: scale(var(--tbyp-seam));
  transition: transform var(--tbyp-merge) var(--tbyp-ease),
    border-radius var(--tbyp-merge) var(--tbyp-ease),
    box-shadow var(--tbyp-merge) var(--tbyp-ease);
}

/* The close: the tray settles, and the 24 pieces give up their seams, corners
   and shadows to become one mark. The hole is untouched — it keeps its colour
   and its recess, so what is missing stays missing. */
.tbyp.is-landed .tbyp__face {
  transform: scale(1);
  border-radius: 0;
  box-shadow: var(--tbyp-shadow-off);
}

.tbyp.is-landed .tbyp__board {
  animation: tbyp-settle 420ms var(--tbyp-ease);
}

/* Cell edges rarely land on whole pixels, so closed seams can still leave a
   hairline of tray showing. Backing the board with the logo puts every
   hairline on the colour it was always meant to be. Held until the merge has
   finished, or it would show through the seams while they are still open. */
.tbyp.is-solved .tbyp__board {
  background-image: var(--tbyp-logo);
  background-size: 100% 100%;
}

@keyframes tbyp-settle {
  0% {
    transform: scale(1);
  }
  40% {
    transform: scale(1.02);
  }
  100% {
    transform: scale(1);
  }
}

@media (prefers-reduced-motion: reduce) {
  .tbyp__tile,
  .tbyp__face {
    transition: none;
  }
  .tbyp.is-landed .tbyp__board {
    animation: none;
  }
}
