/*
 * IJCRM design system.
 *
 * Ported from mockup/index.html, which remains the design source but is no
 * longer a deliverable. The class names here follow the real partials in
 * src/views/partials/, not the mockup's markup: those partials are semantic and
 * carry a skip link and ARIA state, and the mockup markup was throwaway.
 *
 * One bug from the mockup is deliberately not carried over. There, a rule of
 * `nav.main ul{display:flex}` out-specified `.drop{display:none}` and left every
 * dropdown permanently open on screen. Every dropdown rule below is therefore
 * scoped through `li.has-drop >` so it always out-specifies the flex rule,
 * rather than relying on source order to break the tie.
 */

:root {
  /* Brand palette. These name the colour, so they are the only place a hex
     literal belongs. Components reference the semantic tokens below instead,
     because a component that says `color: var(--navy)` becomes a lie the day
     the brand changes and --navy has to hold a red. */
  --navy: #16324F;
  --navy-deep: #0F2337;
  --crimson: #A62639;
  --crimson-dark: #8B1E2F;
  --steel: #6E8CA8;
  --ink: #1F2A37;
  --muted: #5B6B7C;
  --paper: #F4F6F8;
  --card: #FFFFFF;
  --line: #DCE3EA;
  --chip: #EAF0F5;

  /* Semantic layer. Every pairing below is measured against WCAG AA at 4.5:1
     for body text. Verified 2026-09-13: the weakest is --text-muted on
     --surface-sunken at 5.05:1, and every other pair clears 5.4:1. */
  --color-primary: var(--navy);
  --color-on-primary: #FFFFFF;
  --color-accent: var(--crimson);
  --color-accent-hover: var(--crimson-dark);
  --color-on-accent: #FFFFFF;
  --text-strong: var(--ink);
  --text-muted: var(--muted);
  --text-on-dark: #D6E1EB;
  --text-on-dark-muted: #AEC2D3;
  --surface: var(--card);
  --surface-sunken: var(--paper);
  --surface-inverse: var(--navy-deep);
  --border: var(--line);

  --serif: "Source Serif 4", Georgia, "Times New Roman", serif;
  --sans: "Source Sans 3", -apple-system, "Segoe UI", Arial, sans-serif;

  /* Modular type scale. Sizes are snapped to these steps so headings and body
     share one rhythm instead of drifting across half-pixel values. */
  --fs-xs: 12px;
  --fs-sm: 13px;
  --fs-base: 14px;
  --fs-md: 16px;
  --fs-lg: 18px;
  --fs-xl: 20px;
  --fs-2xl: 26px;
  --fs-3xl: 30px;
  --fs-4xl: 34px;

  /*
   * Display tier, added 2026-09-13.
   *
   * The scale above stopped at 34px, so an h2 on a content page was 26px and
   * the page title was the only thing above it. Six sections all set at 26px
   * with 16px body between them is a page with two levels in it, which is what
   * "the font looks very basic" describes: nothing is emphatic because nothing
   * is allowed to be. These three steps give a page a headline, a section head
   * and a lead, and they are fluid because a fixed 52px headline that is right
   * at 1440px is a wall of type at 390px.
   */
  --fs-5xl: clamp(30px, 3.2vw, 40px);
  --fs-6xl: clamp(34px, 4.6vw, 56px);
  --fs-lead: clamp(18px, 1.6vw, 21px);

  /* 4/8 spacing rhythm. */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 24px;
  --sp-6: 32px;
  --sp-7: 40px;
  --sp-8: 48px;
  --sp-9: 64px;

  /* Minimum comfortable hit area. Below this, links sitting in a list are
     genuinely hard to tap accurately on a phone. */
  --tap-min: 44px;

  /*
   * Layout width, "infinity" treatment adopted 2026-09-13 from the Supercity
   * mockups. The previous 1140px sat 175px in from each edge of a 1440px screen
   * and made the page look like a document floating on a desktop.
   *
   * Two things produce the edge-to-edge feel, and the second matters more than
   * the first. The container is wider with a fluid gutter, and the hero and the
   * full-bleed bands run to the viewport edge instead of stopping inside it.
   * Measured against the Supercity home mockup: its container is 1240px with
   * 48px padding, which puts content 148px in at 1440px. Widening alone would
   * not have closed the gap; its hero photograph bleeding off the right edge is
   * what does.
   *
   * 1680px is the ceiling because beyond roughly that width a four-column card
   * grid stops being scannable. Text measure is capped per component rather
   * than by the container, which is where it belongs.
   */
  --wrap: 1680px;
  --gutter: clamp(20px, 3vw, 48px);

  /*
   * Content-page grid, added 2026-09-13, and the reason is a measured defect
   * rather than a preference.
   *
   * `--wrap` is 1680px because the homepage fills it: a bleeding hero, a
   * four-column card grid, a full-width index strip. Every other page put a
   * single 68ch column inside that same container and left the remaining
   * ~1000px empty, so the reading column sat pinned to the left edge and every
   * sentence wrapped three words early against a void. At 1728px the prose was
   * 30% of the window.
   *
   * The measure was never the bug and is not what changed. 72ch is still a
   * measure; what changed is that it is now one track of three, so the width
   * the page does not spend on prose is spent on a contents rail and on card
   * grids that break out across both remaining tracks. The composition is
   * centred, which is the difference between whitespace that reads as designed
   * and whitespace that reads as broken: symmetric margins are a margin, and a
   * thousand pixels on one side is a mistake.
   */
  /*
   * The reading measure, in rem rather than ch, and the unit is the point.
   *
   * `ch` is the advance width of "0", which in Source Sans 3 is noticeably
   * wider than the average letter, so a 76ch track measured 90 to 97 real
   * characters per line against a 65 to 75 target. Worse, `ch` resolves
   * against the font of whichever element it is written on: the grid TRACK
   * inherits the body's 16px and the `.prose` box inside it sets 17px, so the
   * same token meant two different widths eight per cent apart and the smaller
   * one silently won. Counted on rendered lines with Range rects, not
   * estimated from an assumed glyph width, which is what made the gap visible.
   *
   * 34rem is 544px, which counts about 78 characters at this size. Slightly
   * above the classic 66 on purpose: 17px at 1.72 leading gives the eye a
   * strong return, and a journal page is scanned as much as it is read. It is
   * not much wider than the 68ch it replaces, and it was never supposed to be.
   * The reading column was not the defect; the thousand pixels of nothing
   * beside it were, and those are now a contents rail, card grids that break
   * out across two tracks, and side notes.
   */
  --measure: 34rem;
  --rail: 240px;
  --aside: 360px;
  --shell-gap: clamp(28px, 3.2vw, 64px);

  /*
   * The composition's own width, so the page header and the body below it are
   * one grid rather than two that happen to be on the same page.
   *
   * Without this the header sat at the --wrap gutter and the centred shell
   * started 240px further in, so the h1 and the first paragraph under it had
   * different left edges. That reads as a header bolted onto a body.
   *
   * `ch` resolves against the element's own font, so this only holds while both
   * users inherit the body font. Both do; neither sets one.
   */
  --shell-max: calc(var(--rail) + var(--measure) + var(--aside) + 2 * var(--shell-gap));

  /*
   * Motion tokens. One duration scale and one easing pair for the whole site,
   * so every transition shares a rhythm. --motion is for micro-interaction,
   * --motion-slow for entrances. Exits use --motion-fast, deliberately shorter
   * than the entrance that preceded them, which is what makes a dismissal feel
   * responsive rather than reluctant.
   */
  --motion-fast: 120ms;
  --motion: 180ms;
  --motion-slow: 520ms;
  --ease-out: cubic-bezier(.2, .8, .3, 1);
  --ease-in: cubic-bezier(.5, 0, .8, .3);
}

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

html { scroll-behavior: smooth; }

@media (prefers-reduced-motion: reduce) {
  /* Both scroll containers, not just the document. The slider track does its
     own smooth scrolling, and an animated jump sideways is exactly the kind of
     movement this preference exists to stop. */
  html, .slider-track { scroll-behavior: auto; }
  *, *::before, *::after { transition: none !important; animation: none !important; }
}

body {
  font-family: var(--sans);
  color: var(--ink);
  background: var(--paper);
  font-size: 16px;
  line-height: 1.6;
  -webkit-text-size-adjust: 100%;
}

img { max-width: 100%; height: auto; }
a { color: var(--crimson); }
:focus-visible { outline: 3px solid var(--crimson); outline-offset: 2px; }

.wrap { max-width: var(--wrap); margin: 0 auto; padding: 0 var(--gutter); }

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

/* Visible only once focused, so keyboard users can jump the nav. */
.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--crimson); color: #fff; padding: 12px 20px;
  font-weight: 700; text-decoration: none;
}
.skip-link:focus { left: 0; }

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

.btn {
  display: inline-block; text-decoration: none; font-weight: 700;
  font-size: 16px; padding: 13px 26px; border-radius: 2px;
  border: 1.5px solid transparent; cursor: pointer; font-family: inherit;
  transition: background-color var(--motion) var(--ease-out),
              color var(--motion) var(--ease-out),
              transform var(--motion) var(--ease-out),
              box-shadow var(--motion) var(--ease-out);
}
/* Press feedback. Scale rather than a colour shift alone, because on a phone
   the finger is covering the colour. */
