:root {
    --bg:      #0d0d0d;
    --surface: #141414;
    --border:  #252525;
    --fg:      #e8e8e8;
    --dim:     #c0c0c0;
    --idle:    #505050;
    --loading: #bb8800;
    --success: #008800;
    --error:   #cc2222;
    --accent:  #3377aa;
    --warn:    #886600;
    --font:    monospace;
    --sz:      13px;
}

html.light {
    --bg:      #f5f5f5;
    --surface: #ffffff;
    --border:  #cccccc;
    --fg:      #1a1a1a;   /* 15.9:1 on bg ✓ AAA */
    --dim:     #555555;   /*  6.8:1 on bg ✓ AAA */
    --idle:    #999999;   /*  2.6:1 — intentional: placeholder only, WCAG §1.4.3 exception */
    --loading: #7a5500;   /*  6.1:1 on bg ✓ AAA */
    --success: #006600;   /*  6.6:1 on bg ✓ AAA */
    --error:   #cc2222;   /*  5.0:1 on bg ✓ AA  */
    --accent:  #2266aa;   /*  5.4:1 on bg ✓ AA  */
    --warn:    #886600;   /*  4.9:1 on bg ✓ AA  */
    --font:    system-ui, -apple-system, 'Segoe UI', Helvetica, Arial, sans-serif;
    --sz:      14px;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

body {
    background: var(--bg);
    color: var(--fg);
    font-family: var(--font);
    font-size: var(--sz);
    line-height: 1.5;
}

/* ---- Header ---- */
#header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 6px 14px;
    border-bottom: 1px solid var(--border);
    background: var(--surface);
}
#header .title { color: var(--accent); letter-spacing: 3px; font-size: 12px; }
#header .session { color: var(--dim); font-size: 10px; }

#auth-area { display: flex; align-items: center; gap: 6px; font-size: 10px; }
#auth-area input {
    background: var(--bg);
    border: 1px solid var(--border);
    color: var(--fg);
    font-family: var(--font);
    font-size: 11px;
    padding: 2px 4px;
    outline: none;
}
#auth-area input:focus { border-color: var(--accent); }
#auth-logged { color: var(--dim); }

/* ---- Nav ---- */
#nav button {
    background: none;
    border: 1px solid var(--border);
    color: var(--dim);
    font-family: var(--font);
    font-size: var(--sz);
    padding: 3px 12px;
    margin-left: 4px;
    cursor: pointer;
}
#nav button:hover  { color: var(--fg);     border-color: var(--dim); }
#nav button.active { color: var(--accent); border-color: var(--accent); }

/* ---- Content ---- */
#content { padding: 14px; }

.tab { display: none; }
.tab.active { display: block; }

/* ---- Eyes grid ---- */
.eyes-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 14px;
    align-items: start;
}
/* photos column sits between OD and OS — doctor-only, absent when logged out */
.eyes-grid.with-shots { grid-template-columns: 1fr 300px 1fr; }

/* ---- Eye panel ---- */
.eye-panel {
    border: 1px solid var(--border);
    padding: 10px 12px;
}
.eye-panel h2 {
    font-size: 12px;
    letter-spacing: 2px;
    color: var(--accent);
    margin-bottom: 10px;
    padding-bottom: 5px;
    border-bottom: 1px solid var(--border);
    display: flex;
    align-items: center;
    gap: 10px;
}

/* ---- Section labels ---- */
.sec {
    color: var(--dim);
    font-size: 10px;
    letter-spacing: 2px;
    margin: 8px 0 4px;
    padding-bottom: 2px;
    border-bottom: 1px solid var(--border);
}

/* ---- Fields ---- */
.field {
    display: flex;
    align-items: center;
    margin-bottom: 4px;
    gap: 8px;
}
.field label {
    width: 175px;
    flex-shrink: 0;
    color: var(--dim);
}
.req { color: var(--error); font-size: 11px; }
.field label .req { color: var(--error); font-size: 11px; }
.field input,
.field select {
    width: 145px;
    background: var(--bg);
    border: 1px solid var(--border);
    color: var(--fg);
    font-family: var(--font);
    font-size: var(--sz);
    padding: 2px 5px;
    outline: none;
}
.field input:focus,
.field select:focus { border-color: var(--accent); }
.field input[type=checkbox] {
    width: auto;
    cursor: pointer;
    appearance: none;
    -webkit-appearance: none;
    width: 13px;
    height: 13px;
    border: 1px solid var(--dim);
    background: var(--bg);
    position: relative;
    flex-shrink: 0;
}
.field input[type=checkbox]:checked {
    background: var(--accent);
    border-color: var(--accent);
}
.field input[type=checkbox]:checked::after {
    content: '';
    position: absolute;
    left: 2px; top: 0px;
    width: 5px; height: 9px;
    border: 2px solid var(--bg);
    border-top: none;
    border-left: none;
    transform: rotate(45deg);
}

/* placeholder — clearly dim so users can distinguish from real values */
::placeholder { color: var(--idle); opacity: 1; }

/* hide number input spinners */
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
input[type=number] { -moz-appearance: textfield; }
.field select option { background: var(--bg); }

/* ---- PK block ---- */
.pk-block {
    display: block;
    margin: 4px 0;
    padding: 6px 8px;
    border: 1px dashed var(--border);
}

/* ---- Calc controls ---- */
.calc-controls {
    margin-top: 10px;
    padding-top: 8px;
    border-top: 1px solid var(--border);
}
.calc-row {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 4px;
}
.calc-row .cname { width: 80px; color: var(--dim); font-size: 12px; }
.calc-row .cstatus { width: 90px; font-size: 12px; }

.s-idle    { color: var(--idle); }
.s-loading { color: var(--loading); }
.s-success { color: var(--success); }
.s-error   { color: var(--error); }

/* ---- Buttons ---- */
button.btn {
    background: none;
    border: 1px solid var(--border);
    color: var(--dim);
    font-family: var(--font);
    font-size: 11px;
    padding: 2px 8px;
    cursor: pointer;
}
button.btn:hover { border-color: var(--fg); color: var(--fg); }
button.btn:disabled { color: var(--idle); border-color: var(--idle); cursor: default; }
button.btn.btn-ready { border-color: var(--success); color: var(--success); }
button.btn.btn-ready:hover { border-color: #00aa00; color: #00aa00; }

button.btn-run-all {
    margin-top: 8px;
    padding: 4px 18px;
    font-size: 12px;
    letter-spacing: 1px;
    border-color: var(--accent);
    color: var(--accent);
}
button.btn-run-all:hover { background: var(--accent); color: var(--bg); }

button.btn-dup {
    font-size: 10px;
    padding: 1px 7px;
    letter-spacing: 0;
}

button.btn-clr {
    font-size: 10px;
    padding: 1px 7px;
    letter-spacing: 0;
    color: var(--error);
    border-color: var(--error);
    opacity: 0.6;
}
button.btn-clr:hover { opacity: 1; color: var(--error); border-color: var(--error); }

/* ---- Global Run All bar ---- */
.run-all-bar {
    display: flex;
    align-items: center;
    gap: 16px;
    margin-top: 12px;
    padding-top: 10px;
    border-top: 1px solid var(--border);
}

button.btn-run-all-global {
    padding: 5px 24px;
    font-size: 12px;
    letter-spacing: 2px;
    border-color: var(--accent);
    color: var(--accent);
}
button.btn-run-all-global:hover { background: var(--accent); color: var(--bg); }

button.btn-save {
    padding: 5px 16px;
    font-size: 12px;
    letter-spacing: 2px;
    border-color: var(--success);
    color: var(--success);
}
button.btn-save:hover { background: var(--success); color: var(--bg); }

#patid-row { display: flex; align-items: center; gap: 6px; }
#patid-row .dim { color: var(--dim); font-size: 11px; }
.patid-val { color: var(--accent); font-size: 11px; }
/* Владелец чужой карточки под админом — вторая подпись рядом с «кто вошёл», не замена ей.
   Тише основного поля: вопрос «чья карточка» возникает реже, чем «под кем я». */
.patid-owner { color: var(--warn); font-size: 10px; margin-left: 12px; }

/* Пациент заведён — поле заперто. Читается, но не набирается: личность меняется только
   кнопками NEW / DUPLICATE / DELETE. Цвет тот же, что у остальных значений в шапке, —
   это не "неактивно", а "установлено". */
/* "not saved", the editors' mark: the doctor sees the state of his input at all times
   instead of discovering it when he leaves the card. Warn colour, not error — nothing is
   broken, the card is simply not in the database yet. */
.patid-dirty {
    margin-left: 12px;
    color: var(--warn);
    font-size: 11px;
    border: 1px solid var(--warn);
    padding: 1px 6px;
}

#input-patid.locked { color: var(--accent); border-color: var(--border); cursor: default; }
button.btn.btn-danger { color: var(--error); border-color: var(--error); }
button.btn.btn-danger:hover { background: var(--error); color: var(--fg); }
/* Нижнее подчёркивание в моноширинном шрифте рисуется ПОД базовой линией, и при padding в
   2px его срезала нижняя граница поля: `..._copy` читалось как `... c`. Имя копии на этом
   символе и держится, так что место под ним обязано быть (владелец 06.09). */
#patid-row input {
    background: var(--bg);
    border: 1px solid var(--border);
    color: var(--fg);
    font-family: var(--font);
    font-size: var(--sz);
    padding: 2px 5px 4px;
    line-height: 1.35;
    outline: none;
}
#patid-row input:focus { border-color: var(--accent); }

