/* ==========================================================================
   inbefore — PHP port shims
   --------------------------------------------------------------------------
   theme-system.css is a verbatim copy of the frontend prototype's design
   system. These are the few pieces the theme templates need that the prototype
   never had, because the prototype renders them from JavaScript or not at all:
   the site logo, the page shells' spacing wrappers, breadcrumb lists emitted by
   breadcrumb_render(), the footer locale switcher and the admin-only controls.

   Loaded last, so a rule here always wins over theme-system.css.
   ========================================================================== */

/* 01. Header -------------------------------------------------------------- */

.brand-logo {
  display: block;
  max-height: 34px;
  width: auto;
}

/* Theme toggle: both icons are in the markup, CSS picks one. */
.icon-dark,
.icon-light {
  display: inline-flex;
  align-items: center;
}

.icon-light,
[data-theme="dark"] .icon-dark {
  display: none;
}

[data-theme="dark"] .icon-light {
  display: inline-flex;
}

/* 02. Card reset ---------------------------------------------------------- */

/* Bootstrap 4 paints .card white with a 1px border and a .25rem radius. The
   design system keeps cards transparent and styles .card__media instead, so
   without this the card bodies stay white in dark mode and titles vanish. */
.card {
  background: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}

/* 03. Page shells --------------------------------------------------------- */

.ad-top {
  padding-top: 26px;
}

.hero-wrap {
  padding-top: 26px;
}

.section--topics {
  padding-bottom: 46px;
}

/* 03. Topic columns ------------------------------------------------------- */

/* The prototype used an inline style here; a class is easier to keep. */
.topic-item__body {
  min-width: 0;
}

/* Source cards: the prototype used an inline style for the name block and put
   the follow button in the foot; keeping the button in its own row keeps the
   layout stable when the label changes to "Following". */
.src-card__body {
  min-width: 0;
}

.src-card__actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 4px;
}

.src-card__actions .btn {
  margin-left: auto;
}

.profile__logo-wrap {
  flex: none;
}

.follow-btn--lg {
  height: 40px;
  padding-inline: 18px;
  font-size: 14px;
  flex: none;
}

/* 04. Sidebar ------------------------------------------------------------- */

/* Sidebar search box: full width of the widget, icon-only submit. */
.search--widget {
  max-width: none;
}

.search--widget .search__field {
  padding-right: 4px;
}

.search__submit--icon {
  padding-inline: 13px;
}

.search__submit--icon span {
  display: none;
}

.widget__foot {
  /* The button under a list widget ("All sources"). The body above it already
     contributes its own 16px, so these are the gaps left around the button
     itself: a little air between the last row and the button, and enough room
     under it that it never reads as pressed against the widget's edge. */
  padding: 6px 18px 20px;
}

.btn--block {
  width: 100%;
  justify-content: center;
}

/* 05. Breadcrumbs --------------------------------------------------------- */

/* breadcrumb_render() emits an <ol> of <li> items inside the .crumbs nav. */
.crumbs__list {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  list-style: none;
  margin: 0;
  padding: 0;
}

