/* ============================================================
   dl.myl.gs · portal components
   Built on the tokens in colors_and_type.css + portal-additions.css.
   No new colors: every value here resolves to an existing token.
   ============================================================ */

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-default);
  line-height: var(--lh-default);
  color: var(--fg-secondary);
  background: var(--bg-subtle);
}

a { color: var(--fg-link); text-decoration: none; }
a:hover { color: var(--ml-blue-700); text-decoration: underline; }

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

.wrap { max-width: var(--content-max); margin: 0 auto; padding: 0 var(--space-6); }
.reading { max-width: var(--reading-max); margin: 0 auto; }
.stack > * + * { margin-top: var(--space-6); }
.row { display: flex; align-items: center; gap: var(--space-4); }
.row-between { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); }
.grow { flex: 1 1 auto; min-width: 0; }
.muted { color: var(--fg-tertiary); }
.nowrap { white-space: nowrap; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The browser hides [hidden] with `display: none` from its own stylesheet, and
   ANY author rule setting display beats it. So `.thing { display: flex }` plus
   `el.hidden = true` leaves the element on screen, with no error anywhere and
   nothing wrong in the JS - which is exactly how the language picker shipped
   its code/name pair permanently visible. Every flex or grid component that is
   ever toggled would otherwise need to remember this individually.
   !important is warranted: hidden means hidden, and there is no case in this
   app where a component's display should win over it. */
[hidden] { display: none !important; }

main { padding: var(--space-9) 0 64px; }

/* ---------------------------------------------------------------- topbar */

.topbar {
  height: var(--topbar-h);
  background: var(--bg-canvas);
  border-bottom: 1px solid var(--border-subtle);
  display: flex; align-items: center;
}
.topbar .wrap { display: flex; align-items: center; gap: var(--space-6); width: 100%; }
.brand { display: flex; align-items: center; gap: var(--space-3); font-weight: var(--fw-bold);
         color: var(--fg-primary); text-decoration: none; }
.brand:hover { text-decoration: none; color: var(--fg-primary); }
/* The lockup carries the word "myLanguage" as artwork, so it has to be read
   at a glance rather than recognized as a mark. 44px in a 76px bar. */
.brand img { height: 44px; display: block; }
.topbar nav { display: flex; align-items: center; gap: var(--space-5); margin-left: auto; }
.topbar nav a { color: var(--fg-tertiary); font-size: var(--fs-small); font-weight: var(--fw-semibold); }
.topbar nav a:hover, .topbar nav a[aria-current="page"] { color: var(--fg-primary); text-decoration: none; }

.orgchip {
  display: inline-flex; align-items: center; gap: var(--space-2);
  padding: 4px 12px; border-radius: var(--radius-pill);
  background: var(--gray-50); border: 1px solid var(--border-subtle);
  font-size: var(--fs-small); color: var(--fg-secondary);
}

/* ---------------------------------------------------------------- card */

.card {
  background: var(--bg-canvas);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  padding: var(--space-8);
}
.card-tight { padding: var(--space-6); }
.card > h2, .card > h1 { margin-top: 0; }
.card-head { margin-bottom: var(--space-6); }

h1 { font: var(--fw-bold) var(--fs-heading)/1.2 var(--font-sans); color: var(--fg-primary); margin: 0 0 var(--space-3); }
h2 { font: var(--fw-semibold) var(--fs-heading-sm)/1.25 var(--font-sans); color: var(--fg-primary); margin: 0 0 var(--space-4); }
h3 { font: var(--fw-bold) var(--fs-view-title)/var(--lh-view-title) var(--font-sans); color: var(--fg-primary); margin: 0 0 var(--space-3); }
p { margin: 0 0 var(--space-4); }
p:last-child { margin-bottom: 0; }

/* ---------------------------------------------------------------- buttons */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
  padding: 10px var(--space-7);
  border-radius: var(--radius-pill);      /* the established button shape */
  font: var(--fw-bold) var(--fs-small)/1 var(--font-sans);
  border: 1px solid transparent;
  cursor: pointer;
  text-decoration: none;
  min-height: 40px;
}
.btn:hover { text-decoration: none; }
.btn[disabled], .btn[aria-disabled="true"] { opacity: .5; cursor: not-allowed; }

.btn-primary   { background: var(--btn-primary-bg); color: var(--btn-primary-fg); }
.btn-primary:hover  { background: var(--ml-blue-600); color: var(--btn-primary-fg); }
.btn-primary:active { background: var(--btn-primary-bg-press); }

