/* The simulator, loaded by the pages that require includes/sim.php and by
 * nothing else. Every colour resolves through a token from tokens.css.
 *
 * Radios pick the clip. With no script, :has() switches the posters, one
 * selector per view — the closed set the builds produce, tools/ for the two
 * standalone pages and ideas/build.py for the types and lens sets. sim.js
 * adds .is-live and marks the showing clip .is-on, and from then on that rule
 * decides. THEY ARE SEPARATE RULES ON PURPOSE: one selector a browser cannot
 * parse drops the whole list, so a :has() beside the .is-live selector would
 * take it down too. */
.sim {
  max-width: 58rem;
  margin-top: var(--space-xl);
}

.sim__modes {
  min-width: 0;
  margin: 0 0 var(--space-md);
  padding: 0;
  border: 0;
}

.sim__modes legend {
  padding: 0;
  margin-bottom: var(--space-xs);
}

.sim__opts {
  display: grid;
}

.sim__mode {
  position: relative;
  display: flex;
}

.sim__mode + .sim__mode {
  margin-top: calc(-1 * var(--rule-hair));
}

.sim__mode:focus-within {
  z-index: 2;
}

/* The input covers its segment, so a tap anywhere lands on it and the arrow
 * keys move through the group natively. */
.sim__mode input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.sim__mode > span {
  display: flex;
  flex: 1;
  align-items: center;
  min-height: 44px;
  padding: var(--space-sm) var(--space-lg);
  border: var(--rule-hair) solid var(--color-accent);
  color: var(--color-accent);
  font-size: var(--text-sm);
  font-weight: 500;
  white-space: nowrap;
}

.sim__mode:hover > span {
  background: var(--color-paper-2);
}

.sim__mode input:checked + span {
  position: relative;
  z-index: 1;
  background: var(--color-accent);
  color: var(--color-accent-ink);
}

.sim__mode input:focus-visible + span {
  outline: var(--rule-thick) solid var(--color-focus);
  outline-offset: 3px;
}

/* The cue sim.js arms with .is-nudged: an accent wash that comes and goes
 * three times, and a "Click to try" arrow at the option's centre that stays
 * until a choice. A wash rather than a tick, which would read as a second
 * selected option. The hint sits above the option, on the legend's line,
 * clear of its text from 320 up — re-measure if either label changes. sim.js
 * adds .is-flipped where the text would pass the group's right edge and
 * .is-below where a row sits above the option. Its spans are why the
 * option's are `> span`. */
.sim__mode--nudge::before {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--color-accent);
  opacity: 0;
  pointer-events: none;
}

.sim__mode--nudge.is-nudged::before {
  animation: sim-nudge 1.4s var(--ease-in-out) 3;
}

.sim__hint {
  position: absolute;
  bottom: calc(100% + var(--space-3xs));
  left: calc(50% - 12px);
  display: flex;
  align-items: center;
  gap: var(--space-3xs);
  width: max-content;
  color: var(--color-accent);
  font-size: var(--text-sm);
  font-weight: 600;
  line-height: 1.3;
  white-space: nowrap;
  cursor: pointer;
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--dur-short) var(--ease-out), visibility var(--dur-short);
}

.is-nudged .sim__hint {
  opacity: 1;
  visibility: visible;
}

.is-flipped .sim__hint {
  right: calc(50% - 12px);
  left: auto;
  flex-direction: row-reverse;
}

.is-below .sim__hint {
  top: calc(100% + var(--space-3xs));
  bottom: auto;
}

/* max-width: none, or the reset's 100% lets the text squeeze it to nothing
 * inside a narrow option. */