.crumbs__list li {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.crumbs__list li:not(:last-child)::after {
  content: "/";
  color: var(--muted-2);
  opacity: 0.6;
}

/* 06. Footer -------------------------------------------------------------- */

.footer-locales {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}

.footer-locales a {
  color: inherit;
  opacity: 0.75;
}

.footer-locales a.is-active {
  opacity: 1;
  font-weight: 600;
}

.locale-icon {
  width: 16px;
  height: 12px;
  object-fit: cover;
  vertical-align: middle;
  margin-right: 5px;
}

/* 07. Follow button ------------------------------------------------------ */

/* Both icons are in the markup, aria-pressed picks one. */
.follow-icon {
  display: inline-flex;
  align-items: center;
}

.follow-icon--done,
.follow-btn[aria-pressed="true"] .follow-icon--add {
  display: none;
}

.follow-btn[aria-pressed="true"] .follow-icon--done {
  display: inline-flex;
}

/* 08. Engagement states --------------------------------------------------- */

.follow-btn.is-active,
.like-btn.is-active {
  background: var(--brand-soft);
  border-color: var(--brand);
  color: var(--brand);
}

.is-busy,
.follow-btn:disabled,
.like-btn:disabled,
.share-btn:disabled {
  opacity: 0.6;
  cursor: progress;
  pointer-events: none;
}

/* 09. Bootstrap leftovers ------------------------------------------------- */

/* The layout no longer uses Bootstrap's grid, but the dashboard shortcuts and
   a few theme partials still do. Keep container gutters sane under the new
   max width. */
.container {
  padding-inline: var(--gutter);
}

@media (max-width: 720px) {
  .container {
    padding-inline: 16px;
  }
}

/* 10. Monogram fallbacks -------------------------------------------------- */

/* inb_feed_mark() emits an <img> when a source has a logo and a monogram span
   when it does not. The monogram is sized for the chip it originally came
   from, so match it to the slot it actually lands in. */
/* body classes vs component classes --------------------------------------- */

/* Several templates set body_class to the page name, and one of those names
   (`search`) is also a component class. Two problems follow, both fixed by
   the more specific `body.search` selector printed last:

   - the component's max-width lands on <body>, rendering the page 560px wide;
   - the old theme hard-codes `body.search { background: #fff }` (and #fafafa
     under 992px) for its own search screen, which ignores dark mode. */
body.search {
  max-width: none;
  background: var(--bg);
}

/* 11. Search page --------------------------------------------------------- */

/* The hero and chips carry the page; the results themselves are Google CSE
   markup styled from search.php, since those class names cannot be reached
   from here without a stylesheet of `!important` overrides. */

/* The search landing puts its field directly under the hero, and the hero ends
   on a hairline border. Measured on the live page, the gap between the hero's
   bottom edge and the top of the form was 0px — the site's most important input
   was welded to a dividing line, with nothing between them.

   Every other page gets this separation for free from `.layout`, which carries
   28px of padding-top. The landing page has no `.layout` — its hero is followed
   by a bare `.section` with no padding at all — so it had nothing.

   40px rather than the standard 28: the field is the entire point of this page,
   and the extra air is what makes it read as the focal element instead of as
   the first row of a list. Scoped to the landing body class so the results page,
   which does use `.layout`, keeps its own spacing. */
body.search-landing .container > .section {
  padding-top: 40px;
}

.search--hero {
  max-width: 720px;
  margin: 0 auto;
}

.search--hero .search__field {
  height: 54px;
  padding: 0 8px 0 18px;
}

.search--hero .search__input {
  font-size: 16px;
}

/* Only the desktop button keeps the wide labelled treatment. The base sheet's
   ≤720px rule turns .search__submit into a 32px circle, and a media query adds
   no specificity — so a bare .search--hero rule here would out-specify it and
   leave the button as an empty pink pill. */
@media (min-width: 721px) {
  .search--hero .search__submit {
    height: 38px;
    padding: 0 20px;
    font-size: 14px;
  }
}

.search .chip-row {
  margin: 18px 0 0;
}

/* Landing keeps the engine chips under the centred field. */
.search--hero ~ .chip-row {
  justify-content: center;
  margin-top: 24px;
}

/* A chip row that *leads* a results section: no top margin (the section above
   already spaced it) and 22px below before the results start.

   `:first-child` is doing real work here, not decoration. Without it this rule
   also matched the search landing page, where the engine chips sit *under* the
   search field. It is more specific than the `.search--hero ~ .chip-row` rule
   above, so it won and forced that row's margin-top to 0 — leaving the chips
   welded to the bottom of the search field, exactly the way the field used to
   be welded to the hero. Measured on the live page: 0px between them. On the
   results page the chip row does lead the section, so this is where the rule
   belongs. */
.search .section > .chip-row:first-child {
  margin: 0 0 22px;
}

/* Engine pills are links, so they key off aria-current rather than the
   pressed state .chip uses elsewhere. */
.chip[aria-current="page"] {
  background: var(--text-strong);
  border-color: var(--text-strong);
  color: var(--bg);
  font-weight: 600;
}

.result-favicon {
  display: inline-block;
  vertical-align: -2px;
  margin-right: 6px;
  border-radius: 3px;
}

/* 12. Instant answer (instant-answer sidebar widget) --------------------- */

.answer .widget__body {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.answer__image {
  width: 100%;
  max-height: 190px;
  object-fit: cover;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
}

.answer__abstract {
  margin: 0;
  font-size: 14.5px;
  line-height: 1.62;
  color: var(--text);
}

.answer__source {
  font-weight: 600;
  color: var(--brand);
  white-space: nowrap;
}

.answer__facts {
  display: flex;
  flex-direction: column;
  margin: 0;
  border-top: 1px solid var(--line);
}

.answer__fact,
.answer__source-line {
  display: flex;
  gap: 12px;
  padding: 9px 0;
  border-bottom: 1px solid var(--line);
  font-size: 13.5px;
}

.answer__fact dt,
.answer__source-line dt {
  flex: 0 0 40%;
  color: var(--muted-2);
}

.answer__fact dd,
.answer__source-line dd {
  margin: 0;
  min-width: 0;
  color: var(--text-strong);
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.answer__fact.is-extra {
  display: none;
}

.answer__facts.is-open .answer__fact.is-extra {
  display: flex;
}

.answer__toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: 100%;
  padding: 9px 0;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--muted);
  font-size: 13px;
  font-weight: 600;
}

.answer__toggle:hover {
  color: var(--text-strong);
  border-color: var(--line-strong);
}

.answer__toggle svg {
  width: 15px;
  height: 15px;
  transition: transform 0.16s var(--ease);
}

.answer__toggle.upside-down svg {
  transform: rotate(-90deg);
}

.answer__links {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.icon-btn--sm {
  width: 34px;
  height: 34px;
  border-color: var(--line);
}

.icon-btn--sm svg {
  width: 16px;
  height: 16px;
}

/* Same specificity as .source-chip__mono in theme-system.css, but printed
   later, so the slot wins. */
.src-strip__logo {
  width: 26px;
  height: 26px;
  border-radius: 6px;
  font-size: 11px;
}

.src-card__logo {
  width: 44px;
  height: 44px;
  border-radius: var(--radius-sm);
  font-size: 15px;
}

/* 13. Search widget, moved to the main column ----------------------------- */

/* Same widget the sidebar used to carry, now the first thing in the main
   column, so it should read as the head of the listing it searches. The field
   gets more room than the 324px sidebar column allowed, and .section + .section
   already supplies the gap to the feed below. */
.widget--search .search--widget {
  max-width: 680px;
}

/* 14. Pagination ---------------------------------------------------------- */

/* Pagination::renderHtml() emits Bootstrap's shape — ul.pagination >
   li.page-item > a.page-link — which matches neither the design system's
   `.pagination a` nor its `[aria-current="page"]` hook for the current page.
   theme-modern.css had patched both, but its `.pagination .active > a`
   (background: brand) lost the colour to its own
   `.pagination .page-item .page-link` (color: text), so the current page
   rendered dark-on-brand. Same specificity as that rule, printed later, so
   this sheet owns pagination outright now. */
.pagination .page-item > .page-link {
  min-width: 40px;
  height: 40px;
  padding: 0 12px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--muted);
  font-size: 14px;
  font-weight: 500;
}

.pagination .page-item > .page-link:hover {
  background: var(--surface-2);
  border-color: var(--line-strong);
  color: var(--text-strong);
}

.pagination .page-item.active > .page-link {
  background: var(--brand);
  border-color: var(--brand);
  color: var(--brand-ink);
  font-weight: 700;
}

.pagination .page-item.disabled > .page-link {
  opacity: 0.45;
}

/* .load-more already spaces its children, and .pagination adds its own top
   margin on top of that — two rules that only ever met here. */
.load-more .pagination {
  margin-top: 0;
}

/* The note beside the loader is the small print of the listing, not a
   paragraph: the prototype sets it to 12.5px inline. */
.load-more .text-muted {
  font-size: 12.5px;
}

/* 15. Header autocomplete dropdown ---------------------------------------- */

/* jquery.auto-complete appends its menu to <body>, so it sits outside the
   header and picks up nothing from here. The only sheet that styles it is the
   legacy styles.css, which paints it #fff with square corners — a bright white
   slab in dark mode. Restyled as .suggest, the panel the prototype builds for
   the same job. */
.autocomplete-suggestions {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  overflow-x: hidden;
  overflow-y: auto;
  padding: 6px;
}

.autocomplete-suggestion {
  padding: 9px 12px;
  border-radius: var(--radius-sm);
  font-size: 14.5px;
  line-height: 1.3;
  color: var(--text);
}

.autocomplete-suggestion b {
  font-weight: 700;
  color: var(--text-strong);
}

.autocomplete-suggestion.selected {
  background: var(--surface-2);
  color: var(--text-strong);
}

.autocomplete-suggestion.selected b {
  color: var(--text-strong);
}

/* Anchoring. The vendor script sizes the panel to the <input> and left-aligns
   it to the input, so in the header it floated between the magnifier and the
   submit button — a little narrower than the pill it belongs to, and starting
   to the right of the pill's left edge. On phones the vendor sheet then forces
   it to 100% of the viewport, which pushed it off the right edge entirely.

   The panel belongs to the header search field, so the script hands us that
   field's rect and these two declarations pin the panel to it. The values ride
   in custom properties rather than being written straight onto the element,
   because the vendor keeps calling .css() on the very same properties and an
   inline style (even an inline !important) is its to overwrite. It never
   touches custom properties, so nothing here can be clobbered. Left and width
   only: the vertical position it computes is already right, since the panel is
   appended to <body> and measured in document coordinates. */
.suggest--field {
  left: var(--suggest-left) !important;
  width: var(--suggest-width) !important;
  /* Top too: the script hangs the panel off the bottom of the <input>, and the
     input is shorter than the pill it sits in, so the first row of suggestions
     was tucked behind the pill's lower edge. */
  top: var(--suggest-top) !important;
}

/* 15b. The hero lead card is a link target ---------------------------------- */

/* The design system's stretched-link contract — one title link stretched over
   the whole card through .card__title a::after — expects the card to be the
   overlay's containing block. The ported hero emits .hero__lead with the card's
   inner parts but without .card itself, so the overlay resolved against the
   viewport instead: a link as tall and wide as the window, sitting on top of
   the header and every control on the page. A tap on the theme switch opened
   the lead story. Same rule as .card's position in the design system. */
.hero__lead {
  position: relative;
}

/* 16. Breadcrumb separators ----------------------------------------------- */

/* breadcrumb_render() emits <li class="breadcrumb-item">, and Bootstrap ships
   a "›" between .breadcrumb-item siblings. The .crumbs shell below draws its
   own "/", so the two were rendering side by side ("Local / › Politics").
   Drop Bootstrap's inside that shell. */
.crumbs__list .breadcrumb-item + .breadcrumb-item::before {
  content: none;
}

/* page.php and pages/contact-us.php still render the breadcrumb through
   Bootstrap's markup (ol.breadcrumb) rather than the .crumbs shell the
   article templates use. Same tokens either way, so the two agree. */
.breadcrumb {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
  background: none;
  font-size: 13px;
  color: var(--muted-2);
}

.breadcrumb .breadcrumb-item {
  display: inline-flex;
  align-items: center;
}

.breadcrumb .breadcrumb-item + .breadcrumb-item::before {
  content: "/";
  padding: 0 8px 0 0;
  color: var(--muted-2);
  opacity: 0.6;
}

.breadcrumb a {
  color: inherit;
}

.breadcrumb a:hover {
  color: var(--brand);
}

.breadcrumb .breadcrumb-item.active {
  color: var(--text);
}

/* 17. Reboot's element defaults ------------------------------------------- */

/* theme-system.css opens with the prototype's reset, `* { margin: 0;
   padding: 0 }` — specificity (0,0,0). Bootstrap's Reboot styles the same
   elements by tag name, so wherever the two overlap Reboot wins whatever the
   load order: `h1..h6 { margin-bottom: .5rem }`, `p { margin-bottom: 1rem }`
   and `ul, ol, dl { margin-bottom: 1rem }` all still applied to the ported
   markup. That is the 8px under every card title, the 16px under every card
   excerpt, the 8px that made a section head 32px tall instead of 24px, and the
   16px under the trending list — none of which the prototype has. Restate the
   reset at tag specificity, after Reboot. The design system's own rules are
   class-based, so they still win over this. */
h1,
h2,
h3,
h4,
h5,
h6,
p,
ul,
ol,
dl,
pre,
blockquote,
figure {
  margin: 0;
}

/* Two places leaned on Reboot for rhythm and have no prototype equivalent to
   copy: the static pages (page.php, pages/contact-us.php) and any article
   content that nests its paragraphs in a wrapper. The article body already
   spaces its own children; this keeps those fallbacks spaced at the same
   value after the paragraph margin goes away. */
.page-content > * + *,
.page-content p + p {
  margin-top: 1.1em;
}

.article__body p + p {
  margin-top: 1.15em;
}

/* 18. Vertical rhythm between blocks --------------------------------------- */

/* Two spots where a block follows something that is not a `.section`, so the
   design system's `.section + .section` never fires and the two sat flush:

   - the source profile card under the breadcrumb — the prototype carries it in
     a wrapper with `margin-block: 16px 26px`;
   - the discovery sections under an article — the prototype puts 48px on the
     first one (the comment thread sits between them when it is enabled). */
.profile {
  margin-block: 16px 26px;
}

.article + .section,
.comments + .section {
  margin-top: 48px;
}

/* 19. Link hover ----------------------------------------------------------- */

/* styles.css carries Reboot plus the old theme, and the latter closes with
   `a:hover { color: #e50132; text-decoration: underline }`. That single rule did
   two things the design system never asks for:

   - it underlined every link on hover. The design system signals the pointer
     with colour and surface changes, and the prototype has no hover underline
     anywhere;
   - at (0,1,1) it outranks every component class at (0,1,0), so it also
     repainted the label of a plain .btn or a .section-head__link in that legacy
     red instead of the colour the design system gives it.

   Both are neutralised below. Hover is a colour change. */
a:hover,
a:focus,
a:active {
  text-decoration: none;
}

/* Plain inline links — the ones with no class of their own — take the brand
   colour. Class-based links are components and keep their own colour, so a
   .btn--brand label can never end up brand-coloured on its own brand fill. */
a:not([class]):hover,
a:not([class]):focus {
  color: var(--brand);
}

/* Prose keeps its underline in every state: that is a reading convention, not
   a hover effect. */
.article__body a:hover,
.article__body a:focus,
.page-content a:hover,
.page-content a:focus {
  text-decoration: underline;
}

/* The link components that declare a colour but no hover colour of their own —
   this is where the legacy red used to show through. The rest of the set
   (.cat-pill, .chip, .icon-btn, .share-btn, .topic__more, .pagination a) states
   its hover colour in the design system already. */
a.btn:hover {
  color: var(--text-strong);
}

/* .btn.is-done states its colour at a higher specificity than a.btn:hover. */
a.btn.is-done:hover {
  color: var(--muted-2);
}

a.btn--brand:hover {
  color: var(--brand-ink);
}

a.section-head__link:hover,
a.article__cat:hover,
a.answer__source:hover {
  color: var(--brand-hover);
}

a.card__cat:hover {
  color: var(--text-strong);
}

/* No text of their own — every child paints its own colour — but a
   currentColor icon would otherwise pick the legacy red up. */
a.brand:hover,
a.source-chip:hover,
a.src-strip__row:hover {
  color: inherit;
}

/* 20. Category rail -------------------------------------------------------- */

/* The prototype fills the current pill with brand. On the live site that put a
   solid red pill beside the red wordmark in the same 68px bar and the rail read
   as one loud band, so the current category is carried by colour alone. */
.cat-pill[aria-current="page"] {
  background: none;
  color: var(--brand);
  font-weight: 600;
}

.cat-pill[aria-current="page"]:hover {
  background: var(--surface-2);
  color: var(--brand-hover);
}

/* 21. read-original mark --------------------------------------------------- */

/* inb_feed_mark() falls back to a monogram <span> when a publisher has no
   logo, and the prototype's .read-original__logo only describes the <img>. */
span.read-original__logo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 15px;
  font-weight: 700;
  color: #fff;
}

