/* ═══════════════════════════════════════════════════════════════════════════
   Fun-Tastic 悠樂長 · figures.css
   The two shared data figures. Each is one partial, and the partial links this
   file (and src/js/figures.js) from whichever page includes it.

     1  The 225 dots      {{> figure-225 variant="full"}}   {{> figure-225 variant="compact"}}
     2  The station line  {{> station-line}}                {{> station-line variant="compact"}}
     3  Print

   Everything here sits in the components layer, like every other shared component,
   so page CSS (unlayered) can adjust a figure without !important.

   Both figures lay themselves out from their own width (container queries), not the
   viewport's: put them in a narrow column or across the container and they adapt.
   Without container queries a browser simply keeps the narrow layout.

   The markup is always the finished picture. Hidden-until-animated states exist only
   on a figure that figures.js has armed (.is-armed, or inline values it wrote), which
   it does only with motion allowed and only while the figure is still below the fold.
   ═══════════════════════════════════════════════════════════════════════════ */

@layer components {

/* ───────────────────────────────────────────────────── 1 The 225 dots ──
   One dot, one person. Colour belongs to the group and never changes with the ground:
   mint is always "no finding needing referral", orchid always "advised to see an eye doctor".
   A tone may only lift a colour where its ground needs it.

   Checked with the dataviz validator (OKLab ΔE ×100, Machado 2009):
     on white   mint / orchid          colour-blind ΔE 19.6 · mint is 2.0:1 on white, so the counts,
                                       the legend and the alt text carry the values (they always do)
     on indigo  mint / orchid lifted   colour-blind ΔE 14.7 · 6.2:1 and 3.4:1 against the ground
   The two colours also differ in lightness, so they hold in greyscale; and after the regroup,
   position says the same thing again. Under forced colours the orchid group turns to rings. */

.fig225 {
  --fig-clear: var(--mint);
  --fig-follow: var(--orchid);
  --fig-idle: rgb(18 47 108 / .2);          /* the crowd before any result: an indigo tint */
  --fig-orchid-lift: #C858B7;               /* orchid lifted 15% towards white, for marks on indigo (orchid itself is 2.65:1 there) */
  --fig-lh: calc(var(--lh-text) - .2);      /* small multi-line text, in every script */
  container: fig225 / inline-size;
  color: var(--fg);
}
:is(.section--indigo, .panel--indigo, .tone-indigo) .fig225 {
  --fig-follow: var(--fig-orchid-lift);
  --fig-idle: rgb(255 255 255 / .32);
}
/* a light panel inside an indigo section goes back to the light values */
:is(.panel--white, .panel--mist-indigo, .panel--mist-orchid, .panel--mist-mint, .tone-white, .tone-mist-indigo, .tone-mist-orchid, .tone-mist-mint) .fig225 {
  --fig-follow: var(--orchid);
  --fig-idle: rgb(18 47 108 / .2);
}

.fig225__in {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-l);
}
.fig225__in > * { min-inline-size: 0; max-inline-size: none; }

/* The crowd. Scales with its column; capped so a dot never grows past about 16px. */
.fig225__field { display: grid; gap: .9rem; inline-size: 100%; max-inline-size: 26rem; }
.fig225__dots { display: block; inline-size: 100%; block-size: auto; overflow: visible; }
.fig225__set--clear { fill: var(--fig-clear); }
.fig225__set--follow { fill: var(--fig-follow); }
.fig225__idle { fill: var(--fig-idle); }
.fig225__unit {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: .65rem;
  color: var(--fg-muted);
  font-size: var(--fs-small);
  line-height: var(--fig-lh);
}
.fig225__unit::before {
  content: "";
  inline-size: .7rem;
  block-size: .7rem;
  margin-block-start: .3em;
  margin-block-start: calc((1lh - .7rem) / 2);
  border-radius: 50%;
  background: currentColor;
}

.fig225__total > .stat__label { max-inline-size: 18em; line-height: var(--fig-lh); }

