/* Patent Case Watch — styles */

/* THE hidden ATTRIBUTE HAS TO WIN.
 *
 * `[hidden] { display: none }` lives in the UA stylesheet, and any AUTHOR rule
 * that sets display beats it no matter how weak its selector. `.load-more`
 * declares `display: block`, so `more.hidden = true` in app.js set the
 * attribute and the browser went on painting the button: filter the feed to
 * 2008, click Load more, and all 178 opinions arrive with the button still
 * sitting under them offering "78 remaining".
 *
 * !important, and not a narrower `.load-more[hidden]`, because the next
 * element someone toggles with .hidden will hit exactly this and the symptom
 * — a control that ignores being hidden — reads as a JavaScript bug, which is
 * where the time goes. `.filter-group`, the other element app.js hides, has no
 * display rule and worked by luck. */
[hidden] { display: none !important; }

: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);
}

/* The digest link sits in the top-right of the masthead, in space the header
   already occupies — so the second route to the signup costs no height at all.
   Grid, not float: the tagline must be free to wrap to as many lines as it
   needs without the link moving or overlapping it. */
/* THE BANDS. Each full-width band sets its own inset, and they had drifted off
 * the 4px scale in every direction: 34/30 here, 28/60 on the feed, 22/30 in
 * the footer, 22/24 on the subscribe base. The components inside them were
 * already tokenised -- .feed-item at 16/20/12, .item-title at 4 -- so the page
 * was half on the scale and half off it. All --s6/--s7 now. */
.masthead .wrap {
  padding-top: var(--s7);
  padding-bottom: var(--s7);
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: start;
  column-gap: var(--s4, 16px);
}
.masthead .brand,
.masthead .tagline { grid-column: 1; }
.masthead-digest {
  grid-column: 2;
  grid-row: 1 / span 2;
  align-self: center;
  justify-self: end;
  padding: 6px 12px;
  border: 1px solid rgba(255, 255, 255, 0.45);
  border-radius: var(--radius, 6px);
  font-size: var(--fs-meta, 13px);
  font-weight: 500;
  line-height: 1.2;
  color: #fff;
  text-decoration: none;
  white-space: nowrap;
}
.masthead-digest:hover,
.masthead-digest:focus-visible {
  background: rgba(255, 255, 255, 0.14);
  border-color: rgba(255, 255, 255, 0.75);
}
/* It stays in the top-right corner at every width, including phones. Letting it
   drop under the wordmark on narrow screens added 42px to the masthead —
   measured — which is the opposite of the point: on a 375x812 phone that height
   comes straight out of the opinion list. The tagline wraps in column one
   instead, which costs nothing. */
@media (max-width: 560px) {
  .masthead-digest { padding: 5px 10px; }
  .masthead-digest-long { display: none; }
}

.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 ---------- */

/* 64 at the foot, matching .opinion-body, so the two page types a reader moves
 * between end with the same breathing room. Derived from the scale rather than
 * written as 64px so it tracks --s7. */
.feed .wrap { padding-top: var(--s7); padding-bottom: calc(var(--s7) * 2); }

.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 ---------- */

/* THE HEAD RHYTHM. Unlike the tail, nothing up here is a specificity loss --
 * every computed value matches the rule that declares it. The problem is the
 * scale: the four separations between the breadcrumb, the title block, the
 * holding banner, the ribbons, the metadata card and the at-issue card were
 * 26px, 26px, 22px and 21.7px. Four gaps that read as one gap and are not,
 * none of them on the 4px scale the rest of the site uses, and the 21.7 was
 * an em margin drifting with the font size. They are all --s6 now. */
.breadcrumb {
  padding-top: var(--s6);
  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(--s6);   /* the head rhythm; see .breadcrumb */
}
.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: var(--s6); }

.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: var(--s6);   /* the head rhythm; see .breadcrumb */
}
.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; }

/* "Cite this opinion": selectable plain text, no copy button. Set as a quiet
 * card rather than a heading-and-paragraph, so a reader scanning for it finds
 * it at a glance, and a reader who is not looking for it is not detained.
 *
 * The citation line is serif and slightly larger than its surroundings because
 * it is the thing being offered; everything else in the block explains it.
 * user-select is left alone -- select-all-on-click sounds helpful and prevents
 * the common case, which is dragging out the case name without the docket. */
.cite-this {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s4) var(--s5);
  margin: 0 0 var(--s6);
  max-width: 68ch;
}
.cite-this h2 {
  font-family: var(--sans);
  font-size: var(--fs-label);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: 0 0 var(--s2);
}
.cite-line {
  font-family: var(--serif);
  font-size: var(--fs-body);
  line-height: 1.5;
  margin: 0 0 var(--s3);
}
.cite-meta {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--s1) var(--s3);
  margin: 0 0 var(--s3);
  font-size: var(--fs-meta);
}
.cite-meta dt { color: var(--ink-faint); }
/* The permalink and the govinfo ID are long, unbreakable-looking strings in a
 * narrow column; without this they overflow the card on a phone. */
