/* Patent Case Watch — styles */

:root {
  --paper: #fafaf7;
  --card: #ffffff;
  --ink: #1c2128;
  --ink-soft: #4b5563;
  /* Darkened from #8a919c, which was 3.18:1 on --card, 3.04:1 on --paper and
   * 2.78:1 on --neutral-bg, against the 4.5:1 that WCAG 1.4.3 requires for text
   * this size. It carried eleven components -- metadata rows, filter labels,
   * the result count, breadcrumbs, definition-list terms, the footer -- so one
   * token accounted for eleven of the thirteen contrast failures on the site.
   * Hue and saturation are unchanged (216.7deg -> 217.9deg); only lightness
   * moved, so the grey still reads as the same cool grey. Now 5.29 / 5.06 /
   * 4.63:1 on those three backgrounds, and still clearly lighter than
   * --ink-soft, which is what keeps the three-step text hierarchy legible. */
  --ink-faint: #656c78;
  --line: #e5e4df;
  --navy: #1f3a5f;
  --navy-dark: #16293f;
  --edtx: #8c2f39;      /* deep brick — E.D. Tex. */
  --cafc: #1f3a5f;      /* federal navy — Fed. Cir. */
  /* Only --cafc-bg survives, on the tag ribbons. --edtx-bg went with the venue
   * badge's fill; a token nothing references is a colour the next person has to
   * decide whether to trust. */
  --cafc-bg: #eaeff5;
  --granted: #1e6f43;
  --granted-bg: #e7f2ea;
  --denied: #9b2c2c;
  --denied-bg: #f9ebeb;
  /* Darkened from #8a6d1a (4.34:1 on --mixed-bg) -- a marginal 1.4.3 miss, and
   * the only holding chip that failed. Taken to 5.05:1 rather than the bare
   * 4.5:1 so the four chips sit in the same band: granted 5.36, denied 6.49,
   * neutral 6.62. Hue is untouched (44.5deg -> 44.4deg), so it is still the
   * same ochre. */
  --mixed: #7e6318;
  --mixed-bg: #f7f1de;
  --neutral: #4b5563;
  --neutral-bg: #eef0f2;
  --serif: "Iowan Old Style", "Palatino", "Palatino Linotype", Georgia, "Times New Roman", serif;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;

  /* A 4px spacing scale. The values it replaces -- 14, 18, 20, 22, 24, 26, 28,
   * 30, 34 -- are each within 2px of a multiple of four and none of them agree
   * with any other, which is what made the page feel slightly loose without any
   * single thing looking wrong. */
  --s1: 4px;
  --s2: 8px;
  --s3: 12px;
  --s4: 16px;
  --s5: 20px;
  --s6: 24px;
  --s7: 32px;

  /* Three type sizes in a feed card, not five. It was saying four things --
   * where, when, what, and a two-line excerpt -- in 10.5, 11.5, 12.5, 14 and
   * 15.5px, which reads as five levels of importance for four pieces of
   * information. Label / meta / body, and the serif title above them. */
  --fs-label: 11px;
  --fs-meta: 13px;
  --fs-body: 15px;
  --fs-title: 19px;

  /* One radius. Cards were 8px, chips and badges 4px, buttons 6px and 999px. */
  --radius: 6px;
}

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

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

body {
  font-family: var(--sans);
  background: var(--paper);
  color: var(--ink);
  line-height: 1.55;
  font-size: 15.5px;
}

a { color: var(--navy); text-decoration: none; }
a:hover { text-decoration: underline; }

/* WCAG 1.4.1 Use of Colour (Level A). A link inside running prose was
 * distinguished from the text around it by colour alone: navy #1f3a5f against
 * ink #1c2128 is 1.41:1, where the criterion wants 3:1. Underlining is the
 * only fix that does not require repainting either colour.
 *
 * Scoped to `p a` because that is exactly what axe-core's link-in-text-block
 * rule flagged, on every page it flagged anything: the contact link, the
 * corrections address, and the govinfo link in the footer sentence. Links that
 * already carry a non-colour affordance are deliberately untouched -- the
 * masthead wordmark, card titles, tag ribbons, the PDF button, and the year-nav
 * chips are all shapes or headings, not words in a sentence.
 *
 * Breadcrumbs are included for a different reason: every crumb is --ink-faint,
 * so the linked crumbs and the current-page crumb were the same colour as each
 * other. There was no visual cue at all, which is worse than a weak one. */