/* ---- UUID + messages ---- */
.uuid-line {
    margin-top: 7px;
    font-size: 10px;
    color: var(--dim);
    word-break: break-all;
}
.uuid-line span { color: var(--fg); }

/* pre-line — чтобы список отказов валидации шёл строкой на ошибку (api.js, validationText) */
/* Опись уцелевшего на RESULTS, когда расчётов нет. Цвета обычные, не тревожные: это
   перечень того, что на месте, а не предупреждение (владелец 06.09). */
.survivors      { margin-top: 10px; border: 1px solid var(--border); background: var(--surface); padding: 8px 10px; }
.survivor-row   { font-size: 11px; margin-top: 3px; }
.survivor-note  { font-size: 11px; margin-top: 8px; }

.msg { margin-top: 5px; font-size: 11px; min-height: 14px; white-space: pre-line; }
.msg.info    { color: var(--dim); }
.msg.ok      { color: var(--success); }
.msg.err     { color: var(--error); }
.msg.warn    { color: var(--warn); }

/* ---- Generic helpers ---- */
.dim  { color: var(--dim); }
.warn { color: var(--warn); }

/* ---- Results tab ---- */
.res-eye-bar {
    display: flex;
    gap: 6px;
    margin-bottom: 12px;
    padding-bottom: 8px;
    border-bottom: 1px solid var(--border);
}
.res-eye-bar button {
    background: none;
    border: 1px solid var(--border);
    color: var(--dim);
    font-family: var(--font);
    font-size: 12px;
    padding: 3px 18px;
    letter-spacing: 2px;
    cursor: pointer;
}
.res-eye-bar button:hover  { color: var(--fg);     border-color: var(--dim); }
.res-eye-bar button.active   { color: var(--accent);  border-color: var(--accent); }
.res-eye-bar button.disabled { color: var(--idle); border-color: var(--border); cursor: default; }

.res-grid {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    gap: 14px;
    align-items: start;
}

.res-col {
    border: 1px solid var(--border);
    padding: 10px 12px;
}
.res-col-hdr {
    color: var(--dim);
    font-size: 10px;
    letter-spacing: 2px;
    margin-bottom: 8px;
    padding-bottom: 5px;
    border-bottom: 1px solid var(--border);
}

.res-table {
    border-collapse: collapse;
    width: 100%;
    margin-bottom: 8px;
    font-size: 11px;
}
.res-table th, .res-table td {
    border: 1px solid var(--border);
    padding: 3px 7px;
    text-align: right;
}
.res-table th            { color: var(--dim); font-size: 10px; text-align: left; }
.res-table td:first-child { text-align: left; }

button.btn-copy {
    font-size: 10px;
    padding: 2px 8px;
    color: var(--accent);
    border-color: var(--accent);
    opacity: 0.8;
}
button.btn-copy:hover    { background: var(--accent); color: var(--bg); opacity: 1; }
button.btn-copy:disabled { color: var(--idle); border-color: var(--idle); cursor: default; opacity: 1; }

/* Middle: user choice form */
.choice-form { margin-bottom: 8px; }
.choice-row {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 5px;
}
.choice-row label {
    width: 95px;
    flex-shrink: 0;
    color: var(--dim);
    font-size: 11px;
}
.choice-row input {
    width: 90px;
    background: var(--bg);
    border: 1px solid var(--border);
    color: var(--fg);
    font-family: var(--font);
    font-size: var(--sz);
    padding: 2px 5px;
    outline: none;
}
.choice-row input:focus { border-color: var(--accent); }
.choice-actions {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: 8px;
}
.choice-history {
    margin-top: 6px;
    border-top: 1px solid var(--border);
    padding-top: 4px;
}
.choice-history a { color: var(--accent); text-decoration: none; }
.hist-select .hist-row { cursor: pointer; }
.hist-select .hist-row:hover { background: var(--border); }
.pdf-open { cursor: pointer; text-decoration: underline; }
.pdf-open:hover { color: var(--fg); }
.teleon-pdf-panel { margin: 4px 0 6px; border-left: 2px solid var(--border); padding-left: 8px; }
/* One combo, one block: the form in force on top, everything it replaced folded away.
   <details> and not a button — the CSP forbids inline handlers, and this needs no JS. */
/* The doctor's own tokens, not our dollars: shown next to an answer only when the balance
   actually moved. */
.explain-charged { color: var(--warn); }

/* ---- EXPLAIN full screen ---- */
/* Fixed, not a modal card: the whole point is the room. The header stays put and the log
   is the only thing that scrolls, so the identifiers of what you are reading never leave
   the screen while you compare two answers. */
.explain-full {
    position: fixed;
    inset: 0;
    z-index: 90;
    background: var(--bg);
    display: flex;
    flex-direction: column;
    padding: 10px 14px 12px;
    gap: 6px;
}
.explain-full-hdr {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    padding-bottom: 6px;
    border-bottom: 1px solid var(--border);
}
.explain-full-t  { color: var(--accent); letter-spacing: 2px; font-size: 12px; }
.explain-full-sp { flex: 1; }
.explain-full-log {
    flex: 1;
    overflow-y: auto;
    /* Answers are prose; at full width they turn into a wall the eye cannot track. The cap
       is on the text, not on the window — tables break out of it below. */
    max-width: 1100px;
    width: 100%;
    /* The log scrolls one way only. A sideways scrollbar under a page of prose asks the
       reader to drag the text back after every paragraph, and it appears for one runaway
       line — a RAW dump, a uuid, a table — while the prose that fills the screen never
       needed it. Anything too wide wraps or scrolls inside itself; the log does not move. */
    overflow-x: hidden;
}
/* Prose breaks anywhere rather than pushing the line out of view: the long tokens here are
   uuids and field names, which have no spaces to break at and are read a character at a
   time anyway. */
.explain-full .explain-a { overflow-wrap: anywhere; }
/* Model answers put RAW dumps in fenced blocks. `white-space: pre` there means one long
   line survives as one long line — inside the log that used to be the sideways scrollbar.
   Wrapped, the dump stays readable: it is a list of fields, not code whose indentation
   carries meaning. */
.explain-full .explain-a .md-pre {
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}
/* Tables were the thing dragging the log sideways: `calc(100vw - 32px)` is wider than the
   1100px cap above, so every answer containing one got a scrollbar under the whole page of
   prose. Here they stay inside the log and wrap their cells instead. Wrapping costs a table
   height, and height is free in a column that already scrolls down; sideways is the one
   direction the reader cannot follow without dragging the text back after every line. The
   narrow panel keeps `nowrap` — there the cells would wrap to one word per line. */
.explain-full .explain-a .md-tbl {
    width: auto;
    max-width: 100%;
}
.explain-full .explain-a .md-tbl th,
.explain-full .explain-a .md-tbl td {
    white-space: normal;
    /* Wrap between words, never inside one. The `anywhere` above is right for prose full of
       uuids; in a table it tells the browser a column can be one character wide, and the
       auto layout believes it — "совпадает" came out as "совпадае/т" in a column squeezed
       to nothing while its neighbour kept the room. */
    overflow-wrap: normal;
    word-break: normal;
}
/* Everything under the log lines up with it rather than with the window: an input the width
   of a 27" screen is a different thing to type into than the one in the panel, and a notice
   stretched that far stops reading as a line. */
.explain-full .explain-bar,
.explain-full .explain-modes,
.explain-full .explain-stale,
.explain-full .explain-notice,
.explain-full .explain-blocked,
.explain-full .explain-pick-hint,
.explain-full .explain-err { max-width: 1100px; width: 100%; }
.explain-full-msg { font-size: 11px; min-height: 14px; }
.explain-full-btn { margin-left: 4px; }
@media (max-width: 700px) {
    .explain-full { padding: 8px; }
}
/* Sits with the language buttons but is not one of them: it ends the conversation. */
.explain-restart { margin-left: auto; }
/* The chat is closed for this patient (no order form yet) — the backend's own words,
   in place of an input box that could only answer with a refusal. */
.explain-blocked { margin: 3px 0; padding: 4px 6px; border: 1px solid var(--border);
                   color: var(--dim); font-size: 11px; }
/* Not an error and not a warning about the answer — a note about which day it describes. */
.explain-stale { margin: 3px 0; padding: 3px 6px; border-left: 2px solid var(--warn);
                 color: var(--dim); font-size: 11px; }
/* The documents of one printed form, on that form's line: PDF1 · PDF2 · TXT · COPY. They
   are links in a line of text, not a toolbar — the doctor reads the line and takes what
   he needs from it. */
.pdf-txt {
    color: var(--accent);
    cursor: pointer;
    text-decoration: underline;
    text-underline-offset: 2px;
}
.pdf-txt:hover { color: var(--fg); }
/* Named but not built: our own order form, planned for v08. Dim and unclickable, and NOT
   struck through — a strikethrough reads as "cancelled", which is the opposite of what is
   meant. `help` cursor is the hint that hovering explains it. */