/* The legend is the breakdown: a tinted row per group, starting with one dot of that group. */
.fig225__legend { container: fig225-legend / inline-size; display: grid; gap: .6rem; }
.fig225__key {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: .15rem .9rem;
  padding: .95rem 1.25rem 1.05rem;
  border-radius: var(--r-row);
  background: var(--row);
  max-inline-size: none;
}
.fig225__swatch { inline-size: 1.05rem; block-size: 1.05rem; border-radius: 50%; background: var(--fig-clear); }
.fig225__key--follow > .fig225__swatch { background: var(--fig-follow); }
.fig225__key > .stat__num { font-size: clamp(2.25rem, 1.9rem + 1.5vw, 3.25rem); }
.fig225__key > .stat__label {
  grid-column: 2;
  max-inline-size: none;
  color: var(--fg);
  line-height: var(--fig-lh);
  text-wrap: pretty;
}
@container fig225-legend (min-width: 25rem) {
  .fig225__key { grid-template-columns: auto auto minmax(0, 1fr); column-gap: 1rem; }
  .fig225__key > .stat__num { min-inline-size: 3ch; }
  .fig225__key > .stat__label { grid-column: 3; }
}

/* "Advised" is not "diagnosed": the note belongs to the orchid group, so it starts with its dot. */
.fig225__note {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: .65rem;
  max-inline-size: var(--measure);
  color: var(--fg);
  font-size: var(--fs-small);
  line-height: var(--fig-lh);
}
.fig225__note::before {
  content: "";
  inline-size: .7rem;
  block-size: .7rem;
  margin-block-start: .3em;
  margin-block-start: calc((1lh - .7rem) / 2);
  border-radius: 50%;
  background: var(--fig-follow);
}

/* Full variant: average age, the range, the student volunteers. */
.fig225__more {
  display: grid;
  gap: var(--space-l) var(--space-xl);
  grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
  padding-block-start: var(--space-l);
  border-block-start: 1px solid var(--rule);
}
.fig225__fact > .stat__label { line-height: var(--fig-lh); }
.fig225__range { margin-block-start: .35rem; max-inline-size: 18em; color: var(--fg-muted); font-size: var(--fs-small); line-height: var(--fig-lh); }

/* From 38rem of its own width: the crowd on the start side, the numbers beside it. */
@container fig225 (min-width: 38rem) {
  .fig225__in {
    grid-template-columns: minmax(16rem, 26rem) minmax(16rem, 32rem);   /* the numbers stay next to their dots */
    grid-template-rows: auto auto 1fr;
    grid-template-areas: "field total" "field legend" "field note";
    column-gap: clamp(2rem, 1rem + 3vw, 4.5rem);
    justify-content: start;
    align-items: start;
  }
  .fig225__field { grid-area: field; }
  .fig225__total { grid-area: total; }
  .fig225__legend { grid-area: legend; }
  .fig225__note { grid-area: note; }
  .fig225__more { grid-column: 1 / -1; }
}

/* Pointing at a row of the legend picks out its group (an extra, never the only way to tell). */
@media (hover: hover) and (pointer: fine) {
  .fig225__set { transition: opacity var(--dur-2) ease; }
  .fig225:has(.fig225__key--clear:hover) .fig225__set--follow,
  .fig225:has(.fig225__key--follow:hover) .fig225__set--clear { opacity: .2; }
}

/* Forced colours remove our palette: solid dots against rings keep the two groups apart. */
@media (forced-colors: active) {
  .fig225__set--clear, .fig225__key--clear > .fig225__swatch { fill: CanvasText; background: CanvasText; }
  .fig225__set--follow { fill: none; stroke: CanvasText; stroke-width: 2; }
  .fig225__key--follow > .fig225__swatch { background: none; border: 2px solid CanvasText; }
  .fig225__idle { fill: GrayText; }
}