/* 22. Sidebar sticky bounds ------------------------------------------------ */

/* The prototype's rule (position: sticky; top: header + 8px) assumes a sidebar
   shorter than the viewport. Ours is not always: the full stack on a category
   page is taller than a laptop screen, so the widgets below the fold could
   never be reached and the stuck column ran down over the footer. Capping the
   height at what is actually left below the header keeps it stuck where it
   belongs and lets the stack scroll inside itself when it has to.

   No bar on the column, though (see §33): `scrollbar-width: thin` used to sit
   here, and Blink reads a non-`none` value as an instruction that outranks the
   `::-webkit-scrollbar` rule §33 hides everything with. So the moment the
   instant-answer panel was taller than the cap, an 11px scrollbar was drawn
   down the right edge of the knowledge panel and 11px of the panel's width
   went with it. The column still scrolls exactly as before. */
.sidebar {
  align-self: start;
  max-height: calc(100vh - var(--header-h) - 24px);
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* Nothing in the stack may shrink. A flex column squeezes its children before
   it will scroll, and a squeezed widget is a clipped widget — .widget hides its
   own overflow, so the last rows and the "All sources" button underneath them
   were being cut in half rather than pushed into the scrollbar the cap was
   meant to introduce. touch and narrow screens never hit this (no cap below),
   but the rule is harmless there and keeps the fix in one place. */
.sidebar > * {
  flex: 0 0 auto;
}

/* Touch and narrow screens: no sticking at all, and one widget per row so the
   stack reads as a normal continuation of the page. The 1080px breakpoint is
   where .layout collapses to a single column. */
@media (max-width: 1080px) {
  .sidebar {
    position: static;
    max-height: none;
    overflow: visible;
  }
}

@media (max-width: 720px) {
  .sidebar {
    grid-template-columns: minmax(0, 1fr);
    gap: 18px;
  }
}

/* 23. Header account menu ------------------------------------------------- */

/* The prototype's .account block, extended for the pieces it never had: a real
   avatar image, an icon-only avatar for a guest, and a sign-out row that has to
   be a <button> inside a form. */
.account__avatar {
  overflow: hidden;
}

.account__avatar--img {
  display: block;
  object-fit: cover;
  padding: 0;
  background: var(--surface-2);
}

.account__avatar svg {
  width: 16px;
  height: 16px;
}

/* One row, one size.

   The design system draws this button 38px tall with asymmetric padding,
   because the prototype's version held an avatar *and* the reader's name. Here
   it holds only the avatar, which is itself 40px — so the button's box was two
   pixels shorter than the thing inside it, and the whole control sat a size off
   beside the RSS and theme circles that are 40px squares. It is a 40px circle
   now, matching them on every axis: same width, same height, same centre line.
   The avatar is inset by the 1px ring so the border stays visible around it. */
.header-actions .account__btn {
  width: 40px;
  height: 40px;
  padding: 0;
  gap: 0;
  justify-content: center;
}

.header-actions .account__btn .account__avatar {
  width: 38px;
  height: 38px;
}

/* The guest avatar is an icon sitting in an icon cluster, so its glyph is the
   20px the neighbouring icons use rather than the 16px the old 30px avatar was
   tuned for. A signed-in reader gets a photo or initials instead, which is not
   a glyph and is unaffected. */
.header-actions .account__btn .account__avatar svg {
  width: 20px;
  height: 20px;
}

/* Phones are the one place the icons are not 40px: the design system takes them
   to 36px at 720px, so this button has to come down with them or the very
   mismatch being fixed reappears on a phone. The avatar keeps its 1px inset. */
@media (max-width: 720px) {
  .header-actions .account__btn {
    width: 36px;
    height: 36px;
  }

  .header-actions .account__btn .account__avatar {
    width: 34px;
    height: 34px;
  }
}

.account__mail {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.account__form {
  margin: 0;
}

/* It is a <button>, so it needs everything the anchor rows get for free. */
.account__link--btn {
  width: 100%;
  border: 0;
  background: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.account__foot {
  padding: 8px 6px 6px;
  border-top: 1px solid var(--line);
  margin-top: 6px;
}

@media (max-width: 720px) {
  /* Narrow screens: the panel keeps hugging the avatar, but it can no longer
     assume 232px of room to its left is free. */
  .account__menu {
    min-width: 0;
    width: min(240px, calc(100vw - 32px));
  }
}

@media (max-width: 360px) {
  /* The prototype hides the whole action cluster on the narrowest phones,
     which would take the avatar — and with it the only way to sign in or reach
     a following list a guest can build up — off the screen. The header wraps
     the search box onto its own row here, so brand and icons still fit side by
     side; they just sit shoulder to shoulder. */
  .header-actions {
    display: flex;
    gap: 0;
  }
}

/* 24. Topic-head rule ------------------------------------------------------ */

/* The prototype draws 2px of --rule-strong under a topic head. Next to the 1px
   --line hairline the section heads use, the topic columns ended up reading
   heavier than the sections they sit inside, and in light mode oklch(0.8) is a
   mid grey that competes with the headlines below it. One hairline, the same
   weight and colour as every other rule on the page. --rule-strong now has no
   users left; it stays declared in the design system so the sheet and the
   prototype do not drift. */
.topic__head {
  border-bottom-width: 1px;
  border-bottom-color: var(--line);
}

/* 25. Page hero meta ------------------------------------------------------- */

/* The prototype's .page-hero__meta row holds short count spans. The category
   description rides in it too, and a sentence or two wants a readable measure
   rather than a 1100px line. Short spans are unaffected. */
.page-hero__meta > span {
  max-width: 72ch;
  line-height: 1.55;
}

/* 26. Footer on phones ----------------------------------------------------- */

/* Phones get their own footer order. A single stacked column pushed the page
   links right above the copyright and left the two short link groups (two or
   three items each) alone on full width rows. Instead: the brand keeps a full
   row, Categories and Discover share the next one, the page links take the row
   after them, and the copyright is centred under everything. The DOM order in
   the footer partial already matches, so only the grid needs redrawing. */
@media (max-width: 720px) {
  .footer-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 24px 20px;
  }

  .footer-col--brand,
  .footer-col--about {
    grid-column: 1 / -1;
  }

  /* Page links are the whole column here: the "About" heading is dropped. */
  .footer-col--about .footer-title {
    display: none;
  }

  .footer-bottom {
    flex-direction: column;
    align-items: center;
    text-align: center;
  }
}

/* 27. Topic-item thumbnails ------------------------------------------------ */

/* The topic columns are headline lists: a source mark, the headline, its time,
   and now the story's own picture on the right. Small and fixed at 4:3 (72x54)
   so a column of four rows still reads as a list of headlines rather than a
   stack of cards.

   The body is the flex item that grows, which is what pins the picture to the
   right edge of the column instead of letting it sit right after the headline. */
.topic-item__body {
  flex: 1;
}

.topic-item__thumb {
  flex: none;
  width: 72px;
  height: 54px;
  object-fit: cover;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  background: var(--surface-2);
}

/* Narrowest columns (a 320px phone leaves about 288px for a whole column):
   the headline keeps its room and the picture gives some up. */
@media (max-width: 480px) {
  .topic-item__thumb {
    width: 58px;
    height: 44px;
  }
}

/* 28. Tall sidebars: publisher and for-you pages ---------------------------- */

/* The §22 cap fits an article or a category page, where the stack is short
   enough to follow the reader. A publisher page is a hundred imported posts
   long, and its stack is not: capped and stuck, the widgets below the fold
   could only be reached through a scrollbar inside the column, which reads as
   the list being cut off.

   So the column itself is no longer the sticky thing here. It takes the full
   height of the listing beside it (invisible — only .widget boxes paint), the
   widgets draw in full with nothing to scroll, and it is the last widget that
   stays with the reader: it scrolls in normally, then holds at the header line
   for as long as the listing lasts. Stretching the column is what makes that
   work at all — a sticky child can never outlive its container, so with
   align-self: start the widget was dragged off screen with the column. The
   desktop-only guard is deliberate: below 1080px the column turns into a grid
   and follows the listing in normal flow. */
/* "For you" wants the same treatment for a different reason: it carries two
   lists the reader deliberately asked to see whole — the publishers they follow
   and the most read ranking — and the follow list grows by a row every time
   they add a publisher, so the §22 cap would start clipping it again on the day
   they follow their sixth source. The listing beside it is long (a paginated
   feed plus the suggestion grid), so the column has height to spare and the
   last widget can hold at the header line. */
@media (min-width: 1081px) {
  .source-page .sidebar,
  .for-you .sidebar {
    position: static;
    align-self: stretch;
    max-height: none;
    overflow: visible;
  }

  /* The last widget, whatever the caller renders after it. */
  .source-page .sidebar > .widget:not(:has(~ .widget)),
  .for-you .sidebar > .widget:not(:has(~ .widget)) {
    position: sticky;
    top: calc(var(--header-h) + 8px);
  }
}

/* 29. The vendor Bootstrap brand ------------------------------------------- */

/* styles.css is the vendor's Bootstrap 4 build and brings its own crimson
   brand with it: .btn-primary is #fe3560, .btn-outline-primary builds on the
   same value, and a bare `color: #fe3560` is what plain links inherit. The
   design system above re-points almost everything at the tokens, but the
   classes it never had a reason to redefine kept the old colour — the comment
   forms and the contact page emit plain Bootstrap buttons, so they stayed red
   while the rest of the site went green.

   The ported templates now use .btn--brand, and this shim catches anything
   else that ever asks for a Bootstrap primary — a plugin, a page template, a
   theme clone. It is the same colour the tokens declare, so there is nothing
   to undefine here. */
.btn-primary,
.btn-primary.disabled,
.btn-primary:disabled,
.btn-primary:not(:disabled):not(.disabled):active,
.btn-primary:not(:disabled):not(.disabled).active,
.show > .btn-primary.dropdown-toggle {
  background: var(--brand);
  border-color: var(--brand);
  color: var(--brand-ink);
  box-shadow: none;
}

.btn-primary:hover,
.btn-primary:focus,
.btn-primary.focus {
  background: var(--brand-hover);
  border-color: var(--brand-hover);
  color: var(--brand-ink);
  box-shadow: none;
}

.btn-primary:not(:disabled):not(.disabled):active:focus {
  box-shadow: none;
}

.btn-outline-primary {
  border-color: var(--brand);
  color: var(--brand);
  background: none;
}

.btn-outline-primary:hover,
.btn-outline-primary:focus,
.btn-outline-primary.focus {
  background: var(--brand);
  border-color: var(--brand);
  color: var(--brand-ink);
}

.btn-link,
.btn-link:hover,
.btn-link:focus {
  color: var(--brand);
}

.text-primary {
  color: var(--brand);
}

.badge-primary,
.bg-primary {
  background: var(--brand-soft);
  color: var(--brand);
}

/* Checkboxes and radios, which the comment and contact forms inherit from
   Bootstrap's custom-controls. */
.custom-control-input:checked ~ .custom-control-label::before,
.custom-radio .custom-control-input:checked ~ .custom-control-label::before,
.custom-checkbox .custom-control-input:checked ~ .custom-control-label::before {
  background-color: var(--brand);
  border-color: var(--brand);
}

.custom-control-input:focus ~ .custom-control-label::before {
  box-shadow: 0 0 0 3px var(--brand-soft);
}

.form-control:focus {
  border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-soft);
}

/* Pagination, dropdowns and the link colour the reset inherits from the
   vendor sheet. */
.page-item.active .page-link {
  background: var(--brand);
  border-color: var(--brand);
  color: var(--brand-ink);
}

.page-link {
  color: var(--brand);
}

.dropdown-item:active,
.dropdown-item:focus {
  background: var(--brand);
  color: var(--brand-ink);
}

/* 30. The listing two-up on phones ----------------------------------------- */

/* The feed is an auto-fill grid, so on a phone it settled on a single column of
   full-width cards and the reader scrolled through one story at a time. Two
   columns is how a phone listing is normally browsed, and it is what the topic
   grid already does. */
@media (max-width: 720px) {
  .feed {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 18px 12px;
  }

  .feed .card__body {
    gap: 6px;
  }

  /* A two-up card is about half a phone wide, so the sizes the desktop layout
     uses are a step too big: the title ran to five lines and pushed the meta
     row out of sight.

     The remaining sizes climb with the viewport rather than being pinned to a
     phone value, because the card goes from about 140px wide on a small handset
     to about 330px at the top of this range and one flat size is either cramped
     at one end or oversized at the other. The upper bounds are the sizes the
     card uses on desktop (17.5 / 12.5 / 12.5); the lower bounds are the
     smallest the text can be and still read. */
  .feed .card__title {
    font-size: clamp(14.5px, 9px + 1.5vw, 17.5px);
    line-height: 1.3;
    letter-spacing: -0.008em;
    -webkit-line-clamp: 3;
  }

  /* The excerpt is the first thing to go. At this width it is two or three
     words a line and six lines of them - a wall of text rather than a summary -
     and it pushes the meta row off the bottom of the card. */
  .feed .card__excerpt {
    display: none;
  }

  .feed .card__cat {
    top: 7px;
    left: 7px;
    padding: 3px 8px;
    font-size: 10.5px;
  }

  .feed .source-chip {
    gap: 5px;
  }

  .feed .source-chip__logo {
    width: 14px;
    height: 14px;
  }

  /* Ceiling is the desktop size, so a phone never draws the publisher's name
     larger than a desktop does. */
  .feed .source-chip__name {
    font-size: clamp(10px, 5.8px + 1.1vw, 11.5px);
  }

  .feed .card__meta {
    gap: 8px;
    font-size: clamp(11px, 6px + 1.2vw, 12.5px);
  }

  .feed .meta-item svg {
    width: 12px;
    height: 12px;
  }
}

/* 31. Search page: the answer panel ---------------------------------------- */

/* The instant-answer panel is the one card on the search page with real prose
   in it, and at the shared 324px column it wrapped into a tall thin ribbon.
   The search layout gives its sidebar 350px, which is the width the panel is
   read at; every other page keeps the shared column. */
@media (min-width: 1081px) {
  body.search .layout {
    grid-template-columns: minmax(0, 1fr) 350px;
  }
}

/* 32. Ad slots ------------------------------------------------------------- */

/* The design system draws an ad slot as a hatched placeholder box with a
   centred "Advertisement" pill and a 128 / 260px floor. That is the right look
   for an empty slot in the prototype, but every slot here is filled by hand in
   Dashboard -> Settings -> Ads, so the box the admin's code lands in should be
   a plain rail, not a placeholder: a small label, then the unit centred at its
   own size. The floors go too — an ad that has been sold is as tall as it is,
   and a fixed 110px was only ever guessing. */
.ad-slot {
  display: block;
  min-height: 0;
  margin: 26px 0;
  padding: 0;
  border: 0;
  background: none;
  text-align: center;
}

.ad-slot--tall,
.ad-slot--inline {
  min-height: 0;
}

/* "Advertisement" above the unit rather than over it: a label sitting on top
   of third-party markup can end up underneath it, and the label is the one
   thing that has to stay legible. */
.ad-slot__label {
  display: block;
  width: max-content;
  margin: 0 auto 8px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted-2);
}