.pdf-soon { color: var(--idle); cursor: help; }
.prompt-msg { font-size: 10px; color: var(--dim); margin-left: 4px; }
.prompt-msg.err { color: var(--error); }

.pdf-combo { margin-top: 6px; }
.pdf-combo:first-child { margin-top: 0; }
.pdf-combo-hdr { color: var(--dim); font-size: 11px; }
/* Under the combo name: the saved choice and the recalculated keratometry the forms were
   printed from. Dim, because it is the explanation and not the list. */
.pdf-combo-why { color: var(--idle); font-size: 10px; line-height: 1.6; margin: 1px 0 2px; }
.pdf-combo-why .fg { color: var(--dim); }
.pdf-line { margin-top: 2px; font-size: 12px; }
.pdf-line-live { font-weight: bold; }
.pdf-live-mark { color: var(--success); font-size: 11px; }
.pdf-none { color: var(--warn); font-size: 11px; margin-top: 2px; }
.pdf-doom { color: var(--warn); font-size: 11px; }
.pdf-hist { margin-top: 2px; }
.pdf-hist > summary { color: var(--dim); font-size: 11px; cursor: pointer; list-style: none; }
.pdf-hist > summary::-webkit-details-marker { display: none; }
.pdf-hist > summary::before { content: '[+] '; }
.pdf-hist[open] > summary::before { content: '[-] '; }
.pdf-hist > summary:hover { color: var(--fg); }
.pdf-hist .pdf-line { padding-left: 10px; }
.choice-history a:hover { text-decoration: underline; }
button.btn-save-choice {
    padding: 3px 14px;
    font-size: 11px;
    letter-spacing: 1px;
    color: var(--success);
    border-color: var(--success);
}
button.btn-save-choice:hover { background: var(--success); color: var(--bg); }

/* Deviations */
.res-deltas  { margin-top: 12px; }
.delta-row   {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    font-size: 11px;
    margin-bottom: 4px;
    align-items: center;
}
.delta-name  { color: var(--dim); width: 120px; flex-shrink: 0; font-size: 10px; }

/* Predictions */
.res-pred      { margin-top: 12px; }
.res-site-pred { margin-top: 10px; }
.pred-row    {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    font-size: 11px;
    margin-bottom: 4px;
    align-items: center;
}
.pred-row.warn { color: var(--warn); }
.pred-row .dim { width: auto; min-width: fit-content; flex-shrink: 0; }

/* ---- RAW data block ---- */
.raw-calc {
    font-size: 11px;
    color: var(--dim);
    cursor: pointer;
    padding: 3px 0;
    margin-bottom: 2px;
}
.raw-calc:hover { color: var(--fg); }
.raw-tri {
    font-size: 9px;
    margin-right: 4px;
}
.raw-pre {
    font-size: 10px;
    color: var(--fg);
    word-break: break-all;
    white-space: pre-wrap;
    padding: 6px 8px;
    margin: 4px 0;
    border: 1px solid var(--border);
    background: var(--surface);
    max-height: 400px;
    overflow-y: auto;
}

/* ---- TRACE block ---- */
.trace-calc {
    font-size: 11px;
    color: var(--dim);
    cursor: pointer;
    padding: 3px 0;
    margin-bottom: 2px;
}
.trace-calc:hover { color: var(--fg); }
.trace-tri {
    font-size: 9px;
    margin-right: 4px;
}
.trace-entries {
    margin: 4px 0 6px 14px;
    border-left: 1px solid var(--border);
    padding-left: 10px;
}
.trace-entry {
    margin-bottom: 8px;
    padding: 4px 6px;
    border: 1px solid var(--border);
    background: var(--surface);
}
.trace-hdr {
    font-size: 10px;
    color: var(--accent);
    margin-bottom: 4px;
}
.trace-sec {
    font-size: 10px;
    color: var(--dim);
    margin: 4px 0 2px;
    letter-spacing: 1px;
}
.trace-line {
    font-size: 10px;
    color: var(--fg);
    word-break: break-all;
    white-space: pre-wrap;
    padding: 1px 0;
    line-height: 1.4;
}
.trace-expand {
    font-size: 10px;
    color: var(--accent);
    cursor: pointer;
    padding: 2px 0 2px 14px;
    letter-spacing: 1px;
}
.trace-expand:hover { opacity: 0.7; }

/* ---- Teleon block ---- */
.teleon-block {
    margin-top: 20px;
    padding-top: 12px;
    border-top: 1px solid var(--border);
}
.teleon-table-wrap {
    display: flex;
    gap: 16px;
    align-items: flex-start;
}
/* Whose axis this is — the half of the diagram that was missing. */
.toric-src { color: var(--dim); font-size: 10px; }

.toric-panel {
    flex-shrink: 0;
    border: 1px solid var(--border);
    padding: 10px 12px;
}

/* The panel's own place, used when there is no combination table to sit beside — after
   re-entered biometry the calculations are gone and the chat is not. */
.explain-host { display: flex; margin-top: 10px; }
/* Stands where the panel would have been, so the plan answers the question in the place
   the question arises. Same header as the panel — it is the same feature, named. */
.explain-closed { flex: 1 1 auto; margin-top: 10px; }
.explain-host > .explain-panel { flex: 1 1 auto; }

/* -- EXPLAIN: the chat, in the free space to the right of the combination table.
   It takes whatever width is left and stretches with the window; it is the last child of
   the flex row, so on a narrow screen it drops under the table with everything else. */
.explain-panel {
    flex: 1 1 320px;
    min-width: 240px;
    border: 1px solid var(--border);
    padding: 10px 12px;
    display: flex;
    flex-direction: column;
    gap: 6px;
}
/* Scrolls like a messenger: the newest answer at the bottom. */
.explain-log {
    flex: 1 1 auto;
    min-height: 120px;
    max-height: 210px;
    overflow-y: auto;
    border: 1px solid var(--border);
    background: var(--bg);
    padding: 6px 8px;
    font-size: 11px;
    line-height: 1.5;
}
.explain-empty { line-height: 1.6; }
.explain-turn { margin-bottom: 10px; }
.explain-turn:last-child { margin-bottom: 0; }
.explain-q-line {
    color: var(--accent);
    margin-bottom: 3px;
    white-space: pre-wrap;
}
.explain-a { color: var(--fg); white-space: pre-wrap; }
/* which pages of the handbook were read, and by what — small, but not hidden: this is how
   the doctor sees whether the model looked in the right place */
.explain-src, .explain-meta { color: var(--dim); font-size: 10px; margin-top: 3px; }
.explain-notice { color: var(--warn); font-size: 11px; }
.explain-err { font-size: 11px; }
.explain-bar { display: flex; gap: 6px; align-items: flex-start; }
.explain-q {
    flex: 1 1 auto;
    background: var(--bg);
    color: var(--fg);
    border: 1px solid var(--border);
    font: inherit;
    font-size: 11px;
    padding: 4px 6px;
    resize: vertical;
}
.explain-q::placeholder { color: var(--idle); }
.explain-q:disabled { color: var(--idle); }
.explain-modes { display: flex; gap: 4px; flex-wrap: wrap; }
.btn-mode {
    background: none;
    border: 1px solid var(--border);
    color: var(--dim);
    font: inherit;
    font-size: 10px;
    padding: 1px 7px;
    cursor: pointer;
}
.btn-mode.active { color: var(--accent); border-color: var(--accent); }
/* the language switch stands slightly apart: it answers a different question from the modes
   next to it — not how thorough the answer is, but what language it comes back in */
.btn-lang { margin-left: 8px; }
.btn-lang + .btn-lang { margin-left: 0; }
.btn-mode:disabled { color: var(--idle); cursor: default; }

/* -- the six views ---------------------------------------------------------- */
/* Four model tabs, then the two read-only ones behind a separator. The separator is not
   ornament: the first four also decide who answers the next question, the last two do not,
   and the doctor should see that they are a different kind of control. */
.explain-tabs { display: flex; gap: 4px; flex-wrap: wrap; align-items: center; }
.explain-tabs-sep { color: var(--border); font-size: 10px; margin: 0 2px; }
.explain-eye { color: var(--dim); font-size: 10px; }
/* Where the input box would be on a read-only view. Says why it is missing, in the place it
   is missing from — a blank gap there reads as something failing to load. */
.explain-pick-hint { color: var(--idle); font-size: 10px; padding: 4px 0; }

/* A gap in the numbering. Deliberately quiet: it marks an absence, and an absence drawn as
   loudly as a turn would read as one. */
.explain-gap {
    color: var(--idle);
    font-size: 10px;
    padding: 2px 0 2px 8px;
    border-left: 1px solid var(--border);
    margin: 4px 0;
}

/* Who answered, in the views that mix models. Named up front, because comparing a cheap
   model with an expensive one on the same question is the whole point of those views. */
.explain-who { color: var(--accent); font-size: 10px; margin-bottom: 2px; }

.explain-rate { margin-top: 3px; }
.btn-rate {
    background: none;
    border: 1px solid var(--border);
    color: var(--idle);
    font: inherit;
    font-size: 10px;
    padding: 0 6px;
    cursor: pointer;
}
.btn-rate.on { color: var(--success); border-color: var(--success); }

