/* ========================================================================
   GagaMuller Learning — 04-components.css
   Cards, buttons, chips, tables, inputs, progress bars, avatars and the sign-in gate.

   Loaded by index.html as sheet 4 of 7. The order is the cascade: a
   later sheet may override an earlier one, so it must not be changed.
   ======================================================================== */
  /* ---------- completion detail ---------- */
  .cdt-r{display:flex; gap:14px; align-items:flex-start; padding:9px 0;
    border-bottom:1px solid var(--line);}
  .cdt-r:last-of-type{border-bottom:0;}
  .cdt-r > span{flex:0 0 168px; color:var(--muted); font-size:12.5px; padding-top:1px;}
  .cdt-r > div{flex:1; min-width:0;}
  .cdt-r b{font-size:13.5px; font-weight:640; word-break:break-word;}
  @media (max-width:560px){ .cdt-r{flex-direction:column; gap:2px;}
                            .cdt-r > span{flex:none;} }

  /* ---------- one person's progress, in full (admin-user page) ----------
     A read-only drill-down opened by View from the dashboard. The header is a
     banner carrying the person and the one-line verdict; below it the same card
     and table vocabulary the rest of the admin centre uses, so nothing new has
     to be learned to read it. */
  .ud-head{background:linear-gradient(180deg, var(--raised), var(--surface));
    border:1px solid var(--line); border-radius:var(--radius); padding:20px 22px; margin-bottom:4px;}
  .ud-id{display:flex; gap:16px; align-items:flex-start; flex-wrap:wrap;}
  /* The person's metadata, right-aligned columns that wrap under the name on a
     narrow window rather than crushing it. */
  .ud-meta{display:flex; gap:22px; flex-wrap:wrap; margin-left:auto;}
  .ud-meta > div{display:flex; flex-direction:column; gap:2px; min-width:0;}
  .ud-meta span{font:600 10.5px var(--mono); letter-spacing:.4px; color:var(--muted); text-transform:uppercase;}
  .ud-meta b{font-size:13px; font-weight:650;}
  @media (max-width:720px){ .ud-meta{margin-left:0; width:100%; gap:16px 22px;} }

  /* The round status icon on the compliance card and the empty-paths note. */
  .ud-ic{width:36px; height:36px; border-radius:11px; display:grid; place-items:center; flex:none;}
  .ud-ic svg{width:19px; height:19px; stroke-width:2;}
  .ud-ic.ok{background:var(--ok-soft); color:var(--ok);}
  .ud-ic.bad{background:var(--bad-soft); color:var(--bad);}
  .ud-ic.warn{background:var(--warn-soft); color:var(--warn);}
  .ud-ic.info{background:var(--info-soft); color:var(--info);}
  /* A tinted left edge on the compliance card, matching its verdict, so the
     eye reads "good / needs attention" before a word. */
  .ud-compliance{border-left-width:4px;}
  .ud-compliance.ok{border-left-color:var(--ok);}
  .ud-compliance.bad{border-left-color:var(--bad);}
  .ud-compliance.warn{border-left-color:var(--warn);}

  /* The outstanding-course chips: a coloured dot, the title, its status. They
     wrap freely and never stretch a row. */
  .ud-chips{display:flex; flex-wrap:wrap; gap:7px;}
  .ud-chip{display:inline-flex; align-items:center; gap:7px; padding:5px 11px; border-radius:999px;
    background:var(--fill); border:1px solid var(--line); font:600 12px var(--display); color:var(--ink);}
  .ud-chip .d{width:8px; height:8px; border-radius:50%; flex:none;}
  .ud-chip.bad{background:var(--bad-soft); border-color:transparent; color:var(--bad);}

  .ud-path{display:flex; flex-direction:column;}

  /* ---------- cards ---------- */
  .card{background:var(--surface); border:1px solid var(--line); border-radius:var(--radius);}
  .card.pad{padding:20px;}
  /* Rows size to their own content: a stretched row let a long register drag
     the short card beside it into a tall empty box. Three places had patched
     this onto themselves; here it is once. Stretching is now the exception. */
  .grid{display:grid; gap:16px; align-items:start;}
  .g2{grid-template-columns:repeat(auto-fit,minmax(330px,1fr));}
  .g3{grid-template-columns:repeat(auto-fit,minmax(270px,1fr));}
  .g4{grid-template-columns:repeat(auto-fit,minmax(205px,1fr));}
  .kpi{padding:17px 19px; position:relative; overflow:hidden;}
  .kpi .k-l{font:700 10.5px var(--mono); letter-spacing:0.45px; color:var(--muted);}
  .kpi .k-v{font:800 30px var(--display); letter-spacing:-1px; margin:7px 0 3px; font-variant-numeric:tabular-nums;}
  .kpi .k-s{font-size:13px; color:var(--muted);}
  .kpi .k-i{position:absolute; top:15px; right:15px; width:31px; height:31px; border-radius:10px; display:grid; place-items:center;}
  .kpi .k-i svg{width:16px; height:16px; stroke-width:2;}

  /* ---------- buttons / chips ---------- */
  .btn{display:inline-flex; align-items:center; justify-content:center; gap:8px;
    padding:9px 16px; border-radius:999px; font:600 13px var(--display);
    background:var(--raised); border:1px solid var(--line); color:var(--ink);
    transition:border-color .15s ease, background .15s ease, filter .15s ease;}
  .btn:hover{border-color:color-mix(in srgb, var(--brand) 45%, var(--line)); background:var(--brand-soft);}
  .btn:focus-visible{outline:2px solid var(--brand); outline-offset:2px;}
  .btn.p{background:var(--brand-grad); border-color:transparent; color:#fff; box-shadow:var(--brand-shadow);}
  .btn.p:hover{filter:brightness(1.1); background:var(--brand-grad);}
  .btn.g{background:var(--ok-grad); border-color:transparent; color:var(--ink-on-strong);}
  .btn.d{background:var(--bad-soft); border-color:color-mix(in srgb, var(--bad) 40%, transparent); color:var(--bad);}
  .btn.sm{padding:6px 12px; font-size:12.5px;}
  .btn.xs{padding:4px 10px; font-size:11.5px; gap:5px;}
  .btn:disabled{opacity:.45; cursor:not-allowed;}
  .btn svg{width:15px; height:15px; stroke-width:2;}
  .btn.xs svg{width:13px; height:13px;}
  .icb{width:34px; height:34px; border-radius:50%; display:grid; place-items:center;
    border:1px solid var(--line); background:var(--raised); transition:border-color .15s ease, background .15s ease;}
  .icb:hover{border-color:color-mix(in srgb, var(--brand) 45%, var(--line)); background:var(--brand-soft);}
  .icb svg{width:16px; height:16px; stroke-width:1.9;}

  /* A badge is a state, not text somebody wrote - so it is not selectable.
     Dragging across a name to copy it was picking up "Manager" and "Learner"
     with it, which is never what anybody wanted on the clipboard. The name,
     the address and every other cell stay perfectly selectable. */
  .tag{display:inline-flex; align-items:center; gap:6px; padding:3px 10px; border-radius:999px;
    font:700 11px var(--display); white-space:nowrap; letter-spacing:.1px;
    -webkit-user-select:none; user-select:none;}
  .pill{-webkit-user-select:none; user-select:none;}
  .tag.ok{background:var(--ok-soft); color:var(--ok);}
  .tag.wa{background:var(--warn-soft); color:var(--warn);}
  .tag.ba{background:var(--bad-soft); color:var(--bad);}
  .tag.in{background:var(--info-soft); color:var(--info);}
  .tag.vi{background:var(--brand-soft); color:var(--brand);}
  /* Course Curator. Its own colour rather than a shared one: it is the badge
     that answers "why can she publish and he cannot", so it has to be findable
     at a glance down a column of forty people. */
  .tag.pk{background:var(--pink-soft); color:var(--pink);}
  /* Hybrid Admin: several departments at once, and the badge says so. A
     gradient rather than a flat tone, because the tier it sits beside (Admin)
     already owns the violet and the difference between them is exactly what
     this badge has to carry. The dot is given its own solid colour so the tag
     still reads at a glance where the gradient is palest. */
  /* Hybrid Admin: several departments at once, and the badge says so.

     NO BORDER. It had one, and that was the whole fault: every other tag is
     borderless, so a 1px border made this one two pixels taller and wider than
     the badges beside it, sat it off the shared baseline in a row of them, and
     put a hard line where the gradient met the pill's rounded corner - which
     reads as a rendering artefact rather than a design. Matching the others
     exactly and letting only the FILL differ is what makes it look deliberate.

     background-clip:padding-box keeps the gradient inside the radius on the
     browsers that otherwise bleed it a hair past the corner. */
  .tag.mx{background:linear-gradient(100deg,
      var(--brand-soft) 0%, var(--pink-soft) 50%, var(--warn-soft) 100%);
    background-clip:padding-box; color:var(--brand);}
  /* A solid dot, not a second gradient: at 7px a gradient is mud, and the dot
     is what carries the tag where the fill is palest. */
  .tag.mx .dot{background:var(--brand);}
  .tag.ne{background:var(--fill); color:var(--muted); border:1px solid var(--line);}
  /* The role tier, which is the one badge that appears two and three at a time
     and sits inline beside a name. At the status-tag size a row of three was
     heavier than the name it described and pushed the table row taller than
     its own line height, so it gets its own step down: 10px type, tighter
     padding, a 5px dot, and middle alignment so it sits on the text baseline
     rather than dragging the line box around. Status tags elsewhere - Compliant,
     Past due, In progress - keep the larger size: they appear one at a time in
     a column of their own and are the point of that column. */
  .tag.role{gap:5px; padding:1.5px 8px; font-size:10px; letter-spacing:.3px;
    line-height:15px; vertical-align:middle;}
  .tag.role .dot{width:5px; height:5px;}
  /* Two or three in a row need to breathe without the join between them being
     a full word space. */
  .tag.role + .tag.role{margin-left:1px;}
  .dot{width:6px; height:6px; border-radius:50%; background:currentColor; flex:none;}
  .badge-src{display:inline-flex; align-items:center; gap:5px; font:600 10.5px var(--mono);
    padding:2px 8px; border-radius:7px; background:var(--brand-soft); color:var(--brand); letter-spacing:.2px;}

  .seg{display:inline-flex; background:var(--raised); border:1px solid var(--line); border-radius:999px; padding:3px; gap:2px;}
  .seg button{padding:6px 14px; border-radius:999px; font:600 12.5px var(--display); color:var(--muted); transition:.15s;}
  .seg button.on{background:var(--brand-grad); color:#fff; box-shadow:var(--brand-shadow);}

  /* ---------- table ---------- */
  /* Long lists scroll rather than growing without limit. The header row is
     already position:sticky, so it stays put while the body scrolls - which is
     also what keeps the column headings lined up with their cells. */
  .tbl-wrap{overflow:auto; max-height:58vh; border-radius:var(--radius); border:1px solid var(--line);
    background:var(--surface); overscroll-behavior:contain;}
  .tbl-wrap.short{max-height:320px;}
  .tbl-wrap.free{max-height:none;}
  /* A scrollbar that belongs to the surface rather than the operating system. */
  .tbl-wrap::-webkit-scrollbar, .scroll-y::-webkit-scrollbar{width:10px; height:10px;}
  .tbl-wrap::-webkit-scrollbar-thumb, .scroll-y::-webkit-scrollbar-thumb{
    background:var(--fill-2); border-radius:99px; border:2px solid var(--surface);}
  .tbl-wrap::-webkit-scrollbar-thumb:hover, .scroll-y::-webkit-scrollbar-thumb:hover{background:var(--brand-soft);}
  .tbl-wrap::-webkit-scrollbar-track, .scroll-y::-webkit-scrollbar-track{background:transparent;}
  /* Same treatment for lists that are not tables. */
  /* A short bounded list - deadlines, recent audit entries, a course picker.
     The cap earns its place here: these sit between other controls and would
     push them off the screen. No overscroll-behavior:contain, though. Contain
     stops the wheel chaining to the page when the list reaches its end, so the
     page freezes under the pointer until you move it somewhere else. It is the
     right rule for a modal body and for the course player, which cover the
     page; it is the wrong rule for a panel sitting IN the page. */
  .scroll-y{max-height:340px; overflow-y:auto; padding-right:4px;}
  /* The card grids that grow with the number of courses, paths or reports are
     NOT scroll regions any more; the class is kept only so the call sites
     resolve. Capped with overflow-y:auto they became scroll containers inside a
     page that already scrolls, and the wheel went to the innermost scroller - so
     the pointer over a card nudged the little box a few pixels and the page
     underneath appeared frozen. A cap on a list inside a scrolling page buys
     little and costs that; the grids run to their natural height now. */
  .scroll-tall{padding:2px 4px 2px 2px;}
  /* Headers are nowrap and body cells were not, with no min-width - so a narrow
     column squeezed cells while headers held, and nothing lined up. Give the
     table a floor and let the wrapper scroll. */
  table{width:100%; min-width:860px; border-collapse:collapse; font-size:13.5px;}
  .tbl-wrap.narrow table{min-width:600px;}
  /* A dense table that must fit its container WITHOUT a horizontal scrollbar -
     drop the 860px floor so columns share the width, and tighten padding and
     type so nine columns settle in the dialog. */
  .tbl-wrap.fit{overflow-x:hidden;}
  .tbl-wrap.fit table{min-width:0; table-layout:auto; font-size:12.5px;}
  .tbl-wrap.fit thead th{padding:9px 9px;}
  /* TOP, not middle: these cells stack a primary line over a muted detail, and
     middle-align floated a one-line cell to the centre of a two-line neighbour,
     so the row read as misaligned. Top-aligned, primary lines share a baseline. */
  .tbl-wrap.fit tbody td{padding:9px 9px; vertical-align:top;}
  /* One size for every secondary line, label or code: the .mini rule outranks
     .mono, replacing the stray inline 11px overrides that sat beside 10.5px. */
  .tbl-wrap.fit td .mini{font-size:10.5px;}
  /* An icon on a mini line is the size of the line. The set draws a bare
     <svg viewBox="0 0 24 24"> and every other place sizes it; inside .mini
     nothing did, so it fell back to 300x150 and dwarfed the text beside it. */
  .mini svg{width:1em; height:1em; flex:none; vertical-align:-0.13em; stroke-width:2;}
  thead th{text-align:left; padding:11px 15px; font:700 10.5px var(--mono); letter-spacing:0.45px;
    color:var(--muted); background:var(--raised); border-bottom:1px solid var(--line);
    position:sticky; top:0; z-index:2; white-space:nowrap;}
  tbody td{padding:11px 15px; border-bottom:1px solid var(--hair); vertical-align:middle;}
  tbody tr:last-child td{border-bottom:0;}
  tbody tr:hover{background:var(--fill);}
  td.num,th.num{text-align:right; font-variant-numeric:tabular-nums;}

  /* ---------- inputs ---------- */
  .fld{display:flex; flex-direction:column; gap:6px;}
  .fld label{font:600 12px var(--display); color:var(--muted);}
  /* A bare element selector, deliberately.

     The original rule listed input[type=text] and friends, which does NOT match
     <input id="x"> with no type attribute - so those fell through to the browser
     default of a white box while inheriting the near-white body colour, giving
     white text on white.

     The obvious fix, input:not([type=checkbox]):not([type=radio])..., matches
     everything but carries specificity (0,4,1) - which silently outranks
     .srch input (0,1,1) and wiped out its padding-left, dropping the search text
     underneath the magnifying glass. So: keep the base at (0,0,1) and undo it
     for the few types that are not text fields. Anything more specific in the
     app then still wins, which is what a base rule is for. */
  input,select,textarea{
    background:var(--raised); color:var(--ink); border:1px solid var(--line); border-radius:var(--radius-s);
    padding:9px 12px; font:500 13.5px var(--display); width:100%;
    transition:border-color .15s ease, box-shadow .15s ease;}
  input[type=checkbox],input[type=radio],input[type=range],input[type=file],input[type=color]{
    background:none; border:0; border-radius:0; padding:0; width:auto;}
  /* Lets the native date picker, spinners and autofill follow the theme rather
     than always rendering light. */
  input,select,textarea{color-scheme:dark;}
  body.light input,body.light select,body.light textarea{color-scheme:light;}
  input::placeholder,textarea::placeholder{color:var(--muted); opacity:.9;}
  textarea{font-family:var(--display); line-height:1.55; resize:vertical;}
  input:focus,select:focus,textarea:focus{outline:0; border-color:var(--brand); box-shadow:0 0 0 3px var(--brand-soft);}
  .srch{position:relative;}
  .srch svg{position:absolute; left:11px; top:50%; transform:translateY(-50%); width:15px; height:15px; color:var(--muted); stroke-width:2;}
  .srch input{padding-left:34px;}
  /* Form field label. Matches the small section labels used
     elsewhere in the admin modals. */
  .fl{display:block; font:700 10.5px var(--mono); letter-spacing:0.45px;
    color:var(--muted); margin-bottom:6px;}
  .chk{display:flex; align-items:center; gap:10px; padding:9px 12px; border-radius:var(--radius-s);
    border:1px solid var(--line); background:var(--raised); cursor:pointer; transition:.14s; font-size:13.5px;}
  .chk:hover{border-color:color-mix(in srgb, var(--brand) 40%, var(--line));}
  .chk.on{border-color:var(--brand); background:var(--brand-soft);}
  .chk input{accent-color:var(--brand-solid); width:15px; height:15px; margin:0;}

  /* ---------- course-picker group headings ----------
     The small headings groupCoursesForPicker() draws over a checkbox list when
     courses are assigned to a path or a person, so the picker groups them by
     department and code family the same way the library does. Two tiers: a
     department heading (pick-grp-dept) stronger than a family heading, so the
     hierarchy reads at a glance. The first one needs no top margin - it opens
     the list - so a sibling combinator spaces only the ones that follow a row. */
  .pick-grp{display:flex; align-items:baseline; gap:9px; padding:2px 2px 1px;
    font:700 11px var(--mono); letter-spacing:.7px; color:var(--brand); text-transform:uppercase;}
  .pick-grp .mini{letter-spacing:.6px; opacity:.7; text-transform:none;}
  .chk + .pick-grp, .label + .pick-grp{margin-top:12px;}
  /* A department heading sits above its families: a touch larger, inked rather
     than brand-coloured, with a hairline under it so it reads as the tier that
     contains the families below. */
  .pick-grp-dept{font-size:12px; color:var(--ink); letter-spacing:.5px;
    padding-bottom:6px; margin-bottom:3px; border-bottom:1px solid var(--hair);}
  .chk + .pick-grp-dept{margin-top:16px;}

  /* ---------- progress ---------- */
  .bar{height:7px; border-radius:999px; background:var(--fill-2); overflow:hidden;}
  /* In the top-aligned dense tables, drop the 7px bar onto the line of a status
     tag beside it, and give its caption the usual stacked gap. */
  .tbl-wrap.fit td > .bar{margin-top:5px;}
  .tbl-wrap.fit td > .bar + .mini{margin-top:5px;}
  .bar i{display:block; height:100%; border-radius:999px; background:var(--brand-grad);
    transition:width .7s cubic-bezier(.22,1,.36,1);}
  .bar i.ok{background:var(--ok-grad);}
  .bar i.wa{background:var(--warn-grad);}
  .ring{width:52px; height:52px; flex:none; position:relative;}
  .ring svg{transform:rotate(-90deg); width:100%; height:100%;}
  .ring circle{fill:none; stroke-width:5;}
  .ring .bgc{stroke:var(--fill-2);}
  .ring .fgc{stroke:var(--brand-solid); stroke-linecap:round;
    transition:stroke-dashoffset .8s cubic-bezier(.22,1,.36,1);}
  /* "100%" is four glyphs in a 42px inner circle, so it collided with the
     stroke. Smaller, tighter tracking, and the % sign reduced further - the
     number is the information, the sign is just a unit. */
  /* The label is a single span, so this box has exactly one child to centre.
     Centring two children was the cause of both earlier ring faults: as a grid
     they took a row each and stacked, and as a flex box with baseline alignment
     they ended up in the top-left corner. */
  .ring b{position:absolute; inset:0; display:grid; place-items:center;
    font:700 10px var(--mono); letter-spacing:-.3px; font-variant-numeric:tabular-nums;
    line-height:1;}
  .ring b .rv{display:inline-block; white-space:nowrap;}
  .ring b i{font-style:normal; font-size:.78em; opacity:.7; margin-left:.5px;}
  .ring.sm b{font-size:9px;}
  .stack{display:flex; height:22px; border-radius:8px; overflow:hidden; background:var(--fill-2); border:1px solid var(--line);}
  .stack i{display:block; height:100%; transition:width .6s;}
  .legend{display:flex; gap:14px; flex-wrap:wrap; font-size:12.5px; color:var(--muted);}
  .legend span{display:inline-flex; align-items:center; gap:6px;}
  .legend i{width:9px; height:9px; border-radius:3px; display:block;}

  /* ---------- avatar ---------- */
  .av{width:32px; height:32px; border-radius:50%; flex:none; display:grid; place-items:center;
    font:700 11px var(--mono); color:#fff; letter-spacing:.3px; overflow:hidden;}
  .av img{width:100%; height:100%; object-fit:cover;}
  .av.lg{width:46px; height:46px; font-size:13px;}
  .av.sm{width:25px; height:25px; font-size:9.5px;}

  /* ---------- sign-in gate ---------- */
  .gate{min-height:100vh; display:grid; place-items:center; padding:28px;}
  .gate-in{max-width:520px; text-align:center; animation:pageIn .5s cubic-bezier(.4,0,.2,1) both;}
  .gate-in .brand-title{text-align:center; font-size:40px;}
  @media (max-width:560px){ .gate-in .brand-title{font-size:29px;} }
  /* On a gate screen the brand line is the heading, so what happened is a
     level below it rather than competing with it. */
  .gate-in .gate-title{font:700 19px var(--display); margin:18px 0 8px;}
  .gate-in h1{margin-bottom:10px;}
  .gate-in p{color:var(--muted); font-size:14.5px;}
  .ag-hint{margin-top:16px; font-size:12.5px; color:var(--muted); background:var(--raised);
    border:1px solid var(--line); border-radius:12px; padding:12px 15px; text-align:left;}
  .spin{width:26px; height:26px; margin:18px auto 0; border-radius:50%;
    border:2.5px solid var(--fill-2); border-top-color:var(--brand); animation:sp .8s linear infinite;}
  @keyframes sp{to{transform:rotate(360deg);}}
  .persona{display:flex; align-items:center; gap:11px; width:100%; padding:9px 11px; border-radius:12px;
    transition:.14s; text-align:left; border:1px solid transparent;}
  .persona:hover{background:var(--fill); border-color:var(--line);}

  /* ---- dashboard section headings ------------------------------------
     A band naming what the next few cards are about, so the page reads as
     four or five subjects rather than one column of cards. */
  .sec-h{margin:26px 0 13px; padding-bottom:9px; border-bottom:1px solid var(--line);}
  .sec-h:first-child{margin-top:0;}
  .sec-h h2{font:700 15px var(--display); letter-spacing:.2px; margin:0;}
  .sec-h p{font-size:12.5px; color:var(--muted); margin:3px 0 0;}

  /* ---- the storage health light --------------------------------------
     A strip, not a card: it says nothing at all on the days it is working,
     and at card weight it took the top of the page from the commitments. */
  .savebar{display:flex; align-items:center; gap:10px; flex-wrap:wrap;
    padding:9px 14px; margin-bottom:6px; border-radius:999px;
    border:1px solid var(--line); background:var(--raised); font-size:13px;}
  .savebar svg{width:15px; height:15px; flex:none; stroke-width:2;}
  .savebar b{font-size:13px;}
  .savebar .mini{flex:1; min-width:180px;}
  .savebar.ok{color:var(--ok); border-color:rgba(12,245,113,.35);}
  .savebar.bad{color:var(--bad); border-color:rgba(224,106,138,.5); background:var(--bad-soft);}
  .savebar.ok b, .savebar.bad b{color:var(--ink);}

  /* ---- a deadline panel ----------------------------------------------
     The most consequential thing on the dashboard - the number somebody will
     be asked about - so it gets the size it earns. --dl is the state's colour,
     set inline, and the rail, the figures and the bar all read it so they
     cannot disagree with each other. */
  .dl-wrap{display:grid; grid-template-columns:repeat(auto-fit,minmax(430px,1fr));
    gap:14px; margin-bottom:18px;}
  .dl-hero{--dl:var(--brand); padding:20px 22px 18px; position:relative; overflow:hidden;
    /* The fallback first, then the tinted one. A browser without color-mix
       drops the second declaration and keeps the ordinary card border. */
    border-color:var(--line);
    border-color:color-mix(in srgb, var(--dl) 38%, transparent);}
  .dl-hero::before{content:''; position:absolute; inset:0 auto 0 0; width:4px; background:var(--dl);}
  .dl-top{display:flex; gap:19px; align-items:flex-start; flex-wrap:wrap;}
  .dl-name{flex:1; min-width:210px;}
  .dl-name h3{font-size:18.5px; margin:0; letter-spacing:-.2px;}
  .dl-name .mini{margin:5px 0 0;}
  .dl-verdict{font-size:13.5px; color:var(--muted); margin:9px 0 0;}
  .dl-act{display:flex; flex-direction:column; gap:3px; flex:none; min-width:112px;}
  .dl-act .btn{margin-top:9px; align-self:flex-start;}

  /* The countdown, as its own chip - it is the fact people scan for and it
     was buried mid-sentence. */
  .dl-clock{display:inline-flex; align-items:center; gap:5px; flex:none;
    padding:4px 11px; border-radius:999px; font:700 11.5px var(--mono);
    letter-spacing:.3px; background:var(--fill); color:var(--muted);}
  .dl-clock svg{width:13px; height:13px; stroke-width:2;}
  .dl-clock.warn{background:var(--warn-soft); color:var(--warn);}
  .dl-clock.bad{background:var(--bad-soft); color:var(--bad);}

  /* The three numbers the ring is made of, side by side, because comparing
     them to each other is the whole reading. */
  .dl-figs{display:grid; grid-template-columns:repeat(auto-fit,minmax(88px,1fr));
    gap:10px; margin:17px 0 11px;}
  .dl-fig{padding:9px 12px; border-radius:12px; background:var(--fill-2);}
  .dl-fig b{display:block; font:800 21px var(--display); letter-spacing:-.5px;
    font-variant-numeric:tabular-nums; line-height:1.1;}
  .dl-fig span{display:block; font-size:11.5px; color:var(--muted); margin-top:2px;}

  .dl-row{--dl:var(--line); gap:10px; padding:10px 12px; border:1px solid var(--line);
    border-left:3px solid var(--dl); border-radius:var(--radius-s);}

  /* ---- the authoring leaderboard --------------------------------------
     A bar each rather than a column of integers: "who has written the most"
     is a comparison, and a bar answers a comparison at a glance. */
  .auth-row{display:flex; gap:11px; align-items:flex-start;}
  .auth-rank{flex:none; width:22px; text-align:right; font:800 14px var(--display);
    color:var(--muted); font-variant-numeric:tabular-nums; padding-top:1px;}
  .auth-n{flex:none; font:800 15px var(--display); font-variant-numeric:tabular-nums;}
  .auth-gone{flex:none; width:30px; height:30px; border-radius:999px; display:grid;
    place-items:center; background:var(--fill-2); color:var(--muted);}
  .auth-gone svg{width:15px; height:15px; stroke-width:2;}
  /* ---- the walkthrough ---------------------------------------------------
     A ring on the real interface, not a screenshot of it, and NOT a dimmed
     page: greying everything out hides the application you are being shown
     round. What stops the wrong thing being pressed is four transparent
     panels, not a scrim - see tourBlockers(). */
  #tourRoot{position:fixed; inset:0; z-index:400; pointer-events:none;}

  /* The panels. Transparent and invisible; their only job is to swallow a
     press. The gap between them is the step's target, so the real control
     underneath stays live and the tour can require it to be used. */
  .tour-block{position:fixed; pointer-events:auto; background:transparent;}

  /* THE RING. Two shadows breathing in and out: a tight one that draws the
     edge and a wide one that glows. Scale is deliberately not animated - the
     ring sits exactly on the element's box, and a pulsing size would make it
     drift off what it is pointing at. */
  .tour-ring{position:fixed; border-radius:13px; pointer-events:none;
    border:2px solid var(--brand);
    animation:tourPulse 1.6s cubic-bezier(.4,0,.6,1) infinite;
    transition:top .34s cubic-bezier(.22,1,.36,1), left .34s cubic-bezier(.22,1,.36,1),
               width .34s cubic-bezier(.22,1,.36,1), height .34s cubic-bezier(.22,1,.36,1),
               opacity .2s ease;}
  @keyframes tourPulse{
    0%,100%{box-shadow:0 0 0 0 var(--brand-glow), 0 0 14px 2px var(--brand-glow);}
    50%    {box-shadow:0 0 0 9px rgba(114,21,244,0), 0 0 26px 7px var(--brand-glow);}
  }

  /* THE CARD, built once and moved. A card rebuilt each step has no previous
     position to transition from, so it would jump between targets instead of
     travelling to the next thing it is talking about. */
  .tour-card{position:fixed; width:min(370px, calc(100vw - 24px)); pointer-events:auto;
    background:var(--surface); border:1px solid var(--brand);
    border-radius:var(--radius); box-shadow:var(--shadow), 0 0 30px var(--brand-glow);
    padding:16px 18px 14px;
    transition:top .38s cubic-bezier(.22,1,.36,1), left .38s cubic-bezier(.22,1,.36,1);}
  .tour-card.centre{top:50%; left:50%; transform:translate(-50%,-50%);}

  /* The contents fade and rise on each step, which is what makes a change of
     card read as a new thing said rather than as a flicker. */
  .tour-body{opacity:0; transform:translateY(6px);
    transition:opacity .26s ease, transform .26s cubic-bezier(.22,1,.36,1);}
  .tour-body.in{opacity:1; transform:none;}

  .tour-card .tour-step{font:700 10.5px var(--mono); letter-spacing:.5px; color:var(--brand);}
  .tour-card h3{font-size:16px; margin:7px 0 6px;}
  .tour-card p{font-size:13.5px; color:var(--muted); margin:0; line-height:1.6;}
  /* What to press. Its own line, in the brand colour, because on an act step
     it is the instruction and everything above it is context. */
  .tour-do{display:flex; align-items:center; gap:7px; margin-top:11px !important;
    padding:8px 11px; border-radius:10px; background:var(--brand-soft);
    color:var(--brand) !important; font-weight:700; font-size:12.5px !important;}
  .tour-do-i{width:14px; height:14px; flex:none; stroke-width:2.2;}
  .tour-foot{display:flex; align-items:center; gap:8px; margin-top:14px;}

  /* One dot per step: how far in you are, and how much is left. */
  .tour-dots{display:flex; gap:4px; flex:1; justify-content:center; flex-wrap:wrap;}
  .tour-dots i{width:5px; height:5px; border-radius:999px; background:var(--fill-2);
    transition:background .2s ease, transform .2s ease;}
  .tour-dots i.done{background:var(--brand-soft);}
  .tour-dots i.on{background:var(--brand); transform:scale(1.5);}

  /* The transition is for travelling between steps. While the page is merely
     scrolling under the ring it has to be off, or the ring lags a frame behind
     what it is pointing at - which reads as the bubble jerking. */
  .tour-ring.snap, .tour-card.snap{transition:none;}

  /* The leaving copy. On dismiss the real #tourRoot is removed at once - state
     has to be clean for whatever comes next - and this inert clone of the card
     and ring is faded where it sat, so the box ALONE shuts off smoothly while
     the page behind it stays still. It carries no blockers and no handlers, so
     nothing in it can be pressed; it exists only to be watched leaving. */
  .tour-ghost{position:fixed; inset:0; z-index:400; pointer-events:none;
    opacity:1; transition:opacity .22s ease;}
  .tour-ghost.leaving{opacity:0;}

  @media (prefers-reduced-motion: reduce){
    .tour-ring{animation:none; transition:opacity .2s ease;}
    .tour-card, .tour-body{transition:none;}
  }

  /* Standing aside for a dialog. The card moves to a corner and stops
     competing with what it just told you to open; the panels are already off,
     so the dialog underneath is completely usable. */
  #tourRoot.waiting .tour-card{top:auto !important; bottom:20px;
    left:50% !important; transform:translateX(-50%); width:min(330px, calc(100vw - 24px));}
  #tourRoot.waiting .tour-ring{opacity:0;}