p a,
.breadcrumb a { text-decoration: underline; text-underline-offset: 2px; }

.wrap { max-width: 880px; margin: 0 auto; padding: 0 24px; }

/* ---------- Masthead ---------- */

.masthead {
  background: var(--navy-dark);
  color: #fff;
  border-bottom: 3px solid var(--edtx);
}

.masthead .wrap {
  padding-top: 34px;
  padding-bottom: 30px;
}

.brand {
  font-family: var(--serif);
  font-size: 30px;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: #fff;
  /* index.html carries the wordmark as <h1>; every other page uses a <div> and
   * spends its <h1> on the page title. Zeroing the margin means the element can
   * be either without the browser's default h1 margin moving the masthead. */
  margin: 0;
}
.brand a { color: #fff; }
.brand a:hover { text-decoration: none; }

.brand .amp { color: #c8a24a; font-style: italic; }

.tagline {
  margin-top: 6px;
  font-size: 13.5px;
  color: #b9c4d2;
  letter-spacing: 0.02em;
}

/* ---------- Filter bar ---------- */

/* Hidden by default, because every control inside is built by JavaScript: a
 * no-JS visitor would otherwise get four labels, two empty dropdowns and a
 * search box that accepts typing and does nothing.
 *
 * The reveal has to happen in CSS, not JavaScript. It was previously the
 * hidden attribute cleared in app.js init(), which meant the browser painted
 * the page with no filter bar and then app.js inserted ~300px above the feed:
 * Lighthouse measured CLS 0.292 on mobile, and an A/B against an
 * otherwise-identical page put 0.217 of that on this one element. (The
 * commit that introduced it claimed CLS 0 in good faith -- the headless
 * browser it was measured in never painted, so it recorded no shifts at all.
 * CLS cannot be measured anywhere that does not paint.)
 *
 * `scripting: enabled` is evaluated while the stylesheet is parsed, before
 * anything is painted, so the bar is present and correctly sized in the first
 * frame. No inline <style> (blocked by style-src 'self'), no head script, no
 * extra request. app.js keeps a display fallback for browsers that do not
 * support the media feature -- those still get a working filter bar, just
 * with the shift this removes everywhere else. */
.filters {
  display: none;
  background: var(--card);
  border-bottom: 1px solid var(--line);
  box-shadow: 0 1px 2px rgba(20, 25, 35, 0.04);
}

@media (scripting: enabled) {
  .filters { display: block; }
}

/* A grid, because a wrapping flex row cannot be relied on not to wrap.
 *
 * The controls are a fixed set of five groups plus the result count. As a flex
 * row they re-flowed whenever any child's width changed, and one did: .pills
 * shipped 42px and app.js took it to 198px, pushing the fifth group onto a
 * second line and dropping the feed 28px at 1440 — CLS 0.2652, measured on
 * production, five times the mobile figure Lighthouse reported at launch
 * because Lighthouse runs mobile-emulated by default.
 *
 * The columns are `auto` and `1fr`, with no pixel reservations anywhere: the
 * venue buttons now ship in the HTML (see the vp: markers in index.html), so
 * the first column is sized by real content in the first frame. A grid with a
 * declared column count cannot re-flow no matter what any child does, which is
 * the property that matters — the previous fix reserved the bar's height and
 * was undone by its width. */
.filters .wrap {
  padding-top: 12px;
  padding-bottom: 12px;
  display: grid;
  /* Venue is `auto` because its buttons are real content in the first frame.
   * The other four are `1fr` and NOT `auto`: an auto track is sized by its
   * contents, and a <select> that ships with no options has a small max-content
   * width that grows the moment app.js adds them — which re-flowed the row all
   * over again, 0.027 of it, after the pills were fixed. A fraction track is
   * sized by the container, so nothing a control does to itself can move its
   * neighbours.
   *
   * The four fractions are not equal. Search carries a 22-character
   * placeholder and is the control people actually reach for, so it gets the
   * widest track; Year holds "All years" and a four-digit number and needs the
   * least. Sizing them by content instead would reintroduce exactly the
   * dependency this grid exists to remove. */
  grid-template-columns: auto minmax(0, 1fr) minmax(0, 1.4fr) minmax(0, 1fr) minmax(0, 0.8fr);
  gap: 12px 24px;
  align-items: end;
}

.filter-group { min-width: 0; }

/* Sixth child of a five-column grid, so it lands on a row of its own. Told to
   span every column, or it inherits the width of the venue column and the count
   wraps inside 198px. */
/* Sixth child of a five-column grid, so it lands on a row of its own. Told to
 * span every column, or it inherits the width of the venue column and the count
 * wraps inside 198px.
 *
 * All of its layout is here. It used to be declared twice — once beside the
 * feed styles with `margin-left: auto`, which was how it pushed itself right in
 * the old flex bar and does nothing at all in a grid, and once more in the
 * 640px block. Two rules for one element, one of them dead, is how the next
 * person changes the wrong one. */
.filter-meta {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--s1);
}

/* Fill the grid track. A fixed 190px width sat the controls flush against each
   other and the column gap never appeared. */
.filters .wrap .filter-select,
.filters .wrap .year-select,
.filters .wrap .search-input { width: 100%; max-width: none; }

/* Two rows on a phone, instead of four that became six.
 *
 * Measured at 320px: the bar was 375px tall in a 568px viewport, with the feed
 * starting at 548px — an iPhone SE user saw five filter controls and no
 * opinions at all. It is 170px now.
 *
 * Most of that comes from the labels, which move into the accessibility tree
 * rather than off the page: every control already carries its own aria-label,
 * and each <select>'s first option reads "All subjects" / "All years" /
 * "All judges", so a sighted user loses a heading that was repeating what the
 * control below it already said, at 17px of vertical each. The venue group
 * keeps its name through aria-labelledby, which is why the label element stays
 * in the DOM instead of being deleted. */
@media (max-width: 760px) {
  .filters .wrap {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 8px;
  }
  .filter-label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .filter-group:first-child { grid-column: 1 / -1; }
}

.filter-label {
  font-size: var(--fs-label);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--ink-faint);
  margin-bottom: var(--s1);
}