/* -- the feed --------------------------------------------------------------- */
.explain-ev { padding: 3px 0; border-bottom: 1px solid var(--border); font-size: 11px; }
.explain-ev:last-child { border-bottom: none; }
.explain-ev b { color: var(--dim); font-weight: normal; }
.explain-ev-chat { cursor: pointer; }
.explain-ev-chat:hover b { color: var(--accent); }
.explain-ev-chat b { color: var(--accent); }
.explain-ev-q { color: var(--dim); font-size: 10px; margin-top: 2px; }
/* A conversation whose run is missing from the feed. Drawn apart from the events around it:
   placing it under the nearest run would say it explained numbers it never saw. */
.explain-orphan { color: var(--warn); font-size: 10px; margin-top: 2px; }
.explain-open-hdr {
    display: flex;
    gap: 8px;
    align-items: center;
    font-size: 10px;
    margin-bottom: 6px;
}
/* the little of Markdown the models use. No colours of its own beyond the palette: the
   answer must not end up looking louder than the numbers it explains. */
.explain-a .md-h { color: var(--accent); margin: 4px 0 2px; }
.explain-a .md-gap { height: 6px; }
.explain-a .md-list { margin: 2px 0 2px 16px; padding: 0; }
.explain-a .md-list li { margin: 1px 0; }
.explain-a .md-code {
    background: var(--surface);
    border: 1px solid var(--border);
    padding: 0 3px;
}
.explain-a .md-pre {
    background: var(--surface);
    border: 1px solid var(--border);
    padding: 4px 6px;
    margin: 4px 0;
    overflow-x: auto;
    white-space: pre;
}
.explain-a a { color: var(--accent); }
/* the models separate sections with a rule; a line of dashes drawn as dashes would read as
   part of the answer, and this panel already separates things with borders */
.explain-a .md-hr { border-top: 1px solid var(--border); margin: 6px 0; }
/* the models answer a "break it down" question with a table; it has to scroll on its own,
   the panel is narrow and the page must never scroll sideways */
.explain-a .md-tbl { border-collapse: collapse; margin: 4px 0; display: block; overflow-x: auto; }
.explain-a .md-tbl th, .explain-a .md-tbl td {
    border: 1px solid var(--border);
    padding: 1px 5px;
    text-align: left;
    white-space: nowrap;
}
.explain-a .md-tbl th { color: var(--dim); font-weight: normal; }
/* the block still being written: dim until the blank line that closes it */
.explain-a .md-tail { color: var(--dim); }

/* what this patient's explanations have cost, by model */
.explain-spend { border-top: 1px solid var(--border); padding-top: 6px; }
.explain-spend-hdr { color: var(--dim); font-size: 10px; margin-bottom: 3px; }
.explain-spend-tbl { width: 100%; border-collapse: collapse; font-size: 10px; }
.explain-spend-tbl td { padding: 1px 6px 1px 0; color: var(--fg); }
.explain-spend-tbl td.dim { color: var(--dim); }
.explain-spend-sum { color: var(--dim); font-size: 10px; margin-top: 3px; }

/* three numbers, not one — this answer, this conversation, today */
.explain-cost { color: var(--dim); font-size: 10px; }
.explain-cost b { color: var(--fg); font-weight: normal; }
.explain-over { color: var(--warn); }
.explain-warn { color: var(--warn); font-size: 10px; line-height: 1.5; }
.teleon-bar {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 8px 0;
}
.sia-bar, .teleon-sel-row {
    display: flex;
    align-items: center;
    gap: 4px;
    margin-bottom: 10px;
    flex-wrap: wrap;
}
.btn-sia {
    background: none;
    border: 1px solid var(--border);
    color: var(--dim);
    font: inherit;
    padding: 1px 7px;
    cursor: pointer;
    font-size: 11px;
}
.btn-sia:hover  { border-color: var(--accent); color: var(--fg); }
.btn-sia.active { border-color: var(--accent); color: var(--accent); background: rgba(51,119,170,0.12); }
.teleon-table { width: auto; }
.teleon-table td { font-size: 11px; text-align: right; }
.teleon-table td:first-child { text-align: left; color: var(--dim); }
.teleon-table tr { cursor: pointer; }
.teleon-table tr:hover td { background: rgba(51,119,170,0.10); }
.teleon-table tr.teleon-hl td { background: rgba(51,119,170,0.22); color: var(--fg); }
.teleon-table tr.teleon-hl td:first-child { border-left: 2px solid var(--accent); color: var(--fg); }
.teleon-pass2 {
    margin-top: 16px;
    padding: 8px 10px;
    border: 1px solid var(--border);
    font-size: 11px;
}
.teleon-pass2-row {
    display: flex;
    gap: 8px;
    margin-bottom: 4px;
}
/* Ширина по САМОЙ ДЛИННОЙ метке блока — «Axis mismatch». В 60px она не влезала и рвалась
   пополам: «Axis» с числом на одной строке, «mismatch» на следующей, будто это ещё одна
   величина. `nowrap` держит и на случай следующей длинной метки (найдено 08.09). */
.teleon-pass2-row .dim { width: 96px; flex-shrink: 0; white-space: nowrap; }
/* Сдвиг между проходами: тише самих чисел, но заметен — он и есть ответ на вопрос «что дал
   пересчёт». Цветом не красим: рост тóрика не лучше и не хуже падения, это просто факт. */
.pass2-delta { color: var(--dim); margin-left: 8px; }
/* Черта перед полями бланка: выше неё — что посчиталось, ниже — что печатать. Читаются
   одинаково (подпись слева, значение справа), а отвечают на разные вопросы, и без границы
   «Clinic» встаёт в один ряд с «Axis», как ещё одна величина расчёта. */
.teleon-form-start { margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border); }
.teleon-lbl { width: 56px; flex-shrink: 0; }
.teleon-info-inp {
    background: var(--bg);
    border: 1px solid var(--border);
    color: var(--fg);
    font-family: inherit;
    font-size: 11px;
    padding: 2px 6px;
    width: 180px;
}
.teleon-info-inp:focus { outline: none; border-color: var(--accent); }

/* ---- SQL tab ---- */
.sql-bar {
    display: flex;
    gap: 8px;
    align-items: center;
    margin-bottom: 12px;
    padding-bottom: 8px;
    border-bottom: 1px solid var(--border);
}
.sql-bar input {
    width: 200px;
    background: var(--bg);
    border: 1px solid var(--border);
    color: var(--fg);
    font-family: var(--font);
    font-size: var(--sz);
    padding: 3px 7px;
    outline: none;
    /* Подчёркивание `_` рисуется НИЖЕ базовой линии и в тесной строке обрезается краем поля:
       «E2E_LONG» читалось как «E2E LONG», а по такому имени пациента не найти (найдено
       владельцем 08.09). Высота строки даёт глифу место, а не косметический воздух. */
    line-height: 1.5;
}
.sql-bar input:focus { border-color: var(--accent); }

.sql-table {
    border-collapse: collapse;
    width: auto;
    margin-bottom: 10px;
    font-size: 12px;
}
.sql-table th, .sql-table td {
    border: 1px solid var(--border);
    padding: 4px 10px;
    text-align: left;
}
.sql-table th { color: var(--dim); font-size: 10px; letter-spacing: 1px; }
.sql-row { cursor: pointer; }
.sql-row:hover td { background: var(--surface); }

.sql-actions { display: flex; gap: 6px; white-space: nowrap; }

button.btn-sql-run { color: var(--accent); border-color: var(--accent); }
button.btn-sql-run:hover { background: var(--accent); color: var(--bg); }

button.btn-sql-del { color: var(--error); border-color: var(--error); opacity: 0.5; }
button.btn-sql-del:hover { opacity: 1; }

.sql-status { font-size: 11px; letter-spacing: 1px; white-space: nowrap; }
.sql-st-miss { color: var(--idle); }

.sql-pages {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 11px;
}

/* ---- Debug ---- */
#debug-log { font-size: 11px; color: var(--dim); }
#debug-log div {
    margin-bottom: 2px;
    border-bottom: 1px solid var(--border);
    padding: 2px 0;
    word-break: break-all;
    white-space: pre-wrap;
}
#debug-log div:last-child { color: var(--fg); }
#debug-log .req  { color: var(--loading); }
#debug-log .resp { color: var(--dim); }
#debug-log .err  { color: var(--error); }

/* ---- Placeholder ---- */
.placeholder { color: var(--dim); font-size: 12px; padding: 20px 0; }

/* ---- Check data banner ---- */
.check-banner {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 6px 10px;
    margin-bottom: 10px;
    border: 1px solid var(--border);
    font-size: 11px;
}
.check-critical { border-color: var(--error); color: var(--error); }
.check-warn     { border-color: var(--warn);  color: var(--warn);  }

/* Оттенки тревожности: жёлтый -> красный, три ступени (#105). Оттенок ставится по КОДУ
   причины через таблицу MSG_SEV в js/state.js — здесь только цвета, чтобы «поменять
   важность» правилось в одном месте и не требовало трогать стили.
   Ступени намеренно различимы, но не крикливы: три красных подряд перестают быть красными. */