.btn-secondary { background: var(--btn-secondary-bg); color: var(--btn-secondary-fg);
                 border-color: var(--border-subtle); }
.btn-secondary:hover { background: var(--slate-100); color: var(--btn-secondary-fg); }

.btn-danger    { background: var(--btn-danger-bg); color: var(--btn-danger-fg); }
.btn-danger:hover  { background: var(--btn-danger-bg-press); color: var(--btn-danger-fg); }

.btn-ghost     { background: transparent; color: var(--btn-ghost-fg); }
.btn-ghost:hover { background: var(--btn-ghost-bg-hover); color: var(--fg-primary); }

.btn-block { width: 100%; }
.btn-sm { min-height: 32px; padding: 6px var(--space-5); }

/* ---------------------------------------------------------------- forms */

.field { margin-bottom: var(--space-6); }
.field label { display: block; margin-bottom: var(--space-2); }
.field .help { margin: var(--space-2) 0 0; font-size: var(--fs-small); color: var(--fg-tertiary); }
.field .err  { margin: var(--space-2) 0 0; font-size: var(--fs-small); color: var(--fg-danger-strong); }

/* Opt-out, not opt-in. This was a list of six types, and the one
   `type="search"` in the app was not on it, so the license search box was
   handed the browser's own control instead: a different font, different
   padding, and in WebKit a pill border with an inner clear button. Nothing was
   wrong in the markup, and nothing said a type had to be registered here
   first. Naming what is genuinely NOT a text field instead means the next type
   someone reaches for is already right.

   Chained :not() rather than :not(a, b, c): the selector-list form only
   reached Safari in 16.4, and this rule decides whether every form in the app
   is legible. */
input:not([type=checkbox]):not([type=radio]):not([type=submit]):not([type=button]):not([type=reset]):not([type=image]):not([type=file]):not([type=hidden]):not([type=range]):not([type=color]),
select, textarea {
  width: 100%;
  padding: 10px var(--space-4);
  font: var(--fw-regular) var(--fs-default)/1.4 var(--font-sans);
  color: var(--fg-primary);
  background: var(--bg-canvas);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
}
/* The rule above gives search the same box as everything else; these strip the
   parts of the native control that sit inside it. The page carries its own
   Clear link, which works in every browser, so the WebKit-only cancel button
   would be a second affordance for the same thing in some browsers and not
   others. */
input[type=search] { -webkit-appearance: none; appearance: none; }
input[type=search]::-webkit-search-decoration,
input[type=search]::-webkit-search-results-button,
input[type=search]::-webkit-search-cancel-button { -webkit-appearance: none; display: none; }

input::placeholder { color: var(--fg-placeholder); }
input[aria-invalid="true"] { border-color: var(--ml-red-500); }

/* One field, not six boxes. The tracking is doing the job the six boxes were
   drawn for - digit-by-digit legibility - without needing per-digit JS, and a
   pasted code from a desktop authenticator lands in one place. */
.code-input {
  height: 56px;
  font-family: var(--font-mono);
  font-size: 24px; letter-spacing: .35em; text-align: center;
}

/* ---------------------------------------------------------------- alerts */

.alert {
  display: flex; gap: var(--space-3); align-items: flex-start;
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius-md);
  border: 1px solid; font-size: var(--fs-small);
}
.alert-error   { background: var(--bg-danger);  color: var(--fg-danger-strong); border-color: var(--border-danger); }
.alert-warning { background: var(--bg-warning); color: var(--fg-warning);       border-color: var(--border-warning); }
.alert-success { background: var(--bg-success); color: var(--fg-success);       border-color: var(--border-success); }
.alert-info    { background: var(--bg-info);    color: var(--fg-info);          border-color: var(--border-info); }

/* ---------------------------------------------------------------- badges */

.badge {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 10px; border-radius: var(--radius-pill);
  font: var(--fw-bold) 12px/18px var(--font-sans);
  border: 1px solid;
}
.badge-published { background: var(--bg-success); color: var(--fg-success); border-color: var(--border-success); }
.badge-importing { background: var(--bg-info);    color: var(--fg-info);    border-color: var(--border-info); }
.badge-empty     { background: var(--gray-50);    color: var(--fg-tertiary);border-color: var(--border-subtle); }
.badge-expiring  { background: var(--bg-warning); color: var(--fg-warning); border-color: var(--border-warning); }
.badge-failed    { background: var(--bg-danger);  color: var(--fg-danger-strong); border-color: var(--border-danger); }
.badge-admin     { background: var(--ml-blue-100);color: var(--ml-blue-700);border-color: var(--border-info); }
.badge-neutral   { background: var(--gray-50);    color: var(--fg-secondary);border-color: var(--border-subtle); }
/* The one pill that is a control rather than a fact. Blue and dashed so it
   does not read as one more language: a solid gray pill saying "Add" in a row
   of solid gray pills naming languages is a language called Add. */