.btn:active { transform: translateY(1px) scale(.985); transition-duration: var(--motion-fast); }
.btn-primary { background: var(--crimson); color: #fff; }
.btn-primary:hover { background: var(--crimson-dark); box-shadow: 0 6px 18px rgba(166,38,57,.26); }
.btn-ghost { border-color: var(--navy); color: var(--navy); }
.btn-ghost:hover { background: var(--navy); color: #fff; }

/* ---------- masthead ---------- */

.site-header { background: var(--navy); color: #fff; }
.site-header .masthead {
  display: flex; align-items: center; gap: 20px;
  flex-wrap: wrap; padding-top: 26px; padding-bottom: 22px;
}
.brand { display: flex; align-items: center; gap: 16px; text-decoration: none; color: inherit; }
/* Height-constrained, width auto, so the logo keeps its 169x115 ratio instead
   of being squashed into a square. */
.brand img { flex: none; height: 56px; width: auto; }
.brand-text strong {
  display: block; font-family: var(--serif); font-weight: 700;
  font-size: clamp(22px, 3.2vw, 30px); line-height: 1.15; letter-spacing: .01em;
}
.brand-text span {
  display: block; color: #AEC2D3; font-size: 14px;
  margin-top: 3px; letter-spacing: .03em; text-wrap: balance;
}
.masthead-meta { margin-left: auto; display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
.issn {
  font-size: 12px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase;
  background: rgba(255,255,255,.1); border: 1px solid rgba(255,255,255,.22);
  padding: 6px 12px; border-radius: 2px; font-variant-numeric: tabular-nums;
}

/* ---------- navigation ---------- */

nav.main {
  background: #fff; border-bottom: 1px solid var(--line);
  position: sticky; top: 0; z-index: 50; box-shadow: 0 1px 0 rgba(22,50,79,.04);
}
nav.main .wrap { display: flex; align-items: center; }
nav.main ul.nav-list { display: flex; list-style: none; flex-wrap: wrap; }
nav.main li { position: relative; }

nav.main .top {
  display: block; padding: 15px; color: var(--navy); text-decoration: none;
  font-weight: 600; font-size: 14px; border-bottom: 3px solid transparent;
  background: none; border-left: 0; border-right: 0; border-top: 0;
  font-family: inherit; cursor: pointer;
  transition: color var(--motion) var(--ease-out),
              border-bottom-color var(--motion) var(--ease-out),
              background-color var(--motion) var(--ease-out);
}
nav.main .top:hover,
nav.main .top.active,
nav.main .top[aria-current="page"] { border-bottom-color: var(--crimson); color: var(--crimson-dark); }

/* Dropdown parents get a caret in CSS rather than a character in the markup,
   so screen readers announce the label without reading a stray glyph. */
nav.main li.has-drop > .top::after {
  content: ""; display: inline-block; margin-left: 7px; vertical-align: 2px;
  border: 4px solid transparent; border-top-color: currentColor; border-bottom: 0;
}

nav.main li.has-drop > ul.drop {
  display: none; position: absolute; top: 100%; left: 0;
  background: #fff; border: 1px solid var(--line); min-width: 240px;
  box-shadow: 0 10px 24px rgba(22,50,79,.12); list-style: none; z-index: 60;
}
/* Hover for pointers, focus-within for keyboards. focus-within is what makes
   the menu usable with no JavaScript at all. */
nav.main li.has-drop:hover > ul.drop,
nav.main li.has-drop:focus-within > ul.drop { display: block; }

nav.main li.has-drop > ul.drop a {
  display: block; padding: 10px 16px; color: var(--ink);
  text-decoration: none; font-size: 14px; border-bottom: 1px solid var(--paper);
}
nav.main li.has-drop > ul.drop a:hover,
nav.main li.has-drop > ul.drop a[aria-current="page"] { background: var(--chip); color: var(--crimson-dark); }

/* ---------- page sections ---------- */

/*
 * Longhand, and it matters. Pages use `<section class="block wrap">`, so a
 * shorthand `padding: 64px 0` here silently resets the horizontal gutter that
 * `.wrap` sets earlier in this file: same specificity, later rule wins.
 *
 * That bug shipped in the first build and was invisible, because a 1140px
 * container on a 1440px screen left a 150px centring margin that looked like
 * the gutter. Widening the container to 1680px removed the margin and dropped
 * every section heading flush against the left edge of the viewport.
 */
section.block { padding-top: 72px; padding-bottom: 72px; }

/*
 * Alternating ground.
 *
 * Without it every section is the same tint with the same spacing, which is
 * what made the first build read as one flat sheet with no pace. Banding is
 * cheaper and quieter than a divider rule: it groups each section's content
 * against its own ground rather than drawing a line the reader has to ignore.
 *
 * A modifier class rather than :nth-of-type, because the page mixes several
 * kinds of section and an nth rule would silently re-band itself the day one is
 * inserted or reordered.
 */
section.block--alt { background: var(--surface); }
.sec-head { max-width: 640px; margin-bottom: 36px; }
.sec-head h2 {
  font-family: var(--serif); font-weight: 600; font-size: clamp(24px, 3vw, 32px);
  color: var(--navy); text-wrap: balance; margin-bottom: 10px;
}
.sec-head p { color: var(--muted); }
.eyebrow {
  font-size: 12px; font-weight: 700; letter-spacing: .14em;
  text-transform: uppercase; color: var(--crimson); margin-bottom: 14px;
}

/* ---------- inner page header ---------- */

/*
 * Task 2.5. The Direction B header for every page that is not the homepage.
 *
 * Same gradient ground as the hero so the two read as one site, and a fraction
 * of its height so they do not read as one page. The hero earns 540px because
 * it carries a slider, a live issue card and the strand mark; a page title
 * carrying none of those and given the same room announces itself louder than
 * the content underneath it.
 *
 * Inside `.wrap`, deliberately. The homepage feels edge-to-edge because one
 * element breaks the container while everything else holds it. Bleeding this
 * one too would spend that contrast on a heading.
 */
.page-head {
  background: linear-gradient(165deg, #fff 0%, var(--paper) 62%, var(--chip) 100%);
  border-bottom: 1px solid var(--line);
  padding: clamp(34px, 5vw, 64px) 0 clamp(30px, 4.5vw, 52px);
}

/*
 * Title beside the facts panel, added 2026-09-13.
 *
 * The title is capped at 18ch so it breaks into two or three strong lines
 * rather than one thin one, which is the whole reason a serif display size is
 * worth having. That cap used to leave the rest of the band empty; the panel
 * is what now occupies it, and it is the first thing an author checking a
 * journal actually looks for. `align-items: end` sets the panel's last row on
 * the standfirst's baseline area rather than floating it against the top.
 *
 * minmax(0, …) on both tracks, same reason as .page-cols had: a bare ch or px
 * track refuses to shrink below its content, and one long unbroken email
 * address in the panel would then widen the row past the viewport.
 */
.page-head-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(24px, 3vw, 56px);
  max-width: var(--shell-max);
  margin-inline: auto;
}
.page-head-grid.has-facts {
  grid-template-columns: minmax(0, 1fr) minmax(0, var(--aside));
  /* Centred, not end-aligned. The panel is the taller of the two, so aligning
     the copy to its bottom edge pushed the h1 down the band and left the space
     ABOVE the title empty, which is the one place on the page a void is most
     obvious. Centred, the copy sits against the panel's mass. */
  align-items: center;
}
.page-title {
  font-family: var(--serif); font-weight: 600;
  font-size: var(--fs-6xl); line-height: 1.08;
  color: var(--navy); text-wrap: balance; letter-spacing: -.018em;
  max-width: 18ch;
}
/* Measure, not a box width. The standfirst is read as prose, and a line of it
   running the full 1680px container would be unreadable on a wide monitor. */
.standfirst {
  margin-top: 18px; max-width: 54ch;
  font-size: var(--fs-lead); line-height: 1.55; color: var(--muted);
  text-wrap: pretty;
}

/* One column well before the panel is too narrow to read as a table of facts.
   Below this the two tracks would be about 300px each and the dt/dd pairs
   would wrap. */
@media (max-width: 900px) {
  .page-head-grid.has-facts { grid-template-columns: minmax(0, 1fr); }
  .page-head-grid .fact-panel { max-width: 460px; }
}

/*
 * Breadcrumbs.
 *
 * Rendered only where the hierarchy is real; page-head.php decides, because the
 * condition is about the trail's length and not about styling. See the note
 * there for why a two-item trail is chrome rather than orientation.
 *
 * The separator is a ::before on the list item rather than a character in the
 * markup, so a screen reader reads "Home, Archive, Edition 45" instead of
 * "Home slash Archive slash Edition 45".
 */
/* Constrained to the same composition as the header and the body below it.
   Left at the full --wrap the trail started 240px to the left of the h1 it
   belongs to, which reads as a stray line rather than as this page's path. */
.crumbs { margin-bottom: 18px; max-width: var(--shell-max); margin-inline: auto; }
.crumbs ol { list-style: none; display: flex; flex-wrap: wrap; align-items: center; }
.crumbs li { display: flex; align-items: center; font-size: var(--fs-base); }
.crumbs li + li::before {
  content: "/"; margin: 0 10px; color: var(--steel); speak: never;
}
.crumbs a { color: var(--muted); text-decoration: none; }
.crumbs a:hover { color: var(--crimson); text-decoration: underline; }
.crumbs a:focus-visible { outline: 3px solid var(--crimson); outline-offset: 3px; border-radius: 2px; }
/* The current page is the one crumb that is not a link, so weight rather than
   colour marks it. Colour alone would be the only signal for anyone who cannot
   distinguish it from the links beside it. */
.crumbs [aria-current="page"] { color: var(--ink); font-weight: 600; }

/* Echoes back the URL that missed. Monospace because it is a literal address
   the reader may be comparing against what they typed, and break-all because a
   long path must wrap rather than widen the page: the one URL on this site
   nobody controls the length of is the one that produces a 404. */
.missing-path { color: var(--muted); margin-bottom: 34px; }
.missing-path code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .92em; background: var(--chip); color: var(--ink);
  padding: 3px 7px; border-radius: 4px; word-break: break-all;
}

/* ---------- hero ---------- */

/*
 * Full-bleed split, after the Supercity mockup. The grid spans the viewport and
 * carries padding on the LEFT ONLY, so the figure column has nothing between it
 * and the right edge and runs off the screen.
 *
 * The left padding is computed rather than fixed so the hero copy lands on the
 * same vertical as `.wrap` content further down the page. Below 1680px both
 * resolve to the gutter; above it both resolve to the centring margin plus the
 * gutter. Without this the headline and the section headings beneath it would
 * disagree by the width of the centring margin on a large monitor, which reads
 * as a misalignment even to someone not looking for one.
 *
 * The percentage resolves against `.hero`'s content box, which is the full body
 * width. It is only ever used as padding on `.hero-grid`, whose containing block
 * is `.hero`; using this token anywhere nested deeper would silently resolve
 * against the wrong width.
 */
.hero {
  --edge: max(var(--gutter), calc((100% - var(--wrap)) / 2 + var(--gutter)));
  background: linear-gradient(165deg, #fff 0%, var(--paper) 58%, var(--chip) 100%);
  border-bottom: 1px solid var(--line);
  overflow: hidden;
}
.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(310px, 380px) minmax(0, .85fr);
  align-items: center;
  gap: clamp(28px, 3.2vw, 56px);
  min-height: 540px;
  padding-left: var(--edge);
}
.hero-copy { padding: 64px 0; max-width: 640px; }
.hero h1 {
  font-family: var(--serif); font-weight: 600; font-size: clamp(30px, 3.6vw, 46px);
  line-height: 1.14; color: var(--navy); text-wrap: balance; margin-bottom: 18px;
  letter-spacing: -.012em;
}
.hero .lede { color: var(--muted); max-width: 54ch; margin-bottom: 28px; }
.hero-actions { display: flex; gap: 12px; flex-wrap: wrap; }

/* ---------- hero slider ---------- */

/*
 * Replaces the legacy Nivo Slider, and deliberately keeps only the part of it
 * that was working.
 *
 * What rotates is the message. The strand mark and the current-issue card stay
 * put: the mark is the journal's identity and should not flicker, and an issue
 * panel that rotates away is a panel nobody can rely on finding. That also
 * matches what the old banners actually did, since both of them were text set
 * on artwork rather than different pictures.
 *
 * Built on scroll-snap with same-page links as controls, so there is no
 * JavaScript, nothing to initialise, and no failure state where a script does
 * not load and the visitor is left with three slides stacked on top of each
 * other. Without CSS it is three articles in a row, which still reads.
 */

.slider { position: relative; display: flex; flex-direction: column; }

.slider-track {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 100%;
  /*
   * The gap is a guard, not spacing you are meant to notice.
   *
   * Columns sized at 100% land on a fractional pixel at most viewport widths,
   * so a snapped panel can sit a sub-pixel off and let the edge of the next one
   * show through. It appeared as a stray crimson rule beside the copy on
   * desktop, which was the next panel's primary button, and as a speck on
   * mobile. Separating the panels means whatever shows at the boundary is
   * ground rather than content. Snapping is unaffected: each panel still snaps
   * on its own start edge, the scroll step is simply 100% plus the gap.
   */
  column-gap: 28px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  /* The dots are the affordance; a scrollbar under a banner reads as a defect.
     Keyboard scrolling and swipe both still work. */
  scrollbar-width: none;
}
.slider-track::-webkit-scrollbar { display: none; }

/* Focusable so the panel is reachable and arrow-scrollable from the keyboard.
   Chromium only does this automatically when the container has no focusable
   children, and these panels are full of links. */
.slider-track:focus-visible { outline: 3px solid var(--crimson); outline-offset: 4px; }

/*
 * scroll-margin-top is doing something non-obvious and load-bearing.
 *
 * A link to #slide-2 asks the browser to bring that panel into view in every
 * scrolling ancestor, not just the track. The document is one of them, so the
 * first dot click also scrolled the page down by the height of the masthead and
 * nav, about 228px. Clicking a slider control and having the page walk away
 * underneath you is worse than the slider is worth.
 *
 * Reserving a viewport's worth of margin above each panel means the requested
 * position is always above the top of the document, so it clamps to zero and
 * the page does not move vertically at all. The track still scrolls
 * horizontally, because that axis has no margin set. Deep-linking to
 * /#slide-3 now opens at the top of the page with the third panel showing,
 * which is the behaviour you would want anyway.
 */
.slide { scroll-snap-align: start; min-width: 0; scroll-margin-top: 100vh; }

/* h1 on the first panel, h2 on the rest, so the document outline stays honest.
   They have to look identical, because to a reader they are the same thing. */
.slide-title {
  font-family: var(--serif); font-weight: 600; font-size: clamp(30px, 3.6vw, 46px);
  line-height: 1.14; color: var(--navy); text-wrap: balance; margin-bottom: 18px;
  letter-spacing: -.012em;
}

.slider-nav { display: flex; align-items: center; gap: 10px; margin-top: 30px; }

/*
 * Bars rather than circles, and sized to the 44px minimum through padding
 * rather than height, so the target is comfortable on a phone while the mark
 * itself stays a thin rule.
 */
.slider-dot {
  --dot: var(--line);
  display: flex; align-items: center; width: 46px; position: relative;
  /* Explicit floor rather than derived padding: padding of 20px around a 3px
     rule came to 43px, which quietly missed the 44px minimum this file sets. */
  min-height: var(--tap-min);
}
.slider-dot::before {
  content: ""; display: block; width: 100%; height: 3px; background: var(--dot);
  transition: background-color var(--motion) var(--ease-out);
}
.slider-dot:hover { --dot: var(--steel); }

/*
 * Current-panel state with no script.
 *
 * :target marks the panel the visitor navigated to, and :has() lets the dot
 * that points at it be styled from the slider above. The second rule restores
 * the first dot when nothing is targeted, which is the state on arrival.
 *
 * Scoped to :not([data-js]) because these rules and the scripted ones cannot
 * both be live. slider.js never sets the hash, so with scripting on no slide is
 * ever :target and the fallback arm below would pin the first dot permanently
 * while the panels moved past it.
 */
.slider:not([data-js]):has(#slide-1:target) .slider-dot[href="#slide-1"],
.slider:not([data-js]):has(#slide-2:target) .slider-dot[href="#slide-2"],
.slider:not([data-js]):has(#slide-3:target) .slider-dot[href="#slide-3"],
.slider:not([data-js]):not(:has(.slide:target)) .slider-dot:first-child { --dot: var(--crimson); }

/* The scripted equivalent. slider.js owns aria-current, so the state a screen
   reader is told and the state a sighted reader sees come from one attribute
   rather than from two mechanisms that can disagree. */
.slider[data-js] .slider-dot[aria-current="true"] { --dot: var(--crimson); }

/*
 * Pause control.
 *
 * Exists only when slider.js is running, because only then is there motion to
 * stop. WCAG 2.2.2 is Level A and applies as soon as content moves by itself
 * for more than five seconds, so this ships with auto-advance or auto-advance
 * does not ship.
 */
.slider-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--tap-min); height: var(--tap-min);
  margin-left: 6px; padding: 0;
  background: none; border: 0; border-radius: 50%;
  color: var(--muted); cursor: pointer;
  transition: color var(--motion) var(--ease-out), background-color var(--motion) var(--ease-out);
}
.slider-toggle:hover { color: var(--navy); background: var(--chip); }
.slider-toggle:focus-visible { outline: 3px solid var(--crimson); outline-offset: 2px; }
.slider-toggle svg { width: 17px; height: 17px; fill: currentColor; }

/* One icon at a time, chosen by the state the slider is actually in. Both paths
   ship in the markup so toggling costs no request and cannot flash. */
.slider[data-playing="on"] .slider-toggle .i-play,
.slider[data-playing="off"] .slider-toggle .i-pause { display: none; }

/* ---------- hero figure ---------- */

.hero-figure { position: relative; align-self: stretch; overflow: hidden; }

/*
 * The artwork is masked, not covered.
 *
 * Supercity fades its hero photograph by laying a cream-coloured gradient over
 * the image's left edge. That works there because the panel beside it is a flat
 * cream. Here the hero ground is itself a gradient, so an overlay painted in
 * one flat colour disagreed with the ground everywhere except the single point
 * where they happened to match, and left a visible vertical seam down the left
 * of the figure.
 *
 * Masking the SVG's own alpha makes no assumption about what is behind it. The
 * strands dissolve into whatever the ground is at that height, so the seam
 * cannot exist and the hero gradient is free to change without breaking this.
 *
 * The ramp is short (fully opaque by 34%) because this column is only around
 * 400px wide at 1440px. The first attempt faded across 62% and erased most of
 * the composition, which read as an unfinished image rather than a bled one.
 */
.hero-svg {
  position: absolute; inset: 0; width: 100%; height: 100%; display: block;
  -webkit-mask-image: linear-gradient(90deg, transparent 0%, rgba(0,0,0,.45) 16%, #000 34%);
  mask-image: linear-gradient(90deg, transparent 0%, rgba(0,0,0,.45) 16%, #000 34%);
}

/* ---------- current issue card ---------- */

.issue-card {
  background: var(--card); border: 1px solid var(--line); border-top: 4px solid var(--crimson);
  padding: 26px; box-shadow: 0 8px 28px rgba(22,50,79,.07);
}
.issue-card h2 { font-family: var(--serif); font-size: 20px; color: var(--navy); margin-bottom: 4px; }
.issue-card .vol { color: var(--muted); font-size: 14px; margin-bottom: 16px; }
.issue-card dl { display: grid; grid-template-columns: auto 1fr; gap: 7px 14px; font-size: 14px; margin-bottom: 18px; }
.issue-card dt { color: var(--muted); }
.issue-card dd { font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums; }
.issue-card .deadline {
  background: var(--chip); border-left: 3px solid var(--crimson);
  padding: 10px 14px; font-size: 14px; margin-bottom: 18px;
}
.issue-card .btn { display: block; width: 100%; text-align: center; padding: 11px 0; }

/* ---------- subject cards ---------- */

/*
 * The four blocks below were all the same rounded white box before 2026-09-13,
 * which is what made the page read as card-soup: four different kinds of
 * content wearing one costume. They are now told apart by structure, because
 * they do different jobs.
 *
 *   subjects  raised cards      -> entry points, they lead somewhere
 *   why       hairline list     -> prose, it is read not clicked
 *   steps     connected track   -> a sequence, order is the information
 *   fees      figures on a band -> two numbers, nothing else
 */

/* ---------- subject cards: raised, because they lead somewhere ---------- */

.subjects { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; list-style: none; }
.sub-card {
  background: var(--card); border: 1px solid var(--line); border-top: 0;
  padding: 26px 24px 24px; border-radius: 2px; position: relative;
  transition: box-shadow var(--motion) var(--ease-out), transform var(--motion) var(--ease-out);
}
/* Accent rule as a pseudo-element rather than border-top, so it can widen on
   hover without the 3px shifting the card's contents by a pixel. */
.sub-card::before {
  content: ""; position: absolute; top: 0; left: 0; height: 3px; width: 46px;
  background: var(--crimson);
  transition: width var(--motion-slow) var(--ease-out);
}
.sub-card:hover { box-shadow: 0 14px 34px rgba(22,50,79,.12); transform: translateY(-3px); }
.sub-card:hover::before { width: 100%; }
.sub-card .ic {
  width: 44px; height: 44px; background: var(--chip); color: var(--navy);
  display: grid; place-items: center; border-radius: 2px; margin-bottom: 16px;
  transition: background-color var(--motion) var(--ease-out);
}
.sub-card:hover .ic { background: color-mix(in srgb, var(--crimson) 12%, var(--chip)); }
.sub-card .ic svg {
  width: 22px; height: 22px; stroke: var(--navy); stroke-width: 1.6;
  fill: none; stroke-linecap: round; stroke-linejoin: round;
}
.sub-card h3 { font-family: var(--serif); font-size: 18px; color: var(--navy); margin-bottom: 8px; }
.sub-card p { font-size: 14px; color: var(--muted); line-height: 1.55; }

/* ---------- why: a list, not boxes ---------- */

.why {
  display: grid; grid-template-columns: repeat(3, 1fr);
  column-gap: clamp(32px, 4vw, 72px); row-gap: 0; list-style: none;
}
.why > li { padding: 28px 0; border-top: 1px solid var(--line); }
.why h3 {
  font-family: var(--serif); font-size: 19px; color: var(--navy);
  margin-bottom: 8px; display: flex; align-items: baseline; gap: 11px;
}
.why h3::before { content: ""; width: 7px; height: 7px; background: var(--crimson); flex: none; border-radius: 50%; }
/* Capped independently of the container: at 1680px a third of the row is over
   500px, which is past the 75-character ceiling for comfortable reading. */
.why p { font-size: 15px; color: var(--muted); max-width: 46ch; }

/* ---------- submission steps: a track, because order is the point ---------- */

/*
 * Numbered because submission genuinely is a sequence: a reader needs to know
 * review precedes payment. The connecting rule is drawn on the list rather than
 * per item, so it cannot end up with a stub hanging off the last marker.
 */
.steps {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: clamp(20px, 2.6vw, 40px); counter-reset: step; list-style: none;
  position: relative;
}
.steps::before {
  content: ""; position: absolute; top: 21px; left: 0; right: 0;
  height: 1px; background: var(--line);
}
.step { position: relative; padding-top: 62px; }
.step::before {
  counter-increment: step; content: counter(step, decimal-leading-zero);
  position: absolute; top: 0; left: 0;
  width: 43px; height: 43px; border-radius: 50%;
  background: var(--card); border: 1px solid var(--line); color: var(--navy);
  font-family: var(--serif); font-weight: 700; font-size: 16px;
  display: grid; place-items: center; font-variant-numeric: tabular-nums;
  transition: background-color var(--motion) var(--ease-out), color var(--motion) var(--ease-out);
}
.step:first-child::before { background: var(--crimson); border-color: var(--crimson); color: #fff; }
.step h3 { font-family: var(--serif); font-size: 19px; color: var(--navy); margin-bottom: 8px; }
.step p { font-size: 15px; color: var(--muted); max-width: 42ch; }

/* ---------- fees: two figures, no boxes ---------- */

/*
 * Sized to its contents, not to the container.
 *
 * This was a full-width band, which was fine at the old 1140px wrap and wrong
 * the moment the container went to 1680px: two short figures sat at the left
 * and roughly twelve hundred pixels of flat grey ran off to the right. A filled
 * panel has to be filled by something. Emphasis comes from the fill and the
 * crimson rule, and neither needs width to work, so the panel now stops where
 * the figures stop.
 *
 * max-width keeps it honest below ~700px, where fit-content would otherwise
 * refuse to shrink and push the page into horizontal scroll.
 */
.fees {
  display: flex; flex-wrap: wrap; gap: clamp(28px, 5vw, 88px);
  width: fit-content; max-width: 100%;
  list-style: none; background: var(--chip);
  border-left: 3px solid var(--crimson);
  padding: 34px clamp(24px, 4vw, 48px);
}
.fee b {
  display: block; font-family: var(--serif); font-size: clamp(28px, 3.4vw, 38px);
  color: var(--navy); font-variant-numeric: tabular-nums; line-height: 1.1;
}
.fee span { color: var(--muted); font-size: 14px; }

/* ---------- indexing strip ---------- */

.index-strip { background: var(--card); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); padding: 36px 0; }
/* Grouped rather than space-between. At 1680px the two ends were over a
   thousand pixels apart and stopped reading as one statement. */
.index-strip .wrap { display: flex; align-items: center; gap: clamp(18px, 3vw, 44px); flex-wrap: wrap; }
.index-strip .label { font-size: 12px; font-weight: 700; letter-spacing: .14em; text-transform: uppercase; color: var(--muted); }
.badges { display: flex; gap: 14px; flex-wrap: wrap; list-style: none; }
.badges li {
  border: 1px solid var(--line); background: var(--paper); color: var(--navy);
  font-weight: 600; font-size: 14px; padding: 9px 18px; border-radius: 2px;
}

/* ---------- closing call to action ---------- */

.cta-band { background: linear-gradient(120deg, var(--navy-deep), var(--navy)); color: #fff; text-align: center; padding: 64px var(--gutter); }
.cta-band h2 { font-family: var(--serif); font-weight: 600; font-size: clamp(24px, 3.4vw, 34px); text-wrap: balance; margin-bottom: 12px; }
.cta-band p { color: #AEC2D3; margin-bottom: 26px; }

/* ---------- footer ---------- */

.site-footer { background: var(--navy-deep); color: #93A7B8; font-size: 14px; }
.footer-grid { display: grid; grid-template-columns: 1.6fr 1fr 1fr 1.2fr; gap: 36px; padding-top: 52px; padding-bottom: 40px; }
.footer-head { color: #fff; font-size: 14px; letter-spacing: .1em; text-transform: uppercase; margin-bottom: 14px; }
.footer-col p { max-width: 40ch; line-height: 1.6; margin-bottom: 10px; }
.footer-col ul { list-style: none; }
.footer-col li { margin-bottom: 9px; }
.site-footer a { color: #B9C7D4; text-decoration: none; }
.site-footer a:hover { color: #fff; text-decoration: underline; }
.issn-line { font-variant-numeric: tabular-nums; letter-spacing: .04em; }
.footer-base {
  border-top: 1px solid rgba(255,255,255,.12); padding-top: 18px; padding-bottom: 18px;
  display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; font-size: 13px;
}

/* ---------- long-form content pages ---------- */

/*
 * Reading typography, reworked 2026-09-13.
 *
 * Body rises from 16px to 17px and line-height from 1.6 to 1.72. Both are
 * small numbers and together they are most of what separates a page that reads
 * like a document from one that reads like a form: 17/29 at a 72ch measure is
 * roughly the setting a journal uses in print, and it is what makes a paragraph
 * scannable without being read.
 *
 * The h2 grows from 26px to the --fs-5xl step and carries a short accent rule
 * above it. The rule is structural rather than decorative: these pages are a
 * sequence of independent sections rather than a continuous argument, and a
 * reader scrolling for "Appeals" needs the section boundary to be visible at a
 * glance rather than inferred from a slightly larger line of text.
 *
 * `text-wrap: pretty` on paragraphs, `balance` on headings. Balance is the
 * right choice for two or three lines and the wrong one for twenty, because it
 * makes the browser optimise the whole block; pretty only prevents the orphan.
 */
.prose { max-width: var(--measure); font-size: 17px; line-height: 1.72; }
.prose > :first-child { margin-top: 0; }
.prose h2 {
  font-family: var(--serif); font-weight: 600;
  color: var(--navy); font-size: var(--fs-5xl); line-height: 1.16;
  letter-spacing: -.012em; text-wrap: balance;
  margin: var(--sp-8) 0 var(--sp-4);
}
.prose h2::before {
  content: ""; display: block;
  width: 42px; height: 3px; background: var(--color-accent);
  margin-bottom: var(--sp-4);
}
.prose h3 {
  font-family: var(--serif); font-weight: 600; color: var(--navy);
  font-size: var(--fs-xl); margin: var(--sp-6) 0 var(--sp-3);
  text-wrap: balance;
}
.prose p, .prose ul, .prose ol { margin-bottom: var(--sp-4); text-wrap: pretty; }
.prose ul, .prose ol { padding-left: var(--sp-5); }
.prose li { margin-bottom: var(--sp-2); }
.prose li::marker { color: var(--steel); }

/*
 * The first paragraph of a section carries the section. Setting it a step up
 * gives a reader who is skimming somewhere to land, and it is the cheapest
 * hierarchy available because it costs no markup on the page: one class.
 */
.prose .lead { font-size: var(--fs-lead); line-height: 1.6; color: var(--text-strong); }

/* Tables of articles are wide. Scroll the table, never the page body. */
.table-scroll { overflow-x: auto; }
.table-scroll table { border-collapse: collapse; width: 100%; min-width: 640px; background: var(--card); }
.table-scroll th, .table-scroll td { border: 1px solid var(--line); padding: 12px 14px; text-align: left; font-size: 14px; }
.table-scroll th { background: var(--chip); color: var(--navy); font-size: 13px; letter-spacing: .04em; text-transform: uppercase; }
.table-scroll td.pages { font-variant-numeric: tabular-nums; white-space: nowrap; }

/* ---------- static and policy pages (Task 3.4) ---------- */

/*
 * Prose beside a facts panel.
 *
 * The panel is second in the source and second in the tab order, which is what
 * a sighted reader gets too: the page's argument first, the reference box
 * after it. Putting the aside first in the DOM and moving it with `order`
 * would make the keyboard order disagree with the visual one.
 *
 * minmax(0, 68ch) rather than 68ch. A grid track sized by a bare ch unit
 * refuses to shrink below its content's minimum, so a long unbroken URL inside
 * the prose would push the whole row wider than the viewport and scroll the
 * page sideways. The 0 minimum is what lets it yield.
 */
/*
 * The content-page shell.
 *
 * Three tracks with named lines: a contents rail, the reading measure, and a
 * third track that is empty for prose and is spanned into by anything that is
 * genuinely wider than a sentence. Card grids, tables, step lists and logo
 * grids all take `.shell-wide` and run across `main` and `extra` together,
 * which is roughly 1080px at desktop and fits three cards.
 *
 * That third track is the part worth understanding, because an always-empty
 * column looks like a mistake and is the opposite of one. Without it, a card
 * grid is capped at the reading measure and four charge cards stack into a
 * single 660px column; with it, the prose keeps a measure a human can read
 * while the structured content gets the width it needs. The page then has two
 * rhythms instead of one, which is what stops six sections of 17px text
 * reading as a wall.
 *
 * `justify-content: center` centres the whole composition in --wrap rather
 * than letting it hug the left edge. See the --measure note for why that
 * distinction is the entire defect this replaces.
 *
 * Children are placed explicitly rather than by source order, and auto
 * placement fills rows from there: the rail is placed in row 1 column `rail`,
 * the first prose block finds `main` free in the same row, and everything
 * after it advances a row at a time. Nothing here relies on the rail being
 * first in the markup, only on it being in the markup before the content it
 * describes, which is also the order a screen reader wants.
 */
.page-shell {
  display: grid;
  grid-template-columns:
    [rail-start] minmax(0, var(--rail))
    [rail-end main-start] minmax(0, var(--measure))
    [main-end extra-start] minmax(0, var(--aside))
    [extra-end];
  column-gap: var(--shell-gap);
  /*
   * ROW GAP IS ZERO AND THE SPACING IS MARGINS. That is not a style choice, it
   * is what the sticky rail costs.
   *
   * The rail has to span every row rather than sit in row 1, or the row it
   * shares with the first content block is sized to the RAIL, and a page whose
   * first block is short opens with a hundred pixels of nothing between the
   * figures and the first heading. Spanning is done with `span 99` because
   * `1 / -1` resolves against the EXPLICIT grid, and these rows are all
   * implicit, so it would collapse to a single row and change nothing.
   *
   * The empty implicit rows that creates are zero-height and free. A row GAP
   * is not: it would be charged 98 times, for about 4000px of blank page. So
   * the gap is zero and the rhythm comes from margins below.
   */
  row-gap: 0;
  justify-content: center;
  align-items: start;
  max-width: var(--shell-max);
  margin-inline: auto;
  padding: clamp(40px, 5vw, 72px) 0 clamp(48px, 6vw, 88px);
}

/* Every content block after the first. The rail is excluded on both sides of
   the combinator: it is not preceded by a sibling that should push it down,
   and the first content block sits beside it rather than after it. */
.page-shell > :not(.page-toc) + :not(.page-toc) { margin-top: var(--sp-7); }

/*
 * No contents rail. For a page whose content IS the navigation: seven reason
 * cards, thirteen board members, a wall of questions. A rail listing two
 * sections on such a page is a control that orients nobody, and it would leave
 * its track holding nothing.
 *
 * The line names survive the change, so `.shell-wide` and `.shell-side` mean
 * the same thing on both variants and no component needs to know which shell
 * it is in.
 */
.page-shell--norail {
  grid-template-columns:
    [rail-start main-start] minmax(0, var(--measure))
    [main-end extra-start] minmax(0, 1fr)
    [extra-end rail-end];
}

/*
 * NOTE THE ABSENCE OF A max-width OVERRIDE, and that the second track is 1fr
 * rather than --aside. Both are the same correction.
 *
 * The rail's width does not come off the page when the rail goes; it moves to
 * the second track. Narrowing the shell instead left the body about 300px
 * inside its own page header, so the h1 and the first card had different left
 * edges on exactly the pages where the cards are the whole point.
 */

/*
 * The listing pages' container. Not a grid: the archive and the issue listings
 * are one full-width column of rows, and a measure track would be wrong for
 * them. What they need from the shell is its WIDTH, for two reasons.
 *
 * It keeps the page header and the body on the same left edge, because the
 * header is now constrained to --shell-max and a body still filling --wrap
 * starts 240px to its left. And it closes the gap inside an article row:
 * a title capped at its own measure with a PDF button pinned to the right of a
 * 1600px container put half a metre of nothing between the paper and the
 * button that downloads it.
 */
.page-body {
  max-width: var(--shell-max);
  margin-inline: auto;
  padding: clamp(40px, 5vw, 72px) 0 clamp(48px, 6vw, 88px);
}

/* min-width: 0 on every child, not just the grid tracks. A grid item's
   automatic minimum size is its content, so a long URL inside a paragraph
   would push its own item wider than the track that is trying to contain it. */
.page-shell > * { grid-column: main; min-width: 0; }
.page-shell > .page-toc { grid-column: rail; grid-row: 1 / span 99; }
.page-shell > .shell-wide { grid-column: main-start / extra-end; }
.page-shell > .shell-side { grid-column: extra; }

/*
 * Two columns: the rail folds under the measure is the wrong answer, so below
 * this width the rail becomes a disclosure above the content instead. 1180px
 * is where rail + gap + 72ch + gap + aside stops fitting inside the gutter.
 */
@media (max-width: 1180px) {
  .page-shell {
    grid-template-columns:
      [rail-start main-start] minmax(0, var(--measure))
      [main-end extra-start] minmax(0, var(--aside))
      [extra-end rail-end];
  }
  /* grid-row back to auto, and this is the line that stops the fold being a
     disaster. Folded, the rail is a horizontal bar in its own row; left
     spanning 99 rows it would sit on top of the entire page. */
  .page-shell > .page-toc { grid-column: rail-start / rail-end; grid-row: auto; }
  .page-shell > .page-toc + * { margin-top: var(--sp-7); }
}

@media (max-width: 900px) {
  .page-shell {
    grid-template-columns: [rail-start main-start extra-start] minmax(0, 1fr) [main-end extra-end rail-end];
  }
  .page-shell > :not(.page-toc) + :not(.page-toc) { margin-top: var(--sp-6); }
}

/*
 * Contents rail.
 *
 * Every link is a real anchor to a real id, so this works with JavaScript
 * disabled, which is the whole build. There is no scroll-spy and no active
 * state: the only honest ways to compute one need script, and a rail that
 * highlights the wrong section is worse than a rail that highlights none.
 */
.page-toc {
  position: sticky;
  top: var(--sp-5);
  font-size: var(--fs-base);
  border-left: 2px solid var(--border);
  padding-left: var(--sp-4);
}
.page-toc h2 {
  font-family: var(--sans);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--sp-3);
}
.page-toc ol { list-style: none; }
.page-toc li + li { margin-top: var(--sp-1); }
.page-toc a {
  display: block;
  padding: 7px 0;
  color: var(--text-muted);
  text-decoration: none;
  line-height: 1.4;
  text-wrap: pretty;
}
.page-toc a:hover { color: var(--color-accent); }
.page-toc a:focus-visible { outline: 3px solid var(--crimson); outline-offset: 3px; border-radius: 2px; }

/* Short viewports opt out of sticky so a long contents list cannot become
   taller than the window and strand its own last entries. */
@media (max-height: 720px) {
  .page-toc { position: static; }
}

/* Folded above the content, the rail is a horizontal row of links rather than
   a column, and it loses the sticky and the left rule that only make sense
   beside something. */
@media (max-width: 1180px) {
  .page-toc {
    position: static;
    border-left: 0;
    border-top: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
    padding: var(--sp-4) 0;
  }
  .page-toc ol { display: flex; flex-wrap: wrap; gap: 0 var(--sp-5); }
  .page-toc li + li { margin-top: 0; }
}

.fact-panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-top: 3px solid var(--color-accent);
  padding: var(--sp-5) var(--sp-5) var(--sp-4);
}

.fact-panel h2 {
  font-family: var(--sans);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--sp-4);
}

.fact-panel dl { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0; }
.fact-panel dt {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  padding-top: var(--sp-3);
}
.fact-panel dd {
  font-size: var(--fs-md);
  color: var(--text-strong);
  font-weight: 600;
  padding-bottom: var(--sp-3);
  border-bottom: 1px solid var(--border);
}
.fact-panel dd:last-of-type { border-bottom: 0; }

/* Digits that sit in a column, so they line up rather than dancing. */
.num { font-variant-numeric: tabular-nums; }

/* An email address is one long unbreakable token; without this it decides the
   panel's minimum width. */
.wrap-anywhere { overflow-wrap: anywhere; }

.fact-panel-foot {
  margin-top: var(--sp-4);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--border);
  font-size: var(--fs-base);
}
.fact-panel-foot .btn { display: block; text-align: center; }

/* ---- shared card surface (2026-09-13) ---- */

/*
 * One surface definition, referenced by every card grid below, so a card on
 * the charges page and a card on the review page cannot drift apart. The
 * border-top accent is the journal's one repeated device; it is 3px and
 * crimson everywhere it appears, including the facts panel above.
 *
 * No box-shadow. A shadow implies the card is lifted off the page and can be
 * picked up, which is true of a modal and false of every card here; a hairline
 * border on a white surface over the sunken paper ground already separates it,
 * and doing it with a shadow as well is the look these pages are trying not to
 * have.
 */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  padding: var(--sp-5);
  min-width: 0;
}
.card--accent { border-top: 3px solid var(--color-accent); }
.card h3 {
  font-family: var(--serif);
  font-size: var(--fs-xl);
  font-weight: 600;
  color: var(--color-primary);
  margin: 0 0 var(--sp-2);
  text-wrap: balance;
}
.card p { color: var(--text-muted); line-height: 1.6; }
.card p + p { margin-top: var(--sp-3); }

/*
 * auto-fit rather than a column count, so the same grid is 3 across inside
 * .shell-wide, 2 across when the shell folds, and 1 on a phone without a
 * single media query. The 260px floor is where a card stops holding a
 * readable two-line heading.
 */
.card-grid {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--sp-4);
}

/* ---- side note ---- */

/*
 * Lives in the shell's third track, beside the prose rather than interrupting
 * it. Used for the aside a reader does not need in order to follow the
 * section: a related policy, a caveat, a pointer to another page. Anything a
 * reader DOES need belongs in the measure, because this column folds under the
 * content at 900px and is read last.
 */
.side-note {
  background: var(--chip);
  border-left: 3px solid var(--color-primary);
  padding: var(--sp-4) var(--sp-5);
  font-size: var(--fs-md);
  line-height: 1.6;
  color: var(--text-muted);
}
.side-note h2, .side-note h3 {
  font-family: var(--sans);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--color-primary);
  margin: 0 0 var(--sp-2);
}
.side-note h2::before { content: none; }
.side-note p + p { margin-top: var(--sp-3); }

/* ---- key figures ---- */

/*
 * A row of numbers set at display size. These pages state real quantities (351
 * articles, 90 issues, 72 hours to publication) and every one of them was
 * previously a digit inside a sentence. A figure a reader can take in without
 * reading is the one piece of "formatted text" these pages genuinely had
 * available and were not using.
 */
.figure-row {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--sp-4);
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  padding: var(--sp-5) 0;
}
.figure-row > li { min-width: 0; }
.figure-value {
  display: block;
  font-family: var(--serif);
  font-size: var(--fs-5xl);
  font-weight: 600;
  line-height: 1;
  color: var(--color-primary);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.02em;
}
.figure-label {
  display: block;
  margin-top: var(--sp-2);
  font-size: var(--fs-base);
  color: var(--text-muted);
  line-height: 1.4;
  text-wrap: pretty;
}

/* ---- editorial board ---- */

/*
 * auto-fill with a 280px floor, so the grid decides its own column count from
 * the space available instead of from a breakpoint table. Thirteen members
 * land as 4, 3, 2 or 1 across without a media query.
 */
.board-grid {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: var(--sp-4);
  margin-bottom: var(--sp-8);
}

.board-member {
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--color-primary);
  padding: var(--sp-4) var(--sp-5);
}

.member-name {
  font-family: var(--serif);
  font-size: var(--fs-lg);
  font-weight: 600;
  color: var(--color-primary);
  margin-bottom: var(--sp-1);
  text-wrap: balance;
}
.member-role { font-size: var(--fs-base); color: var(--text-strong); margin-bottom: var(--sp-2); }
.member-affiliation { font-size: var(--fs-base); color: var(--text-muted); line-height: 1.5; }

/* The country reads as a separate fact, not a continuation of the address.
   A pseudo-element comma keeps it out of the copied text as a stray glyph. */
.member-country { display: block; margin-top: var(--sp-1); color: var(--text-strong); font-weight: 600; }

.board-note { margin-bottom: var(--sp-6); }

/* ---- logo grids (indexing) ---- */

.logo-section { margin-bottom: var(--sp-8); }
/* Matches .prose h2 exactly, accent rule included. These headings sit outside
   .prose because their section is `.shell-wide`, and two section heads on one
   page set at two different sizes read as a hierarchy that is not there. */
.logo-section h2 {
  font-family: var(--serif);
  font-weight: 600;
  font-size: var(--fs-5xl);
  line-height: 1.16;
  letter-spacing: -.012em;
  color: var(--navy);
  text-wrap: balance;
  margin-bottom: var(--sp-4);
}
.logo-section h2::before {
  content: "";
  display: block;
  width: 42px;
  height: 3px;
  background: var(--color-accent);
  margin-bottom: var(--sp-4);
}
.section-note { color: var(--text-muted); max-width: 62ch; margin-bottom: var(--sp-5); }

.logo-grid {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: var(--sp-4);
  margin-top: var(--sp-4);
}

.logo-card {
  background: var(--surface);
  border: 1px solid var(--border);
  padding: var(--sp-4);
  text-align: center;
}

/*
 * The logos are 211x140 and 215x140 PNGs with baked-in white grounds and
 * borders. height:auto with a max keeps every card's image box the same
 * without distorting any of them, and the declared width/height attributes in
 * the markup reserve the space before they load.
 */
.logo-card img {
  display: block;
  margin: 0 auto var(--sp-3);
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 96px;
}

.logo-name { font-size: var(--fs-md); font-weight: 600; color: var(--text-strong); margin-bottom: var(--sp-1); }
.logo-name a { color: var(--color-primary); }
.logo-note { font-size: var(--fs-base); color: var(--text-muted); line-height: 1.45; }

/* ---- metrics table ---- */

/* Matches the base rule's specificity so it actually wins. `.metrics-table`
   alone (0,1,0) loses to `.table-scroll table` (0,1,1) and the 640px minimum
   stayed in force, which means more sideways scrolling on a phone than this
   table needs. */
.table-scroll table.metrics-table { min-width: 520px; }
.metrics-table th[scope="row"] { font-weight: 600; color: var(--text-strong); background: transparent; text-transform: none; letter-spacing: 0; font-size: var(--fs-base); }
.metric-value {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  font-size: var(--fs-lg);
  color: var(--color-primary);
  white-space: nowrap;
}

/* ---- reason list (why choose) ---- */

/*
 * Nine independent reasons, each two sentences long. As a stack of
 * rule-separated rows capped at 72ch they were a single column the reader had
 * to walk end to end; as cards they are scannable, which is the actual job of
 * a page an author reads while deciding between journals.
 */
.reason-list {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--sp-4);
  margin-bottom: var(--sp-7);
}
.reason {
  min-width: 0;
  padding: var(--sp-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-top: 3px solid var(--color-accent);
}
.reason h2 {
  font-family: var(--serif);
  font-size: var(--fs-xl);
  margin-bottom: var(--sp-2);
  text-wrap: balance;
  color: var(--color-primary);
}
/* The accent is the card's top rule now. A second dot in front of the heading
   would be the same signal twice. */
.reason h2::before { content: none; }
.reason p { color: var(--text-muted); line-height: 1.65; }

/* ---- FAQ ---- */

/*
 * A description list, so the question/answer relationship is in the markup
 * rather than implied by two paragraphs sitting next to each other. Each pair
 * is wrapped in a div, which is the only element HTML permits between <dl> and
 * its <dt>/<dd> children and is what makes a per-pair border possible.
 */
/*
 * Two columns of cards rather than one long stack. A reader arrives at an FAQ
 * looking for one question, so the layout that serves them is the one that
 * puts the most questions on screen at once; a single column capped at 72ch
 * showed four.
 *
 * align-items is not set, so a card is as tall as its own answer rather than
 * as tall as the tallest in its row. Equal-height rows would pad the short
 * answers with dead space to match the long ones.
 */
.faq {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: var(--sp-4);
  margin-bottom: var(--sp-7);
  align-items: start;
}
.faq-item {
  min-width: 0;
  padding: var(--sp-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-top: 3px solid var(--color-accent);
}
.faq dt {
  font-family: var(--serif);
  font-size: var(--fs-xl);
  font-weight: 600;
  color: var(--color-primary);
  margin-bottom: var(--sp-2);
  text-wrap: balance;
}
.faq dd { color: var(--text-muted); line-height: 1.65; }

/* ---- charges ---- */

.charge-cards {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--sp-4);
  margin-bottom: var(--sp-7);
}

.charge-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-top: 3px solid var(--color-accent);
  padding: var(--sp-5);
}
.charge-label { font-size: var(--fs-base); color: var(--text-muted); margin-bottom: var(--sp-2); }
.charge-amount {
  font-family: var(--serif);
  font-size: var(--fs-3xl);
  font-weight: 600;
  color: var(--color-primary);
  font-variant-numeric: tabular-nums;
  margin-bottom: var(--sp-2);
  text-wrap: balance;
}
.charge-note { font-size: var(--fs-base); color: var(--text-muted); }

.pay-form { margin: var(--sp-4) 0 var(--sp-6); }
.pay-note { font-size: var(--fs-base); color: var(--text-muted); margin-top: var(--sp-2); }

/* ---- numbered steps ---- */

/*
 * A real <ol>, numbered by a counter rather than by markers, so the number can
 * be a filled disc without giving up the list semantics. counter-increment on
 * the item and the marker in ::before is the only way to style the number
 * itself with any reliability across browsers.
 */
.numbered-steps { list-style: none; counter-reset: step; padding-left: 0; }
.numbered-steps > li {
  counter-increment: step;
  position: relative;
  padding-left: calc(var(--sp-7) + var(--sp-1));
  margin-bottom: var(--sp-5);
  color: var(--text-muted);
  line-height: 1.65;
}
.numbered-steps > li::before {
  content: counter(step);
  position: absolute;
  left: 0;
  top: 0;
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--chip);
  color: var(--color-primary);
  font-size: var(--fs-base);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.numbered-steps strong { color: var(--text-strong); }

/*
 * The card variant. Same <ol>, same counter, so the list semantics and the
 * numbering are untouched and a screen reader still reads "list of 5 items";
 * only the box changes. The number leaves the absolutely positioned disc and
 * becomes a display figure at the top of the card, because in a card there is
 * room for it to be the first thing seen.
 *
 * PLACED AFTER THE BASE RULES ON PURPOSE, and not merely tidily. Every
 * declaration here overrides one above it at the SAME specificity (0,1,2 for
 * `.numbered-steps--cards > li::before` and `.numbered-steps > li::before`
 * alike), so source order is the only thing deciding the tie. Written 200
 * lines earlier in the file, as this block first was, every step rendered with
 * the base grey disc and none of this applied.
 */
.numbered-steps--cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: var(--sp-4);
  padding-left: 0;
}
.numbered-steps--cards > li {
  position: static;
  padding: var(--sp-5);
  padding-left: var(--sp-5);
  margin-bottom: 0;
  min-width: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-top: 3px solid var(--color-accent);
  font-size: var(--fs-md);
  line-height: 1.6;
}
.numbered-steps--cards > li::before {
  content: counter(step, decimal-leading-zero);
  position: static;
  display: block;
  width: auto;
  height: auto;
  border-radius: 0;
  background: none;
  font-family: var(--serif);
  font-size: var(--fs-3xl);
  font-weight: 600;
  color: var(--steel);
  line-height: 1;
  margin-bottom: var(--sp-3);
}
/* The step's name is the card's heading, so it is set as one rather than left
   as a bold run-in at the head of the sentence it introduces. */
.numbered-steps--cards > li > strong:first-child {
  display: block;
  font-family: var(--serif);
  font-size: var(--fs-xl);
  font-weight: 600;
  color: var(--color-primary);
  margin-bottom: var(--sp-2);
}

/* ---- misc ---- */

.download-row { display: flex; flex-wrap: wrap; gap: var(--sp-3); margin: var(--sp-4) 0 var(--sp-6); }

.contact-primary {
  font-family: var(--serif);
  font-size: var(--fs-2xl);
  margin-bottom: var(--sp-3);
  overflow-wrap: anywhere;
}

/* Renamed from .call-issue / .call-window when the call for papers panel moved
   into the shared page header. Same two rules, no longer named after the one
   page that happened to need them first. */
.fact-panel-lead {
  font-family: var(--serif);
  font-size: var(--fs-xl);
  font-weight: 600;
  color: var(--color-primary);
  margin-bottom: var(--sp-1);
}
.fact-panel-note { font-size: var(--fs-base); color: var(--text-muted); line-height: 1.55; margin-bottom: var(--sp-4); }

/*
 * Touch targets. The rule keys on a coarse pointer, not on viewport width: a
 * narrow desktop window is still a mouse, and padding every inline link to
 * 44px there would wreck the prose line spacing for no benefit.
 */
@media (pointer: coarse) {
  .fact-panel dd a,
  .fact-panel-foot a,
  .logo-name a,
  .faq dd a { display: inline-block; min-height: var(--tap-min); line-height: var(--tap-min); }

  /*
   * Standalone links: a navigation control sitting on its own line, not a link
   * inside a sentence. Measured at 20 to 22px tall before this, which is half
   * the minimum and is the "View certificate" column, the DOI on every article
   * row, and "All issues" at the foot of every issue page.
   *
   * inline-flex with a min-height rather than a taller line-height, because
   * line-height would move the text off its typographic baseline relative to
   * the "Pages 01-07" text sitting beside it in the same meta row. This grows
   * the hit area and leaves the type where it was.
   *
   * Links inside running prose are deliberately NOT included. WCAG 2.5.8
   * exempts a target inside a sentence, and padding every inline link to 44px
   * would double the leading of every paragraph on the site to fix a problem
   * that the exception exists because it is not one.
   */
  .article-meta .doi,
  .back-link a,
  .sec-head a,
  .badges a,
  .table-scroll td a {
    display: inline-flex;
    align-items: center;
    min-height: var(--tap-min);
  }

  /*
   * The contents rail, added 2026-09-13. Its links measured 34px, which the
   * layout gate caught at both 768 and 1440 because it drives a hasTouch
   * context at every width.
   *
   * `display: flex` rather than inline-flex, so each entry keeps the full
   * width of the rail as its hit area instead of only the width of its own
   * words. "ISSN" is four characters and would otherwise be a 28px target with
   * 44px of dead rail beside it, which is the version of this fix that passes
   * a measurement and helps nobody.
   *
   * Only under a coarse pointer. A nine-entry rail at 44px a row is 400px
   * tall, and spending that on a mouse user to satisfy a rule written for
   * fingers would push the rail past the height of the section it indexes.
   */
  .page-toc a {
    display: flex;
    align-items: center;
    min-height: var(--tap-min);
    padding-block: 0;
  }
}

/* ---------- motion ---------- */

/*
 * Two moments, not twenty. An orchestrated hero entrance on load, and one
 * reveal per section on scroll. Everything else is micro-interaction attached
 * to the control it belongs to.
 *
 * Only transform and opacity are animated, so nothing here triggers layout and
 * nothing contributes to CLS. The global prefers-reduced-motion rule at the top
 * of this file disables all of it with `animation: none !important`, which
 * leaves every element in its natural painted state rather than stuck at the
 * start of a keyframe, because `backwards` fill only applies while an animation
 * exists.
 */

@keyframes rise { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: none; } }
@keyframes reach { from { opacity: 0; transform: translateX(-120px); } to { opacity: 1; transform: none; } }
@keyframes settle { from { opacity: 0; transform: scale(.3); } to { opacity: 1; transform: none; } }

