/* ============================================================
   DeuelLab Notebook — application stylesheet.

   Loaded after design/tokens.css, which owns every colour, size and
   density value. Nothing here invents a hex except the two ramps
   declared at the top, both of which are justified in place.
   ============================================================ */

/* ---- fonts -------------------------------------------------
   Self-hosted, never the Google CDN: it transmits visitor IPs to a
   third party. Files come from design/fonts/fetch-fonts.sh; if they
   are absent the stack falls back to system-ui and the interface is
   merely less tight, not broken.
   ------------------------------------------------------------ */

@font-face {
  font-family: "Fira Sans";
  src: url("../design/fonts/woff2/FiraSans-Regular.05f26db4e8fd.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Fira Sans";
  src: url("../design/fonts/woff2/FiraSans-Italic.9a68d78532b5.woff2") format("woff2");
  font-weight: 400; font-style: italic; font-display: swap;
}
@font-face {
  font-family: "Fira Sans";
  src: url("../design/fonts/woff2/FiraSans-Medium.7342c1ed135b.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Fira Sans";
  /* The scale asks for 650 in places. Fira has no 650 — it maps here, to
     600 SemiBold. The browser must never synthesise the missing weight. */
  src: url("../design/fonts/woff2/FiraSans-SemiBold.bf7123c08d0a.woff2") format("woff2");
  font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "JetBrains Mono";
  src: url("../design/fonts/woff2/JetBrainsMono[wght].570921a7dc7c.woff2") format("woff2");
  font-weight: 100 800; font-style: normal; font-display: swap;
}

:root {
  /* ---- interface danger ------------------------------------
     Deliberately NOT --retracted. That token means "PubMed lists a
     retraction notice for this paper" — a scientific fact. A failed
     password field is an interface state. Sharing one token would
     make the loudest colour in the system mean two unrelated things,
     and the retraction badge is the one that must never be routine.
     UZH Berry, which clears 6.5:1 on white.
     ------------------------------------------------------------ */
  --danger:      #BF0D3E;
  --danger-dim:  #FBC6D4;

  /* ---- identity --------------------------------------------
     Avatar discs. Also not the semantic tokens: if --signed also
     means "Anna", it reliably means neither. All six carry white
     text at 4.5:1 or better, and they are theme-independent by
     design — a saturated fill reads on either ground.
     ------------------------------------------------------------ */
  --id-blue:   #0028A5;
  --id-berry:  #8F0A2E;
  --id-apple:  #536B18;
  --id-cyan:   #147082;
  --id-orange: #7E2601;
  --id-slate:  #4D4D4D;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --danger:     #F3537F;
    --danger-dim: #3D0417;
  }
}
:root[data-theme="dark"] {
  --danger:     #F3537F;
  --danger-dim: #3D0417;
}

/* ============================================================
   Reset and base
   ============================================================ */

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

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

body {
  margin: 0;
  min-height: 100vh;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: 2px; }
a:hover { color: var(--accent-2); }

/* Native <button> chrome (grey buttonface, 3D outset border) was never
   reset here, so any button relying only on this base rule — the toolbar's
   icon buttons, .icon-btn — rendered with the browser's own default button
   styling instead of the app's flat/modern look. .btn and its variants set
   their own background/border already, so this is additive there, not a
   behaviour change. */
button {
  font: inherit;
  color: inherit;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
}

code, kbd, samp, .mono { font-family: var(--font-mono); }

/* ============================================================
   The lockup — university mark, hairline, group name.
   Institution first. Appears once in the shell, never on a page.
   ============================================================ */

.lockup {
  display: flex;
  align-items: center;
  gap: 0.62rem;
  font-weight: 600;
  font-size: 0.9rem;
  letter-spacing: -0.01em;
  color: var(--ink);
}
.lockup img,
.lockup svg { height: 23px; width: auto; display: block; }
.lockup .bar { width: 1px; height: 18px; background: var(--rule); flex: none; }
@media (max-width: 900px) { .lockup img, .lockup svg { height: 20px; } }

/* The mark is a single path with no fill attribute, so it inherits.
   Loaded via <img> it cannot inherit, so dark mode inverts it instead. */
:root[data-theme="dark"] .lockup img,
:root:not([data-theme="light"]) .lockup img { filter: invert(1) brightness(1.6); }
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .lockup img { filter: none; }
}

/* ============================================================
   Auth pages — a single sheet on the desk
   ============================================================ */

.auth {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1.4rem;
  padding: 2rem 1rem 4rem;
  background: var(--ground);
}

.auth__sheet {
  width: 100%;
  max-width: 27rem;
  background: var(--surface);
  border-radius: var(--radius-page);
  box-shadow: var(--shadow-page);
  padding: 2rem 2.1rem 2.2rem;
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
}

.auth__sheet--wide { max-width: 34rem; }

.auth h1 {
  margin: 0;
  font-size: var(--text-entry-title);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.02em;
  text-wrap: balance;
}

.auth p {
  margin: 0;
  font-size: var(--text-ui);
  line-height: var(--leading-ui);
  color: var(--ink-2);
}

.auth__foot {
  font-size: var(--text-ui);
  color: var(--ink-3);
  text-align: center;
  max-width: 27rem;
}

/* ============================================================
   Forms
   ============================================================ */

.field { display: flex; flex-direction: column; gap: 0.3rem; }

