/* Adera — a clinic's record of medication courses.
 *
 * The world this borrows from is the pharmacy shelf: apothecary glass, printed
 * dosage labels, the rhythm of a blister pack. Warm paper ground, one deep
 * green that behaves like bottle glass, and amber reserved strictly for "not
 * yet given". Everything stays quiet so the dose ribbon can be the loud thing.
 */

:root {
  --paper:   #FBF8F1;   /* warm cream ground */
  --card:    #FFFDF8;   /* a shade warmer than white */
  --sunk:    #F5F1E7;   /* wells, hovered rows */
  --ink:     #1C1A16;   /* warm near-black */
  --muted:   #7C7466;
  --faint:   #A79E8D;
  --rule:    #E8E1D2;   /* warm hairline */
  --seal:    #1E5147;   /* apothecary glass green */
  --seal-lift:#2A6B5E;
  --dose:    #B0741F;   /* amber: awaiting, never anything else */
  --halt:    #9A3324;
  --shadow:  0 1px 2px rgba(28,26,22,.04), 0 8px 24px -12px rgba(28,26,22,.10);
  --shadow-lift: 0 2px 4px rgba(28,26,22,.05), 0 18px 40px -16px rgba(28,26,22,.18);
  --radius:  14px;
  --rail:    15.5rem;

  --display: "Fraunces", "Iowan Old Style", Georgia, serif;
  --body:    "IBM Plex Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
  --data:    "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, monospace;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 15px/1.55 var(--body);
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3 { font-family: var(--display); font-weight: 500; letter-spacing: -.015em; margin: 0; }
h1 { font-size: 1.75rem; font-variation-settings: "SOFT" 30, "opsz" 40; }
h2 { font-size: 1.12rem; }
a { color: var(--seal); text-underline-offset: 2px; }
code, .mono { font-family: var(--data); font-size: .88em; }

/* ─── shell ──────────────────────────────────────────────────────────────── */

.shell { display: flex; min-height: 100vh; }

.rail {
  width: var(--rail);
  flex: 0 0 var(--rail);
  background: var(--card);
  border-right: 1px solid var(--rule);
  padding: 1.5rem 1rem;
  display: flex;
  flex-direction: column;
  gap: .2rem;
  position: sticky;
  top: 0;
  height: 100vh;
}
.rail .mark {
  font-family: var(--display);
  font-size: 1.4rem;
  font-variation-settings: "SOFT" 60, "WONK" 1, "opsz" 40;
  letter-spacing: -.02em;
  padding: 0 .6rem 1.4rem;
  display: flex; align-items: baseline; gap: .5rem;
}
.rail .mark small {
  font-family: var(--data); font-size: .58rem; letter-spacing: .12em;
  text-transform: uppercase; color: var(--faint);
}
.rail a {
  display: flex; align-items: center; gap: .6rem;
  padding: .52rem .6rem; border-radius: 9px;
  color: var(--muted); text-decoration: none; font-weight: 500; font-size: .93rem;
  transition: background .18s ease, color .18s ease;
}
.rail a:hover { background: var(--sunk); color: var(--ink); }
.rail a.on { background: var(--seal); color: #fff; }
.rail a.on .ico { opacity: 1; }
.rail .ico { width: 1.05rem; text-align: center; opacity: .7; font-size: .95rem; }
.rail .foot { margin-top: auto; padding-top: 1rem; border-top: 1px solid var(--rule); }
.rail .who { padding: .1rem .6rem .6rem; }
.rail .who b { display: block; font-weight: 600; font-size: .9rem; }
.rail .who span { font-family: var(--data); font-size: .68rem; color: var(--faint);
                  letter-spacing: .06em; text-transform: uppercase; }

.main { flex: 1; min-width: 0; padding: 2rem 2.2rem 4rem; max-width: 78rem; }

.pagehead {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap; margin-bottom: 1.5rem;
}
.pagehead p { margin: .3rem 0 0; color: var(--muted); font-size: .93rem; max-width: 44ch; }

/* ─── surfaces ───────────────────────────────────────────────────────────── */

.card {
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 1.3rem 1.4rem;
}
.card + .card { margin-top: 1rem; }
.card > h2 { margin-bottom: .2rem; }
.card > h2 + .hint { margin: 0 0 1rem; }
.hint { color: var(--muted); font-size: .89rem; }

.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); gap: .8rem; }
.stat { background: var(--card); border: 1px solid var(--rule); border-radius: var(--radius);
        padding: 1.05rem 1.1rem; box-shadow: var(--shadow); }
