/* ====================================================================
 * CCTL, the upright timeline
 *
 * A reusable block: one line running down the middle, numbered tiles
 * sitting on it, and the text of a step to the left and right in turn.
 * The line fills orange as the reader scrolls, and every tile takes the
 * colour of the page at the moment the filling head arrives at it.
 *
 * MARKUP CONTRACT
 *
 *   <div class="cctl">                  the block, holds nothing but steps
 *     <div class="cctl-item">           one step, any number of them
 *       <h3 class="cctl-title">…        (any widgets, these two are the
 *       <p  class="cctl-text">…          ones the stylesheet sizes)
 *     </div>
 *     …
 *   </div>
 *
 * assets/ccx-timeline.js appends one more child, <span class="cctl-rail">,
 * which carries the orange filling and its head. It is appended LAST and
 * every rule below counts steps with :nth-of-type, so the injected span
 * cannot shift the left and right order. Both together are the reason a
 * step must be a div and nothing else may stand between the steps.
 *
 * WITHOUT THE SCRIPT the block still stands: the resting line is drawn by
 * the stylesheet, the tiles simply stay in their unlit state, which is the
 * look the page had before this component existed.
 *
 * WHAT THE SCRIPT WRITES, and what it is for
 *
 *   --cctl-p          0 to 1, how far the filling stands
 *   --cctl-head       0 or 1, the bright point at the filling head, hidden
 *                     while the filling is at either end
 *   --cctl-rail-end   distance from the bottom of the block to the centre
 *                     of the last tile, so the line stops in that centre
 *                     instead of running on into the empty space the last
 *                     step leaves below itself
 *   --cctl-n          how many steps the block holds, on the block
 *   --cctl-i          which step this is, on a step
 *
 * The last two are what the orange rim of a resting tile is drawn from, and
 * the stylesheet works them out for itself further down as well, so the rim
 * is there on a page where the script never runs.
 *
 * Colours are the ones the rest of the site uses: the orange runs from
 * #FF8E2B at the top to #CE5904 at the bottom, the resting line is the
 * silver of the body text at low opacity, and a lit tile takes #EE7D1A,
 * the same value the security timeline on the home page lights up in.
 * ==================================================================== */

.cctl {
  --cctl-node: 56px;   /* edge length of a numbered tile */
  --cctl-radius: 14px; /* corner of a tile, and of the rim lying over it */
  --cctl-gap: 34px;    /* air between a tile and the text beside it */
  --cctl-step: 64px;   /* air between two steps */
  --cctl-rail-w: 2px;
  --cctl-text: 430px;  /* how wide a step is allowed to set */

  /* the orange rim that says how far along the block a tile stands */
  --cctl-ring-w: 2px;
  --cctl-arc-first: 0.19;  /* how much of the way round the first tile gets */
  --cctl-n: 4;             /* steps in the block, see "how many" below */

  /* how a tile takes the light. Short and front loaded for the same
   * reason as on the security timeline: most of the colour has to be
   * there while the head of the filling still stands inside the tile,
   * otherwise the tile reads as a second event rather than as the one
   * the light caused. */
  --cctl-fill-t: 0.2s;
  --cctl-snap: cubic-bezier(0.2, 0.85, 0.3, 1);

  --cctl-p: 0;
  --cctl-head: 0;
  --cctl-rail-end: 0px;

  /* A block wider than this pulls the two halves so far apart that the
   * line stops holding them together. Overridable per page by setting
   * --cctl-max on the block. */
  --cctl-max: 1120px;

  position: relative;
  counter-reset: cctl;
  width: 100%;
  max-width: var(--cctl-max);
  margin-left: auto;
  margin-right: auto;
}

/* ------------------------------------------------------------------ line */

/* The resting line. Pure stylesheet, so it is there before the script is
 * and stays there if the script never runs. It begins in the centre of
 * the first tile and ends where --cctl-rail-end puts it. */
.cctl::before {
  content: "";
  position: absolute;
  z-index: 0;
  left: 50%;
  margin-left: calc(var(--cctl-rail-w) / -2);
  top: calc(var(--cctl-node) / 2);
  bottom: var(--cctl-rail-end);
  width: var(--cctl-rail-w);
  border-radius: var(--cctl-rail-w);
  background: linear-gradient(180deg,
    rgba(215, 220, 232, 0.06) 0%,
    rgba(215, 220, 232, 0.18) 22%,
    rgba(215, 220, 232, 0.18) 78%,
    rgba(215, 220, 232, 0.06) 100%);
  pointer-events: none;
}