.field > label,
.field__label {
  font-size: var(--text-label);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.field input[type="text"],
.field input[type="email"],
.field input[type="password"],
.field input[type="number"],
.field input[type="tel"],
.field select,
.field textarea {
  width: 100%;
  font: inherit;
  font-size: var(--text-ui);
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 0.5rem 0.6rem;
  transition: border-color 0.12s, box-shadow 0.12s;
}
.field input:hover, .field select:hover, .field textarea:hover { border-color: var(--ink-3); }

/* ---- filter bar ------------------------------------------------
   The "search + a couple of narrow selects + Filter/Clear" row above
   every library table. It lived in samples.css and, byte-identically,
   in tasks.css, whose own comment admitted the copy ("tasks.css carries
   its own copy of the same rules rather than pulling in an unrelated
   app's stylesheet") — the reason that argument never worked is that
   app.css is the stylesheet every page already loads, so this is where
   a shared component belongs. */

.filter-bar {
  display: flex;
  gap: 0.6rem;
  flex-wrap: wrap;
  align-items: flex-end;
}
.filter-bar .field { flex: none; min-width: 9rem; }
.filter-bar .field--grow { flex: 1; min-width: 12rem; }

/* additional_specs.md: "choose 'my Team' as default (should be
   configurable by the user)" — a small, quiet second control, never
   competing visually with the actual search/filter form above it. */
.filter-bar--default {
  margin-top: -0.3rem;
  gap: 0.4rem;
}
.filter-bar--default .field { min-width: 0; }
.filter-bar--default label { font-size: 0.7rem; color: var(--ink-3); }
.filter-bar--default select {
  font-size: 0.76rem;
  padding: 0.25rem 0.5rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink-2);
}

/* A checkbox and its label on one line. Also formerly duplicated —
   tasks.css and sequences.css each defined it, and samples/item_detail
   .html uses the class while loading neither, so the "link a plasmid to
   a new sequence" form's Circular checkbox rendered unstyled. */
.field--checkbox {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: var(--text-ui);
  color: var(--ink-2);
  cursor: pointer;
}
.field input:focus, .field select:focus, .field textarea:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-wash);
  outline: none;   /* replaced by the ring above, not removed */
}
.field--error input { border-color: var(--danger); }

/* A six-digit code is data, not prose. Mono, spaced, tabular. */
.field input[name$="token"],
.field input[name$="otp_token"],
.field input[inputmode="numeric"] {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 1.1rem;
  letter-spacing: 0.28em;
  text-align: center;
  padding-inline: 0.4rem;
}

/* two_factor renders the code as an IntegerField, so browsers add spinner
   arrows. Nobody nudges a one-time code up by one. */
.field input[type="number"] { appearance: textfield; -moz-appearance: textfield; }
.field input[type="number"]::-webkit-outer-spin-button,
.field input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

.field__help { font-size: 0.78rem; color: var(--ink-3); line-height: 1.45; }
.field__error { font-size: 0.78rem; color: var(--danger); font-weight: 500; }

.form-stack { display: flex; flex-direction: column; gap: 0.95rem; }

/* ============================================================
   Buttons
   ============================================================ */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  font-size: var(--text-ui);
  font-weight: 600;
  padding: 0.5rem 0.9rem;
  border-radius: var(--radius);
  border: 1px solid var(--rule);
  background: var(--surface);
  color: var(--ink-2);
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
  transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.btn:hover { background: var(--surface-2); color: var(--ink); }
.btn[disabled] { opacity: 0.45; cursor: not-allowed; }

.btn--primary {
  background: var(--accent);
  border-color: var(--accent);
  color: #FFFFFF;
}
.btn--primary:hover { background: var(--accent-2); border-color: var(--accent-2); color: #FFFFFF; }

.btn--quiet { border-color: transparent; background: none; color: var(--ink-3); }
.btn--quiet:hover { background: var(--surface-2); color: var(--ink); }

.btn--danger { border-color: var(--danger); color: var(--danger); }
.btn--danger:hover { background: var(--danger-dim); color: var(--danger); }

.btn-row {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
}
.btn-row--split { justify-content: space-between; }

/* ============================================================
   Messages — colour plus icon plus word, never colour alone
   ============================================================ */

.msg {
  display: flex;
  gap: 0.55rem;
  align-items: flex-start;
  padding: 0.65rem 0.8rem;
  border-radius: var(--radius);
  font-size: var(--text-ui);
  line-height: var(--leading-ui);
}
.msg[hidden] { display: none; }
.msg svg { width: 15px; height: 15px; flex: none; margin-top: 0.18rem; }
.msg--info      { background: var(--accent-wash);   color: var(--accent); }
.msg--success   { background: var(--signed-dim);    color: var(--signed); }
.msg--warning   { background: var(--unverified-dim); color: var(--unverified); }
.msg--error     { background: var(--danger-dim);    color: var(--danger); }
.msg--retracted { background: var(--danger-dim);    color: var(--danger); }

/* ============================================================
   Enrolment: QR, secret, backup tokens
   ============================================================ */

.qr {
  display: grid;
  place-items: center;
  padding: 0.9rem;
  /* The QR is black modules on white and stays that way in dark mode —
     inverting it is a scan failure, not a style choice. */
  background: #FFFFFF;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
}
.qr img { display: block; width: 190px; height: 190px; image-rendering: pixelated; }

.secret {
  font-family: var(--font-mono);
  font-size: var(--text-data);
  word-break: break-all;
  background: var(--surface-2);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 0.55rem 0.65rem;
  color: var(--ink-2);
}

.tokens {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
  gap: 0.4rem;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-data);
}
.tokens li {
  background: var(--surface-2);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 0.35rem 0.5rem;
  text-align: center;
  color: var(--ink-2);
}