/*
 * Hero entrance, staggered at 70ms. Below 30ms a stagger is not perceptible and
 * above about 60ms it starts to feel like waiting.
 *
 * Only the first panel animates. The other two are off-screen inside the track,
 * so animating them would spend the entrance on things nobody can see, and they
 * would have finished by the time anyone scrolled to them anyway.
 */
#slide-1 > *,
.slider-nav,
.hero .issue-card { animation: rise var(--motion-slow) var(--ease-out) backwards; }
#slide-1 > *:nth-child(1) { animation-delay: 40ms; }
#slide-1 > *:nth-child(2) { animation-delay: 110ms; }
#slide-1 > *:nth-child(3) { animation-delay: 180ms; }
#slide-1 > *:nth-child(4) { animation-delay: 250ms; }
.slider-nav { animation-delay: 320ms; }
.hero .issue-card { animation-delay: 390ms; }

/*
 * The strands arrive along their own axis. Because each rect sits inside the
 * rotated <g>, a plain translateX is already expressed in the rotated frame, so
 * every bar slides in along its own length rather than all of them sliding
 * sideways together. That is the whole reason the rotation lives on the group
 * and not on the individual rects.
 */
.strand {
  animation: reach 660ms var(--ease-out) backwards;
  animation-delay: calc(140ms + var(--i) * 45ms);
}
.dot {
  animation: settle 480ms var(--ease-out) backwards;
  animation-delay: calc(420ms + var(--i) * 60ms);
}
.spine { animation: rise 620ms var(--ease-out) 120ms backwards; }