:root {
    --sev-1: #7a6a00;   /* к сведению        — приглушённый жёлтый */
    --sev-2: #8a4a00;   /* стоит посмотреть  — янтарный */
    --sev-3: #880000;   /* смотреть обязательно — красный, он же --error */
}
.check-banner.sev-1 { border-color: var(--sev-1); color: var(--sev-1); }
.check-banner.sev-2 { border-color: var(--sev-2); color: var(--sev-2); }
.check-banner.sev-3 { border-color: var(--sev-3); color: var(--sev-3); }
.check-msgs     { flex: 1; }
/* Все сообщения живут в ОДНОЙ рамке (#106, решение владельца 08.09). Рамка отделяет список
   замечаний от остального экрана, а не каждое сообщение друг от друга: группировка по цвету
   рассыпалась бы обратно на пять рамок, как только оттенков станет больше.
   Разделителя между строками нет намеренно — линия внутри вернула бы тот же дробный вид. */
.check-stack    { flex: 1; display: flex; flex-direction: column; gap: 5px; }
/* Цвет рамки — по самому тревожному внутри, цвет СТРОКИ — свой. Объявлено после правил
   `.check-banner.sev-*`: специфичность та же, побеждает порядок, и строка перекрашивает
   унаследованный от рамки цвет. */
.check-msgs.sev-1 { color: var(--sev-1); }
.check-msgs.sev-2 { color: var(--sev-2); }
.check-msgs.sev-3 { color: var(--sev-3); }
.check-msgs.check-critical { color: var(--error); }
/* Какой калькулятор и о каком числе говорит баннер (#106). Одна и та же проверка
   срабатывает у EVO и у PEARL-DGS независимо, и без этих двух подписей строки выглядели
   слово в слово одинаково при разных числах внутри. */
.check-site     { font-weight: bold; }
.check-val      { opacity: .75; }
.check-sep      { opacity: .5; }
/* Строка комбинации во время расчёта: выбрать нельзя, и это видно до нажатия. Приглушаем,
   а не прячем — таблица не должна дёргаться на время счёта (#113). */
tr.teleon-busy { opacity: .45; cursor: default; }
/* Линза на кнопке RUN: тише самого слова RUN, но читается без наклона к экрану. Кнопка
   отвечает на два вопроса сразу — что сделает и с чем, — и второй не должен спорить с
   первым за внимание (#119). */
.teleon-run-combo { color: var(--dim); font-size: 10px; margin-left: 4px; }
button.btn-recalc { color: var(--error); border-color: var(--error); font-size: 10px; white-space: nowrap; }
.check-recalc-bar { margin-bottom: 10px; }

/* ---- Toric axis diagram ---- */
.res-grid-4 { grid-template-columns: 1fr 1fr 1fr auto; }
.toric-col  { min-width: 196px; }
.toric-diagram { display: block; }
.toric-tabs { display: flex; gap: 4px; margin-bottom: 8px; }
button.toric-tab { font-size: 10px; padding: 1px 8px; letter-spacing: 1px; }
button.toric-tab.active { color: var(--accent); border-color: var(--accent); }
.toric-lbl { font-size: 10px; color: var(--dim); margin-top: 6px; line-height: 1.8; }

/* ---- Photos column ---- */
.shots-panel {
    border: 1px solid var(--accent);
    padding: 10px 12px;
}
.shots-panel h2 {
    font-size: 12px;
    letter-spacing: 2px;
    color: var(--accent);
    margin-bottom: 2px;
    padding-bottom: 5px;
    border-bottom: 1px solid var(--border);
}
.shots-hint { color: var(--idle); font-size: 10px; margin-bottom: 9px; }
.shots-msg  { font-size: 10px; margin-top: 6px; min-height: 14px; }

/* One upload window over the two eye sub-columns (owner's mock-up 2026-08-29). The doctor
   never names a slot: the layout is by eye, the number is only where the backend files
   the file. */
.shots-drop {
    border: 1px dashed var(--accent);
    color: var(--dim);
    font-size: 10px;
    text-align: center;
    padding: 12px 8px;
    display: flex;
    flex-direction: column;
    gap: 6px;
    align-items: center;
    cursor: pointer;
}
.shots-drop:hover { color: var(--fg); }
.shots-drop.over  { border-style: solid; background: var(--surface); }
.shots-drop.full  { border-color: var(--idle); color: var(--idle); cursor: default; }

/* Count and RECOGNIZE on one line, the way the mock-up puts them. */
.shots-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin: 8px 0 10px;
    /* The column is ~250px and holds up to three things. Wrapping by ITEM keeps each of
       them whole; without this the flex items shrank instead and broke inside the words —
       "0 of 6 / cells / free" down three lines (owner, 05.09). */
    flex-wrap: wrap;
}
.shots-count { font-size: 10px; color: var(--dim); line-height: 1.5; white-space: nowrap; }
/* A batch is running. Loading colour, same as a calculation in flight: the answer is on
   its way, nothing is wrong. Sits next to the cell count so it is seen without scrolling
   to the foot of the column, where the message line lives. */
/* Its own full-width row: it is the longest of the three and the only one that appears
   and disappears, so letting it share a line makes the other two jump about. */
.shots-progress { font-size: 10px; color: var(--loading); line-height: 1.5;
                  flex: 1 0 100%; white-space: nowrap; }
.cell.reading { border-color: var(--loading); }
.cell-note.loading { color: var(--loading); }
/* The count is from a read that failed — the number on screen is the previous one. Warn
   colour, not error: nothing is broken, the figure is simply not confirmed. */
.shots-count-stale { color: var(--warn); }

/* Two sub-columns: OD left, OS right. A two-eye printout is rendered in both — one cell
   each — because that is what it occupies in the backend's count. */
.shots-cells {
    display: grid;
    /* minmax(0,1fr), not 1fr: a long file name is nowrap, and a bare 1fr takes its
       min-content width — the OS column then grows straight through the panel border
       into the eye form beside it. */
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 8px;
    align-items: start;
}
.cells-col   { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.cells-undet { margin-top: 10px; display: flex; flex-direction: column; gap: 8px; }
.cells-cap {
    font-size: 10px;
    letter-spacing: 1px;
    color: var(--accent);
    border-bottom: 1px solid var(--border);
    padding-bottom: 3px;
}
.cell {
    border: 1px solid var(--border);
    padding: 5px;
    min-width: 0;
}
.cell.loaded { border-style: dashed; }
.cell.bad    { border-color: var(--error); }
.cell-cap {
    display: flex;
    justify-content: space-between;
    gap: 4px;
    font-size: 10px;
    margin-top: 4px;
}
.cell-eye { color: var(--fg); white-space: nowrap; }
.cell-dev { color: var(--idle); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
/* Момент съёмки с телефона не влезает в ячейку одной строкой (~140px против 28 символов),
   а обрезать дату многоточием бессмысленно — читается ровно её хвост. Переносим по двум
   элементам: метка над датой, внутри даты разрыва нет. */
.cell-dev.stamp { white-space: normal; text-overflow: clip; text-align: right; }
.cell-dev.stamp > span { white-space: nowrap; }
/* A device guessed by a feature, not read off the sheet — coloured as a warning so it is
   never mistaken for a name the parser actually saw. */
.cell-dev.guess { color: var(--warn); }
.viewer-device {
    font-size: 10px;
    color: var(--dim);
    margin: 4px 0 8px;
    line-height: 1.5;
}
.viewer-device.guess { color: var(--warn); }
.viewer-device .fg { color: var(--fg); }
.cell-note        { color: var(--idle); font-size: 10px; margin: 3px 0 5px; line-height: 1.4; }
.cell-note.bad    { color: var(--error); }
.cell-note.warn   { color: var(--warn); }
.cell-thumb {
    display: block;
    width: 100%;
    height: 58px;
    object-fit: cover;
    border: 1px solid var(--border);
    background: var(--surface);
    cursor: pointer;
}
.cell-thumb:hover { border-color: var(--accent); }
/* Address state and the resend button, under the email field. */
.acc-verify { align-items: center; gap: 6px; font-size: 11px; }

/* The button the doctor is looking for. The whole area still takes a drop and still opens
   the dialog on a click — this only makes one of those two visible. */
.btn-upload {
    font-size: 10px;
    padding: 2px 10px;
    border: 1px solid var(--border);
    background: none;
    color: var(--dim);
    cursor: pointer;
}
.btn-upload:hover { color: var(--fg); border-color: var(--accent); }
.slot-row  { display: flex; gap: 5px; flex-wrap: wrap; }
.slot-note { color: var(--idle); font-size: 10px; margin-top: 4px; }
button.btn.btn-pay { border-color: var(--accent); color: var(--accent); }
button.btn.btn-pay:hover:not(:disabled) { background: var(--accent); color: var(--bg); }
/* .btn-ready / .btn-pay recolour the button, so disabled has to win explicitly */
button.btn.btn-ready:disabled,
button.btn.btn-pay:disabled { color: var(--idle); border-color: var(--idle); background: none; cursor: default; }

.shots-consent .consent-text { font-size: 11px; line-height: 1.6; margin-bottom: 8px; }
.consent-check {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    font-size: 11px;
    color: var(--warn);
    margin-bottom: 8px;
    cursor: pointer;
}
.consent-check input { margin-top: 3px; flex-shrink: 0; }

/* ---- Photo full view ---- */
#image-viewer {
    margin-top: 14px;
    border: 1px solid var(--accent);
    padding: 10px 12px;
}
.viewer-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; }
/* Two models on one scan (after two.html, 07.10): the photo on the left stays in view while
   the table and the texts on the right are read against it. */