/* ============================================================
   Chips — a state is a colour AND a shape AND a word
   ============================================================ */

.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.28rem;
  font-size: 0.7rem;
  font-weight: 600;
  padding: 0.16rem 0.42rem;
  border-radius: 3px;
  white-space: nowrap;
}
.chip svg { width: 0.9em; height: 0.9em; flex: none; }
.chip--draft   { background: var(--surface-2); color: var(--ink-2); border: 1px solid var(--rule); }
.chip--sealed  { background: var(--unverified-dim); color: var(--unverified); }
.chip--signed  { background: var(--signed-dim); color: var(--signed); }
.chip--unver   { background: var(--unverified-dim); color: var(--unverified); }
.chip--agent   { background: var(--agent-dim); color: var(--agent); }

/* ============================================================
   Avatars
   ============================================================ */

.avatar {
  width: 26px; height: 26px;
  border-radius: 50%;
  display: grid; place-items: center;
  font-size: 0.62rem; font-weight: 700;
  color: #FFFFFF;
  flex: none;
  user-select: none;
}
.avatar--blue   { background: var(--id-blue); }
.avatar--berry  { background: var(--id-berry); }
.avatar--apple  { background: var(--id-apple); }
.avatar--cyan   { background: var(--id-cyan); }
.avatar--orange { background: var(--id-orange); }
.avatar--slate  { background: var(--id-slate); }

/* ============================================================
   Application shell — one thin bar, and it does not compete
   ============================================================ */

.shell { min-height: 100vh; display: flex; flex-direction: column; background: var(--ground); }

.topbar {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  padding: 0 0.9rem;
  height: 46px;
  flex: none;
  background: var(--surface);
  border-bottom: 1px solid var(--rule);
}
.topbar__right { margin-left: auto; display: flex; align-items: center; gap: 0.5rem; }

.topbar__search {
  margin-left: 0.6rem;
  flex: 1;
  max-width: 22rem;
  background: var(--surface-2);
  border: 1px solid transparent;
  border-radius: var(--radius);
  padding: 0.32rem 0.6rem;
  display: flex;
  align-items: center;
  gap: 0.4rem;
  color: var(--ink-3);
}
.topbar__search:focus-within { border-color: var(--accent); background: var(--surface); }
.topbar__search svg { width: 14px; height: 14px; flex: none; }
.topbar__search input {
  border: 0; background: none; font: inherit; font-size: var(--text-ui);
  color: var(--ink); width: 100%; outline: none;
}
.topbar__search input::placeholder { color: var(--ink-3); }

.icon-btn {
  width: 28px; height: 28px;
  border-radius: var(--radius);
  display: grid; place-items: center;
  color: var(--ink-3);
  flex: none;
}
.icon-btn:hover { background: var(--surface-2); color: var(--ink); }
.icon-btn svg { width: 16px; height: 16px; }

/* fixes.md: Security/Admin/Sign out live under the initials avatar now,
   not as loose buttons in the topbar — same floating-panel look as
   .swatch-popup (position: fixed, toggled by static/js/topbar.js). */
.user-menu { position: relative; }
.user-menu > .avatar { border: 0; cursor: pointer; }
.user-menu > .avatar:hover, .user-menu > .avatar:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.user-menu__popup {
  position: fixed;
  z-index: 65;
  display: none;
  flex-direction: column;
  min-width: 12rem;
  padding: 0.35rem;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: 6px;
  box-shadow: var(--shadow-pop);
}
.user-menu__popup.is-open { display: flex; }
.user-menu__name {
  margin: 0.15rem 0.5rem 0.35rem;
  padding-bottom: 0.35rem;
  border-bottom: 1px solid var(--rule);
  font-weight: 600;
  font-size: var(--text-ui);
  color: var(--ink);
}
.user-menu__popup a,
.user-menu__popup button {
  display: block;
  width: 100%;
  padding: 0.4rem 0.5rem;
  border-radius: var(--radius);
  color: var(--ink-2);
  font: inherit;
  font-size: var(--text-ui);
  text-align: left;
  text-decoration: none;
  background: none;
  border: 0;
  cursor: pointer;
}
.user-menu__popup a:hover,
.user-menu__popup button:hover { background: var(--surface-2); color: var(--ink); }
.user-menu__popup form { margin: 0; }

.shell__body { flex: 1; padding: 2rem 1rem 4rem; }

/* ============================================================
   Left sidebar — fixes.md: "make more use of the left menu."
   A fixed column, sibling to .shell__body (see _sidebar.html for why it's
   a sibling include rather than nested markup) — :has() lets the body's
   margin react to the sidebar's mere presence without every template
   needing its own layout class.
   ============================================================ */

/* .notes-layout (notes/shell.html's rail+canvas row) needs the same
   offset as .shell__body — both are .sidebar's flow-sibling on their
   respective pages, and .sidebar being position:fixed takes it out of
   flow entirely, so without this its first ~208px sits UNDER the fixed
   sidebar (invisible, and — worse — unclickable, since the sidebar still
   paints and hit-tests above static content there). */
.shell:has(.sidebar) .shell__body,
.shell:has(.sidebar) .notes-layout { margin-left: 208px; }

.sidebar {
  position: fixed;
  top: 47px; /* .topbar's height + its 1px border */
  left: 0;
  bottom: 0;
  width: 208px;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  padding: 0.9rem 0.7rem;
  background: var(--surface);
  border-right: 1px solid var(--rule);
  overflow-y: auto;
}

