/*
The service list: rows of number, name, description and arrow, separated by hairlines,
with the row lighting up under the cursor.

It is a component and not part of block 03 because the same row appears on the treatment
pages. It owns the row and its two states; it does not own its own position on a page, and
every dimension arrives through a custom property so a block can place it.

Not the accordion. docs/design-system.md lists an unbuilt "Accordion", and this is a
different pattern that happens to look adjacent: nothing here opens or closes, a row is a
link to another page. The accordion is the "Four steps to your baseline" block further down
the same page, and it is still unbuilt.

The states are the design's, taken from the hover frame and not invented: the row fills
with a horizontal ramp of --accent, the description goes, the arrow arrives. Nothing moves.

They are not all of them the component's, though, and the file is ordered by that. The fill
belongs to the row and is stated once for everybody. The swap — description out, arrow in —
belongs to the row layout: it is the description's own column that the arrow takes over, and
below --bp-lg there is no such column, the description is under the name and is the row's
content. So the swap is stated inside the layout it describes rather than for all widths and
taken back in the narrow ones.
*/
.service-list {
  border-top: var(--rule-w) solid var(--rule-color);
}

/*
A row is a link, so it focuses, activates and is announced without a line of script, and the
fill has a keyboard equivalent for free. block, not flex, on the anchor itself:
the inner row is what lays the columns out, so the anchor stays a plain box that the
browser can outline in one piece.
*/
.service-list__row {
  position: relative;
  display: flex;
  align-items: center;
  min-height: var(--services-row-h);
  padding-inline: var(--services-num-x) var(--services-row-pad-r);
  border-bottom: var(--rule-w) solid var(--rule-color);
}

/*
The fill is a layer of its own rather than the row's own background: a gradient is not an
interpolable value, so it cannot be transitioned, while the opacity of the layer carrying
it can. z-index is not needed — the layer comes first in the box's paint order and the row
establishes a stacking context through position: relative, so the text sits above it.
*/
.service-list__row::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--services-row-fill);
  opacity: 0;
  transition: opacity var(--services-state-dur) var(--ease);
  pointer-events: none;
}

/*
The fill is the row saying which row it is, and that question is asked in both layouts and
by both input methods — so it is here, in the shared part, and not with the swap below.

Keyboard and cursor part company at the next rule, not at this one: focus lights the row and
stops there. It does not take the description away, because the swap is a thing the cursor
does while it is passing over a row, and focus is not passing over anything — it is parked,
and the description is the row's content, which the reader has just asked to be taken to.
*/
.service-list__row:focus-visible::before {
  opacity: 1;
}

/*
One markup for two layouts. In the row layout the name and the description are two columns
of the row itself; stacked, they are one column beside the number — so the wrapper that
groups them is dissolved here and only becomes a box below --bp-lg. display: contents is what
lets a single element be a box in one layout and nothing at all in the other; the
alternative is printing the row twice and hiding one of them.
*/
.service-list__body {
  display: contents;
}

/*
The number is not content the client types. It is the row's position in the list, so the
template counts it out: deleting the third service renumbers the rest by itself, which a
field could not guarantee. It is still real text rather than a CSS counter, because a
counter lives in a pseudo-element — it cannot be selected, copied, or read out.
*/
.service-list__num {
  flex: 0 0 auto;
  width: calc(var(--services-name-x) - var(--services-num-x));
  color: var(--ink-muted);
  font-size: var(--fs-services-num);
  font-weight: var(--fw-regular);
  letter-spacing: var(--ls-services-num);
  line-height: var(--lh-services-num);
}

/*
The leading cell, when it holds a picture instead of a position.

Same column as the number and the same job — the name starts where it always did — so it is
stated as a sibling of --services-num rather than as a second layout. The service pages' benefits
list is where it comes from: those rows are not numbered, they carry an icon.

The picture is decorative and the row already announces itself by its name, so it is hidden from
the accessibility tree in the markup and carries no alt.

The cell is as wide as the number's, so the name starts on the same line in both lists and the
column is one decision. --services-icon-x is the glyph's inset *inside* that cell, not from the
edge of the band: the row already pads by --services-num-x, and adding the design's 19 on top of
it put the glyph 14px too far in on the first measurement.

object-fit: contain centres the glyph in its box without caring which of the five it is or how
much of its own file is transparent margin.
*/
.service-list__icon {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: flex-start;
  width: calc(var(--services-name-x) - var(--services-num-x));
  padding-left: var(--services-icon-x, 0px);
}