.stat .n { font-family: var(--display); font-size: 2.1rem; line-height: 1;
           font-variation-settings: "SOFT" 20, "opsz" 60; letter-spacing: -.03em; }
.stat .l { font-family: var(--data); font-size: .66rem; letter-spacing: .1em;
           text-transform: uppercase; color: var(--faint); margin-top: .45rem; }
.stat.amber .n { color: var(--dose); }

/* ─── tables ─────────────────────────────────────────────────────────────── */

.scroll { overflow-x: auto; margin: 0 -1.4rem; padding: 0 1.4rem; }

/* A strip that scrolls with no scrollbar -- which is every strip on a phone --
   reads as broken content rather than as content you can push. A word cut in
   half looks like a rendering fault, not like an invitation, and the dose
   ribbon sat past that cut where nobody had a reason to look.
   Four backgrounds, no JavaScript: two opaque ones attached `local` so they
   ride with the content and cap the ends, and two shadows attached to the box
   itself, which the opaque pair covers whenever that end is reached. What is
   left is a shadow on exactly the side that has more. */
.scroll, .rail {
  background:
    linear-gradient(to right, var(--card) 40%, transparent) 0 0 / 1.7rem 100%
      no-repeat local,
    linear-gradient(to left, var(--card) 40%, transparent) 100% 0 / 1.7rem 100%
      no-repeat local,
    radial-gradient(farthest-side at 0 50%, rgba(28, 26, 22, .13), transparent)
      0 0 / .5rem 100% no-repeat,
    radial-gradient(farthest-side at 100% 50%, rgba(28, 26, 22, .13), transparent)
      100% 0 / .5rem 100% no-repeat,
    var(--card);
}
table { width: 100%; border-collapse: collapse; }
th {
  font-family: var(--data); font-size: .66rem; font-weight: 500; letter-spacing: .1em;
  text-transform: uppercase; color: var(--faint); text-align: left;
  padding: .5rem .6rem; border-bottom: 1px solid var(--rule); white-space: nowrap;
}
td { padding: .7rem .6rem; border-bottom: 1px solid var(--rule); vertical-align: middle; }
tbody tr { transition: background .15s ease; }
tbody tr:hover { background: var(--sunk); }
tbody tr:last-child td { border-bottom: 0; }
td .name { font-weight: 600; }
/* Recessed, not hidden: an action faded to near-invisible reads as disabled,
   and people stop believing they can click it. */
.rowacts { display: flex; gap: .35rem; justify-content: flex-end; opacity: .72;
           transition: opacity .18s ease; }
tr:hover .rowacts, .rowacts:focus-within { opacity: 1; }
@media (hover: none) { .rowacts { opacity: 1; } }

/* ─── controls ───────────────────────────────────────────────────────────── */

label { display: block; font-weight: 550; font-size: .85rem; margin: .9rem 0 .3rem; }
input[type=text], input[type=tel], input[type=date], input[type=time],
input[type=password], input[type=email], input[type=number], select, textarea {
  width: 100%; padding: .6rem .75rem; font: inherit; color: inherit;
  background: var(--paper); border: 1px solid var(--rule); border-radius: 9px;
  transition: border-color .16s ease, box-shadow .16s ease;
}
input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: none; border-color: var(--seal);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--seal) 14%, transparent);
}
/* A file input on the client's path is a FIELD, not a row action. `.filebtn`
   above is capped at 8.5rem so it stops dwarfing the `btn-sm` links it sits
   between in an admin row; under its own label on the wizard, that cap reads
   as a control someone shrank by mistake. Only the button half is redressed
   here -- the input itself stays the browser's own, because on a phone it is
   what opens the camera. */