.sidebar__quick-action:empty { display: none; }
.sidebar__quick-action .btn { width: 100%; justify-content: center; }

.sidebar__nav { list-style: none; margin: 0.4rem 0 0; padding: 0; display: flex; flex-direction: column; gap: 0.1rem; }

.sidebar__group-label {
  margin: 0.9rem 0.5rem 0.2rem;
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-3);
}

.sidebar__nav a {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.42rem 0.55rem;
  border-radius: var(--radius);
  color: var(--ink-2);
  font-size: var(--text-ui);
  text-decoration: none;
}
.sidebar__nav a svg { width: 16px; height: 16px; flex: none; }
.sidebar__nav a:hover { background: var(--surface-2); color: var(--ink); }
.sidebar__nav a.is-active { background: var(--accent-wash); color: var(--accent); font-weight: 600; }

.sidebar__toc { margin-top: 0.6rem; padding-top: 0.6rem; border-top: 1px solid var(--rule); }
.sidebar__toc ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.05rem; }
.sidebar__toc a {
  display: block;
  padding: 0.3rem 0.55rem;
  border-radius: var(--radius);
  color: var(--ink-3);
  font-size: 0.78rem;
  text-decoration: none;
  line-height: 1.3;
}
.sidebar__toc a:hover { background: var(--surface-2); color: var(--ink); }
.sidebar__toc a[data-level="3"] { padding-left: 1.1rem; }