/*
 * The stagger index, as classes. Task 7.4.
 *
 * This used to be `style="--i: N"` written onto each element by
 * src/views/partials/hero-figure.php, and it was the only inline CSS left on
 * the site. Ten values here cost ten lines and let style-src stay 'self' with
 * no 'unsafe-inline' on any page, which is the directive an XSS on a journal
 * running a submission form would otherwise walk straight through.
 *
 * Ten because there are ten strands; the five dots reuse the first five. Both
 * .strand and .dot read --i, with their own base delay and step, so one scale
 * serves both. A rule past the number of elements is dead rather than wrong,
 * which is the right way round: an eleventh strand animates with the tenth's
 * delay instead of with no delay at all.
 */
.stagger-0 { --i: 0; }
.stagger-1 { --i: 1; }
.stagger-2 { --i: 2; }
.stagger-3 { --i: 3; }
.stagger-4 { --i: 4; }
.stagger-5 { --i: 5; }
.stagger-6 { --i: 6; }
.stagger-7 { --i: 7; }
.stagger-8 { --i: 8; }
.stagger-9 { --i: 9; }

/*
 * Scroll reveal, with no JavaScript and no IntersectionObserver.
 *
 * Guarded twice. @supports means a browser without scroll-driven animations
 * simply renders the content, rather than leaving it at opacity 0 forever,
 * which is the classic way this technique takes a page down. The media query
 * means it is opt-out for anyone who has asked for less motion.
 *
 * Targeted structurally rather than through a .reveal class, so a new section
 * on a future page inherits it without anyone remembering to add markup.
 */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    section.block > .sec-head,
    section.block > ul,
    section.block > ol,
    .index-strip > .wrap {
      animation: rise linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 62%;
    }
  }
}

