/* The portal's billboard and rows: the Library's Netflix look, shared by every customer page (JT, Oct 1 2026: "All of
   the customer pages shoudl mimic that style where there is a row"). Copied VERBATIM from library.html (the billboard,
   ROWS, TILE and card-as-control rules); library.html still carries its own copy, so a change here must be made there too
   until the library loads this file. Markup: js/rows.js builds it. */
/* ---------- BILLBOARD HERO ----------
   A large tile, not a full-bleed banner. It sits in the same column as
   every row below it, with the same gutter, the same corner radius and the
   same border, so the page reads as one set of tiles with the first one
   given the room it deserves. That is how Netflix does its billboard: it
   is a card, not a background.

   It rotates. The first slide is always where you actually were, because
   the most useful thing a library can tell you is which sentence you
   stopped on. The rest are what to pick up next. */
.billboard {
  position: relative;
  max-width: 1640px; margin: 0 auto;
  padding: 8px 28px 22px;
}
.billboard__tile {
  position: relative; overflow: hidden;
  border-radius: 14px; border: 1px solid var(--border-subtle);
  background: #07060a;
  /* Height, not aspect-ratio. With a ratio and a max-height the ratio
     wins and quietly takes the width back: the tile came out 1132px wide
     next to rows that were 1238, and the hero no longer lined up with the
     thing it sits on top of. */
  width: 100%; height: clamp(360px, 34vw, 540px);
}
.billboard__slide {
  position: absolute; inset: 0;
  opacity: 0; visibility: hidden;
  transition: opacity 0.6s ease, visibility 0.6s;
}
.billboard__slide.is-on { opacity: 1; visibility: visible; }
@media (prefers-reduced-motion: reduce) {
  .billboard__slide { transition: none; }
}
.billboard__art { position: absolute; inset: 0; }
/* Brighter than the old banner: inside a bordered tile the art is the
   subject rather than a texture behind the page. */
.billboard__art img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transform: scale(1.02); opacity: 0.92;
}
.billboard__shade {
  position: absolute; inset: 0;
  background:
    linear-gradient(to right, rgba(7,6,10,0.94) 0%, rgba(7,6,10,0.72) 38%, rgba(7,6,10,0.2) 68%, transparent 100%),
    linear-gradient(to top, rgba(7,6,10,0.88) 0%, rgba(7,6,10,0.3) 34%, transparent 62%);
}
.billboard__grain {
  position: absolute; inset: 0; pointer-events: none; z-index: 1;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.96  0 0 0 0 0.94  0 0 0 0 0.91  0 0 0 0.05 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  opacity: 0.26; mix-blend-mode: overlay; animation: grain 8s steps(8) infinite;
}
@keyframes grain {
  0%   { transform: translate(0,0); }
  20%  { transform: translate(-2%,-1%); }
  40%  { transform: translate(2%,1%); }
  60%  { transform: translate(-1%,2%); }
  80%  { transform: translate(1%,-2%); }
  100% { transform: translate(0,0); }
}
/* Copy sits low-left, the way a billboard does, rather than centred in the
   middle of the picture. */