/* No min-height any more. It was reserving one row for a container that shipped
 * empty and was filled by app.js; the buttons are now in the HTML, so the row
 * is reserved by being there. Reserving the height was also only ever half the
 * problem — the width was what re-flowed the bar. */
.pills { display: flex; flex-wrap: wrap; gap: 6px; }

.pill {
  font-family: var(--sans);
  font-size: 12.5px;
  padding: 4px 11px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--paper);
  color: var(--ink-soft);
  cursor: pointer;
  transition: all 0.12s ease;
  white-space: nowrap;
}

.pill:hover { border-color: var(--navy); color: var(--navy); }

.pill.active {
  background: var(--navy);
  border-color: var(--navy);
  color: #fff;
}

.search-input {
  font-family: var(--sans);
  font-size: 13px;
  padding: 5px 12px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--paper);
  color: var(--ink);
  width: 190px;
  outline: none;
  transition: border-color 0.12s ease;
}
.search-input:focus { border-color: var(--navy); }
.search-input::placeholder { color: var(--ink-faint); }

.year-select,
.filter-select {
  font-family: var(--sans);
  font-size: 12.5px;
  padding: 4px 10px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--paper);
  color: var(--ink-soft);
  outline: none;
}
.year-select:focus,
.filter-select:focus { border-color: var(--navy); }

.filter-select {
  width: 190px;
  max-width: 190px;
}