/* ---------- responsive ---------- */

/*
 * Breakpoints are 1024 and 768, the conventional tablet and phone boundaries,
 * rather than the 920/560 this file started with. Those were picked to suit one
 * layout and meant nothing on a real device: 920 falls mid-tablet and 560 sits
 * between common phone widths, so a 768px iPad and a 390px iPhone both landed
 * in the wrong bucket.
 */
/*
 * Hero collapse, at 1200 rather than 1024. Three columns stop working before
 * the tablet boundary: the middle column has a 310px floor, so below roughly
 * 1200px the copy column is squeezed harder than the figure and the headline
 * starts breaking badly.
 *
 * The figure is not dropped, it becomes a full-width band under the copy. It is
 * vector, so a second arrangement costs nothing, and hiding it would take the
 * whole point of the design away from exactly the visitors most likely to be on
 * a tablet or phone.
 */
@media (max-width: 1200px) {
  .hero-grid {
    grid-template-columns: minmax(0, 1.15fr) minmax(280px, 360px);
    padding-right: var(--gutter);
    row-gap: 0;
    min-height: 0;
  }
  .hero-copy { padding: 56px 0 40px; }
  .hero-figure {
    grid-column: 1 / -1;
    height: 170px;
    /*
     * Cancels the grid's own padding so the band still bleeds both ways.
     * Written as --gutter rather than --edge even though the left padding is
     * --edge: below 1200px the max() in --edge always resolves to the gutter
     * branch, and --edge carries a percentage that would resolve against this
     * element's containing block rather than the hero's. Same result, but only
     * by accident, and an accident is not worth relying on.
     */
    margin-inline: calc(-1 * var(--gutter));
  }
  /* As a wide short band the ramp works better vertically than horizontally:
     the strands should emerge from under the copy above them. */
  .hero-figure .hero-svg {
    -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 46%);
    mask-image: linear-gradient(180deg, transparent 0%, #000 46%);
  }
}

