/* ==========================================================================
   IJAMPH — Design System
   Premium, clinical aesthetic. Mobile-first, responsive to large PC.
   ========================================================================== */

/* ---- Design tokens ------------------------------------------------------ */
:root {
  /* Brand — calm clinical teal/blue */
  --c-primary:        #0d6c7e;
  --c-primary-600:    #0a5a6a;
  --c-primary-700:    #084855;
  --c-primary-50:     #e8f4f6;
  --c-accent:         #2fa8c0;

  /* Semantic */
  --c-success:        #1b8a5a;
  --c-success-bg:     #e6f5ee;
  --c-warning:        #b5730b;
  --c-warning-bg:     #fbf1e0;
  --c-danger:         #c0392b;
  --c-danger-bg:      #fbeae8;
  --c-info:           #2563a8;
  --c-info-bg:        #e8f0fa;

  /* Neutrals */
  --c-bg:             #f4f7f9;
  --c-surface:        #ffffff;
  --c-surface-2:      #f8fafb;
  --c-border:         #e2e8ec;
  --c-border-strong:  #cbd6dc;
  --c-text:           #16232a;
  --c-text-muted:     #5c6b73;
  --c-text-faint:     #8b98a0;

  /* Typography */
  --font-sans: "Inter", "Segoe UI", system-ui, -apple-system, Roboto, Arial, sans-serif;
  --font-serif: "Georgia", "Times New Roman", serif;

  /* Radius & shadow */
  --r-sm: 6px;
  --r-md: 10px;
  --r-lg: 16px;
  --r-full: 999px;
  --shadow-sm: 0 1px 2px rgba(16, 35, 42, .06), 0 1px 3px rgba(16, 35, 42, .08);
  --shadow-md: 0 4px 12px rgba(16, 35, 42, .08), 0 2px 4px rgba(16, 35, 42, .06);
  --shadow-lg: 0 12px 32px rgba(16, 35, 42, .12), 0 4px 8px rgba(16, 35, 42, .06);

  /* Layout — edge-to-edge width with a decent responsive side margin */
  --header-h: 66px;
  --maxw: 100%;
  --gap: clamp(1rem, 2.5vw, 1.75rem);
}

/* ---- Reset -------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 1.6;
  color: var(--c-text);
  background: var(--c-bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
img, svg, video { display: block; max-width: 100%; height: auto; }
a { color: var(--c-primary); text-decoration: none; }
a:hover { color: var(--c-primary-700); }
button, input, select, textarea { font: inherit; color: inherit; }
h1, h2, h3, h4 { line-height: 1.25; font-weight: 700; letter-spacing: -0.01em; }
:focus-visible { outline: 3px solid var(--c-accent); outline-offset: 2px; border-radius: 4px; }

/* ---- Layout helpers ----------------------------------------------------- */
.container { width: 100%; max-width: var(--maxw); margin-inline: auto; padding-inline: clamp(1.25rem, 4vw, 4rem); }
.page { padding-block: clamp(1.75rem, 4vw, 3rem); }
.stack > * + * { margin-top: 1rem; }
.muted { color: var(--c-text-muted); }
/* Default size for every icon() SVG. A viewBox alone gives no intrinsic
   size, so an icon in a context with no rule of its own would stretch to
   fill its container. Deliberately the weakest icon rule in the file --
   .btn svg, .inline-ic and the rest are more specific and still win. */
.ic { width: 20px; height: 20px; flex: 0 0 auto; }
.inline-ic { display: inline-block; width: 15px; height: 15px; vertical-align: -2px; }

/* The browser's [hidden] rule is a bare attribute selector, so any class that
   sets display -- .btn, .chip, .pager-btn -- silently beats it and the element
   stays visible. Make the attribute win, as it should. */
[hidden] { display: none !important; }

/* icon() emits an SVG with a viewBox but no intrinsic size, so it fills its
   container unless something constrains it. Only .card-head h3 did, which
   meant an icon in any other heading rendered enormous. Size them all. */
h1 svg, h2 svg, h3 svg, h4 svg {
  width: 20px; height: 20px; display: inline-block; vertical-align: -.2em; flex: 0 0 auto;
}
.jcard-foot span { display: inline-flex; align-items: center; gap: .35rem; }
.faint { color: var(--c-text-faint); }
.center { text-align: center; }
.mt-0 { margin-top: 0; }
.hidden { display: none !important; }

/* ==========================================================================
   Header / Navigation
   ========================================================================== */
.site-header {
  position: sticky; top: 0; z-index: 50;
  background: rgba(255,255,255,.9);
  backdrop-filter: saturate(180%) blur(10px);
  border-bottom: 1px solid var(--c-border);
}
.nav {
  height: var(--header-h);
  display: flex; align-items: center; gap: 1rem; flex-wrap: nowrap;
}
/* Shrinkable, and clipped when it does shrink. Without the overflow the
   wordmark kept its intrinsic width past the point the flex row had room for
   it and painted straight over the Sign in / Register buttons on tablet
   widths -- min-width:0 lets a flex item shrink below its content, but nothing
   stops that content spilling out unless the box is told to hide it. */