.viewer-grid.v2 { grid-template-columns: minmax(260px, 40%) 1fr; align-items: start; }
.v2 .v-photo { position: sticky; top: 8px; }
.v2 .viewer-img { height: auto; max-height: 75vh; }
.v2 .v-read h4 { margin-top: 10px; }
.v2 .v-read h4:first-child { margin-top: 0; }
.ev-pills { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; font-size: 11px; }
.ev-pill { border: 1px solid var(--border); padding: 0 6px; color: var(--dim); }
.ev-pill.ok  { color: var(--success); border-color: var(--success); }
.ev-pill.bad { color: var(--fg); background: var(--error); border-color: var(--error); }
.ev-table { border-collapse: collapse; width: 100%; font-size: 11px; }
.ev-table th, .ev-table td { border: 1px solid var(--border); padding: 2px 6px; text-align: left; vertical-align: top; }
.ev-table th { color: var(--dim); font-weight: normal; background: var(--surface); }
.ev-table td.n { font-variant-numeric: tabular-nums; white-space: nowrap; }
.ev-t-match td.st    { color: var(--success); }
.ev-t-conflict td    { background: var(--surface); }
.ev-t-conflict td.st { color: var(--warn); }
.ev-t-decided td     { color: var(--dim); }
.ev-t-bad            { color: var(--idle); }
.ev-table td.ev-t-differ { color: var(--warn); }
/* The reading itself is the choice (07.10): a plain number until pointed at; the standing
   decision carries a dot and the accent, so "which one did I take" reads at a glance. */
.ev-cell { background: none; border: 1px dashed var(--idle); color: var(--fg); font: inherit;
           padding: 0 4px; cursor: pointer; }
.ev-cell:hover { border-color: var(--accent); border-style: solid; }
.ev-cell.on { border: 1px solid var(--accent); color: var(--accent); }
.ev-force { background: none; border: 1px solid var(--border); color: var(--dim); font: inherit; font-size: 11px; padding: 0 4px; cursor: pointer; }
.ev-force:hover { border-color: var(--accent); color: var(--accent); }
.ev-cell.ev-bad { color: var(--idle); }
.ev-req { font-size: 11px; color: var(--dim); margin: -2px 0 8px; }
.v-texts { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 8px; }
.v-text-model { font-size: 11px; color: var(--accent); margin-bottom: 2px; }
.v2 .viewer-raw { height: auto; max-height: 60vh; }
mark.ev-m-ok  { background: none; color: var(--success); border-bottom: 1px solid var(--success); }
mark.ev-m-bad { background: var(--error); color: var(--fg); }
.viewer-col h4 {
    font-size: 11px;
    letter-spacing: 1px;
    color: var(--accent);
    font-weight: normal;
    margin-bottom: 6px;
}
.viewer-col h4 .dim { color: var(--dim); letter-spacing: 0; }
.viewer-img {
    width: 100%;
    height: 330px;
    object-fit: contain;
    border: 1px solid var(--border);
    background: var(--surface);
}
.viewer-raw {
    border: 1px solid var(--border);
    background: var(--surface);
    padding: 8px;
    font-size: 10px;
    color: var(--dim);
    height: 330px;
    overflow: auto;
    white-space: pre-wrap;
}
.viewer-parsed {
    border: 1px solid var(--border);
    background: var(--surface);
    padding: 8px;
    height: 330px;
    overflow: auto;
    font-size: 11px;
}
.parsed-eye { color: var(--accent); letter-spacing: 1px; margin: 10px 0 4px; }
.parsed-eye:first-child { margin-top: 0; }
.parsed-row { display: flex; justify-content: space-between; gap: 8px; padding: 1px 0; }
.parsed-row span:first-child { color: var(--dim); }
.parsed-row span:last-child  { color: var(--warn); }
.parsed-row.empty span:last-child { color: var(--idle); }
.parsed-row.extra span:last-child { color: var(--idle); }
.parsed-row .calcd { color: var(--accent); }
.parsed-note { color: var(--idle); font-size: 10px; margin-top: 6px; line-height: 1.4; }

/* doubtful — three levels, three different things asked of the doctor:
   computed = not on the form at all (cannot be checked against the scan),
   guessed  = we paired the axis and the power ourselves (check it),
   suspect  = read cleanly but cannot be true (almost certainly wrong)      */
.doubt-tag {
    font-size: 9px;
    letter-spacing: 0.5px;
    border: 1px solid currentColor;
    padding: 0 3px;
    margin-left: 4px;
}
.doubt-tag.computed, .parsed-doubt.computed { color: var(--idle); }
.doubt-tag.guessed,  .parsed-doubt.guessed  { color: var(--warn); }
.doubt-tag.suspect,  .parsed-doubt.suspect  { color: var(--error); }
.parsed-row.doubt-suspect span:last-child { color: var(--error); }
.parsed-doubt { font-size: 10px; margin-top: 4px; line-height: 1.4; }
.parsed-warning { color: var(--error); font-size: 10px; margin-top: 6px; line-height: 1.4; }
.viewer-warnings {
    border: 1px solid var(--error);
    color: var(--error);
    padding: 6px 8px;
    margin-top: 10px;
    font-size: 11px;
    line-height: 1.5;
}
.viewer-warning { color: var(--error); font-size: 11px; margin-top: 8px; }
.viewer-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin-top: 10px;
}
.viewer-actions .dim { color: var(--dim); font-size: 10px; }
.viewer-actions .warn-text { color: var(--warn); font-size: 11px; }
.viewer-meta { color: var(--idle); font-size: 10px; margin-top: 8px; }
.viewer-cost {
    display: flex;
    align-items: baseline;
    gap: 10px;
    flex-wrap: wrap;
    margin-top: 10px;
}
.viewer-cost .cost-val {
    color: var(--accent);
    font-size: 22px;
    letter-spacing: 1px;
}
.viewer-cost .cost-rest { color: var(--dim); font-size: 12px; }

/* Where the number in this field came from, read from the database (V7W-5). Unknown shows
   nothing at all: most cards predate the rule, and a mark on every field would say "typed
   by the doctor" about numbers nobody can vouch for. `scan` shares --warn with the
   machine-filled border on purpose — same fact, one from the database and one from this
   session. `admin` gets the accent because it is the one case where the number in the
   doctor's card was changed neither by them nor by the device. */
/* Цвет отвечает на вопрос «кто отвечает за это число сейчас», а не «что с полем было»:
   он читается раньше текста, и историю в него не уложить. Классы по СМЫСЛУ, а не по коду
   маски — `.fsrc-3` после перехода на битовую маску означал бы `doctor|scan` и красил бы
   поправленное врачом в цвет админа (#104). */
.fsrc { font-size: 10px; color: var(--dim); }
.fsrc-doctor    { color: var(--dim); }
.fsrc-scan      { color: var(--warn); }    /* распознанное и НЕ проверенное врачом */
.fsrc-admin     { color: var(--accent); }
.fsrc-derived   { color: var(--idle); }    /* вычислено нами, вводить его врач не может */
.fsrc-other     { color: var(--dim); }     /* комбинация, которой мы не знаем: см. `?N` */
.fsrc-doubt     { color: var(--warn); }    /* сомнение парсера перебивает цвет источника */
/* The price stands under its button, not inside the label. "(10)" on the button read as a
   count of something (owner, 04.09, looking at the phone); "(10 tokens)" fixed the unit but
   still asked to be parsed as part of the action. A separate line says one thing only. */
.price-note { color: var(--warn); font-size: 10px; }
/* Sits under the two FILL buttons and belongs to both of them. */
.fill-mode { color: var(--dim); font-size: 11px; display: inline-flex; align-items: center; gap: 4px; }
.fill-mode input { margin: 0; }
/* Shooting the printout on the spot only makes sense on the device that has a camera in the
   doctor's hand. `[o]` instead of an icon: this interface has no icon set, and a lone glyph
   from somewhere else would read as a rendering accident. */
.btn-camera { display: none; }
/* The fold button and its summary belong to the phone layout; the media block below turns
   them on. On a wide screen the two eyes are columns side by side and there is nothing to
   fold away — and a panel folded on a phone must not stay folded here, so the class does
   nothing outside that block. */
.btn-fold, .eye-sum { display: none; }
/* Typed over, not yet written to the database. Sits in the empty room to the right of the
   input rather than beside the label: it must not shift the field, because it appears while
   the doctor is typing into it. Same colour and wording family as the "not saved" mark by
   the Patient ID — one says the card differs from the database, this one says where. */
/* Emptied on purpose. `null` rather than `changed` because that is what the card will
   hold — the mark names the state the field is in, not the fact that a hand touched it.
   Dimmer than `changed` too: an absent value is not a warning, it is an answer. */