.cite-meta dd { margin: 0; overflow-wrap: anywhere; }
.cite-meta code { font-size: 12px; }
.cite-note { font-size: 12px; line-height: 1.5; color: var(--ink-faint); margin: 0; }
@media (max-width: 30rem) {
  /* Below ~480px a max-content label column plus a permalink leaves the value
     column a few characters wide. Stack instead. */
  .cite-meta { grid-template-columns: 1fr; gap: 0; }
  .cite-meta dt { margin-top: var(--s2); }
}

/* THE TAIL RHYTHM. Everything below the opinion text -- footnotes, the
 * extraction note, this source line, the digest card -- is separated by
 * horizontal rules, and the space around them had drifted to eight different
 * values: 34px above the footnotes rule, 8px above the next one, 14px and 16px
 * below them, 17px, 24px, 30px. Four rules in the last 300px of the page at
 * eight different intervals reads as damage rather than structure.
 *
 * One interval now, everywhere in the tail: --s7 (32px) of air ABOVE each
 * rule, --s4 (16px) below it. The source line carries no rule of its own, so
 * it sits at 16px and reads as part of the provenance note above it, which is
 * what it is. */
.source-link {
  font-size: 12.5px;
  color: var(--ink-faint);
  margin: var(--s4) 0 var(--s7);
}

/* About, Privacy, Terms, Contact — and subscribed/unsubscribed/link-expired.
 * .summary-section appears on those seven pages and nowhere else: no opinion,
 * archive, hub or feed page uses it, so everything below is contained. */

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

/* These pages had no max-width at all, so at 1280px they set 712px of 15px
 * text -- 104 characters a line, against 86 for the opinions they describe.
 *
 * 62ch, NOT the 68ch every other reading surface here uses, and the difference
 * is not an oversight. ch is the width of "0", which is not a fixed ratio of
 * average lowercase width: the opinion text is serif (--serif, "0" = 8.90px at
 * 16px) and these pages are sans (-apple-system, "0" = 9.77px). The same 68ch
 * token therefore buys 605px of serif and 685px of sans -- 86 characters a
 * line against 96. Matching the token would have left these pages 10 characters
 * wider than the opinions while looking, in the stylesheet, like it matched.
 * 62ch of this font is 606px, which is 84. Match the measure, not the number. */
.summary-section p {
  font-size: 16px;
  max-width: 62ch;
  color: #2a3038;
  margin-bottom: 14px;
  line-height: 1.7;
}

/* THE SAME 14px WAS DOING THREE JOBS: between paragraphs, between a heading
 * and its own first paragraph, and between one section and the next. A section
 * heading sat exactly equidistant between the section it closed and the one it
 * opened, with only its 1px rule to say which side it belonged to. Privacy has
 * eight sections and eighteen paragraphs; at one spacing value the whole page
 * reads as a single column of undifferentiated prose. */
.summary-section + .summary-section { margin-top: 32px; }

/* Contact and Terms open on a section heading rather than a lead paragraph,
 * which put a 17px semibold serif 6px under a 26px semibold serif and read as
 * a subtitle. Scoped to the adjacency so the archive and hub pages -- which
 * share .archive-title but follow it with .archive-intro -- are untouched. */
.archive-title + .summary-section > h2:first-child { margin-top: 16px; }

.opinion-body { padding-bottom: calc(var(--s7) * 2); }   /* 64, matching .feed .wrap */

/* 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; }

/* The download button at the head of the Full Text panel. It leads the panel,
 * so its top margin is the panel's own padding and only the space beneath it
 * is this rule's business. */
.actions.actions-top { margin: 0 0 20px; }

/* The extraction disclaimer, now at the foot of the page under the citation
 * block. Set apart from .cite-this above it with a rule, so it reads as a note
 * about the whole page rather than as one more line of the citation. */
/* `.panel-text .text-provenance`, and the extra class is load-bearing: this
 * note is a <p> inside .panel-text, so `.panel-text p { margin: 0 0 1.35em }`
 * outranks a bare `.text-provenance` and zeroed its top margin. The 18px this
 * rule used to ask for had never once applied -- the gap on the page was the
 * 8px bottom margin of the last footnote, which is why a horizontal rule sat
 * almost touching the one above it. Second instance of the same specificity
 * loss in this tail; the other is `.opinion .footnotes h2`. */
.panel-text .text-provenance {
  margin: var(--s7) 0 var(--s4);   /* the tail rhythm; see .source-link */
  padding-top: var(--s4);
  border-top: 1px solid var(--line);
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--ink-faint);
  max-width: 68ch;
}

