/* App-level additions that the static prototype did not need. tokens.css and
   components.css stay verbatim copies of design/assets. */

/* Geist, self-hosted (static/fonts, OFL): no third-party stylesheet blocks the
   first render. The fallbacks are Arial and Courier New resized to Geist's
   metrics (size-adjust and overrides measured from the font files), so the
   swap to Geist moves no text (CLS). */
@font-face { font-family: "Geist"; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url("../fonts/geist-latin-v5.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: "Geist"; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url("../fonts/geist-latin-ext-v5.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family: "Geist Mono"; font-style: normal; font-weight: 400 600; font-display: swap;
  src: url("../fonts/geist-mono-latin-v6.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: "Geist Mono"; font-style: normal; font-weight: 400 600; font-display: swap;
  src: url("../fonts/geist-mono-latin-ext-v6.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family: "Geist Fallback"; src: local("Arial"), local("ArialMT"), local("Helvetica"), local("Roboto");
  size-adjust: 104.57%; ascent-override: 96.11%; descent-override: 28.21%; line-gap-override: 0%; }
@font-face { font-family: "Geist Mono Fallback"; src: local("Courier New"), local("CourierNewPSMT"), local("Menlo"), local("DejaVu Sans Mono");
  size-adjust: 100%; ascent-override: 100.52%; descent-override: 29.5%; line-gap-override: 0%; }
:root {
  --font-sans: "Geist", "Geist Fallback", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-mono: "Geist Mono", "Geist Mono Fallback", ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
}

/* Owner avatars: the image covers the initials, which remain the fallback. */
.avatar { position: relative; overflow: hidden; }
.avatar > img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; border-radius: inherit; }

/* htmx request indicators reuse the design's loading button state. */
.htmx-request.btn, .htmx-request .btn[type="submit"] { pointer-events: none; opacity: .7; }
[aria-busy="true"] { cursor: progress; }

/* EthicalAds text unit inside the design's sponsor shell. */
.sponsor .sponsor__text [data-ea-publisher], .sponsor [data-ea-publisher] { display: block; }

/* Server-rendered README: images never overflow the panel. */
.readme__body img { max-width: 100%; height: auto; }

/* Legal and confirmation pages. */
.doc-page { padding-block: var(--sp-8) var(--sp-16); }
.doc-page .prose { margin-top: var(--sp-6); max-width: 72ch; }

/* Explore trending strip: anchor the tiles' .sr-only text inside the scrolling
   strip; otherwise it is positioned against the page and widens it below 390px
   (the prototype has the same overflow). */
.trend-tile { position: relative; }

/* Repo page: the discreet "Report this repo" link and its form page. */
.side-report { margin: 0; font-size: var(--fs-12); text-align: right; }
.side-report a { color: var(--muted); }
.side-report a:hover { color: var(--text); }
.report-form { margin-top: var(--sp-6); max-width: 36rem; display: grid; gap: var(--sp-4); }
.report-form .textarea { width: 100%; }
.report-form__actions { display: flex; gap: var(--sp-2); flex-wrap: wrap; }

/* Repository health (scoring.HealthLevel): a dot on repo cards whose label shows
   on hover or tap (focus), a badge and the "Health signals" panel on repo pages,
   and the topic list's health filter. Colours are the design's status tokens. */
:is(.health-dot, .health-badge).is-healthy { --health: var(--rise); }
:is(.health-dot, .health-badge).is-mixed { --health: var(--warning); }
:is(.health-dot, .health-badge).is-low { --health: var(--faint); }
:is(.health-dot, .health-badge).is-new { --health: var(--info); }
:is(.health-dot, .health-badge).is-archived { --health: var(--danger); }
.health-dot { display: inline-flex; align-items: center; gap: 6px; height: 22px; padding: 0 4px; border-radius: var(--r-chip); color: var(--muted); font-size: var(--fs-12); font-weight: var(--fw-medium); white-space: nowrap; cursor: default; }
.health-dot__mark { width: 8px; height: 8px; border-radius: 50%; background: var(--health, var(--faint)); flex: none; }
.health-dot__label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.health-dot:focus { outline: none; }
.health-dot:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); }
.health-dot:focus .health-dot__label { position: static; width: auto; height: auto; overflow: visible; clip-path: none; }
@media (hover: hover) {
  .health-dot:hover .health-dot__label { position: static; width: auto; height: auto; overflow: visible; clip-path: none; }
  .health-panel__disclaimer a:hover, .list-filters__how:hover { color: var(--text); }
}
.health-badge { color: var(--text); background: var(--raised); }
.health-panel .panel__head { justify-content: flex-start; }
.health-panel__checked { margin-left: auto; }
.health-panel__note { margin: 0 0 var(--sp-3); font-size: var(--fs-13); color: var(--muted); }
.health-facts { display: grid; margin: 0; }
.health-facts > div { display: grid; grid-template-columns: minmax(8rem, 12rem) minmax(0, 1fr); gap: 2px var(--sp-4); padding: var(--sp-2) 0; border-top: 1px solid var(--border); font-size: var(--fs-13); }
.health-facts > div:first-child { border-top: 0; padding-top: 0; }
.health-facts dt { color: var(--muted); }
.health-facts dd { margin: 0; display: grid; gap: 2px; min-width: 0; }
.health-facts__value { color: var(--text); }
a.health-facts__value { text-decoration: underline; text-underline-offset: 3px; }
.health-facts__detail { color: var(--muted); font-size: var(--fs-12); }
.health-panel__disclaimer { margin: var(--sp-4) 0 0; font-size: var(--fs-12); color: var(--muted); }
.health-panel__disclaimer a { color: var(--muted); text-decoration: underline; text-underline-offset: 3px; }
.list-filters { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2) var(--sp-4); }
.list-filters .tb-field { display: inline-flex; align-items: center; gap: var(--sp-2); }
.list-filters .tb-label { font-size: var(--fs-13); color: var(--muted); }
.list-filters .select { height: 34px; width: auto; min-width: 150px; font-size: var(--fs-13); }
.list-filters__how { margin-left: auto; font-size: var(--fs-12); color: var(--muted); }
@media (max-width: 767px) {
  .health-facts > div { grid-template-columns: minmax(0, 1fr); }
  .list-filters .tb-field { flex: 1 1 100%; }
  .list-filters .tb-label { min-width: 2.5rem; }
  .list-filters .select { flex: 1 1 auto; }
  .list-filters__how { margin-left: 0; }
}