.badge-add {
  background: var(--ml-blue-100); color: var(--ml-blue-700);
  border-color: var(--border-info); border-style: dashed;
  cursor: pointer;
  /* Full shorthand, and the icon eats into the left the way the removed
     padding-right override used to eat into the right: an 8/10 split around a
     12px glyph plus a word optically centres where 10/10 does not. A <button>
     also brings its own UA padding and line-height, so both are restated here
     rather than left to .badge to win by cascade order. */
  padding: 2px 10px 2px 8px;
  font: var(--fw-bold) 12px/18px var(--font-sans);
}
.badge-add:hover { background: var(--bg-info); border-style: solid; }
.badge-add .i { width: 12px; height: 12px; }

/* ---------------------------------------------------------------- tables */

.table { width: 100%; border-collapse: collapse; }
.table th {
  text-align: left; padding: var(--space-3) var(--space-4);
  font: var(--fw-bold) var(--fs-small)/1 var(--font-sans);
  color: var(--table-header-fg);
  border-bottom: 1px solid var(--border-hairline);
  white-space: nowrap;
}
.table td {
  padding: var(--space-4);
  border-bottom: 1px solid var(--border-hairline);
  vertical-align: middle;
}
.table tbody tr:hover { background: var(--table-row-hover); }
.table tbody tr:last-child td { border-bottom: 0; }
.table .num { text-align: right; font-variant-numeric: tabular-nums; }

/* width:100% on an auto-layout table is a floor, not a ceiling: once the
   intrinsic content is wider than the card the table simply overflows it,
   because .card sets no overflow of its own. The audit tables reach that
   point first (six columns of timestamps, emails and event targets), but
   any of them can. The table scrolls inside its own box instead, so the
   page body never scrolls sideways. */
.table-wrap { overflow-x: auto; }

/* .truncate on its own does nothing inside a table: text-overflow needs a
   constrained width, and an auto-layout cell sizes to its content. This gives
   it one, so long targets and filenames ellipsize in place and the scrollbar
   above stays a fallback rather than the normal experience. */
.table td.truncate { max-width: 30ch; }

/* The file-name cell. Same reasoning as .truncate, sized for the longest name
   we actually ship (tactical-prod-sdkInternal-offline-release-1.0.749.apk, 52
   characters) so nothing in the current catalogue clips at all: the ceiling is
   there for the name that one day does not fit, not as the normal state.
   Wrapping is the wrong failure - these names differ only in their last few
   characters, so two wrapped names are harder to tell apart than two clipped
   ones, and the full name is on the title either way. */