/* Mirrored in right-to-left pages, so mint is always on the start side (the drawing has no text). */
[dir="rtl"] .fig225__dots { transform: scaleX(-1); }

/* ─────────────────────────────────────────────── 2 The station line ──
   A transit strip map. The line is the heading colour of whatever ground it sits on and the
   stops are punched out in the ground colour, so it needs no rule per tone.
   Each stop carries the piece of line that leads to the next one (.stline__seg), so the map
   needs no measuring: stops may be any height, in any language.
     --p   0 … 1, how much of the line is drawn (figures.js; 1 without it)
     --i   the stop's index, 0 … 8 (inline, from the partial)                                  */

.stline {
  --st-line: var(--heading);
  --st-thick: .375rem;
  --st-mark: 1.375rem;
  --st-ring: .28rem;
  --st-gap: 1.4rem;
  --st-col: 2.75rem;
  container: stline / inline-size;
  color: var(--fg);
}
.stline__stops { --p: 1; --n: 8; display: grid; row-gap: var(--st-gap); }
.stline__stop {
  --f: clamp(0, calc(var(--p) * var(--n) - var(--i)), 1);     /* how much of this stop's piece of line is drawn */
  position: relative;
  display: grid;
  grid-template-columns: var(--st-col) minmax(0, 1fr);
  column-gap: .9rem;
  align-items: start;
  max-inline-size: none;
  font-size: var(--fs-body);
  line-height: var(--lh-ui);
}
.stline__seg {
  position: absolute;
  z-index: 0;
  inset-block-start: .65em;
  inset-block-start: .5lh;                                    /* the middle of the first line: where the stop sits */
  inset-inline-start: calc((var(--st-col) - var(--st-thick)) / 2);
  inline-size: var(--st-thick);
  block-size: calc(100% + var(--st-gap));
  border-radius: var(--r-full);
  background: var(--st-line);
  clip-path: inset(0 0 calc((1 - var(--f)) * 100%) 0 round 99px);
  pointer-events: none;
}

.stline__mark {
  position: relative;
  z-index: 1;
  justify-self: center;
  box-sizing: border-box;
  inline-size: var(--st-mark);
  block-size: var(--st-mark);
  margin-block-start: .05em;
  margin-block-start: calc((1lh - var(--st-mark)) / 2);
  border: var(--st-ring) solid var(--st-line);
  border-radius: var(--r-full);
  background: var(--bg);
}
/* Macau, the biggest station (one day, 16 May 2026): a ring with a dot in its centre. */
.stline__stop--major .stline__mark {
  display: grid;
  place-content: center;
}
.stline__stop--major .stline__mark::before {
  content: "";
  inline-size: .42rem;
  block-size: .42rem;
  border-radius: 50%;
  background: var(--dot-a);
}

.stline__label { display: grid; gap: .1rem; justify-items: start; min-inline-size: 0; }
.stline__name { color: var(--heading); font-weight: var(--fw-strong); text-wrap: balance; }
.stline__date { color: var(--fg-muted); font-size: var(--fs-small); text-wrap: balance; }
.stline__note {
  margin-block-start: .35rem;
  padding: .2rem .75rem;
  border-radius: var(--r-full);
  background: var(--row);
  color: var(--heading);
  font-size: var(--fs-small);
  font-weight: var(--fw-medium);
}
.stline__stop--major { font-size: var(--fs-lead); }
.stline__stop--major .stline__name { font-family: var(--font-display); font-weight: var(--fw-sub); }

/* Compact: for a busy section. Smaller stops and type; on the vertical line, name and date share a row. */
.stline--compact { --st-thick: .3rem; --st-mark: 1.125rem; --st-ring: .23rem; --st-gap: 1rem; --st-col: 2.25rem; }
.stline--compact .stline__stop { font-size: var(--fs-small); }
.stline--compact .stline__stop--major { font-size: var(--fs-body); }
.stline--compact .stline__label { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 .65rem; }
.stline--compact .stline__stop--major .stline__mark { gap: .18rem; }
.stline--compact .stline__stop--major .stline__mark::before,
.stline--compact .stline__stop--major .stline__mark::after { inline-size: .34rem; block-size: .34rem; }