/* Landing preview: topic tabs (one active, the design's chips), "All topics" link
   and the visitor subtitle in the title row. */
.preview__sub { color: var(--muted); font-weight: var(--fw-regular); font-size: var(--fs-13); }
.preview__tabs { display: flex; gap: var(--od-gap, 8px); }
.preview__all { color: var(--muted); text-decoration: none; gap: 4px; }
/* The tab row scrolls sideways: a fade on each overflowing edge, arrow buttons
   for mice without horizontal scrolling; the row's rule moves to the wrapper so
   the fade does not eat it. */
.preview__rail { position: relative; border-bottom: 1px solid var(--border); }
.preview__rail .preview__chips { border-bottom: 0; }
.preview__chips.is-fade-end { -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 72px), transparent); mask-image: linear-gradient(to right, #000 calc(100% - 72px), transparent); }
.preview__chips.is-fade-start { -webkit-mask-image: linear-gradient(to left, #000 calc(100% - 72px), transparent); mask-image: linear-gradient(to left, #000 calc(100% - 72px), transparent); }
.preview__chips.is-fade-start.is-fade-end {
  -webkit-mask-image: linear-gradient(to right, transparent, #000 72px, #000 calc(100% - 72px), transparent);
  mask-image: linear-gradient(to right, transparent, #000 72px, #000 calc(100% - 72px), transparent);
}
.preview__scroll { position: absolute; top: 50%; transform: translateY(-50%); z-index: 1; box-shadow: var(--shadow-pop); }
.preview__scroll--prev { left: var(--sp-2); }
.preview__scroll--next { right: var(--sp-2); }
@media (hover: none) { .preview__scroll { display: none; } }
.preview__caption a { display: inline-flex; align-items: center; gap: 4px; }

/* Section separators span the content column, not the viewport: the design's
   full-bleed border-top on landing blocks and the footer read as stray lines
   crossing the page (and the footer's legal rule overhung the text). */
section.block, footer.site-footer, div.site-footer__legal { border-top: 0; position: relative; }
section.block::before, footer.site-footer::before, div.site-footer__legal::before {
  content: ""; position: absolute; top: 0; left: 50%; transform: translateX(-50%);
  width: calc(min(100%, var(--container)) - 2 * var(--sp-6)); border-top: 1px solid var(--border); pointer-events: none;
}
@media (max-width: 767.98px) {
  section.block::before, footer.site-footer::before, div.site-footer__legal::before { width: calc(min(100%, var(--container)) - 2 * var(--sp-4)); }
}

/* Sticky headers share the page background, so their bottom rule read as a
   stray line crossing the page over the content scrolled under them. The rule
   is gone; once the page scrolls (app.js adds .is-scrolled) the bar lifts with a
   translucent blurred background and a soft shadow instead. */
.site-header, .topbar { border-bottom-color: transparent; transition: box-shadow var(--dur-fast) var(--ease-out); }
.site-header.is-scrolled, .topbar.is-scrolled {
  background: color-mix(in srgb, var(--bg) 86%, transparent);
  -webkit-backdrop-filter: blur(12px) saturate(1.2); backdrop-filter: blur(12px) saturate(1.2);
  box-shadow: 0 10px 24px -14px rgb(0 0 0 / 0.55);
}

/* Mobile overflows. The rank table scrolls inside its panel instead of
   widening the page; the digest viewer applies the email's own small-screen
   rules (its inline <style> is blocked by the CSP here) and scrolls if needed. */
.rank-table-wrap { overflow-x: auto; }
.canvas { overflow-x: auto; }
@media (max-width: 620px) {
  .canvas .email .px { padding-left: 18px !important; padding-right: 18px !important; }
  .canvas .email .hide-sm { display: none !important; }
  .canvas .email .h1 { font-size: 20px !important; line-height: 26px !important; }
}

/* Public header (components.css "Public site header" sets the 1280/1200/1024/768
   ladder): the menu sheet also has a search field on top. */
.sheet-search { margin-bottom: var(--sp-3); }

/* Topic pages: the H1 says what the page is ("Trending Rust repos"), the slug
   is a small label above it. */
.topic-label { margin: 0 0 var(--sp-1); font-family: var(--font-mono); font-size: var(--fs-14); color: var(--muted); }
.topic-title.topic-title--name { font-family: var(--font-sans); letter-spacing: -0.03em; }
.load-more [data-load-more].is-loading { pointer-events: none; opacity: 0.6; }

/* Footer: link columns for the week's popular topics, the collections, the AI pages, the
   product and the account: three per row on tablets, five side by side on wide screens
   (the brand column gives them room). */
@media (min-width: 768px) { .site-footer__cols { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 1100px) {
  .site-footer__inner { grid-template-columns: minmax(0, 1fr) minmax(0, 2.6fr); }
  .site-footer__cols { grid-template-columns: repeat(5, minmax(0, 1fr)); }
}
/* Repo pages: related topics as chips (links to neighbouring topic pages). */
.related-chips { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
/* Topic lists: an owner's later repos fold under their first one. */
.owner-more > summary { list-style: none; width: fit-content; margin-left: var(--sp-4); color: var(--muted); cursor: pointer; }
.owner-more > summary::-webkit-details-marker { display: none; }
.owner-more[open] > summary { margin-bottom: var(--sp-2); }
.owner-more > .repo-list { padding-left: var(--sp-4); border-left: 2px solid var(--border); }
.gh-topics { margin-top: var(--sp-3); }

/* /trending: every topic's fastest-rising repos. */
.trending-page { display: grid; gap: var(--sp-4); padding-block: var(--sp-6) var(--sp-12); }
.trending-page .topic-label { margin: 0; }
.trending-page .page-title { margin: 0; }
.trending-lead { margin: 0; color: var(--muted); max-width: 70ch; }

/* Repo pages: share links and the README badge. */
.share-row { grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2) var(--sp-3); font-size: var(--fs-14); }
.share-row__badge { display: block; }
.trending-page .crumbs ol { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; align-items: center; gap: 4px; font-size: var(--fs-13); color: var(--muted); }
.trending-page .crumbs li { display: inline-flex; align-items: center; gap: 4px; }
.trending-page .crumbs li + li::before { content: "/"; color: var(--faint); margin-right: 2px; }
.trending-page .crumbs a { color: var(--muted); text-decoration: none; }
.trending-page .crumbs [aria-current="page"] { color: var(--text); font-family: var(--font-mono); }

/* Members with few stars: popular niche topics under the star suggestions
   (onboarding step 2) and one-click topics in the empty feed. */
.ob-popular__title { margin: var(--sp-6) 0 var(--sp-3); font-size: var(--fs-14); font-weight: var(--fw-semibold); color: var(--muted); }
.feed-starters { width: 100%; max-width: 440px; margin: var(--sp-6) auto 0; text-align: left; }

/* /emerging and the AI niche pages (/ai/…): /trending's layout, the topic page's
   caption and "Show more", a short intro and follow buttons. */
.trending-page .list-caption { margin: 0; font-size: var(--fs-13); color: var(--muted); }
.trending-page .list-caption .num { color: var(--text); }
.trending-page .load-more { display: flex; justify-content: center; margin-top: var(--sp-2); }
.trending-page .section { margin-top: var(--sp-6); }
.niche-page .topic-label a { color: var(--muted); text-decoration: none; }
.niche-page .topic-label a:hover { color: var(--text); text-decoration: underline; text-underline-offset: 3px; }
.niche-intro { display: grid; gap: var(--sp-3); max-width: 70ch; color: var(--muted); }
.niche-intro p { margin: 0; }
.niche-follow { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4); }
.niche-follow__item { display: inline-flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-13); color: var(--muted); }
.niche-note { margin: 0 0 var(--sp-3); font-size: var(--fs-13); color: var(--muted); }
/* The footer stacks Resources under Account. */
.site-footer__title--more { margin-top: var(--sp-6); }

/* Languages: the EN/FR switch is made of links here (the prototype's buttons need
   script), the current one marked with aria-current instead of aria-pressed; the
   language links of the member sidebar and settings; the French suggestion banner
   (shown by app.js). */
.lang-switch__item { display: inline-flex; align-items: center; justify-content: center; text-decoration: none; }
.lang-switch__item:hover { color: var(--text); }
.lang-switch__item[aria-current="true"] { background: var(--raised); color: var(--text); font-weight: var(--fw-semibold); box-shadow: 0 0 0 1px var(--border-strong); }
.lang-links { display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); }
.lang-links a { color: var(--muted); }
.lang-links a[aria-current="true"] { color: var(--text); text-decoration: none; }
.sidebar__foot .lang-links { margin-top: var(--sp-2); font-size: var(--fs-12); }
.lang-banner { border-bottom: 1px solid var(--border); background: var(--surface); }
.lang-banner__inner { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2) var(--sp-3); padding-block: var(--sp-2); font-size: var(--fs-14); }
.lang-banner__inner p { margin: 0; flex: 1 1 auto; }
.lang-choice { margin: var(--sp-2) 0 0; font-size: var(--fs-14); }
/* Very narrow repo cards (repo page rail): the period label ("this week",
   "cette semaine") is decorative (screen readers get it from .sr-only) and has no room. */
@container (max-width: 320px) { .repo-card__trend .gain-period { display: none; } }

/* Collections: repo names link the Upstar page; the external icon next to them
   opens GitHub. Titles clip long names, so they also contain the icon's
   absolutely positioned screen-reader label. */
.newcomer-card__title, .coll-row__title { position: relative; display: flex; align-items: center; }
.newcomer-card__title > .repo-link, .coll-row__title > .repo-link { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.coll-row__gh { display: inline-flex; flex: none; text-decoration: none; }
.coll-row__gh .coll-row__ext { margin-left: 6px; }
.coll-row__gh:hover .coll-row__ext, .coll-row__gh:focus-visible .coll-row__ext { color: var(--text); }

/* Repo page, admins only: "Block this repo". */
.admin-block__form { display: grid; gap: var(--sp-2); }
.admin-block__form .btn { justify-self: start; }

/* Freshness line on ranking pages ("Updated 12 min ago"); past two hours it
   turns into a warning (components.Freshness, app.js freshness). */
.freshness { display: flex; align-items: flex-start; gap: var(--sp-2); margin: var(--sp-2) 0 0; color: var(--muted); font-size: var(--fs-13); line-height: 1.4; }
.freshness svg { flex: none; margin-top: 2px; }
.freshness__warn, .freshness--stale .freshness__ok { display: none; }
.freshness--stale .freshness__warn { display: block; }
.freshness--stale { width: fit-content; max-width: 100%; padding: var(--sp-1) var(--sp-2); border-radius: 6px; background: var(--warning-soft); color: var(--warning); }
.preview__bar { flex-wrap: wrap; }
.preview__bar .freshness { margin: 0; }
.preview__bar .freshness--stale { flex-basis: 100%; order: 1; }

/* Repo descriptions on cards and rows (repo cards, collection rows, newcomer
   cards): up to two lines, cut with an ellipsis at the end of the second. The
   full text stays in the HTML (screen readers, search engines; the compact
   card keeps it in its title tooltip too). The box is always two lines tall,
   so a short description, a late font or a "Load more" swap never moves what
   follows (CLS 0). Repo pages show the description whole (.repo-desc). */
.repo-card__desc, .repo-card__desc.od-truncate, .coll-row__desc, .newcomer-card__desc {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2;
  overflow: hidden; overflow-wrap: anywhere; white-space: normal; text-overflow: clip;
  line-height: 1.45; height: calc(2 * 1.45em);
}

/* Landing, desktop: the hero's left column (title, text, buttons, figures)
   stays in view while the preview's ten repos scroll by. Only when the
   viewport is tall enough to show all of it; mobile is unchanged. */
@media (min-width: 1024px) and (min-height: 720px) {
  .hero__copy { position: sticky; top: calc(var(--topbar-h) + var(--sp-6)); }
}

/* "Same week, two rankings" with real repos: names link their pages. */
.crow__link { color: inherit; text-decoration: none; }
.crow__link:hover, .crow__link:focus-visible { text-decoration: underline; }