.field.changed.emptied::after {
    content: 'null';
    color: var(--idle);
}
.field.changed::after {
    content: 'changed';
    color: var(--warn);
    font-size: 10px;
    white-space: nowrap;
}
/* machine-filled form value — cleared as soon as the doctor edits it by hand */
.field input.machine-filled,
.field select.machine-filled { border-color: var(--warn); color: var(--warn); }

/* The parser's doubt, on the field it is about. `computed` deliberately does NOT touch the
   border: the number is still machine-written — CCT printed as 0.523 mm and multiplied by
   a thousand is as much the scan's doing as one read off the sheet. Dimming the border made
   the field look hand-typed, which is the one thing it is not (owner, 04.09). The word
   under it stays grey: that is the part saying "this cannot be checked against the photo". */
.field input.doubt-guessed  { border-color: var(--warn); }
.field input.doubt-suspect  { border-color: var(--error); color: var(--error); }
/* one word in the row, the sentence in .doubt-list below — a wrapped sentence here would
   push the two eye columns out of line with each other */
.field-doubt {
    font-size: 9px;
    letter-spacing: 0.5px;
    white-space: nowrap;
    cursor: help;
}
.field-doubt.computed { color: var(--idle); }
.field-doubt.guessed  { color: var(--warn); }
.field-doubt.suspect  { color: var(--error); }

.doubt-list {
    border-left: 2px solid var(--border);
    padding-left: 8px;
    margin: 8px 0 4px;
    font-size: 10px;
    line-height: 1.5;
}
.doubt-list .dl-field { color: var(--fg); }
.doubt-list .computed { color: var(--idle); }
.doubt-list .guessed  { color: var(--warn); }
.doubt-list .suspect  { color: var(--error); }

/* ---- Two models, the doctor's choice (v08, #1337) ----
   Only a mark in the form: a field the scans dispute. The choice is in the scan view's
   table (07.10 — one place to choose). */
.field input.ev-pending { border-color: var(--warn); border-style: dashed; }

/* ---- Photo at full size ---- */
.lightbox {
    position: fixed;
    inset: 0;
    z-index: 60;
    background: rgba(0, 0, 0, 0.92);
    display: flex;
    flex-direction: column;
}
.lightbox-bar {
    display: flex;
    align-items: center;
    gap: 10px;
    justify-content: flex-end;
    padding: 8px 12px;
    flex-shrink: 0;
}
.lightbox-bar .dim { font-size: 11px; }
.lightbox-body {
    flex: 1;
    min-height: 0;
    overflow: auto;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0 12px 12px;
}
.lightbox-img {
    max-width: 90vw;
    max-height: 90vh;
    object-fit: contain;
    cursor: zoom-in;
}
/* 1:1 — a dense printout is read by scrolling, not by squeezing it into the screen.
   Centring with flex here would put the overflowing left edge out of scroll reach, so the
   container goes back to block flow and the image centres with margins. */
.lightbox-body.full { display: block; }
.lightbox-img.full {
    display: block;
    margin: 0 auto;
    max-width: none;
    max-height: none;
    cursor: zoom-out;
}

/* the page behind must not scroll under the open photo */
body.no-scroll { overflow: hidden; }

/* ---- Teleon: power outside the catalogue ---- */
/* information with options, not a refusal — RUN stays enabled (doctor's decision, Q-002) */
.teleon-limit {
    border: 1px solid var(--warn);
    padding: 6px 8px;
    margin: 6px 0;
    font-size: 11px;
    line-height: 1.5;
}
.teleon-limit .limit-msg  { color: var(--warn); }
.teleon-limit .limit-opts { color: var(--dim); margin-top: 4px; }
.teleon-limit .limit-list { margin: 2px 0 0 16px; color: var(--fg); }
.teleon-limit .limit-list li { list-style: square; }

/* ---- Mobile ---- */
@media (max-width: 700px) {
    #header {
        flex-wrap: wrap;
        gap: 6px;
        padding: 8px 10px;
    }
    #nav button { padding: 3px 8px; font-size: 11px; }

    #auth-area { flex-wrap: wrap; }
    #auth-area input { width: 80px; }

    #content { padding: 8px; }

    .eyes-grid,
    .eyes-grid.with-shots { grid-template-columns: 1fr; }
    /* One column puts the photos BETWEEN the eyes, because that is where they sit in the
       markup — and the two eyes of one patient are read as a pair. `order` moves the
       photo column below both of them; the grid keeps the source order otherwise. */
    #panel-od    { order: 1; }
    #panel-os    { order: 2; }
    #panel-shots { order: 3; }
    /* The two eye sub-columns are already narrow; on a phone they keep their pairing —
       a scan of the right eye must not end up under one of the left. Only the cells get
       shorter, so the column stays one thumb-scroll away from the form above it. */
    .cell-thumb { height: 48px; }
    .shots-cells { gap: 6px; }
    .viewer-grid, .viewer-grid.v2 { grid-template-columns: 1fr; }
    .v2 .v-photo { position: static; }

    /* Safari on iOS zooms the page whenever a field with a font under 16px takes focus,
       and it does not zoom back out. That single behaviour is what makes a site read as
       "the desktop one" on a phone: the doctor taps AL, the layout jumps, and the second
       field is off-screen. 16px in the inputs costs nothing on a wide screen — this block
       only exists below 700px — and buys back the whole form.
       A full mobile layout is a separate task with its own spec; this is the floor. */
    /* Specific enough to beat `.field input { font-size: var(--sz) }` — a bare element
       selector loses to a class one, and the zoom came back unnoticed the first time. */
    input, select, textarea,
    .field input, .field select,
    #content input, #content select, #content textarea { font-size: 16px; }
    /* A 22px control is a desktop control. Not thumb-sized either — that is the coming
       spec's business — but not a pixel hunt. */
    #content button, #content input, #content select { min-height: 30px; }
    #nav button { min-height: 28px; }

    /* The field keeps its desktop shape: label and input on ONE line. The label used to be
       pinned to 100px here, which is narrower than "Axial Length (mm)" — so the longest
       label wrapped to two lines and the form grew a third taller for nothing. Measured at
       390px: the widest label is 139px, leaving over 200px for the input beside it.
       The gap between fields is bigger than the gap inside one, so a mark that does not fit
       and wraps still reads as belonging to the field above it, not the one below. */
    /* Until the model has read the scan, TRANSCRIPT and PARSED hold "-- not recognized yet --"
       and nothing else. On a wide screen that is a preview of what is coming; on a phone,
       where the columns stack, it is two screens of placeholder between the doctor and the
       RECOGNIZE button. The photo stays — it is the thing he is deciding about. */
    .btn-camera { display: inline-block; margin-left: 6px; }

    /* The five admin views are one row of navigation; wrapped, the last one hangs alone
       under the others and reads as a separate control (owner, 04.09). They fit at 360px
       once the padding stops being desktop-sized — the labels themselves are short enough.
       The 30px tap target above is kept: what shrinks is the space around the word, not
       the button. */
    .admin-bar .btn, .admin-filter .btn { font-size: 10px; padding: 1px 4px; }
    .admin-bar, .admin-filter { gap: 4px; }
    /* The reason box is 140px on the desktop, where it sits beside two buttons. At this
       width its own placeholder does not fit, so the field asks for something the doctor
       cannot read. Given the row, it does. */
    .adm-inp { width: 100%; }

    /* Admin tables at 360–384px. They FIT — they just were not asked to: 7px of padding on
       five columns and a date allowed to break made "2026-09-04 22:20" three lines tall and
       pushed `Why` off the screen (owner, 04.09). Nothing here scrolls or hides; the numbers
       are short, the space around them was not. */
    .res-table { font-size: 10px; }
    .res-table th, .res-table td { padding: 2px 4px; }
    /* Timestamps, logins and addresses are read as one token or not at all — a wrapped IP
       is worse than a narrow column. Only the tables whose cells are short carry this;
       PLANS joined this set on 04.09, when its prices became a column each instead of one
       cell holding the whole list. */
    .tbl-tight th, .tbl-tight td { white-space: nowrap; }
    /* ...except the last column, which is where the prose lives: a refusal reason in
       SIGN-INS. Held on one line it is the widest thing in the table and pushes the address
       out of view — the numbers to its left are what must not wrap, not it. */
    .tbl-tight th:last-child, .tbl-tight td:last-child { white-space: normal; }
    /* The timestamp is the one cell that reads fine on two lines — it already has a space
       in it, and "2026-09-04" over "22:48" loses nothing. It buys the 45px that a junk
       login like `nosuchuser_ratelimit` takes: that one is a single token and breaking it
       mid-word would make it unrecognisable. */
    .tbl-tight th:first-child, .tbl-tight td:first-child { white-space: normal; }
    /* PLANS: the button is the widest thing in its row and the row does not need it whole
       on one line. */
    .account-table .btn { font-size: 9px; padding: 1px 3px; white-space: normal; }
    .viewer-grid.unread .v-read { display: none; }

    /* Fold an eye away. Only the heading stays, with the three numbers that say whether
       this eye holds data — see eyeFoldSummary(). Everything else in the panel goes,
       including the calculation rows: a folded eye is one the doctor is not working on. */
    .btn-fold { display: inline-block; }
    /* "OD — Right Eye" is a name, not a sentence: broken across two lines it stops reading
       as one. The heading wraps between its parts instead — title, button, then the summary
       on its own line if the numbers do not fit beside them. */
    .eye-panel h2 { flex-wrap: wrap; gap: 6px; letter-spacing: 1px; white-space: nowrap; }
    .eye-sum { display: inline; white-space: normal; }
    .eye-panel.folded > *:not(h2) { display: none; }
    .eye-panel.folded h2 { margin-bottom: 0; }
    .eye-panel.folded .btn-dup,
    .eye-panel.folded .btn-clr { display: none; }
    .eye-sum { color: var(--dim); font-size: 11px; margin-left: 8px; }

    .field { flex-wrap: wrap; gap: 2px 6px; margin-bottom: 8px; }
    .field label { width: auto; flex: 0 0 auto; font-size: 11px; }
    /* The input yields, everything else keeps its size: a number is four characters wide
       and the label and the marks are words. Given `flex: 1 1 70px` the field ends up as
       one line — label, value, marks — and the input still takes whatever room the marks
       do not need, so an unmarked field reads as a wide box. */
    .field input,
    .field select { width: auto; flex: 1 1 70px; min-width: 60px; }
    .field .fsrc { flex: 0 0 auto; white-space: nowrap; }

    #patid-row { flex-wrap: wrap; }
    #patid-row input { width: 100%; }

    .run-all-bar { flex-wrap: wrap; }

    .res-grid, .res-grid-4 { grid-template-columns: 1fr; }
    .teleon-table-wrap { flex-direction: column; }

    .check-banner { flex-wrap: wrap; }

    .sql-bar { flex-wrap: wrap; }
    .sql-bar input { width: 100%; }
    #sql-table-wrap { overflow-x: auto; }

    .res-table { font-size: 10px; }
    .choice-row label { width: 70px; font-size: 10px; }
    .choice-row input { width: 80px; }
}