.load-more {
  display: block;
  width: 100%;
  font-family: var(--sans);
  font-size: 14px;
  font-weight: 600;
  color: var(--navy);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 12px;
  cursor: pointer;
  margin: 4px 0 24px;
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.load-more:hover { border-color: var(--navy); box-shadow: 0 2px 8px rgba(20,25,35,0.06); }

.meta-note { color: var(--ink-faint); font-size: 12px; }

.auto-note {
  font-size: 12.5px;
  color: var(--ink-faint);
  background: var(--neutral-bg);
  border-left: 3px solid var(--line);
  padding: 8px 12px;
  border-radius: 0 4px 4px 0;
  margin-bottom: 14px;
}


/* No white-space: nowrap here. "Showing 100 of 4,580 opinions - newest decision
 * first" cannot fit on one line at 320px, and forcing it produced 18px of
 * horizontal page overflow -- a WCAG 1.4.10 Reflow failure, which also covers
 * 400% zoom on a 1280px screen. Removing nowrap takes the overflow to exactly
 * 0; the count simply wraps to a second line on narrow screens. */
.result-count { font-size: 12.5px; color: var(--ink-faint); padding-top: 2px; }

.clear-btn {
  font-size: 12.5px;
  color: var(--edtx);
  background: none;
  border: none;
  cursor: pointer;
  /* Vertical padding to clear the 24px minimum tap target (WCAG 2.5.8). This
     is the only standalone control on the page that missed it, and on a phone
     it sits right under the result count where a near-miss hits nothing. The
     padding only counts because the visible state below is inline-BLOCK; on a
     plain inline box, vertical padding does not affect the line box at all. */
  padding: 5px 0;
  text-decoration: underline;
  display: none;
}
.clear-btn.visible { display: inline-block; }


/* ---------- Feed ---------- */

.feed .wrap { padding-top: 28px; padding-bottom: 60px; }

.feed-item {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s4) var(--s5) var(--s3);
  margin-bottom: var(--s3);
  transition: box-shadow 0.15s ease, border-color 0.15s ease;
}

.feed-item:hover {
  border-color: #c9ccd2;
  box-shadow: 0 3px 12px rgba(20, 25, 35, 0.07);
}

.item-meta {
  display: flex;
  align-items: center;
  gap: var(--s2);
  margin-bottom: var(--s1);
  flex-wrap: wrap;
}

/* A kicker, not a badge.
 *
 * The venue was a filled pill sitting next to a filled holding chip, and the
 * two read as equally weighted. They are not: the venue is a standing category
 * that only ever takes two values, while the holding is the outcome — the one
 * thing on a card a litigator is actually scanning for, and the only place on
 * this site where colour carries meaning rather than decoration. Two coloured
 * fills per card meant the meaningful one had to compete with a label.
 *
 * Dropping the fill raises contrast rather than lowering it, since the tint was
 * lighter than the page: E.D. Tex. goes 7.04:1 to 8.14:1, Fed. Cir. 9.93:1 to
 * 11.48:1, both well past the 4.5:1 this size needs. The extra letter-spacing
 * is doing the job the fill used to — holding the word together as a unit
 * without drawing a box around it. */
.venue-badge {
  font-size: var(--fs-label);
  font-weight: 700;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  background: none;
  padding: 0;
  border-radius: 0;
}
.venue-badge.edtx { color: var(--edtx); }
.venue-badge.cafc { color: var(--cafc); }

.item-date, .item-type { font-size: var(--fs-meta); color: var(--ink-faint); }
/* .item-meta is a wrapping flex row, so this separator belongs to the item that
   follows it and travels with it onto the next line — leaving an orphaned "·"
   at the start of that line. It only ever wraps on narrow screens, where the
   line break already separates the two values, so the dot is dropped there
   rather than dragged along. */
.item-type::before { content: "·"; margin-right: 10px; }
@media (max-width: 640px) {
  .item-type::before { content: none; }
}

.item-title {
  font-family: var(--serif);
  font-size: var(--fs-title);
  font-weight: 600;
  line-height: 1.35;
  margin-bottom: var(--s1);
}
.item-title a { color: var(--ink); }
.item-title a:hover { color: var(--navy); }

.item-teaser {
  font-size: var(--fs-body);
  line-height: 1.6;
  color: var(--ink-soft);
  margin-bottom: var(--s3);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  /* Teasers are not in feed-index.js; they arrive later from the per-year
     snippet shard. Reserve the two clamped lines up front so the cards do not
     render short and then push everything below them down when the shard
     lands — measured at a 4,340 px jump for a 100-card page. Must equal
     2 x the line-height declared directly above: this block sets its own
     rather than inheriting the body's 1.55, so the two have to be changed
     together or the reservation stops matching the clamp. */
  min-height: 3.2em;
}

/* Shown in place of a card excerpt when that year's snippet shard could not
 * be loaded. Must come after .item-teaser: both are single-class selectors,
 * so specificity ties and source order is what decides the colour.
 * Deliberately distinguished from real excerpt text -- an excerpt is the
 * court's own words, and a failure notice in the same voice would be
 * indistinguishable from one. Keeps the reserved two-line height, so a
 * failed shard still causes no layout shift. */