/* An ad unit is a block of its own and usually ships a fixed width, so centre
   it instead of letting it sit hard against the left edge of the prose. */
.ad-slot > *,
.ad-slot > ins,
.ad-slot > iframe {
  margin-inline: auto;
}

/* Inline in the middle of an article: the surrounding paragraphs already carry
   1em of margin, so the rail only needs a touch more and a hairline above it to
   read as a break rather than another paragraph. */
.article__body .ad-slot {
  margin: 30px 0;
  padding-top: 22px;
  border-top: 1px solid var(--line);
}

/* 33. No scrollbars -------------------------------------------------------- */

/* The window, the header rail, the sticky sidebar and a <pre> in an article all
   scroll; none of them draw a bar. Scrollback is untouched — the wheel, the
   trackpad, touch, the keyboard and `scroll-behavior` all still work, Element
   .scrollTop still moves, and every container still reports its overflow to
   assistive tech. The back-to-top button (below) is what replaces the window
   bar's "there is more page" hint.

   `!important` on the universal rule is load-bearing, not muscle-flexing: once
   Blink has a value other than `none` for `scrollbar-width` on an element it
   stops honouring the `::-webkit-scrollbar` rule above, so a single
   component-level `thin` anywhere in the sheet would draw a real bar that this
   block could no longer hide. The universal rule has no specificity of its
   own, so the flag is what makes "none" mean none. */
