/*
 * Site theme: Bootstrap 5.3 dressed in Vercel's Geist tokens.
 *
 * Loaded after vendor/assets/stylesheets/bootstrap.min.css, so every override below wins
 * by cascade order alone — keep the selectors identical to Bootstrap's own (.btn-primary
 * against .btn-primary). Bootstrap's variant colors are literal hex values compiled into
 * the CSS variables (.btn-primary{--bs-btn-bg:#0d6efd}), so they can only be replaced by
 * redefining those variables here; changing --bs-primary does nothing.
 *
 * Token values are copied from vercel.com's production CSS (--ds-* custom properties).
 * Two token blocks: `:root` / [data-bs-theme="light"] and [data-bs-theme="dark"]. The
 * attribute lives on <html> and is set by the head script in the layout plus the footer
 * switcher (see docs/ui.md §4); with neither, the page renders light.
 *
 * Fonts: Ubuntu for text, Ubuntu Mono for code — both vendored in vendor/assets/fonts
 * and declared through --bs-body-font-family / --bs-font-monospace so nothing has to
 * hard-code a stack (see docs/ui.md).
 */

@font-face {
  font-family: "Ubuntu";
  src: url("/assets/ubuntu-latin-400-normal-c1bb3829.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}

@font-face {
  font-family: "Ubuntu";
  src: url("/assets/ubuntu-latin-500-normal-7185e354.woff2") format("woff2");
  font-weight: 500;
  font-display: swap;
}

@font-face {
  font-family: "Ubuntu";
  src: url("/assets/ubuntu-latin-700-normal-acb7d6b6.woff2") format("woff2");
  font-weight: 700;
  font-display: swap;
}

/* Ubuntu has no 600; .fw-semibold (600) lands on the 700 face by font matching. */

@font-face {
  font-family: "Ubuntu Mono";
  src: url("/assets/ubuntu-mono-latin-400-normal-f2bda096.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}

@font-face {
  font-family: "Ubuntu Mono";
  src: url("/assets/ubuntu-mono-latin-700-normal-9316c6c1.woff2") format("woff2");
  font-weight: 700;
  font-display: swap;
}

:root,
[data-bs-theme="light"] {
  /* Bootstrap's CSS only ships `color-scheme: dark` (inside its own dark block), so
     light has to be declared here or native controls and scrollbars follow the OS. */
  color-scheme: light;

  /* Geist tokens — light */
  --ds-background-100: #fff;
  --ds-background-200: #fafafa;
  --ds-gray-100: #f2f2f2;
  --ds-gray-200: #ebebeb;
  --ds-gray-300: #e6e6e6;
  --ds-gray-400: #eaeaea;
  --ds-gray-500: #c9c9c9;
  --ds-gray-600: #a8a8a8;
  --ds-gray-700: #8f8f8f;
  --ds-gray-800: #7d7d7d;
  --ds-gray-900: #4d4d4d;
  --ds-gray-1000: #171717;
  --ds-gray-alpha-400: rgba(0, 0, 0, 0.08);
  --ds-gray-alpha-600: rgba(0, 0, 0, 0.24);
  --ds-blue-700: #0070f7;
  --ds-red-100: #ffeef0;
  --ds-red-400: #ffd8d7;
  --ds-red-800: #e70022;
  --ds-red-900: #d60020;
  --ds-red-1000: #46000c;
  --ds-amber-100: #fff6e1;
  --ds-amber-400: #ffdd84;
  --ds-amber-1000: #541c00;
  --ds-green-100: #ecfdec;
  --ds-green-400: #b9f5bc;
  --ds-green-800: #279141;
  --ds-green-1000: #00370d;
  --ds-focus-ring: rgba(0, 0, 0, 0.16);
  --ds-focus-border: 0 0 0 1px var(--ds-gray-alpha-600), 0 0 0 4px var(--ds-focus-ring);
  --ds-radius: 6px;
  /* Primary button under the cursor and while pressed: Vercel lifts black by one step,
     then goes all the way. Dark mode inverts the button, so both flip with the theme. */
  --ds-primary-hover: #383838;
  --ds-primary-active: #000;
  /* Translucent, so the blurred page shows through under the sticky navbar. */
  --ds-navbar-bg: rgba(255, 255, 255, 0.8);
  --ds-font-sans-fallback: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --ds-font-mono-fallback: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;

  /* Bootstrap mapped onto the tokens above */
  --bs-body-font-family: "Ubuntu", var(--ds-font-sans-fallback);
  /* Bootstrap points code, kbd, pre, samp and .font-monospace at this one. */
  --bs-font-monospace: "Ubuntu Mono", var(--ds-font-mono-fallback);
  --bs-body-color: var(--ds-gray-1000);
  --bs-body-color-rgb: 23, 23, 23;
  --bs-body-bg: var(--ds-background-100);
  --bs-body-bg-rgb: 255, 255, 255;
  --bs-body-font-weight: 400;
  --bs-emphasis-color: var(--ds-gray-1000);
  --bs-secondary-color: var(--ds-gray-900);
  --bs-tertiary-bg: var(--ds-gray-100);
  --bs-tertiary-bg-rgb: 242, 242, 242;
  --bs-border-color: var(--ds-gray-alpha-400);
  --bs-border-color-translucent: var(--ds-gray-alpha-400);
  --bs-border-radius: var(--ds-radius);
  --bs-border-radius-sm: 4px;
  --bs-border-radius-lg: 8px;
  --bs-link-color: var(--ds-gray-1000);
  --bs-link-hover-color: var(--ds-gray-900);
  /* Bootstrap compiles #0d6efd into the variables below, and the components read the
     *-rgb triples rather than the hex values: `a` uses --bs-link-color-rgb, .text-primary
     and .bg-primary use --bs-primary-rgb, .link-secondary uses --bs-secondary-rgb.
     Overriding only the hex forms leaves links and badges blue, so both forms are set
     here. Components holding a literal hex (.btn-primary, .pagination, the form
     controls, .dropdown-menu) are handled by their own blocks below. */
  --bs-primary: var(--ds-gray-1000);
  --bs-primary-rgb: 23, 23, 23;
  --bs-primary-text-emphasis: var(--ds-gray-1000);
  --bs-primary-bg-subtle: var(--ds-gray-100);
  --bs-primary-border-subtle: var(--ds-gray-300);
  --bs-link-color-rgb: 23, 23, 23;
  --bs-link-hover-color-rgb: 77, 77, 77;
  --bs-secondary-rgb: 77, 77, 77;
  --bs-focus-ring-color: var(--ds-focus-ring);
  --bs-form-invalid-color: var(--ds-red-800);
  --bs-form-invalid-border-color: var(--ds-red-800);
  --bs-form-valid-color: var(--ds-green-800);
  --bs-form-valid-border-color: var(--ds-green-800);
}

/* Geist tokens — dark: the same names, vercel.com's `.dark` values. Bootstrap's own
   baked-in dark palette is for its blue theme, and it loses to the `:root` block above
   anyway (equal specificity, our file loads last), so the mapping only has to restate
   the literals: everything pointing at a `--ds-*` value follows the switch on its own. */
[data-bs-theme="dark"] {
  color-scheme: dark;

  --ds-background-100: #000;
  --ds-background-200: #000;
  --ds-gray-100: #1a1a1a;
  --ds-gray-200: #1f1f1f;
  --ds-gray-300: #292929;
  --ds-gray-400: #2e2e2e;
  --ds-gray-500: #454545;
  --ds-gray-600: #878787;
  --ds-gray-700: #8f8f8f;
  --ds-gray-800: #7d7d7d;
  --ds-gray-900: #a0a0a0;
  --ds-gray-1000: #ededed;
  --ds-gray-alpha-400: rgba(255, 255, 255, 0.14);
  --ds-gray-alpha-600: rgba(255, 255, 255, 0.51);
  --ds-blue-700: #0071f6;
  --ds-red-100: #330a11;
  --ds-red-400: #6f101b;
  --ds-red-800: #e2162a;
  --ds-red-900: #ff5e63;
  --ds-red-1000: #ffeaed;
  --ds-amber-100: #291800;
  --ds-amber-400: #573200;
  --ds-amber-1000: #fff3d9;
  --ds-green-100: #00250a;
  --ds-green-400: #004616;
  --ds-green-800: #009335;
  --ds-green-1000: #daffe5;
  --ds-focus-ring: rgba(255, 255, 255, 0.16);
  --ds-primary-hover: #cccccc;
  --ds-primary-active: #fff;
  --ds-navbar-bg: rgba(0, 0, 0, 0.8);

  /* Bootstrap mapped onto the tokens above — the literals only */
  --bs-body-color-rgb: 237, 237, 237;
  --bs-body-bg-rgb: 0, 0, 0;
  --bs-primary-rgb: 237, 237, 237;
  --bs-link-color-rgb: 237, 237, 237;
  --bs-link-hover-color-rgb: 160, 160, 160;
  --bs-secondary-rgb: 160, 160, 160;
  --bs-tertiary-bg-rgb: 26, 26, 26;
}

body {
  -webkit-font-smoothing: antialiased;
}

/* Vercel sets its headings tight. */
h1, h2, h3, h4 {
  letter-spacing: -0.02em;
}

/* Buttons: flat, 14px, black primary. */
.btn {
  --bs-btn-font-size: 0.875rem;
  --bs-btn-font-weight: 500;
  --bs-btn-padding-x: 0.875rem;
  --bs-btn-padding-y: 0.375rem;
  --bs-btn-box-shadow: none;
  --bs-btn-focus-box-shadow: 0 0 0 4px rgba(0, 112, 247, 0.25);
}

.btn-primary {
  --bs-btn-color: var(--ds-background-100);
  --bs-btn-bg: var(--ds-gray-1000);
  --bs-btn-border-color: var(--ds-gray-1000);
  --bs-btn-hover-color: var(--ds-background-100);
  --bs-btn-hover-bg: var(--ds-primary-hover);
  --bs-btn-hover-border-color: var(--ds-primary-hover);
  --bs-btn-active-color: var(--ds-background-100);
  --bs-btn-active-bg: var(--ds-primary-active);
  --bs-btn-active-border-color: var(--ds-primary-active);
  --bs-btn-disabled-color: var(--ds-background-100);
  --bs-btn-disabled-bg: var(--ds-gray-1000);
  --bs-btn-disabled-border-color: var(--ds-gray-1000);
}

/* Dark mode's pressed (and hovered) red is near-white, so the label flips to black to
   stay readable — in light mode both fills are dark and keep Bootstrap's white. */
[data-bs-theme="dark"] .btn-danger {
  --bs-btn-hover-color: #000;
  --bs-btn-active-color: #000;
}

.btn-danger {
  --bs-btn-bg: var(--ds-red-800);
  --bs-btn-border-color: var(--ds-red-800);
  --bs-btn-hover-bg: var(--ds-red-900);
  --bs-btn-hover-border-color: var(--ds-red-900);
  --bs-btn-active-bg: var(--ds-red-1000);
  --bs-btn-active-border-color: var(--ds-red-1000);
}

.btn-outline-secondary {
  --bs-btn-color: var(--ds-gray-1000);
  --bs-btn-border-color: var(--ds-gray-alpha-400);
  --bs-btn-hover-color: var(--ds-gray-1000);
  --bs-btn-hover-bg: var(--ds-gray-100);
  --bs-btn-hover-border-color: var(--ds-gray-alpha-400);
  --bs-btn-active-color: var(--ds-gray-1000);
  --bs-btn-active-bg: var(--ds-gray-200);
  --bs-btn-active-border-color: var(--ds-gray-300);
  --bs-btn-disabled-color: var(--ds-gray-700);
  --bs-btn-disabled-border-color: var(--ds-gray-300);
}

/* Badges: Vercel tints a light background with dark text; Bootstrap's .text-bg-* is a solid
   fill with white text, so these roles carry the colors instead. */
/* Bootstrap's badge in Vercel's tag shape: 12px/500 text in a 20px pill (4px padding a
   side, 8px a side horizontally), rather than the 4px corner Bootstrap ships with.
   Vercel draws its statuses and its tags the same way, so a maintainer's label and the
   issue's state read as the same kind of object; only the colour tells them apart.
   Every knob is a variable because the variants below set nothing but colour. */
.badge {
  --bs-badge-font-weight: 500;
  --bs-badge-font-size: 0.75rem;
  --bs-badge-padding-x: 0.5rem;
  --bs-badge-padding-y: 0.25rem;
  --bs-badge-border-radius: 9999px;
}

.badge-primary {
  background-color: var(--ds-gray-1000);
  color: var(--ds-background-100);
}

/* The label shape: gray-1000 on gray-100 is the contrast Vercel's gray tag uses, and it
   is what makes a 12px chip readable at a glance. */
.badge-neutral {
  background-color: var(--ds-gray-100);
  color: var(--ds-gray-1000);
}

.badge-success {
  background-color: var(--ds-green-100);
  color: var(--ds-green-1000);
}

.badge-danger {
  background-color: var(--ds-red-100);
  color: var(--ds-red-1000);
}

.badge-attention {
  background-color: var(--ds-amber-100);
  color: var(--ds-amber-1000);
}

/* Forms: Vercel's focus is a hairline black ring, not Bootstrap's blue glow. */
.form-label {
  font-size: 0.875rem;
  font-weight: 500;
}

.form-text {
  font-size: 0.8125rem;
}

.form-control:focus,
.form-select:focus {
  border-color: var(--ds-gray-alpha-600);
  box-shadow: var(--ds-focus-border);
}

.form-check-input:checked,
.form-check-input[type="checkbox"]:indeterminate {
  background-color: var(--ds-gray-1000);
  border-color: var(--ds-gray-1000);
}

.form-check-input:focus {
  border-color: var(--ds-gray-alpha-600);
  box-shadow: var(--ds-focus-border);
}

/* Alerts */
.alert {
  --bs-alert-border-radius: var(--ds-radius);
  --bs-alert-padding-y: 0.75rem;
}

.alert-danger {
  --bs-alert-color: var(--ds-red-1000);
  --bs-alert-bg: var(--ds-red-100);
  --bs-alert-border-color: var(--ds-red-400);
}

.alert-success {
  --bs-alert-color: var(--ds-green-1000);
  --bs-alert-bg: var(--ds-green-100);
  --bs-alert-border-color: var(--ds-green-400);
}

/* Pagination: borderless, gray hover. */
.pagination {
  --bs-pagination-border-width: 0;
  --bs-pagination-color: var(--ds-gray-1000);
  --bs-pagination-hover-color: var(--ds-gray-1000);
  --bs-pagination-hover-bg: var(--ds-gray-100);
  --bs-pagination-active-color: var(--ds-background-100);
  --bs-pagination-active-bg: var(--ds-gray-1000);
  --bs-pagination-disabled-color: var(--ds-gray-700);
  --bs-pagination-disabled-bg: transparent;
  --bs-pagination-focus-box-shadow: var(--ds-focus-border);
  gap: 0.25rem;
}

/* Dropdowns: Bootstrap compiles #0d6efd straight into --bs-dropdown-link-active-bg, so
   a selected menu item is blue until that variable is redefined — the same trap as the
   button variants above. Active matches .pagination-active: black fill, white text. The
   menu also inherits the 14px base instead of Bootstrap's 1rem, so it matches its
   btn-sm trigger. */
.dropdown-menu {
  --bs-dropdown-font-size: 0.875rem;
  --bs-dropdown-link-active-color: var(--ds-background-100);
  --bs-dropdown-link-active-bg: var(--ds-gray-1000);
}

/* Navbar: Vercel's is 64px tall, translucent, with a hairline bottom border. */
.navbar {
  --bs-navbar-padding-y: 0;
  --bs-navbar-color: var(--ds-gray-900);
  --bs-navbar-hover-color: var(--ds-gray-1000);
  --bs-navbar-active-color: var(--ds-gray-1000);
  --bs-navbar-brand-color: var(--ds-gray-1000);
  --bs-navbar-brand-hover-color: var(--ds-gray-1000);
  --bs-navbar-toggler-border-color: var(--ds-gray-alpha-400);
  --bs-navbar-toggler-padding-x: 0.5rem;
  --bs-navbar-toggler-focus-width: 0;
  --bs-navbar-nav-link-padding-x: 0.75rem;
}

/* Bootstrap hard-codes a blue keyboard-focus ring on nav links; everywhere else the
   theme uses the hairline black one. */
.nav-link:focus-visible {
  box-shadow: var(--ds-focus-border);
}

.site-navbar {
  background-color: var(--ds-navbar-bg);
  backdrop-filter: saturate(180%) blur(5px);
  border-bottom: 1px solid var(--ds-gray-alpha-400);
}

/* The 64px belongs to the row itself, not to the <header> around it: on the header the
   nav would sit at the top of a taller box, leaving more air below it than above. As a
   min-height on the flex row, Bootstrap's own align-items:center does the centering, and
   the row still grows when the mobile menu expands under it. */
.site-navbar .navbar {
  min-height: 64px;
}

/* The signed-in avatar in the nav. Round, because that is the shape GitHub shows the
   same picture in, and 24px so the trigger stays the height of the links beside it. */
.nav-avatar {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background-color: var(--ds-gray-alpha-400);
}

/* The home page's technology tiles. The anchor is the box, not a word inside one: the
   whole card is the target, which is what a visitor aims at. Colours are the ones
   .btn-outline-secondary already uses, so the two bordered things a visitor clicks on
   that page agree, and the focus ring is the theme's hairline rather than a browser
   default drawn tight around the text. */
.topic-tile {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-height: 88px;
  padding: 1rem;
  border: 1px solid var(--ds-gray-alpha-400);
  border-radius: var(--ds-radius);
  color: var(--ds-gray-1000);
  font-weight: 500;
  text-decoration: none;
}

.topic-tile:hover {
  background-color: var(--ds-gray-100);
  color: var(--ds-gray-1000);
}

.topic-tile:focus-visible {
  outline: none;
  box-shadow: var(--ds-focus-border);
}

/* The headline numbers on the repository page (shared/_stat_strip). A grid of equal
   cells, not a list of rows: the block exists so stars, forks and open beginner issues
   can be compared in one look, and a number is read before the word under it — hence
   column-reverse, which puts the value on top while leaving the <dt> before its <dd> in
   the DOM. The hairlines are a 1px grid gap over a tinted background rather than borders
   on the cells: a wrapped row then separates itself, with no rule left hanging in the
   air at the end of a short last row. */
.stat-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr));
  gap: 1px;
  margin: 0;
  background-color: var(--ds-gray-alpha-400);
  border: 1px solid var(--ds-gray-alpha-400);
  border-radius: var(--ds-radius);
  overflow: hidden;
}

.stat-strip__item {
  display: flex;
  flex-direction: column-reverse;
  gap: 0.125rem;
  padding: 0.625rem 0.875rem;
  background-color: var(--bs-body-bg);
}

.stat-strip__value {
  margin: 0;
  font-size: 1.125rem;
  font-weight: 600;
  line-height: 1.3;
}

/* The dense fact line under it, and the same line on the issue page
   (shared/_fact_list): label/value pairs flowing as one paragraph of facts. No box —
   two framed blocks stacked on top of each other is the thing that made the old table
   look like a form. `--framed` is the standalone variant, held between two hairlines. */
.fact-list {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.375rem 1.25rem;
  margin: 0;
  font-size: 0.875rem;
}

.fact-list--framed {
  padding: 0.625rem 0;
  border-top: 1px solid var(--ds-gray-alpha-400);
  border-bottom: 1px solid var(--ds-gray-alpha-400);
}

.fact-list__item {
  display: flex;
  align-items: baseline;
  gap: 0.375rem;
}

.fact-list__value {
  margin: 0;
}

.fact-list__value .badge + .badge {
  margin-left: 0.25rem;
}

/* Both blocks name their values the same way: small caps in the secondary colour, so a
   label reads as an index to its value rather than as a second value beside it. */
.stat-strip__label,
.fact-list__label {
  font-size: 0.6875rem;
  font-weight: 400;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ds-gray-900);
}