.billboard__inner {
  position: absolute; z-index: 2;
  left: 0; right: 0; bottom: 0;
  max-width: 640px;
  padding: 0 40px 34px;
  display: flex; flex-direction: column; gap: 14px;
}
.billboard__rank {
  display: inline-flex; align-items: center; gap: 10px;
  width: max-content;
  background: rgba(10,10,12,0.55); backdrop-filter: blur(10px);
  border: 1px solid var(--gold-border);
  padding: 5px 12px 5px 5px; border-radius: 999px;
}
.billboard__rank-tag {
  background: var(--gold-grad); color: #1a1408;
  font-family: var(--display-bold); font-weight: 900;
  font-size: 0.68rem; letter-spacing: 0.16em; text-transform: uppercase;
  padding: 4px 10px; border-radius: 999px;
}
.billboard__rank-meta { color: var(--text-bright); font-size: 0.78rem; }
.billboard__rank-meta b { color: var(--gold-light); }
.billboard__title {
  font-family: var(--display-bold); font-weight: 900;
  font-size: clamp(2.1rem, 4.4vw, 3.7rem); line-height: 0.94;
  letter-spacing: -0.015em; text-transform: uppercase;
  color: var(--white); margin: 0;
  text-shadow: 0 2px 24px rgba(0,0,0,0.6);
}
.billboard__title em {
  display: block; margin-top: 8px;
  font-family: var(--display); font-style: italic; font-weight: 500;
  font-size: clamp(1.1rem, 1.9vw, 1.7rem); line-height: 1.1;
  letter-spacing: 0; text-transform: none; color: var(--gold-light);
}
.billboard__sub {
  color: var(--text-bright); font-size: 0.92rem; line-height: 1.5;
  margin: 0; max-width: 52ch;
  text-shadow: 0 1px 8px rgba(0,0,0,0.6);
}
.billboard__meta {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  color: var(--text-muted); font-size: 0.78rem;
}
.billboard__meta b { color: var(--text-bright); font-weight: 700; margin-right: 4px; }
.billboard__meta .chip {
  border: 1px solid var(--border-subtle); border-radius: 4px;
  padding: 3px 8px; letter-spacing: 0.1em; text-transform: uppercase;
  font-size: 0.68rem; font-weight: 700;
}
.billboard__actions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 2px; }
.billboard .btn--white {
  background: var(--white); color: #12100f; border: 0; font-weight: 700;
}
.billboard .btn--white:hover { background: var(--text-bright); }
.billboard .btn--ghost {
  background: rgba(10,10,12,0.55); backdrop-filter: blur(8px);
  color: var(--text-bright); border: 1px solid var(--border-subtle);
}
.billboard .btn--ghost:hover { border-color: var(--gold-border); color: var(--gold-light); }

/* Which of the featured tiles you are on, and a way to jump. Netflix puts
   these at the top right of a row; on the billboard they belong bottom
   right, clear of the copy. */
.billboard__dots {
  position: absolute; z-index: 3; right: 22px; bottom: 22px;
  display: flex; gap: 7px;
}
.billboard__dot {
  width: 26px; height: 3px; border-radius: 2px; padding: 0;
  border: 0; cursor: pointer;
  background: rgba(244,240,231,0.28);
  transition: background var(--t), width var(--t);
}
.billboard__dot:hover { background: rgba(244,240,231,0.55); }
.billboard__dot.is-on { background: var(--gold); width: 34px; }
.billboard__dot:focus-visible { outline: var(--focus-ring); outline-offset: 3px; }

@media (max-width: 900px) {
  .billboard { padding: 8px 14px 18px; }
  .billboard__tile { height: clamp(300px, 62vw, 420px); }
  .billboard__inner { padding: 0 20px 24px; max-width: none; }
  .billboard__sub { display: none; }
}

/* ---------- ROWS ---------- */
.row {
  position: relative;
  max-width: 1640px; margin: 0 auto;
  padding: 0 28px 6px;
  /* How many cards fill the row. The tiles size themselves off this, so
     the row is always full rather than three fixed-width cards with a
     column of dead space to the right of them. */
  --per-row: 3;
}
@media (min-width: 1700px) { .row { --per-row: 4; } }
@media (max-width: 1180px) { .row { --per-row: 2; } }
@media (max-width: 720px)  { .row { --per-row: 1; } }
.row--first { position: relative; z-index: 3; }
/* "Your certificate" (Sep 30 2026): the first thing under the search, so a member always knows where the certificate
   lives and how close it is (lesson 4.7 sends them to "Library, then Your certificate"). Filled from
   GET /api/certificates/me after the rows paint; empty until then, and it stays empty if that call fails. */
.cert-row:empty { display: none; }
.cert-banner { display: flex; align-items: center; gap: 18px; padding: 16px 20px; border-radius: 16px; text-decoration: none;
               border: 1px solid var(--gold-border); background: linear-gradient(120deg, var(--gold-dim), rgba(255,255,255,0.015) 65%);
               transition: transform var(--t), border-color var(--t); }