html,
body {
  scrollbar-width: none; /* Firefox */
  -ms-overflow-style: none; /* legacy Edge / IE */
}

html::-webkit-scrollbar,
body::-webkit-scrollbar,
*::-webkit-scrollbar {
  width: 0 !important;
  height: 0 !important;
  display: none !important;
}

* {
  scrollbar-width: none!important;
  -ms-overflow-style: none;
}

/* 34. Back to top ---------------------------------------------------------- */

/* A round button in the bottom-right corner, in the theme's own vocabulary.
   Off by default: .is-visible is what reveals it, and the script that adds it
   only does so once the reader is past the fold. pointer-events is tied to the
   same class so the button cannot swallow a tap while it is hidden. */
.to-top {
  position: fixed;
  right: 22px;
  bottom: 22px;
  z-index: 40;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  padding: 0;
  border-radius: var(--radius-pill);
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--text-strong);
  box-shadow: var(--shadow-lg);
  cursor: pointer;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translateY(10px);
  transition: opacity 0.22s var(--ease), transform 0.22s var(--ease),
    visibility 0.22s var(--ease), background 0.16s var(--ease),
    color 0.16s var(--ease);
}

.to-top svg {
  width: 20px;
  height: 20px;
}

.to-top.is-visible {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: none;
}

.to-top:hover,
.to-top:focus-visible {
  background: var(--brand);
  border-color: var(--brand);
  color: var(--brand-ink);
}

/* Sits above the thumb on a phone, and clears the safe area on an iPhone. */
@media (max-width: 720px) {
  .to-top {
    right: 14px;
    bottom: calc(14px + env(safe-area-inset-bottom, 0px));
    width: 42px;
    height: 42px;
  }
}

/* 35. Category rail: the active pill, and the edges --------------------- */

/* The rail is horizontal and scrolls, so on a narrow screen the pill for the
   page you are on can easily be off-screen — you arrive on Politics and the
   rail shows whatever happens to fit from the left. The script centres the
   current pill; this is the colour that says which one it is. */
.cat-pill[aria-current="page"] {
  background: var(--brand);
  border-color: var(--brand);
  color: var(--brand-ink);
}

.cat-pill[aria-current="page"] .cat-pill__count {
  background: color-mix(in oklab, var(--brand-ink) 22%, transparent);
  color: inherit;
}

/* Overflow arrows: shown only while the rail is hovered, and only on the side
   that actually has more to scroll to (the script sets is-scrollable-start and
   is-scrollable-end). Hover is a mouse gesture, so touch and keyboards skip it
   — a keyboard still scrolls the rail by tabbing through its links, which the
   browser brings into view on its own. */
.cat-rail {
  position: relative;
}

.cat-rail__arrow {
  position: absolute;
  top: 0;
  bottom: 0;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted);
  cursor: pointer;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.18s var(--ease), visibility 0.18s var(--ease),
    color 0.16s var(--ease);
}

.cat-rail__arrow svg {
  width: 20px;
  height: 20px;
}

.cat-rail__arrow:hover {
  color: var(--brand);
}

.cat-rail__arrow--prev {
  left: 0;
  padding-left: 6px;
  background: linear-gradient(
    to right,
    var(--surface) 55%,
    color-mix(in oklab, var(--surface) 0%, transparent) 100%
  );
}

.cat-rail__arrow--next {
  right: 0;
  padding-right: 6px;
  background: linear-gradient(
    to left,
    var(--surface) 55%,
    color-mix(in oklab, var(--surface) 0%, transparent) 100%
  );
}

@media (hover: hover) and (pointer: fine) {
  .cat-rail.is-scrollable-start:hover .cat-rail__arrow--prev,
  .cat-rail.is-scrollable-end:hover .cat-rail__arrow--next {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
  }
}