@media (max-width: 900px) {
  .hero-grid { grid-template-columns: 1fr; }
  .hero-copy { padding-bottom: 28px; }
  .hero .issue-card { margin-bottom: 40px; }
}

@media (max-width: 1024px) {
  .subjects, .steps { grid-template-columns: repeat(2, 1fr); }
  .why { grid-template-columns: 1fr; }
  /* The connecting rule only makes sense while the steps are on one row. */
  .steps::before { display: none; }
  .step { padding-top: 0; padding-left: 60px; min-height: 43px; }
  .footer-grid { grid-template-columns: 1fr 1fr; }

  /* No JavaScript in this build, so the nav scrolls sideways rather than
     collapsing behind a toggle that would need a script to open.
     nowrap on the list is not enough on its own: the items still shrink and
     the labels break inside themselves, so "For Authors" stacks onto two lines
     and the row grows taller instead of scrolling. The item has to refuse to
     shrink and the label has to refuse to wrap. */
  nav.main .wrap { overflow-x: auto; overscroll-behavior-inline: contain; }
  nav.main ul.nav-list { flex-wrap: nowrap; }
  nav.main ul.nav-list > li { flex: 0 0 auto; }
  nav.main .top { white-space: nowrap; }
  nav.main li.has-drop > ul.drop { position: static; box-shadow: none; border: 0; min-width: 0; }
  nav.main li.has-drop:hover > ul.drop,
  nav.main li.has-drop:focus-within > ul.drop { display: none; }
}