.service-list__icon img {
  display: block;
  width: var(--services-icon-size);
  height: var(--services-icon-size);
  object-fit: contain;
}

/*
The name column is the one that gives way below 1440: the description keeps its measure,
so the line breaks the design was drawn with survive as long as they can.
*/
.service-list__name {
  flex: 1 1 auto;
  min-width: 0;
  color: var(--ink);
  font-family: var(--font-display);
  font-size: var(--fs-services-name);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-services-name);
  line-height: var(--lh-services-name);
}

.service-list__desc {
  flex: 0 0 var(--services-desc-w);
  color: var(--ink-muted);
  font-size: var(--fs-services-desc);
  font-weight: var(--fw-regular);
  letter-spacing: var(--ls-services-desc);
  line-height: var(--lh-services-desc);
  translate: 0;
  transition:
    opacity var(--services-state-dur) var(--ease),
    translate var(--services-state-dur) var(--ease),
    visibility var(--services-state-dur);
}

/*
The arrow arrives from where the description went, i.e. it starts a nudge behind its
resting place and travels forward into it. The offset is negative for that reason: the
description is leaving by +shift, so entering from −shift puts both of them on the same
rightward path.

The vertical half is not part of the movement — it is the centring, and it has to stay in
the same declaration because translate is one property and a second rule would replace it
rather than add to it.
*/
.service-list__arrow {
  position: absolute;
  top: 50%;
  right: var(--services-arrow-right);
  translate: calc(var(--services-swap-shift) * -1) -50%;
  color: var(--ink);
  opacity: 0;
  transition:
    opacity var(--services-state-dur) var(--ease),
    translate var(--services-state-dur) var(--ease);

  --arrow-size: var(--services-arrow-size);
  --arrow-stroke: var(--services-arrow-stroke);
}

/*
The swap: the description gives way to the arrow. It is here, behind two conditions, rather
than in the shared part above, because it is a property of the row layout and not of the
component — in the stacked layout there is nothing to swap. The description is not beside
the name there but under it, in the row's one column, and it is the row's content rather
than a caption the arrow can take the place of. Written the other way round — stated for
everyone and taken back below the breakpoint — the stacked layout would be describing a
state it does not have, which is how the arrow came to be swapping in on a wide screen with
the rows already stacked.

The two conditions:

  min-width  the row layout, i.e. the far side of --bp-lg. It is 1301 against the 1300 the
             stacked layout is written at, so the two cannot both apply at some fractional
             width; the half-open px between them falls to neither, and all that is missing
             there is the hover state on a viewport 1300.5px wide.
  hover      a device with a cursor that can rest on a row and leave again. Without this a
             touch screen that reports itself wide enough — a tablet in landscape, a
             convertible — takes the tap as a hover, and the description it just replaced
             stays replaced until something else is touched. The same pair of conditions
             the hero uses for its cursor tilt (hero.js).

visibility is in the transition list, and that is the whole of it working rather than a
refinement. It is a discrete property: listed, it holds `visible` for the full interval
and flips at the end, so the text is actually on screen while it fades and is out of the
accessibility tree and out of the cursor's way the moment it is gone. Left out of the
list — as it was — it flips on the first frame instead, the text vanishes at once, and
the fade underneath it plays against nothing. That was the jerk, not the duration.
--bp-lg
*/
@media (min-width: 1301px) and (hover: hover) and (pointer: fine) {
  .service-list:not(.service-list--static) .service-list__row:not(.service-list__row--flat):hover::before {
    opacity: 1;
  }

  .service-list:not(.service-list--static) .service-list__row:not(.service-list__row--flat):hover .service-list__desc {
    opacity: 0;
    visibility: hidden;
    translate: var(--services-swap-shift) 0;
  }

  .service-list:not(.service-list--static) .service-list__row:not(.service-list__row--flat):hover .service-list__arrow {
    opacity: 1;
    translate: 0 -50%;
  }
}