/* Reachable by keyboard on any device: focusing one reveals it. */
.cat-rail__arrow:focus-visible {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

@media (max-width: 900px) {
  .cat-rail__arrow {
    width: 34px;
  }
}

/* 36. The read-original strip on phones ---------------------------------- */

/* The strip is one flex row: the publisher's mark, two sentences of
   attribution, and a button out to them. On a phone that row squeezes the
   sentences into a narrow column between the mark and the button — the button
   stays full size while the text it belongs to becomes unreadable. Below 900px
   the button is allowed to drop to its own line; below 720px the strip becomes
   a two-column grid, mark beside text, with the button across the bottom.

   The text is explicitly placed in the second column rather than relying on
   auto-placement, so a publisher with no logo (the mark is omitted entirely in
   that case) still gets the full width instead of a 36px sliver. */
@media (max-width: 900px) {
  .read-original {
    flex-wrap: wrap;
  }

  .read-original__text {
    flex: 1 1 200px;
  }
}

@media (max-width: 720px) {
  .read-original {
    display: grid;
    grid-template-columns: 36px minmax(0, 1fr);
    align-items: center;
    gap: 12px;
    padding: 14px;
    margin-top: 22px;
  }

  .read-original__logo {
    width: 36px;
    height: 36px;
  }

  .read-original__text {
    grid-column: 2;
    font-size: 13px;
    line-height: 1.5;
  }

  .read-original:not(:has(.read-original__logo)) .read-original__text {
    grid-column: 1 / -1;
  }

  .read-original__text b {
    font-size: 13.5px;
    line-height: 1.4;
  }

  /* Full-width and centred: a 36px-tall target spans the strip, which is
     easier to hit than a pill floating at the right end of a row. */
  .read-original .btn {
    grid-column: 1 / -1;
    width: 100%;
  }
}

/* 37. The source chip: a notch smaller ----------------------------------- */

/* The publisher mark and name sit directly above a card's headline, and at the
   prototype's 18px mark and 12.5px uppercase name they competed with the 17.5px
   title they exist to introduce — under the image, the credit line was the
   loudest thing in the card. Both step down once: a 16px mark and an 11.5px
   name, so the headline is plainly the biggest thing in the card again.

   One component serves all three sizes of card (the feed, the hero lead and
   the compact rail), so they cannot drift apart on this. */
.source-chip {
  gap: 6px;
}

.source-chip__logo,
.source-chip__mono {
  width: 16px;
  height: 16px;
}

/* The monogram is initials inside that same square, so its type steps down
   with the box rather than filling it. */
.source-chip__mono {
  font-size: 9.5px;
}

.source-chip__name {
  font-size: 11.5px;
}

/* 38. Bylines and journalist pages ---------------------------------------- */

/* The credit row above a card's headline: the publisher, then the writer, on
   one line while they fit and wrapping together when they do not.

   Both chips are .source-chip and both were stepped down in section 37, so
   they arrive the right size. The wrapper only decides how they share the row:
   as a single flex child of .card__body it takes one row of the card's column
   instead of giving each chip a row of its own. */
.card__chips {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 10px;
  min-width: 0;
}

/* The writer is a person, so the mark is a circle and the name is written the
   way a name is written. The publisher above it keeps its square mark and its
   uppercase label, and that contrast is the point: at a glance the two read as
   different kinds of credit, an outlet and a human, rather than as two logos.

   Normal case also hands back the width the uppercase label spends, which is
   what a two-word name in a half-width card needs. */
.source-chip--author {
  gap: 5px;
}

.source-chip--author .source-chip__mono {
  border-radius: 50%;
}

.source-chip--author .source-chip__name {
  text-transform: none;
  font-weight: 600;
  letter-spacing: 0;
  color: var(--muted);
}

.source-chip--author:hover .source-chip__name {
  color: var(--brand);
}

/* A desk credit ("Editorial Staff") is a span rather than a link, so it must
   not offer a hover affordance it cannot honour. */
span.source-chip--author {
  cursor: default;
}

/* The article byline gains a second line: publisher, then writer, then the
   timestamp and view count. That is the order a newspaper prints them in. */
.byline__author {
  margin-top: 2px;
  font-size: 13.5px;
  color: var(--muted);
}

.byline__author a {
  font-weight: 600;
  color: var(--text-strong);
}

.byline__author a:hover {
  color: var(--brand);
}

/* The source's own date, in the byline row. It is bumped one step brighter
   than the rest of the row on purpose: the timestamp beside it is ours and
   this one is the publisher's, and on an imported story the second is the
   fact a reader is actually owed. */
.byline__original {
  color: var(--muted);
}

/* The outlet lists on a journalist page ("Niguro · Nepali Times") are inline
   runs, but .byline__sep was written as a flex child in a gapped row. Inline it
   needs its own breathing room. */
.src-card__domain .byline__sep,
.profile__domain .byline__sep {
  margin-inline: 8px;
  vertical-align: middle;
}

/* A mark with no logo is a monogram <span>, and width and height do not apply
   to an inline element. Measured on the live site, that costs two things, and
   both were already happening to any publisher without a logo:

     * in the profile header the mark stayed display:inline, so the 76px box was
       ignored and a logo-less publisher rendered as a ~42px pill instead of an
       avatar;
     * in a card the mark is blockified for free (it is a flex child of
       .src-card__head) but the initials still sat in the top-left corner of the
       box rather than centred in it.

   Neither is a journalist bug, but every journalist page would show both, so
   they are fixed at the component instead of in the screens that exposed them.
   Scoped to `span` so a real logo <img> keeps the layout it already has. */
span.src-card__logo,
.profile__logo-wrap .profile__logo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.profile__logo-wrap {
  display: flex;
  align-items: center;
  justify-content: center;
}

/* A journalist's mark is a face rather than a masthead, so it is a circle —
   directory card, profile header and article avatar alike. Scoped to the
   journalist screens: the publisher pages share these classes and their square
   marks are correct for a logo. */
body.journalists .src-card__logo,
body.journalist-page .profile__logo {
  border-radius: 50%;
}

/* .profile__logo pads a real logo image away from its border. On a circular
   person mark that padding is just dead space, so it goes. */
body.journalist-page .profile__logo {
  padding: 0;
}

/* The person glyph.

   A mark is a fixed box, and the shared .ic default of 16px is wrong in both
   of them: inside a 16px chip mark the glyph would touch the edges, and inside
   a 76px profile mark it would look lost. So the glyph is sized per component.

   The background matters as much as the size. A monogram <span> carries its own
   colour as an inline style (see inb_monogram), but the avatar mark has no
   inline style — without these rules it would render as a transparent box with
   a white slashed circle in it. Where the component already paints a surface
   (the source card and profile marks do) this only agrees with it. */
.source-chip__avatar {
  background: var(--surface-2);
  color: var(--muted);
}

.source-chip__avatar .ic {
  width: 11px;
  height: 11px;
}

span.src-card__logo.source-chip__avatar .ic {
  width: 22px;
  height: 22px;
}

.profile__logo-wrap .profile__logo.source-chip__avatar .ic {
  width: 38px;
  height: 38px;
}

/* 39. Comment form: the name and e-mail row ------------------------------ */

/* Two bugs met in one row, and both were visible on every article page.

   `.form-row` is Bootstrap's, and it ships as `display:flex` with
   `margin: 0 -5px` and no bottom margin. Inside a form carrying 16px of
   padding, the negative margins pulled the name and e-mail inputs 5px wide on
   each side — measured live, they sat 4px past the content box while the
   textarea beneath them sat 1px inside it, so the two rows did not share a left
   edge and the inputs crowded the form's own padding.

   The missing bottom margin was the worse half: the row and the textarea had
   exactly 0px between them, so the fields touched. Every other gap in the form
   came from a hand-written margin on one child; the fix is to make the form a
   column with one gap, which is what gives the whole thing a rhythm instead of
   three unrelated numbers. */
.inb-comment-form {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.inb-comment-form .form-row {
  margin: 0;
  gap: 12px;
}

/* The inputs are flex items in a wrapping row; without this a long value in
   one of them stops the pair from shrinking below its content width. */
.inb-comment-form .form-row > * {
  min-width: 0;
}

/* The footer already carried `margin-top: 12px` for the space the flex gap now
   provides, so it has to stand down or the gap would be 24px. */
.inb-comment-form__footer {
  margin-top: 0;
}

/* 40. The 404 page --------------------------------------------------------- */

/* The 404 is the one screen rendered with no header and no footer, so it owns
   the whole viewport. `.page-flex` centres with `min-height: 100%`, which is a
   percentage against a parent that never gets a height — so it resolved to
   nothing and the block sat at the top of a tall empty page. A viewport unit
   gives it a definite floor to centre inside. dvh rather than vh so a phone's
   collapsing address bar does not leave a strip of dead space underneath. */
.page-flex.err404 {
  min-height: 100dvh;
  padding: 40px 0;
}

/* `.page-flex-content` ships a 1rem padding of its own; the flex gap below
   owns the spacing, and a second source of it would double up on phones. */
.page-flex.err404 .page-flex-content {
  padding: 0;
}

.err404__inner {
  max-width: 620px;
  margin: 0 auto;
  text-align: center;
}

.err404__brand {
  display: inline-block;
  margin-bottom: 30px;
  text-decoration: none;
}

.err404__brand .site-logo {
  max-height: 46px;
  width: auto;
}

.err404__brand .brand-word {
  font-family: var(--font);
  font-size: 30px;
  font-weight: 800;
  letter-spacing: -0.03em;
  color: var(--text-strong);
}

/* The heading string reads "404 <span class="text-muted">That's an error.</span>".
   The number is the whole point of the screen, so it takes the large type and
   the sentence inside the muted span steps down to a caption — one element,
   two sizes, no extra markup and no second translation string. */
.err404__title {
  margin: 0;
  font-family: var(--font);
  font-size: clamp(64px, 13vw, 132px);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.045em;
  color: var(--text-strong);
}

/* `!important` is not decoration here. The caption carries Bootstrap's own
   `.text-muted`, which ships as `color: #6c757d !important` — so the theme's
   `.text-muted { color: var(--muted) }` never wins, and this line rendered in
   Bootstrap's mid grey on the dark background (measured 3.8:1) instead of the
   theme's muted token (about 6.5:1). Only this caption is forced, so nothing
   else on the site shifts. */
.err404__title .text-muted {
  display: block;
  margin-top: 0.9em;
  font-size: 0.22em;
  font-weight: 600;
  letter-spacing: 0;
  color: var(--muted) !important;
}

.err404__text {
  margin: 20px 0 0;
  font-size: 17px;
  line-height: 1.5;
  color: var(--text);
}

.err404__muted {
  margin: 6px 0 0;
  font-size: 14px;
  color: var(--muted);
}

.err404__action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-top: 30px;
  padding: 13px 28px;
  border-radius: var(--radius-pill);
  background: var(--brand);
  color: var(--brand-ink);
  font-size: 15px;
  font-weight: 600;
  text-decoration: none;
  transition: background 0.16s ease;
}

.err404__action:hover,
.err404__action:focus {
  background: var(--brand-hover);
  color: var(--brand-ink);
}

/* The number takes the type down to 64px before this, and at that size the
   0.22em caption lands under 15px — too small to read. Give the pair more
   room to breathe instead of shrinking the caption further. */
@media (max-width: 480px) {
  .err404__title .text-muted {
    font-size: 0.28em;
    margin-top: 0.7em;
  }

  .err404__brand {
    margin-bottom: 22px;
  }
}

/* 41. The save button ------------------------------------------------------ */

/* Save sits in the article action bar beside the like button and shares its
   shape deliberately — they are siblings, and a reader should not have to
   learn two visual languages for two adjacent controls. It differs in one
   place: it always keeps its label. A bare bookmark glyph next to a heart
   reads as a second follow button, and the two do opposite things, one public
   and one private. */
.save-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 9px;
  height: 40px;
  padding: 0 16px 0 14px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--line-strong);
  background: var(--surface);
  color: var(--text-strong);
  font-size: 14px;
  font-weight: 600;
  text-decoration: none;
  transition: all 0.18s var(--ease);
}