.sidebar__recent { margin-top: 0.6rem; padding-top: 0.6rem; border-top: 1px solid var(--rule); }
.sidebar__recent ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.05rem; }
.sidebar__recent a {
  display: block;
  padding: 0.3rem 0.55rem;
  border-radius: var(--radius);
  color: var(--ink-3);
  font-size: 0.78rem;
  text-decoration: none;
  line-height: 1.3;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sidebar__recent a:hover { background: var(--surface-2); color: var(--ink); }
.sidebar__recent a.is-active { color: var(--accent); font-weight: 600; }

@media (max-width: 860px) {
  .sidebar { display: none; }
  .shell:has(.sidebar) .shell__body,
  .shell:has(.sidebar) .notes-layout { margin-left: 0; }
}

.sheet {
  background: var(--surface);
  max-width: 38rem;
  margin: 0 auto;
  padding: 2.2rem 2.4rem 2.6rem;
  border-radius: var(--radius-page);
  box-shadow: var(--shadow-page);
  display: flex;
  flex-direction: column;
  gap: 1rem;
}
.sheet h1 {
  margin: 0;
  font-size: var(--text-entry-title);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.2;
}
.sheet h2 {
  margin: 0.6rem 0 0;
  font-size: var(--text-body-h);
  font-weight: 600;
  letter-spacing: -0.01em;
}
.sheet p { margin: 0; color: var(--ink-2); max-width: var(--measure); }
.sheet--wide { max-width: 72rem; }

/* Dashboard project/manuscript cards. */
.dash-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  gap: 0.9rem;
}
.dash-card {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 0.9rem 1rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  text-decoration: none;
  color: inherit;
  background: var(--surface-3);
}
.dash-card:hover { border-color: var(--accent-dim); background: var(--accent-wash); }
.dash-card__head { display: flex; flex-direction: column; gap: 0.1rem; }
.dash-card__title { font-weight: 600; color: var(--ink); }
.dash-card__meta { font-size: 0.78rem; color: var(--ink-3); }
.dash-card__list { list-style: none; margin: 0; padding: 0.4rem 0 0; border-top: 1px solid var(--rule-soft); display: flex; flex-direction: column; gap: 0.35rem; }
.dash-card__list li { display: flex; justify-content: space-between; gap: 0.6rem; font-size: 0.82rem; }
.dash-card__item-title { color: var(--ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dash-card__item-meta { color: var(--ink-3); flex: none; }
.dash-card__empty { margin: 0.4rem 0 0; color: var(--ink-3); font-size: 0.82rem; }
.dash-card__avatars { display: flex; flex-wrap: wrap; gap: 0.3rem; padding: 0.4rem 0 0; border-top: 1px solid var(--rule-soft); }

/* Generic list table — additional_specs.md's several inventory apps
   (projects, samples, reagents, cell lines, sequences, grants) all need
   "rows of records with a few columns," so this lives once here rather
   than once per app's own stylesheet. */
.data-table { width: 100%; border-collapse: collapse; font-size: var(--text-ui); }
.data-table th {
  text-align: left; font-weight: 600; color: var(--ink-2); font-size: var(--text-label);
  padding: 0.4rem 0.6rem; border-bottom: 1px solid var(--rule);
}
.data-table td { padding: 0.5rem 0.6rem; border-bottom: 1px solid var(--rule); vertical-align: top; }
.data-table tr:last-child td { border-bottom: none; }
.data-table--scroll { overflow-x: auto; }

.meta { font-size: 0.74rem; color: var(--ink-3); display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }

.rule { border: 0; border-top: 1px solid var(--rule-soft); margin: 0.3rem 0; }

.stack { display: flex; flex-direction: column; gap: 0.6rem; }
.row { display: flex; gap: 0.6rem; align-items: center; }

.list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.list li {
  display: flex; align-items: center; gap: 0.6rem;
  min-height: var(--row-data);
  padding: 0.35rem 0;
  border-bottom: 1px solid var(--rule-soft);
  font-size: var(--text-ui);
  color: var(--ink-2);
}
.list li:last-child { border-bottom: 0; }

.muted { color: var(--ink-3); }

/* Manuscript word/character count (static/js/manuscript_stats.js) — a
   quiet stat line under the page, same width as .page so it reads as the
   document's own footer rather than app chrome. */
.word-count {
  width: 100%;
  max-width: 58rem;
  margin: -0.6rem auto 0;
  padding: 0 5.2rem;
  font-size: var(--text-label);
  font-variant-numeric: tabular-nums;
}

.mentions-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.7rem; }
.mentions-list li { padding: 0.6rem 0; border-bottom: 1px solid var(--rule-soft); }
.mentions-list li:last-child { border-bottom: 0; }
.mentions-list__title { font-weight: 600; text-decoration: none; color: var(--accent); }
.mentions-list__title:hover { text-decoration: underline; }
.mentions-list__meta { margin-left: 0.5rem; font-size: 0.74rem; color: var(--ink-3); }
.mentions-list__excerpt { margin: 0.25rem 0 0; color: var(--ink-2); font-size: 0.85rem; }

/* ============================================================
   The notebook — rail (navigation) and canvas (one document)
   ============================================================ */

.notes-layout { flex: 1; display: flex; min-height: 0; }

.rail {
  width: 230px;
  flex: none;
  background: var(--surface-3);
  border-right: 1px solid var(--rule);
  overflow-y: auto;
  padding: 0.6rem 0.5rem 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}
.rail__label {
  font-size: var(--text-label);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-3);
  padding: 0.7rem 0.55rem 0.35rem;
}
.rail__item {
  width: 100%;
  text-align: left;
  padding: 0.42rem 0.55rem;
  border-radius: var(--radius);
  display: flex;
  flex-direction: column;
  gap: 0.16rem;
  text-decoration: none;
  color: inherit;
  transition: background 0.12s;
}
.rail__item:hover { background: var(--surface-2); }
.rail__item--current { background: var(--accent-wash); box-shadow: inset 2px 0 0 var(--accent); }
.rail__title {
  font-size: 0.79rem; font-weight: 550; color: var(--ink); line-height: 1.3;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rail__meta { font-size: 0.66rem; color: var(--ink-3); display: flex; align-items: center; gap: 0.35rem; }
.rail__empty { padding: 0.7rem 0.55rem; font-size: var(--text-ui); }

/* min-height: 0 lets .canvas shrink to the space .notes-layout actually
   has, instead of a flex item's default min-height:auto refusing to
   shrink below its content's height — without it, a long note pushes the
   whole page taller and forces the outer .shell to scroll instead of
   .canvas__scroll alone, taking .docbar/.toolbar along with it even
   though they're meant to stay put (fixes.md: "keep the menubar with the
   formatting options sticky at the top" when scrolling a note). */
.canvas { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; background: var(--ground); }

.docbar {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0 0.9rem;
  height: 42px;
  flex: none;
  background: var(--surface);
  border-bottom: 1px solid var(--rule);
}
.doc-title {
  font-size: 0.82rem; font-weight: 650; color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Click-to-rename — user's ask: "Change the title of a document directly
   in the menubar when the document is open." Only rendered interactive
   (data-editable) when this session may currently edit; a plain span
   otherwise. */
.doc-title[data-editable] {
  cursor: text;
  border-radius: 4px;
  padding: 0.15rem 0.35rem;
  margin: 0 -0.35rem;
}
.doc-title[data-editable]:hover, .doc-title[data-editable]:focus-visible {
  background: var(--surface-2);
  outline: none;
}
.doc-title__input {
  font: inherit;
  font-weight: 650;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--accent);
  border-radius: 4px;
  padding: 0.05rem 0.3rem;
  width: min(28rem, 60vw);
  max-width: 100%;
}
.doc-title__input:focus { outline: none; }
/* An exclamation in a circle, inlined so it needs no network request and
   no build step. Used masked, so the fill colour comes from currentColor. */
:root {
  --icon-alert: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 1.5A6.5 6.5 0 1 0 8 14.5 6.5 6.5 0 0 0 8 1.5Zm-.75 3h1.5v5h-1.5v-5Zm0 6.25h1.5v1.5h-1.5v-1.5Z'/%3E%3C/svg%3E");
}

/* Text for assistive tech only. Not `display:none` — that would remove it
   from the accessibility tree, which is the opposite of the point. Used
   wherever a state is shown graphically (a colour, a glyph) and still needs
   a name: samples/box_detail.html's expiry markers, for instance. */
.u-visually-hidden {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%); overflow: hidden; white-space: nowrap;
}

.doc-saved {
  margin-left: auto; font-size: 0.7rem; color: var(--ink-3);
  font-variant-numeric: tabular-nums; white-space: nowrap;
  display: inline-flex; align-items: center; gap: 0.3rem;
}
/* There is no save button, so this indicator is the whole save contract.
   A failure must not read the same as "All changes saved" — it gets the
   danger colour, weight, and a glyph, and stops being nowrap so the reason
   is legible rather than clipped. */
.doc-saved--warn { color: var(--ink-2); }
.doc-saved--error {
  color: var(--danger); font-weight: 600; font-size: var(--text-ui);
  white-space: normal;
}
.doc-saved--error::before,
.doc-saved--warn::before {
  content: ""; flex: none; width: 0.85em; height: 0.85em;
  background-color: currentColor;
  /* Masked rather than an <img>: it inherits currentColor, so the same glyph
     reads correctly in the warn and error colours and in both themes. */
  -webkit-mask: var(--icon-alert) center / contain no-repeat;
  mask: var(--icon-alert) center / contain no-repeat;
}