/* ---- Agreement overlay ---- */
#agreement-overlay {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.85);
    z-index: 1000;
    align-items: center;
    justify-content: center;
}
#agreement-overlay.active { display: flex; }
/* The height cap and the scroll are the same pair the signup modal carries, and for the
   same reason: the overlay is `fixed`, so anything taller than the screen is simply cut off
   with no way to reach it. On a phone that took I AGREE below the fold — the one control
   the screen exists for. `dvh` is what the phone actually shows: `vh` is measured against
   the viewport with the address bar hidden, so 90vh still overflows while the bar is up. */
#agreement-modal {
    background: var(--surface);
    border: 1px solid var(--border);
    padding: 32px 40px;
    max-width: 560px;
    width: 90%;
    max-height: 90vh;
    max-height: 90dvh;
    overflow-y: auto;
    font-family: var(--font);
    font-size: 13px;
    color: var(--fg);
}
#agreement-text {
    margin: 16px 0 24px;
    line-height: 1.7;
    color: var(--dim);
}
#agreement-text strong { color: var(--fg); }
#agreement-actions { text-align: center; }

/* ---- Personal account ---- */
/* A section that is being re-read while last visit's numbers are still on screen. The
   colour is the loading one used by the calculation statuses — same meaning, same
   vocabulary: the figure below is not wrong, it is not yet the answer. */
.sec-upd {
    color: var(--loading);
    font-size: 10px;
    letter-spacing: 0;
}

.account-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 24px;
    align-items: start;
}
.account-col { min-width: 0; }
.field.read { align-items: baseline; }
.acc-read { color: var(--fg); }
.acc-note { color: var(--idle); font-size: 10px; }
.acc-hint { font-size: 10px; line-height: 1.5; margin-bottom: 8px; }
.account-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin: 8px 0 14px;
}
/* One token, wherever it lands: used for a date or a clock time inside a cell that is
   allowed to wrap between them but not inside either. */
.nb { white-space: nowrap; }
.account-table { width: 100%; margin-top: 6px; }
.account-table td, .account-table th { vertical-align: top; }
.account-table .plus  { color: var(--success); }
.account-table .minus { color: var(--warn); }
.hist-note { color: var(--idle); font-size: 10px; }
.topup-inp { width: 70px; }

.balance-big {
    color: var(--accent);
    font-size: 30px;
    letter-spacing: 1px;
    margin: 4px 0 2px;
}
.balance-big.empty { color: var(--error); }
.balance-big span { color: var(--dim); font-size: 12px; letter-spacing: 0; }
.balance-plan { color: var(--fg); font-size: 11px; margin-bottom: 6px; }
/* what the plan is, in the doctor's words — the backend text, not our internal note */
.plan-desc { color: var(--dim); font-size: 11px; line-height: 1.5; margin-bottom: 6px; max-width: 46em; }
/* a plan that is never billed: says so instead of showing a zero that reads as "empty" */
.balance-service { color: var(--dim); font-size: 12px; margin: 6px 0 2px; }

/* the admin's cabinet is not "my balance" but "what this installation costs us" */
.account-spend { margin-top: 14px; border-top: 1px solid var(--border); padding-top: 10px; }

/* tokens left, next to the account they belong to */
.balance-chip {
    color: var(--accent);
    border: 1px solid var(--border);
    padding: 1px 6px;
    font-size: 11px;
    margin-right: 8px;
}
.balance-chip.empty { color: var(--error); border-color: var(--error); }
/* zero-price plan: a mark instead of a count, so the spot does not sit empty */
.balance-chip.service { color: var(--dim); border-color: var(--border); }

/* refusal for money, with the remedy attached */
.token-gate {
    border: 1px solid var(--error);
    padding: 6px 8px;
    margin: 8px 0;
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    font-size: 11px;
}
.token-gate .gate-msg { color: var(--error); flex: 1; min-width: 160px; line-height: 1.4; }

/* ---- Access request (public) ---- */
.overlay {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.85);
    z-index: 1000;
    align-items: center;
    justify-content: center;
}
.overlay.active { display: flex; }
.overlay .modal {
    background: var(--surface);
    border: 1px solid var(--border);
    padding: 24px 28px;
    max-width: 560px;
    width: 90%;
    max-height: 90vh;
    overflow: auto;
}
.signup-lead { color: var(--dim); font-size: 11px; line-height: 1.6; margin: 10px 0 14px; }
.signup-done { color: var(--fg); font-size: 13px; line-height: 1.6; margin: 16px 0; }
.signup-actions { display: flex; align-items: center; gap: 8px; margin-top: 12px; flex-wrap: wrap; }
/* live layout warning: empty most of the time, so it holds no height until it speaks */
.signup-note { color: var(--warn); font-size: 11px; line-height: 1.5; margin: 6px 0 0; }

/* ---- Admin ---- */
.admin-bar, .admin-filter {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    margin-bottom: 10px;
}
.admin-card {
    border: 1px solid var(--border);
    padding: 8px 10px;
    margin-bottom: 8px;
}
.admin-card .card-head { color: var(--fg); margin-bottom: 4px; }
.admin-card .card-body { font-size: 11px; line-height: 1.5; }
.card-comment { color: var(--dim); margin: 4px 0; }
.card-actions { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 8px; }
.req-state { font-size: 10px; margin-left: 8px; color: var(--idle); }
.req-state.pending  { color: var(--warn); }
.req-state.approved { color: var(--success); }
.req-state.rejected { color: var(--error); }
.approve-form { border-top: 1px solid var(--border); margin-top: 8px; padding-top: 8px; }
.adm-inp {
    background: var(--bg);
    border: 1px solid var(--border);
    color: var(--fg);
    font-family: var(--font);
    font-size: var(--sz);
    padding: 2px 5px;
    width: 140px;
}
.adm-inp.narrow { width: 80px; }
/* A price and the word naming it. Held together so a wrapping row never puts one on the
   line above the other — see priceField() in js/admin.js. */
.adm-price { display: inline-flex; align-items: center; gap: 4px; }
.adm-row-actions { display: flex; gap: 4px; flex-wrap: wrap; }
.spend-row { display: flex; gap: 20px; flex-wrap: wrap; margin-bottom: 10px; }
.spend-cell .spend-val { color: var(--accent); font-size: 20px; }

@media (max-width: 700px) {
    .account-grid { grid-template-columns: 1fr; }

    /* NEW PLAN is a form, not a toolbar: eight controls wrapped by whatever fits made a
       ragged block where `haiku` ended a line and its box began the next (owner, 04.09).
       On a phone each control gets its own line, and the labels line up flush left — the
       eye goes straight down them instead of catching on every indent.
       These rules sit in THIS media block, not the one at the admin section above: the
       base `.admin-filter` and `.adm-inp` declarations come later in the file, and at equal
       specificity the later rule wins whatever the media query says. */
    .admin-filter.plan-form { flex-direction: column; align-items: flex-start; }
    .plan-form .adm-price { justify-content: flex-start; }
    .plan-form .adm-price > .dim { width: 72px; }
    .plan-form .adm-price .adm-inp { width: 90px; }
    .plan-form .btn { align-self: flex-start; margin-top: 4px; }
}

/* show/hide a password: the layout mistake is invisible by definition, so let it be seen */
.btn-eye {
    font-size: 10px;
    padding: 1px 5px;
    opacity: 0.7;
}