.brand {
  display: flex; align-items: center; gap: .6rem;
  font-weight: 800; font-size: 1.15rem; color: var(--c-text); letter-spacing: -.02em;
  flex: 0 1 auto; min-width: 0; overflow: hidden;
}
.brand:hover { color: var(--c-text); }
.brand-mark {
  width: 50px; height: 50px; flex: 0 0 50px;
  display: grid; place-items: center; overflow: hidden;
  background: #fff; color: #fff; border-radius: 50%;
  box-shadow: var(--shadow-sm); border: 1px solid var(--c-border);
}
.brand-mark img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; display: block; }
.brand-text { display: block; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.brand small { display: block; font-size: .66rem; font-weight: 600; color: var(--c-text-muted); letter-spacing: .03em; margin-top: -1px; text-transform: none;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: min(60vw, 480px); }

/* Full wordmark logo image (contains the emblem + journal name). */
.brand-logo { display: block; height: 58px; width: auto; max-width: 100%; }
/* On dark surfaces (footer, auth panels) the light-background logo sits on a white card. */
.brand-logo-box { display: inline-flex; background: #fff; border-radius: var(--r-md); padding: .45rem .7rem; box-shadow: var(--shadow-sm); }
.brand-logo-box .brand-logo { height: 46px; }
@media (max-width: 599px) { .brand-logo { height: 40px; } }

.nav-links { display: flex; align-items: center; gap: .25rem; margin-left: auto; }
.nav-links a {
  padding: .5rem .8rem; border-radius: var(--r-sm);
  color: var(--c-text-muted); font-weight: 600; font-size: .93rem;
  transition: background .15s, color .15s;
}
.nav-links a:hover { background: var(--c-primary-50); color: var(--c-primary-700); }
.nav-links a.active { color: var(--c-primary-700); background: var(--c-primary-50); }

/* Nav dropdown (Journal menu) */
.nav-dd { position: relative; }
.nav-dd-toggle {
  display: inline-flex; align-items: center; gap: .3rem;
  padding: .5rem .8rem; border: 0; border-radius: var(--r-sm); background: none; cursor: pointer;
  color: var(--c-text-muted); font-weight: 600; font-size: .93rem; font-family: inherit;
}
.nav-dd-toggle svg { width: 15px; height: 15px; transition: transform .18s; }
.nav-dd-toggle:hover, .nav-dd.open .nav-dd-toggle, .nav-dd.active .nav-dd-toggle { background: var(--c-primary-50); color: var(--c-primary-700); }
.nav-dd.open .nav-dd-toggle svg { transform: rotate(180deg); }
.nav-dd .dropdown-menu { left: 0; right: auto; min-width: 234px; }
.nav-dd .dropdown-menu a svg { width: 15px; height: 15px; color: var(--c-text-faint); }
@media (min-width: 1024px) {
  .nav-dd:hover .dropdown-menu { opacity: 1; visibility: visible; transform: none; }
}

/* Never squeezed: the bell, the user chip and the menu button keep their size
   and the brand gives way instead. */
.nav-actions { display: flex; align-items: center; gap: .6rem; flex: 0 0 auto; }

/* Notification bell */
.bell { position: relative; display: inline-grid; place-items: center; width: 40px; height: 40px; border-radius: var(--r-full); color: var(--c-text-muted); background: transparent; border: 1px solid transparent; cursor: pointer; }
.bell svg { width: 22px; height: 22px; }
.bell:hover { background: var(--c-surface-2); color: var(--c-primary); }
.bell .dot { position: absolute; top: 7px; right: 8px; min-width: 16px; height: 16px; padding: 0 4px; border-radius: var(--r-full); background: var(--c-danger); color: #fff; font-size: .66rem; font-weight: 700; display: grid; place-items: center; border: 2px solid var(--c-surface); }

/* User chip / dropdown */
.userchip { display: flex; align-items: center; gap: .55rem; padding: .3rem .5rem .3rem .3rem; border: 1px solid var(--c-border); border-radius: var(--r-full); background: var(--c-surface); cursor: pointer; max-width: 220px; min-width: 0; }
.userchip:hover { border-color: var(--c-border-strong); }
.userchip > svg { width: 18px; height: 18px; flex: 0 0 18px; color: var(--c-text-faint); }
.avatar { width: 32px; height: 32px; border-radius: var(--r-full); background: linear-gradient(135deg, var(--c-primary), var(--c-accent)); color: #fff; display: grid; place-items: center; font-weight: 700; font-size: .85rem; flex: 0 0 32px; }
.userchip .who { text-align: left; line-height: 1.1; min-width: 0; overflow: hidden; }
.userchip .who b, .userchip .who span { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 130px; }
.userchip .who b { font-size: .88rem; }
.userchip .who span { font-size: .72rem; color: var(--c-text-muted); }

.dropdown { position: relative; }
.dropdown-menu {
  position: absolute; right: 0; top: calc(100% + 8px); min-width: 200px;
  background: var(--c-surface); border: 1px solid var(--c-border); border-radius: var(--r-md);
  box-shadow: var(--shadow-lg); padding: .4rem; opacity: 0; visibility: hidden;
  transform: translateY(-6px); transition: .16s;
}
.dropdown.open .dropdown-menu { opacity: 1; visibility: visible; transform: none; }
.dropdown-menu a, .dropdown-menu button { display: flex; width: 100%; align-items: center; gap: .6rem; padding: .55rem .7rem; border-radius: var(--r-sm); font-size: .9rem; font-weight: 500; color: var(--c-text); background: none; border: 0; text-align: left; cursor: pointer; }
.dropdown-menu a svg, .dropdown-menu button svg { width: 18px; height: 18px; flex: 0 0 18px; color: var(--c-text-faint); }
.dropdown-menu a:hover, .dropdown-menu button:hover { background: var(--c-surface-2); }
.dropdown-menu hr { border: 0; border-top: 1px solid var(--c-border); margin: .35rem 0; }

/* Mobile menu toggle */
.nav-toggle { display: none; width: 42px; height: 42px; border: 1px solid var(--c-border); border-radius: var(--r-sm); background: var(--c-surface); cursor: pointer; place-items: center; }
.nav-toggle svg { width: 22px; height: 22px; }

/* ==========================================================================
   Buttons
   ========================================================================== */
.btn {
  --btn-bg: var(--c-primary); --btn-fg: #fff; --btn-bd: var(--c-primary);
  display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
  padding: .65rem 1.15rem; border-radius: var(--r-sm); border: 1px solid var(--btn-bd);
  background: var(--btn-bg); color: var(--btn-fg); font-weight: 600; font-size: .94rem;
  cursor: pointer; transition: .15s; white-space: nowrap; line-height: 1.2;
}
.btn:hover { filter: brightness(1.06); box-shadow: var(--shadow-sm); }
.btn:active { transform: translateY(1px); }
.btn-lg { padding: .85rem 1.5rem; font-size: 1rem; }
.btn-sm { padding: .4rem .75rem; font-size: .84rem; }
.btn-block { display: flex; width: 100%; }
.btn-ghost { --btn-bg: transparent; --btn-fg: var(--c-primary-700); --btn-bd: var(--c-border-strong); }
.btn-ghost:hover { --btn-bg: var(--c-primary-50); }
.btn-secondary { --btn-bg: var(--c-surface); --btn-fg: var(--c-text); --btn-bd: var(--c-border-strong); }
.btn-secondary:hover { --btn-bg: var(--c-surface-2); }
.btn-success { --btn-bg: var(--c-success); --btn-bd: var(--c-success); }
.btn-danger  { --btn-bg: var(--c-danger); --btn-bd: var(--c-danger); }
.btn:disabled { opacity: .5; cursor: not-allowed; }
.btn svg { width: 18px; height: 18px; }

/* ==========================================================================
   Cards & surfaces
   ========================================================================== */
.card { background: var(--c-surface); border: 1px solid var(--c-border); border-radius: var(--r-lg); box-shadow: var(--shadow-sm); }
.card-pad { padding: clamp(1.15rem, 3vw, 1.75rem); }
.card-head { padding: 1.15rem 1.5rem; border-bottom: 1px solid var(--c-border); display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.card-head h3 { font-size: 1.05rem; }
/* Without an explicit size the global `svg { display:block; height:auto }`
   reset lets a card-head icon expand to the full width of the heading. Every
   other icon context in this file sizes its svg; this one was missing. */
.card-head h3 svg { width: 20px; height: 20px; display: inline-block; vertical-align: -.22em; }
.card-body { padding: 1.5rem; }
.card-foot { padding: 1rem 1.5rem; border-top: 1px solid var(--c-border); background: var(--c-surface-2); border-radius: 0 0 var(--r-lg) var(--r-lg); }
.card-hover { transition: transform .18s, box-shadow .18s, border-color .18s; }
.card-hover:hover { transform: translateY(-3px); box-shadow: var(--shadow-md); border-color: var(--c-border-strong); }

/* Phones: 1.5rem of card padding either side of a 360px screen is a tenth of
   the width spent on nothing, and it is what pushed the card headers' buttons
   into wrapping. The card keeps its shape, just less of the margin. */
@media (max-width: 599px) {
  .card-head { padding: 1rem 1.05rem; gap: .65rem; }
  .card-body { padding: 1.15rem 1.05rem; }
  .card-foot { padding: .9rem 1.05rem; }
  .list-toolbar { padding: .65rem 1.05rem; }
}

/* ==========================================================================
   Badges / pills / status
   ========================================================================== */
.badge { display: inline-flex; align-items: center; gap: .35rem; padding: .2rem .6rem; border-radius: var(--r-full); font-size: .74rem; font-weight: 700; letter-spacing: .02em; text-transform: uppercase; }
.badge::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.badge-uploaded { color: var(--c-info); background: var(--c-info-bg); }
.badge-review   { color: var(--c-warning); background: var(--c-warning-bg); }
.badge-approved { color: var(--c-success); background: var(--c-success-bg); }
.badge-published{ color: #6b21a8; background: #f3e8fd; }
.badge-rejected { color: var(--c-danger); background: var(--c-danger-bg); }
.pill { display: inline-flex; align-items: center; gap: .35rem; padding: .25rem .7rem; border-radius: var(--r-full); font-size: .78rem; font-weight: 600; background: var(--c-primary-50); color: var(--c-primary-700); }
/* A pill that goes somewhere -- a keyword on a published article, which leads
   to everything else filed under it. Lifts on hover so it reads as clickable
   rather than as one more label. */
a.pill-link { transition: background .15s, color .15s, transform .15s; }
a.pill-link:hover, a.pill-link:focus-visible {
  background: var(--c-primary); color: #fff; transform: translateY(-1px);
}
a.pill-link:hover svg { color: currentColor; }

/* ==========================================================================
   Forms
   ========================================================================== */
.form-grid { display: grid; gap: 1.15rem; }
/* align-content:start stops the auto rows stretching when a field is placed
   beside a taller sibling. Without it, a neighbour carrying a hint or error
   line inflates this field's label and input to fill the row, pushing the
   control out of alignment. */
.field { display: grid; gap: .4rem; align-content: start; }
/* Grid items default to min-width: auto, so a field would not shrink below the
   intrinsic width of the control inside it -- the same reason .detail-panel > *
   needs this. Without it the sign-in card pushed a 360px screen sideways. */
.field > * { min-width: 0; }
.field > label { font-size: .88rem; font-weight: 600; color: var(--c-text); }
.field .req { color: var(--c-danger); }
.field .hint { font-size: .8rem; color: var(--c-text-muted); }
.input, .select, .textarea {
  width: 100%; padding: .7rem .85rem; border: 1px solid var(--c-border-strong);
  border-radius: var(--r-sm); background: var(--c-surface); font-size: .95rem;
  transition: border-color .15s, box-shadow .15s;
}
.input:focus, .select:focus, .textarea:focus { border-color: var(--c-primary); box-shadow: 0 0 0 3px var(--c-primary-50); outline: none; }
.textarea { resize: vertical; min-height: 120px; }
.input-group { display: flex; align-items: center; gap: .55rem; border: 1px solid var(--c-border-strong); border-radius: var(--r-sm); padding: 0 .8rem; background: var(--c-surface); }
.input-group:focus-within { border-color: var(--c-primary); box-shadow: 0 0 0 3px var(--c-primary-50); }
/* min-width: 0 because a flex item defaults to min-width: auto, and an <input>
   has an intrinsic width of about 20 characters. Without this the field refuses
   to shrink past ~247px and pushes narrow screens sideways -- it was doing that
   to the sign-in card at 360px. */
.input-group input { border: 0; padding: .7rem 0; flex: 1; min-width: 0; background: none; }
.input-group input:focus { outline: none; box-shadow: none; }
/* A unit printed inside the field, after the value -- "10 %". Part of the
   control's appearance, not its content, so it is aria-hidden where used and
   the label carries the meaning. */
.input-suffix { flex: 0 0 auto; color: var(--c-text-muted); font-weight: 600; }
.input-group svg { width: 18px; height: 18px; color: var(--c-text-faint); flex: 0 0 18px; }

/* Password reveal toggle. Icon-only, so the accessible name lives on
   aria-label and the on/off state on aria-pressed -- both updated in app.js.
   Which icon shows is driven purely by aria-pressed, so state and visuals
   cannot drift apart. */
.pw-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 auto; padding: .3rem; margin-right: -.25rem;
  border: 0; background: none; cursor: pointer; border-radius: var(--r-sm);
  color: var(--c-text-muted); line-height: 0;
}
.pw-toggle:hover { color: var(--c-primary); background: var(--c-primary-50); }
.pw-toggle:focus-visible { outline: 3px solid var(--c-accent); outline-offset: 1px; }
.pw-toggle svg { color: currentColor; }
.pw-ic { display: inline-flex; }
.pw-ic-hide { display: none; }
.pw-toggle[aria-pressed="true"] .pw-ic-show { display: none; }
.pw-toggle[aria-pressed="true"] .pw-ic-hide { display: inline-flex; }
.pw-toggle[aria-pressed="true"] { color: var(--c-primary); }
.check { display: flex; align-items: flex-start; gap: .6rem; font-size: .9rem; }
.check input { width: 18px; height: 18px; margin-top: .15rem; accent-color: var(--c-primary); flex: 0 0 18px; }
.two-col { display: grid; gap: 1.15rem; }
.name-row { display: grid; gap: 1.15rem; }
.field-error { color: var(--c-danger); font-size: .8rem; }

/* File dropzone */
.dropzone {
  border: 2px dashed var(--c-border-strong); border-radius: var(--r-md);
  padding: 2rem 1.5rem; text-align: center; background: var(--c-surface-2);
  cursor: pointer; transition: .18s;
}
.dropzone:hover, .dropzone.dragover { border-color: var(--c-primary); background: var(--c-primary-50); }
.dropzone .dz-icon { width: 48px; height: 48px; margin: 0 auto .75rem; color: var(--c-primary); }
.dropzone b { color: var(--c-primary-700); }
.file-item { display: flex; align-items: center; gap: .8rem; padding: .8rem 1rem; border: 1px solid var(--c-border); border-radius: var(--r-md); background: var(--c-surface); }
.file-item .fi-icon { width: 40px; height: 40px; flex: 0 0 40px; border-radius: var(--r-sm); background: var(--c-danger-bg); color: var(--c-danger); display: grid; place-items: center; font-weight: 700; font-size: .72rem; }
.file-item .fi-meta { flex: 1; min-width: 0; }
.file-item .fi-meta b { display: block; font-size: .9rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.file-item .fi-meta span { font-size: .8rem; color: var(--c-text-muted); }

/* ==========================================================================
   Tables
   ========================================================================== */
/* A table too wide for the screen scrolls inside this wrapper. On a phone that
   is most of them, and until now nothing said so: the Role, Status and Joined
   columns simply were not there, with no edge, no shadow and no hint that
   moving a finger sideways would find them.

   The two shadows are painted by the wrapper itself and pinned to its scroll
   position, so they appear only on the side there is more to see: a shadow on
   the right while content is hidden to the right, gone once you reach the end.
   No JavaScript, no scroll listener -- background-attachment does it.
   (The technique is Roman Komarov's: two "cover" gradients in the local
   attachment layer sit over two "shadow" gradients in the scroll layer, so each
   shadow is hidden exactly when its edge is reached.) */
.table-wrap {
  overflow-x: auto;
  border: 1px solid var(--c-border);
  border-radius: var(--r-lg);
  background:
    /* the two covers, which move with the content */
    linear-gradient(to right, var(--c-surface) 30%, rgba(255,255,255,0)) left center,
    linear-gradient(to left,  var(--c-surface) 30%, rgba(255,255,255,0)) right center,
    /* the two shadows, which stay put */
    radial-gradient(farthest-side at 0 50%, rgba(15,23,42,.14), rgba(15,23,42,0)) left center,
    radial-gradient(farthest-side at 100% 50%, rgba(15,23,42,.14), rgba(15,23,42,0)) right center;
  background-repeat: no-repeat;
  background-size: 42px 100%, 42px 100%, 16px 100%, 16px 100%;
  background-attachment: local, local, scroll, scroll;
  background-color: var(--c-surface);
}
table.data { width: 100%; border-collapse: collapse; min-width: 640px; }
/* Top-aligned, as .data-compact already is. A queue row is as tall as its
   stack of action buttons, and centring left the title floating in the middle
   of an otherwise empty cell -- it reads as a row once everything starts on
   the same line. */
table.data th, table.data td { padding: .85rem 1.1rem; text-align: left; vertical-align: top; border-bottom: 1px solid var(--c-border); }
table.data thead th { font-size: .76rem; text-transform: uppercase; letter-spacing: .04em; color: var(--c-text-muted); background: var(--c-surface-2); font-weight: 700; }
table.data tbody tr:last-child td { border-bottom: 0; }
table.data tbody tr:hover { background: var(--c-primary-50); }
table.data td .t-title { font-weight: 600; color: var(--c-text); }
table.data td .t-sub { font-size: .8rem; color: var(--c-text-muted); }

/* Below a laptop, 640px across six columns is not room for a name and a date:
   every cell wrapped to three lines and the rows grew to the height of a
   paragraph. The table is given the width it actually needs and .table-wrap
   scrolls it sideways instead -- a wide table you push across reads better
   than a narrow one you cannot. */
@media (max-width: 899px) {
  table.data { min-width: 46rem; }
  table.data th, table.data td { padding: .7rem .8rem; }
  /* Short values -- a name, a count, a status, a date -- keep to their line.
     Left to wrap they turned an author into three lines of one word each.
     Excludes .data-compact, whose cells hold affiliations and want to wrap. */
  table.data:not(.data-compact) th,
  table.data:not(.data-compact) td { white-space: nowrap; }
  /* The first column is the title, which is long and should wrap. */
  table.data:not(.data-compact) th:first-child,
  table.data:not(.data-compact) td:first-child { white-space: normal; min-width: 15rem; }
}

/* ==========================================================================
   Pagination (server-side list pager)
   Mobile-first: everything stacks and centres; from 600px up the info,
   page-size picker and page links sit on one row.
   ========================================================================== */
.pager { display: flex; flex-direction: column; align-items: center; gap: .85rem; text-align: center; }
.pager-info { font-size: .86rem; margin: 0; }

/* While a fragment is being fetched: dim slightly and block stray clicks on
   controls that are about to be replaced. */
#userList { transition: opacity .12s ease; }
/* Dimming is handled by the shared .is-loading rules below, which fade the
   children and leave the waiting mark on top at full strength; .is-refreshing
   there takes the clicks out from the first millisecond of the request. */

/* ==========================================================================
   Loading feedback: page progress bar, busy buttons, busy regions.
   ========================================================================== */
@keyframes mj-spin { to { transform: rotate(360deg); } }
@keyframes mj-sweep { from { transform: translateX(-100%); } to { transform: translateX(400%); } }

#appProgress {
  position: fixed; inset: 0 0 auto 0; height: 4px; z-index: 2000;
  overflow: hidden; pointer-events: none;
  background: var(--c-primary-50);
  opacity: 0; transition: opacity .18s ease;
}
#appProgress.is-on { opacity: 1; box-shadow: 0 1px 6px rgba(13, 108, 126, .45); }
#appProgress span {
  display: block; height: 100%; width: 30%;
  background: var(--c-primary);
  animation: mj-sweep 1s linear infinite;
}

/* The page goes soft while it waits. Painted by an overlay rather than a
   filter on the content: filter would make every fixed-position child
   (header, dialogs, this bar) position against it and break the layout.
   The delay means a fast response never flashes it. */
.is-loading-page::before {
  content: ""; position: fixed; inset: 0; z-index: 900; pointer-events: none;
  /* NO backdrop-filter. Blurring the entire viewport put the whole page
     through a rasterise-and-blur pass on every AJAX call, which on a page this
     size reads as the screen going soft and coming back -- movement where the
     honest answer is "nothing has changed yet". A plain tint says the same
     thing, costs the compositor nothing, and leaves text readable while it
     waits. */
  background: rgba(247, 250, 251, .45);
  animation: mj-fade-in .25s ease .35s both;
}
/* z-index 900 keeps this above the page and the sticky header (50) but below
   .modal-overlay (1000), so a dialog you are working in stays sharp. */
@keyframes mj-fade-in { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .is-loading-page::before { animation: none; }
}

/* Reusable spinner, for anywhere a wait needs a mark of its own. */
.spinner {
  display: inline-block; width: 1.05em; height: 1.05em; vertical-align: -.15em;
  border: 2px solid var(--c-border-strong); border-top-color: var(--c-primary);
  border-radius: 50%; animation: mj-spin .7s linear infinite;
}
.load-block {
  display: flex; align-items: center; gap: .6rem;
  margin: 0; padding: .5rem 0; color: var(--c-text-muted);
}

/* A pressed button: the label is held in place (so nothing reflows) and a
   spinner takes its visual place. --btn-fg keeps it legible on any variant. */
.btn.is-busy {
  position: relative; color: transparent !important;
  cursor: progress; pointer-events: none;
}
.btn.is-busy > svg, .btn.is-busy > * { visibility: hidden; }
.btn.is-busy::after {
  content: ""; position: absolute; top: 50%; left: 50%;
  width: 1.05em; height: 1.05em; margin: -.525em 0 0 -.525em;
  border: 2px solid var(--btn-fg); border-top-color: transparent;
  border-radius: 50%; opacity: .9;
  animation: mj-spin .7s linear infinite;
}

/* A link or control that kicked off an AJAX call. It cannot use ::after like
   .btn.is-busy (that slot is often an icon), so the spinner replaces the
   leading icon instead and the label stays readable. */
.btn.is-working { pointer-events: none; opacity: .85; }
.btn.is-working > svg { display: none; }
.btn.is-working::before {
  content: ""; flex: 0 0 auto; width: 1em; height: 1em;
  border: 2px solid currentColor; border-top-color: transparent;
  border-radius: 50%; opacity: .8;
  animation: mj-spin .7s linear infinite;
}
/* The panel that is about to be filled, so the wait is visible where the
   content will appear rather than only at the top of the window. */
.load-panel {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: .85rem;
  padding: 2.5rem 1rem; color: var(--c-text-muted); text-align: center;
  background: var(--c-surface); border: 1px solid var(--c-border);
  border-radius: var(--r-lg);
}
.load-panel .spinner { width: 1.4rem; height: 1.4rem; }
/* Inside the notifications drawer the panel is the drawer -- a second bordered
   card inside it just draws a box around the mark. */
#notifPanelBody .load-panel { border: 0; background: none; }

/* ---------------------------------------------------------------------------
   The waiting mark (inc/loader.php)

   A spinning ring says "something is happening" and nothing else. This says
   what: a manuscript, its lines being written on, under the same slow pulse
   the hero artwork beats to. It is the one animation a reader of this site
   sees while a submission is saved or fetched, so it is drawn from the same
   vocabulary as the rest of the journal rather than borrowed from a generic
   loading widget.

   Every base style below is the finished state and each animation returns to
   it, so the global reduced-motion rule leaves a complete drawing rather than
   a frozen half-drawn one.
   --------------------------------------------------------------------------- */
.jload { display: inline-flex; flex-direction: column; align-items: center; gap: .6rem; }
.jload-art { width: 54px; height: 54px; flex: 0 0 auto; overflow: visible; }
.jload-page { fill: var(--c-surface); stroke: var(--c-border-strong); stroke-width: 1.6; }
/* The three ruled lines fill left to right, one after another, like a page
   being written. */
.jload-line {
  stroke: var(--c-primary); stroke-width: 2.4; stroke-linecap: round;
  stroke-dasharray: 20; stroke-dashoffset: 0;
  animation: jload-write 1.8s ease-in-out infinite;
}
.jload-line-2 { animation-delay: .18s; }
.jload-line-3 { animation-delay: .36s; }
/* The seal: the mark of a reviewed paper, beating at the resting rhythm the
   hero artwork uses. */
.jload-seal { fill: var(--c-accent); opacity: .9; transform-origin: 38px 38px; animation: jload-beat 1.8s ease-out infinite; }
.jload-text { font-size: .9rem; color: var(--c-text-muted); }
/* The narration: what the server is doing right now, replaced line by line as
   the work moves on. Fades each new line in so the change is noticed without
   the block jumping about. */
.jload-step {
  font-size: .84rem; color: var(--c-primary-700); font-weight: 600;
  min-height: 1.3em; text-align: center;
  animation: jload-step-in .35s ease-out;
}
/* Second line, revealed only when a wait runs long -- see app.js. */
.jload-slow { font-size: .82rem; color: var(--c-text-faint); max-width: 34ch; text-align: center; }

@keyframes jload-step-in {
  from { opacity: 0; transform: translateY(3px); }
  to   { opacity: 1; transform: none; }
}

/* Laid over a form while it saves. The form stays underneath rather than being
   replaced, so a validation error comes back to what was typed. */
.save-veil {
  position: absolute; inset: 0; z-index: 5;
  display: grid; place-items: center; gap: .6rem;
  /* Opaque enough to hold the mark clearly without blurring the form under it
     -- the form is what the user is about to come back to if anything fails. */
  background: rgba(255, 255, 255, .92);
}
.modal-body { position: relative; }

@keyframes jload-write {
  0%        { stroke-dashoffset: 20; opacity: .25; }
  35%, 70%  { stroke-dashoffset: 0;  opacity: 1; }
  100%      { stroke-dashoffset: 0;  opacity: .25; }
}
@keyframes jload-beat {
  0%, 100% { transform: scale(1);    opacity: .55; }
  12%      { transform: scale(1.25); opacity: 1; }
  26%      { transform: scale(1.06); opacity: .8; }
  40%      { transform: scale(1.18); opacity: .95; }
}

/* A region waiting to be replaced by an AJAX response.

   The old ring spinner lived here as a ::after pseudo-element. It was the last
   place in the app still showing a loader that was not the journal mark, so it
   is gone: regionBusy() now appends a real .region-load element carrying the
   same mark and the same narrated stages as every other wait. */
.is-loading { position: relative; }
.is-loading > * { opacity: .45; transition: opacity .15s; }
/* The mark floating above the dimmed content is not part of that content. */
.is-loading > .region-load { opacity: 1; }

/* A refresh is under way, but not yet one worth showing.
   The guard has to start the moment the request does -- clicking a row that is
   being replaced is how you end up with two requests racing for the same panel
   -- while the dim waits with the mark it belongs to. So blocking the clicks
   and showing the wait are two classes, not one: this is the half that costs
   nothing to look at. */
.is-refreshing { pointer-events: none; }

.region-load {
  position: absolute; z-index: 4; inset: 0;
  display: flex; align-items: flex-start; justify-content: center;
  padding-top: clamp(2rem, 12vh, 6rem);
  /* Nothing underneath is clickable while it is being replaced. */
  pointer-events: none;
}
/* The mark sits on a card of its own, or it reads as part of the dimmed rows
   behind it rather than as something in front of them. */
.region-load .jload {
  padding: 1.15rem 1.6rem;
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  border-radius: var(--r-lg);
  box-shadow: 0 10px 28px rgba(15, 23, 42, .12);
}

/* Rows-per-page strip above the table — aligns under the search box. */
.list-toolbar { display: flex; justify-content: flex-start; padding: .7rem 1.5rem; border-bottom: 1px solid var(--c-border); background: var(--c-surface-2); }
.pager-size { display: flex; align-items: center; gap: .5rem; font-size: .86rem; color: var(--c-text-muted); }
.pager-size .select { width: auto; padding: .35rem 1.9rem .35rem .6rem; font-size: .86rem; }

.pager-nav { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: .3rem; }
.pager-btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 2.25rem; height: 2.25rem; padding: 0 .5rem;
  border: 1px solid var(--c-border); border-radius: var(--r-md);
  background: var(--c-surface); color: var(--c-text);
  font-size: .86rem; font-weight: 600; line-height: 1;
  transition: background .15s, border-color .15s, color .15s;
}
.pager-btn svg { width: 16px; height: 16px; }
a.pager-btn:hover { background: var(--c-primary-50); border-color: var(--c-primary); color: var(--c-primary-700); }
.pager-btn.is-current { background: var(--c-primary); border-color: var(--c-primary); color: #fff; cursor: default; }
.pager-btn.is-disabled { opacity: .4; cursor: not-allowed; color: var(--c-text-muted); }
.pager-gap { padding: 0 .15rem; color: var(--c-text-muted); }

/* Search form in the users card header. Opt-in wrapping so the heading and
   the form stack instead of squashing on narrow screens. */
.card-head-stack { flex-wrap: wrap; }
.user-search { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.user-search .input-group { max-width: 240px; margin: 0; }

/* Visually hidden but still read by screen readers. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* On narrow phones the numbered links eat the row — keep first/prev/next/last
   and the current page, and hide the rest rather than wrapping to 3 lines. */
@media (max-width: 479px) {
  .pager-num:not(.is-current), .pager-gap { display: none; }
  .user-search .input-group { max-width: none; flex: 1 1 100%; }
}

@media (min-width: 600px) {
  .pager { flex-direction: row; flex-wrap: wrap; justify-content: space-between; text-align: left; }
  .pager-info { margin-right: auto; }
  /* Sit under the search button once the card header is side-by-side. */
  .list-toolbar { justify-content: flex-end; }
}

/* ==========================================================================
   Multi-step submission wizard
   Mobile-first: the stepper scrolls horizontally on small screens rather than
   wrapping into an unreadable stack.
   ========================================================================== */
.stepper {
  list-style: none; padding: 0 0 .25rem 0; margin: 0 0 var(--gap) 0;
  display: flex; gap: .4rem; overflow-x: auto; scrollbar-width: thin;
}
.stepper .step { flex: 1 1 0; min-width: 9.5rem; }
.stepper .step > a,
.stepper .step > span {
  display: flex; align-items: center; gap: .6rem;
  padding: .7rem .9rem; border-radius: var(--r-md);
  background: var(--c-surface); border: 1px solid var(--c-border);
  color: var(--c-text-muted); font-size: .9rem; font-weight: 600;
  white-space: nowrap; height: 100%;
}
.stepper .step > a:hover { border-color: var(--c-primary); color: var(--c-primary-700); }
.stepper .step-no {
  display: inline-grid; place-items: center; flex: 0 0 26px;
  width: 26px; height: 26px; border-radius: var(--r-full);
  background: var(--c-surface-2); border: 1px solid var(--c-border-strong);
  font-size: .82rem; line-height: 1;
}
.stepper .step-no svg { width: 14px; height: 14px; }
.stepper .is-current > a,
.stepper .is-current > span { border-color: var(--c-primary); color: var(--c-primary-700); background: var(--c-primary-50); }
.stepper .is-current .step-no { background: var(--c-primary); border-color: var(--c-primary); color: #fff; }
.stepper .is-done .step-no { background: var(--c-success); border-color: var(--c-success); color: #fff; }
.stepper .is-done > a,
.stepper .is-done > span { color: var(--c-text); }

/* Wizard footer / action bar */
.wizard-actions { display: flex; justify-content: space-between; align-items: center; gap: .75rem; flex-wrap: wrap; margin-top: .5rem; }
.wizard-actions-right { display: flex; gap: .6rem; flex-wrap: wrap; }
.wizard-foot { display: flex; justify-content: space-between; align-items: center; gap: 1rem; flex-wrap: wrap; }

/* Step 1 requirement checklist: roomy rows, easy to scan and tick. */
.requirements { list-style: none; padding: 0; margin: 0; display: grid; gap: .85rem; }
.requirements li {
  padding: .85rem 1rem; border: 1px solid var(--c-border); border-radius: var(--r-md);
  background: var(--c-surface-2);
}
.requirements li:hover { border-color: var(--c-border-strong); }
.requirements .check { align-items: flex-start; font-size: .92rem; line-height: 1.5; margin: 0; }
.requirements .field-error { display: block; margin-top: .4rem; }

/* Ethics approval, grouped on the manuscript step so the three fields read as
   one requirement rather than three unrelated ones. */
.ethics-block {
  margin: 0; padding: 1rem 1.1rem;
  border: 1px solid var(--c-border-strong); border-radius: var(--r-md);
  background: var(--c-surface-2);
  display: grid; gap: .9rem;
}
.ethics-block legend {
  padding: 0 .4rem; font-size: .9rem; font-weight: 700; color: var(--c-text);
}

/* ---------------------------------------------------------------------------
   Rich text editor (assets/js/editor.js), matched to the app's input styling.
   --------------------------------------------------------------------------- */
.rte { border: 1px solid var(--c-border-strong); border-radius: var(--r-sm); background: var(--c-surface); }
.rte:focus-within { border-color: var(--c-primary); box-shadow: 0 0 0 3px var(--c-primary-50); }

.rte-toolbar {
  display: flex; flex-wrap: wrap; align-items: center; gap: .15rem;
  padding: .35rem .4rem;
  background: var(--c-surface-2);
  border-bottom: 1px solid var(--c-border);
  border-radius: var(--r-sm) var(--r-sm) 0 0;
}
.rte-sep { width: 1px; align-self: stretch; margin: .15rem .3rem; background: var(--c-border-strong); }

.rte-btn {
  min-width: 30px; height: 28px; padding: 0 .4rem;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid transparent; border-radius: var(--r-sm);
  background: transparent; color: var(--c-text);
  font-size: .82rem; font-family: inherit; line-height: 1; cursor: pointer;
}
.rte-btn:hover:not(:disabled) { background: var(--c-primary-50); border-color: var(--c-border-strong); }
.rte-btn:focus-visible { outline: 2px solid var(--c-primary); outline-offset: 1px; }
.rte-btn:disabled { opacity: .35; cursor: default; }
/* Bigger where the pointer is a finger. 30x28 is comfortable with a mouse and
   fiddly with a thumb -- and the editor toolbar is where an author writing on a
   tablet spends their time. pointer: coarse is the query that means "touch",
   so a desktop toolbar keeps its compact size. */
@media (pointer: coarse) {
  .rte-btn { min-width: 38px; height: 38px; font-size: .9rem; }
  .rte-toolbar { gap: .2rem; }
}
/* The drawn alignment icons; they colour themselves from the button. */
.rte-btn svg { display: block; }

/* --- The image placement row ---
   A second toolbar that appears only while an image is selected, so nine
   placement controls are not sitting greyed out above every section that has
   no picture in it. It scrolls sideways rather than wrapping onto three lines
   on a phone, which would push the text out of view every time a figure is
   tapped. */
.rte-imagebar {
  display: flex; align-items: center; gap: .15rem;
  padding: .3rem .4rem;
  background: var(--c-primary-50);
  border-bottom: 1px solid var(--c-border);
  overflow-x: auto; scrollbar-width: thin;
}
.rte-imagebar[hidden] { display: none; }
.rte-imagebar-label {
  flex: none; margin-right: .25rem; padding-left: .1rem;
  font-size: .72rem; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--c-text-muted);
}
.rte-imagebar .rte-btn { flex: none; }
.rte-btn-text { padding: 0 .55rem; font-size: .78rem; }
/* The placement the selected image actually has. */
.rte-btn.is-on {
  background: var(--c-primary); border-color: var(--c-primary); color: #fff;
}
.rte-btn.is-on:hover:not(:disabled) { background: var(--c-primary); }
.rte-btn-danger { color: var(--c-danger); }
@media (pointer: coarse) {
  .rte-btn-text { padding: 0 .7rem; }
}

.rte-body {
  min-height: 9rem; max-height: 32rem; overflow-y: auto;
  padding: .7rem .85rem;
  font-size: .95rem; line-height: 1.65;
  border-radius: 0 0 var(--r-sm) var(--r-sm);
}
.rte-body:focus { outline: none; }
/* Placeholder. An "empty" editor still contains "<p><br></p>", so :empty never
   matches -- editor.js sets .is-empty instead, which it can judge properly. */
.rte-body.is-empty::before {
  content: attr(data-placeholder);
  display: block; color: var(--c-text-faint); pointer-events: none; height: 0;
}

/* Content inside the editor and content rendered back out share these, so what
   an author writes looks the same as what an editor later reads. */
.rte-body > :first-child { margin-top: 0; }
.rte-body > :last-child { margin-bottom: 0; }
.rte-body p, .detail-rich p, .pub-body p { margin: 0 0 .7rem; }
.rte-body h2, .rte-body h3, .detail-rich h2, .detail-rich h3, .pub-body h2, .pub-body h3 {
  margin: 1rem 0 .5rem; line-height: 1.3;
}
.rte-body h2, .detail-rich h2, .pub-body h2 { font-size: 1.15rem; }
.rte-body h3, .detail-rich h3, .pub-body h3 { font-size: 1rem; }
.rte-body blockquote, .detail-rich blockquote, .pub-body blockquote {
  margin: 0 0 .7rem; padding: .3rem 0 .3rem .8rem;
  border-left: 3px solid var(--c-border-strong); color: var(--c-text-muted);
}
.rte-body ul, .rte-body ol, .detail-rich ul, .detail-rich ol, .pub-body ul, .pub-body ol {
  margin: 0 0 .7rem 1.2rem;
}

/* An <a> with no href is not a link. Word writes bare anchors as bookmark
   markers and leaves them all through an exported document, so they arrive on
   any paste out of one; left to the global link rule they render in the brand
   colour and read as blue text the author never asked for, leading nowhere. */
.rte-body a:not([href]), .detail-rich a:not([href]), .pub-body a:not([href]),
.cmt-bubble a:not([href]), .article-prose a:not([href]) {
  color: inherit; text-decoration: none; cursor: text;
}

/* Images are stored inline as data URIs; cap them to the column either way. */
.rte-body img, .detail-rich img, .pub-body img, .cmt-bubble img,
.article-prose img {
  max-width: 100%; height: auto; border-radius: var(--r-sm);
}

/* Tables, in the editor and in everything that renders stored content --
   including .article-prose, the published article itself, which is where a
   results table pasted out of Excel finally gets read. A wide table scrolls its
   own container rather than stretching the page. */
.rte-body table, .detail-rich table, .pub-body table, .cmt-bubble table,
.article-prose table {
  width: 100%; border-collapse: collapse; margin: 0 0 .7rem; font-size: .9rem;
  display: block; overflow-x: auto;
}
.rte-body th, .rte-body td,
.detail-rich th, .detail-rich td,
.pub-body th, .pub-body td,
.cmt-bubble th, .cmt-bubble td,
.article-prose th, .article-prose td {
  border: 1px solid var(--c-border-strong); padding: .4rem .55rem; vertical-align: top;
  min-width: 4rem;
}
.rte-body th, .detail-rich th, .pub-body th, .cmt-bubble th,
.article-prose th {
  background: var(--c-surface-2); font-weight: 700; text-align: left;
}
/* Cell being typed into, so the caret's position in a grid is obvious. */
.rte-body td:focus, .rte-body th:focus { outline: 2px solid var(--c-primary); outline-offset: -2px; }

/* ==========================================================================
   Figure placement inside rich content
   ==========================================================================

   The mj-* classes App\Support\HtmlSanitizer::SAFE_CLASSES lets through. They
   are the only styling an editor can attach to stored content, so every one of
   them is defined here -- and defined once for all five places stored content
   is rendered, so a chart centred on the publish screen is centred on the
   published article and in the editorial preview too. A class with no rule
   here would be a placement the editor could choose and nobody would see.
   ========================================================================== */
/* Floats are taken out of flow, so a figure taller than the paragraph beside it
   would otherwise hang past the end of the section it belongs to. */
:is(.rte-body, .article-prose, .detail-rich, .pub-body, .cmt-bubble)::after {
  content: ""; display: table; clear: both;
}

/* Where the figure sits when it is on a line of its own. */
:is(.rte-body, .article-prose, .detail-rich, .pub-body, .cmt-bubble)
  :is(img, figure, table).mj-align-left { display: block; margin-left: 0; margin-right: auto; }
:is(.rte-body, .article-prose, .detail-rich, .pub-body, .cmt-bubble)
  :is(img, figure, table).mj-align-center { display: block; margin-left: auto; margin-right: auto; }
:is(.rte-body, .article-prose, .detail-rich, .pub-body, .cmt-bubble)
  :is(img, figure, table).mj-align-right { display: block; margin-left: auto; margin-right: 0; }

/* Text wrapping around it. Capped at half the column: a floated figure wider
   than that leaves a gutter of one word per line rather than a paragraph. */
:is(.rte-body, .article-prose, .detail-rich, .pub-body, .cmt-bubble)
  :is(img, figure, table).mj-float-left {
  float: left; max-width: 50%; margin: .25rem 1.1rem .8rem 0;
}
:is(.rte-body, .article-prose, .detail-rich, .pub-body, .cmt-bubble)
  :is(img, figure, table).mj-float-right {
  float: right; max-width: 50%; margin: .25rem 0 .8rem 1.1rem;
}

/* How much of the column it takes. Height stays auto, so nothing is squashed. */
:is(.rte-body, .article-prose, .detail-rich, .pub-body, .cmt-bubble) :is(img, figure, table).mj-w-25 { width: 25%; }
:is(.rte-body, .article-prose, .detail-rich, .pub-body, .cmt-bubble) :is(img, figure, table).mj-w-50 { width: 50%; }
:is(.rte-body, .article-prose, .detail-rich, .pub-body, .cmt-bubble) :is(img, figure, table).mj-w-75 { width: 75%; }
:is(.rte-body, .article-prose, .detail-rich, .pub-body, .cmt-bubble) :is(img, figure, table).mj-w-100 { width: 100%; }
/* A figure sizes itself; the image inside it fills whatever it was given. */
:is(.rte-body, .article-prose, .detail-rich, .pub-body, .cmt-bubble) figure.mj-figure > img { width: 100%; }

/* A figure and its caption: the caption belongs to the picture, so it is
   centred under it and sized down whatever the surrounding prose does. */
:is(.rte-body, .article-prose, .detail-rich, .pub-body, .cmt-bubble) figure.mj-figure {
  margin: 0 0 .9rem; max-width: 100%;
}
:is(.rte-body, .article-prose, .detail-rich, .pub-body, .cmt-bubble) figure.mj-figure > figcaption,
:is(.rte-body, .article-prose, .detail-rich, .pub-body, .cmt-bubble) .mj-figcaption {
  margin-top: .4rem; font-size: .85rem; line-height: 1.45;
  color: var(--c-text-muted); text-align: center;
}

/* Text alignment of a block. Last of the three groups on purpose: these carry
   the same specificity as the caption rule above, so source order is what
   decides, and an alignment written on a caption has to beat the centring the
   caption would otherwise take -- a caption under a right-placed figure
   belongs under the figure, not in the middle of the column. */
:is(.rte-body, .article-prose, .detail-rich, .pub-body, .cmt-bubble) .mj-text-left { text-align: left; }
:is(.rte-body, .article-prose, .detail-rich, .pub-body, .cmt-bubble) .mj-text-center { text-align: center; }
:is(.rte-body, .article-prose, .detail-rich, .pub-body, .cmt-bubble) .mj-text-right { text-align: right; }
:is(.rte-body, .article-prose, .detail-rich, .pub-body, .cmt-bubble) .mj-text-justify { text-align: justify; }

/* Mobile: a phone column has no room to wrap text beside a figure, and a
   quarter-width chart on a 360px screen is unreadable. Every placement
   collapses to a full-width block, which is the only one that works there. */
@media (max-width: 599px) {
  :is(.rte-body, .article-prose, .detail-rich, .pub-body, .cmt-bubble)
    :is(img, figure, table):is(.mj-float-left, .mj-float-right, .mj-w-25, .mj-w-50, .mj-w-75, .mj-w-100) {
    float: none; width: 100%; max-width: 100%;
    display: block; margin-left: auto; margin-right: auto;
  }
}

/* The image the toolbar's placement buttons would act on. Selecting an image
   inside contenteditable gives no visible feedback of its own in every
   browser, and these buttons are useless if you cannot tell what they target. */
.rte-body img.is-rte-selected {
  outline: 2px solid var(--c-primary); outline-offset: 2px;
}

/* ==========================================================================
   Submission detail panel (beneath the submissions table)
   ========================================================================== */
.detail-panel { display: grid; grid-template-columns: 1fr; gap: var(--gap); padding: 1.25rem 1.5rem; }
/* The withheld notice sits between the panel head and the tabs, so it lines
   up with the panel body rather than with the card edge. */
.detail-withheld { margin: 1.25rem 1.5rem 0; }
@media (max-width: 599px) { .detail-withheld { margin: 1rem 1.05rem 0; } }
/* Grid items default to min-width:auto, which lets the authors table push the
   column wider than the screen instead of scrolling inside its .table-wrap.
   Allowing the columns to shrink is what makes that wrapper work. */
.detail-panel > * { min-width: 0; }
/* Wraps: on a phone the status badge plus three or four editorial actions do
   not fit on one line, and without this they ran off the side of the card. */
.detail-head-actions { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }

.detail-title { font-size: 1.2rem; line-height: 1.35; margin: 0; }
.detail-subtitle { margin: .3rem 0 0; color: var(--c-text-muted); font-size: 1rem; }
.detail-facts { display: grid; gap: .6rem 1.5rem; margin: 1rem 0 0; grid-template-columns: 1fr 1fr; }
.detail-facts dt { font-size: .74rem; font-weight: 600; text-transform: uppercase; letter-spacing: .04em; color: var(--c-text-muted); }
.detail-facts dd { margin: .1rem 0 0; font-size: .93rem; }

.detail-block { margin-top: 1.5rem; }
.detail-block h5 { font-size: .8rem; text-transform: uppercase; letter-spacing: .04em; color: var(--c-text-muted); margin: 0 0 .5rem; }

/* ---------------------------------------------------------------------------
   Whose content is this?

   The panel stacks three different things: what the author submitted, the
   conversation about it, and what the editorial team added afterwards
   (published sections, review requests, decisions). Read as one column of
   identical blocks they are easy to confuse -- an editor can end up reading
   their own published summary as if the author had written it. Each block
   therefore carries a coloured edge and a source chip.
   --------------------------------------------------------------------------- */
.detail-block[data-source] { border-left: 3px solid var(--c-border-strong); padding-left: .9rem; }
.detail-block[data-source] > h5 { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.detail-block[data-source] > h5::after {
  content: attr(data-source-label);
  padding: .1rem .45rem; border-radius: 999px;
  font-size: .66rem; font-weight: 700; letter-spacing: .04em;
  border: 1px solid currentColor;
  opacity: .9;
}
/* Submitted by the author. */
.detail-block[data-source="author"] { border-left-color: var(--c-accent); }
.detail-block[data-source="author"] > h5::after { color: var(--c-primary-700); }
/* The exchange between them. */
.detail-block[data-source="conversation"] { border-left-color: var(--c-primary); }
.detail-block[data-source="conversation"] > h5::after { color: var(--c-primary); }
/* Written or decided by the editorial team. */
.detail-block[data-source="editorial"] { border-left-color: var(--c-warning); }
.detail-block[data-source="editorial"] > h5::after { color: var(--c-warning); }
.detail-rich { font-size: .95rem; line-height: 1.65; }
.detail-rich p { margin: 0 0 .7rem; }
.detail-rich ul, .detail-rich ol { margin: 0 0 .7rem 1.1rem; }
.detail-refs {
  margin: 0; padding: .85rem 1rem; background: var(--c-surface-2);
  border: 1px solid var(--c-border); border-radius: var(--r-sm);
  font-family: inherit; font-size: .9rem; line-height: 1.6;
  white-space: pre-wrap; word-break: break-word; max-height: 16rem; overflow-y: auto;
}
/* Compact variant of table.data, for tables nested inside a panel. */
.data-compact { min-width: 34rem; }
.data-compact th, .data-compact td { padding: .5rem .6rem; font-size: .87rem; vertical-align: top; }
.data-compact thead th { font-size: .7rem; }
.data-compact .badge { margin-left: .35rem; font-size: .68rem; padding: .1rem .4rem; }
/* Keep the name and its badge on one line, so each author really is one row.
   .table-wrap scrolls sideways if that makes the table wider than the panel. */
.data-compact td[data-author-name] { white-space: nowrap; }

/* Right column. It is absolutely positioned inside its grid cell so it never
   contributes to the row height -- that is what stops it growing taller than
   the left column.

   The boxes are sized by their contents, not by the column. They used to split
   it in half (grid-template-rows: 1fr 1fr), which left a submission with two
   documents showing a mostly empty panel next to a very tall left column. Now
   each box is as tall as what is in it, they stack from the top, and the
   column itself scrolls if the two together outgrow it. */
.detail-side { position: relative; min-height: 12rem; }
.detail-side-inner {
  display: grid; grid-auto-rows: max-content; align-content: start;
  gap: var(--gap); min-height: 0;
}
.detail-box {
  display: flex; flex-direction: column; min-height: 0;
  border: 1px solid var(--c-border); border-radius: var(--r-md); background: var(--c-surface-2);
  padding: .9rem 1rem;
}
.detail-box h5 {
  display: flex; align-items: center; gap: .45rem; margin: 0 0 .7rem;
  font-size: .8rem; text-transform: uppercase; letter-spacing: .04em; color: var(--c-text-muted);
}
.detail-box h5 svg { width: 15px; height: 15px; }
/* Grows with its contents, up to a ceiling that keeps one long list from
   pushing everything else out of view. */
.detail-scroll { overflow-y: auto; min-height: 0; max-height: 24rem; }

@media (min-width: 1024px) {
  .detail-panel { grid-template-columns: 1.6fr 1fr; align-items: stretch; }
  /* Fill the cell without stretching it: the left column sets the height, and
     anything past it scrolls here rather than stretching the card. */
  .detail-side-inner { position: absolute; inset: 0; overflow-y: auto; }
}

/* Phones: the panel gets the width back that its padding was taking, and the
   fact list becomes one column -- two columns of "Article type / Original
   research" on a 360px screen wrapped every value onto three lines. */
@media (max-width: 599px) {
  .detail-panel { padding: 1rem 1.05rem; }
  .detail-facts { grid-template-columns: 1fr; gap: .75rem; }
  .detail-title { font-size: 1.08rem; }
  .detail-subtitle { font-size: .95rem; }
  .detail-block[data-source] { padding-left: .7rem; }
  .detail-tabs button { padding: .5rem .7rem; font-size: .86rem; }
  /* A submission's own header: the code, its status and the editorial actions.
     Stacked, so the buttons start at the left edge under the code rather than
     trailing off it. */
  .card-head-stack .detail-head-actions { width: 100%; }
}

/* ---------------------------------------------------------------------------
   The mail composer (inc/mail-composer.php)

   An editor writing to an author sees the letter, not a textarea: the logo, the
   greeting, the submission's details and the confidentiality footer are drawn
   around the one part they actually type, at the width and type size the email
   itself uses (600px, 16px/1.6 -- see MailTemplate::render). Everything but the
   middle is inert, so what is theirs to change is obvious at a glance.
   --------------------------------------------------------------------------- */
.mail-compose-stage {
  padding: 1.25rem; background: var(--c-bg);
  border: 1px solid var(--c-border); border-radius: var(--r-md);
}
.mail-compose {
  max-width: 600px; margin-inline: auto;
  background: var(--c-surface);
  border: 1px solid var(--c-border); border-radius: 12px; overflow: hidden;
}
.mail-compose-brandbar { display: grid; place-items: center; padding: 1.4rem 1.25rem 1rem; }
.mail-compose-logo { width: 240px; max-width: 80%; height: auto; }
.mail-compose-rule { height: 4px; background: var(--c-primary); }
.mail-compose-body { padding: 1.75rem 1.6rem 1.5rem; font-size: 1rem; line-height: 1.6; }
.mail-compose-heading {
  margin: 0 0 1rem; font-size: 1.2rem; font-weight: 700; line-height: 1.3; color: var(--c-text);
}
/* The parts the platform writes. Muted, so the editor's own words are the thing
   with contrast on the page. */
.mail-compose-fixed { margin: 0 0 1rem; color: var(--c-text-muted); }
.mail-compose-fixed b { color: var(--c-text); font-weight: 600; }
.mail-compose-signoff { margin: 1.1rem 0 0; }
/* The editable middle, set to match the text around it rather than to the
   smaller type a form field usually gets. */
.mail-compose-input { font-size: 1rem; line-height: 1.6; }
.mail-compose .rte { border-radius: var(--r-sm); }
.mail-compose .rte-body { min-height: 9rem; font-size: 1rem; line-height: 1.6; padding: .9rem 1rem; }
.mail-compose-foot {
  padding: 1.2rem 1.6rem; background: #0f2b33; color: #b9ccd3;
  font-size: .8rem; line-height: 1.6;
}
.mail-compose-foot p { margin: 0; }
.mail-compose-sig { margin-bottom: 0 !important; font-weight: 700; color: #fff; }

/* The confidentiality panel, matching MailTemplate::confidentialPanel() so the
   editor is looking at what the recipient will get. It sits above the dark
   footer, not inside it -- it carries an obligation about unpublished research
   and reviewer anonymity, and grey-on-black under a copyright line is where a
   reader's eye has learned to find nothing worth reading. */
.mail-compose-confidential {
  margin: 0 1.6rem 1.2rem; padding: .9rem 1rem;
  background: var(--c-warning-bg);
  border: 1px solid #ecd3a8; border-left: 4px solid var(--c-warning);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  font-size: .78rem; line-height: 1.6; color: #6b4a12;
}
.mail-compose-confidential b {
  display: block; margin-bottom: .45rem;
  font-size: .72rem; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: #825209;
}
.mail-compose-confidential p { margin: 0 0 .55rem; }
.mail-compose-confidential p:last-child { margin-bottom: 0; }
@media (max-width: 599px) {
  .mail-compose-confidential { margin-inline: 1.05rem; }
}

@media (max-width: 599px) {
  .mail-compose-stage { padding: .6rem; }
  .mail-compose-body { padding: 1.25rem 1rem 1rem; }
  .mail-compose-foot { padding: 1rem; }
}

/* A button that reads as a link -- for actions that move you somewhere on the
   page (another tab) rather than to a URL, which is what a real <a> promises. */
.linkish {
  border: 0; padding: 0; background: none; font: inherit; cursor: pointer;
  color: var(--c-primary); font-weight: 600; text-decoration: underline;
  text-underline-offset: 2px;
}
.linkish:hover { color: var(--c-primary-700); }

/* Plagiarism checks, in the box above Documents. One row per recorded result,
   newest first, with the percentage as the thing the eye lands on. */
.check-list { list-style: none; margin: 0; padding: 0; display: grid; gap: .55rem; }
.check-row {
  display: flex; gap: .6rem; align-items: flex-start;
  padding: .55rem .65rem;
  border: 1px solid var(--c-border); border-radius: var(--r-sm); background: var(--c-surface);
}
/* The current result, picked out the way the active document version is. */
.check-row.is-latest { border-color: var(--c-primary); box-shadow: 0 0 0 2px var(--c-primary-50); }
.check-pct { flex: 0 0 auto; font-size: .78rem; }
.check-body { min-width: 0; font-size: .89rem; }
.check-body .muted { font-size: .8rem; }
.check-note { margin-top: .3rem; font-size: .84rem; overflow-wrap: anywhere; }

/* Progress roadmap */
.roadmap { list-style: none; margin: 0; padding: 0; display: grid; gap: .1rem; }
.roadmap-step { display: flex; gap: .7rem; padding-bottom: .9rem; position: relative; font-size: .9rem; }
.roadmap-step:not(:last-child)::before {
  content: ""; position: absolute; left: 10px; top: 22px; bottom: 0;
  width: 2px; background: var(--c-border-strong);
}
.roadmap-step.is-done:not(:last-child)::before { background: var(--c-success); }
.roadmap-dot {
  flex: 0 0 22px; width: 22px; height: 22px; border-radius: 50%;
  display: grid; place-items: center; z-index: 1;
  background: var(--c-surface); border: 2px solid var(--c-border-strong); color: #fff;
}
.roadmap-dot svg { width: 12px; height: 12px; }
.roadmap-step.is-done .roadmap-dot { background: var(--c-success); border-color: var(--c-success); }
.roadmap-step.is-current .roadmap-dot { background: var(--c-primary); border-color: var(--c-primary); box-shadow: 0 0 0 3px var(--c-primary-50); }
.roadmap-step.is-alert .roadmap-dot { background: var(--c-danger); border-color: var(--c-danger); }
.roadmap-step.is-pending { opacity: .6; }
.roadmap-at { font-size: .8rem; color: var(--c-text-muted); }
.roadmap-hint { font-size: .82rem; color: var(--c-text-faint); }

/* Document versions, newest first */
.doc-list { list-style: none; margin: 0; padding: 0; display: grid; gap: .55rem; }
.doc {
  display: flex; gap: .65rem; padding: .6rem .7rem;
  border: 1px solid var(--c-border); border-radius: var(--r-sm); background: var(--c-surface);
}
.doc.is-active { border-color: var(--c-primary); box-shadow: 0 0 0 2px var(--c-primary-50); }
.doc-ic { flex: 0 0 auto; color: var(--c-text-faint); }
.doc-ic svg { width: 17px; height: 17px; }
.doc-body { min-width: 0; font-size: .89rem; }
.doc-body .muted { font-size: .8rem; }
.doc-original { word-break: break-word; }
/* View / download. They wrap onto their own line on a narrow panel rather
   than squeezing the file name. */
.doc-actions { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .5rem; }
.doc-actions .btn { padding: .3rem .6rem; font-size: .8rem; }
.doc-actions .btn svg { width: 14px; height: 14px; }

/* Wide content dialog, for the editorial action forms. The plain .modal is
   sized for a title and a sentence; this one hosts a form. */
/* Two classes, because the base .modal (text-align:center, padding) is defined
   further down this file and would otherwise win on source order. */
.modal.modal-wide {
  max-width: 640px; width: 100%; text-align: left;
  padding: 0; max-height: 88vh; display: flex; flex-direction: column;
}
.modal-head {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: 1.1rem 1.35rem; border-bottom: 1px solid var(--c-border);
}
.modal-head .modal-title { margin: 0; font-size: 1.05rem; }
.modal-head-actions { display: flex; gap: .5rem; flex: 0 0 auto; }
.modal-body { padding: 1.35rem; overflow-y: auto; }

/* Document viewer: a near-full-screen frame, since a manuscript page needs
   the room. The iframe fills whatever is left under the head bar. */
.modal.modal-doc {
  max-width: min(1080px, 96vw); width: 100%; text-align: left;
  padding: 0; height: 90vh; display: flex; flex-direction: column;
}
.modal-doc .modal-title { overflow-wrap: anywhere; }
.modal-doc-body { flex: 1 1 auto; min-height: 0; background: var(--c-surface-2); }
.modal-doc-body iframe { display: block; width: 100%; height: 100%; border: 0; }
@media (max-width: 599px) {
  .modal.modal-doc { height: 94vh; max-width: 100%; }
  .modal-doc .modal-head { padding: .8rem .9rem; }
  .modal-head-actions .btn { padding: .35rem .55rem; font-size: .8rem; }
}

/* ---- Rendered .docx preview (inside the viewer iframe) ---- */
.doc-preview-page { background: var(--c-surface-2); padding: clamp(.75rem, 2vw, 1.5rem); }
.dp-page {
  max-width: 46rem; margin: 0 auto; padding: clamp(1.25rem, 4vw, 3rem);
  background: #fff; color: var(--c-text);
  border: 1px solid var(--c-border); border-radius: var(--r-sm);
  box-shadow: var(--shadow-sm); line-height: 1.65;
}
.dp-page > :first-child { margin-top: 0; }
.dp-title { font-size: 1.6rem; margin: 0 0 .75rem; }
.dp-subtitle { color: var(--c-text-muted); font-size: 1.05rem; margin: 0 0 1rem; }
.dp-page h1, .dp-page h2, .dp-page h3,
.dp-page h4, .dp-page h5, .dp-page h6 { margin: 1.5rem 0 .6rem; line-height: 1.3; }
.dp-p { margin: 0 0 .85rem; }
.dp-blank { margin: 0 0 .5rem; }
.dp-center { text-align: center; }
.dp-right { text-align: right; }
.dp-justify { text-align: justify; }
.dp-quote {
  margin: 1rem 0; padding: .5rem 0 .5rem 1rem;
  border-left: 3px solid var(--c-primary); color: var(--c-text-muted);
}
.dp-list { margin: 0 0 .85rem 1.35rem; padding: 0; }
.dp-list li { margin: .2rem 0; }
.dp-link { color: var(--c-primary); }
.dp-tab { display: inline-block; width: 2.5em; }
.dp-img { max-width: 100%; height: auto; display: block; margin: 1rem auto; }
.dp-missing { color: var(--c-text-faint); font-style: italic; }
/* Word's horizontal rule, which is a VML rectangle rather than a picture. A
   span, because it is emitted inside the paragraph it was drawn in. */
.dp-rule { display: block; height: 0; margin: 1.25rem 0; border-top: 1px solid var(--c-border-strong); }
/* Charts rebuilt from the document's own data by App\Journal\DocxChart.
   Mobile first: below the tablet breakpoint the reading column is narrower
   than the axis labels can survive, so the chart keeps a legible width and
   scrolls inside itself -- the same bargain .dp-table-wrap makes -- rather
   than shrinking its text to nothing or pushing the page sideways. From
   tablet up the column is wide enough and the chart simply fits it. */
.dp-chart { display: block; margin: 1.25rem 0; max-width: 100%; overflow-x: auto; }
.dp-chart svg { display: block; width: 100%; min-width: 460px; height: auto; }
@media (min-width: 600px) {
  .dp-chart svg { min-width: 0; }
}
.dp-chart-title { font-size: 15px; font-weight: 600; fill: var(--c-text); }
.dp-chart-tick, .dp-chart-key { font-size: 11px; fill: var(--c-text-muted); }
.dp-chart-share { font-size: 11px; font-weight: 600; fill: #fff; }
.dp-chart-grid { stroke: var(--c-border); stroke-width: 1; }
.dp-chart-axis { stroke: var(--c-border-strong); stroke-width: 1; }
.dp-chart-line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.dp-chart-area { fill-opacity: .18; stroke: none; }
.dp-chart-slice, .dp-chart-bar { stroke: var(--c-surface); stroke-width: 1; }
.dp-table-wrap { overflow-x: auto; margin: 1rem 0; }
.dp-table { border-collapse: collapse; width: 100%; font-size: .93rem; }
.dp-table td { border: 1px solid var(--c-border-strong); padding: .45rem .6rem; vertical-align: top; }
.dp-table td > :last-child { margin-bottom: 0; }
.dp-note { margin-top: 1.5rem; padding: .7rem .9rem; background: var(--c-warning-bg); border-radius: var(--r-sm); }
.dp-foot {
  max-width: 46rem; margin: 1rem auto 0; text-align: center;
  color: var(--c-text-muted); font-size: .85rem;
}
.dp-fallback { max-width: 30rem; margin: 3rem auto; text-align: center; }
.dp-fallback svg { width: 40px; height: 40px; color: var(--c-text-faint); }
.dp-fallback h2 { font-size: 1.15rem; margin: .75rem 0 .5rem; }
.dp-fallback p { color: var(--c-text-muted); margin-bottom: 1.25rem; }
/* Stop the page behind scrolling while the dialog is open. */
body.modal-open { overflow: hidden; }

/* Searchable chooser (reviewer picker). */
.chooser {
  border: 1px solid var(--c-border-strong); border-radius: var(--r-sm);
  background: var(--c-surface); max-height: 20rem; overflow-y: auto;
}
.chooser-row {
  display: flex; align-items: flex-start; gap: .7rem;
  padding: .7rem .85rem; border-bottom: 1px solid var(--c-border);
  cursor: pointer; margin: 0;
}
.chooser-row:last-child { border-bottom: 0; }
.chooser-row:hover { background: var(--c-primary-50); }
.chooser-row input { width: 18px; height: 18px; margin-top: .15rem; flex: 0 0 18px; accent-color: var(--c-primary); }
.chooser-row.is-taken { opacity: .55; cursor: not-allowed; background: var(--c-surface-2); }
.chooser-row.is-taken:hover { background: var(--c-surface-2); }
.chooser-body { display: grid; gap: .1rem; min-width: 0; }
.chooser-meta { font-size: .82rem; color: var(--c-text-muted); }
.chooser-row .badge { margin-left: auto; align-self: center; }
/* Group heading inside the chooser: editors above, reviewers below. Sticky so
   it stays readable while the list scrolls under it. */
.chooser-group {
  position: sticky; top: 0; z-index: 1; margin: 0;
  padding: .45rem .85rem; background: var(--c-surface-2);
  border-bottom: 1px solid var(--c-border);
  font-size: .74rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--c-text-muted);
}
/* ---------------------------------------------------------------------------
   Correspondence (submission detail).

   Two-sided, like a messaging app: what you wrote sits on the right, what
   anyone else wrote on the left. Nothing has to be labelled "you" for that to
   be legible -- the side is the label -- and it holds for whoever is reading,
   because the side is decided per viewer rather than per role.

   Mobile-first: bubbles run near-full width on a phone and tighten to 78% once
   there is room, so both sides stay readable either way.
   --------------------------------------------------------------------------- */
/* The surface the conversation sits on, so it reads as its own panel. */
.cmt-thread {
  background: var(--c-bg);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  padding: .9rem 1rem 1.1rem;
}

/* Version divider, centred on a hairline. */
.cmt-version { display: flex; align-items: center; gap: .6rem; margin: 1.2rem 0 .9rem; }
.cmt-version:first-child { margin-top: .2rem; }
.cmt-version::before,
.cmt-version::after { content: ""; flex: 1; height: 1px; background: var(--c-border-strong); }
.cmt-version-label {
  flex: 0 0 auto;
  padding: .18rem .6rem; border-radius: 999px;
  background: var(--c-surface); border: 1px solid var(--c-border-strong);
  color: var(--c-text-muted); font-size: .72rem; font-weight: 700;
  letter-spacing: .05em; text-transform: uppercase;
}

.cmt-list { list-style: none; margin: 0; padding: 0; display: grid; gap: .8rem; }

/* Avatar then bubble. Mine mirrors: the row reverses, so the avatar sits on
   the outside of the right-hand side exactly as it does on the left. */
.cmt-row { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: .6rem; align-items: start; }
.cmt.is-mine .cmt-row { grid-template-columns: minmax(0, 1fr) auto; }
.cmt.is-mine .cmt-avatar { order: 2; }
.cmt.is-mine .cmt-col { order: 1; display: flex; flex-direction: column; align-items: flex-end; }
.cmt-col { min-width: 0; }

.cmt-avatar {
  width: 34px; height: 34px; flex: 0 0 34px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--c-primary); color: #fff;
  font-size: .74rem; font-weight: 700; letter-spacing: .02em;
}
.cmt.is-mine .cmt-avatar { background: var(--c-accent); }
.cmt.is-internal .cmt-avatar { background: var(--c-warning); }

/* The bubble. Shrink-wrapped rather than full width, with the tail corner on
   the side it came from. */
.cmt-bubble {
  display: inline-block; max-width: 100%;
  padding: .55rem .8rem;
  border: 1px solid var(--c-border); border-radius: 16px 16px 16px 4px;
  background: var(--c-surface);
  overflow-wrap: anywhere;
}
.cmt-bubble > p { margin: 0; }
.cmt-bubble > p + p { margin-top: .45rem; }
.cmt-bubble .detail-rich > :last-child { margin-bottom: 0; }
.cmt.is-mine .cmt-bubble {
  background: var(--c-primary-50); border-color: #cfe6ea;
  border-radius: 16px 16px 4px 16px;
}
.cmt.is-internal .cmt-bubble {
  background: var(--c-warning-bg); border-color: var(--c-warning); border-style: dashed;
}
/* The most recent thing said, so an editor returning to a submission can see
   what has come in without reading the whole thread. */
.cmt.is-latest .cmt-bubble { border-color: var(--c-primary); box-shadow: 0 0 0 3px var(--c-primary-50); }

/* Name line, inside the bubble. */
.cmt-head { display: flex; align-items: baseline; gap: .4rem; flex-wrap: wrap; margin-bottom: .15rem; }
.cmt-who { font-size: .85rem; color: var(--c-text); }
.cmt-name { font-size: .78rem; color: var(--c-text-muted); }
.cmt-name::before { content: "· "; }
.cmt-tag {
  font-size: .68rem; font-weight: 700; letter-spacing: .03em; text-transform: uppercase;
  color: var(--c-primary-700); background: var(--c-primary-50);
  padding: .05rem .4rem; border-radius: 999px;
}
.cmt.is-mine .cmt-tag { background: var(--c-surface); }
.cmt.is-internal .cmt-tag { color: var(--c-warning); background: transparent; }
.cmt-lock {
  display: block; font-size: .72rem; font-weight: 700; color: var(--c-warning);
  margin-bottom: .3rem;
}

/* Small print under the bubble, on the same side as the bubble it belongs to. */
.cmt-foot {
  display: flex; align-items: center; gap: .55rem; flex-wrap: wrap;
  padding: .25rem .3rem 0 .8rem;
  font-size: .76rem; color: var(--c-text-faint);
}
.cmt.is-mine .cmt-foot { justify-content: flex-end; padding: .25rem .8rem 0 .3rem; }

@media (min-width: 600px) {
  .cmt-bubble { max-width: 78%; }
}
@media (max-width: 599px) {
  .cmt-thread { padding: .7rem .6rem 1rem; }
  .cmt-avatar { width: 28px; height: 28px; flex-basis: 28px; font-size: .66rem; }
}

/* The most recent review in the requests panel, picked out from the rest. */
.decision-list > li.is-latest-review {
  border-left: 3px solid var(--c-primary);
  padding-left: .7rem;
  background: var(--c-primary-50);
  border-radius: var(--r-sm);
}

/* Last activity on a queue row, so an admin can see what has come in without
   opening each submission. */
.t-latest {
  margin-top: .35rem; font-size: .82rem; color: var(--c-text-muted);
  display: flex; align-items: center; gap: .35rem; flex-wrap: wrap;
}
.t-latest b { color: var(--c-text); font-weight: 600; }
.t-latest .badge { margin-right: .1rem; }
.t-latest-quote { font-style: italic; }
/* Whole-row tint for something that arrived in the last few days. Kept to a
   left edge and a faint wash so a table of fresh rows is still readable. */
.row-fresh > td:first-child { box-shadow: inset 3px 0 0 var(--c-primary); }
.row-fresh > td { background: var(--c-primary-50); }

/* Decision history: a compact trail, the wording lives in the thread. */
.decision-trail { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }
.decision-trail li { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; font-size: .9rem; }

/* Editorial decisions inside the detail panel. */
.decision-list { list-style: none; margin: 0; padding: 0; display: grid; gap: .9rem; }
.decision-head { display: flex; align-items: baseline; gap: .6rem; flex-wrap: wrap; font-size: .93rem; }
.decision-head .muted { font-size: .82rem; }
.decision-note {
  margin-top: .4rem; padding: .6rem .75rem;
  border-left: 3px solid var(--c-border-strong); background: var(--c-surface-2);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
}
.decision-note p { margin: .25rem 0 0; font-size: .9rem; line-height: 1.6; }
.decision-note-tag {
  font-size: .7rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  color: var(--c-text-muted);
}
/* Editorial-only notes are visually distinct so they are never mistaken for
   something the author can see. */
.decision-note.is-internal { border-left-color: var(--c-warning); background: var(--c-warning-bg); }
.decision-note.is-internal .decision-note-tag { color: var(--c-warning); }

/* Admin queue: action buttons per row, and the work-queue chips. */
.row-actions { display: flex; gap: .3rem; flex-wrap: wrap; justify-content: flex-end; }
.row-actions .btn { white-space: nowrap; }
/* Accept / Decline answered from the row itself: a form, but it should sit in
   the action list as though it were two more buttons. */
.row-respond { display: contents; }
.btn.is-disabled, .row-actions .is-disabled {
  opacity: .45; cursor: not-allowed; pointer-events: none;
}
.queue-label {
  font-size: .72rem; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
  color: var(--c-text-muted); align-self: center; margin-right: .2rem;
}
.chip-action { border-style: dashed; }
.chip-action.is-on { border-style: solid; }

/* Status filter chips on the submissions list. */
.chip {
  display: inline-flex; align-items: center; gap: .4rem;
  padding: .35rem .75rem; border: 1px solid var(--c-border-strong);
  border-radius: var(--r-full); background: var(--c-surface);
  font-size: .84rem; font-weight: 600; color: var(--c-text-muted);
  transition: background .15s, border-color .15s, color .15s;
}
.chip:hover { border-color: var(--c-primary); color: var(--c-primary); }
.chip.is-on { background: var(--c-primary); border-color: var(--c-primary); color: #fff; }
.chip-n {
  display: inline-grid; place-items: center; min-width: 1.35rem; padding: 0 .3rem;
  border-radius: var(--r-full); background: var(--c-primary-50); color: var(--c-primary-700);
  font-size: .75rem; font-weight: 700;
}
.chip.is-on .chip-n { background: rgba(255,255,255,.22); color: #fff; }
/* The cross on a removable filter chip (the keyword on browse.php). */
.chip-x { margin-left: .1rem; font-size: 1rem; line-height: 1; opacity: .8; }

/* Submission confirmation page. */
.st-icon-lg {
  display: inline-grid; place-items: center; width: 68px; height: 68px;
  border-radius: 50%; background: var(--c-success-bg); color: var(--c-success);
}
.st-icon-lg svg { width: 34px; height: 34px; }
.tracking-box {
  display: flex; align-items: center; justify-content: center; gap: .85rem; flex-wrap: wrap;
  margin: 1.5rem auto 0; padding: 1rem 1.25rem; max-width: 26rem;
  border: 1px dashed var(--c-primary); border-radius: var(--r-md); background: var(--c-primary-50);
}
.tracking-label { font-size: .78rem; font-weight: 600; text-transform: uppercase; letter-spacing: .05em; color: var(--c-primary-700); }
.tracking-code { font-size: 1.25rem; font-weight: 800; letter-spacing: .02em; color: var(--c-primary-700); }

/* The same box holding a URL instead of a manuscript ID.
   A permanent link is several times the length of a code, so the centred 26rem
   box that suits "IJAMPH-26-104" would either overflow or set it in 1.25rem
   bold across three ragged lines. Here the label takes its own row, the URL
   sets left-aligned in a monospace face -- where a reader can tell a 1 from an
   l before pasting it into a QR generator -- and the Copy button stays on the
   end of it. */
.tracking-box.is-link {
  max-width: none; margin-top: 0; text-align: left;
  justify-content: space-between; row-gap: .5rem;
}
.tracking-box.is-link .tracking-label { flex: 0 0 100%; }
.tracking-box.is-link .tracking-code {
  flex: 1 1 16rem; min-width: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .95rem; font-weight: 600; letter-spacing: 0;
  word-break: break-all;
}
@media (max-width: 599px) {
  /* The button under the URL rather than squeezed beside it. */
  .tracking-box.is-link { justify-content: flex-start; }
  .tracking-box.is-link .tracking-code { flex-basis: 100%; font-size: .9rem; }
}

/* --- The QR code offered beside the link --- */
.qr-offer { display: flex; gap: 1.15rem; align-items: flex-start; flex-wrap: wrap; }
.qr-copy { flex: 1 1 18rem; min-width: 0; }
/* White background and a border rather than the card's tint: a QR is read as
   dark-on-light and must keep its contrast whatever the surface behind it, and
   the padding is the quiet zone the image already carries, made visible. */
.qr-preview {
  flex: none; width: 210px; height: 210px;
  background: #fff; border: 1px solid var(--c-border); border-radius: var(--r-sm);
  /* Nearest-neighbour: a QR is a grid of hard squares, and smoothing it on the
     way down turns every module edge into a grey ramp -- which is exactly what
     a scanner has to threshold back out again. */
  image-rendering: pixelated;
}
@media (max-width: 599px) {
  .qr-offer { justify-content: center; }
  .qr-preview { width: 168px; height: 168px; }
}

.next-steps { list-style: none; padding: 0; margin: 0; display: grid; gap: 1.1rem; }
.next-steps li { display: flex; gap: .9rem; align-items: flex-start; }
.next-steps p { margin: .15rem 0 0; font-size: .92rem; }
.next-no {
  display: inline-grid; place-items: center; flex: 0 0 28px; width: 28px; height: 28px;
  border-radius: 50%; background: var(--c-primary-50); color: var(--c-primary-700);
  font-size: .85rem; font-weight: 700;
}

/* Author cards on the submission wizard's Authors step. */
.author-card {
  border: 1px solid var(--c-border); border-radius: var(--r-md);
  background: var(--c-surface); padding: 1rem 1.1rem;
  display: grid; gap: .85rem;
}
.author-card.is-locked { background: var(--c-surface-2); }

/* A finished co-author reads as a summary card; the form is hidden until Edit.
   Without JS neither class is applied, so every card stays expanded and the
   server validates the lot on Next. */
.author-card [data-author-summary] { display: none; }
.author-card [data-edit-author] { display: none; }
.author-card.is-collapsed { background: var(--c-surface-2); }
.author-card.is-collapsed [data-author-edit] { display: none; }
.author-card.is-collapsed [data-author-summary] { display: grid; }
.author-card.is-collapsed [data-edit-author] { display: inline-flex; }
@media (min-width: 600px) {
  .author-card.is-collapsed [data-author-summary] { grid-template-columns: 1fr 1fr; gap: .6rem 1.5rem; }
}
.author-card.is-dragging { opacity: .5; border-color: var(--c-primary); }
.author-card-head { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
.author-heading { font-size: .95rem; }
.author-pos {
  display: inline-grid; place-items: center; flex: 0 0 26px;
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--c-primary); color: #fff; font-size: .8rem; font-weight: 700;
}
.author-card-actions { margin-left: auto; display: flex; gap: .3rem; }
.author-card-actions .btn[disabled] { opacity: .4; cursor: not-allowed; }

/* Drag affordance. The handle is draggable, not the card, so selecting text
   inside the inputs still behaves normally. */
.drag-handle {
  display: inline-grid; place-items: center; flex: 0 0 auto;
  width: 26px; height: 26px; border-radius: var(--r-sm);
  color: var(--c-text-faint); cursor: grab;
}
.drag-handle:active { cursor: grabbing; }
.drag-handle:hover { background: var(--c-primary-50); color: var(--c-primary); }
.drag-handle svg { width: 16px; height: 16px; }

/* Read-only facts for the submitting author. */
.author-facts { display: grid; gap: .6rem; margin: 0; }
.author-facts > div { display: grid; gap: .1rem; }
.author-facts dt { font-size: .78rem; font-weight: 600; color: var(--c-text-muted); text-transform: uppercase; letter-spacing: .03em; }
.author-facts dd { margin: 0; font-size: .95rem; color: var(--c-text); }
@media (min-width: 600px) { .author-facts { grid-template-columns: 1fr 1fr; gap: .6rem 1.5rem; } }

/* One author per row: name, corresponding radio, remove. */
.author-row { display: grid; grid-template-columns: 1fr auto auto; gap: .6rem; align-items: center; }
@media (max-width: 599px) {
  .author-row { grid-template-columns: 1fr auto; }
  .author-row .check { grid-column: 1 / -1; }
}

/* ==========================================================================
   Auth layout (login / register)
   ========================================================================== */
/* Auth pages are standalone layouts -- they do not include inc/header.php --
   so the full viewport height is available. Subtracting --header-h here left
   a strip of page background below the hero on tall screens. */
.auth-wrap { min-height: 100vh; display: grid; grid-template-columns: 1fr; }
/* Padding scales with viewport height so the panel does not force the page
   to scroll on short laptop screens. */
.auth-hero { display: none; background: linear-gradient(150deg, var(--c-primary-700), var(--c-primary), var(--c-accent)); color: #fff; padding: clamp(1.5rem, 4vh, 3rem); position: relative; overflow: hidden; }
.auth-hero::after { content: ""; position: absolute; inset: 0; background: radial-gradient(circle at 80% 20%, rgba(255,255,255,.12), transparent 45%); }
/* Its own class rather than the home page's .hero-inner, which these panels
   used to borrow. That one turns into a two-column grid at 900px, so the auth
   panel split into logo-beside-text and broke the headline to one word a line.
   Nothing on this side should track the marketing hero's layout.

   Plain block flow, one column: logo on top, text centred beneath it. The block
   itself is centred by `margin: auto` in the grid; `text-align` centres the
   content within it. Note .brand and the list rows are flex containers, so they
   need justify-content -- text-align does not reach them. */
.auth-hero-inner { position: relative; z-index: 1; max-width: 440px; margin: auto; text-align: center; }
.auth-hero .brand { justify-content: center; }

/* Larger brand lockup on the auth panels. Scoped to .auth-hero so the site
   header (46px) and dark footer (80px) keep their own sizes.
   The logo artwork is opaque with a white background -- it cannot sit directly
   on the teal gradient -- so the white card is required, not decorative. It scales
   with the logo and gets a softer shadow at this size. */
.auth-hero .brand-logo-box { padding: clamp(.6rem, 1.4vh, 1rem) clamp(.85rem, 1.8vh, 1.25rem); border-radius: var(--r-lg); box-shadow: 0 10px 30px rgba(4, 30, 38, .22); }
/* Caps at the intended 320px, but shrinks on short screens so the lockup
   never pushes the panel past the fold. */
.auth-hero .brand-logo-box .brand-logo { height: auto; width: min(320px, 30vh); max-width: 100%; }
/* The heading margin was an inline style on every auth page; it lives here
   now so it can scale with height too. */
.auth-hero h2 { font-size: clamp(1.5rem, 2.4vw + .3vh, 2.3rem); margin-top: clamp(1rem, 3vh, 2.5rem); margin-bottom: 1rem; }
.auth-hero p { opacity: .9; font-size: 1.05rem; }
/* The list is centred as a block (width: fit-content + auto margins) while the
   rows stay left-aligned inside it, so the icons keep a tidy vertical column
   instead of staggering with each line's length. */
.auth-hero ul { list-style: none; padding: 0; margin-top: clamp(1rem, 2.5vh, 2rem); display: grid; gap: clamp(.6rem, 1.4vh, 1rem); width: fit-content; margin-inline: auto; text-align: left; }
.auth-hero li { display: flex; gap: .75rem; align-items: flex-start; }
.auth-hero li svg { width: 22px; height: 22px; flex: 0 0 22px; margin-top: 2px; }
/* min-width: 0 so this column can shrink to the viewport; the grid default of
   min-width: auto sizes it to the card's content instead. */
/* align-content: center packs the card and the links under it as one block.
   place-items only centres each item inside its own row, and with the rows
   stretching to fill a tall panel that left the links stranded near the
   bottom of the screen. */
.auth-form-side { position: relative; display: grid; place-items: center; align-content: center; min-width: 0; padding: clamp(1.5rem, 5vw, 3rem); }
/* Way back to the public site: these pages carry no header, so without it
   the only exit is the browser back button. Sits above the card, aligned
   with its left edge. */
/* Pinned to the top-right corner rather than sitting in the flow: it takes
   no vertical space there, so the form starts higher up the panel. */
.auth-back {
  position: absolute; top: 1.15rem; right: 1.25rem; z-index: 2;
  display: inline-flex; align-items: center; gap: .35rem;
  font-size: .9rem; font-weight: 500;
  color: var(--c-text-muted); text-decoration: none;
}
.auth-back:hover { color: var(--c-primary); }
.auth-back svg { width: 16px; height: 16px; }
@media (min-width: 600px) { .auth-back { top: 1.5rem; right: 2rem; } }
/* The panel centres its card, so the card can start where the link is. This
   reserves the strip the link occupies, at every width. */
.auth-form-side:has(.auth-back) { padding-top: 3.5rem; }
.auth-card { width: 100%; max-width: 420px; }

/* Reference links under the sign-in card -- "Instructions for A | R | E".
   Separators are drawn between the links rather than typed into the markup, so
   the pipes are never read out by a screen reader and never end up stranded at
   the start of a wrapped line. */
.auth-links {
  display: flex; flex-wrap: wrap; justify-content: center; align-items: baseline;
  gap: .3rem .75rem; margin-top: 1.5rem; max-width: 420px;
  font-size: .88rem; color: var(--c-text-muted); text-align: center;
}
.auth-links a { color: var(--c-primary); font-weight: 500; text-decoration: none; }
.auth-links a:hover { text-decoration: underline; }
.auth-links a + a { position: relative; }
.auth-links a + a::before {
  content: "|"; position: absolute; left: -.44rem;
  color: var(--c-border); font-weight: 400;
}

/* Registration drops the card surface and uses the panel itself: the form is
   long enough that a 420px column forced the page to scroll. Capped at 680px
   rather than full-bleed -- inputs stretched to ~880px on a large monitor are
   hard to scan, so the extra width goes into columns instead (see .auth-cols). */
.auth-card--plain { max-width: 680px; }
.auth-cols { display: grid; gap: 1.15rem; grid-template-columns: 1fr; }
@media (min-width: 1024px) {
  /* Title is narrow by nature; the two name fields share the rest. */
  .auth-cols.cols-name { grid-template-columns: 120px 1fr 1fr; }
  .auth-cols.cols-two  { grid-template-columns: 1fr 1fr; }
}

/* Short viewports (most 768px and 720px laptops): the feature list is
   decorative -- the sign-in card is the task -- so drop it rather than make
   the whole page scroll. Everything above still scales fluidly. */
@media (max-height: 820px) {
  .auth-hero ul { display: none; }
  .auth-hero p { font-size: 1rem; }
  /* Registration is the long form; with password-confirm and locality/country
     it needs the vertical rhythm tightened too, or it scrolls at 720/768. */
  .auth-form-side { padding-block: 1.25rem; }
  /* Keep the reserved strip tight here: this form already scrolls on a 768px
     laptop, and every pixel above the card makes that worse. */
  .auth-back { top: .5rem; right: 1.25rem; }
  .auth-form-side:has(.auth-back) { padding-top: 2.1rem; }
  .auth-card--plain .form-grid,
  .auth-card--plain .auth-cols { gap: .85rem; }
}

/* ==========================================================================
   Hero / marketing (home)
   ========================================================================== */
.hero { background: linear-gradient(160deg, #0b3d47 0%, var(--c-primary) 55%, var(--c-accent) 120%); color: #fff; position: relative; overflow: hidden; }
.hero::after { content: ""; position: absolute; inset: 0; background: radial-gradient(circle at 85% 15%, rgba(255,255,255,.14), transparent 40%), radial-gradient(circle at 10% 90%, rgba(255,255,255,.08), transparent 40%); pointer-events: none; }
.hero-inner {
  position: relative; z-index: 1; padding-block: clamp(3rem, 8vw, 5.5rem);
  display: grid; gap: 2rem; align-items: center;
}
/* Copy on its own until there is room for the artwork beside it. */
.hero-copy { display: grid; gap: 2rem; }
/* The journal name, sized to be read before the headline is. An accent rule
   down the left ties the two lines together as one masthead. */
.hero-journal {
  position: relative;
  display: grid; gap: .15rem;
  padding-left: clamp(.85rem, 1.5vw, 1.15rem);
  border-left: 3px solid rgba(255, 255, 255, .55);
}
/* The accent rule draws itself down on load, which pulls the eye to the name
   before anything else moves. Every base style here is the finished state, so
   the global reduced-motion rule leaves the masthead correct, just still. */
.hero-journal::before {
  content: ""; position: absolute; left: -3px; top: 0; bottom: 0; width: 3px;
  background: linear-gradient(180deg, var(--c-accent), rgba(255, 255, 255, .85));
  transform-origin: top;
  animation: hero-rule 1s cubic-bezier(.2, .8, .2, 1) both;
}
/* The headline arrives after the name and stays plain -- no mark of its own,
   so nothing pulls against the masthead. */
.hero h1 {
  animation: hero-quote-in 1s cubic-bezier(.2, .8, .2, 1) .35s both;
}

@keyframes hero-rule      { from { transform: scaleY(0); } to { transform: scaleY(1); } }
@keyframes hero-name-in   { from { opacity: 0; transform: translateY(12px); }
                            to   { opacity: 1; transform: none; } }
@keyframes hero-quote-in  { from { opacity: 0; transform: translateY(16px); }
                            to   { opacity: 1; transform: none; } }
/* Resting state is the 0% frame, so with animation off the name still carries
   the shadow that lifts it off the gradient behind. */
@keyframes hero-name-glow {
  0%, 100% { text-shadow: 0 2px 20px rgba(0, 0, 0, .30), 0 0 18px rgba(255, 255, 255, .16); }
  50%      { text-shadow: 0 2px 20px rgba(0, 0, 0, .30), 0 0 40px rgba(255, 255, 255, .55); }
}
/* ---- The full title is the masthead now ----
   It stands alone in the banner, set larger than the headline below it and
   carrying the only repeating movement on the page, so the journal's name is
   what the eye lands on first. A soft glow lifts it off the gradient.
   Capped at 20ch so it breaks into two or three even lines instead of one very
   long one. */
.hero-journal-full {
  display: block;
  font-size: clamp(1.55rem, 3.4vw, 2.5rem); font-weight: 800; line-height: 1.12;
  letter-spacing: .01em; max-width: 20ch;
  color: #fff;
  /* Declared statically as well as in the keyframes so the name keeps its lift
     off the background when animation is switched off. */
  text-shadow: 0 2px 20px rgba(0, 0, 0, .30), 0 0 18px rgba(255, 255, 255, .16);
  /* Once it has settled the name breathes with a slow glow -- a glow rather
     than a highlight sweeping across the letters, which would have to dim the
     rest of the text for the bright band to read. This only ever adds light. */
  animation: hero-name-in .9s cubic-bezier(.2, .8, .2, 1) both,
             hero-name-glow 5s ease-in-out 1.4s infinite;
}
/* Decorative, so it is the first thing to go when space is tight. */
.hero-art { display: none; }
.hero-art .hero-svg { display: block; width: 100%; height: auto; max-width: 440px; margin-inline: auto; }
@media (min-width: 900px) {
  .hero-inner { grid-template-columns: minmax(0, 1.15fr) minmax(0, .85fr); gap: clamp(2rem, 5vw, 4rem); }
  .hero-art { display: block; }
}
/* Set below the masthead deliberately: the name is the focal point, and the
   headline reads as its strapline rather than out-shouting it. */
.hero h1 { font-size: clamp(1.25rem, 2.4vw, 1.75rem); max-width: 26ch; font-weight: 600; }
@media (min-width: 900px) { .hero h1 { max-width: 100%; } }
.hero p.lead { font-size: clamp(1.05rem, 2.2vw, 1.3rem); opacity: .92; max-width: 54ch; }
.hero .cta-row { display: flex; flex-wrap: wrap; gap: .9rem; }
.hero .btn-secondary { --btn-bg: rgba(255,255,255,.12); --btn-fg: #fff; --btn-bd: rgba(255,255,255,.35); }
.hero .btn-secondary:hover { --btn-bg: rgba(255,255,255,.2); }
/* A fixed 2x2 grid rather than a wrapping flex row. These labels are words now,
   not short numbers, so a flex row breaks 3 + 1 and the last one reads as an
   afterthought. Four across only fits above about 1400px -- the artwork takes
   the copy column to roughly half the page from 900px up -- which is too narrow
   a window to be worth a breakpoint, and 2x2 is balanced at every width. */
.hero-stats {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.5rem clamp(1rem, 3vw, 2rem); padding-top: 1rem; max-width: 34rem;
}
.hero-stats .stat b {
  display: block; font-size: clamp(1.15rem, 2.4vw, 1.55rem); line-height: 1.15;
}
.hero-stats .stat span { display: block; opacity: .85; font-size: .85rem; margin-top: .15rem; }

/* Section heading */
.section-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 1rem; flex-wrap: wrap; margin-bottom: 1.5rem; }
.section-head h2 { font-size: clamp(1.4rem, 3vw, 1.9rem); }
.section-head .eyebrow { color: var(--c-primary); font-weight: 700; font-size: .82rem; letter-spacing: .06em; text-transform: uppercase; margin-bottom: .3rem; }

/* Journal card grid */
/* A grid track is min-width: auto by default, which means it grows to the
   widest thing inside it and refuses to shrink below that. Anything with a
   min-width of its own -- table.data is 640px -- therefore pushes the track
   wider than the screen and takes the page with it, and the .table-wrap that
   was supposed to scroll it never gets the chance because it is never the
   narrow one.

   This was create-user.php overflowing by 502px on a phone: the users table
   held the column open at 871px inside a 390px viewport.

   min-width: 0 lets the track shrink to the viewport, at which point the
   wrapper scrolls the table inside it, which is what it is for. */
.grid { display: grid; gap: var(--gap); }
.grid > *, .detail-grid > * { min-width: 0; }
.grid-cards { grid-template-columns: 1fr; }
.jcard { display: flex; flex-direction: column; overflow: hidden; }
/* Cover thumbnail beside the title, the way an issue is listed in print. The
   padding that used to sit on .jcard-top moves up here so the image and the
   text share one inset. */
.jcard-head { display: flex; align-items: flex-start; gap: 1rem; padding: 1.25rem 1.4rem .5rem; }
/* min-width: 0 so a long unbroken title cannot push the cover out of the card. */
.jcard .jcard-top { padding: 0; min-width: 0; }
/* The card title is the link; this stretches its hit area over the whole card,
   so the card behaves as one target while the accessible name stays just the
   title. The card needs position: relative for the ::after to anchor to. */
.jcard { position: relative; }
.jcard-link { color: inherit; text-decoration: none; }
.jcard-link::after { content: ""; position: absolute; inset: 0; z-index: 1; }
.jcard-link:hover { color: var(--c-primary); }
.jcard:focus-within { outline: 3px solid var(--c-accent); outline-offset: 2px; }
/* The ring belongs to the card, drawn above; the link itself must not draw a
   second one around just the words. */
.jcard-link:focus-visible { outline: none; }

/* ---- Indexing services on the journal page ---- */
.indexing-grid { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); }
.indexing-card {
  display: grid; gap: .6rem; align-content: start; justify-items: center;
  padding: 1.4rem 1.25rem; text-align: center;
  text-decoration: none; color: var(--c-text);
  transition: transform .15s, box-shadow .15s, border-color .15s;
}
.indexing-card:hover { transform: translateY(-2px); border-color: var(--c-primary); box-shadow: var(--shadow-md); }
/* Logos vary wildly in proportion, so the box is fixed and the image fits
   inside it rather than the other way round. */
.indexing-logo { max-width: 100%; max-height: 56px; width: auto; height: auto; object-fit: contain; }
/* Stand-in until a logo file is added: the service name set as a wordmark, on
   the same 56px line so swapping in an image does not move the card. */
.indexing-wordmark {
  display: grid; place-items: center; min-height: 56px;
  font-size: 1.15rem; font-weight: 700; letter-spacing: -.01em; color: var(--c-primary-700);
}
.indexing-note { font-size: .85rem; color: var(--c-text-muted); }

/* The DOI on a card: quiet, monospaced so the digits and dots stay legible,
   and allowed to break rather than widen the card. */
.jcard-doi {
  padding: 0 1.4rem .25rem; margin: 0;
  font-size: .78rem; color: var(--c-text-faint);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  overflow-wrap: anywhere;
}
.jcard-doi span {
  font-family: inherit; font-weight: 700; letter-spacing: .04em;
  margin-right: .35rem; color: var(--c-text-muted);
}

/* ---- A published article's own page ---- */
.article-doi {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .45rem;
  margin-top: .55rem; font-size: .88rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  overflow-wrap: anywhere;
}
.article-doi-label { font-weight: 700; letter-spacing: .04em; color: var(--c-text-muted); }
/* Full container width, like every other page: the site is edge-to-edge with a
   responsive gutter, and the article was the one screen that capped itself --
   first at the left edge with the right third empty, then centred with the gap
   split either side. It now lines up with the header above it.

   The prose inside keeps a readable measure of its own (.article-prose below),
   so widening the page does not turn a paragraph into one 200-character line. */
.article { max-width: 100%; }
.article-head { display: flex; flex-wrap: wrap; gap: 1.5rem; align-items: flex-start; margin-bottom: 2rem; }
.article-cover {
  flex: 0 0 132px; width: 132px; height: auto; aspect-ratio: 4 / 5;
  object-fit: cover; border-radius: var(--r-md);
  border: 1px solid var(--c-border); background: var(--c-surface-2);
  box-shadow: 0 4px 14px rgba(4, 30, 38, .16);
}
/* min-width: 0 so a long unbroken title cannot push the cover out of the row. */
.article-headings { flex: 1 1 20rem; min-width: 0; }
.article-headings h1 { font-size: clamp(1.5rem, 3.2vw, 2.2rem); line-height: 1.2; margin: .35rem 0 .5rem; }
.article-subtitle { font-size: 1.05rem; color: var(--c-text-muted); margin-bottom: .75rem; }
.article-authors { font-size: .95rem; margin-bottom: .6rem; }
.article-author { white-space: nowrap; }
.article-author .corr { color: var(--c-primary); font-weight: 700; }
.article-meta { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1rem;
                font-size: .85rem; color: var(--c-text-faint); }
.article-meta span { display: inline-flex; align-items: center; gap: .35rem; }

.article-body { display: grid; gap: 1.75rem; }
/* Grid items default to min-width:auto, which is min-content -- and the
   min-content of a table is the sum of its columns, which does not wrap. So one
   published article with a wide results table made every section refuse to
   shrink into its column: the body stayed at the table's width and ran straight
   under the contents panel on the right.

   It only showed between about 1060px and 1360px, which is 13-inch tablets and
   14-inch laptops -- wide enough for the two-column layout to be on, not wide
   enough for the column to fit the table. Above that it hid itself.

   min-width:0 lets the section shrink to its column; the table then scrolls
   inside its own box, which it is already set up to do (overflow-x:auto).
   Same fix, same reason, as .detail-panel > * further up. */
.article-body,
.article-body > * { min-width: 0; }
.article-section h2 { font-size: 1.15rem; margin-bottom: .6rem;
                      padding-bottom: .4rem; border-bottom: 1px solid var(--c-border); }
/* The page runs the full width; the sentences do not. 95ch is generous -- wider
   than the 65-75 a book uses -- so the column still fills a laptop screen edge
   to edge and only holds back on a very wide monitor, where an uncapped line
   would run past the point the eye can track back to the next one. */
.article-prose { color: var(--c-text); line-height: 1.65; max-width: 95ch; }
.article-prose p { margin: 0 0 .85rem; }
.article-prose ul, .article-prose ol { margin: 0 0 .85rem; padding-left: 1.25rem; }
.article-prose li { margin-bottom: .35rem; }
.article-prose h2, .article-prose h3, .article-prose h4 { font-size: 1rem; margin: 1rem 0 .5rem; }
.article-prose blockquote { margin: 0 0 .85rem; padding-left: .9rem;
                            border-left: 3px solid var(--c-border); color: var(--c-text-muted); }
.article-prose :last-child { margin-bottom: 0; }
.keyword-row { display: flex; flex-wrap: wrap; gap: .4rem; }
.author-cards { list-style: none; padding: 0; margin: 0; display: grid; gap: .9rem; }
.author-cards li { padding-left: .9rem; border-left: 2px solid var(--c-border); font-size: .92rem; }

/* ---- Publish screen and the sections it produces ---- */
/* The cover picker: preview beside the file input, stacking on a phone. */
.cover-picker { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 1.25rem; }
/* The two file inputs stack beside the preview; min-width: 0 so a long "no file
   chosen" label cannot push the row wider than the card. */
.upload-fields { flex: 1 1 18rem; min-width: 0; display: grid; gap: 1rem; }
.upload-fields input[type="file"] { max-width: 100%; }
.cover-picker .cover-preview {
  flex: 0 0 120px; width: 120px; height: auto; aspect-ratio: 4 / 5;
  object-fit: cover; border-radius: var(--r-md);
  border: 1px solid var(--c-border); background: var(--c-surface-2);
}
.block-list { display: grid; gap: 1.25rem; }
/* A fieldset, so each group is announced as one thing rather than as loose
   inputs. The browser default border and padding are replaced outright. */
.block-group {
  border: 1px solid var(--c-border); border-radius: var(--r-md);
  padding: 1rem 1.1rem 1.15rem; margin: 0;
  display: grid; gap: .9rem; background: var(--c-surface-2);
}
.block-legend {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; width: 100%; padding: 0;
}
.block-number { font-size: .82rem; font-weight: 700; text-transform: uppercase;
                letter-spacing: .04em; color: var(--c-text-muted); }
.publish-form { display: grid; gap: var(--gap); }
/* Quill needs a height of its own; without one the editor collapses to a
   toolbar with a sliver of writing space beneath it. */
.block-group .rich-text .ql-editor { min-height: 8rem; }

/* Sections as rendered back in the detail panel. */
.pub-block + .pub-block { margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--c-border); }
.pub-block h6 { font-size: .95rem; margin: 0 0 .35rem; }
.pub-body { color: var(--c-text); font-size: .92rem; }
.pub-body p { margin: 0 0 .6rem; }
.pub-body ul, .pub-body ol { margin: 0 0 .6rem; padding-left: 1.15rem; }
.pub-body :last-child { margin-bottom: 0; }
.detail-cover {
  width: 96px; height: auto; aspect-ratio: 4 / 5; object-fit: cover;
  border-radius: var(--r-sm); border: 1px solid var(--c-border);
  float: right; margin: 0 0 .6rem .9rem;
}

/* Journal cover in the About overview. The cover is floated so the copy runs
   alongside it and then continues underneath once the image ends, rather than
   being held in a column beside it. */
.overview-cover-row { display: flow-root; }   /* contains the float */
.overview-copy > p:last-child { margin-bottom: 0; }
.journal-cover {
  float: left; width: 108px; height: auto; aspect-ratio: 4 / 5;
  margin: .2rem 1.15rem .6rem 0;
  object-fit: cover; border-radius: var(--r-md);
  border: 1px solid var(--c-border); background: var(--c-surface-2);
  box-shadow: 0 4px 14px rgba(4, 30, 38, .16);
}
@media (min-width: 600px) { .journal-cover { width: 132px; margin-right: 1.5rem; } }

.jcard-cover {
  flex: 0 0 64px; width: 64px; height: auto; aspect-ratio: 4 / 5;
  object-fit: cover; border-radius: var(--r-sm);
  border: 1px solid var(--c-border); background: var(--c-surface-2);
  box-shadow: 0 2px 8px rgba(4, 30, 38, .12);
}
@media (min-width: 600px) { .jcard-cover { flex-basis: 76px; width: 76px; } }
.jcard .jcard-cat { font-size: .74rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--c-accent); }
.jcard h3 { font-family: var(--font-serif); font-size: 1.2rem; margin: .4rem 0; line-height: 1.3; }
.jcard .authors { font-size: .85rem; color: var(--c-text-muted); }
.jcard .excerpt { padding: 0 1.4rem; color: var(--c-text-muted); font-size: .92rem; flex: 1; }
.jcard .jcard-foot { display: flex; align-items: center; justify-content: space-between; gap: .5rem 1rem; padding: 1rem 1.4rem; margin-top: 1rem; border-top: 1px solid var(--c-border); font-size: .82rem; color: var(--c-text-faint); }
/* The citation coordinate (Vol · Issue · pages). Right-aligned so that if it
   wraps on a narrow card it stacks under itself rather than drifting left, and
   the date on the other end keeps its place. */
.jcard-locator { text-align: right; }

/* ==========================================================================
   Page header (app pages)
   ========================================================================== */
.page-head { margin-bottom: var(--gap); }
.page-head .crumbs { font-size: .82rem; color: var(--c-text-muted); margin-bottom: .5rem; display: flex; gap: .4rem; align-items: center; }
.page-head .crumbs a { color: var(--c-text-muted); }
.page-head h1 { font-size: clamp(1.5rem, 3.5vw, 2.1rem); }
.page-head p { color: var(--c-text-muted); margin-top: .35rem; max-width: 68ch; }
.page-head .head-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }

/* Stat tiles row */
.stat-row { display: grid; gap: 1rem; grid-template-columns: repeat(2, 1fr); margin-bottom: var(--gap); }
.stat-tile { display: flex; align-items: center; gap: .9rem; padding: 1.1rem 1.25rem; }
.stat-tile .st-icon { width: 44px; height: 44px; flex: 0 0 44px; border-radius: var(--r-md); display: grid; place-items: center; }
.stat-tile .st-icon svg { width: 22px; height: 22px; }
.stat-tile b { font-size: 1.6rem; line-height: 1; display: block; }
.stat-tile span { font-size: .82rem; color: var(--c-text-muted); }
.ic-blue { background: var(--c-info-bg); color: var(--c-info); }
.ic-amber { background: var(--c-warning-bg); color: var(--c-warning); }
.ic-green { background: var(--c-success-bg); color: var(--c-success); }
.ic-purple { background: #f3e8fd; color: #6b21a8; }

/* ==========================================================================
   Tabs / segmented control
   ========================================================================== */
.tabs { display: flex; gap: .25rem; padding: .3rem; background: var(--c-surface-2); border: 1px solid var(--c-border); border-radius: var(--r-md); overflow-x: auto; }
.tabs button { flex: 1; min-width: max-content; padding: .55rem 1rem; border: 0; background: none; border-radius: var(--r-sm); font-weight: 600; font-size: .9rem; color: var(--c-text-muted); cursor: pointer; white-space: nowrap; }
.tabs button.active { background: var(--c-surface); color: var(--c-primary-700); box-shadow: var(--shadow-sm); }

/* ---- Submission detail tabs ----
   Wraps rather than scrolls sideways. The left column is only about
   three-fifths of a desktop panel, which is not enough for five tabs, and a
   tab clipped at the edge of a strip with no visible scrollbar is a tab nobody
   finds -- a second row is plainer. */
.detail-tabs {
  margin: 1.1rem 0 0;
  flex-wrap: wrap; row-gap: .25rem; overflow: visible;
}
.detail-tabs button {
  display: inline-flex; align-items: center; gap: .4rem; justify-content: center;
  /* Not flex:1 -- with five tabs on a narrow panel equal shares squeeze the
     labels to nothing. Each tab is as wide as its own label instead, and the
     strip scrolls. */
  flex: 0 0 auto;
}
.detail-tabs button svg { color: var(--c-text-faint); }
.detail-tabs button.active svg { color: var(--c-primary); }
.detail-tabs .tab-n {
  display: inline-grid; place-items: center; min-width: 1.35rem; padding: 0 .35rem;
  border-radius: var(--r-full); background: var(--c-border); color: var(--c-text-muted);
  font-size: .72rem; font-weight: 700; line-height: 1.5;
}
.detail-tabs button.active .tab-n { background: var(--c-primary-50); color: var(--c-primary-700); }

/* The panel a tab shows. Its first block sits straight under the strip, so the
   1.5rem that separates one block from the next would read as a gap here. */
.detail-tabpanel { margin-top: 1.25rem; }
.detail-tabpanel > .detail-block:first-child,
.detail-tabpanel > .detail-facts:first-child { margin-top: 0; }

/* ==========================================================================
   Notifications list
   ========================================================================== */
.notif-list { display: flex; flex-direction: column; }
.notif { display: flex; gap: 1rem; padding: 1.1rem 1.25rem; border-bottom: 1px solid var(--c-border); position: relative; }
.notif:last-child { border-bottom: 0; }
.notif.unread { background: var(--c-primary-50); }
.notif.unread::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--c-primary); }
.notif .n-icon { width: 42px; height: 42px; flex: 0 0 42px; border-radius: var(--r-full); display: grid; place-items: center; }
.notif .n-icon svg { width: 20px; height: 20px; }
.notif .n-body { flex: 1; min-width: 0; }
.notif .n-body p { font-size: .93rem; }
.notif .n-body .n-time { font-size: .78rem; color: var(--c-text-faint); margin-top: .2rem; }
.notif .n-unread-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--c-primary); flex: 0 0 9px; margin-top: .5rem; }

/* ==========================================================================
   Alerts / banners
   ========================================================================== */
.alert { display: flex; gap: .75rem; padding: .9rem 1.1rem; border-radius: var(--r-md); border: 1px solid; font-size: .9rem; align-items: flex-start; }
.alert svg { width: 20px; height: 20px; flex: 0 0 20px; margin-top: 1px; }
.alert-info { background: var(--c-info-bg); border-color: #bcd4ee; color: #1a4a80; }
.alert-success { background: var(--c-success-bg); border-color: #b8e0cc; color: #14603f; }
.alert-warning { background: var(--c-warning-bg); border-color: #ecd3a8; color: #825209; }

/* ==========================================================================
   Detail layout (review page)
   ========================================================================== */
.detail-grid { display: grid; gap: var(--gap); grid-template-columns: 1fr; }
.meta-list { display: grid; gap: .9rem; }
.meta-list .m-row { display: grid; gap: .15rem; }
.meta-list .m-row dt { font-size: .76rem; text-transform: uppercase; letter-spacing: .03em; color: var(--c-text-faint); font-weight: 700; }
.meta-list .m-row dd { font-size: .95rem; }

/* Timeline */
.timeline { display: grid; gap: 0; }
.tl-item { display: grid; grid-template-columns: 28px 1fr; gap: .8rem; padding-bottom: 1.25rem; position: relative; }
.tl-item:not(:last-child)::before { content: ""; position: absolute; left: 13px; top: 24px; bottom: 0; width: 2px; background: var(--c-border); }
.tl-dot { width: 28px; height: 28px; border-radius: 50%; display: grid; place-items: center; background: var(--c-surface); border: 2px solid var(--c-border-strong); z-index: 1; }
.tl-dot.done { background: var(--c-success); border-color: var(--c-success); color: #fff; }
.tl-dot.current { background: var(--c-primary); border-color: var(--c-primary); color: #fff; }
.tl-dot svg { width: 14px; height: 14px; }
.tl-body b { font-size: .92rem; }
.tl-body span { font-size: .8rem; color: var(--c-text-muted); display: block; }

/* PDF preview stub */
.pdf-preview { aspect-ratio: 3/4; background: var(--c-surface-2); border: 1px solid var(--c-border); border-radius: var(--r-md); display: grid; place-items: center; text-align: center; padding: 2rem; }
.pdf-preview svg { width: 56px; height: 56px; color: var(--c-danger); margin: 0 auto 1rem; }

/* ==========================================================================
   Modal / pop-up (alerts, confirmations, info — one shared design)
   ========================================================================== */
.modal-overlay {
  position: fixed; inset: 0; z-index: 1000;
  display: grid; place-items: center; padding: 1.25rem;
  /* Dimmed, not blurred -- the dialog is often a loader, and blurring the
     whole page behind it was the most visible blur in the app. */
  background: rgba(11, 32, 39, .58);
  opacity: 0; visibility: hidden; transition: opacity .2s, visibility .2s;
}
.modal-overlay.open { opacity: 1; visibility: visible; }
.modal {
  width: 100%; max-width: 440px; background: var(--c-surface);
  border-radius: var(--r-lg); box-shadow: var(--shadow-lg);
  padding: clamp(1.5rem, 4vw, 2rem); text-align: center;
  transform: translateY(10px) scale(.98); transition: transform .2s;
}
.modal-overlay.open .modal { transform: none; }
.modal-icon { width: 56px; height: 56px; border-radius: 50%; display: grid; place-items: center; margin: 0 auto 1.1rem; }
.modal-icon svg { width: 28px; height: 28px; }
.modal-icon.type-success { background: var(--c-success-bg); color: var(--c-success); }
.modal-icon.type-info    { background: var(--c-info-bg); color: var(--c-info); }
.modal-icon.type-warning { background: var(--c-warning-bg); color: var(--c-warning); }
.modal-icon.type-danger, .modal-icon.type-error { background: var(--c-danger-bg); color: var(--c-danger); }
.modal-icon.type-question { background: var(--c-primary-50); color: var(--c-primary); }
.modal-title { font-size: 1.2rem; margin-bottom: .4rem; }
.modal-message { color: var(--c-text-muted); margin-bottom: 1.6rem; line-height: 1.55; }
.modal-actions { display: flex; gap: .75rem; justify-content: center; flex-wrap: wrap; }
.modal-actions .btn { min-width: 120px; }
body.modal-open { overflow: hidden; }

/* ==========================================================================
   Journal (single-page "About") — full-width section blocks
   ========================================================================== */
.journal-content { max-width: 100%; }
/* A readable measure for the long policy pages -- privacy, guidelines, the
   journal's own about page.

   Unconstrained, a paragraph on a desktop ran to about 143 characters a line.
   The eye loses its place returning from the end of a line that long, which is
   why continuous text is set narrow in print and why these pages, which people
   actually have to read rather than skim, were the hardest reading in the app.
   90ch is close to the 95ch the published article already uses, so the two
   still look like the same journal, and it leaves nothing like the empty
   gutters an over-narrow column would.

   The cap is on the prose only. Section headers, their icon badges and the lead
   line keep the full width, so the page still reads as full-bleed sections
   rather than one narrow column floating in space. */
.journal-content .prose { max-width: 90ch; }
/* Policy links here are long bare URLs; without this one of them is wider
   than a phone screen and drags the whole page sideways. */
.journal-content a { overflow-wrap: anywhere; }

/* Section blocks — separated by an icon-badged header and a divider rule */
.section-block { scroll-margin-top: calc(var(--header-h) + 1.25rem); }
.section-block + .section-block {
  margin-top: clamp(2.25rem, 5vw, 3.5rem);
  padding-top: clamp(2.25rem, 5vw, 3.5rem);
  border-top: 1px solid var(--c-border);
}
.sec-header { display: flex; align-items: center; gap: .9rem; margin-bottom: .85rem; }
.sec-ic { width: 48px; height: 48px; flex: 0 0 48px; border-radius: var(--r-md); display: grid; place-items: center; }
.sec-ic svg { width: 24px; height: 24px; }
.section-block h2 { font-size: clamp(1.5rem, 3.5vw, 2rem); }
.section-block > .lead { color: var(--c-text-muted); font-size: 1.05rem; margin-bottom: 1.25rem; }
.section-block h3 { font-size: 1.15rem; margin: 1.6rem 0 .5rem; }
.section-block h4 { font-size: 1rem; font-weight: 700; margin: 1.4rem 0 .65rem; }
.prose p { color: var(--c-text); margin-bottom: 1rem; }
.prose ul { margin: 0 0 1rem; padding-left: 1.1rem; color: var(--c-text); }
.prose li { margin-bottom: .5rem; }
.prose li::marker { color: var(--c-primary); }

/* Editorial board */
.board-grid { display: grid; gap: 1rem; grid-template-columns: 1fr; margin-top: 1rem; }
/* Aligned to the top, not centred: a card with a department, a city and an
   email is three or four lines tall, and centring left the avatar floating
   halfway down beside it. */
.board-card { display: flex; align-items: flex-start; gap: .9rem; padding: 1.1rem 1.2rem; }
.board-card > div { min-width: 0; }
.board-card .avatar { width: 46px; height: 46px; flex: 0 0 46px; font-size: 1rem; }
.board-card b { display: block; font-size: .95rem; }
.board-card .b-role { color: var(--c-primary-700); font-size: .82rem; font-weight: 600; }
.board-card .b-aff { color: var(--c-text-muted); font-size: .82rem; }
/* An address is one unbroken token and long ones would otherwise push the card
   wider than its grid column. */
.board-card .b-email { font-size: .82rem; margin: .1rem 0 .15rem; overflow-wrap: anywhere; }
.board-card .b-email svg { color: var(--c-text-faint); }
.board-chief { border-color: var(--c-primary); background: linear-gradient(180deg, var(--c-primary-50), var(--c-surface)); }

/* ==========================================================================
   Back-to-top button
   ========================================================================== */
.to-top {
  position: fixed; z-index: 60;
  right: clamp(1rem, 3vw, 2rem); bottom: clamp(1rem, 3vw, 2rem);
  width: 48px; height: 48px; border-radius: var(--r-full);
  display: grid; place-items: center; cursor: pointer;
  background: var(--c-primary); color: #fff; border: 0;
  box-shadow: var(--shadow-lg);
  opacity: 0; visibility: hidden; transform: translateY(12px);
  transition: opacity .2s, transform .2s, visibility .2s, background .15s;
}
.to-top.show { opacity: 1; visibility: visible; transform: none; }
.to-top:hover { background: var(--c-primary-700); }
.to-top:active { transform: translateY(1px); }
.to-top svg { width: 22px; height: 22px; }

/* ==========================================================================
   Archive (year sections + month links)
   ========================================================================== */
.archive-year + .archive-year { margin-top: clamp(1.75rem, 4vw, 2.75rem); }
.archive-year .section-head h2 { font-size: clamp(1.3rem, 3vw, 1.7rem); }
/* Wider tracks than the old month names needed: an issue tile now carries a
   date range and a volume/issue line. */
.month-links { display: grid; gap: .75rem; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); }
/* The volume/issue sits under the date range as a second line. */
.month-link .vol-issue {
  display: block; margin-top: .15rem;
  font-size: .74rem; font-weight: 600; color: var(--c-text-muted);
}
.month-link:hover .vol-issue { color: inherit; }
.month-link {
  display: flex; align-items: center; justify-content: space-between; gap: .75rem;
  padding: .85rem 1.1rem; border: 1px solid var(--c-border); border-radius: var(--r-md);
  background: var(--c-surface); box-shadow: var(--shadow-sm); font-weight: 600; color: var(--c-text);
  transition: transform .15s, box-shadow .15s, border-color .15s, color .15s;
}
.month-link:hover { transform: translateY(-2px); border-color: var(--c-primary); color: var(--c-primary-700); box-shadow: var(--shadow-md); }
.month-link .count { font-size: .74rem; font-weight: 700; color: var(--c-text-muted); background: var(--c-surface-2); border-radius: var(--r-full); padding: .12rem .55rem; }

/* ==========================================================================
   Footer
   ========================================================================== */
.site-footer { background: #0b2027; color: #cdd8dd; margin-top: 3rem; }
/* Row of columns: [logo + description] | The Journal | Account | Resources */
.footer-main { display: grid; gap: 2rem; padding-block: 2.75rem 2.25rem; grid-template-columns: 1fr; align-items: start; }
.footer-logo-link { display: inline-block; }
.footer-logo-box { padding: .55rem .9rem; }
.footer-logo-box .brand-logo { height: 80px; }
.footer-desc { margin: 1.1rem 0 0; font-size: .92rem; line-height: 1.6; color: #9fb0b7; max-width: 42ch; }
.footer-col h4 { font-size: .82rem; text-transform: uppercase; letter-spacing: .05em; color: #7f939b; margin-bottom: .9rem; }
.footer-col a { display: block; color: #cdd8dd; font-size: .9rem; padding: .25rem 0; }
.footer-col a:hover { color: #fff; }
.footer-bottom { border-top: 1px solid #1c3540; padding-block: 1.25rem; font-size: .84rem; color: #7f939b; display: flex; flex-wrap: wrap; gap: .75rem; justify-content: space-between; }

/* ==========================================================================
   Responsive — Tablet (>=600px)
   ========================================================================== */
@media (min-width: 600px) {
  .grid-cards { grid-template-columns: repeat(2, 1fr); }
  .two-col { grid-template-columns: repeat(2, 1fr); }
  .name-row { grid-template-columns: 110px 1fr 1fr; }
  .stat-row { grid-template-columns: repeat(4, 1fr); }
  .footer-main { grid-template-columns: 1.7fr 1fr 1fr; }
  .board-grid { grid-template-columns: repeat(2, 1fr); }
}

/* ==========================================================================
   Responsive — Desktop (>=1024px)
   ========================================================================== */
@media (min-width: 1024px) {
  .grid-cards { grid-template-columns: repeat(3, 1fr); }
  .auth-wrap { grid-template-columns: 1.05fr 1fr; }
  .auth-hero { display: grid; }
  .detail-grid { grid-template-columns: 1.6fr 1fr; }
  .review-grid { grid-template-columns: 1fr 1.15fr; }
  .board-grid { grid-template-columns: repeat(3, 1fr); }
  .footer-main { grid-template-columns: 1.9fr 1fr 1fr 1fr; }
}

/* Keep the header compact on mid-size screens — show the long subtitle
   only on large displays where there is room for it. */
@media (max-width: 1279px) {
  .site-header .brand small { display: none; }
}

/* Small laptops, where the full menu first appears: seven items, the user chip
   and the bell all have to fit on one row, so the links tighten rather than
   crowd the wordmark out of the header. */
@media (min-width: 1024px) and (max-width: 1279px) {
  .nav-links { gap: 0; }
  .nav-links a, .nav-dd-toggle { padding: .5rem .6rem; font-size: .88rem; }
}

/* ==========================================================================
   Responsive — Mobile / tablet nav (<=1023px)
   ========================================================================== */
@media (max-width: 1023px) {
  .nav-toggle { display: grid; }
  /* .nav-links is taken out of the flow just below, so nothing is left to push
     the actions across -- without this they sat bunched against the wordmark
     instead of at the right edge of the header. */
  .nav-actions { margin-left: auto; }
  .nav-links {
    position: fixed; inset: var(--header-h) 0 auto 0;
    flex-direction: column; align-items: stretch; gap: .1rem;
    background: var(--c-surface); border-bottom: 1px solid var(--c-border);
    padding: .75rem 1rem 1.1rem; box-shadow: var(--shadow-lg);
    transform: translateY(-120%); transition: transform .25s ease; margin-left: 0;
  }
  .site-header.nav-open .nav-links { transform: translateY(0); overflow-y: auto; max-height: calc(100vh - var(--header-h)); }
  .nav-links a { padding: .8rem 1rem; font-size: 1rem; }
  .userchip .who { display: none; }
  /* Journal dropdown expands inline within the mobile panel */
  .nav-dd { width: 100%; }
  .nav-dd-toggle { width: 100%; justify-content: space-between; padding: .8rem 1rem; font-size: 1rem; }
  .nav-dd .dropdown-menu {
    position: static; opacity: 1; visibility: visible; transform: none;
    box-shadow: none; border: 0; padding: .1rem 0 .4rem .75rem; min-width: 0;
  }
  .nav-dd .dropdown-menu a { padding: .65rem 1rem; font-size: .95rem; }
}

/* Phones. Signed out there are two buttons and the menu toggle beside the
   wordmark, which together want more room than a 360px screen has: the emblem
   carries the brand on its own here, and the buttons tighten up. */
@media (max-width: 599px) {
  .nav { gap: .6rem; }
  .site-header .brand-text { display: none; }
  /* Nothing left in the brand but the emblem, and it must not be the thing
     that gives way -- half a clipped logo is worse than a tighter button. */
  .site-header .brand { flex: 0 0 auto; }
  .site-header .brand-mark { width: 42px; height: 42px; flex-basis: 42px; }
  .nav-actions { gap: .35rem; }
  .nav-actions .btn-sm { padding: .38rem .5rem; font-size: .82rem; }
  .nav-actions .btn-sm svg { width: 16px; height: 16px; }
  .nav-toggle { width: 38px; height: 38px; }
  .nav-toggle svg { width: 20px; height: 20px; }
  .bell { width: 36px; height: 36px; }
}

@media (min-width: 1024px) {
  .nav-links { display: flex !important; transform: none !important; }
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; scroll-behavior: auto !important; }
}

/* ---- Contact us ---- */
.contact-grid { display: grid; gap: var(--gap); align-items: start; }
@media (min-width: 1024px) { .contact-grid { grid-template-columns: 1.5fr 1fr; } }
.contact-facts { display: grid; gap: 1.1rem; margin: 0; }
.contact-facts dt {
  display: flex; align-items: center; gap: .45rem;
  font-size: .74rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: .04em; color: var(--c-text-muted);
}
.contact-facts dt svg { width: 15px; height: 15px; }
.contact-facts dd { margin: .25rem 0 0; font-size: .95rem; line-height: 1.55; }
/* <address> defaults to italic; the postal address should read as plain text. */
.contact-address { font-style: normal; margin: 0; line-height: 1.6; }

/* ---- Vancouver reference list ---- */
.ref-list { margin: 0; padding-left: 1.4rem; font-size: .92rem; line-height: 1.6; }
.ref-list li { margin: 0 0 .45rem; padding-left: .2rem; }
.ref-list li::marker { color: var(--c-text-muted); }

/* Style guidance under the references field, collapsed by default. */
.ref-help { margin-top: .6rem; font-size: .87rem; }
.ref-help summary { cursor: pointer; color: var(--c-primary); font-weight: 500; }
.ref-help dl {
  margin: .75rem 0 .5rem; padding: .85rem 1rem; display: grid; gap: .7rem;
  background: var(--c-surface-2); border: 1px solid var(--c-border); border-radius: var(--r-sm);
}
.ref-help dt { font-size: .74rem; font-weight: 600; text-transform: uppercase; letter-spacing: .04em; color: var(--c-text-muted); }
.ref-help dd { margin: .2rem 0 0; line-height: 1.5; overflow-wrap: anywhere; }
.ref-help p { margin: 0; font-size: .85rem; }

/* ---- Journal particulars ---- */
.particulars {
  margin: 0; display: grid; gap: 1px;
  background: var(--c-border); border: 1px solid var(--c-border);
  border-radius: var(--r-md); overflow: hidden;
}
.particular {
  display: grid; gap: .15rem;
  padding: .8rem 1rem; background: var(--c-surface);
}
.particulars dt {
  font-size: .74rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: .04em; color: var(--c-text-muted);
}
.particulars dd { margin: 0; font-size: .95rem; line-height: 1.55; overflow-wrap: anywhere; }
/* Two columns once there is room; the label sits beside its value rather
   than above it, which reads faster in a long list of short facts. */
@media (min-width: 600px) {
  .particular { grid-template-columns: 13rem 1fr; gap: 1rem; align-items: baseline; }
  .particulars dt { padding-top: .1rem; }
}
@media (min-width: 1024px) {
  .particulars { grid-template-columns: 1fr 1fr; }
  .particular { grid-template-columns: 11rem 1fr; }
}

/* ==========================================================================
   Slide-out drawer (notifications), with a frosted-glass surface.
   ========================================================================== */
.drawer-scrim {
  position: fixed; inset: 0; z-index: 1100;
  background: rgba(11, 32, 39, .42);
  opacity: 0; transition: opacity .22s ease;
}
.drawer-scrim.open { opacity: 1; }

.drawer {
  position: fixed; top: 0; right: 0; bottom: 0; z-index: 1101;
  width: min(420px, 100vw);
  display: flex; flex-direction: column;
  transform: translateX(100%); transition: transform .24s cubic-bezier(.4, 0, .2, 1);
  box-shadow: -18px 0 48px rgba(11, 32, 39, .18);
}
.drawer.open { transform: none; }

/* The glass itself: a translucent pane over a blurred backdrop, with a bright
   inner edge along the top-left so it reads as a lit sheet rather than a flat
   panel. Falls back to a solid surface where backdrop-filter is unsupported. */
.glass {
  background: rgba(255, 255, 255, .96);
  border-left: 1px solid rgba(255, 255, 255, .6);
}
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  .glass {
    background: linear-gradient(160deg, rgba(255,255,255,.78), rgba(255,255,255,.62));
    backdrop-filter: blur(18px) saturate(180%);
    -webkit-backdrop-filter: blur(18px) saturate(180%);
    box-shadow: -18px 0 48px rgba(11, 32, 39, .18),
                inset 1px 1px 0 rgba(255, 255, 255, .75);
  }
}

.drawer-head {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: 1.1rem 1.1rem 1.1rem 1.35rem;
  border-bottom: 1px solid rgba(255, 255, 255, .55);
  background: linear-gradient(180deg, rgba(255,255,255,.55), rgba(255,255,255,.15));
}
.drawer-head h2 {
  display: flex; align-items: center; gap: .55rem;
  margin: 0; font-size: 1.05rem;
}
.drawer-head h2 svg { width: 19px; height: 19px; color: var(--c-primary); }
.drawer-head-actions { display: flex; gap: .4rem; flex: 0 0 auto; }
.drawer-head-actions .btn { background: rgba(255,255,255,.7); }

.drawer-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
.drawer-foot {
  flex: 0 0 auto; padding: .85rem 1.35rem;
  border-top: 1px solid rgba(255, 255, 255, .55);
  background: linear-gradient(0deg, rgba(255,255,255,.55), rgba(255,255,255,.15));
  font-size: .88rem;
}
.drawer-foot a { display: inline-flex; align-items: center; gap: .4rem; color: var(--c-primary); }

/* Rows sit on the glass, so they lose their opaque background. */
.drawer .notif { background: transparent; border-bottom: 1px solid rgba(255,255,255,.5); }
.drawer .notif.unread { background: rgba(255, 255, 255, .45); }
.drawer .notif .n-icon { background: rgba(255,255,255,.75); }

.notif-empty { padding: 3rem 1.5rem; text-align: center; }
.notif-empty svg { width: 34px; height: 34px; color: var(--c-text-faint); }
.notif-empty p { margin: .6rem 0 0; }

body.drawer-open { overflow: hidden; }

@media (max-width: 599px) {
  .drawer { width: 100vw; }
  .drawer-head { padding-left: 1.1rem; }
}
@media (prefers-reduced-motion: reduce) {
  .drawer { transition: none; }
}

/* ---- Settings: two columns of cards from tablet up ---- */
.settings-grid { display: grid; gap: var(--gap); align-items: start; grid-template-columns: 1fr; }
@media (min-width: 900px) { .settings-grid { grid-template-columns: 1fr 1fr; } }
/* A two-column row whose second field is conditionally hidden: collapse to a
   single column so the remaining field fills the width. */
.two-col.is-single { grid-template-columns: 1fr; }

/* A retired id kept alive so old links and bookmarks still land correctly.
   Same scroll offset as a section block, so the sticky header does not cover
   the heading it points at. */
.anchor-alias { display: block; height: 0; scroll-margin-top: calc(var(--header-h) + 1.25rem); }

/* Chip rows in the list toolbars (work queues, status filters). The toolbar
   itself is a plain flex row; these need to wrap and breathe. */
.list-toolbar.queue-chips { flex-wrap: wrap; gap: .45rem; align-items: center; }

/* Standalone .st-icon (empty states, auth pages). It was only ever styled
   inside .stat-tile, so used on its own the box did not centre its icon. */
.st-icon { display: grid; place-items: center; }
.st-icon > svg { width: 24px; height: 24px; }
.st-icon-lg > svg { width: 34px; height: 34px; }

/* ==========================================================================
   Hero artwork (inc/hero-art.php)
   Every base style below is the *finished* state, and each animation starts
   from somewhere else and returns here. That way the global reduced-motion
   rule -- `* { animation: none !important }` -- leaves the drawing complete
   rather than frozen mid-way, with no separate reduced-motion styles to keep
   in step.
   ========================================================================== */
/* The halo and the ring beat together as one pulse -- they are concentric, so
   beating them out of step would read as two separate things. 1.25s is about
   48 bpm: a resting rhythm, slow enough to sit behind the text rather than
   nag at it. */
.hero-svg .ha-halo     { opacity: .05; transform-origin: 240px 176px; animation: ha-beat 1.25s ease-out infinite; }
.hero-svg .ha-ring     { opacity: .12; transform-origin: 240px 176px; animation: ha-beat-ring 1.25s ease-out infinite; }

/* Drawn in full by default; the animation sweeps it on. */
.hero-svg .ha-trace    { stroke-dasharray: 620; stroke-dashoffset: 0; animation: ha-draw 7s cubic-bezier(.5, 0, .2, 1) infinite; }

.hero-svg .ha-molecule { transform-origin: 240px 70px; animation: ha-drift 11s ease-in-out infinite; }
.hero-svg .ha-node     { transform-box: fill-box; transform-origin: center; animation: ha-node 4.5s ease-in-out infinite; }
.hero-svg .ha-node-2   { animation-delay: .6s; }
.hero-svg .ha-node-3   { animation-delay: 1.2s; }
.hero-svg .ha-node-4   { animation-delay: 1.8s; }
.hero-svg .ha-node-5   { animation-delay: 2.4s; }

.hero-svg .ha-spark    { opacity: .3; animation: ha-twinkle 6s ease-in-out infinite; }
.hero-svg .ha-spark-2  { animation-delay: 1.1s; }
.hero-svg .ha-spark-3  { animation-delay: 2.2s; }
.hero-svg .ha-spark-4  { animation-delay: 3.3s; }
.hero-svg .ha-spark-5  { animation-delay: 4.4s; }
.hero-svg .ha-spark-6  { animation-delay: 5.5s; }

.hero-svg .ha-line     { opacity: .4; animation: ha-settle 1.1s ease-out both; }
.hero-svg .ha-line-2   { animation-delay: .08s; }
.hero-svg .ha-line-3   { animation-delay: .16s; }
.hero-svg .ha-line-4   { animation-delay: .24s; }
.hero-svg .ha-line-5   { animation-delay: .32s; }
.hero-svg .ha-line-6   { animation-delay: .40s; }

/* A heartbeat is not a symmetric pulse. Systole is two quick contractions --
   lub, dub -- and then most of the cycle is diastole, at rest. It is that
   pause, more than the beat itself, that makes it read as a heart rather than
   a throb, so the two peaks are packed into the first third and the rest of
   the cycle holds still.
   0% and 100% are the resting values, which are also the base styles above, so
   the reduced-motion rule leaves the circle sitting correctly at rest. */
@keyframes ha-beat      { 0%        { opacity: .05; transform: scale(1); }
                          8%        { opacity: .11; transform: scale(1.05); }   /* lub */
                          17%       { opacity: .07; transform: scale(1.012); }
                          25%       { opacity: .09; transform: scale(1.034); }  /* dub */
                          42%, 100% { opacity: .05; transform: scale(1); } }
@keyframes ha-beat-ring { 0%        { opacity: .12; transform: scale(1); }
                          8%        { opacity: .30; transform: scale(1.035); }
                          17%       { opacity: .17; transform: scale(1.008); }
                          25%       { opacity: .24; transform: scale(1.024); }
                          42%, 100% { opacity: .12; transform: scale(1); } }
@keyframes ha-draw    { 0%       { stroke-dashoffset: 620; }
                        55%, 92% { stroke-dashoffset: 0; }
                        100%     { stroke-dashoffset: -620; } }
@keyframes ha-drift   { 0%, 100% { transform: translateY(0); }
                        50%      { transform: translateY(-7px); } }
@keyframes ha-node    { 0%, 100% { opacity: .75; }
                        50%      { opacity: 1; } }
@keyframes ha-twinkle { 0%, 100% { opacity: .15; }
                        50%      { opacity: .55; } }
@keyframes ha-settle  { from { opacity: 0; transform: translateX(-8px); }
                        to   { opacity: .4; transform: none; } }

/* ---------------------------------------------------------------------------
   Answering a review request (review-request.php)

   Built as an ordinary page of the site, not as a sign-in screen: the reviewer
   is reading a paper to decide on it, so the paper gets the width and the
   abstract is open rather than folded behind a toggle.
   --------------------------------------------------------------------------- */
.review-answer { max-width: 62rem; margin-inline: auto; }
.review-answer .answer-title { font-size: clamp(1.25rem, 2.6vw, 1.6rem); line-height: 1.3; margin: 0 0 .25rem; }
/* Accept sits first and heaviest; declining is a choice, not a dead end. */
.review-answer .answer-actions { display: flex; gap: .75rem; flex-wrap: wrap; align-items: center; }
.review-answer .answer-done { max-width: 34rem; margin-inline: auto; }
.review-answer .answer-done h1 { font-size: 1.5rem; margin-top: .75rem; }
.review-answer .answer-done .st-icon { width: 56px; height: 56px; border-radius: 16px; margin: 0 auto; }

/* ---------------------------------------------------------------------------
   Cards / list switch (inc/card-view.php) and the list layout it turns on.
   --------------------------------------------------------------------------- */
.head-row-actions { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
.view-switch {
  display: inline-flex; gap: .2rem; padding: .2rem;
  background: var(--c-surface-2); border: 1px solid var(--c-border); border-radius: var(--r-md);
}
.view-switch-btn {
  display: inline-flex; align-items: center; gap: .4rem;
  padding: .4rem .7rem; border-radius: var(--r-sm);
  font-size: .86rem; font-weight: 600; color: var(--c-text-muted);
}
.view-switch-btn svg { width: 17px; height: 17px; }
.view-switch-btn:hover { color: var(--c-primary-700); }
.view-switch-btn.is-on { background: var(--c-surface); color: var(--c-primary-700); box-shadow: var(--shadow-sm); }

/* The list layout: the same card, laid on its side. One per row, the cover
   small and to the left, the text given the width it was short of in a tile --
   which is the point of offering the choice at all. */
.grid-cards.is-list { grid-template-columns: 1fr; }
.grid-cards.is-list .jcard { display: grid; gap: .3rem 1.1rem; grid-template-columns: 1fr; }
.grid-cards.is-list .jcard-head { display: flex; gap: 1.1rem; align-items: flex-start; }
.grid-cards.is-list .jcard-cover { flex: 0 0 64px; width: 64px; margin: 0; }
.grid-cards.is-list .excerpt { margin-top: .2rem; }

@media (min-width: 900px) {
  /* Wide enough to put the date and issue beside the text rather than under it. */
  .grid-cards.is-list .jcard { grid-template-columns: minmax(0, 1fr) auto; align-items: start; }
  .grid-cards.is-list .jcard-head { grid-column: 1; }
  .grid-cards.is-list .excerpt { grid-column: 1; }
  .grid-cards.is-list .jcard-doi { grid-column: 1; }
  .grid-cards.is-list .jcard-foot {
    grid-column: 2; grid-row: 1 / span 3;
    flex-direction: column; align-items: flex-end; gap: .35rem;
    border: 0; padding-left: 1rem; white-space: nowrap;
  }
}

/* ---- Article history and citation (public article page) ---- */
.article-history {
  display: flex; flex-wrap: wrap; gap: .35rem 1.1rem;
  margin: .3rem 0 .6rem; font-size: .86rem; color: var(--c-text-muted);
}
.article-history b { color: var(--c-text); font-weight: 600; }
/* The citation, set apart so it reads as something to be copied rather than as
   more prose. Monospaced-ish spacing, generous line height: it is a string a
   reader is checking character by character. */
.cite-box {
  display: flex; gap: 1rem; align-items: flex-start; flex-wrap: wrap;
  padding: .9rem 1rem; background: var(--c-surface-2);
  border: 1px solid var(--c-border); border-radius: var(--r-md);
}
.cite-text { flex: 1 1 22rem; margin: 0; font-size: .92rem; line-height: 1.7; overflow-wrap: anywhere; }
.cite-box .btn { flex: 0 0 auto; }

/* ---- The whole-page wait (app.js showPageVeil) ----
   Covers the page a click is leaving, so a navigation that takes a second is
   visibly working rather than apparently ignored. Fades in over a beat so an
   instant page never flashes it. */
.page-veil {
  position: fixed; inset: 0; z-index: 90;
  display: grid; place-items: center;
  /* Solid, not blurred. A blurred copy of the page being left is a picture of
     the wrong page; a clean ground with the mark on it is a picture of
     waiting. It also holds still, which a blur does not. */
  background: var(--c-bg, #f4f7f9);
  opacity: 0; transition: opacity .18s ease .35s;
}
.page-veil.is-on { opacity: 1; }
.page-veil .jload-text { font-size: 1rem; }
@media (prefers-reduced-motion: reduce) {
  .page-veil { transition: none; }
}

/* ---------------------------------------------------------------------------
   Article contents panel (article.php)

   The sections the editor published, listed beside the article, with whichever
   one is being read picked out. Mobile-first: above the text on a narrow
   screen, alongside and pinned from a laptop up, where there is room for a
   column that does not cost the article any width it was using.
   --------------------------------------------------------------------------- */
.article-layout { display: grid; gap: var(--gap); align-items: start; }
.article-toc-inner {
  padding: 1rem 1.1rem;
  background: var(--c-surface); border: 1px solid var(--c-border); border-radius: var(--r-md);
}
.article-toc-title {
  display: flex; align-items: center; gap: .45rem;
  margin: 0 0 .6rem; font-size: .78rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .05em; color: var(--c-text-muted);
}
.article-toc-title svg { color: var(--c-text-faint); }
.article-toc-list { list-style: none; margin: 0; padding: 0; display: grid; gap: .1rem; counter-reset: toc; }
.article-toc-list a {
  display: block; padding: .4rem .6rem; border-radius: var(--r-sm);
  border-left: 2px solid transparent;
  font-size: .9rem; line-height: 1.4; color: var(--c-text-muted);
}
.article-toc-list a:hover { background: var(--c-primary-50); color: var(--c-primary-700); }
/* Set by the scrollspy in app.js on the section currently in view. */
.article-toc-list a.active {
  background: var(--c-primary-50); color: var(--c-primary-700);
  font-weight: 600; border-left-color: var(--c-primary);
}

@media (min-width: 1024px) {
  /* The list on the right, the article keeping the measure it had. */
  .article-layout { grid-template-columns: minmax(0, 1fr) 17rem; }
  .article-toc { order: 2; }
  .article-body { order: 1; }
  /* The cell has to be as tall as the article for the panel inside it to have
     anywhere to travel: a sticky element can only move within its containing
     block, and .article-layout sets align-items:start, which shrinks every cell
     to its contents. Without this the list scrolled away with the page and had
     to be scrolled back to for every jump. */
  .article-toc { align-self: stretch; height: 100%; }
  .article-toc-inner {
    position: sticky; top: calc(var(--header-h) + 1rem);
    max-height: calc(100vh - var(--header-h) - 2rem); overflow-y: auto;
  }
}

/* Anchored sections clear the sticky header when jumped to. */
.article-section { scroll-margin-top: calc(var(--header-h) + 1rem); }

/* The submission detail panel, when it is jumped to from the list: clear of the
   sticky header rather than tucked under it. */
.detail-panel-wrap { scroll-margin-top: calc(var(--header-h) + 1rem); }

/* The text field inside the shared dialog, for MJ.prompt(). Left-aligned in a
   box whose other content is centred, because a label above a field that is
   centred reads as a caption rather than as a form. */
.modal-field { text-align: left; margin: 0 0 1.1rem; }
.modal-field-label {
  display: block; margin-bottom: .35rem;
  font-size: .85rem; font-weight: 600; color: var(--c-text);
}
.modal-field .textarea { width: 100%; }

/* ---------------------------------------------------------------------------
   Reading view (article.php)

   The same page, set for reading rather than for scanning: a warm paper
   ground, a serif face, a longer line and a wider measure. Everything that is
   chrome -- the cover, the contents panel, the tool row -- steps back, because
   a reader who has chosen this is here for the words.
   --------------------------------------------------------------------------- */
/* The contents panel's head: its title, and the reading-view button under it.

   The button is deliberately the loudest thing in this panel. Everything else
   here is muted navigation, and a quiet control among quiet links is one
   nobody sees -- so it takes the journal's accent, spans the panel, and fills
   solid once the view is on, which is state you can read without reading. */
.article-toc-head { display: grid; gap: .55rem; margin-bottom: .7rem; }
.article-toc-head .article-toc-title { margin: 0; }
.article-tools { display: block; }
.reading-toggle {
  width: 100%; justify-content: center; gap: .45rem;
  background: var(--c-primary-50); color: var(--c-primary-700);
  border: 1px solid var(--c-primary); font-weight: 600;
}
.reading-toggle:hover { background: var(--c-primary); color: #fff; }
.reading-toggle svg { width: 16px; height: 16px; }
/* On: filled, so the panel says which view you are in. */
.reading-toggle[aria-pressed="true"] {
  background: var(--c-primary); color: #fff; border-color: var(--c-primary);
}
.reading-toggle[aria-pressed="true"]:hover { background: var(--c-primary-700); border-color: var(--c-primary-700); }

.reading-view body { background: #f6f1e7; }
.reading-view .article-prose,
.reading-view .article-section h2,
.reading-view .article-headings h1,
.reading-view .article-subtitle,
.reading-view .ref-list,
.reading-view .cite-text {
  font-family: var(--font-serif);
}
.reading-view .article-prose {
  font-size: 1.12rem; line-height: 1.85; color: #2a2520; max-width: 72ch;
}
.reading-view .article-prose p { margin-bottom: 1.15rem; }
.reading-view .article-section h2 { font-size: 1.3rem; }
.reading-view .article-section { scroll-margin-top: calc(var(--header-h) + 1rem); }
.reading-view .ref-list { line-height: 1.8; }
/* The panel and the cover are navigation, not reading. Kept, quietened. */
.reading-view .article-toc-inner { background: #fffaf1; border-color: #e6dcc9; }
.reading-view .article-cover { opacity: .92; }
.reading-view .site-header { background: rgba(246, 241, 231, .92); }
.reading-view .card,
.reading-view .cite-box { background: #fffaf1; border-color: #e6dcc9; }

@media (prefers-reduced-motion: no-preference) {
  .reading-view .article-prose { transition: none; }
}

/* ==========================================================================
   "This file replaces that one" (publish-journal.php)

   Editing a published article overwrites what readers already have. The card
   names the file currently on the article; choosing a new one strikes it
   through and the notice underneath says what arrives in its place.
   ========================================================================== */
.current-file {
  display: flex; align-items: center; gap: .7rem;
  margin: .15rem 0 .55rem;
  padding: .6rem .75rem;
  border: 1px solid var(--c-border); border-radius: var(--r-md);
  background: var(--c-surface-2);
  font-size: .88rem;
  transition: opacity .15s, border-color .15s;
}
.current-file > svg { flex: 0 0 auto; color: var(--c-text-muted); }
.current-file > div { min-width: 0; flex: 1 1 auto; }
.current-file b { display: block; word-break: break-word; }
.current-file .muted { display: block; font-size: .8rem; color: var(--c-text-muted); }
.current-file .pill-quiet {
  flex: 0 0 auto;
  background: var(--c-surface); border: 1px solid var(--c-border);
  color: var(--c-text-muted); font-size: .72rem; font-weight: 600;
  padding: .15rem .5rem; border-radius: 999px; white-space: nowrap;
}

/* On its way out: dimmed and struck through, so the swap reads at a glance. */
.current-file.is-replaced { opacity: .55; border-style: dashed; }
.current-file.is-replaced b { text-decoration: line-through; }
.current-file.is-replaced .pill-quiet::after { content: " — being replaced"; }

.replace-notice {
  margin: .45rem 0 0; padding: .55rem .7rem;
  border-radius: var(--r-md);
  font-size: .84rem; line-height: 1.5;
}
.replace-notice.is-replacing {
  background: var(--c-warning-bg, #fdf6e7); border: 1px solid #ecd3a8; color: #825209;
}
.replace-notice.is-adding {
  background: var(--c-surface-2); border: 1px solid var(--c-border); color: var(--c-text-muted);
}

@media (max-width: 599px) {
  .current-file { flex-wrap: wrap; }
  .current-file .pill-quiet { order: 3; }
}
/* The "last reviewed / applies to" line under the heading on a policy page.
   Not a heading and not body copy: one quiet line of provenance, which is what
   a reader checks first on a document like this. */
.policy-meta {
  display: flex; align-items: center; flex-wrap: wrap; gap: .45rem;
  margin: 0 0 clamp(2rem, 4vw, 3rem);
  padding: .7rem .95rem;
  border: 1px solid var(--c-border); border-radius: var(--r-md);
  background: var(--c-surface-2);
  color: var(--c-text-muted); font-size: .88rem;
}
.policy-meta b { color: var(--c-text); }
.policy-meta .inline-ic { color: var(--c-text-faint); }

/* ==========================================================================
   "On this page" for a long policy (privacy.php)

   A privacy statement is read by people hunting one answer -- can you delete my
   data, who sees my manuscript -- and eleven sections is more than anyone
   scrolls through to find it. So the list behaves exactly as the published
   article's contents panel does: above the text on a phone, and on a wide
   screen a column on the right that stays put while the policy scrolls past,
   with the current section marked.

   The marking is the scrollspy already in app.js: the panel carries
   data-spy-nav, each section data-spy-section, and .active is what it sets.
   ========================================================================== */
.policy-layout { display: grid; gap: var(--gap); align-items: start; }
.policy-body,
.policy-body > * { min-width: 0; }

.policy-toc-inner {
  padding: 1rem 1.1rem;
  background: var(--c-surface); border: 1px solid var(--c-border); border-radius: var(--r-md);
}
.policy-toc-title {
  display: flex; align-items: center; gap: .45rem;
  margin: 0 0 .6rem; font-size: .78rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .05em; color: var(--c-text-muted);
}
.policy-toc-title svg { color: var(--c-text-faint); }
.policy-toc-list { list-style: none; margin: 0; padding: 0; display: grid; gap: .1rem; }
.policy-toc-list li { margin: 0; }
.policy-toc-list a {
  display: block; padding: .4rem .6rem; border-radius: var(--r-sm);
  border-left: 2px solid transparent;
  font-size: .9rem; line-height: 1.4; color: var(--c-text-muted); text-decoration: none;
  transition: background .12s, color .12s, border-color .12s;
}
.policy-toc-list a:hover, .policy-toc-list a:focus-visible {
  background: var(--c-primary-50); color: var(--c-primary-700);
}
/* Set by the scrollspy on whichever section is in view. */
.policy-toc-list a.active {
  background: var(--c-primary-50); color: var(--c-primary-700);
  font-weight: 600; border-left-color: var(--c-primary);
}

/* Below desktop the panel sits above the policy, and eleven items in one column
   would push the text off the screen before it starts -- so it goes wide. */
@media (max-width: 1023px) {
  .policy-toc-list { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .1rem 1rem; }
}
@media (max-width: 599px) {
  .policy-toc-list { grid-template-columns: 1fr; }
}

@media (min-width: 1024px) {
  .policy-layout { grid-template-columns: minmax(0, 1fr) 17rem; }
  .policy-toc { order: 2; }
  .policy-body { order: 1; }
  /* The cell has to be as tall as the policy for the panel inside it to have
     anywhere to travel: a sticky element can only move within its containing
     block, and align-items:start shrinks every cell to its contents. */
  .policy-toc { align-self: stretch; height: 100%; }
  .policy-toc-inner {
    position: sticky; top: calc(var(--header-h) + 1rem);
    max-height: calc(100vh - var(--header-h) - 2rem); overflow-y: auto;
  }
}

/* Jumped-to sections clear the sticky header rather than hiding under it. */
.section-block { scroll-margin-top: calc(var(--header-h) + 1rem); }

/* The lead has to obey the same measure as the prose it introduces. It is a
   direct child of .section-block, so the cap on .journal-content .prose never
   reached it -- which left every section opening with one line running the full
   width of the page and the rest of it set to a comfortable measure. */
.journal-content .section-block > .lead { max-width: 90ch; }

/* The legal line at the foot of the signed-out pages (inc/auth-footer.php).
   Quiet: it is there to be found by someone who goes looking before typing
   their name into a form, not to compete with the form itself. */
.auth-legal {
  display: flex; flex-wrap: wrap; justify-content: center; align-items: center;
  gap: .3rem .6rem;
  margin: 1.75rem 0 .3rem;
  font-size: .85rem; color: var(--c-text-muted);
}
.auth-legal a { color: var(--c-text-muted); text-decoration: none; }
.auth-legal a:hover, .auth-legal a:focus-visible { color: var(--c-primary-700); text-decoration: underline; }
.auth-copyright {
  margin: 0; text-align: center;
  font-size: .78rem; color: var(--c-text-faint);
}

/* A notification row is clickable in its own right -- clicking it marks it
   read, whether or not it has somewhere to go. Only the unread ones: a row
   already read does nothing, and a pointer cursor promising otherwise is a lie. */
.notif[data-unread] { cursor: pointer; }
.notif[data-unread]:hover { background: var(--c-primary-50); }
/* Said only on rows with no link, where "click me" is not otherwise obvious.
   Shown on hover and on keyboard focus within, so it is not permanent clutter. */
.n-hint {
  display: block; margin-top: .2rem;
  font-size: .74rem; color: var(--c-text-faint);
  opacity: 0; transition: opacity .12s;
}
.notif[data-unread]:hover .n-hint,
.notif[data-unread]:focus-within .n-hint { opacity: 1; }

/* A literal name inside policy prose -- a cookie name, a file name. Set apart
   from the sentence so it reads as a thing rather than a word, without the
   heavy tinted-box treatment that would make a paragraph look like code. */
.prose code {
  padding: .1em .35em;
  border: 1px solid var(--c-border);
  border-radius: var(--r-sm);
  background: var(--c-surface-2);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .88em;
  overflow-wrap: anywhere;
}