/* The browse frames swap their contents in place. Turbo already flags an in-flight
   request with [aria-busy] on <turbo-frame>, so the visible half of the loading state is
   a function of that attribute instead of JavaScript state that could drift from it:
   the list dims, and the toolbar spinner ([data-issues-loader]) appears. No JS toggles
   either of these, which is why they cannot get stuck on. */
turbo-frame[aria-busy] .list-group {
  opacity: 0.5;
}

[data-issues-loader] {
  display: none;
}

turbo-frame[aria-busy] [data-issues-loader] {
  display: inline-block;
}

/* The multi-value filter control (ui/multi_select): a dropdown whose menu is a list of
   checkboxes. Only the box needs a rule — the domain facet has forty-odd values, so the
   menu has to scroll instead of running off the viewport, and it has to be wide enough
   that a checkbox and a language name fit on one line. Colors and borders are
   Bootstrap's own dropdown, already themed. */
.multi-select-menu {
  max-height: 16rem;
  min-width: 15rem;
  overflow-y: auto;
}

/* Icons: <iconify-icon> (tdesign for the interface, devicon for languages and topics —
   see docs/ui.md §6). The web component renders asynchronously, so until it does the
   element is an empty inline box; sizing that box to 1em here is what keeps the icon
   from shifting the layout when it arrives, and it makes every icon scale with the text
   it sits in. No color: tdesign follows `currentColor`, devicon carries its own. */