.save-btn:hover {
  border-color: var(--brand);
  color: var(--brand);
}

.save-btn svg {
  width: 17px;
  height: 17px;
  transition: transform 0.24s var(--ease);
}

.save-btn[aria-pressed="true"] {
  background: var(--brand-soft);
  border-color: var(--brand);
  color: var(--brand);
}

/* Filled bookmark once saved, so the state is legible without reading the
   label — the same trick the like button uses. */
.save-btn[aria-pressed="true"] svg {
  fill: currentColor;
}

/* 42. Photo credit --------------------------------------------------------- */

/* Positioned against the figure, so the figure has to be the containing block. */
.article__hero {
  position: relative;
}

/* Over the hero, bottom right. It carries its own background rather than
   relying on a scrim over the photo, because the image underneath is
   arbitrary: a licence line has to stay readable over a white sky as well as
   over a dark one, and a gradient scrim only solves one of those.

   `--brand` / `--brand-ink` rather than a hardcoded green and white, so the
   badge keeps the accent and flips with the rest of the theme in dark mode —
   white on the light-mode green, and the theme's own dark ink on the lighter
   dark-mode green, which is the only pairing that stays legible there. */
.photo-credit {
  position: absolute;
  right: 12px;
  bottom: 12px;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  max-width: calc(100% - 24px);
  padding: 6px 12px;
  border-radius: var(--radius-pill);
  background: var(--brand);
  color: var(--brand-ink);
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1.25;
}

/* The words "Photo credit" are the label, the name is the fact — so the label
   steps back rather than competing with the publisher it is introducing. */
.photo-credit__label {
  font-weight: 500;
  opacity: 0.75;
}

.photo-credit a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid color-mix(in oklab, currentColor 45%, transparent);
}

.photo-credit a:hover {
  border-bottom-color: currentColor;
}

/* On a phone the badge would otherwise eat the width of the picture. */
@media (max-width: 560px) {
  .photo-credit {
    right: 8px;
    bottom: 8px;
    padding: 5px 10px;
    font-size: 11.5px;
  }

  /* On a phone the action bar wraps; the label is what makes this button wider
     than the round share buttons, so the label steps aside and the glyph
     carries it. */
  .save-btn {
    padding: 0 13px;
    gap: 0;
  }

  .save-btn [data-save-label] {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }
}

/* 43. Photo credit, inline ------------------------------------------------- */

/* A picture inside the article body. The hero's badge floats over the photo
   because there is only one of it; a report can carry half a dozen photographs,
   and a stack of badges over them would fight the pictures for attention. So
   the body picture keeps the hero's colours and loses the positioning: the
   credit is printed flat beneath the image, like a caption. */
.article__figure {
  margin: 0;
}

/* Used when the picture shared its paragraph with words: a <figure> would be a
   block inside a <p>, so the wrapper is a span laid out as a block instead. */
.article__figure--inline {
  display: block;
}

.article__figure img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius-sm);
}

/* Scoped to the body so it cannot reach the hero, which must stay an overlay.
   `position: static` is the whole point: `.photo-credit` above is absolute. */
.article__body .photo-credit--inline {
  position: static;
  display: block;
  max-width: 100%;
  margin-top: 8px;
  padding: 0;
  border-radius: 0;
  background: none;
  color: var(--muted);
  font-size: 12.5px;
  font-weight: 500;
  line-height: 1.4;
}

/* The label steps back here too, but on a flat credit the whole line is already
   quiet, so it only has to be distinguishable, not dimmer. */
.article__body .photo-credit--inline .photo-credit__label {
  opacity: 1;
  font-weight: 500;
}

/* The publisher's own caption, when the feed shipped one, and the credit line
   we add under it. */
.article__body figcaption {
  color: var(--muted);
  font-size: 13.5px;
  line-height: 1.45;
}

/* A gallery's caption would otherwise inherit the body's full paragraph width
   and read as another paragraph rather than a note under the pictures. */
.article__body .blocks-gallery-caption {
  max-width: 68ch;
}

/* 44. The article lightbox ------------------------------------------------- */

/* Pictures that open a viewer get a cursor that says so. Scoped to
   `.lightbox-ready`, which the script adds to <html> once it has actually
   wired them up, so a browser that never runs the script is never promised a
   click that does nothing. */
.lightbox-ready .article__body img,
.lightbox-ready .article__hero img {
  cursor: zoom-in;
}

/* A picture is a control now, so it has to show where the keyboard is. */
.lightbox-ready .article__body img:focus-visible,
.lightbox-ready .article__hero img:focus-visible {
  outline: 3px solid var(--brand);
  outline-offset: 3px;
}

/* The page behind the viewer must not scroll while it is open. */
body.lightbox-open {
  overflow: hidden;
}

/* Deliberately dark in both colour schemes, like the spotlight player: a
   photograph is the brightest thing in a viewer, and a light chrome around it
   makes every picture look washed out. Above the player (1080) because a
   reader who opened a picture from a story is not watching a video. */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 1090;
  display: flex;
  flex-direction: column;
  background: oklch(0.13 0.008 265 / 0.97);
  color: oklch(1 0 0);
  font-family: var(--font);
}