.cert-banner:hover, .cert-banner:focus-visible { transform: translateY(-1px); border-color: var(--gold); outline: none; }
.cert-banner__seal { flex: none; width: 46px; height: 46px; border-radius: 50%; background: var(--gold-grad); color: #1a1408;
                     display: flex; align-items: center; justify-content: center; font-weight: 900; font-size: 0.8rem; letter-spacing: 0.04em; }
.cert-banner__body { flex: 1; min-width: 0; }
.cert-banner__eyebrow { color: var(--gold); font-size: 0.72rem; font-weight: 800; letter-spacing: 0.16em; text-transform: uppercase; }
.cert-banner__title { color: var(--white); font-weight: 800; font-size: 1.05rem; margin: 2px 0 2px; }
.cert-banner__sub { color: var(--text-muted); font-size: 0.88rem; line-height: 1.45; }
.cert-banner__bar { height: 6px; border-radius: 999px; background: rgba(255,255,255,0.08); overflow: hidden; margin: 8px 0 0; max-width: 420px; }
.cert-banner__bar i { display: block; height: 100%; background: var(--gold-grad); }
.cert-banner__go { flex: none; color: var(--gold-light); font-weight: 800; font-size: 0.9rem; white-space: nowrap; }
@media (max-width: 640px) { .cert-banner { flex-wrap: wrap; } .cert-banner__go { width: 100%; } }
.row__head {
  display: flex; align-items: baseline; justify-content: space-between;
  margin-bottom: 2px; padding: 0 6px; gap: 24px;
}
.row__title {
  font-family: var(--display); font-weight: 600;
  font-size: 1.35rem; margin: 0; color: var(--text-bright);
  letter-spacing: 0.005em;
}
.row__title em { color: var(--gold-light); font-style: italic; font-weight: 600; }
.row__sub { display: none; }
.row__more {
  color: var(--gold-light); text-decoration: none;
  font-size: 0.74rem; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase;
  white-space: nowrap;
}
.row__more::after { content: ' →'; }
/* The scrollbar rules that used to live here beat the shared .hscroll__track
   ones on document order, so every row kept a native grey bar under it even
   after the brand arrows went on. The arrows are the control now. */
.row__scroller {
  display: flex; gap: 12px; overflow-x: auto; overflow-y: visible;
  padding: 10px 2px 14px;
  scroll-snap-type: x mandatory;
}

