/* The working state: what the app shows while a schedule is being built.
 *
 * A solve takes a few seconds and until now the app spent them with a disabled
 * button. This is what happens instead.
 *
 * The idea is the product's own: a row of call blocks that will not settle.
 * Each bar is one person, in their own colour, and the bars keep repacking --
 * growing, shrinking, pushing each other along the week -- because that is
 * literally what the solver is doing while you watch. It is a loop, not a
 * progress bar: the search does not know how far through it is, and a bar that
 * pretends otherwise is lying.
 *
 * Motion rules: only `flex-grow`, `transform` and `opacity` animate, all of it
 * declarative, no timers. Nothing here reflows the page behind it.
 */

.working {
  position: absolute;
  inset: 0;
  /* Above the field canvas, which sits at 45 while a build runs. The other way
     round and a few hundred balls drift across the words. */
  z-index: 50;
  display: grid;
  place-items: center;
  /* Barely a scrim. The thing being rebuilt is not hidden behind this: by the
     time the overlay is up the year has already come apart into the field, and
     the field is what there is to watch. All this does is sit the card's
     corner of the screen back a little so the words read. */
  background:
    radial-gradient(46% 34% at 50% 40%, rgba(8, 11, 15, .55) 0%, rgba(8, 11, 15, .22) 62%,
                    rgba(8, 11, 15, 0) 100%);
  animation: work-in 220ms ease both;
  /* The overlay is as tall as the year behind it, which on a twelve-month
     schedule is several screens. Aligning to the start and letting the card
     stick keeps it in front of whatever part of the year is being looked at
     rather than parked halfway down a page nobody has scrolled to. */
  align-items: start;
}
.working[hidden] { display: none; }
@keyframes work-in { from { opacity: 0; } to { opacity: 1; } }

.working-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  padding: 26px 30px 24px;
  max-width: min(78vw, 520px);
  width: 100%;
  position: sticky;
  top: 32vh;
  /* A plate, so the words are read against something rather than against a
     calendar. Barely there -- the year underneath should still be visible,
     because what is being rebuilt is the point. */
  border-radius: 14px;
  border: 1px solid rgba(174, 190, 204, .10);
  background: rgba(11, 15, 20, .72);
  /* Softens the balls passing behind the words instead of hiding them. They
     are the whole point of the screen; the card only has to win where the
     sentence is. */
  backdrop-filter: blur(7px);
  box-shadow: 0 18px 60px -24px rgba(0, 0, 0, .9);
}

/* ------------------------------------------------------------ the circles */

/* One circle per person, swelling and shrinking on its own clock.
 *
 * These were bars that repacked along a week, and bars were the wrong shape
 * for this product: everything else here -- the hero, the app's background,
 * a year coming apart and going back together -- is made of circles, and a
 * bar chart in the middle of it read as a borrowed loading widget.
 *
 * It is a loop, not a progress bar. The search does not know how far through
 * it is, and a bar that pretends otherwise is lying.
 */
.track {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(7px, 2.4vw, 13px);
  width: 100%;
  height: 34px;
}

.track i {
  display: block;
  width: 15px;
  height: 15px;
  flex: none;
  border-radius: 50%;
  background: var(--c, #6d7f93);
  transform-origin: center;
  /* Each circle runs the same swell on its own clock, and the durations are
     deliberately not divisible into one another -- 2.6, 2.89, 3.18 -- so the
     row never lands in the same arrangement twice and the loop has no visible
     seam. */
  animation: swell var(--dur, 3.1s) cubic-bezier(.4, 0, .3, 1) var(--lag, 0s) infinite;
}

@keyframes swell {
  0%, 100% { transform: scale(.42); opacity: .38; }
  22%      { transform: scale(1);   opacity: 1; }
  46%      { transform: scale(.55); opacity: .5; }
  68%      { transform: scale(.86); opacity: .88; }
}

/* --------------------------------------------------------------- the words */

.working-line {
  margin: 0;
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -.015em;
  color: var(--paper, #eaf1f7);
}
.working-line b {
  font-weight: 600;
  /* The elapsed count sits in the sentence, so it must not shove the words
     sideways as it ticks. */
  font-variant-numeric: tabular-nums;
}

.working-sub {
  margin: 0;
  font-size: 13px;
  color: var(--mute, #8fa0b1);
  text-align: center;
  min-height: 1.2em;
}

/* The three dots, on their own clock rather than one animation per dot. */
.dots i {
  display: inline-block;
  width: 3px; height: 3px;
  margin-left: 3px;
  border-radius: 50%;
  background: currentColor;
  opacity: .25;
  animation: dot 1.25s ease-in-out infinite;
}
.dots i:nth-child(2) { animation-delay: .16s; }
.dots i:nth-child(3) { animation-delay: .32s; }
@keyframes dot {
  0%, 70%, 100% { opacity: .25; transform: none; }
  35%           { opacity: 1; transform: translateY(-2px); }
}

/* ------------------------------------------------------------ the finish */

/* The circles stop where they are, come to one size and turn green in a run
   left to right when the answer lands. The search has been working invisibly
   for several seconds; without this the overlay simply vanishes and nothing
   says it succeeded. */
.working.done .track i {
  animation: none;
  transform: scale(1);
  opacity: 1;
  background: var(--green, #35d47c);
  transition: background 180ms ease, transform 220ms cubic-bezier(.3, 1.4, .5, 1),
              opacity 180ms ease;
  transition-delay: calc(var(--n, 0) * 26ms);
}
/* The green holds long enough to be read as an answer rather than a flicker,
   and only then does the overlay go. */
.working.done { animation: work-out 380ms ease 620ms both; }
@keyframes work-out {
  from { opacity: 1; }
  to   { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .working, .working.done { animation: none; }
  .track i, .dots i { animation: none; }
  .track i { transform: none; opacity: 1; }
  .working.done .track i { transition: background 180ms ease; }
}

@media (max-width: 640px) {
  .track { height: 26px; }
  .track i { width: 12px; height: 12px; }
  .working-line { font-size: 15.5px; }
}
