/* =====================================================================
   Role spec — sticky intro, and one rail per requirement list.

   Three lists are three separate specifications, so each owns its own
   rail that runs from its first point to its last and stops. One line
   threaded through the whole page would imply the sections are one
   continuous sequence, which they are not.

   NOTHING HERE HIDES ANYTHING BY ITSELF. Every rule that starts an
   element invisible is scoped to .js-rolespec, which include/spec.js adds
   only once it has decided it is going to run. With JavaScript off the
   whole spec is simply present.
   ===================================================================== */

.rolespec{
  --rolespec-in:   cubic-bezier(.2,.8,.25,1);
  --rolespec-fill: cubic-bezier(.33,0,.12,1);
  display: grid;
  grid-template-columns: minmax(0, 360px) minmax(0, 1fr);
  gap: 0 80px;
  align-items: start;
}

/* ---- left: the sticky intro ------------------------------------------ */
.rolespec__intro{ position: sticky; top: 120px; }

.rolespec__title{
  margin: 0 0 18px;
  font-size: clamp(26px, 2.6vw, 34px);
  font-weight: 400;
  letter-spacing: -.024em;
  line-height: 1.2;
  color: var(--text);
}
.rolespec__summary{
  margin: 0 0 30px;
  font-size: 15px;
  line-height: 1.75;
  color: var(--text-2);
  max-width: 42ch;
}
/* Arrives as ink and warms to rust a beat later, so it reads as emphasis
   being applied rather than a colour that was always there. */
.rolespec__summary em{ font-style: normal; color: var(--text); }
.js-rolespec .rolespec.is-in .rolespec__summary em{
  transition: color 620ms var(--rolespec-in) 900ms;
  color: var(--accent);
}

.rolespec__facts{
  margin: 0 0 30px;
  padding: 0;
  border-top: 1px solid var(--line);
}
.rolespec__facts > div{
  display: grid;
  grid-template-columns: 108px minmax(0, 1fr);
  gap: 16px;
  padding: 11px 0;
  border-bottom: 1px solid var(--line);
}
.rolespec__facts dt{
  font-family: var(--f-mono);
  font-size: 10px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--text-3);
  padding-top: 2px;
}
.rolespec__facts dd{
  margin: 0;
  font-size: 14.5px;
  line-height: 1.45;
  color: var(--text);
}

/* ---- right: the requirement groups ----------------------------------- */
.rolespec__body{ min-width: 0; }

.grp{
  position: relative;
  padding: 0 0 52px 0;
}
.grp__title{
  margin: 0 0 22px 34px;
  font-family: var(--f-mono);
  font-size: 10.5px;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--text-3);
}

/* The rail. x is 4px so a 1px line runs through the centre of a 9px node
   sitting at left:0 — one pixel out and the dots read as threaded onto a
   line beside them rather than onto the line itself. */
.grp__track,
.grp__fill{
  position: absolute;
  left: 4px;
  width: 1px;
}
.grp__track{ background: var(--line-2); }
.grp__fill{
  height: 0;
  background: var(--accent);
  /* A TRANSITION, not an animation. Each arriving row simply sets a new
     height, so the line extends from wherever it currently is to
     wherever the reader has reached — one continuous travel however
     fast they scroll. Six keyframe animations would chase each other
     down the page. */
  transition: height 1250ms var(--rolespec-fill);
}
.grp__cap{
  position: absolute;
  left: 0;
  width: 13px;
  height: 1px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 420ms var(--rolespec-in);
}
.js-rolespec .grp.is-in .grp__cap{ transform: scaleX(1); }

/* The browser's default 40px on an <ol> would shove every node 40px
   right of its own rail. */
.grp__list{
  list-style: none;
  margin: 0;
  padding-inline-start: 0;
}
.pt{
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr);
  gap: 0 14px;
  /* No hairline between rows: the rail already separates them, and
     tight rows make the dots read as one strand instead of a stack of
     bands. */
  padding: 9px 0;
}
.pt__i{
  font-family: var(--f-mono);
  font-size: 10.5px;
  font-style: normal;
  letter-spacing: .1em;
  font-variant-numeric: tabular-nums;
  color: var(--text-3);
  padding-top: 4px;
  text-align: right;
}
.pt__t{
  margin: 0;
  font-size: 15px;
  line-height: 1.7;
  color: var(--text-2);
  max-width: 60ch;
}

/* The node sits on the rail, positioned by spec.js from the index's own
   measured centre. .pt must not be positioned, or this would resolve
   against the row instead of the group. */
.pt__n{
  position: absolute;
  left: 0;
  top: var(--node-y, 0);
  width: 7px;
  height: 7px;
  box-sizing: content-box;
  border: 1px solid var(--line-2);
  background: var(--bg);
  transition: background 380ms var(--rolespec-in), border-color 380ms var(--rolespec-in),
              transform 380ms var(--rolespec-in);
}
.js-rolespec .pt.is-in .pt__n{
  background: var(--accent);
  border-color: var(--accent);
  transform: scale(1.3);
}
/* One expanding ring, once. A loop would read as "waiting", and by the
   time it fires the point has already arrived. */
.pt__n::after{
  content: "";
  position: absolute;
  inset: -1px;
  border: 1px solid var(--accent);
  opacity: 0;
}
.js-rolespec .pt.is-in .pt__n::after{
  animation: rolespec-ping 620ms var(--rolespec-in) forwards;
}
@keyframes rolespec-ping{
  from{ opacity: .85; transform: scale(.55); }
  to  { opacity: 0;   transform: scale(1.7); }
}

.js-rolespec .pt__i,
.js-rolespec .pt__t{ opacity: 0; }
.js-rolespec .pt.is-in .pt__i{ animation: rolespec-rise 460ms var(--rolespec-in) 120ms forwards; }
.js-rolespec .pt.is-in .pt__t{ animation: rolespec-rise 460ms var(--rolespec-in) 200ms forwards; }
@keyframes rolespec-rise{
  from{ opacity: 0; transform: translate3d(0, 6px, 0); }
  to  { opacity: 1; transform: none; }
}

/* ---- one item's stack arrives a technology at a time ------------------
   Exactly once per page. Repeated anywhere else it becomes a tic. */
.tk{ display: inline-block; }
.tk__sep{ color: var(--accent); }
.js-rolespec .tk{ opacity: 0; }
.js-rolespec .pt.is-in .tk{
  animation: rolespec-rise 420ms var(--rolespec-in) forwards;
  animation-delay: calc(500ms + var(--s) * 150ms);
}

@media (max-width: 880px){
  .rolespec{ grid-template-columns: 1fr; gap: 44px; }
  .rolespec__intro{ position: static; }
}

/* Belt and braces: spec.js declines to add .js-rolespec under reduced
   motion, but it still runs the measurement pass and then sets the
   final state, so the rails are drawn and every point is present. */
@media (prefers-reduced-motion: reduce){
  .js-rolespec .pt__i,
  .js-rolespec .pt__t,
  .js-rolespec .tk{ opacity: 1; animation: none; transform: none; }
  .pt__n{ background: var(--accent); border-color: var(--accent); }
  .pt__n::after{ display: none; }
  .grp__fill, .grp__cap, .pt__n{ transition: none; }
  .grp__cap{ transform: scaleX(1); }
}