/* ---------- 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;
}

/* --s6, the same separation the opinion pages now use between blocks. This was
 * 22px and .archive-month below was 26px -- the two values the opinion head
 * carried until this pass, still living here. The lede sits above a month
 * heading on an archive page and above a hub's first list, which are the same
 * relationship, so they should not measure differently by page type. Reaches
 * 2,333 pages: the archive years and every patent, judge, subject and holding
 * hub. */
.archive-intro { font-size: 14px; color: var(--ink-soft); margin-bottom: var(--s6); }

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

.archive-list { list-style: none; }
/* 12px, up from 7. The archive is a dense index and should stay one, but 7 was
 * off the scale and made a ~35px row out of a 14px line -- tight for a link
 * you are meant to hit, and visibly tighter than the same list of opinions on
 * the feed. --s3 is the smallest step that reads as deliberate; it is also
 * what the filter bar and the feed item's lower half use. */
.archive-list li {
  padding: var(--s3) 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; }

/* Sits in the tail too, between the source line and the digest card, so it
 * takes the same 32px. It was the last 34 left over. Its heading carries an
 * underline rather than a rule above it, which is why the block itself has no
 * border-top: four rules in this stretch is already the limit. */
.related-docs { margin-top: var(--s7); }
.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: var(--s6);
  padding-bottom: var(--s7);
  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. */

/* CAREFUL: this padding is dead. The only element carrying .subscribe also
 * carries .subscribe-feed, which redeclares padding at the same specificity
 * (0,1,0) and wins on source order -- so the band on the feed is 12/12, not
 * this. .subscribe-doc, the other variant, is on zero pages. Kept on the scale
 * anyway so a future variant that does not override inherits something sane,
 * but do not tune this expecting the feed to move: tune .subscribe-feed. */
.subscribe {
  background: var(--card);
  border-bottom: 1px solid var(--line);
  padding: var(--s6) 0;
}

/* The feed banner is one row: heading, field, button. It was a stacked block —
   heading, a three-line paragraph, then the form — measuring 275px on a
   1280x800 desktop and 421px on a 375x812 phone, which pushed the opinion list
   so far down that one card was visible on desktop and none on mobile.
   The .wrap here becomes the row, so the heading sits beside the field rather
   than above it, and the notes span the full width underneath. */
/* This is the rule that actually sets the feed banner's height -- see the
 * warning on .subscribe above. Was 12/14, asymmetric for no reason. */
.subscribe-feed { padding: var(--s3) 0; }
.subscribe-feed .wrap {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s2, 8px) var(--s4, 16px);
}
.subscribe-feed .subscribe-h {
  margin: 0;
  font-size: 1.02rem;
  white-space: nowrap;
}
/* The form is already a flex row; let it take the remaining width and stop the
   notes from forcing the field onto its own line at desktop widths. */
.subscribe-feed .subscribe-form {
  flex: 1 1 clamp(260px, 40%, 460px);
  align-items: center;
}
.subscribe-feed .subscribe-input { flex: 1 1 180px; }
.subscribe-feed .subscribe-note,
.subscribe-feed .subscribe-premium { flex-basis: 100%; }
.subscribe-feed .subscribe-premium {
  margin: 0;
  padding-top: 0;
  border-top: 0;
}
/* Below the fold-critical width the row stacks, which is correct — a 320px
   phone cannot hold a heading, a field and a button side by side. It is still
   far shorter than the block version because the paragraph is gone. */
@media (max-width: 620px) {
  .subscribe-feed .wrap { gap: var(--s2, 8px); }
  .subscribe-feed .subscribe-h { flex-basis: 100%; }
}
.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; }

/* The detail-page stand-in for the signup form. Opinion pages carry zero
   executable JavaScript, so they get a link to the feed's form rather than the
   form itself — this is a paragraph with an anchor in it, nothing more. */
.subscribe-cta {
  margin-top: var(--s7);      /* the tail rhythm; see .source-link */
  padding-top: var(--s4);
  border-top: 1px solid var(--line);
}
.subscribe-cta p {
  margin: 0;
  font-size: var(--fs-meta);
  color: var(--ink-faint);
}

/* The paid-tier notice. Sits below the form and outside it, separated by a rule
   rather than a box: it is an aside about something that does not exist yet,
   and giving it a card would make it compete with the control that actually
   does something. */
.subscribe-premium {
  margin: var(--s4) 0 0;
  padding-top: var(--s3);
  border-top: 1px solid var(--line);
  font-size: var(--fs-meta);
  color: var(--ink-faint);
}
.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%; }
}

/* --- Full Text / Analysis tabs -------------------------------------------
 *
 * CSS-only. Detail pages carry zero executable JavaScript, so the two radios
 * below ARE the tab state: :checked drives which panel shows and which label
 * reads as selected. A native radio group also brings arrow-key navigation for
 * free, which is the keyboard behaviour ARIA tabs specify and which scripted
 * markup would have to reimplement.
 *
 * The radios are moved off-screen rather than display:none — a hidden input is
 * not focusable, which would make the tabs keyboard-unreachable. */