/* The filling sits in its own element because the bright head has to be
 * allowed outside the filled part, and a clip on the filling would cut a
 * pseudo element of the filling away with it. */
.cctl .cctl-rail {
  position: absolute;
  z-index: 1;
  left: 50%;
  margin-left: calc(var(--cctl-rail-w) / -2);
  top: calc(var(--cctl-node) / 2);
  bottom: var(--cctl-rail-end);
  width: var(--cctl-rail-w);
  pointer-events: none;
}

/* The orange itself is drawn at full length and then cut off at the
 * reading position. Growing the height instead would squeeze the
 * gradient, and the two ends would keep changing colour while the reader
 * scrolls; cutting keeps every point of the line at the colour it will
 * have when the line is full. */
.cctl .cctl-fill {
  position: absolute;
  inset: 0;
  border-radius: var(--cctl-rail-w);
  background: linear-gradient(180deg, #FF8E2B 0%, #CE5904 100%);
  clip-path: inset(0 0 calc(100% - 100% * var(--cctl-p)) 0);
  box-shadow: 0 0 12px rgba(255, 142, 43, 0.28);
}

/* the head of the filling, a small light that runs down the line */
.cctl .cctl-rail::after {
  content: "";
  position: absolute;
  left: 50%;
  top: calc(100% * var(--cctl-p));
  transform: translate(-50%, -50%);
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: #ffd7ab;
  box-shadow: 0 0 14px 4px rgba(255, 142, 43, 0.45);
  opacity: var(--cctl-head);
  transition: opacity 0.25s ease;
}

/* ----------------------------------------------------------------- steps */

.cctl .cctl-item {
  position: relative;
  box-sizing: border-box;
  counter-increment: cctl;
  width: 50%;
  min-height: var(--cctl-node);
  padding-bottom: var(--cctl-step);
}
.cctl .cctl-item:last-of-type {
  padding-bottom: 0;
}

/* Odd steps take the left half and end at the middle, even steps start
 * there. Counted by type, so the injected line element cannot turn the
 * order around.
 *
 * The side comes from align-self and not from an automatic margin: the
 * block is a flex column built by the page builder, which writes its own
 * align-items onto it, and a margin would have to argue with that value
 * on every page the component is used on. align-self is the property that
 * setting is meant to be overruled with. */
.cctl .cctl-item:nth-of-type(odd) {
  align-self: flex-start;
  padding-right: calc(var(--cctl-node) / 2 + var(--cctl-gap));
  text-align: right;
  align-items: flex-end;
}
.cctl .cctl-item:nth-of-type(even) {
  align-self: flex-end;
  padding-left: calc(var(--cctl-node) / 2 + var(--cctl-gap));
  text-align: left;
  align-items: flex-start;
}

/* A step is a flex column, so a width cap plus the alignment above is
 * what keeps a long sentence from running the full half of the page and
 * still holds the block against the line.
 *
 * Five class names, and they are all needed. The page builder caps every
 * widget in a container at the full width through a selector carrying four
 * of its own classes, so a shorter selector here loses and the sentence
 * runs the whole half of the page. Measured before and after: 498px of
 * text against the 430 asked for. */
.cctl .cctl-item.cctl-item > .elementor-widget.elementor-widget {
  width: 100%;
  max-width: var(--cctl-text);
}

/* -------------------------------------------------------- numbered tile */

/* The number is counted, not written into the page: a step added later
 * carries the right figure without anyone editing anything.
 *
 * The colour of a lit tile travels, the light and dark modelling on top
 * of it does not. A browser hands a background image straight over at the
 * start of a transition instead of blending it, so the modelling has to
 * stay put or the whole tile would jump; only the colour underneath
 * moves. Same construction as the security timeline on the home page,
 * which is also where the two match in appearance. */
.cctl .cctl-item::before {
  content: counter(cctl);
  position: absolute;
  /* Every side is written out, and that is not tidiness.
   *
   * The page builder gives .e-con::before and ::after a full set of values
   * for its own background overlay, left:0 and a size among them. A tile
   * that only says "right" is then over constrained, left wins, and all the
   * odd tiles line up along the left edge of the block instead of standing
   * on the line. It looks like the right value was ignored, and it was.
   *
   * The same trap took the idea cards and the pillars of the home page in
   * two earlier rounds, both times through an inherited border. */
  top: 0;
  bottom: auto;
  left: auto;
  right: auto;
  z-index: 2;
  box-sizing: border-box;
  width: var(--cctl-node);
  height: var(--cctl-node);
  border-radius: var(--cctl-radius);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: "Plus Jakarta Sans", sans-serif;
  font-size: 20px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0.01em;
  color: rgba(255, 255, 255, 0.52);
  background-color: #192034;
  background-image: linear-gradient(135deg,
    rgba(255, 255, 255, 0.10) 0%, rgba(0, 0, 0, 0.14) 100%);
  border: 1px solid rgba(255, 255, 255, 0.14);
  box-shadow: 0 0 0 0 rgba(206, 89, 4, 0);
  transition: color var(--cctl-fill-t) var(--cctl-snap),
              background-color var(--cctl-fill-t) var(--cctl-snap),
              border-color var(--cctl-fill-t) var(--cctl-snap);
  pointer-events: none;
  -webkit-user-select: none;
  user-select: none;
}
.cctl .cctl-item:nth-of-type(odd)::before {
  right: calc(var(--cctl-node) / -2);
  left: auto;
}
.cctl .cctl-item:nth-of-type(even)::before {
  left: calc(var(--cctl-node) / -2);
  right: auto;
}

/* ------------------------------------------------------ the loading rim
 *
 * A resting tile carries an orange rim that runs part of the way round it,
 * and how far it runs says which step of how many this one is: a corner on
 * the first tile, half the way round at the middle of the block, the full
 * circuit on the last one. A reader who has not scrolled yet can therefore
 * see the length of the road from the tiles alone, and the rim of a single
 * tile answers how much of the block is still to come.
 *
 * This is the mark the tiles of the services page carry as drawn artwork,
 * brought back as something the stylesheet works out for itself. There the
 * four tiles are four separate drawings, each holding the same orange ring
 * under a different clipping rectangle: a corner, the right half, the right
 * half plus the bottom, and the whole ring. Measured against the perimeter
 * those clips come to 19.1, 50.0, 75.4 and 100 per cent, which is step over
 * count with the first tile drawn shorter than the arithmetic would have
 * it. Both of those are what the two rules below say.
 *
 * HOW IT IS DRAWN. The rim is a border on a box lying exactly over the
 * tile, and a conic gradient used as a mask decides how much of that border
 * is shown. The gradient turns clockwise from twelve o'clock, which is
 * where the artwork starts and the way it grows. Angle and perimeter are
 * not the same measure on a square: they agree exactly at the corners and
 * at the middle of every edge, and between those points they part by at
 * most about one per cent of the way round, two pixels on a tile this size.
 *
 * The rim is the mark that tells a reader at a glance that a grey tile is
 * waiting rather than switched off. Once the tile is lit the whole tile is
 * orange and the rim has nothing left to say, so it goes.
 */
.cctl .cctl-item::after {
  content: "";
  position: absolute;
  /* written out for the same reason as the tile above */
  top: 0;
  bottom: auto;
  left: auto;
  right: auto;
  z-index: 3;
  box-sizing: border-box;
  /* the same box as the tile, so the rim lies on the tile's own edge */
  width: var(--cctl-node);
  height: var(--cctl-node);
  border: var(--cctl-ring-w) solid #FF8E2B;
  border-radius: var(--cctl-radius);
  opacity: 0.85;
  transition: opacity 0.25s ease;
  pointer-events: none;

  --cctl-arc: calc(var(--cctl-i) / var(--cctl-n));
  -webkit-mask-image: conic-gradient(from 0deg at 50% 50%,
    #000 calc(var(--cctl-arc) * 360deg), transparent 0);
  mask-image: conic-gradient(from 0deg at 50% 50%,
    #000 calc(var(--cctl-arc) * 360deg), transparent 0);
}

/* The first tile is held back to a corner. Step over count would give it a
 * quarter of the way round in a block of four and a third in a block of
 * three, and at a third the rim has already turned the second corner and
 * stopped reading as a beginning. 0.19 is the figure the artwork uses. The
 * smaller of the two wins, so a long block, where step over count is
 * already below that, keeps its own smaller figure. */
.cctl .cctl-item:first-of-type::after {
  --cctl-arc: min(calc(1 / var(--cctl-n)), var(--cctl-arc-first));
}

.cctl .cctl-item:nth-of-type(odd)::after {
  right: calc(var(--cctl-node) / -2);
  left: auto;
}
.cctl .cctl-item:nth-of-type(even)::after {
  left: calc(var(--cctl-node) / -2);
  right: auto;
}

/* -------------------------------------------------- which step, how many
 *
 * The rim needs two numbers the stylesheet cannot read off the page by
 * itself: where a step stands and how many steps the block holds.
 *
 * The script writes both, and it is exact for any number of steps. What
 * follows is the answer for a page where the script never runs, and it is
 * written out rather than left to the script because the resting state of
 * this block is meant to stand on the stylesheet alone. Without these rules
 * such a page would give every tile the same rim, which is the very thing
 * the rim replaces.
 *
 * The count is settled by asking the block whether it holds an nth step.
 * All of those rules carry the same weight, so the last one that matches
 * wins and the block ends up with the true number. The steps are counted by
 * type for the same reason the sides are: the script appends a span for the
 * line, and counting by type keeps it out of the reckoning.
 *
 * Twelve is where the written out rules stop. A thirteenth step on a page
 * without script falls back to the corner of the first tile, a rim that is
 * too short rather than a block that is broken. With the script, which is
 * what all but a handful of readers get, there is no limit at all.
 */
.cctl:has(> .cctl-item:nth-of-type(2))  { --cctl-n: 2; }
.cctl:has(> .cctl-item:nth-of-type(3))  { --cctl-n: 3; }
.cctl:has(> .cctl-item:nth-of-type(4))  { --cctl-n: 4; }
.cctl:has(> .cctl-item:nth-of-type(5))  { --cctl-n: 5; }
.cctl:has(> .cctl-item:nth-of-type(6))  { --cctl-n: 6; }
.cctl:has(> .cctl-item:nth-of-type(7))  { --cctl-n: 7; }
.cctl:has(> .cctl-item:nth-of-type(8))  { --cctl-n: 8; }
.cctl:has(> .cctl-item:nth-of-type(9))  { --cctl-n: 9; }
.cctl:has(> .cctl-item:nth-of-type(10)) { --cctl-n: 10; }
.cctl:has(> .cctl-item:nth-of-type(11)) { --cctl-n: 11; }
.cctl:has(> .cctl-item:nth-of-type(12)) { --cctl-n: 12; }

.cctl .cctl-item { --cctl-i: 1; }
.cctl .cctl-item:nth-of-type(2)  { --cctl-i: 2; }
.cctl .cctl-item:nth-of-type(3)  { --cctl-i: 3; }
.cctl .cctl-item:nth-of-type(4)  { --cctl-i: 4; }
.cctl .cctl-item:nth-of-type(5)  { --cctl-i: 5; }
.cctl .cctl-item:nth-of-type(6)  { --cctl-i: 6; }
.cctl .cctl-item:nth-of-type(7)  { --cctl-i: 7; }
.cctl .cctl-item:nth-of-type(8)  { --cctl-i: 8; }
.cctl .cctl-item:nth-of-type(9)  { --cctl-i: 9; }
.cctl .cctl-item:nth-of-type(10) { --cctl-i: 10; }
.cctl .cctl-item:nth-of-type(11) { --cctl-i: 11; }
.cctl .cctl-item:nth-of-type(12) { --cctl-i: 12; }

/* ------------------------------------------------------------------- lit */

.cctl .cctl-item.is-lit::before {
  color: #12182b;
  background-color: #EE7D1A;
  border-color: rgba(255, 178, 105, 0.85);
  box-shadow: 0 6px 18px rgba(206, 89, 4, 0.35);
}
.cctl .cctl-item.is-lit::after {
  opacity: 0;
}

/* The flare runs the first time a tile catches the light and never again,
 * because the script hands the class out once per tile and takes it back
 * after the run. A flare that repeated on every scroll past would turn
 * the block into a string of fairy lights. The last keyframe is the
 * resting glow of a lit tile, so a tile looks the same afterwards whether
 * the flare ran or not. */
.cctl .cctl-item.cctl-flare::before {
  animation: cctl-flare 0.6s ease-out both;
}
@keyframes cctl-flare {
  0% {
    box-shadow: 0 0 0 0 rgba(206, 89, 4, 0),
                0 0 0 0 rgba(255, 176, 92, 0);
  }
  16% {
    box-shadow: 0 6px 22px rgba(206, 89, 4, 0.50),
                0 0 0 4px rgba(255, 176, 92, 0.22);
  }
  100% {
    box-shadow: 0 6px 18px rgba(206, 89, 4, 0.35),
                0 0 0 12px rgba(255, 176, 92, 0);
  }
}

/* --------------------------------------------------------- middle sizes */

@media (max-width: 1180px) and (min-width: 901px) {
  .cctl {
    --cctl-node: 50px;
    --cctl-radius: 13px;
    --cctl-gap: 26px;
    --cctl-step: 54px;
    --cctl-text: 340px;
  }
  /* the rim follows the tile through the variables, so only the figure in
   * the tile needs saying here */
  .cctl .cctl-item::before { font-size: 18px; }
}

/* ------------------------------------------------------- one column mode
 *
 * Two columns need room on both sides of the line. Below 901px the halves
 * get so narrow that a sentence breaks after three or four words, so the
 * block turns upright: the line moves to the left edge, the tiles sit on
 * it and every step reads left to right like normal text. Same shape the
 * security timeline on the home page takes at the same width.
 *
 * Every rule here repeats the selector of the rule it replaces, so the
 * two match in weight and this one wins by standing later in the file.
 * -------------------------------------------------------------------- */
@media (max-width: 900px) {
  .cctl {
    --cctl-node: 46px;
    --cctl-radius: 12px;
    --cctl-gap: 18px;
    --cctl-step: 34px;
    --cctl-text: 100%;
  }

  .cctl::before,
  .cctl .cctl-rail {
    left: calc(var(--cctl-node) / 2);
  }

  .cctl .cctl-item:nth-of-type(odd),
  .cctl .cctl-item:nth-of-type(even) {
    width: 100%;
    align-self: stretch;
    padding-left: calc(var(--cctl-node) + var(--cctl-gap));
    padding-right: 0;
    text-align: left;
    align-items: flex-start;
  }

  .cctl .cctl-item:nth-of-type(odd)::before,
  .cctl .cctl-item:nth-of-type(even)::before {
    left: 0;
    right: auto;
    font-size: 17px;
  }
  /* the rim moves with the tile: both sides now stand at the left edge, so
   * both sides of the rim have to be told, and the size and the corner come
   * from the variables above */
  .cctl .cctl-item:nth-of-type(odd)::after,
  .cctl .cctl-item:nth-of-type(even)::after {
    left: 0;
    right: auto;
    bottom: auto;
  }
}

@media (max-width: 480px) {
  .cctl {
    --cctl-node: 42px;
    --cctl-gap: 14px;
    --cctl-step: 30px;
  }
  .cctl .cctl-item:nth-of-type(odd)::before,
  .cctl .cctl-item:nth-of-type(even)::before { font-size: 16px; }
}

/* ------------------------------------------------------- reduced motion
 *
 * Someone who asked the system for less movement gets the end state at
 * once: the line full, every tile lit, no flare and no head running down
 * the page. The script sets the same values, this block covers the case
 * where the script never gets to run. */
@media (prefers-reduced-motion: reduce) {
  .cctl { --cctl-p: 1; --cctl-head: 0; }
  .cctl .cctl-item::before {
    transition: none;
    color: #12182b;
    background-color: #EE7D1A;
    border-color: rgba(255, 178, 105, 0.85);
    box-shadow: 0 6px 18px rgba(206, 89, 4, 0.35);
  }
  .cctl .cctl-item::after { opacity: 0; }
  .cctl .cctl-item.cctl-flare::before { animation: none; }
}
