/* =========================================================================
   The training map — one module at a time.
   -------------------------------------------------------------------------
   Course picker, module picker, and a canvas holding three to five lessons
   plus a little context. Never more than about a dozen cards, so it never
   needs pan-and-hunt to use.

   The detail panel is anchored to the canvas frame, which is a fixed element.
   An earlier version anchored it to a container that reflowed when the
   Tutorial Assets drawer opened, so the close button slid hundreds of pixels
   sideways out from under the cursor.
   ========================================================================= */

:root {
  --lane-foundations: #1f6b43;
  --lane-data:        #4d7c57;
  --lane-control:     #b8763a;
  --lane-integration: #a8791f;
  --lane-ai:          #7d5288;
  --lane-operations:  #3d6b85;
  --lane-developer:   #4a4f55;
}

/* --- intro ------------------------------------------------------------- */

/* Compact on purpose: the map is the point of this page, and every pixel the
   introduction takes is a pixel the canvas is pushed below the fold. */
.map-intro {
  display: flex; flex-wrap: wrap; gap: .75rem 3rem;
  align-items: baseline; justify-content: space-between;
  padding-block: clamp(1.25rem, 3vw, 2rem) 1.1rem;
}
.map-intro h1 {
  /* No cap. A display line should use the width it has — capping it at a
     reading measure forces breaks with half the row sitting empty. */
  font-size: clamp(1.6rem, 3vw, 2.3rem);
  margin-bottom: .35rem;
}
.map-intro .lede { font-size: 1rem; max-width: 70ch; margin: 0; }

.prog { min-width: 220px; }
.prog__bar {
  display: block; height: 6px; border-radius: 999px;
  background: var(--paper-deep); overflow: hidden; margin-bottom: .5rem;
}
.prog__bar span { display: block; height: 100%; background: var(--green-bright); transition: width .35s; }
.prog__text { font-family: var(--mono); font-size: .72rem; color: var(--ink-faint); }

/* --- course picker ------------------------------------------------------ */

.course-bar {
  display: grid; gap: .5rem;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  padding-bottom: 1.5rem;
  border-bottom: 1px solid var(--hairline);
}

.pick {
  --pick: var(--green);
  display: flex; flex-direction: column; gap: .1rem;
  text-align: left; cursor: pointer;
  background: var(--paper); color: var(--ink);
  border: 1px solid var(--hairline);
  border-top: 3px solid var(--pick);
  border-radius: 8px; padding: .65rem .8rem;
  font: inherit;
  transition: background .15s, border-color .15s, transform .15s;
}
.pick:hover { background: var(--paper-warm); transform: translateY(-1px); }
.pick[aria-selected='true'] { background: var(--paper-warm); border-color: var(--pick); box-shadow: var(--shadow-sm); }
.pick:focus-visible { outline: 2px solid var(--terracotta); outline-offset: 2px; }

.pick__code { font-family: var(--mono); font-size: .62rem; letter-spacing: .1em; color: var(--pick); }
.pick__title { font-size: .88rem; font-weight: 700; line-height: 1.2; }
.pick__count {
  font-family: var(--mono); font-size: .65rem; color: var(--ink-faint);
  margin-top: .15rem;
  display: inline-flex; align-items: center; gap: .3rem;
}

/* --- finished ------------------------------------------------------------
   A module or course with nothing left in it fills in and takes a tick. The
   pickers are the only place a learner sees a whole course at once, so this is
   where progress is worth showing plainly rather than as a fraction.
   ---------------------------------------------------------------------- */

.tick { width: 1em; height: 1em; flex: none; }

.pick[data-done='true'] {
  background: #f1f9f4;
  border-color: #b9d9c6;
}
.pick[data-done='true'] .pick__count { color: var(--green); }
.pick[data-soon='true'] .pick__count { color: var(--ink-faint); font-style: italic; }
.pick[data-done='true'][aria-selected='true'] { border-color: var(--green); }

/* --- body layout -------------------------------------------------------- */