.canvas__scroll { flex: 1; overflow-y: auto; padding: 1.5rem 1rem 4rem; display: flex; flex-direction: column; gap: 0.8rem; align-items: center; }
.canvas__empty { flex: 1; display: grid; place-items: center; color: var(--ink-3); font-size: var(--text-ui); }

/* The page itself — prose density, not UI density. This is what
   design/README.md means by "the page is an object": a fixed 38rem sheet on
   the desk, not a form that happens to hold text. */
.page {
  /* "Make the page width correspond to a pdf for notes, so its true
     WYSIWYG." A matching CONTAINER width (50rem, tracking the PDF's real
     793.7px A4-at-96dpi total) was not enough on its own: this app's own
     printed stylesheet (templates/notes/pdf.html) sets body font-size to
     10.5pt (=14px), while --text-body here is 1.0625rem (=17px) — same
     font (Fira Sans, loaded both places), but ~21% bigger on screen, so
     the same pixel-wide column fits fewer words per line and wraps
     earlier than the PDF, confirmed by a real side-by-side ("mit, DNA,
     Unterdrückung..." in the PDF vs "zum, Prognose. ist..." on screen at
     the same line). Shrinking the screen font to 14px to match would
     make on-screen reading uncomfortably small, so instead the column
     itself is widened by that same ~1.214 ratio: the PDF's real content
     width (793.7 - 2*2.2cm margin = 627.4px) times 17/14 is ~761.9px: at
     the still-2.2cm-derived 5.2rem side padding below, that means an
     outer width of ~928px = 58rem (wider than the PDF's own physical
     page — line-wrap parity is the actual goal here, physical-page-
     silhouette parity is not, once the two font sizes differ). Paragraphs
     below intentionally do NOT cap at --measure inside .page — a printed
     page's text fills its whole margin-bound column, so the on-screen one
     should too.

     fixes2.md round 2: "The width of the note is better, but still not
     exactly the same than the PDF. It is ever so slightly to small" —
     confirmed with a real side-by-side (a German paragraph, live editor
     vs. its own PDF export): at the math-derived 928px, the editor wraps
     one word earlier than the PDF on every line of a several-line
     paragraph (e.g. "...empfindlich auf" on screen vs "...empfindlich auf
     replikativen" in the PDF). The 627.4px content-width figure above is
     exactly reproducible; what it doesn't capture is that the PDF is set
     with WeasyPrint (Pango/HarfBuzz text shaping) while the screen is
     whatever browser the reader has open — two different shaping engines
     laying out the *same* Fira Sans file at the *same* effective size
     still don't measure a given string identically (kerning/hinting
     rounding), and that gap compounds over a ~60-character line. Measured
     directly in-browser (Range.getClientRects() per character, .page's
     own max-width swept 928px-1000px, comparing each line's last word
     against the PDF's own line breaks for the same content): the two wrap
     identically only from 931px to 935px — below that the editor drops
     the PDF's line-ending word to the next line (this bug report's exact
     symptom), above it the editor starts fitting a word *more* per line
     than the PDF does. 933px (58.3rem) sits in the middle of that
     measured window, i.e. --page-width is not the pure ~928px math above
     but that figure plus a small empirically-measured shaping-engine
     buffer. design/tokens.css's --page-width is the one place this
     number lives — discussions.css's .thread and citations.css's
     .bibliography read the same token instead of their own copy, per
     this round's other note: "The references and Discussion boxes dont
     have the same width than the note itself." */
  background: var(--surface);
  width: 100%;
  max-width: var(--page-width);
  margin: 0 auto;
  padding: 5.4rem 5.2rem 5.9rem;
  border-radius: var(--radius-page);
  box-shadow: var(--shadow-page);
}
.page__meta { font-size: var(--text-label); color: var(--ink-3); margin: 0 0 1.2rem; }
.page p, .page li {
  font-size: var(--text-body);
  line-height: var(--leading-body);
  color: var(--ink);
}
.page h1, .page h2, .page h3, .page h4 { font-weight: 650; letter-spacing: -0.01em; line-height: 1.3; }
.page img { max-width: 100%; border-radius: var(--radius); display: block; margin: 0.4rem 0; }
.page .msg { margin-bottom: 0.4rem; }
.page pre {
  background: var(--surface-2, var(--ground));
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 0.7rem 0.85rem;
  overflow-x: auto;
  font-size: var(--text-label);
  line-height: 1.5;
}
.page pre code { background: none; padding: 0; border-radius: 0; }
.page code { background: var(--surface-2, var(--ground)); padding: 0.05em 0.3em; border-radius: 3px; }

/* fixes.md: "I did not find the specified table editor and cant enter a
   table to a note." @tiptap/extension-table's node tree
   (table > tableRow > tableHeader|tableCell), both here (editing + the
   read-only canvas) and in templates/notes/pdf.html's own copy. A table
   is data, not prose — deliberately not capped at --measure like
   paragraphs, since rows commonly need the full sheet width; the
   scrollbar is the fallback for whatever still doesn't fit. */
.page table {
  width: 100%;
  max-width: 100%;
  overflow-x: auto;
  border-collapse: collapse;
  margin: 0.4rem 0 0.8rem;
  font-size: var(--text-body);
}
.page th, .page td {
  border: 1px solid var(--rule);
  padding: 0.4rem 0.6rem;
  text-align: left;
  vertical-align: top;
}
.page th { background: var(--surface-2); font-weight: 650; }
.page .ProseMirror td.selectedCell, .page .ProseMirror th.selectedCell { background: var(--accent-wash); }