.tab-radio {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.tab-strip {
  display: flex;
  gap: 4px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 20px;
  /* Sticks while reading a 40-page opinion, so switching view never means
   * scrolling back to the top. position:sticky needs no script. */
  position: sticky;
  top: 0;
  background: var(--paper);
  z-index: 2;
}

.tab {
  font-family: var(--serif);
  font-size: 15px;
  font-weight: 600;
  color: var(--ink-faint);
  padding: 10px 16px;
  cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  user-select: none;
}

.tab:hover { color: var(--ink); }

#tab-text:checked     ~ .tab-strip .tab[for="tab-text"],
#tab-analysis:checked ~ .tab-strip .tab[for="tab-analysis"],
#tab-cite:checked     ~ .tab-strip .tab[for="tab-cite"] {
  color: var(--ink);
  border-bottom-color: var(--edtx);
}

/* Keyboard focus must be visible on the LABEL, since the input itself is
 * off-screen. Without this the tabs are reachable but invisible to tab through.
 *
 * EVERY TAB HAS TO BE NAMED IN BOTH BLOCKS. They are id selectors, so a tab
 * added without touching them is reachable, selectable and completely
 * unmarked — no underline saying which view you are on, and no focus ring at
 * all for anyone navigating by keyboard. That is what adding the citation tab
 * did until this line, and nothing in the test suite looks at it. */
#tab-text:focus-visible     ~ .tab-strip .tab[for="tab-text"],
#tab-analysis:focus-visible ~ .tab-strip .tab[for="tab-analysis"],
#tab-cite:focus-visible     ~ .tab-strip .tab[for="tab-cite"] {
  outline: 2px solid var(--navy);
  outline-offset: -2px;
  border-radius: 3px;
}

.tab-panel { display: none; }
#tab-text:checked     ~ .panel-text,
#tab-analysis:checked ~ .panel-analysis,
#tab-cite:checked     ~ .panel-cite { display: block; }

/* Long-form reading. The measure is capped in ch so it tracks the font rather
 * than the viewport: legal prose at full container width is exhausting, and
 * these documents run to 40 pages. */
.panel-text p {
  font-size: 16px;
  line-height: 1.7;
  color: #2a3038;
  max-width: 68ch;
  margin: 0 0 1.35em;
  text-indent: 1.6em;
}

/* Both signals, deliberately. A gap alone was 1.15em against a 1.7 line height
 * — less than one blank line — so consecutive paragraphs read as one unbroken
 * column and the reader had nothing to aim at. An indent alone would be the
 * printed convention, but paragraph detection here is inferred from wrap width
 * rather than given, so a paragraph that reflow got wrong should still be
 * visibly a paragraph. The indent is what a court opinion looks like on paper;
 * the gap is what makes it scannable on a screen. */

/* Structural lines are set flush: an indent on a heading or a byline reads as
 * damage rather than as typography. Nor after a heading, where the break in
 * the page already says a new section has started. */
/* .panel-text p (0,1,1) outranks .auto-note (0,1,0), so the extraction notice
 * was being set at full body size in the reading panel — the one place it most
 * needs to stay a caption. Same declarations it asks for, at a weight that
 * reaches. */
.panel-text .auto-note { font-size: 12.5px; color: var(--ink-faint); }

.panel-text .auto-note,
.panel-text .text-head,
.panel-text .text-byline,
.case-caption p,
.case-front p,
.curated-summary p { text-indent: 0; }
.text-head + p,
.text-byline + p { text-indent: 0; }

/* Section headings — DISCUSSION, BACKGROUND, CONCLUSION. The court sets these
 * apart; rendered as body text they were invisible, and a 40-page opinion with
 * no visible structure is a wall. */
.panel-text .text-head {
  font-family: var(--serif);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--ink);
  margin: 2em 0 0.9em;
}
.panel-text .text-head:first-child { margin-top: 0; }

/* The authoring judge. This line is where the opinion actually begins, which
 * is worth marking, because everything above it is cover matter. */
.panel-text .text-byline {
  font-family: var(--serif);
  font-weight: 600;
  color: var(--ink);
  margin: 0 0 1.35em;
}

/* Cover matter: court appealed from, decision date, counsel, panel. Hundreds of
 * words of it, and none of it is the opinion. Quieter and tighter, so the eye
 * can skip it and still find it when it is what the reader wants. */
.case-front {
  max-width: 68ch;
  margin: 0 0 22px;
  padding-bottom: 16px;
  border-bottom: 1px solid var(--line);
}
.case-front p {
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--ink-soft);
  margin: 0 0 8px;
}
.case-front p:last-child { margin-bottom: 0; }