.item-teaser-unavailable { color: var(--ink-faint); font-style: italic; }

.item-foot {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s1) 6px;
  align-items: center;
}

.tag {
  font-size: 12px;
  color: var(--ink-soft);
  background: var(--neutral-bg);
  padding: 3px 10px;
  border-radius: 3px;
}

.holding-chip {
  font-size: 12px;
  font-weight: 700;
  padding: 3px 10px;
  border-radius: 3px;
  margin-left: auto;
  white-space: nowrap;
}
.holding-granted   { color: var(--granted); background: var(--granted-bg); }
.holding-denied    { color: var(--denied);  background: var(--denied-bg); }
.holding-mixed     { color: var(--mixed);   background: var(--mixed-bg); }
.holding-neutral   { color: var(--neutral); background: var(--neutral-bg); }

.empty-state {
  text-align: center;
  color: var(--ink-faint);
  padding: 60px 0;
  font-size: 15px;
}

/* ---------- Detail page ---------- */

.breadcrumb {
  padding-top: 26px;
  font-size: 13px;
  color: var(--ink-faint);
}
.breadcrumb a { color: var(--ink-faint); }
.breadcrumb a:hover { color: var(--navy); }

/* The trail is an <ol> (see breadcrumb_html() in pipeline/gen_data_full.py), so
 * the separator is generated content rather than a literal character in the
 * markup: decoration that is not in the accessibility tree and cannot be read
 * out as "single right-pointing angle quotation mark". Laid out as a wrapping
 * flex row so a long case caption breaks onto a second line instead of forcing
 * the page sideways — the crumb carries the full caption, which is regularly
 * wider than a phone. */
.breadcrumb ol {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
}
/* 4px each side reproduces the two literal spaces the old markup had around the
 * character at this font size; the colour is inherited rather than set, so the
 * trail looks byte-for-byte the same as before the semantics changed. */
.breadcrumb li + li::before {
  content: "\203A";
  margin: 0 4px;
}

.opinion-head { padding-top: var(--s4); padding-bottom: var(--s1); }

.opinion-title {
  font-family: var(--serif);
  font-size: 27px;
  font-weight: 600;
  line-height: 1.3;
  /* More space above than the 10px this had. The venue above it is no longer a
     filled badge with its own padding holding it apart from the title — it is
     bare letter-spaced text, and at 10px it read as part of the heading rather
     than as a kicker over it. */
  margin: var(--s3) 0 var(--s5);
}

.opinion-meta {
  display: grid;
  grid-template-columns: 124px 1fr;
  gap: var(--s1) var(--s4);
  font-size: var(--fs-meta);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s4) var(--s5);
  margin-bottom: var(--s5);
}
.opinion-meta dt {
  font-weight: 700;
  color: var(--ink-faint);
  text-transform: uppercase;
  font-size: var(--fs-label);
  letter-spacing: 0.08em;
  padding-top: 1px;
}
.opinion-meta dd { color: var(--ink); }

.ribbons { display: flex; flex-wrap: wrap; gap: 7px; margin-bottom: 22px; }

.ribbon {
  font-size: 12px;
  font-weight: 600;
  color: var(--navy);
  background: var(--cafc-bg);
  border-left: 3px solid var(--navy);
  padding: 5px 12px;
  border-radius: 0 4px 4px 0;
}

.holding-banner {
  display: inline-flex;
  align-items: baseline;
  gap: 10px;
  font-size: 14px;
  padding: 10px 16px;
  border-radius: 6px;
  margin-bottom: 26px;
}
.holding-banner .label { font-weight: 800; text-transform: uppercase; font-size: 12px; letter-spacing: 0.05em; }

.pdf-btn {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  background: var(--navy);
  color: #fff;
  font-size: 14px;
  font-weight: 600;
  padding: 10px 18px;
  border-radius: 6px;
  margin-bottom: 8px;
  transition: background 0.12s ease;
}
.pdf-btn:hover { background: var(--navy-dark); text-decoration: none; }
.pdf-btn svg { flex-shrink: 0; }