.public input[type=file] {
  display: block; width: 100%; margin: .35rem 0 0;
  font: inherit; font-size: .82rem; color: var(--muted);
}
.public input[type=file]::file-selector-button {
  padding: .45rem .8rem; margin-right: .6rem; font: inherit; font-size: .82rem;
  color: var(--ink); background: var(--card); border: 1px solid var(--rule);
  border-radius: 8px; cursor: pointer;
  transition: background .16s ease, border-color .16s ease;
}
.public input[type=file]::file-selector-button:hover {
  background: var(--sunk); border-color: var(--seal);
}

.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); gap: .1rem .8rem; }

button, .btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .4rem;
  padding: .58rem .95rem; font: inherit; font-weight: 550; font-size: .9rem;
  color: #fff; background: var(--seal); border: 1px solid var(--seal);
  border-radius: 9px; cursor: pointer; text-decoration: none; white-space: nowrap;
  transition: background .16s ease, border-color .16s ease, transform .1s ease, box-shadow .16s ease;
}
button:hover, .btn:hover { background: var(--seal-lift); border-color: var(--seal-lift); }
button:active, .btn:active { transform: translateY(1px); }
button:focus-visible, .btn:focus-visible {
  outline: 2px solid var(--seal); outline-offset: 2px;
}
.btn-quiet, button.quiet {
  background: transparent; color: var(--muted); border-color: var(--rule);
}
.btn-quiet:hover, button.quiet:hover { background: var(--sunk); color: var(--ink); border-color: var(--rule); }
.btn-danger, button.danger { background: var(--halt); border-color: var(--halt); }
.btn-danger:hover, button.danger:hover { background: #82291d; border-color: #82291d; }
.btn-sm, button.sm { padding: .3rem .55rem; font-size: .8rem; border-radius: 7px; }
.btn-block { width: 100%; }

/* A raw `<input type=file>` renders at the browser's own control size, which
   dwarfs the `btn-sm` links it sits beside in a row of actions. Capped to the
   same scale, it reads as one more quiet row action instead of a stray widget. */
.filebtn {
  max-width: 8.5rem; font-size: .72rem; color: var(--muted);
  padding: .3rem 0; border: 0; background: none;
}
.filebtn::file-selector-button {
  padding: .3rem .55rem; margin-right: .4rem; font: inherit; font-size: .8rem;
  color: var(--muted); background: transparent; border: 1px solid var(--rule);
  border-radius: 7px; cursor: pointer; transition: background .16s ease;
}
.filebtn::file-selector-button:hover { background: var(--sunk); color: var(--ink); }

/* ─── pills ──────────────────────────────────────────────────────────────── */

.pill {
  display: inline-flex; align-items: center; gap: .3rem;
  font-family: var(--data); font-size: .68rem; letter-spacing: .04em;
  padding: .16rem .5rem; border-radius: 999px;
  background: var(--sunk); color: var(--muted); border: 1px solid var(--rule);
  white-space: nowrap;
}
.pill.live   { color: var(--seal); background: color-mix(in srgb, var(--seal) 8%, var(--card));
               border-color: color-mix(in srgb, var(--seal) 22%, var(--rule)); }
.pill.wait   { color: var(--dose); background: color-mix(in srgb, var(--dose) 9%, var(--card));
               border-color: color-mix(in srgb, var(--dose) 25%, var(--rule)); }
.pill.stop   { color: var(--halt); background: color-mix(in srgb, var(--halt) 8%, var(--card));
               border-color: color-mix(in srgb, var(--halt) 22%, var(--rule)); }

/* ─── the dose ribbon: one pip per dose, read like a blister pack ────────── */

.ribbon { display: flex; align-items: center; gap: 3px; }
.pip {
  width: 9px; height: 9px; border-radius: 50%;
  border: 1.5px solid var(--faint); background: transparent;
  position: relative; flex: 0 0 auto;
  animation: pip-in .32s cubic-bezier(.2,.8,.3,1) backwards;
}
.pip.sent      { background: var(--seal); border-color: var(--seal); }
.pip.confirmed { background: var(--seal); border-color: var(--seal);
                 box-shadow: 0 0 0 2.5px color-mix(in srgb, var(--seal) 18%, transparent); }
.pip.pending   { border-color: var(--dose); }
.pip.skipped   { border: 0; height: 1.5px; width: 9px; border-radius: 1px;
                 background: var(--rule); }
.pip.failed    { background: var(--halt); border-color: var(--halt); }
.ribbon .more { font-family: var(--data); font-size: .68rem; color: var(--faint);
                margin-left: .25rem; }
@keyframes pip-in { from { opacity: 0; transform: scale(.4); } to { opacity: 1; transform: scale(1); } }

.legend { display: flex; gap: 1rem; flex-wrap: wrap; font-size: .8rem; color: var(--muted); }
.legend span { display: inline-flex; align-items: center; gap: .35rem; }

/* ─── modal ──────────────────────────────────────────────────────────────── */

dialog {
  border: 1px solid var(--rule); border-radius: var(--radius);
  background: var(--card); color: var(--ink);
  padding: 0; width: min(30rem, calc(100vw - 2rem));
  box-shadow: var(--shadow-lift);
}
dialog::backdrop { background: rgba(28,26,22,.28); backdrop-filter: blur(2px); }
dialog[open] { animation: sheet-in .22s cubic-bezier(.2,.8,.3,1); }
dialog[open]::backdrop { animation: fade-in .22s ease; }
dialog .head { padding: 1.2rem 1.3rem .2rem; }
dialog .body { padding: 0 1.3rem 1.1rem; }
dialog .foot {
  display: flex; gap: .5rem; justify-content: flex-end;
  padding: 1rem 1.3rem; border-top: 1px solid var(--rule); background: var(--sunk);
  border-radius: 0 0 var(--radius) var(--radius);
}
@keyframes sheet-in { from { opacity: 0; transform: translateY(8px) scale(.97); } }
@keyframes fade-in { from { opacity: 0; } }

/* ─── toast ──────────────────────────────────────────────────────────────── */

.toast {
  /* Centred by `margin-inline: auto` between two zeroed edges, NOT by
     `left: 50%` and a translate. The translate looks equivalent and is not:
     it leaves the box only 50vw of room to lay out in, so `max-width` below
     never applied on a narrow screen and a message of any length collapsed
     into a tall blob with its text outside the pill. */
  position: fixed; left: 0; right: 0; bottom: 1.6rem;
  width: max-content; margin-inline: auto;
  display: flex; align-items: center; gap: .6rem; z-index: 50;
  background: var(--ink); color: var(--paper);
  padding: .7rem 1.1rem; border-radius: 999px; font-size: .89rem; font-weight: 500;
  box-shadow: var(--shadow-lift);
  animation: toast-in .3s cubic-bezier(.2,.8,.3,1);
  max-width: min(34rem, calc(100vw - 2rem));
}
.toast.bad { background: var(--halt); color: #fff; }
.toast .dot { width: .5rem; height: .5rem; border-radius: 50%; background: #8ED6BE; flex: 0 0 auto; }
.toast.bad .dot { background: #FFD2CB; }
.toast button { background: none; border: 0; color: inherit; opacity: .6; padding: 0 0 0 .3rem; }
.toast button:hover { background: none; opacity: 1; }
@keyframes toast-in { from { opacity: 0; transform: translateY(10px); } }

/* ─── empty states ───────────────────────────────────────────────────────── */

.empty { text-align: center; padding: 2.4rem 1rem; color: var(--muted); }
.empty .glyph { font-family: var(--display); font-size: 2rem; color: var(--rule); display: block;
                margin-bottom: .4rem; }
.empty p { margin: 0 0 1rem; }

/* ─── entrance ───────────────────────────────────────────────────────────── */

.rise { animation: rise .4s cubic-bezier(.2,.8,.3,1) backwards; }
@keyframes rise { from { opacity: 0; transform: translateY(6px); } }

/* ─── centred pages (login, public) ──────────────────────────────────────── */

.centre { max-width: 24rem; margin: 0 auto; padding: 4.5rem 1.1rem; }
.public { max-width: 34rem; margin: 0 auto; padding: 2rem 1.1rem 4rem; }

/* ─── responsive ─────────────────────────────────────────────────────────── */

@media (max-width: 800px) {
  .shell { display: block; }
  .rail {
    width: auto; height: auto; position: static; flex-direction: row;
    align-items: center; gap: .3rem; overflow-x: auto; padding: .7rem .9rem;
    border-right: 0; border-bottom: 1px solid var(--rule);
  }
  .rail .mark { padding: 0 .8rem 0 0; font-size: 1.15rem; }
  .rail .mark small { display: none; }
  /* Not `margin-left: auto`. In a container that scrolls, `auto` measures
     against the SCROLL width, so it pinned logging out to x=619 of 741 -- off
     screen at every phone size, with nothing saying it was there. */
  .rail .foot { margin: 0 0 0 .4rem; padding: 0 0 0 .7rem; border: 0; border-left: 1px solid var(--rule); }
  .rail .who { display: none; }
  .main { padding: 1.4rem 1.1rem 3rem; }
  .scroll { margin: 0 -1.4rem; }

  /* The subscriber list stops being a table. Six columns need 812px and a
     phone has 390, which put the dose ribbon -- the one thing this page is
     built around -- 300px past the right edge, behind a scroll nobody could
     see. Stacked, it is simply there.

     The same `<tr>` is re-laid out rather than a second copy of the row being
     written into the template: one source of truth per course, and the grid
     areas decide where each cell lands. */
  .courses { overflow-x: visible; margin: 0; padding: 0; background: none; }
  .courses thead { display: none; }
  .courses table, .courses tbody { display: block; }
  .courses tr {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "who    state"
      "phone  phone"
      "prod   prod"
      "sched  sched"
      "ribbon ribbon"
      "acts   acts";
    gap: .3rem .7rem;
    padding: 1rem 0;
    border-bottom: 1px solid var(--rule);
  }
  .courses tbody tr:last-child { border-bottom: 0; }
  .courses td { display: block; padding: 0; border: 0; }
  .courses td:nth-child(1) { grid-area: who; }
  .courses td:nth-child(2) { grid-area: phone; }
  .courses td:nth-child(3) { grid-area: prod; }
  .courses td:nth-child(4) { grid-area: sched; }
  .courses td:nth-child(5) { grid-area: ribbon;
                             display: flex; align-items: center; gap: .55rem;
                             flex-wrap: wrap; margin-top: .2rem; }
  .courses td:nth-child(6) { grid-area: state; justify-self: end; }
  .courses td:nth-child(7) { grid-area: acts; }
  /* The name and its channel are a line, not two: the `<br>` between them is
     what the table wanted. */
  .courses td:nth-child(1) br { display: none; }
  .courses td:nth-child(1) .pill { margin-left: .45rem; }
  .courses td:nth-child(3), .courses td:nth-child(4) { color: var(--muted); font-size: .9rem; }
  .courses .rowacts { justify-content: flex-start; margin-top: .5rem; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}

/* ─── the client surface ─────────────────────────────────────────────────────
 * Same paper and same green, but one column and much larger touch targets:
 * this is read on a phone, once, by someone standing in a waiting room.
 */

[hidden] { display: none !important; }   /* beats `button { display: inline-flex }` */

.sub  { color: var(--muted); margin: 0 0 1.2rem; font-size: .95rem; }
.big  { font-family: var(--display); font-size: 2.6rem; line-height: 1; margin: 0 0 .6rem;
        color: var(--seal); font-variation-settings: "SOFT" 40, "opsz" 60; }
.note { background: var(--sunk); border: 1px solid var(--rule); border-radius: 10px;
        padding: .75rem .9rem; font-size: .88rem; color: var(--muted); }
.error { background: color-mix(in srgb, var(--halt) 8%, var(--card));
         border: 1px solid color-mix(in srgb, var(--halt) 35%, var(--rule));
         color: var(--halt); border-radius: 10px; padding: .75rem .9rem;
         font-size: .9rem; margin-bottom: 1.1rem; }
.meta { border-top: 1px solid var(--rule); margin-top: 1.3rem; padding-top: 1rem;
        font-size: .89rem; }
.meta div { display: flex; justify-content: space-between; gap: 1rem; padding: .28rem 0; }
.meta div span:first-child { color: var(--muted); }
.row { display: flex; gap: .7rem; flex-wrap: wrap; }
.row > div { flex: 1; min-width: 8rem; }
.block { width: 100%; margin-top: 1.3rem; }

/* the four-step form behind a QR code */
.steps { display: flex; gap: 4px; margin-bottom: 1.4rem; }
.steps span { flex: 1; height: 3px; border-radius: 2px; background: var(--rule);
              transition: background .3s ease; }
.steps span.done { background: var(--seal); }
.step { display: none; }
.step.active { display: block; }
.nojs .step { display: block; }          /* no JS: every step at once, still usable */
.nojs .steps, .nojs .navbtns { display: none; }
.step h2 { margin-bottom: .3rem; }

.navbtns { display: flex; gap: .6rem; margin-top: 1.5rem; }
.navbtns button { flex: 1; padding: .8rem 1rem; }

.choice {
  display: block; border: 1px solid var(--rule); border-radius: 11px;
  padding: .85rem .95rem; margin-bottom: .55rem; cursor: pointer;
  font-weight: 400; background: var(--paper);
  transition: border-color .16s ease, background .16s ease;
}
.choice:hover { border-color: color-mix(in srgb, var(--seal) 40%, var(--rule)); }
.choice:has(input:checked) {
  border-color: var(--seal); background: color-mix(in srgb, var(--seal) 6%, var(--card));
}
.choice:has(input:focus-visible) { box-shadow: 0 0 0 3px color-mix(in srgb, var(--seal) 14%, transparent); }
.choice input { margin-right: .55rem; accent-color: var(--seal); }
.choice .t { font-weight: 600; }
.choice .d { color: var(--muted); font-size: .88rem; }
.choice.disabled { opacity: .45; cursor: not-allowed; }
.choice.disabled:hover { border-color: var(--rule); }

.summary div { display: flex; justify-content: space-between; gap: 1rem;
               padding: .4rem 0; font-size: .92rem; border-bottom: 1px solid var(--rule); }
.summary div:last-child { border-bottom: 0; }
.summary .k { color: var(--muted); }

/* Language switcher. Quiet by default; the current language is the seal
   green so the row reads as a choice already made, not a row of buttons. */
.langbar { display: flex; gap: .75rem; justify-content: center;
           margin: 2rem auto 1rem; font-family: var(--data); font-size: .75rem; }
.langbar a { color: var(--muted); text-decoration: none; letter-spacing: .06em;
             opacity: .72; }
.langbar a:hover { opacity: 1; text-decoration: underline; }
.langbar a[aria-current] { color: var(--seal); opacity: 1; font-weight: 500; }

/* Visually hidden but reachable by a screen reader, used by the star legend
   and per-star labels below: moved off screen rather than `display:none`,
   which would remove it from the accessibility tree too. */
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0;
  margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0);
  white-space: nowrap; border: 0; }

/* ─── the end-of-treatment stars ──────────────────────────────────────────
   Green, not amber. `--dose` means "not given yet" and nothing else; a
   yellow star here would be the second meaning that makes the first one
   unreadable. Radios are real and visible to a screen reader, moved off
   screen rather than `display:none`, and the labels are what you see.

   The radios are emitted in READING order, 1 first, because a radio group
   is one tab stop and its arrow keys walk tree order -- painting 1..5 out
   of a 5..1 DOM made the right arrow move left and lower the rating.

   Lighting "every star up to the checked one" therefore has to reach
   BACKWARD, which `~` never does. `:has()` does it from the fieldset:
   light everything while something is checked, then put back the stars
   ABOVE it -- `input:checked ~ input + label` is "a label whose own radio
   comes after the checked one", which never touches the checked star
   itself. Hover previews the same shape and has to win over the checked
   state in both directions, so its rules carry the higher specificity
   deliberately: hovering 2 with 5 checked must darken 3, 4 and 5, and
   hovering 4 with 2 checked must light 3 and 4. */
.stars { display: flex; border: 0; padding: 0; margin: .6rem 0 1rem;
  gap: .15rem; }
.stars input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.stars label { font-size: 2rem; line-height: 1; cursor: pointer;
  color: var(--muted); transition: color .12s; }
.stars:has(input:checked) label { color: var(--seal); }
.stars input:checked ~ input + label { color: var(--muted); }
.stars:has(label:hover) label:not(label:hover ~ input + label) {
  color: var(--seal); }
.stars:has(label:hover) label:hover ~ input + label { color: var(--muted); }
.stars input:focus-visible + label { outline: 2px solid var(--seal);
  outline-offset: 2px; }
.rate .field { display: block; margin-bottom: 1rem; }
.rate textarea { width: 100%; margin-top: .35rem; }

/* ─── the rating, riding inside the ribbon ────────────────────────────────── */
/* `.rated` sits inside `.ribbon`, so it inherits the ribbon's grid area on
   the phone layout (`.courses td:nth-child(5)`) and needs no area of its
   own. `--data` is the mono stack already used for the ribbon's own "+N"
   overflow marker just above; `--body` is the text stack.

   The comment panel is IN FLOW, not floated over the table. It used to be
   `position: absolute`, and on the last row it was sliced mid-sentence:
   `.scroll` carries `overflow-x: auto`, which computes to `auto` on BOTH
   axes, so anything leaving its padding box is clipped -- and a panel that
   opens 1.4rem below its summary leaves it by exactly its own height. In
   flow, the row grows instead, which is what a `<details>` does everywhere
   else; the column widens while it is open, and that is the honest cost of
   never cutting a sentence in half.

   It wraps onto its own line inside the ribbon rather than widening the
   column: sized by the cell, a comment became a 143px ribbon of text, and
   sized by its own content it pushed the last column off the screen. The
   negative right margin lets it bleed over the quiet column beside it,
   which it paints over on its own opaque background -- position, not
   layout, so no other column moves. `position: relative` is there only to
   give it a stacking order: in flow and unpositioned, the pills in the
   column beside it painted straight through the sentence. Both switches are behind
   `:has(.rated[open])` so a closed ribbon is byte-for-byte what it was,
   and `align-items` flips to the top or the pips would float to the middle
   of the expanded row and stop reading like a strip. */
.rated { font-family: var(--data); font-size: .72rem; color: var(--seal);
  margin-left: .45rem; white-space: nowrap; }
.rated summary { cursor: pointer; list-style: none; }
.rated summary::-webkit-details-marker { display: none; }
.ribbon:has(.rated[open]) { flex-wrap: wrap; align-items: flex-start; }
.rated[open] { flex-basis: 100%; }
.rated[open] p { position: relative; z-index: 2;
  margin: .4rem -9rem .1rem 0; padding: .55rem .7rem;
  background: var(--paper); border: 1px solid var(--seal); border-radius: 6px;
  font-family: var(--body); font-size: .78rem; line-height: 1.45;
  white-space: normal; color: var(--ink); }

/* Once the row is a stacked card there is no neighbouring column to bleed
   over and no width to spare, so the panel is simply as wide as the ribbon
   area it sits in. This has to come AFTER the rule above, not up in the
   phone block with the rest of the card layout: same specificity, so the
   later one wins, and up there the bleed survived and pushed 144px of
   panel off the side of a 390px screen. */
@media (max-width: 800px) {
  .rated[open] p { margin-right: 0; }
}