/* Inline record mentions (samples, and later reagents) — additional_specs.md
   "Inline mentioning of a vial/sample with a link to the storage position."
   A tag/pill, deliberately not styled like .cite's superscript bracket —
   a mention names a physical thing, it isn't numbered prose. */
.mention {
  display: inline-flex;
  align-items: center;
  gap: 0.3em;
  padding: 0.05em 0.5em 0.05em 0.35em;
  border-radius: 999px;
  background: var(--surface-2);
  border: 1px solid var(--rule);
  color: var(--ink);
  font-size: 0.85em;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
}
.mention:hover, .mention:focus-visible { border-color: var(--ink-3); }
.mention svg { width: 0.85em; height: 0.85em; flex: none; }
.mention__meta { font-weight: 400; color: var(--ink-3); }

/* Generic file attachment chip — additional_specs.md "File upload": "Allow
   upload of non-editable documents to notes as attachements (images, raw
   data files)." A PDF/spreadsheet/raw-data drop has no inline render the
   way an image does, so it becomes a downloadable chip instead — same
   pill shape as .mention (both name a thing rather than read as prose)
   but its own icon/colour so a file attachment is never mistaken for a
   record mention. */
.file-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  padding: 0.15em 0.6em 0.15em 0.4em;
  border-radius: 8px;
  background: var(--surface-2);
  border: 1px solid var(--rule);
  color: var(--ink);
  font-size: 0.85em;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  max-width: 100%;
}
.file-chip:hover, .file-chip:focus-visible { border-color: var(--accent); color: var(--accent); }
.file-chip svg { width: 1em; height: 1em; flex: none; color: var(--ink-3); }
.file-chip:hover svg, .file-chip:focus-visible svg { color: var(--accent); }
.file-chip__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.file-chip__size { font-weight: 400; color: var(--ink-3); flex: none; }

/* The editor mount is a ProseMirror contenteditable — same prose rules as
   the read-only render above apply through .page's descendant selectors,
   so only the editor-specific bits (no default outline, placeholder text)
   need adding here. */
.page .ProseMirror { outline: none; }
.page .ProseMirror p.is-editor-empty:first-child::before {
  content: attr(data-placeholder);
  float: left;
  height: 0;
  color: var(--ink-3);
  pointer-events: none;
}

/* ---------- formatting toolbar ----------
   A permanent bar directly under the title (docbar), not a
   selection-triggered pop-up — themed like the rest of the app's chrome
   (surface + ink tokens), not a floating dark chip, so it reads as part of
   the page rather than a tooltip. */
.toolbar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.2rem;
  padding: 0.4rem 0.9rem;
  background: var(--surface);
  border-bottom: 1px solid var(--rule);
}
.toolbar.is-disabled { opacity: 0.5; pointer-events: none; }
.toolbar button {
  width: 28px;
  height: 28px;
  border-radius: var(--radius);
  display: grid;
  place-items: center;
  color: var(--ink-2);
  font-size: 0.78rem;
}
.toolbar button:hover { background: var(--surface-2); color: var(--ink); }
.toolbar button.is-active { background: var(--accent-wash); color: var(--accent); }
.toolbar button svg { width: 15px; height: 15px; }
.toolbar .sep { width: 1px; height: 18px; background: var(--rule); margin: 0 0.3rem; }
.toolbar select {
  height: 28px;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink-2);
  font-size: 0.78rem;
  padding: 0 0.4rem;
}
.toolbar select:hover { background: var(--surface-2); }
#tb-block-type { min-width: 7rem; }
#tb-font-size { min-width: 5.5rem; }

/* Text-colour / highlight swatch popover — reuses .cite-popup's fixed/
   positioned/click-outside-closes mechanics (citations.css) but a grid of
   dots instead of a search list, so it isn't duplicated here. */
.swatch-popup {
  position: fixed;
  z-index: 65;
  display: none;
  flex-direction: column;
  gap: 0.4rem;
  padding: 0.5rem;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: 6px;
  box-shadow: var(--shadow-pop);
}
.swatch-popup.is-open { display: flex; }
.swatch-popup__grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 0.3rem; }
.swatch-popup__swatch {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--swatch-color);
  border: 1px solid var(--rule);
}
.swatch-popup__swatch:hover { outline: 2px solid var(--accent); outline-offset: 1px; }
.swatch-popup__clear {
  font-size: 0.72rem;
  color: var(--ink-3);
  text-align: left;
  padding: 0.15rem 0.2rem;
}
.swatch-popup__clear:hover { color: var(--ink); }

/* ============================================================
   Version history panel — design/README.md: "Panels slide over,
   never dock." A fixed overlay, not a permanent sidebar, so it
   never steals width from the document measure.
   ============================================================ */

.panel {
  position: fixed;
  top: 0; right: 0; bottom: 0;
  width: min(430px, 92vw);
  background: var(--surface);
  border-left: 1px solid var(--rule);
  box-shadow: var(--shadow-pop);
  transform: translateX(102%);
  /* visibility is not interpolable, so it is stepped: on close it flips to
     hidden only after the 0.22s slide has finished, preserving the animation;
     on open (below) it flips immediately so the panel is there to slide in. */
  transition: transform 0.22s cubic-bezier(0.3, 0.8, 0.4, 1),
              visibility 0s linear 0.22s;
  display: flex;
  flex-direction: column;
  z-index: 70;
  /* transform alone leaves a closed panel in the tab order — keyboard
     users tabbed from the document straight into invisible off-screen
     history rows and the reference search field. visibility:hidden
     removes it from focus while still allowing the slide transition. */
  visibility: hidden;
}
.panel.is-open {
  transform: translateX(0);
  visibility: visible;
  transition: transform 0.22s cubic-bezier(0.3, 0.8, 0.4, 1), visibility 0s linear 0s;
}