/*
── The static list ───────────────────────────────────────────────────────────────────────

A row that is not a link. On the home page a row opens a service; on a service page the same
row is a benefit and there is nowhere to go — so the markup gives it a <div> instead of an <a>,
and there is no focus, no arrow and nothing under the cursor.

A modifier rather than a second component: everything the design draws — the hairlines, the
columns, the type, the way the row folds below --bp-lg — is the same drawing, and the states
are the only difference. The two hover rules above are excluded by :not() rather than undone
here, so a state that is never wanted is never declared instead of being declared and cancelled.

The design's own node carries an arrow-right on four of these five rows. It is not in the render
on any of them: it is the hover state of the component this row was built from, left visible in
the file. See docs/blocks/treatment-03-benefits.md.
*/
.service-list--static .service-list__row {
  cursor: default;
}

/*
A single row with nowhere to go, in a list where the others are links. The whole-list modifier
above cannot say this: on the home page some services have a page and some do not yet, and the
ones that do not are rendered as a span rather than an anchor — see template-parts/home/services.php.
This is what tells the eye the same thing the markup already tells the keyboard.

It is excluded from the hover rules by name in the same :not() shape used for the static list, so
a state that is never wanted is never declared rather than declared and cancelled.
*/
.service-list__row--flat {
  cursor: default;
}

/*
The stacked layout. A different layout, not a narrower one: name and description stack in
one column beside the number, the row grows with its text, and both the description and the
arrow are on show at all times.

It starts at --bp-lg and not at --bp-sm because that is where the row layout stops working,
and the row layout is what says so rather than the device: the row is number, name and a
description column of a fixed measure, so the name column is what narrowing takes away, and
by 1251 the longest name has broken in two and is running into the description beside it. At
900 it is four lines. The design draws two frames and neither is this width, so the rule is
the one the design's own row implies: below the width its columns need, the columns go.

The sizes stay the desktop's the whole way down to --bp-sm — see tokens.css. Only the
stacking is here.
--bp-lg
*/
@media (max-width: 1300px) {
  .service-list__row {
    align-items: start;
    padding-block: var(--services-row-pad-y);

    /*
    The row ends where the arrow begins, so the right padding is the arrow's own inset from
    the edge rather than a second number kept in step with it. On the desktop that inset is
    what is left over beside the description column; here there is no description column,
    and the arrow is the only thing the row has to keep clear of.
    */
    padding-right: var(--services-arrow-right);
  }

  .service-list__num {
    /*
    The number lines up with the cap height of the name beside it rather than with the top
    of its box: at 12px against 25px the two boxes start in different places, and the eye
    reads the digits against the letters.
    */
    padding-top: var(--services-num-nudge);
  }

  /*
  The name and the description become one column, so the row is a two-column layout: the
  number, then everything else. The wrapper is what the arrow makes room for.
  */
  .service-list__body {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: var(--services-row-gap);
    min-width: 0;
    padding-right: calc(var(--services-arrow-size) + var(--space-sm));
  }

  .service-list__desc {
    flex: 1 1 auto;
  }

  /*
  On the top line of the row, as the design has it — not centred on a box whose height
  depends on how much the client typed.
  */
  .service-list__arrow {
    top: var(--services-row-pad-y);
    translate: none;
    opacity: 1;
  }

  /*
  Nothing is taken back here. There used to be two rules undoing the swap and the hover
  fill, and they were the tell: a state that has to be cancelled in half the layouts was
  never the component's to begin with. It is stated once, above, where it applies.
  */
}

/*
No motion means the two states swap on the frame they change, which is what the design
draws — the crossfade was only ever ours. The global clamp in base.css already shortens
these transitions to nothing; they are named here as well because that clamp is a blanket
rule and this is the one place where the timing was a decision of ours rather than the
design's, so it has to be visibly switched off rather than incidentally caught.
*/
@media (prefers-reduced-motion: reduce) {
  .service-list__row::before,
  .service-list__desc,
  .service-list__arrow {
    transition: none;
  }
}