.panel-analysis p { font-size: 15px; line-height: 1.65; max-width: 68ch; }
.analysis-soon { color: var(--ink); }


/* A hand-written editorial summary, kept above the full text on the few
 * documents that have one. Set apart so it never reads as the court's words. */
.curated-summary {
  border-left: 3px solid var(--navy);
  padding: 2px 0 2px 16px;
  margin: 0 0 26px;
  max-width: 68ch;
}
.curated-summary h2 { font-size: 15px; margin: 0 0 8px; }

/* Footnotes, lifted out of the page flow and set at the end where they belong.
 * Left inline they interrupt a sentence mid-argument, because the note lives at
 * the foot of whichever page the sentence happened to break across. */
.fn-ref { font-size: 0.72em; line-height: 0; vertical-align: super; }
.fn-ref a { text-decoration: none; padding: 0 1px; }
.footnotes {
  margin-top: var(--s7);      /* the tail rhythm; see .source-link */
  padding-top: var(--s4);
  border-top: 1px solid var(--line);
  max-width: 68ch;
}
/* .opinion, not decoration. The footnotes block sits INSIDE <article
 * class="opinion">, so `.opinion h2` (below) and `.footnotes h2` both match
 * this heading at identical specificity -- one class, one element -- and the
 * later rule won. "Footnotes" was being drawn by the rule meant for the
 * court's own section headings: 16px instead of 15px, and margin-top 1.9em,
 * which opened 47px of nothing between the rule and the word. That gap is the
 * one a reader notices; the wrong size is the one they don't. */