.lightbox[hidden] {
  display: none;
}

.lightbox:focus {
  outline: none;
}

/* --- the slideshow's clock ---------------------------------------------- */

/* A hairline across the top of the screen rather than a number: it answers
   "how long until the next one" without asking to be read. */
.lightbox__progress {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  background: oklch(0.8 0.14 158);
  opacity: 0;
  transform: scaleX(0);
  transform-origin: left center;
  transition: opacity 0.2s var(--ease);
}

.lightbox__progress.is-running {
  opacity: 1;
  transition: opacity 0.2s var(--ease), transform 0.1s linear;
}

/* --- the picture -------------------------------------------------------- */

/* The one row and column are named rather than left implicit.

   A grid's implicit track is `auto`, and a percentage max-height cannot be
   resolved against an indefinite track — so `.lightbox__img`'s max-height was
   ignored and only its max-width applied. A picture wider than it is tall was
   then scaled to the stage's *width* alone and ran out of the bottom of the
   stage, where `overflow: hidden` cut it in half: a landscape photograph
   opened cropped, top and bottom missing, on any window with room to spare
   under it. Naming the row `minmax(0, 1fr)` makes the track definite, which is
   what lets max-height mean "the height of the stage" and the picture arrive
   whole, at its own aspect ratio, with the letterbox space either side. */
.lightbox__stage {
  flex: 1 1 auto;
  min-height: 0;
  display: grid;
  grid-template-rows: minmax(0, 1fr);
  grid-template-columns: minmax(0, 1fr);
  place-items: center;
  padding: 26px 18px 8px;
  overflow: hidden;
}

.lightbox__img {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border-radius: var(--radius-sm);
  box-shadow: 0 18px 60px oklch(0 0 0 / 0.55);
  transform-origin: center center;
  /* A pinch or a drag on a zoomed picture is a pan, never a page gesture. */
  touch-action: none;
  user-select: none;
  -webkit-user-drag: none;
}

.lightbox__stage.is-zoomed .lightbox__img {
  cursor: grab;
}

.lightbox__stage.is-zoomed .lightbox__img:active {
  cursor: grabbing;
}

/* --- details and controls ---------------------------------------------- */

.lightbox__bar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding: 12px 18px 14px;
  border-top: 1px solid oklch(1 0 0 / 0.1);
  background: oklch(0.1 0.008 265 / 0.92);
}

.lightbox__meta {
  min-width: 0;
}

.lightbox__count {
  margin: 0;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: oklch(0.72 0.012 265);
  font-variant-numeric: tabular-nums;
}

/* The publisher's own caption, kept as they wrote it. */
.lightbox__caption {
  margin: 5px 0 0;
  max-width: 74ch;
  font-size: 13.5px;
  line-height: 1.45;
  color: oklch(0.93 0.005 265);
}

.lightbox__credit {
  margin: 4px 0 0;
  font-size: 12.5px;
  font-weight: 500;
  color: oklch(0.75 0.012 265);
}

.lightbox__credit a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid oklch(1 0 0 / 0.28);
}

.lightbox__credit a:hover {
  border-bottom-color: oklch(1 0 0 / 0.7);
}

.lightbox__tools {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 6px;
}

.lightbox__sep {
  width: 1px;
  height: 20px;
  margin: 0 4px;
  background: oklch(1 0 0 / 0.16);
}

.lightbox__btn {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-pill);
  background: oklch(1 0 0 / 0.12);
  color: oklch(1 0 0);
  text-decoration: none;
  cursor: pointer;
  transition: background 0.16s var(--ease), transform 0.16s var(--ease);
}

.lightbox__btn:hover {
  background: oklch(1 0 0 / 0.24);
  color: oklch(1 0 0);
}

.lightbox__btn:active {
  transform: scale(0.94);
}

.lightbox__btn:disabled,
.lightbox__btn:disabled:hover {
  background: oklch(1 0 0 / 0.08);
  opacity: 0.35;
  cursor: default;
}

/* The two toggles say when they are on, because a pressed play is the
   difference between "this is moving by itself" and "this is broken". */
.lightbox__btn[aria-pressed="true"] {
  background: oklch(0.8 0.14 158);
  color: oklch(0.15 0.02 158);
}

.lightbox__btn--close {
  margin-left: 4px;
  background: oklch(1 0 0 / 0.18);
}

.lightbox__btn svg {
  width: 19px;
  height: 19px;
}

.lightbox__btn:focus-visible,
.lightbox__thumb:focus-visible,
.lightbox-strip__thumb:focus-visible {
  outline: 2px solid oklch(0.86 0.13 158);
  outline-offset: 2px;
}

.lightbox__toast {
  position: absolute;
  left: 50%;
  bottom: 108px;
  transform: translateX(-50%);
  margin: 0;
  padding: 8px 15px;
  border-radius: var(--radius-pill);
  background: oklch(1 0 0 / 0.16);
  font-size: 12.5px;
  color: oklch(1 0 0);
  pointer-events: none;
}

.lightbox__toast[hidden] {
  display: none;
}

/* --- thumbnails, in the viewer and under the article -------------------- */

.lightbox__thumbs {
  display: flex;
  gap: 8px;
  margin: 0;
  padding: 0 0 4px;
  overflow-x: auto;
  list-style: none;
  scrollbar-width: thin;
}

.lightbox__thumb,
.lightbox-strip__thumb {
  display: block;
  padding: 0;
  border-radius: var(--radius-xs);
  overflow: hidden;
  cursor: pointer;
  transition: border-color 0.16s var(--ease), opacity 0.16s var(--ease),
    box-shadow 0.16s var(--ease);
}

.lightbox__thumb img,
.lightbox-strip__thumb img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.lightbox__strip {
  flex: 0 0 auto;
  padding: 0 18px 14px;
}

.lightbox__thumb {
  width: 88px;
  height: 56px;
  border: 1px solid oklch(1 0 0 / 0.18);
  background: oklch(1 0 0 / 0.06);
  opacity: 0.5;
}

.lightbox__thumb:hover {
  opacity: 1;
  border-color: oklch(1 0 0 / 0.45);
}

.lightbox__thumb.is-current {
  opacity: 1;
  border-color: oklch(0.82 0.15 158);
}

/* The strip under the article. It is the one piece of this feature that lives
   in the page's own colour scheme, because it sits between two paragraphs of
   the story rather than over the top of it. */
.lightbox-strip {
  margin: 30px 0 0;
  padding-top: 18px;
  border-top: 1px solid var(--line);
}

.lightbox-strip__head {
  margin: 0 0 10px;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--muted);
}

.lightbox-strip__thumb {
  width: 104px;
  height: 66px;
  border: 1px solid var(--line);
  background: var(--surface-2);
  opacity: 0.8;
}

.lightbox-strip__thumb:hover {
  opacity: 1;
  border-color: var(--line-strong);
}

.lightbox-strip__thumb.is-current {
  opacity: 1;
  border-color: var(--brand);
  box-shadow: 0 0 0 2px var(--brand-soft);
}

/* --- small screens ------------------------------------------------------ */

@media (max-width: 760px) {
  /* The details and the controls stop sharing a line: eight buttons beside a
     caption on a phone leaves the caption two words wide. */
  .lightbox__bar {
    flex-direction: column-reverse;
    align-items: stretch;
    gap: 10px;
    padding: 10px 12px 12px;
  }

  .lightbox__tools {
    justify-content: center;
  }

  .lightbox__btn {
    width: 38px;
    height: 38px;
  }

  .lightbox__caption {
    font-size: 13px;
  }

  .lightbox__stage {
    padding: 16px 10px 6px;
  }

  .lightbox__thumb {
    width: 64px;
    height: 42px;
  }

  .lightbox-strip__thumb {
    width: 84px;
    height: 54px;
  }

  .lightbox__toast {
    bottom: 152px;
  }
}

/* Nothing animates for a reader who asked for less of it. The viewer still
   works; it just arrives and changes instantly. */
@media (prefers-reduced-motion: reduce) {
  .lightbox__progress,
  .lightbox__btn,
  .lightbox__thumb,
  .lightbox-strip__thumb {
    transition: none;
  }
}