iconify-icon {
  display: inline-block;
  width: 1em;
  height: 1em;
  vertical-align: -0.125em;
}

/* Rendered issue text (Markdown.render — see docs/markdown.md). The box is a reader's
   window onto somebody else's document, so everything here is about the document not
   escaping its frame rather than about decorating it: a wide table and a long URL both
   push the page sideways, and a stack trace wrapped mid-line is unreadable, so `pre`
   scrolls instead. No literal colour and no font: colours are the theme's tokens (so the
   dark block switches them on its own) and the mono stack is whatever Bootstrap points
   `code, kbd, pre` at. */
.markdown-body {
  /* A 300-character URL or a pasted stack trace is an ordinary thing to find in a body. */
  overflow-wrap: anywhere;
}

.markdown-body > :first-child {
  margin-top: 0;
}

.markdown-body > :last-child {
  margin-bottom: 0;
}

.markdown-body pre {
  padding: 0.75rem 1rem;
  border-radius: var(--ds-radius);
  background-color: var(--ds-gray-100);
  overflow-x: auto;
}

.markdown-body code {
  padding: 0.125rem 0.25rem;
  border-radius: var(--ds-radius);
  background-color: var(--ds-gray-100);
}

/* Inside a fence the box has the tint and the padding already; a second layer reads as a
   rendering bug. */
.markdown-body pre code {
  padding: 0;
  background-color: transparent;
}

/* A table wider than the column scrolls, the same way a fence does. */
.markdown-body table {
  display: block;
  max-width: 100%;
  overflow-x: auto;
  border-collapse: collapse;
}

.markdown-body th,
.markdown-body td {
  padding: 0.375rem 0.75rem;
  border: 1px solid var(--bs-border-color);
}

.markdown-body th {
  font-weight: 500;
}

.markdown-body img {
  max-width: 100%;
  height: auto;
  border-radius: var(--ds-radius);
}

.markdown-body blockquote {
  margin: 0 0 1rem;
  padding-left: 1rem;
  border-left: 2px solid var(--ds-gray-300);
  color: var(--ds-gray-900);
}

/* GFM's task list: no bullet (the checkbox is the marker), and the box is pulled back
   into the space the bullet would have used so it lines up with the text above it. */
.markdown-body li:has(> input[type="checkbox"]) {
  list-style: none;
}

.markdown-body li > input[type="checkbox"] {
  margin: 0 0.375rem 0 -1.25rem;
}