.sim__hint-arrow {
  flex: none;
  width: 24px;
  height: 24px;
  max-width: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Turned with `rotate`, which applies after the bob's transform, so the bob
 * still moves towards the tip. */
.is-below .sim__hint-arrow {
  rotate: 180deg;
}

.is-nudged .sim__hint-arrow {
  animation: sim-bob 1.4s var(--ease-in-out) 3;
}

.sim__hint-tap {
  display: none;
}

@keyframes sim-nudge {
  50% {
    opacity: 0.24;
  }
}

@keyframes sim-bob {
  50% {
    transform: translateY(3px);
  }
}

.sim__stage {
  position: relative;
  max-width: 100%;
  aspect-ratio: 1280 / 830;
  overflow: hidden;
  background: var(--color-ink-deep);
  border: var(--rule-hair) solid var(--color-rule);
  border-radius: var(--radius-sm);
}

.sim__v {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity var(--dur-short) var(--ease-out);
}

.sim:not(.is-live):has(#sim-normal:checked) .sim__v--normal,
.sim:not(.is-live):has(#sim-blurry:checked) .sim__v--blurry,
.sim:not(.is-live):has(#sim-astigmatism:checked) .sim__v--astigmatism,
.sim:not(.is-live):has(#sim-double-vision:checked) .sim__v--double-vision,
.sim:not(.is-live):has(#sim-floaters:checked) .sim__v--floaters,
.sim:not(.is-live):has(#sim-cataract:checked) .sim__v--cataract,
.sim:not(.is-live):has(#sim-glaucoma-early:checked) .sim__v--glaucoma-early,
.sim:not(.is-live):has(#sim-glaucoma-moderate:checked) .sim__v--glaucoma-moderate,
.sim:not(.is-live):has(#sim-glaucoma-advanced:checked) .sim__v--glaucoma-advanced,
.sim:not(.is-live):has(#sim-scene-day:checked):has(#sim-type-normal:checked) .sim__v--types-day-normal,
.sim:not(.is-live):has(#sim-scene-day:checked):has(#sim-type-nuclear:checked) .sim__v--types-day-nuclear,
.sim:not(.is-live):has(#sim-scene-day:checked):has(#sim-type-cortical:checked) .sim__v--types-day-cortical,
.sim:not(.is-live):has(#sim-scene-day:checked):has(#sim-type-psc:checked) .sim__v--types-day-psc,
.sim:not(.is-live):has(#sim-scene-night:checked):has(#sim-type-normal:checked) .sim__v--types-night-normal,
.sim:not(.is-live):has(#sim-scene-night:checked):has(#sim-type-nuclear:checked) .sim__v--types-night-nuclear,
.sim:not(.is-live):has(#sim-scene-night:checked):has(#sim-type-cortical:checked) .sim__v--types-night-cortical,
.sim:not(.is-live):has(#sim-scene-night:checked):has(#sim-type-psc:checked) .sim__v--types-night-psc,
.sim:not(.is-live):has(#sim-scene-closeup:checked):has(#sim-type-normal:checked) .sim__v--types-closeup-normal,
.sim:not(.is-live):has(#sim-scene-closeup:checked):has(#sim-type-nuclear:checked) .sim__v--types-closeup-nuclear,
.sim:not(.is-live):has(#sim-scene-closeup:checked):has(#sim-type-cortical:checked) .sim__v--types-closeup-cortical,
.sim:not(.is-live):has(#sim-scene-closeup:checked):has(#sim-type-psc:checked) .sim__v--types-closeup-psc,
.sim:not(.is-live):has(#sim-scene-clinic:checked):has(#sim-type-normal:checked) .sim__v--types-clinic-normal,
.sim:not(.is-live):has(#sim-scene-clinic:checked):has(#sim-type-nuclear:checked) .sim__v--types-clinic-nuclear,
.sim:not(.is-live):has(#sim-scene-clinic:checked):has(#sim-type-cortical:checked) .sim__v--types-clinic-cortical,
.sim:not(.is-live):has(#sim-scene-clinic:checked):has(#sim-type-psc:checked) .sim__v--types-clinic-psc,
.sim:not(.is-live):has(#sim-time-day:checked):has(#sim-lens-astig:checked) .sim__v--lens-day-astig,
.sim:not(.is-live):has(#sim-time-day:checked):has(#sim-lens-monofocal:checked) .sim__v--lens-day-monofocal,
.sim:not(.is-live):has(#sim-time-day:checked):has(#sim-lens-multifocal:checked) .sim__v--lens-day-multifocal,
.sim:not(.is-live):has(#sim-time-day:checked):has(#sim-lens-edof:checked) .sim__v--lens-day-edof,
.sim:not(.is-live):has(#sim-time-night:checked):has(#sim-lens-astig:checked) .sim__v--lens-night-astig,
.sim:not(.is-live):has(#sim-time-night:checked):has(#sim-lens-monofocal:checked) .sim__v--lens-night-monofocal,
.sim:not(.is-live):has(#sim-time-night:checked):has(#sim-lens-multifocal:checked) .sim__v--lens-night-multifocal,
.sim:not(.is-live):has(#sim-time-night:checked):has(#sim-lens-edof:checked) .sim__v--lens-night-edof {
  opacity: 1;
}

.sim.is-live .sim__v.is-on {
  opacity: 1;
}

/* Bottom right sits on the dashcam's dark lower edge, hence the dark-field
 * focus colour. */
.sim__play {
  position: absolute;
  right: var(--space-sm);
  bottom: var(--space-sm);
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: var(--scrim-soft);
  color: var(--color-on-dark);
  cursor: pointer;
}

.sim__play[hidden] {
  display: none;
}

.sim__play:focus-visible {
  outline-color: var(--color-focus-dark);
}

.sim__ico {
  width: 22px;
  height: 22px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.sim__ico--play,
.sim__play.is-paused .sim__ico--pause {
  display: none;
}

.sim__play.is-paused .sim__ico--play {
  display: block;
}

/* ---- .sim--grid — more than three views --------------------------------
 * A grid rather than one row, so no label has to shrink or wrap: one column
 * at 320, where "Advanced glaucoma" is wider than half the column; two from
 * 23rem, the odd last cell spanning both; three from 40rem, so nine views
 * make three even rows. A track never goes under its widest label, which is
 * nowrap and otherwise runs under the next cell. The negative margins overlap
 * neighbouring borders so the cells read as one control. Three classes deep
 * so the base rules' media queries below cannot outrank them. */
.sim--grid .sim__opts .sim__mode {
  margin: 0 calc(-1 * var(--rule-hair)) calc(-1 * var(--rule-hair)) 0;
}

.sim--grid .sim__mode > span {
  padding-inline: var(--space-md);
}

/* ---- .simwarn — the do-not-drive clause in the lede --------------------
 * --color-alert, the site's only warm ink. TWO CLASSES DEEP because
 * `.copy strong` is (0,1,1) and a lone class loses to it outright. */
.copy .simwarn,
.simwarn {
  color: var(--color-alert);
}

/* ---- .simnote — the do-not-drive notice under the stage ----------------
 * Paper-2, a hairline and an accent edge, the arrangement .finding uses, and
 * square like everything outside the three recorded corner exceptions. The
 * glyph takes the accent, not --color-alert: that token is for the one
 * do-not-drive clause in the lede, and a second warm mark in the same column
 * spends the emphasis it carries. It stacks below 24rem, where the solid
 * button would not fit beside the glyph. */
.simnote {
  display: grid;
  gap: var(--space-sm);
  margin-top: var(--space-lg);
  padding: var(--space-md);
  background: var(--color-paper-2);
  border: var(--rule-hair) solid var(--color-rule);
  border-inline-start: var(--rule-thick) solid var(--color-accent);
}

.simnote__icon {
  width: 28px;
  height: 28px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  color: var(--color-accent);
}

.simnote__body {
  display: grid;
  justify-items: start;
  gap: var(--space-md);
}

.simnote .simnote__text {
  max-width: var(--measure);
  font-size: var(--text-base);
  color: var(--color-ink-2);
}

.simnote__text strong {
  font-weight: 600;
  color: var(--color-ink);
}

/* Prose after a simulator with no notice. Its own margin, because the one it
 * would collapse with is gone once .sim--axes turns flex. */
.sim + .copy {
  margin-top: var(--space-lg);
}

/* Room under the groups for a hint turned .is-below: 2px, the 24px hint and
 * clearance, not a margin that grows when the hint appears. */
.sim--cued + .copy {
  margin-top: var(--space-xl);
}

@media (min-width: 23rem) and (max-width: 39.99rem) {
  .sim--grid .sim__opts {
    grid-template-columns: repeat(2, minmax(max-content, 1fr));
  }

  .sim--grid .sim__opts .sim__mode:last-child:nth-child(odd) {
    grid-column: 1 / -1;
  }
}

@media (min-width: 24rem) {
  .simnote {
    grid-template-columns: auto minmax(0, 1fr);
    column-gap: var(--space-md);
    padding: var(--space-md) var(--space-lg);
  }
}

@media (min-width: 40rem) {
  .sim__opts {
    grid-auto-flow: column;
    justify-content: start;
  }

  .sim__mode + .sim__mode {
    margin-top: 0;
    margin-left: calc(-1 * var(--rule-hair));
  }

  .sim--grid .sim__opts {
    grid-auto-flow: row;
    grid-template-columns: repeat(3, minmax(max-content, 1fr));
  }
}

/* ---- .sim--axes — one group per axis, under the stage ------------------
 * A wrapping flex row, not a grid, so no breakpoint is tied to one page's
 * labels: an option is never narrower than its nowrap label, a row that
 * cannot hold the next one breaks before it, and the zero basis keeps the
 * widths near equal. The padding takes back the hairline the negative
 * margins push past the edge. After the media queries above, and a class
 * deeper than the rules in them. */
.sim--axes .sim__stage {
  margin-bottom: var(--space-md);
}

.sim--axes .sim__opts {
  display: flex;
  flex-wrap: wrap;
  padding: 0 var(--rule-hair) var(--rule-hair) 0;
}

.sim--axes .sim__opts .sim__mode {
  flex: 1 1 0;
  min-width: max-content;
  margin: 0 calc(-1 * var(--rule-hair)) calc(-1 * var(--rule-hair)) 0;
}

.sim--axes .sim__mode > span {
  justify-content: center;
  padding-inline: var(--space-sm);
}

/* Above the stage with a mouse on a wide screen, where no hand covers the
 * clip and the groups would otherwise start below the fold. Touch keeps them
 * under it at any width. Flex margins do not collapse, hence the stage's. */
@media (min-width: 64rem) and (hover: hover) {
  .sim--axes {
    display: flex;
    flex-direction: column;
  }

  .sim--axes .sim__modes {
    order: -1;
  }

  .sim--axes .sim__stage {
    margin-bottom: 0;
  }

  .sim--cued + .copy {
    margin-top: var(--space-lg);
  }
}

@media (hover: none) {
  .sim__hint-click {
    display: none;
  }

  .sim__hint-tap {
    display: inline;
  }
}

@media (prefers-reduced-motion: reduce) {
  .sim__v,
  .sim__hint {
    transition: none;
  }

  .sim__mode--nudge.is-nudged::before,
  .is-nudged .sim__hint-arrow {
    animation: none;
  }
}