.source-link { font-size: 12.5px; color: var(--ink-faint); margin-bottom: 30px; }

.summary-section h2 {
  font-family: var(--serif);
  font-size: 17px;
  font-weight: 600;
  border-bottom: 1px solid var(--line);
  padding-bottom: 8px;
  margin-bottom: 14px;
}

.summary-section p {
  font-size: 15px;
  color: #2a3038;
  margin-bottom: 14px;
  line-height: 1.7;
}

.opinion-body { padding-bottom: 70px; }

/* Single column, at every width.
 *
 * There was a 300px second column here, holding an ad unit, and the page was
 * widened to 1040px so it would not squeeze the text. With ads gone the column
 * was empty and the text sat at 700px inside a 1040px page, off-centre, with
 * nothing beside it.
 *
 * Giving the column a job instead was tried and measured: put the document's
 * own metadata in it and the values do not fit. "Docket Entry" is a
 * docket-sheet sentence up to ~120 characters, and 300px less a 116px label
 * gutter leaves about 170px, so Court broke over five lines, Case No. over
 * four, Docket Entry over ten. The stickiness argument does not survive
 * measurement either: detail pages render 1,245-2,090px tall at 1440x900, so
 * there is between 0.4 and 1.3 of a viewport for a sticky element to accompany.
 *
 * 760px is the measure, not 1040px: the same body size as the feed at roughly
 * 78 characters a line.
 *
 * The .opinion-layout and .opinion-main wrappers that made the grid are gone
 * from the markup too — with the rail removed they were two nested divs set to
 * display:block around the whole page body, on 6,692 pages. */
@media (min-width: 940px) {
  .wrap.opinion-body { max-width: 760px; }
}

.actions { margin: 26px 0 6px; }

/* ---------- Static archive & opinion pages ---------- */

.archive-body { padding-bottom: 70px; }

.archive-title {
  font-family: var(--serif);
  font-size: 26px;
  font-weight: 600;
  margin: 18px 0 6px;
}

.archive-intro { font-size: 14px; color: var(--ink-soft); margin-bottom: 22px; }

.archive-month {
  font-family: var(--serif);
  font-size: 17px;
  font-weight: 600;
  border-bottom: 1px solid var(--line);
  padding-bottom: 6px;
  margin: 26px 0 10px;
}

.archive-list { list-style: none; }
.archive-list li {
  padding: 7px 0;
  border-bottom: 1px solid var(--line);
  font-size: 14px;
  display: flex;
  gap: 10px;
  align-items: baseline;
  flex-wrap: wrap;
}
.archive-list .item-date { flex-shrink: 0; min-width: 88px; }

.year-nav { display: flex; flex-wrap: wrap; gap: 8px; margin: 16px 0 30px; }
.year-nav a {
  font-size: 13.5px;
  padding: 5px 13px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--card);
  color: var(--navy);
}
.year-nav a:hover { border-color: var(--navy); text-decoration: none; }

.related-docs { margin-top: 34px; }
.related-docs h2 {
  font-family: var(--serif);
  font-size: 17px;
  font-weight: 600;
  border-bottom: 1px solid var(--line);
  padding-bottom: 8px;
  margin-bottom: 10px;
}
.related-docs ul { list-style: none; }
.related-docs li { padding: 6px 0; font-size: 14px; display: flex; gap: 10px; flex-wrap: wrap; align-items: baseline; }
.related-docs .item-date { flex-shrink: 0; min-width: 88px; }

.footer-nav { margin-bottom: 12px; font-size: 13.5px; }

/* ---------- Accessibility ---------- */

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  background: var(--navy-dark);
  color: #fff;
  padding: 10px 18px;
  border-radius: 0 0 6px 0;
  font-size: 14px;
}
.skip-link:focus {
  left: 0;
  text-decoration: none;
  outline: 2px solid #c8a24a;
}