.opinion .footnotes h2 { font-family: var(--serif); font-size: 15px; margin: 0 0 10px; }
.footnotes ol { padding-left: 22px; margin: 0; }
.footnotes li { font-size: 14px; line-height: 1.6; color: var(--ink-soft); margin-bottom: 8px; }
.fn-back { text-decoration: none; margin-right: 4px; }
/* A jumped-to footnote should be obvious, not left for the reader to find. */
.footnotes li:target { background: #fdf6e3; border-radius: 3px; }

/* The case caption: party names, "v.", docket, counsel. A court sets these as
 * short stacked lines, so full paragraph leading turns them into a ladder of
 * disconnected fragments. Tighter, and slightly quieter than the opinion body. */
.case-caption { margin: 0 0 22px; max-width: 68ch; }
.case-caption p {
  margin: 0 0 2px;
  line-height: 1.45;
  font-size: 15px;
  color: var(--ink-soft);
}
.case-caption p:last-child { margin-bottom: 0; }

/* ---------- Opinion text, geometry-aware extractor (pass 4) ---------- */

/* Serif at a ~38em measure. These documents run to fifty pages; a line length
 * tuned for a feed card is punishing over that distance. */
.opinion {
  font-family: var(--serif);
  font-size: 17px;
  line-height: 1.62;
  color: #2a3038;
  max-width: 38em;
  position: relative;
}
.opinion p { margin: 0 0 1.15em; text-indent: 1.6em; }
.opinion h2, .opinion h3 { font-family: var(--serif); margin: 1.9em 0 0.7em; }
.opinion h2 { font-size: 16px; letter-spacing: 0.04em; }
.opinion h3 { font-size: 15.5px; font-weight: 600; }
.opinion h2 + p, .opinion h3 + p, .opinion blockquote + p { text-indent: 0; }

/* The court's own front matter — parties, counsel, panel. Quieter, and never
 * indented: these are stacked lines, not prose. */
.op-front { margin: 0 0 1.6em; padding-bottom: 1.2em; border-bottom: 1px solid var(--line); }
.op-front p { font-size: 13.5px; line-height: 1.5; color: var(--ink-soft); margin: 0 0 6px; text-indent: 0; }
.opinion .byline { font-weight: 600; color: var(--ink); text-indent: 0; }

/* A block quote is set in, tighter, and NOT indented on its first line —
 * the inset is already saying what an indent would say. */
.opinion blockquote {
  margin: 0 0 1.15em 2em;
  font-size: 16px;
  line-height: 1.5;
}
.opinion blockquote p { text-indent: 0; }

/* A list the court set with bullets. list-style:none because the marker the
 * court typed is still the first character of the item — we do not delete the
 * court's glyph so a stylesheet can draw its own. The hanging indent is what
 * makes that read as a list: the marker sits in the gutter and the wrapped
 * lines align under the text, exactly as in the PDF. */
.opinion .op-list, .opinion .op-list ul {
  list-style: none;
  margin: 0 0 1.15em;
  padding: 0 0 0 2em;
}
.opinion .op-list ul { margin: 0.5em 0 0; }
.opinion .op-list li {
  font-size: 16px;
  line-height: 1.7;
  color: #2a3038;
  max-width: 66ch;
  margin: 0 0 0.5em;
  padding-left: 1.4em;
  text-indent: -1.4em;
}
.opinion .op-list li:last-child { margin-bottom: 0; }

/* Star pagination in the gutter, Westlaw-style. Absolute against .opinion so
 * the number sits outside the measure and never interrupts the line. */
.opinion .star {
  position: absolute;
  left: -3.2em;
  font-family: var(--sans);
  font-size: 11px;
  font-weight: 600;
  color: var(--ink-faint);
  text-decoration: none;
  padding: 0 3px;
  /* TEXT-INDENT INHERITS, AND THIS IS A BLOCK BOX. The paragraphs carry a
   * 25.6px first-line indent, so every star inherited it and applied it to
   * its OWN first line: the glyphs were pushed 25.6px right, out of the
   * gutter and against the text they mark, and the shrink-to-fit box grew
   * from 18.6px to 44.2px to hold the indent it would never use. The one
   * star that looked right sat in the opening paragraph, which is the one
   * paragraph with no indent to inherit.
   *
   * FIXED WIDTH, RIGHT ALIGNED, so "*7" and "*247" end at the same place
   * instead of starting at it — a column of page numbers reads as a column
   * only if its right edge is straight. 2.6em clears three digits at this
   * size and leaves 6.6px between the box and the measure. */
  text-indent: 0;
  width: 2.6em;
  text-align: right;
}
.opinion .star::before { content: "*"; opacity: 0.65; }
.opinion .star:hover, .opinion .star:focus-visible { color: var(--navy); }
.opinion .star:target { background: #fdf6e3; border-radius: 3px; }

/* Under 46rem there was no gutter to put it in, so it CAME INLINE — and
 * inline means it sits wherever the word before it ended. Measured on an
 * iPhone SE viewport, eight page numbers in one opinion landed at x = 24, 50,
 * 100, 153, 159, 165, 197 and 299: not page markers any more, just digits
 * loose in the prose. A reader reported it, and the desktop gutter is exactly
 * why it looked wrong — there the same eight all sit at 249.
 *
 * So make the gutter instead of giving it up. 1.9rem is about 26px against an
 * 11px star, wide enough for "*12" and for the four-digit case that does not
 * occur in this corpus. It costs the measure 26px of 327, eight per cent, and
 * that is the honest price of a page number that means anything: a quotation
 * taken off a phone still has to carry the page it came from. */
/* WHERE THE PAGE MARGIN RUNS OUT, PUT THE GUTTER INSIDE THE ARTICLE.
 *
 * The rule above hangs the star 3.2em into the page margin, which only works
 * while there IS one. `.wrap.opinion-body` is 760px centred with 24px of
 * padding, so the article's left offset is max(24, (viewport - 760) / 2) —
 * that reaches the 36px the star needs at a 832px viewport and not before.
 * The breakpoint was 46rem (736px), so between 736 and 832 the number hung
 * off the left edge of the page: measured at 744px, the box started at -11.
 *
 * 56rem = 896px leaves 68px of margin, which is clearance rather than a
 * boundary case. If the measure changes, this number is (760 + 2 * 36) / 16
 * rounded up, and it has to change with it. */
@media (max-width: 56rem) {
  /* The gutter has to clear the star's BOX, not its glyphs: at 1.9rem the
   * number ran to 62 while the text began at 54, so "*3" printed through the
   * "F" of the line it marks. The width and the right alignment come from the
   * base rule; only the position and the size of the gutter change here. */
  .opinion { padding-left: 2.6rem; }
  .opinion .star { left: 0; width: 2rem; padding: 0; }
}

/* Three tabs at the desktop size measure 319px, plus 8px of gaps, inside the
 * 327px an iPhone SE leaves — equal to the pixel, so the last label wrapped
 * and the strip grew to two lines of chrome above the opinion. The labels are
 * the labels; the padding is what gives.
 *
 * Its own breakpoint, and a much narrower one than the gutter above: the strip
 * fits comfortably from about 400px up, and shrinking the tabs on a tablet
 * that has room for them would be fixing a phone's problem everywhere. */
@media (max-width: 26rem) {
  .tab { font-size: 14px; padding: 9px 10px; }
}

/* Citations. A long opinion carries hundreds of these, so styling them like
 * ordinary links would turn a page of judicial prose into a page of blue —
 * the reading experience has to survive the linking. They keep the body
 * colour and carry a faint underline that only asserts itself on hover, which
 * is how a printed reporter marks a cite: visible when you look for it,
 * invisible when you are reading past it.
 *
 * span.cite is the unlinked case: recognised as a citation but not resolvable
 * to a document we can point at. It gets NO visual treatment at all. It is a
 * hook for the citator, not an affordance — a reader must never be invited to
 * click something that does not go anywhere. */
.opinion a.cite {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: var(--line);
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}
.opinion a.cite:hover,
.opinion a.cite:focus-visible {
  color: var(--navy);
  text-decoration-color: currentColor;
}

/* A cite that resolves to an opinion we host. Worth distinguishing — it is the
 * one link on the page that keeps the reader inside the corpus — but only just:
 * a solid rule instead of a faint one, still in the body colour. */
.opinion a.cite-internal { text-decoration-color: var(--ink-faint); }

/* What this opinion decides, above the opinion itself.
 *
 * The body text is the court's and is substantially identical to govinfo's
 * copy of it. This block is the part that is ours — a holding classification,
 * a subject taxonomy, the patents resolved out of the text, the citator's view
 * of what came before and after — and it sits above the text so a reader (and
 * a crawler) meets it first. Every value is a link up to a hub. */
/* Padded like .opinion-meta, because it is the same object: a bordered card in
 * the same column, 24px away. It was inset 14/18 against that one's 16/20. */
.at-issue {
  /* 24 on BOTH sides. The bottom was 0, so this card's border sat flush
   * against the tab strip -- while the 2,541 pages that have no at-issue card
   * put 24px between the metadata card and those same tabs. One boundary,
   * two different gaps, decided by whether the opinion happened to name a
   * patent. Was 1.4em on top: 21.7px, and drifting with the font size. */
  margin: var(--s6) 0;
  padding: var(--s4) var(--s5);
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 8px;
}
.at-issue-row { display: flex; gap: 12px; padding: 5px 0; font-size: 14px; }
.at-issue-row + .at-issue-row { border-top: 1px solid var(--line); }
.at-issue-row .k {
  flex: 0 0 8.5em;
  font: 600 11.5px/1.7 var(--sans);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-faint);
}
.at-issue-row .v { flex: 1; }
.at-issue-row .v a { display: inline-block; margin: 0 0.5em 2px 0; }
.at-issue-row .more { color: var(--ink-faint); font-size: 13px; }

/* Hub index pages: a long list of links with a count beside each. */
.hub-index { list-style: none; margin: 0; padding: 0; columns: 2; column-gap: 34px; }
.hub-index li {
  break-inside: avoid;
  padding: 4px 0;
  border-bottom: 1px solid var(--line);
  font-size: 14px;
  display: flex;
  justify-content: space-between;
  gap: 10px;
}
.hub-count { color: var(--ink-faint); font-variant-numeric: tabular-nums; font-size: 13px; }
@media (max-width: 46rem) {
  .hub-index { columns: 1; }
  .at-issue-row { display: block; }
  .at-issue-row .k { margin-bottom: 2px; }
}

/* A citator section with nothing in it still renders. "Not yet cited" is a
 * real answer to a real question, and a section that appears only sometimes
 * teaches a reader to stop looking for it. */
.citator-empty .citator-note { font-style: italic; }

/* Citator sections — "Cited in N later opinions" and "Authorities". Same
 * shape as .related-docs, because they answer the same kind of question about
 * the document, and a reader should not have to learn two layouts for it. */
/* Dormant -- SHOW_CITATOR is False, so no page carries this class today. Put
 * on the scale anyway: 2.2em is the same em-drift as .at-issue's old 1.4em,
 * and the day the flag flips it would land off the rhythm with everything
 * around it already fixed. */
.citator { margin: var(--s6) 0 0; }
.citator > h2 { font-size: 15px; margin: 0 0 4px; }
.citator-note { font-size: 13px; color: var(--ink-soft); margin: 0 0 10px; }
.citator ul { list-style: none; margin: 0; padding: 0; }
.citator li { padding: 5px 0; border-bottom: 1px solid var(--line); font-size: 14px; }
.citator li:last-child { border-bottom: 0; }
.citator .item-date {
  display: inline-block; min-width: 10.5em;
  font: 12px/1.4 var(--sans); color: var(--ink-faint);
}
.citator-more { font-size: 12.5px; color: var(--ink-faint); margin: 8px 0 0; }

/* The most-cited view. The count leads the row because it is what the page is
 * sorted by — a reader scanning down the left edge should see the ranking. */
.most-cited .cite-count {
  display: inline-block; min-width: 3.2em;
  font: 600 13px/1.4 var(--sans); color: var(--navy);
  font-variant-numeric: tabular-nums;
}
.citator-caveat { color: var(--ink-faint); font-size: 13px; }

@media (max-width: 46rem) {
  .citator .item-date, .most-cited .cite-count { display: block; min-width: 0; }
}

/* Claim-construction charts are real tables in the source and stay tables
 * here. They are wider than the measure, so they scroll in their own box
 * rather than pushing the page sideways. */
.op-table { max-width: 100%; overflow-x: auto; margin: 0 0 1.4em; }
.op-table table { border-collapse: collapse; font-size: 14px; font-family: var(--sans); }
.op-table td {
  border: 1px solid var(--line);
  padding: 6px 9px;
  vertical-align: top;
  min-width: 8em;
}

.opinion .fn-ref { font-size: 0.72em; line-height: 0; vertical-align: super; }
.opinion .fn-ref a { text-decoration: none; padding: 0 1px; }

/* ---------------------------------------------------------------------------
 * PRINT
 *
 * What a printed opinion is for: reading it away from a screen, and quoting it
 * afterwards. Everything that serves navigation on a screen -- masthead,
 * breadcrumb, tabs, download button, hub links -- is dead ink on paper, and
 * every hub module is a list of links to pages the reader cannot reach.
 *
 * What survives is the document: title, the identifying metadata, the court's
 * text, its footnotes, the star pagination, and the citation. The star numbers
 * stay in the MARGIN, which is the whole reason they are worth printing: a
 * quotation taken off paper still has to carry the page it came from.
 *
 * The rule this replaces was `.tab-panel { display: block !important }`, which
 * un-hid BOTH panels -- so every opinion printed since the tabs landed has
 * ended with "Analysis is coming soon.", a placeholder for a feature that does
 * not exist, stapled to a judicial opinion.
 * ------------------------------------------------------------------------- */
@media print {
  /* A left gutter wide enough for star pagination to sit outside the measure. */
  @page { margin: 18mm 16mm 18mm 26mm; }

  /* Navigation and hub modules: neither is reachable from paper. */
  .masthead, .breadcrumb, .tab-strip, .doc-tabs > .tab-radio,
  .ribbons, .at-issue, .citator, .related-docs,
  .subscribe-cta, .site-footer, .actions, .filters { display: none; }

  /* The text and the citation. Analysis is a placeholder and must never
   * print. The citation moved INTO a tab of its own, and a tab panel is
   * display:none until its radio is checked — so without naming it here, the
   * one thing a printed opinion is most needed for would come out blank. That
   * is the same failure as the rule this block replaced, which un-hid every
   * panel and stapled "Analysis is coming soon." to a judicial opinion; the
   * lesson is that panels have to be listed, not swept. */
  .panel-analysis { display: none !important; }
  .panel-text, .panel-cite { display: block !important; }

  body { background: #fff; color: #000; }
  .wrap, .opinion-body { max-width: none; margin: 0; padding: 0; }

  .opinion-title { font-size: 15pt; line-height: 1.25; margin: 0 0 6pt; }
  .venue-badge { font-size: 8pt; }
  /* border-radius must be zeroed with the border. Kept, a 2pt left rule on a
     6px-radius box renders as a parenthesis sitting in front of the holding. */
  .holding-banner { border: 0; border-left: 2pt solid #000; border-radius: 0;
                    padding: 2pt 0 2pt 8pt; background: none; font-size: 10pt; }
  .opinion-meta { font-size: 9.5pt; }
  /* Tinted panels cost toner and say nothing on paper that the words do not. */
  .auto-note, .opinion-meta { background: none; border-color: #999; }

  .panel-text p { font-size: 11pt; line-height: 1.5; max-width: none; }
  /* Same size as the body it sits in. An item is prose the court numbered,
     not a caption, and a list left at its screen size prints visibly larger
     than the paragraph above it. */
  .opinion .op-list li { font-size: 11pt; line-height: 1.5; max-width: none; }
  .opinion .op-list li { break-inside: avoid; page-break-inside: avoid; }
  /* The court's own section headings. This rule used to target `.text-head`,
     a class the legacy text pipeline emitted and the geometry extractor never
     did -- it matched nothing on any of 6,699 pages, so section headings have
     been printing at their screen size since the rewrite. */
  .opinion h2, .opinion h3 { font-size: 11pt; margin: 1.4em 0 0.6em; }
  .case-front p { font-size: 9.5pt; }
  .footnotes li { font-size: 9pt; }

  /* Star pagination keeps its gutter position; on screen a narrow viewport
     pushes it inline, and paper is never narrow. Stated explicitly so the
     46rem inline fallback cannot win by media-query order. */
  .opinion { position: relative; }
  .opinion .star {
    position: absolute; left: -18mm; margin-right: 0;
    font-size: 8pt; color: #444;
  }

  /* Links are dead on paper. An opinion carries hundreds of citation anchors,
     so they must read as plain text -- and their URLs must NOT be expanded
     after them, which would put a govinfo address inside every third sentence
     of the court's prose. */
  .opinion a, .opinion a.cite, .opinion a.patent { color: #000; text-decoration: none; }
  .source-link a { color: #000; text-decoration: none; }

  /* Do not strand a heading at the foot of a page, or split a quotation. */
  .opinion h2, .opinion h3, .cite-this h2 { break-after: avoid; page-break-after: avoid; }
  .opinion blockquote, .cite-this { break-inside: avoid; page-break-inside: avoid; }
  .opinion p { orphans: 3; widows: 3; }
}