.panel-h {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.7rem 0.85rem;
  border-bottom: 1px solid var(--rule);
  flex: none;
}
.panel-h h2 { font-size: 0.86rem; font-weight: 650; margin: 0; }
.panel-h__close { margin-left: auto; }

.panel-b { flex: 1; overflow-y: auto; }

.vrow {
  width: 100%;
  text-align: left;
  padding: 0.5rem 0.85rem;
  border: 0;
  border-bottom: 1px solid var(--rule-soft);
  background: none;
  display: flex;
  gap: 0.6rem;
  align-items: baseline;
  font-size: 0.76rem;
  cursor: pointer;
}
.vrow:hover { background: var(--surface-3); }
.vrow[aria-current="true"] { background: var(--accent-wash); }
.vrow .vn {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  color: var(--ink-3);
  width: 2.2rem;
  flex: none;
  font-variant-numeric: tabular-nums;
}
.vrow .vw {
  flex: 1;
  min-width: 0;
  color: var(--ink-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.vrow .agent-tag { color: var(--agent); }
.vrow .vt {
  color: var(--ink-3);
  font-size: 0.68rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.diff { padding: 0.85rem; font-size: 0.82rem; line-height: 1.6; border-top: 1px solid var(--rule); }
.diff .dh {
  display: block;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 0.5rem;
}
.diff p { margin: 0; color: var(--ink-2); }
.diff .diff-meta { margin: 0.7rem 0 0; font-size: 0.72rem; color: var(--ink-3); }
.diff .diff-meta .mono { font-family: var(--font-mono); }
.diff .diff-empty { color: var(--ink-3); font-style: italic; }
.diff .diff-amend { margin: 0 0 0.6rem; font-size: 0.76rem; color: var(--unverified); }
.ins { background: var(--signed-dim); color: var(--signed); border-radius: 2px; padding: 0 0.1em; }
.del { background: var(--retracted-dim); color: var(--retracted); text-decoration: line-through; border-radius: 2px; padding: 0 0.1em; }

.vrow-signed { color: var(--signed); width: 12px; height: 12px; flex: none; }

/* ============================================================
   Sealing & signing — PLAN.md §4.2/§4.3
   ============================================================ */

.amend-bar { align-items: center; }
.amend-bar__body { display: flex; flex-direction: column; gap: 0.5rem; width: 100%; }
.amend-bar input {
  width: 100%;
  max-width: 26rem;
  font: inherit;
  font-size: var(--text-ui);
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 0.4rem 0.6rem;
}
.amend-bar input:focus {
  border-color: var(--unverified);
  box-shadow: 0 0 0 3px var(--unverified-dim);
  outline: none;
}

/* Sign dialog. position: fixed, not the mockup's position: absolute — the
   real shell has no bounded .app container to clip an absolute overlay to,
   same reasoning as .panel's own fixed positioning. */
.scrim {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.34);
  z-index: 80;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 1rem;
}
.scrim.is-open { display: flex; }
.dialog {
  background: var(--surface);
  border-radius: 6px;
  box-shadow: var(--shadow-pop);
  width: min(420px, 100%);
  padding: 1.2rem 1.3rem 1.3rem;
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
}
.dialog h2 { margin: 0; font-size: 1.02rem; font-weight: 650; }
.dialog p { margin: 0; font-size: 0.83rem; color: var(--ink-2); line-height: 1.55; }
.hashbox {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  background: var(--surface-2);
  border: 1px solid var(--rule);
  border-radius: 4px;
  padding: 0.5rem 0.6rem;
  color: var(--ink-3);
  word-break: break-all;
}

/* Discrepancy resolver (frontend/src/editor.js's makeConflictResolver) —
   fixes.md's "similar to git, but don't overcomplicate things": one row
   per conflicting block-range, base for context + two side-by-side picks. */
.conflict-dialog { width: min(640px, 100%); max-height: 85vh; }
.conflict-dialog__list {
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
  overflow-y: auto;
  max-height: 55vh;
  padding-right: 0.2rem;
}
.conflict-row {
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 0.6rem 0.7rem;
}
.conflict-row__base {
  margin: 0 0 0.5rem;
  font-size: 0.76rem;
  color: var(--ink-3);
  text-decoration: line-through;
}
.conflict-row__options { display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; }
.conflict-row__options button {
  text-align: left;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 0.5rem 0.6rem;
  font-size: 0.82rem;
  color: var(--ink);
  cursor: pointer;
}
.conflict-row__options button:hover { border-color: var(--ink-3); }
.conflict-row__options button.is-chosen { border-color: var(--accent); background: var(--accent-wash); }
.conflict-row__label {
  display: block;
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-3);
  margin-bottom: 0.2rem;
}
.agent-warn {
  display: flex;
  gap: 0.5rem;
  background: var(--agent-dim);
  border-radius: 4px;
  padding: 0.6rem 0.7rem;
  font-size: 0.78rem;
  color: var(--agent);
  line-height: 1.45;
  align-items: flex-start;
}
.agent-warn svg { width: 15px; height: 15px; flex: none; margin-top: 0.1rem; }
#sign-otp { max-width: 10rem; }