/* From 46rem of its own width: a horizontal strip. Labels alternate above and below the line,
   so each has two stops' worth of room; the first and the last keep to their own column. */
@container stline (min-width: 46rem) {
  .stline__stops { grid-template-columns: repeat(9, minmax(0, 1fr)); row-gap: 0; align-items: stretch; }
  .stline__stop {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: 1fr auto 1fr;                         /* equal room above and below keeps every stop on the line */
    row-gap: .85rem;
    justify-items: center;
    text-align: center;
  }
  /* the piece of line shares the stop's own cell and runs one column on, to the middle of the next stop */
  .stline__seg {
    position: relative;
    inset: auto;
    grid-area: 2 / 1;
    align-self: center;
    justify-self: start;
    inline-size: 100%;
    block-size: var(--st-thick);
    margin-inline-start: 50%;
    clip-path: inset(0 calc((1 - var(--f)) * 100%) 0 0 round 99px);
  }
  [dir="rtl"] .stline__seg { clip-path: inset(0 0 0 calc((1 - var(--f)) * 100%) round 99px); }
  .stline__mark { grid-area: 2 / 1; align-self: center; margin-block-start: 0; }
  .stline__stop--major .stline__mark { grid-auto-flow: column; inline-size: calc(var(--st-mark) * 2); block-size: var(--st-mark); }
  .stline__label, .stline--compact .stline__label {
    grid-row: 3;
    display: grid;
    justify-items: center;
    align-self: start;
    inline-size: max-content;
    max-inline-size: 186%;
  }
  .stline__stop:nth-child(odd) > .stline__label { grid-row: 1; align-self: end; }
  /* The two end stops may not reach past the map: centred on their stop while they fit their own
     column, and growing inwards (over the neighbour, whose label is on the other side) when they do not. */
  .stline__stop:is(:first-child, :last-child) > .stline__label { justify-self: start; min-inline-size: 100%; max-inline-size: 150%; }
  .stline__stop:last-child > .stline__label { justify-self: end; }

  /* Where rows can be shared (subgrid), the strip is only as tall as its labels need on each side. */
  @supports (grid-template-rows: subgrid) {
    .stline__stops { grid-template-rows: auto auto auto; row-gap: .85rem; }
    .stline__stop { grid-row: 1 / -1; grid-template-rows: subgrid; }
  }
}

/* While figures.js draws the line: a stop the line has not reached yet waits out of sight,
   then pops, and its label comes out from the line. */
.stline.is-armed .stline__mark { transition: scale var(--dur-4) var(--ease-pop), opacity var(--dur-2) ease; }
.stline.is-armed .stline__label { transition: opacity var(--dur-4) ease, translate var(--dur-5) var(--ease-out); }
.stline.is-armed .stline__stop:not(.is-on) > .stline__mark { opacity: 0; scale: .4; }
.stline.is-armed .stline__stop:not(.is-on) > .stline__label { opacity: 0; translate: calc(var(--dir) * -.6rem) 0; }
@container stline (min-width: 46rem) {
  .stline.is-armed .stline__stop:not(.is-on) > .stline__label { translate: 0 -.5rem; }
  .stline.is-armed .stline__stop:nth-child(odd):not(.is-on) > .stline__label { translate: 0 .5rem; }
}

}   /* end @layer components */

/* ──────────────────────────────────────────────────────────── 3 Print ──
   Paper gets the finished picture, whatever the screen was doing. */
@media print {
  .fig225__idle { display: none !important; }
  .fig225__set, .fig225__set circle { opacity: 1 !important; transform: none !important; }
  .stline__stops { --p: 1 !important; }
  .stline__mark, .stline__label { opacity: 1 !important; scale: none !important; translate: none !important; }
}