/* ---------- TILE ---------- */
.tile {
  position: relative;
  /* A share of the row rather than a fixed 320px. At this width three
     fixed cards left about 240px of nothing on the right; now the same
     three grow to fill it, which is also how Netflix sizes a row. */
  flex: 0 0 calc((100% - (var(--per-row) - 1) * 12px) / var(--per-row));
  min-width: 240px;
  cursor: pointer; text-decoration: none; color: inherit;
  transition: transform 280ms cubic-bezier(.2,.7,.3,1);
  scroll-snap-align: start;
}
.tile:hover { transform: translateY(-6px) scale(1.04); z-index: 5; }
.tile__art {
  position: relative;
  aspect-ratio: 16/9;
  border-radius: 8px;
  overflow: hidden;
  background: var(--card);
  box-shadow: 0 6px 16px rgba(0,0,0,0.45);
  transition: box-shadow 280ms cubic-bezier(.2,.7,.3,1);
  outline: 1px solid var(--border-subtle);
}
.tile:hover .tile__art {
  box-shadow: 0 20px 48px rgba(0,0,0,0.6), 0 0 0 2px var(--accent, var(--gold-border));
  outline-color: transparent;
}
.tile__art img { width: 100%; height: 100%; object-fit: cover; display: block; }
.tile__art-overlay {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; justify-content: flex-end;
  padding: 14px 16px;
  background: linear-gradient(to top, rgba(10,10,12,0.78) 0%, rgba(10,10,12,0.45) 30%, transparent 60%);
}
.tile__art-title {
  font-family: var(--display-bold);
  font-weight: 900; text-transform: uppercase;
  font-size: 1.55rem; line-height: 0.95; letter-spacing: -0.005em;
  color: var(--text-bright);
  text-shadow: 0 2px 10px rgba(0,0,0,0.6);
}
.tile__art-title em {
  display: block; font-family: var(--display); font-style: italic; font-weight: 600;
  font-size: 0.62em; text-transform: none; color: var(--gold-light);
  letter-spacing: 0; margin-top: 4px;
}
.tile__chip {
  position: absolute; top: 10px; left: 10px;
  display: inline-flex; align-items: center; gap: 6px;
  background: rgba(10,10,12,0.7); backdrop-filter: blur(8px);
  border: 1px solid var(--border-strong);
  padding: 4px 10px; border-radius: 999px;
  font-family: var(--display); font-style: italic; font-weight: 600;
  color: var(--gold-light); font-size: 0.84rem; line-height: 1;
}
.tile__chip-label {
  font-family: var(--sans); font-style: normal; font-weight: 700;
  font-size:0.72rem; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--text-bright); opacity: 0.85;
}
.tile__status {
  position: absolute; top: 10px; right: 10px;
  font-size:0.72rem; font-weight: 700; letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 4px 10px; border-radius: 999px;
  background: rgba(10,10,12,0.7); backdrop-filter: blur(8px);
  border: 1px solid var(--border-strong);
  color: var(--text-bright);
}
.tile__status--progress { background: rgba(196,164,96,0.20); border-color: var(--gold-border); color: var(--gold-light); }
/* How long the thing is, on the art, because it is the one fact the
   picture cannot tell you and the reason someone picks one over another. */
.tile__len {
  position: absolute; right: 8px; bottom: 8px; z-index: 2;
  background: rgba(8,8,10,0.72); backdrop-filter: blur(6px);
  border: 1px solid rgba(244,240,231,0.14);
  color: var(--text-bright); border-radius: 4px;
  padding: 2px 7px; font-size: 0.68rem; font-weight: 700;
  letter-spacing: 0.04em;
}
.tile:hover .tile__len, .tile:focus-within .tile__len { opacity: 0; }
/* The lessons badge sits in the art's bottom right corner, over the tagline's last line. Lift the poster title
   above it, so a long tagline is never covered; the lines stay the same (Sep 27 2026: six of twenty badged
   tiles had the end of their tagline under it at 1280px). verify_library_search.py L11 measures it. */