.table td.file { max-width: 56ch; }
.filecell { display: flex; align-items: center; gap: var(--space-3); min-width: 0; }
.fname { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* A checksum is read when a transfer went wrong and never otherwise, so it
   sits under the name it belongs to rather than in a column of its own. */
.hashline { margin-top: 2px; }
.hashline > summary {
  list-style: none; cursor: pointer; display: inline-flex; align-items: center;
  font: var(--fw-semibold) 11px/1.4 var(--font-sans);
  letter-spacing: 0.04em; text-transform: uppercase; color: var(--fg-tertiary);
}
.hashline > summary::-webkit-details-marker { display: none; }
.hashline > summary::before { content: '+'; margin-right: 5px; font-size: 13px; }
.hashline[open] > summary::before { content: '-'; }
.hashline > summary:hover { color: var(--fg-link); text-decoration: underline; }
.hashval { display: flex; align-items: flex-start; gap: var(--space-2); margin-top: 4px; }
/* 64 hex characters do not fit a file-name column on one line, and must not
   be allowed to set the column width. */
.hashval code {
  font-size: 11px; line-height: 16px; color: var(--fg-tertiary);
  word-break: break-all; min-width: 0;
}
.hashval .copybtn { border-left: 0; padding: 0 4px; align-self: center; }

/* The single rule: below --table-stack-bp every data table becomes cards.
   The release-detail file table is the screen this matters most for; it is
   the one customers use, and the first to break on a phone. */
@media (max-width: 640px) {
  .table, .table tbody, .table tr, .table td { display: block; width: 100%; }
  .table thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
  .table tr {
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    padding: var(--space-4);
    margin-bottom: var(--space-4);
    background: var(--bg-canvas);
  }
  .table td { border: 0; padding: var(--space-1) 0; text-align: left !important; }
  /* Stacked rows are full-width blocks; a cap here would clip a filename
     mid-word with nothing to scroll. Let it wrap instead. */
  .table td.truncate { max-width: none; white-space: normal; overflow: visible; }
  /* The card is already the width of the screen, so there is no column to
     protect; a name that does not fit 360 px should wrap rather than be cut
     off with nowhere to hover for the title. */
  .table td.file { max-width: none; }
  .fname { white-space: normal; overflow: visible; word-break: break-all; }
  .table td[data-label]::before {
    content: attr(data-label);
    display: block;
    font: var(--fw-bold) 12px/16px var(--font-sans);
    color: var(--table-header-fg);
    margin-bottom: 2px;
  }
}

/* ---------------------------------------------------------------- copy */

.copyrow {
  display: flex; align-items: stretch; gap: 0;
  border: 1px solid var(--border-subtle); border-radius: var(--radius-md);
  background: var(--gray-25); overflow: hidden;
}
.copyrow code {
  flex: 1 1 auto; min-width: 0;
  padding: 10px var(--space-4);
  font-family: var(--font-mono); font-size: 13px;
  color: var(--fg-primary);
  overflow-x: auto; white-space: pre;
}
.copybtn {
  flex: none; border: 0; border-left: 1px solid var(--border-subtle);
  background: var(--bg-canvas); color: var(--fg-secondary);
  padding: 0 var(--space-5); cursor: pointer;
  font: var(--fw-bold) var(--fs-small)/1 var(--font-sans);
  display: inline-flex; align-items: center; gap: 6px;
}
.copybtn:hover { background: var(--slate-50); color: var(--fg-primary); }

.hash { font-family: var(--font-mono); font-size: 12px; color: var(--fg-tertiary);
        font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------------- enrollment */

.enroll { display: flex; gap: var(--space-8); flex-wrap: wrap; align-items: flex-start; }
.enroll > .stack { flex: 1 1 22rem; }
.qr {
  flex: none;
  padding: var(--space-3); background: var(--white);
  border: 1px solid var(--border-subtle); border-radius: var(--radius-lg);
}
/* The mark is positioned against the code itself, not against the padded
   card. Percentages resolve on the containing block, and the card's padding
   would inflate them by a tenth - straight out of the safety margin. */
.qr-frame { position: relative; display: block; }
.qr img { display: block; }

/* A white plate over the middle of the code, sized in QR modules by the
   server (--plate). The ceiling is error correction, not taste: 21 modules is
   where the weakest version an e-mail can produce stops decoding, and this
   sits one step under it. Raising --plate, lowering the error-correction
   level, or setting a width here in pixels all break the same thing.
   ops/test-qr-logo.py is what proves it still scans. */
.qr-mark {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: var(--plate, 28%); height: auto; aspect-ratio: 1;
  /* Only the plate erases modules, so the inset is free: keep it to what the
     glyph needs to not touch dark modules, and spend the rest on the mark. */
  padding: 4px; box-sizing: border-box;
  /* The bubble is 39.1 x 30.2, not square. Without this the plate's square
     box stretches it. */
  object-fit: contain;
  background: var(--white); border-radius: var(--radius-md);
}

/* A <summary> styled as a button: the disclosure that guards Turn off, and
   the "+N more" expansion, both work with no JS at all. */
summary.btn { list-style: none; cursor: pointer; display: inline-flex; }
summary.btn::-webkit-details-marker { display: none; }
details[open] > summary.btn { margin-bottom: var(--space-2); }

/* ---------------------------------------------------------- language cell */

/* One badge per language. Wrapping is the point: a pack listing five names
   should stack inside its own cell rather than widen the table, so the file
   names beside it keep their room. */
.langs { max-width: 26ch; }
.langs-set {
  display: flex; flex-wrap: wrap; gap: var(--space-1); align-items: center;
}
/* The badge sets its own 12px bold; nothing here overrides it, so a language
   pill is identical to every other neutral pill in the product. */
.langs .badge { max-width: 100%; }
.langs details > summary {
  list-style: none; cursor: pointer;
}
.langs details > summary::-webkit-details-marker { display: none; }
/* Deliberately not a pill: it is the control that opens the row, and dressing
   it as one more badge would read as a sixth language. */
.langs .more {
  color: var(--fg-link); font-size: var(--fs-small);
  font-weight: var(--fw-semibold); white-space: nowrap;
}
.langs details > summary:hover .more { text-decoration: underline; }
/* Expands the row in place, the same mechanic as revealing a token, and it
   needs no JS at all. */
.langs details[open] > summary { display: none; }

@media (max-width: 720px) {
  /* At 360 the table is already card-per-file; the cell just needs to stop
     being narrow, since it sits under the file name as a label/value row. */
  .langs { max-width: none; }
}

/* An expansion of the row above it, not a card of its own: no top border, so
   the two read as one row that grew. Used for a revealed token, and for the
   delivery error under a queued message: both are long text that would force
   a column wide if it sat in a cell. */
.reveal-row > td,
.detail-row > td {
  border-top: 0; padding-top: 0; padding-bottom: var(--space-5);
  background: var(--bg-muted);
}

/* Grayed, not hidden: a lapsed row is the one people most often come looking
   for. The link keeps its own color so the row is still obviously a target. */
.muted-row > td { color: var(--fg-tertiary); }
.muted-row .ml-caption { color: var(--gray-400); }

/* ---------------------------------------------------------------- progress */

.progress { height: 8px; background: var(--gray-150); border-radius: var(--radius-pill); overflow: hidden; }
.progress > span { display: block; height: 100%; background: var(--ml-teal-500); border-radius: inherit; }

/* ---------------------------------------------------------------- empty */

.empty { text-align: center; padding: 56px var(--space-6); }
.empty .i { color: var(--gray-400); width: 32px; height: 32px; }

/* ---------------------------------------------------------------- admin */

.admin-layout { display: flex; gap: var(--space-9); align-items: flex-start; }
.rail { flex: 0 0 var(--admin-rail-w); position: sticky; top: var(--space-6); }
.rail a {
  display: flex; align-items: center; gap: var(--space-3);
  padding: 9px var(--space-4); border-radius: var(--radius-md);
  color: var(--admin-rail-fg); font-size: var(--fs-small); font-weight: var(--fw-semibold);
}
.rail a:hover { background: var(--gray-50); text-decoration: none; color: var(--fg-primary); }
.rail a[aria-current="page"] { background: var(--admin-rail-sel-bg); color: var(--admin-rail-sel-fg); }
.admin-main { flex: 1 1 auto; min-width: 0; }

@media (max-width: 860px) {
  .admin-layout { display: block; }
  .rail { position: static; margin-bottom: var(--space-7); }
  .rail nav { display: flex; flex-wrap: wrap; gap: var(--space-2); }
}

/* ---------------------------------------------------------------- auth */

.auth-page { min-height: 100vh; display: flex; flex-direction: column; }
.auth-page main { flex: 1 1 auto; display: flex; align-items: center; justify-content: center; padding: var(--space-9) var(--space-6); }
.auth-card { width: 100%; max-width: 420px; }
.auth-card .brandmark { text-align: center; margin-bottom: var(--space-7); }
.auth-card .brandmark img { height: 58px; }

.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;
}

/* ------------------------------------------------------------ secret rows */

/* License keys and setup tokens stack inside one card on /downloads. The
   spec's list-row rule is a hairline on top of every row but the first:
   no boxes, no left-border accent (explicitly banned). */
.secret-row { gap: var(--space-2); padding: var(--space-5) 0; }
.secret-row:first-of-type { padding-top: 0; }
.secret-row.ruled { border-top: 1px solid var(--border-hairline); }

/* ------------------------------------------------------------- license card */

/* Sits above the release list because it is the one fact a returning customer
   most often comes back for, and because it is short. */
.license-head {
  display: flex; align-items: center; gap: var(--space-3);
  margin-bottom: var(--space-2);
}
.license-head h2 { margin: 0; font-size: var(--fs-view-title); }
.license-head .i { color: var(--ml-blue-500); }

/* Masked, and there is no reveal: nobody reads a license key off a screen,
   they paste it into an MDM profile. Copy takes the whole value. */
/* Overrides the pre/scroll behavior the copy row uses for one-line values:
   a masked key is meant to wrap, not to hide half of itself off-screen. */
.copyrow code.masked-key {
  letter-spacing: .04em;
  white-space: normal; word-break: break-all; overflow-x: visible;
}

.license-meta {
  display: grid; gap: var(--space-4) var(--space-7);
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  margin-top: var(--space-6);
  padding-top: var(--space-5);
  border-top: 1px solid var(--border-hairline);
}
.license-meta dt { margin: 0 0 2px; }
.license-meta dd { margin: 0; }

@media (max-width: 560px) {
  /* Full-bleed at 360: the key wraps to two lines and Copy becomes a
     full-width target beneath it rather than a cramped inline button. */
  .license-card .copyrow { flex-direction: column; align-items: stretch; }
  .license-card .copybtn { justify-content: center; height: 48px; }
}

/* ------------------------------------------------------- language picker */

/* The admin editor for release_files.languages. The comma-separated text
   input underneath is the value carrier and the no-JS fallback; the picker
   writes through to it, so the form posts the same string either way. */
.langpick { display: flex; flex-direction: column; gap: var(--space-2); min-width: 22ch; }
.langpick-hidden { display: none; }

.langpick-pills { list-style: none; margin: 0; padding: 0;
                  display: flex; flex-wrap: wrap; gap: var(--space-1);
                  align-items: center; }
/* The <li> is the flex item and stretches to the tallest pill on its line by
   default; the badge inside then sits at the top of that stretched box rather
   than on the same centre line as its neighbours. Making the item a flex box
   of its own puts the badge back on the line. */
.langpick-pills > li { display: flex; align-items: center; }
/* Only the language pills carry a remove button, so only they need the room
   taken out of the right side for it. Applying this to every .badge in the
   list, which is what a plain `.langpick-pills .badge` does, pulls the Add
   pill's own label hard against its right edge and it reads as misaligned
   beside the pills either side of it. */
.langpick-pills .badge-lang { padding-right: 4px; }
.langpick-pills .ml-mono { font-size: 11px; color: var(--fg-tertiary); }

.langpick-drop {
  border: 0; background: none; padding: 0 2px; cursor: pointer;
  color: var(--fg-tertiary); display: inline-flex; align-items: center;
}
.langpick-drop:hover { color: var(--fg-danger-strong); }
.langpick-drop .i { width: 12px; height: 12px; }

.langpick-search { width: 100%; }

/* Both of these grow the row. They are NOT absolutely positioned, and must not
   be: the table scrolls inside .table-wrap, and an overflow container clips
   its absolutely positioned descendants. `overflow-x: auto` alone is enough,
   because a box with one axis not visible computes the other from visible to
   auto, so an overlay hanging below a row was cut off at the bottom of the
   scroll box and had to be scrolled into view. There is nowhere to escape to
   short of position:fixed and a scroll handler recomputing coordinates.

   Growing the row is also what this app does everywhere else it reveals
   something in a table: the language details on the customer file table and
   the revealed-token row both expand in place. Expansion, never overlay. */
.langpick-list {
  margin: 2px 0 0; padding: var(--space-1); list-style: none;
  max-height: 12rem; overflow-y: auto;
  background: var(--bg-canvas); border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
}
.langpick-list li {
  padding: 6px var(--space-3); border-radius: var(--radius-sm);
  font-size: var(--fs-small); cursor: pointer;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.langpick-list li.on { background: var(--ml-blue-100); color: var(--ml-blue-700); }
.langpick-list .ml-mono { color: var(--fg-tertiary); }
.langpick-list li.on .ml-mono { color: var(--ml-blue-700); }

/* Same box as the list, because it takes the list's place. In flow for the
   same reason, and it is the one that most needed it: it appears at the moment
   the list disappears, so it is never taller than one line and was the easiest
   thing on the page to lose off the bottom of the scroll box. */
.langpick-none {
  margin: 2px 0 0; padding: 6px var(--space-3);
  font-size: var(--fs-small); color: var(--fg-tertiary);
  background: var(--bg-canvas); border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
}
.langpick-new {
  border: 0; background: none; padding: 0; cursor: pointer;
  font: var(--fw-semibold) var(--fs-small)/1.4 var(--font-sans); color: var(--fg-link);
}
.langpick-new:hover { text-decoration: underline; }

/* Add is drawn as the last item of .langpick-pills, so it needs nothing here
   beyond .badge-add above. */

.langpick-custom { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; }
.langpick-custom .err { flex: 1 0 100%; margin: 0; }