.map-body {
  display: grid; gap: clamp(1.5rem, 3vw, 2.5rem);
  grid-template-columns: minmax(0, 1fr);
  padding-top: 1.5rem;
  align-items: start;
}
/* The canvas gets the full width and the recipes sit under it.
   They used to share a row, which cost the map 250px — and with the map at a
   module a time, that 250px was the difference between the successor modules
   being on screen and being scrolled off. 21 of 26 views overflowed; 3 do now.
   The recipes lose nothing by moving: they are standalone and never gated, so
   they were never something you read *while* looking at a module. */
.rail {
  display: grid; gap: clamp(1.25rem, 3vw, 2rem);
  grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 900px) { .rail { grid-template-columns: 2fr 1fr; align-items: start; } }
.rail__h:first-child { margin-top: 0; }

/* --- module picker ------------------------------------------------------ */

.module-bar { display: flex; flex-wrap: wrap; gap: .4rem; margin-bottom: 1rem; }

.mod {
  display: flex; flex-direction: column; gap: .05rem;
  text-align: left; cursor: pointer;
  background: transparent; color: var(--ink-mid);
  border: 1px solid var(--hairline); border-radius: 8px;
  padding: .5rem .8rem; font: inherit;
  transition: background .15s, color .15s, border-color .15s;
}
.mod:hover { background: var(--paper-warm); }
.mod[aria-selected='true'] { background: var(--ink); color: #fff; border-color: var(--ink); }
.mod:focus-visible { outline: 2px solid var(--terracotta); outline-offset: 2px; }
.mod__n { font-family: var(--mono); font-size: .6rem; letter-spacing: .12em; text-transform: uppercase; opacity: .75; }
.mod__t { font-size: .87rem; font-weight: 700; line-height: 1.2; }
.mod__c {
  font-family: var(--mono); font-size: .62rem; opacity: .7;
  display: inline-flex; align-items: center; gap: .3rem;
}

.mod[data-done='true'] {
  background: #f1f9f4;
  border-color: #b9d9c6;
}
.mod[data-done='true'] .mod__c { color: var(--green); opacity: 1; }

/* Selected wins on color — you are looking at this one — but the tick stays,
   because it is what says the module is finished. */
.mod[data-done='true'][aria-selected='true'] {
  background: var(--green); border-color: var(--green); color: #fff;
}
.mod[data-done='true'][aria-selected='true'] .mod__c { color: #dff0e5; }

.crumb { font-size: .92rem; color: var(--ink-mid); margin: 0 0 .75rem; }
.crumb__meta { font-family: var(--mono); font-size: .7rem; color: var(--ink-faint); margin-left: .75rem; }

/* --- canvas ------------------------------------------------------------- */

.map-shell {
  --dp-w: 360px;
  position: relative;
  /* Height is set from the view's own extent in map.js, so a two-lesson module
     gets a short frame instead of a mostly-empty one. */
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  background:
    radial-gradient(760px 340px at 12% 0%,
      color-mix(in srgb, var(--lane, var(--green)) 10%, transparent), transparent 68%),
    var(--paper-warm);
  overflow: hidden;
  min-height: 260px;
}

.map-scroll { overflow: auto; padding: 1.5rem; scroll-behavior: smooth; }
/* Mouse users drag the canvas to pan it (dragToPan in map.js). Smooth scrolling
   would trail the pointer, and a drag across text would select it. */
.map-scroll { cursor: grab; }
.map-scroll[data-dragging='true'] { cursor: grabbing; scroll-behavior: auto; user-select: none; }
.map-scroll[data-dragging='true'] .node { cursor: grabbing; }

/* Room to scroll the last card out from under the panel.
   The panel overlays the right of the frame rather than taking space from it, so
   without this the canvas runs out of scroll while the final card is still
   covered — scrolled fully right and still half hidden by the thing describing it.
   Padding the scroller (not the canvas) is deliberate: `.map-svg` is positioned
   against `.map-canvas`, and padding that would move the cards and leave every
   line behind. */
.map-shell:has(.detail-panel[data-open='true']) .map-scroll {
  padding-right: calc(1.5rem + var(--dp-w));
}

/* A view wider than the frame scrolls. Fade the edge so it is obvious there is
   more, rather than leaving the successor modules silently off-screen. */
.map-shell[data-overflow='right']::after,
.map-shell[data-overflow='both']::after {
  content: ''; position: absolute; top: 1px; right: 1px; bottom: 1px; width: 56px;
  pointer-events: none; z-index: 5; border-radius: 0 var(--radius) var(--radius) 0;
  background: linear-gradient(90deg, transparent, var(--paper-warm));
}
.map-shell[data-overflow='left']::before,
.map-shell[data-overflow='both']::before {
  content: ''; position: absolute; top: 1px; left: 1px; bottom: 1px; width: 56px;
  pointer-events: none; z-index: 5; border-radius: var(--radius) 0 0 var(--radius);
  background: linear-gradient(270deg, transparent, var(--paper-warm));
}
/* One positioned parent for both layers, inside the scroller. The svg was
   absolutely positioned against .map-shell — which is the frame, not the thing
   that scrolls — so scrolling sideways moved the cards and left every line
   behind. Anything absolute in here must hang off .map-canvas. */
.map-canvas { position: relative; }
.map-svg { position: absolute; top: 0; left: 0; overflow: visible; }
.map-nodes { position: relative; }

/* --- vines -------------------------------------------------------------- */

.vine {
  fill: none; stroke: #cfcabb; stroke-width: 2; stroke-linecap: round;
  transition: stroke .3s, stroke-width .3s, opacity .3s;
}
.vine[data-uncertain='true'] { stroke-dasharray: 6 5; }
.vine[data-live='true'] { stroke: var(--green-bright); stroke-width: 3; }
.vine[data-dim='true'] { opacity: .22; }

/* A join to a neighbouring module is a thread leaving the view, not a step in
   this module's chain. Thinner and paler, so the sequence a learner is actually
   working through stays the strongest line on the canvas. */
.vine[data-module='true'] {
  stroke: #ddd8cc; stroke-width: 1.5; stroke-dasharray: 3 4;
}

/* --- lesson cards ------------------------------------------------------- */

.node {
  position: absolute;
  display: flex; flex-direction: column; gap: .15rem;
  padding: .6rem .7rem;
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-left: 3px solid var(--lane-color, var(--hairline));
  border-radius: 8px;
  box-shadow: var(--shadow-sm);
  font: inherit; color: var(--ink);
  text-align: left; cursor: pointer;
  transition: transform .16s cubic-bezier(.2,.7,.3,1), box-shadow .16s, border-color .16s;
}
.node[data-lane='foundations'] { --lane-color: var(--lane-foundations) }
.node[data-lane='data']        { --lane-color: var(--lane-data) }
.node[data-lane='control']     { --lane-color: var(--lane-control) }
.node[data-lane='integration'] { --lane-color: var(--lane-integration) }
.node[data-lane='ai']          { --lane-color: var(--lane-ai) }
.node[data-lane='operations']  { --lane-color: var(--lane-operations) }
.node[data-lane='developer']   { --lane-color: var(--lane-developer) }

.node:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }
.node:focus-visible { outline: 2px solid var(--terracotta); outline-offset: 2px; z-index: 3; }
/* Selected outlines the card. Like the state rules below, it must leave the left
   edge alone — that stripe is the course, and it does not change because you
   clicked something. */
.node[data-selected='true'] {
  border-top-color: var(--ink);
  border-right-color: var(--ink);
  border-bottom-color: var(--ink);
  box-shadow: var(--shadow-md);
}

.node__code { font-family: var(--mono); font-size: .58rem; letter-spacing: .08em; color: var(--ink-faint); }
.node__title {
  font-size: .84rem; font-weight: 700; line-height: 1.22;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
.node__meta { margin-top: auto; font-family: var(--mono); font-size: .6rem; color: var(--ink-faint); }

/* State is carried by the mark, the border and the aria-label — never by
   color alone. */
.node__stage {
  position: absolute; top: -8px; right: -8px;
  width: 21px; height: 21px; border-radius: 50%;
  display: grid; place-items: center;
  font-size: 11px; line-height: 1;
  background: var(--paper); border: 1px solid var(--hairline); color: var(--ink-faint);
}
/* State colors the outline; the left edge belongs to the course.
   
   These used the `border-color` shorthand, which sets all four sides — so a
   lesson that became ready had its course stripe repainted gold, and two lessons
   in the same module could show different stripes purely because one was ready
   and the other was not. Ticking a prerequisite then changed the *other* card's
   stripe, which is a baffling thing to watch. */
/* Ready is the map's most useful state — it is the answer to "what can I do
   next" — so the card does show it. But at full-strength gold it shouted louder
   than done, and a saturated brown outline beside a calm green one reads as a
   warning about the card rather than an invitation to open it.

   Calibrated against done instead: a faint tint and a softened border, with the
   mark keeping the full color so it still matches the legend. The mark is what
   carries the state — ○ ◇ ✿ are three different glyphs, so none of this is
   color alone. */
.node[data-state='available'] {
  background: #fdfbf4;
  border-top-color: #e6d9b8;
  border-right-color: #e6d9b8;
  border-bottom-color: #e6d9b8;
}
.node[data-state='available'] .node__stage { border-color: var(--gold); color: var(--gold); }

.node[data-state='bloomed'] {
  background: #f4faf5;
  border-top-color: #b9dcc4;
  border-right-color: #b9dcc4;
  border-bottom-color: #b9dcc4;
}
.node[data-state='bloomed'] .node__stage { background: var(--green-bright); border-color: var(--green-bright); color: #fff; }

/* Coming soon: the course is not released, so the card is quieter than the
   others and says so in words in its meta line. Solid border - dashed belongs
   to the neighbouring-module cards and would read as "somewhere else". */
.node[data-state='soon'] {
  background: var(--paper-warm);
  box-shadow: none;
  color: var(--ink-soft);
}
.node[data-state='soon'] .node__stage { border-style: dotted; color: var(--ink-faint); }
.node[data-state='soon'] .node__meta { color: var(--ink-soft); }

@media (prefers-reduced-motion: no-preference) {
  .node[data-state='bloomed'] .node__stage { animation: pop .3s cubic-bezier(.2,.7,.3,1) }
}
@keyframes pop { 0% { transform: scale(.5) } 60% { transform: scale(1.25) } 100% { transform: none } }

/* Cards from a neighbouring module, shown so a two-lesson module still tells
   you where you are. */
.node--context { opacity: .5; background: var(--paper-warm); box-shadow: none; border-style: dashed; }
.node--context:hover { opacity: .9; }

/* --- legend ------------------------------------------------------------- */

.map-legend {
  display: flex; flex-wrap: wrap; gap: .4rem 1.5rem;
  margin: .9rem 0 0; font-size: .78rem; color: var(--ink-faint);
}
.map-legend span { display: inline-flex; align-items: center; gap: .4rem; }
.map-legend i {
  font-style: normal; width: 18px; height: 18px; border-radius: 50%; flex-shrink: 0;
  display: grid; place-items: center; font-size: 10px;
  background: var(--paper); border: 1px solid var(--hairline);
}
.map-legend i[data-s='available'] { border-color: var(--gold); color: var(--gold) }
.map-legend i[data-s='bloomed'] { background: var(--green-bright); border-color: var(--green-bright); color: #fff }
.map-legend i[data-s='soon'] { border-style: dotted; color: var(--ink-faint) }
.map-legend i.ctx { border-style: dashed; opacity: .5 }

/* --- detail panel ------------------------------------------------------- */

.detail-panel {
  position: absolute; inset: 0 0 0 auto; z-index: 10;
  width: min(var(--dp-w), 100%);
  background: var(--paper);
  border-left: 1px solid var(--hairline);
  box-shadow: -12px 0 32px -18px rgba(20,38,28,.4);
  display: flex; flex-direction: column;
  transform: translateX(101%);
  transition: transform .24s cubic-bezier(.2,.7,.3,1);
}
.detail-panel[data-open='true'] { transform: none; }
@media (prefers-reduced-motion: reduce) {
  .detail-panel { transition: none }
  /* Selecting a card now scrolls the canvas, so the scroller's smooth behaviour
     fires on every click rather than never. */
  .map-scroll { scroll-behavior: auto }
}

.dp__head { padding: 1rem 1.1rem .8rem; border-bottom: 1px solid var(--hairline); position: relative; }
.dp__close {
  position: absolute; top: .75rem; right: .8rem;
  width: 1.9rem; height: 1.9rem; cursor: pointer;
  background: var(--paper-warm); border: 1px solid var(--hairline); border-radius: 6px;
  color: var(--ink); font-size: 1.05rem; line-height: 1;
}
.dp__close:hover { background: var(--ink); color: #fff; border-color: var(--ink); }
.dp__close:focus-visible { outline: 2px solid var(--terracotta); outline-offset: 2px; }

.dp__code { font-family: var(--mono); font-size: .64rem; letter-spacing: .12em; color: var(--ink-faint); }
.dp__title { font-size: 1.2rem; line-height: 1.18; margin: .2rem 2.6rem .3rem 0; }
/* The course, and the tick, on one line. The control sits in the header rather
   than the body so it is in the same place whatever the lesson is and however far
   down the card you have scrolled — a learner working through a module reaches
   for it constantly. */
.dp__meta {
  display: flex; align-items: center; gap: .75rem;
  min-height: 1.6rem;
}
.dp__lane {
  font-family: var(--mono); font-size: .62rem; letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-faint);
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.dp__body { overflow-y: auto; padding: 1rem 1.1rem 2rem; flex: 1; }
.dp__ctx {
  background: var(--paper-warm); border: 1px dashed var(--hairline); border-radius: 6px;
  padding: .5rem .65rem; font-size: .8rem; color: var(--ink-soft); margin: 0 0 .9rem;
}
.dp__obj { margin: 0 0 1rem; font-size: .95rem; line-height: 1.55; color: var(--ink-mid); }
.dp__soon {
  display: inline-block; margin: .2rem 0 0; padding: .2rem .6rem;
  border: 1px dotted var(--hairline); border-radius: 999px;
  font-family: var(--mono); font-size: .72rem; color: var(--ink-soft);
}

.dp__h {
  font-family: var(--mono); font-size: .62rem; letter-spacing: .16em;
  text-transform: uppercase; color: var(--ink-faint);
  font-weight: 400; margin: 0 0 .5rem;
  padding-bottom: .35rem; border-bottom: 1px solid var(--hairline);
}

/* Watch / Read / Try — the three things the graph beside it cannot show. */

.dp__len {
  font-family: var(--mono); font-size: .7rem; color: var(--ink-faint);
  margin: -.5rem 0 1.4rem;
}

.dp__sec { margin-bottom: 1.5rem; }
.dp__sec:last-of-type { margin-bottom: 1.75rem; }

.dp__items { list-style: none; margin: 0; padding: 0; display: grid; gap: .35rem; }

.dp__item {
  display: flex; align-items: baseline; gap: .4rem;
  font-size: .85rem; line-height: 1.35; text-decoration: none;
  color: var(--ink); background: var(--paper-warm);
  border: 1px solid var(--hairline); border-radius: 7px;
  padding: .5rem .7rem;
  transition: background .15s, border-color .15s, color .15s;
}
.dp__item:hover { background: var(--ink); color: #fff; border-color: var(--ink); }
.dp__ext { margin-left: auto; opacity: .5; font-size: .78em; flex-shrink: 0; }

/* The two that lead somewhere on this site get the accent. */
.dp__item--go { border-color: #cfe0d5; background: #f2f9f4; font-weight: 700; }
.dp__item--go:hover { background: var(--green); border-color: var(--green); color: #fff; }

.dp__none {
  font-size: .82rem; color: var(--ink-faint); line-height: 1.5; margin: 0;
  border-left: 2px solid var(--hairline); padding-left: .7rem;
}
.dp__none strong { color: var(--ink-mid); }


/* At the top of the card, not the bottom. It is the one control here, and a
   learner ticking their way through a module should not have to scroll past the
   whole lesson to reach it every time. */
.dp__check {
  display: flex; align-items: center; gap: .4rem; flex: none;
  margin-left: auto; padding: .25rem .5rem .25rem .35rem; cursor: pointer;
  background: var(--paper-warm); border: 1px solid var(--hairline); border-radius: 999px;
  font-family: var(--body); font-size: .7rem; font-weight: 700;
  white-space: nowrap; color: var(--ink-soft);
  transition: background .15s, border-color .15s, color .15s;
}
.dp__check:hover { background: var(--paper-deep); color: var(--ink); }
.dp__check[aria-pressed='true'] {
  background: #f1f9f4; border-color: #b9d9c6; color: var(--green);
}
.dp__check:hover { background: var(--paper-deep); }
.dp__check:focus-visible { outline: 2px solid var(--terracotta); outline-offset: 2px; }
.dp__check__box {
  width: .95rem; height: .95rem; flex: none; border-radius: 3px;
  border: 1.5px solid var(--ink-faint); display: grid; place-items: center;
  font-size: .62rem; line-height: 1; color: transparent;
}
.dp__check[aria-pressed='true'] .dp__check__box {
  background: var(--green-bright); border-color: var(--green-bright); color: #fff;
}

.dp__note { font-size: .8rem; color: var(--ink-faint); line-height: 1.5; margin: .6rem 0 0; }

/* --- right rail --------------------------------------------------------- */

.rail__h { font-size: 1.02rem; margin-bottom: .4rem; }
.rail__note { font-size: .8rem; color: var(--ink-faint); line-height: 1.5; margin-bottom: .8rem; }
.rail__empty { font-size: .82rem; color: var(--ink-faint); }
/* Under the canvas now, so the recipes lay out across rather than down. */
.rail__list {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: .5rem;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
}

.rec {
  display: grid; gap: .1rem; cursor: pointer;
  border: 1px solid var(--hairline); border-radius: 8px;
  padding: .55rem .7rem; background: var(--paper);
  transition: background .15s, border-color .15s;
}
.rec:hover { background: var(--paper-warm); border-color: #d4cec0; }
.rec__code { font-family: var(--mono); font-size: .58rem; letter-spacing: .1em; color: var(--terracotta); }
.rec__title { font-size: .82rem; font-weight: 700; line-height: 1.25; }
.rec__teaches { font-size: .72rem; color: var(--ink-faint); }

.rail { border-top: 1px solid var(--hairline); padding-top: 1.5rem; margin-top: .5rem; }

/* A module card is a signpost to somewhere in the rows above. Hovering it says
   where — otherwise a card reading "Module 2" gives no clue which course's
   module 2 it means, and the answer is one glance away. */
.pick[data-preview],
.mod[data-preview] {
  border-color: var(--terracotta);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--terracotta) 22%, transparent);
}
.pick[data-preview][aria-selected='false'],
.mod[data-preview][aria-selected='false'] { background: #fdf2ee; }

/* --- the lesson video ----------------------------------------------------
   A poster that becomes the player when clicked. Nothing is requested from
   YouTube until the learner asks — opening a lesson card is something they do
   constantly while reading the map, and none of those should be a round trip to
   Google.
   ---------------------------------------------------------------------- */

.dp__video { display: grid; gap: .5rem; }

.dp__play {
  width: 100%; aspect-ratio: 16 / 9;
  display: grid; place-items: center; align-content: center; gap: .5rem;
  border: 1px solid var(--hairline); border-radius: 8px;
  background:
    radial-gradient(420px 200px at 50% 40%,
      color-mix(in srgb, var(--lane, var(--green)) 12%, transparent), transparent 70%),
    var(--paper-deep);
  cursor: pointer; font-family: var(--body); color: var(--ink);
  transition: border-color .15s, background .15s;
}
.dp__play:hover { border-color: var(--terracotta); }
.dp__play:focus-visible { outline: 2px solid var(--terracotta); outline-offset: 2px; }

.dp__play__mark {
  width: 2.9rem; height: 2.9rem; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--terracotta); color: #fff;
  font-size: .95rem; padding-left: .18em;
  box-shadow: var(--shadow-sm);
  transition: transform .15s;
}
.dp__play:hover .dp__play__mark { transform: scale(1.06); }
.dp__play__text { font-size: .85rem; font-weight: 700; }

.dp__frame {
  width: 100%; aspect-ratio: 16 / 9;
  border: 1px solid var(--hairline); border-radius: 8px;
  display: block;
}

.dp__play__out {
  font-family: var(--mono); font-size: .66rem;
  letter-spacing: .06em; color: var(--ink-faint);
  text-decoration: none; justify-self: start;
}
.dp__play__out:hover { color: var(--terracotta); }

/* --- neighbouring module cards ------------------------------------------
   One card per adjacent module, replacing faded cards for that module's
   individual lessons. A destination, not a step — so no state marker, and the
   whole card is a way to travel.
   ---------------------------------------------------------------------- */

.node--module {
  background: var(--paper-warm);
  border-style: dashed;
  border-left-width: 3px;
  box-shadow: none;
  opacity: .82;
}
.node--module:hover {
  opacity: 1;
  background: var(--paper);
  border-style: solid;
  box-shadow: var(--shadow-md);
}
/* Two lines: the module's name, and its title. Nothing else — see moduleCard()
   in map.js for why. Centerd, because with the meta line gone a top-aligned
   card looks like a lesson card with its content missing. */
.node--module { justify-content: center; padding-right: 1.15rem; gap: .2rem; }
/* Up to five lines. Module titles run from 9 characters to 73, and the build
   sizes each card to its own title — see modHeight() in tools/build-map.mjs. A
   navigation card that ellipsises the name of the place it goes is not doing its
   one job. */
.node--module .node__title {
  font-size: .74rem; line-height: 1.24; -webkit-line-clamp: 5;
}
/* One line, always. */
.node--module .node__code {
  font-size: .55rem; letter-spacing: .08em; white-space: nowrap;
}

/* An arrow pointing the way the card leads. */
.node--module::after {
  position: absolute; top: 50%; transform: translateY(-50%);
  font-size: .8rem; color: var(--ink-faint); line-height: 1;
}
.node--module[data-side='before']::after { content: '‹'; right: .5rem; }
.node--module[data-side='after']::after  { content: '›'; right: .5rem; }
.node--module:hover::after { color: var(--terracotta); }

/* The recipe a link pointed at. Recipes are rail rows rather than canvas cards,
   so arriving at one needs a mark of its own — without it a PB link just changed
   the course and left the reader guessing which row it meant. */
.rec[data-found='true'] {
  background: var(--paper-warm);
  box-shadow: inset 3px 0 0 var(--green);
}

/* A starter flow: a file to import, not a page to read. Marked apart from the
   Read links so the arrow does not read as "opens in a new tab". */
.dp__item--flow { border-color: var(--sage); background: var(--paper-warm); }
.dp__dl { margin-left: auto; padding-left: .5rem; color: var(--green); font-weight: 600; }
.dp__hint {
  margin: .5rem 0 0; font-size: .78rem; line-height: 1.5; color: var(--ink-faint);
}

/* The poster is the video's real thumbnail, under the play mark. Cover-fit so a
   4:3 fallback still fills the 16:9 frame; the gradient stays as the ground while
   the image is on its way. Text gets a shadow so it survives any thumbnail. */
.dp__play { position: relative; overflow: hidden; padding: 0; isolation: isolate; }
.dp__poster {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; z-index: 0;
}
.dp__play__mark, .dp__play__text { position: relative; z-index: 1; }
.dp__play__text {
  color: #fff; text-shadow: 0 1px 2px rgba(0,0,0,.85), 0 0 14px rgba(0,0,0,.6);
}
.dp__play__mark { box-shadow: 0 2px 10px rgba(0,0,0,.45); }