.pill:focus-visible,
.filter-select:focus-visible,
.year-select:focus-visible,
.search-input:focus-visible,
.load-more:focus-visible,
.clear-btn:focus-visible,
a:focus-visible {
  outline: 2px solid var(--navy);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

.unavailable-note {
  font-size: 14px;
  color: var(--ink);
  background: var(--denied-bg);
  border-left: 3px solid var(--denied);
  padding: 12px 16px;
  border-radius: 0 6px 6px 0;
  margin: 26px 0 8px;
  max-width: 640px;
}

a.ribbon:hover { text-decoration: none; border-left-color: var(--edtx); }

.noscript-note {
  font-size: 14px;
  color: var(--ink-soft);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 12px 16px;
  margin-bottom: 16px;
}

/* ---------- Footer ---------- */

.site-footer {
  border-top: 1px solid var(--line);
  background: var(--card);
}
.site-footer .wrap {
  padding-top: 22px;
  padding-bottom: 30px;
  font-size: 12.5px;
  color: var(--ink-faint);
  line-height: 1.7;
}

/* ---------- Responsive ---------- */

@media (max-width: 640px) {
  /* The 760px block above already sets this bar's gap and columns; this only
     changes where the count sits, which reads better ranged left once the
     controls are a two-column stack rather than a row. */
  .filter-meta { align-items: flex-start; }
  .holding-chip { margin-left: 0; }
  .opinion-meta { grid-template-columns: 1fr; gap: 2px; }
  .opinion-meta dt { margin-top: 10px; }
  .opinion-title { font-size: 22px; }
  .brand { font-size: 24px; }
}

/* ---------- Digest signup (Phase 1) ---------- */
/* Rendered only when apiOrigin is configured; see subscribe_form_html() in
   pipeline/gen_data_full.py. Two placements share these styles: a full-width
   band under the masthead on the feed, and a quieter block at the foot of each
   opinion page. */

.subscribe {
  background: var(--card);
  border-bottom: 1px solid var(--line);
  padding: 22px 0 24px;
}
.subscribe-doc {
  border: 1px solid var(--line);
  border-left: 3px solid var(--navy);
  background: var(--paper);
  margin: 32px 0 8px;
  padding: 18px 0 20px;
}
.subscribe-doc .wrap { padding: 0 20px; max-width: none; }

.subscribe-h {
  font-family: var(--serif);
  font-size: 1.22rem;
  margin: 0 0 4px;
  color: var(--ink);
}
.subscribe-doc .subscribe-h { font-size: 1.08rem; }

.subscribe-blurb {
  margin: 0 0 12px;
  font-size: 0.9rem;
  color: var(--ink-soft);
  max-width: 62ch;
}

.subscribe-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}
/* The visible label is redundant next to the placeholder for sighted users but
   is what screen readers announce; keep it in the accessibility tree only. */
.subscribe-label {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}
.subscribe-input {
  flex: 1 1 240px;
  min-width: 0;
  font-family: var(--sans);
  font-size: 0.95rem;
  padding: 9px 11px;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: #fff;
  color: var(--ink);
}
.subscribe-input:focus {
  outline: 2px solid var(--navy);
  outline-offset: 1px;
  border-color: var(--navy);
}
.subscribe-input:disabled { background: var(--neutral-bg); color: var(--ink-faint); }

.subscribe-btn {
  flex: 0 0 auto;
  font-family: var(--sans);
  font-size: 0.9rem;
  font-weight: 600;
  padding: 10px 18px;
  border: 1px solid var(--navy-dark);
  border-radius: 3px;
  background: var(--navy);
  color: #fff;
  cursor: pointer;
}
.subscribe-btn:hover:not(:disabled) { background: var(--navy-dark); }
.subscribe-btn:disabled { opacity: 0.55; cursor: default; }

/* The honeypot must be unreachable for people — including keyboard and screen
   reader users — while staying a real, fillable field for a scripted client.
   Hence off-canvas rather than display:none, which some bots detect. */
.subscribe-hp {
  position: absolute;
  left: -9999px;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.subscribe-note {
  flex: 1 1 100%;
  margin: 2px 0 0;
  font-size: 0.76rem;
  color: var(--ink-faint);
}
.subscribe-status {
  flex: 1 1 100%;
  margin: 0;
  font-size: 0.85rem;
}
.subscribe-status:empty { display: none; }
.subscribe-status.is-ok { color: var(--granted); font-weight: 600; }
.subscribe-status.is-error { color: var(--denied); font-weight: 600; }

@media (max-width: 560px) {
  .subscribe-form { gap: 8px; }
  .subscribe-input, .subscribe-btn { flex: 1 1 100%; }
}