@media (max-width: 768px) {
  /* .fees was in this list and did nothing: it is a flex row, so
     grid-template-columns never applied to it. It stacks by wrapping. */
  .subjects, .steps { grid-template-columns: 1fr; }
  .footer-grid { grid-template-columns: 1fr; }
  .masthead-meta { margin-left: 0; width: 100%; }
}

/*
 * Touch targets.
 *
 * Applied on pointer:coarse rather than by width, because the thing that makes
 * a 23px-tall footer link hard to hit is a fingertip, not a narrow window. A
 * touch laptop needs this and a narrow desktop window does not.
 *
 * Footer and dropdown links are the offenders: at 14px with 9px separation they
 * were roughly 23px of hit area against a 44px minimum. Padding is used rather
 * than a fixed height so the text still wraps normally when it is long.
 */
@media (pointer: coarse) {
  .footer-col li { margin-bottom: 0; }
  .footer-col li a,
  .contact-list li a {
    display: flex;
    align-items: center;
    min-height: var(--tap-min);
  }
  nav.main li.has-drop > ul.drop a { min-height: var(--tap-min); display: flex; align-items: center; }
  .badges li { min-height: var(--tap-min); display: flex; align-items: center; }
  /* Crumb links are 14px inline text, so they come to roughly 20px tall left
     alone. The row keeps its visual height because the target is grown with
     padding and pulled back with a negative margin, rather than by making the
     text bigger. */
  .crumbs li { min-height: var(--tap-min); }
  .crumbs a { display: flex; align-items: center; padding: 12px 0; margin: -12px 0; }
}

/* ---------------------------------------------------------------------------
   Archive index and issue pages. Task 3.3.
   --------------------------------------------------------------------------- */

.volume { margin-top: clamp(34px, 4vw, 52px); }
.volume:first-of-type { margin-top: clamp(28px, 3.5vw, 44px); }

.volume-title {
  font-family: var(--serif);
  font-size: clamp(21px, 2.2vw, 26px);
  font-weight: 600;
  color: var(--navy);
  padding-bottom: 12px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 22px;
}

/* auto-fill, not auto-fit: with auto-fit a volume holding one issue stretches
   that card across the full 1680px container, so the archive's row height
   changes per volume for no reason the reader can see. */
.issue-grid {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 14px;
}

.issue-card {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-height: var(--tap-min);
  padding: 18px 20px;
  background: var(--card);
  border: 1px solid var(--line);
  border-left: 3px solid var(--steel);
  border-radius: 3px;
  text-decoration: none;
  transition: border-left-color var(--motion) var(--ease-out),
              box-shadow var(--motion) var(--ease-out),
              transform var(--motion) var(--ease-out);
}
.issue-card:hover {
  border-left-color: var(--crimson);
  box-shadow: 0 6px 20px rgba(22, 50, 79, .1);
  transform: translateY(-2px);
}
.issue-card:focus-visible { outline: 3px solid var(--crimson); outline-offset: 3px; }

.issue-card-title { font-weight: 600; color: var(--navy); }
.issue-card-meta { font-size: var(--fs-sm); color: var(--muted); font-variant-numeric: tabular-nums; }

/* --- one issue ----------------------------------------------------------- */

.articles { list-style: none; margin-top: clamp(28px, 3.5vw, 40px); }

/* A grid, so the number, the body and the download control hold their columns
   down the list. Article titles vary from 20 to 275 characters, and with a
   flex row the download button lands at a different x on every row. */
.article {
  display: grid;
  grid-template-columns: 2.5rem minmax(0, 1fr) auto;
  gap: 0 20px;
  align-items: start;
  padding: 22px 0;
  border-bottom: 1px solid var(--line);
}
.article:first-child { border-top: 1px solid var(--line); }

/* aria-hidden in the markup: the <ol> already conveys position to a screen
   reader, so reading the number aloud says it twice. */
.article-no {
  font-family: var(--serif);
  font-size: 17px;
  color: var(--steel);
  font-variant-numeric: tabular-nums;
  padding-top: 2px;
}

.article-title {
  font-family: var(--serif);
  font-size: clamp(17px, 1.6vw, 20px);
  font-weight: 600;
  line-height: 1.34;
  color: var(--ink);
  /* Wraps rather than truncates. An ellipsis on an academic title hides the
     part that distinguishes it from a similar one, and these titles are long
     precisely because they are specific. */
  max-width: 62ch;
}

.article-authors {
  margin-top: 6px;
  font-size: var(--fs-base);
  color: var(--muted);
  max-width: 62ch;
}

.article-meta {
  margin-top: 8px;
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
  font-size: var(--fs-sm);
  color: var(--muted);
}
.article-meta .pages { font-variant-numeric: tabular-nums; }
.article-meta .doi { color: var(--muted); text-decoration: underline; text-underline-offset: 2px; }
.article-meta .doi:hover { color: var(--crimson); }
.article-meta .doi:focus-visible { outline: 3px solid var(--crimson); outline-offset: 3px; }

.btn-pdf {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  min-height: var(--tap-min);
  padding: 10px 18px;
  border: 1px solid var(--navy);
  border-radius: 3px;
  color: var(--navy);
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: .02em;
  text-decoration: none;
  white-space: nowrap;
  transition: background-color var(--motion) var(--ease-out), color var(--motion) var(--ease-out);
}
.btn-pdf:hover { background: var(--navy); color: #fff; }
.btn-pdf:focus-visible { outline: 3px solid var(--crimson); outline-offset: 3px; }

/* Deliberately not styled as a button. It is not disabled, it is absent, and a
   greyed-out button invites the click it cannot satisfy. */
.no-pdf {
  font-size: var(--fs-sm);
  color: var(--muted);
  font-style: italic;
  padding: 10px 0;
  white-space: nowrap;
}

.issue-note,
.empty {
  margin-top: 30px;
  padding: 16px 20px;
  background: var(--chip);
  border-left: 3px solid var(--steel);
  color: var(--muted);
  max-width: 72ch;
}

.back-link { margin: 34px 0 0; }
.back-link a { color: var(--navy); font-weight: 600; text-underline-offset: 3px; }
.back-link a:hover { color: var(--crimson); }
.back-link a:focus-visible { outline: 3px solid var(--crimson); outline-offset: 3px; }

@media (max-width: 640px) {
  /* The download control moves under the body rather than staying in a third
     column, which at 390px would leave the title about 12 characters wide. */
  .article { grid-template-columns: 2rem minmax(0, 1fr); gap: 0 14px; }
  .article-action { grid-column: 2; margin-top: 12px; }
  .btn-pdf { width: 100%; }
  .no-pdf { padding: 0; }
}

/* ---------------------------------------------------------------------------
   Submission form (Task 4.1)
   --------------------------------------------------------------------------- */

/*
 * The one page on this site where a visitor types rather than reads, so the
 * rules that govern it are different from the prose pages.
 *
 * Three that are deliberate:
 *
 *   - Inputs are 16px, not the 14px body size. Anything under 16px makes iOS
 *     Safari zoom the whole page on focus, which then leaves the next field
 *     off-screen. The type scale loses this argument.
 *   - Labels sit above their control, never beside it. A left-aligned label
 *     column collapses on a phone into exactly the layout this avoids, and the
 *     eye already has to travel further on a form than on prose.
 *   - The invalid state is a crimson rule on the left edge plus a marked
 *     message, never colour alone. Colour-only error state is invisible to
 *     roughly one reader in twelve.
 */

.submit-col { min-width: 0; }
.submit-col .prose { max-width: none; }

.before-you-send { margin-bottom: var(--sp-7); }

.form-title {
  font-family: var(--serif);
  color: var(--navy);
  font-size: var(--fs-2xl);
  margin: 0 0 var(--sp-2);
}

.form-legend {
  font-size: var(--fs-base);
  color: var(--text-muted);
  margin-bottom: var(--sp-5);
}

.req { color: var(--color-accent); font-weight: 700; }

/* ---- sections ---- */

/*
 * A fieldset with a real legend, not a styled div with a heading.
 * "Authors" read out before each of twenty author fields is the entire reason
 * the grouping exists; a heading alone conveys it to sighted readers only.
 */
.form-section {
  border: 0;
  border-top: 1px solid var(--border);
  padding: var(--sp-6) 0 0;
  margin: 0 0 var(--sp-7);
}

.form-section > legend {
  font-family: var(--serif);
  font-size: var(--fs-xl);
  color: var(--navy);
  padding: 0 var(--sp-3) 0 0;
  margin-bottom: var(--sp-2);
}

.section-intro {
  font-size: var(--fs-md);
  color: var(--text-muted);
  max-width: 60ch;
  margin-bottom: var(--sp-5);
}

/* ---- one field ---- */

.field { margin-bottom: var(--sp-5); }

.field > label {
  display: block;
  font-size: var(--fs-md);
  font-weight: 600;
  color: var(--text-strong);
  margin-bottom: var(--sp-1);
}

.field-hint {
  font-size: var(--fs-base);
  color: var(--text-muted);
  line-height: 1.5;
  margin-bottom: var(--sp-2);
  max-width: 60ch;
}

.field input[type="text"],
.field input[type="email"],
.field input[type="tel"],
.field input[type="file"],
.field select,
.field textarea {
  display: block;
  width: 100%;
  /* 16px, against the 14px body scale. See the note at the top of this block:
     a smaller value makes iOS zoom on focus. */
  font-family: var(--sans);
  font-size: var(--fs-md);
  line-height: 1.45;
  color: var(--text-strong);
  background: var(--surface);
  border: 1px solid var(--steel);
  border-radius: 3px;
  padding: 11px var(--sp-3);
  /* Comfortably over the 44px touch minimum once padding and line-height are
     added, and identical across the three control types so a row does not step. */
  min-height: var(--tap-min);
}

.field textarea { resize: vertical; min-height: 96px; }

.field select {
  /* Native arrow kept. A custom one needs appearance:none, which on some
     Android builds also removes the keyboard-accessible option list. */
  padding-right: var(--sp-2);
}

.field input[type="file"] {
  padding: 9px var(--sp-3);
  background: var(--surface-sunken);
}

.field input:focus-visible,
.field select:focus-visible,
.field textarea:focus-visible {
  outline: 3px solid var(--crimson);
  outline-offset: 2px;
  border-color: var(--navy);
}

.field input:hover:not(:focus),
.field select:hover:not(:focus),
.field textarea:hover:not(:focus) { border-color: var(--navy); }

/* Two fields on one line where they are genuinely one fact split in two, such
   as a first and last name. Collapses early: 68ch halved is not a field. */
.field-row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 var(--sp-4);
}