.tile__art:has(.tile__len) .tile__art-overlay { padding-bottom: 34px; }
.tile__status--complete { background: rgba(80,160,90,0.24); border-color: rgba(80,160,90,0.5); color: #c2e0c8; }
.tile__progress {
  position: absolute; left: 0; right: 0; bottom: 0;
  height: 3px;
  background: rgba(244,240,231,0.1);
}
.tile__progress-fill { height: 100%; background: var(--gold-grad); }
/* The name stays. The art has a title burned into it, but at tile size on
   a dark image it is not reliably readable, and nobody should have to
   guess what they are about to click. What goes is everything around it:
   the eyebrow repeats the badge on the art, and the lesson count is now on
   the corner of the picture. One line, one name. */
.tile__caption { padding: 9px 4px 0; }
.tile__eyebrow { display: none; }
.tile__caption-title {
  font-family: var(--display); font-weight: 600;
  font-size: 0.98rem; line-height: 1.2; color: var(--text-bright); margin: 0;
  /* One line. A title that wraps makes the row below it jump. */
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tile:hover .tile__caption-title { color: var(--white); }
.tile__caption-meta { display: none; }

/* ---------- CARD AS A CONTROL SURFACE ----------
   Netflix's signature interaction, and the thing this Library was missing:
   hovering a card lifts it above its neighbours and turns it into a panel
   with the exact resume position and the actions inline. A card that is
   only a link makes you open something to find out where you were.

   The card is an <article> with a stretched link covering it, so the whole
   tile stays clickable while real controls can sit above it. Nesting them
   inside an <a> would be invalid. */
.tile__hover {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: var(--sp-4) var(--sp-3) var(--sp-3);
  background: linear-gradient(to top, rgba(8,8,10,0.96) 40%, rgba(8,8,10,0.72) 70%, transparent);
  opacity: 0; transform: translateY(8px);
  transition: opacity var(--t), transform var(--t);
  z-index: 3;
}
.tile:hover .tile__hover,
.tile:focus-within .tile__hover { opacity: 1; transform: translateY(0); }

.tile__hover-next {
  color: var(--text-bright); font-size: 0.78rem; line-height: 1.35;
  margin-bottom: var(--sp-2);
}
.tile__hover-next strong { color: var(--white); font-weight: 700; }
.tile__hover-actions { display: flex; gap: var(--sp-2); align-items: center; }
.tile__play, .tile__more {
  position: relative; z-index: 4;
  display: inline-flex; align-items: center; gap: 6px;
  font: inherit; font-size: 0.78rem; font-weight: 700;
  padding: 7px 14px; border-radius: var(--radius-pill);
  cursor: pointer; transition: all var(--t);
}
.tile__play { background: var(--gold-grad); color: #1a1408; border: 1px solid transparent; }
.tile__play:hover { filter: brightness(1.07); color: #1a1408; }
.tile__more { background: rgba(244,240,231,0.12); color: var(--white); border: 1px solid var(--border-control); }
.tile__more:hover { background: rgba(244,240,231,0.2); color: var(--white); }
.tile__play:focus-visible, .tile__more:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }

/* The stretched link. Covers the card so the whole tile is one target,
   and sits below the hover controls so they win the click. */
.tile__link { position: absolute; inset: 0; z-index: 2; }
.tile { position: relative; }

/* Row pagination. Netflix shows how deep a row goes before you scroll it;
   without it a row of six looks like the whole catalogue. */
.row__pages { display: flex; gap: 3px; margin-left: auto; }
.row__page {
  width: 14px; height: 2px; border-radius: 2px;
  background: rgba(244,240,231,0.22); transition: background var(--t);
}
.row__page.is-on { background: var(--gold-light); }

/* Featured row tiles are larger */
.row--featured .tile { width: 380px; }
.row--featured .tile__art-title { font-size: 1.8rem; }

/* A tile the page acts on (save a seat, pick a plan) is a button, not a link: the same card, with the button's own
   look taken away (rows.js, Oct 2 2026). */
.tile--button { background: none; border: 0; padding: 0; margin: 0; font: inherit; color: inherit; text-align: left;
  /* a browser centres a button's content vertically when the row stretches it beside a taller card: the picture then
     sat lower than its neighbours' (Community's spaces beside the locked Live card, Oct 2 2026) */
  display: flex; flex-direction: column; justify-content: flex-start; }
.tile--button:focus-visible { outline: var(--focus-ring); outline-offset: 4px; border-radius: 8px; }

/* ---------- MEMBER PAGES (Oct 2 2026) ----------
   What the member pages need that the Library does not; js/rows.js documents the options.

   A billboard whose copy decides its height (billboard({ fit: true })): a member page's billboard carries a price, a
   plan or two buttons, which in the Library's fixed height ran off the top of the tile at phone width. Every slide sits
   in the same grid cell, so the tile is as tall as its tallest slide and the rotation still cross-fades. The sub line
   stays on phones here: on a member page it is the explanation, not a teaser. */
.billboard--fit .billboard__tile { display: grid; grid-template-columns: minmax(0, 1fr); height: auto; min-height: clamp(360px, 34vw, 540px); }
/* minmax(0, 1fr) and min-width 0: without them the one column grew to the widest unbreakable line (the rank pill's full
   width), and on a phone the copy ran 48px past the tile's right edge, clipped mid-word (P12, Oct 2 2026) */
.billboard--fit .billboard__slide { position: relative; grid-area: 1 / 1; min-width: 0; display: flex; flex-direction: column; justify-content: flex-end; }
.billboard--fit .billboard__inner { position: relative; padding-top: 72px; }
.billboard--fit .billboard__tile:has(.billboard__dots) .billboard__inner { padding-bottom: 52px; }
/* On a phone the copy runs the full width of the tile, so laid over the picture it crossed the bright part of the art
   (Oct 2 2026, the first phone render). There the picture sits on top and the copy under it, the way Netflix lays a title
   out on a phone: the art fades into the tile, and the rank pill and title overlap only its darkened foot. */
@media (max-width: 900px) {
  .billboard--fit .billboard__tile { min-height: 0; }
  .billboard--fit .billboard__slide { justify-content: flex-start; }
  .billboard--fit .billboard__art { position: relative; inset: auto; flex: none; aspect-ratio: 16 / 9; }
  .billboard--fit .billboard__art::after { content: ''; position: absolute; inset: 0;
    background: linear-gradient(to bottom, rgba(7,6,10,0) 35%, rgba(7,6,10,0.78) 78%, #07060a 100%); }
  .billboard--fit .billboard__shade { display: none; }
  .billboard--fit .billboard__inner { padding-top: 0; margin-top: -44px; }
  .billboard--fit .billboard__sub { display: block; }
}

/* A tile that describes something and goes nowhere: no lift, no pointer, and a short line under its name */
.tile--still { cursor: default; }
.tile--still:hover { transform: none; }
.tile--still:hover .tile__art { box-shadow: 0 6px 16px rgba(0,0,0,0.45); outline-color: var(--border-subtle); }
.tile__desc { margin: 4px 0 0; color: var(--text-muted); font-size: 0.86rem; line-height: 1.45; }

/* Two phone defects found on the first restyled pages (Oct 2 2026):
   - app.css makes every button at least 44px tall at 768px and below (the finger floor), which turned these 3px dots
     into 44px blocks. They keep their drawn size and get an invisible landing area instead, the way app.css treats its
     other drawn-size controls. The Library's own copy of these rules carries the same fix.
   - a long rank line (a plan and its renewal date) widened the copy past the tile's right edge, where the tile clipped
     it mid-word. The rank wraps inside the tile instead, and nothing in the copy may be wider than the copy. */
@media (max-width: 768px) {
  .billboard__dot { min-height: 0; position: relative; }
  .billboard__dot::after { content: ''; position: absolute; left: -4px; right: -4px; top: -20px; bottom: -20px; }
}
.billboard__rank { max-width: 100%; flex-wrap: wrap; }
.billboard__inner { min-width: 0; }
.billboard__inner > * { max-width: 100%; }

/* A compact billboard (billboard({ fit: true, extraClass: 'billboard--compact' })): the header of a working page (a
   messenger, a list, a settings panel), the Library's look at about half the height, so the page's own work starts on
   the first screen (Oct 2 2026). On a phone its picture is a wide strip rather than a full 16:9. */
.billboard--compact.billboard--fit .billboard__tile { min-height: clamp(200px, 17vw, 270px); }
.billboard--compact.billboard--fit .billboard__inner { padding-top: 36px; padding-bottom: 26px; gap: 10px; }
.billboard--compact .billboard__title { font-size: clamp(1.8rem, 3.2vw, 2.7rem); }
@media (max-width: 900px) {
  .billboard--compact.billboard--fit .billboard__art { aspect-ratio: 21 / 9; }
  .billboard--compact.billboard--fit .billboard__inner { padding-top: 0; padding-bottom: 20px; }
}

/* A row heading with its own control (Community's "All verticals") and many page dashes was wider than a phone, and the
   dashes ran off the right edge (verify_portal_look G7, Oct 2 2026). On a phone the heading wraps: the dashes take their
   own line, at the right. */
@media (max-width: 720px) {
  .row__head { flex-wrap: wrap; row-gap: 6px; }
  .row__pages { margin-left: auto; }
}