@media (max-width: 620px) {
  .field-row { grid-template-columns: minmax(0, 1fr); }
}

/* ---- the invalid state ---- */

.field--invalid {
  border-left: 3px solid var(--color-accent);
  padding-left: var(--sp-4);
  margin-left: calc(var(--sp-4) * -1);
}

.field--invalid input,
.field--invalid select,
.field--invalid textarea { border-color: var(--color-accent); }

.field-error {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  margin-top: var(--sp-2);
  font-size: var(--fs-md);
  font-weight: 600;
  /* crimson-dark, not crimson: 6.6:1 on white against 4.6:1, and error text is
     the text most likely to be read under bad conditions. */
  color: var(--crimson-dark);
  max-width: 60ch;
}

/* The mark is the second signal alongside colour, so the state survives a
   greyscale print and a reader who cannot separate red from grey. */
.field-error-mark {
  flex: none;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--crimson-dark);
  color: #fff;
  font-weight: 700;
  font-size: var(--fs-sm);
  line-height: 20px;
  text-align: center;
}

/* ---- checkboxes ---- */

.field--check {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--sp-1) var(--sp-3);
  align-items: start;
}

/*
 * A 44px touch target around a 22px box.
 *
 * The native control measured 22px square, which tests/layout_gate.py caught at
 * every viewport. A checkbox is a control, not body copy, so the 44px minimum
 * applies to it in full, and the two on this form are the ones that decide
 * whether a paper can be sent at all.
 *
 * Making the native box 44px would work and would look like a shipping-label
 * tick box. Instead the input is 44px and transparent, and the visual box is a
 * background image centred inside it, so the hit area and the drawing are
 * sized independently. Background images rather than ::before, because a
 * pseudo-element on a replaced element is widely supported and not guaranteed,
 * and this is not a place to rely on that.
 */
.field--check input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  width: var(--tap-min);
  height: var(--tap-min);
  margin: 0;
  border: 0;
  cursor: pointer;
  background: transparent center / 22px 22px no-repeat
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 22 22'%3E%3Crect x='1' y='1' width='20' height='20' rx='3' fill='%23FFFFFF' stroke='%236E8CA8' stroke-width='2'/%3E%3C/svg%3E");
}

.field--check input[type="checkbox"]:hover {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 22 22'%3E%3Crect x='1' y='1' width='20' height='20' rx='3' fill='%23FFFFFF' stroke='%2316324F' stroke-width='2'/%3E%3C/svg%3E");
}

.field--check input[type="checkbox"]:checked,
.field--check input[type="checkbox"]:checked:hover {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 22 22'%3E%3Crect x='1' y='1' width='20' height='20' rx='3' fill='%2316324F' stroke='%2316324F' stroke-width='2'/%3E%3Cpath d='M5.5 11.3l3.6 3.6 7.4-7.4' fill='none' stroke='%23FFFFFF' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

/* Negative offset so the ring hugs the 22px drawing rather than outlining 44px
   of empty space, which reads as a focus ring on nothing. */
.field--check input[type="checkbox"]:focus-visible {
  outline: 3px solid var(--crimson);
  outline-offset: -9px;
}

/*
 * Windows high-contrast mode drops background images entirely, so an
 * appearance:none checkbox becomes invisible there. Hand the native control
 * back: a smaller target beats an absent one.
 */
@media (forced-colors: active) {
  .field--check input[type="checkbox"] { appearance: auto; -webkit-appearance: auto; }
}

/*
 * Block, NOT flex. A flex label makes every child its own flex item, so the
 * trailing required asterisk became a second item and was pushed to the far
 * end of an 800px row, a full sentence away from the text it marks.
 *
 * The padding is the vertical alignment instead: the input is 44px tall with
 * its 22px box centred, so the box centre is 22px down, and a 24px first line
 * needs 10px above it to sit on the same centre line.
 */
.field--check > label {
  display: block;
  font-weight: 400;
  font-size: var(--fs-md);
  line-height: 1.5;
  margin-bottom: 0;
  padding-top: 10px;
}

/* The hint and any error belong under the label, not under the box. */
.field--check .field-hint,
.field--check .field-error { grid-column: 2; margin-top: var(--sp-1); }

/* ---- authors ---- */

.author-block {
  border: 1px solid var(--border);
  border-left: 3px solid var(--chip);
  padding: var(--sp-4) var(--sp-5) var(--sp-2);
  margin-bottom: var(--sp-4);
  background: var(--surface);
}

.author-block--first { border-left-color: var(--color-accent); }

.author-heading {
  font-family: var(--sans);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--sp-4);
}

/* A summary is the control that opens the block, so it needs a control's touch
   target and a control's focus ring. */
.author-block > summary {
  font-size: var(--fs-md);
  font-weight: 600;
  color: var(--navy);
  cursor: pointer;
  padding: var(--sp-2) 0;
  min-height: var(--tap-min);
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

.author-block > summary:focus-visible { outline: 3px solid var(--crimson); outline-offset: 3px; }
.author-block[open] > summary { margin-bottom: var(--sp-4); }
.author-block .optional { font-weight: 400; color: var(--text-muted); }

/* ---- error summary and form-level alerts ---- */

/*
 * Both are role="alert" and both are anchored at the top of the form, because
 * an error message below the fold is a message nobody reads. The summary lists
 * every problem with a link to the field; the field still carries its own copy.
 */
.error-summary,
.form-alert {
  border: 1px solid var(--crimson-dark);
  border-left: 5px solid var(--crimson-dark);
  background: color-mix(in srgb, var(--crimson) 5%, #fff);
  padding: var(--sp-5);
  margin-bottom: var(--sp-6);
}

.error-summary h2,
.form-alert h2 {
  font-family: var(--serif);
  font-size: var(--fs-xl);
  color: var(--crimson-dark);
  margin: 0 0 var(--sp-3);
}

.error-summary:focus-visible { outline: 3px solid var(--crimson); outline-offset: 3px; }

.error-summary ul { padding-left: var(--sp-5); margin-bottom: 0; }
.error-summary li { margin-bottom: var(--sp-2); font-size: var(--fs-md); line-height: 1.5; }
.error-summary a { color: var(--crimson-dark); font-weight: 600; }
.error-summary a:focus-visible { outline: 3px solid var(--crimson); outline-offset: 2px; }

.error-summary-file,
.form-alert p {
  font-size: var(--fs-md);
  line-height: 1.6;
  margin: var(--sp-3) 0 0;
  max-width: 62ch;
}

.form-alert p:first-of-type { margin-top: 0; }

/* ---- send ---- */

.form-actions {
  border-top: 1px solid var(--border);
  padding-top: var(--sp-5);
}

.form-actions .btn {
  font-size: var(--fs-md);
  padding: 14px var(--sp-6);
  min-height: var(--tap-min);
}

.form-actions-note {
  margin-top: var(--sp-3);
  font-size: var(--fs-base);
  color: var(--text-muted);
}

@media (max-width: 620px) {
  /* Full width, because at 390px a right-aligned submit sized to its label is
     the smallest target on the longest page. */
  .form-actions .btn { display: block; width: 100%; text-align: center; }
}

/* ===========================================================================
   Submission confirmation (Task 4.3)
   =========================================================================== */

/*
 * NAVY, NOT CRIMSON. Every other bordered callout on this site uses crimson,
 * because every other one reports a problem. This block reports the single
 * thing on the page that went right, and an author who has just handed over
 * unpublished work reads a red-edged panel as "something is wrong" before they
 * read a word of it.
 */
.receipt {
  border: 1px solid var(--color-primary);
  border-left: 5px solid var(--color-primary);
  background: var(--surface-sunken);
  padding: var(--sp-5);
  margin-bottom: var(--sp-6);
}

.receipt-label {
  font-family: var(--sans);
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 0 0 var(--sp-2);
}

/*
 * The one value on this page a reader has to copy accurately.
 *
 * tabular-nums so the digits line up rather than shuffling under a
 * proportional face; `user-select: all` so one click takes the whole reference
 * and not a half of it. `overflow-wrap` rather than a fixed size, because at
 * 390px a 21-character token set at 26px does not fit and must break instead
 * of pushing the page sideways.
 */
.receipt-ref {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: clamp(var(--fs-xl), 4.5vw, var(--fs-2xl));
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
  font-weight: 600;
  color: var(--color-primary);
  margin: 0;
  overflow-wrap: anywhere;
  user-select: all;
}

.receipt-note {
  font-size: var(--fs-base);
  line-height: 1.6;
  color: var(--text-muted);
  margin: var(--sp-3) 0 0;
  max-width: 62ch;
}

/* ===========================================================================
   Subscription alert signup (Task 4.4)
   =========================================================================== */

/*
 * One field and one button, so it is sized to that rather than reusing the
 * submission form's full-width fieldset rhythm. Capped at a measure a long
 * institutional address still fits inside: an input stretched to the full
 * prose column reads as though it wants a paragraph.
 */
.alert-form {
  max-width: 32rem;
  margin: var(--sp-5) 0 var(--sp-7);
}

.alert-form .form-actions { margin-top: var(--sp-5); }

/*
 * THE HONEYPOT, AND IT MUST NOT BECOME display:none.
 *
 * Two different jobs are being done here and only one of them is CSS. Hiding
 * it from people is this rule. Hiding it from assistive technology is
 * aria-hidden and tabindex="-1" in the markup, because a screen reader user
 * who fills this in is silently dropped, which is the single worst outcome a
 * spam trap can produce.
 *
 * Off-screen rather than display:none on purpose: the naive form-fillers this
 * is aimed at skip fields the browser reports as not rendered, and a trap they
 * skip catches nothing.
 */
.field--trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/*
 * The confirmation headline, in the same navy panel the submission receipt
 * uses. There is no reference to quote here, so this takes the place
 * .receipt-ref holds there and is set as prose rather than as a token.
 */
.receipt-headline {
  font-family: var(--serif);
  font-size: var(--fs-xl);
  font-weight: 600;
  color: var(--color-primary);
  margin: 0;
}
