html {
	/* reserve the scrollbar gutter: a page growing past the viewport
	   (async fills, loading placeholder -> full page) must not nudge
	   the whole layout left when the scrollbar appears */
	scrollbar-gutter: stable;
}

body {
	color: #000;
	background-color: #fff;
	font-family: monospace;
}

h1, h2, h3, h4, h5, h6 {
	font-size: 1em;
	margin: 0;
}

img, h1, h2 {
	vertical-align: middle;
}

img {
	border: 0;
}

a:target {
	background-color: #ccc;
}

a.d,
a.h,
a.i,
a.line {
	text-decoration: none;
}

#blob a {
	color: #555;
}

#blob a:hover {
	color: blue;
	text-decoration: none;
}

table thead td {
	font-weight: bold;
}

table td {
	padding: 0 0.4em;
}

#content table td {
	vertical-align: top;
	white-space: nowrap;
}

#branches tr:hover td,
#tags tr:hover td,
#index tr:hover td,
#log tr:hover td,
#files tr:hover td,
#blame tr:hover td,
#diffstat tr:hover td,
table.ssdiff tr:hover td {
	background-color: #eee;
}

/* let long ref decorations wrap under the fixed-width subject box */
#log tr td:nth-child(2) {
	white-space: normal;
}

/* Fixed-width cell content: cells that fill in asynchronously (per fetch
   round) hold their text in a constant-width box, so a table's geometry
   is identical before and after the data arrives — variable-length text
   is clipped with an ellipsis instead of resizing its column. */
.cw {
	display: inline-block;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	vertical-align: bottom;
}
/* log subject / index description: 60ch, but yield on narrow viewports
   (the sibling columns need ~36ch) instead of forcing page-wide hscroll */
/* Message/description columns track the viewport: they grow to use a wide
   screen (up to a readable cap) and shrink on a narrow one, reserving room
   for the sibling columns. The width depends only on the viewport, never on
   the (async-arriving) cell text, so the table geometry is still stable as
   rows stream in. */
.cmsg { width: clamp(16ch, calc(100vw - 36ch), 90ch); }
.cdesc { width: clamp(16ch, calc(100vw - 36ch), 90ch); }
.cdate { width: 14ch; } /* relative dates ("11 months ago"), reserved while loading */
.cgx { width: clamp(12ch, calc(100vw - 30ch), 33ch); } /* graph merge chip */
.csize { min-width: 6ch; text-align: right; } /* tree: size in bytes */
.cstat { min-width: 14ch; text-align: right; } /* log ?stats: "N +A -D", reserved while loading */

/* Long file/ref names wrap instead of pushing the whole page into
   horizontal scroll on a narrow viewport. Only the free-text NAME column of
   each data table is affected — #content table td is nowrap by default so
   async-filled columns keep a stable width, but the name cell is known up
   front, never streams, and a 100-char filename or branch name must break
   rather than force page-wide hscroll. The date/size cells keep their own
   fixed-width .cw boxes, and the graph's ref decorations still scroll inside
   their own container. */
#files td:nth-child(2),
#branches td:nth-child(1),
#tags td:nth-child(1),
#index td:nth-child(1) {
	white-space: normal;
	overflow-wrap: anywhere;
}

/* Reveal a clipped message cell's full text on hover, pure CSS (no script):
   the clipped span is lifted out of flow (position:absolute, so the table
   never reflows) into a solid box over its neighbours — readable, and
   selectable/copyable right there. The 0.4em padding leaves a little slack
   past each end so a drag can grab the whole name incl. the last character;
   -webkit-user-drag stops the anchor drag that would otherwise start
   instead of a text selection. The full text is always in the DOM, so
   selecting across a row copies it even without the reveal.

   Gated on (hover: hover): touch devices have no hover, so instead of an
   unreachable reveal they show the message unclipped (see (hover: none)
   below). Table cells are the positioning context for the absolute span. */
@media (hover: hover) {
	#content table td,
	#graph td {
		position: relative;
	}
	/* Reveal the box's inner content, not the box: the fixed-width .cw
	   span stays in flow, so the table column cannot shrink a few px on
	   hover (the old form pulled the span out of flow, shifting the
	   Author column until unhover). */
	td:hover > .cmsg > *,
	td:hover > .cdesc > * {
		position: absolute;
		top: 0;
		left: 0;
		z-index: 5;
		width: max-content;
		max-width: min(90vw, calc(100vw - 16ch));
		padding: 0 0.4em;
		overflow: visible;
		white-space: normal;
		background-color: #eee;
		-webkit-user-drag: none;
	}
	/* the trailing "(HEAD -> master)" would show through the revealed box */
	td:hover > .cmsg ~ .refs,
	#graph td.gm:hover .gmi > .gmt ~ .refs,
	#graph td.gm:focus-within .gmi > .gmt ~ .refs {
		visibility: hidden;
	}
}

/* Touch (no hover): the log SUBJECT shows unclipped and wrapped — it is
   present at first render, so wrapping it costs no reflow. The index
   DESCRIPTION deliberately does NOT wrap here: it is fetched per-repo and
   arrives after the rows are painted, so wrapping it would grow each row as
   it lands (a visible cascade of shifts on a long list). Kept a single-line
   ellipsis, every row's height is fixed by its (already-present) name from
   the first paint, so descriptions fill in with zero reflow. */
@media (hover: none) {
	.cmsg {
		/* Show the full log subject, wrapped — but KEEP the .cw box and its clamp
		   width, so the text wraps INSIDE the reserved column instead of widening
		   the table (display:inline would drop the width and let a long subject
		   push the whole page into horizontal scroll). The subject is present
		   when its row paints, so wrapping adds no load-time reflow. (The graph
		   subject .gmt gets the same treatment, but its override must sit AFTER
		   the base `#graph .gmt` rule to win on source order — see below.) */
		white-space: normal;
		overflow: visible;
		overflow-wrap: anywhere; /* break a long unbreakable token (a ref name
		                            in a subject) rather than overflow the box */
		height: auto;
	}
}

/* Index description: fetched per-repo, so it lands AFTER the row paints —
   wrapping it inline would grow the row (the load reflow we avoid). It stays a
   single-line ellipsis; a tap lifts its full text OUT OF FLOW into an overlay
   — the exact reveal the desktop hover uses — so it reads in full with NO
   table reflow, on load or on tap. The .cdesc tap handler in stagit-ng.js
   toggles .cw-open. */
#index td {
	position: relative; /* containing block for the absolute overlay */
}
.cdesc.cw-open > * {
	position: absolute;
	top: 0;
	left: 0;
	z-index: 5;
	width: max-content;
	max-width: min(90vw, calc(100vw - 16ch));
	padding: 0 0.4em;
	overflow: visible;
	white-space: normal;
	overflow-wrap: anywhere;
	background-color: #eee;
}
@media (hover: none) {
	td:has(> .cdesc) {
		cursor: pointer; /* hint the clipped description is tappable */
	}
}

/* let a drag select the message text instead of dragging the link URL, so
   the revealed name can be selected/copied */
.cmsg a,
.cdesc a,
#graph .gmt a {
	-webkit-user-drag: none;
}

td.num {
	text-align: right;
}

.desc {
	color: #555;
	/* own line with reserved height: a description arriving a round
	   after the first paint must not reflow the header */
	display: block;
	min-height: 1.25em;
}

/* "on <ish> (<commit>): log · graph · ..." strip under the header on every
   ish-scoped page: names the commit-ish, its resolved commit, and links the
   sibling views at that ish (the header nav itself always points at HEAD) */
/* the "on <ish> (<sha>) · ↑ HEAD" context, inline within the nav row; kept on
   one line so a narrow-viewport wrap never breaks it mid-phrase */
.ref-context {
	color: #555;
	white-space: nowrap;
}
/* the separator before the Refs hatch (AA contrast: it carries the whole
   "views | hatch" grouping) */
.nav-sep {
	color: #767676;
}
/* the unified nav row: a touch of line-height so it wraps cleanly on narrow
   viewports (tabs + context + hatch can be long) */
#stagit-ng-body > table:first-child {
	/* Active tabs use bold text while their siblings are links. Filling the
	   container prevents those tiny font-metric differences from changing the
	   header table/column width when switching Log <-> Graph. */
	width: 100%;
}
td.nav {
	line-height: 1.7;
}
/* the resolved commit oid: monospace so the loading placeholder is the same
   width as the sha that replaces it (no reflow when it resolves) */
.ref-context .oid {
	font-family: monospace;
}
/* a sibling view that is unavailable here (e.g. diff on a root commit): muted,
   so it reads distinctly from the bold "you are here". Scoped to .nav (the
   spans are direct children of td.nav, siblings of .ref-context). AA contrast
   so "unavailable" is legible, not just a faint word. */
.nav .na {
	color: #767676;
}
.head {
	color: #555;
	font-size: 0.85em;
}

hr {
	border: 0;
	border-top: 1px solid #555;
	height: 1px;
}

pre {
	font-family: monospace;
}

pre a.h {
	color: #00a;
}

.A,
span.i,
pre a.i {
	color: #070;
}

.D,
span.d,
pre a.d {
	color: #e00;
}

pre a.h:hover,
pre a.i:hover,
pre a.d:hover {
	text-decoration: none;
}

/* blame: meta (commit · date · author) | line number | code */
#blame {
	border-collapse: collapse;
	font-family: monospace;
	/* Code lines don't wrap (white-space:pre); on a narrow viewport the wide
	   table scrolls inside its own box instead of pushing the whole page into
	   horizontal scroll — the same containment the graph page uses. */
	display: block;
	overflow-x: auto;
	max-width: 100%;
}
#blame td {
	vertical-align: top;
	white-space: pre;
}
#blame td.bm {
	padding-right: 1em;
	white-space: nowrap;
}
#blame td.num {
	text-align: right;
	padding-right: 0.5em;
	user-select: none;
}
#blame td.num a {
	color: #555;
	text-decoration: none;
}
#blame td.num a:hover {
	text-decoration: underline;
}

/* side-by-side diff (?dt=1): old num | old text | new num | new text */
table.ssdiff {
	border-collapse: collapse;
	width: 100%;
	font-family: monospace;
}
table.ssdiff td {
	vertical-align: top;
	white-space: pre-wrap;
	word-break: break-all;
	width: 50%;
}
table.ssdiff td.num {
	text-align: right;
	color: #555;
	padding: 0 0.5em;
	user-select: none;
	width: 1%;
	white-space: nowrap;
}
table.ssdiff a.h {
	color: #00a;
}
table.ssdiff a.i {
	color: #070;
}
table.ssdiff a.d {
	color: #e00;
}
table.ssdiff td.ic {
	background-color: #efe;
}
table.ssdiff td.dc {
	background-color: #fee;
}

@media (prefers-color-scheme: dark) {
	body {
		background-color: #000;
		color: #bdbdbd;
	}
	hr {
		border-color: #222;
	}
	a {
		color: #56c8ff;
	}
	a:target {
		background-color: #222;
	}
	.desc {
		color: #aaa;
	}
	#blob a {
		color: #555;
	}
	#blob a:target {
		color: #eee;
	}
	#blob a:hover {
		color: #56c8ff;
	}
	pre a.h {
		color: #00cdcd;
	}
	.A,
	span.i,
	pre a.i {
		color: #00cd00;
	}
	.D,
	span.d,
	pre a.d {
		color: #cd0000;
	}
	table.ssdiff a.h {
		color: #00cdcd;
	}
	table.ssdiff a.i {
		color: #00cd00;
	}
	table.ssdiff a.d {
		color: #cd0000;
	}
	table.ssdiff td.num {
		color: #555;
	}
	table.ssdiff td.ic {
		background-color: #041a04;
	}
	table.ssdiff td.dc {
		background-color: #1d0505;
	}
	.head {
		color: #aaa;
	}
	.ref-context {
		color: #aaa;
	}
	.nav-sep {
		color: #888;
	}
	.nav .na {
		color: #888;
	}
	#branches tr:hover td,
	#tags tr:hover td,
	#index tr:hover td,
	#log tr:hover td,
	#files tr:hover td,
	#blame tr:hover td,
	#diffstat tr:hover td,
	table.ssdiff tr:hover td {
		background-color: #111;
	}
	@media (hover: hover) {
		td:hover > .cmsg > *,
		td:hover > .cdesc > *,
		#graph td.gm:hover .gmt > .gmt-full,
		#graph td.gm:focus-within .gmt > .gmt-full {
			background-color: #111;
		}
	}
	.cdesc.cw-open > * {
		background-color: #111; /* the tapped touch-reveal overlay, dark */
	}
}

/* stagit-ng additions: in-page anchors ride the ?query (and standalone
   routes the hash), so :target never matches; stagit-ng.js applies
   .target to the ?anchor element instead. */
.target {
	background-color: #ccc;
}

/* bottom-anchored: the repo header (logo, name, description, clone url)
   owns the top of the page, and on narrow viewports a top-right overlay
   sits right on it. The bottom corner can only ever cover body rows —
   transient placeholders while a load is in flight. */
/* Bottom-right status cluster: the transfer-stats panel stacked directly
   above the status chip, anchored to the TRUE window bottom. A page with
   wide content (code, diffs, log tables) shows a horizontal scrollbar,
   which shrinks the layout viewport by the scrollbar's height — so a plain
   `bottom: 0` would leave the cluster floating that much (~1em) above the
   corner. 100vh measures the full viewport height (the strip under the
   scrollbar included); translating up by the cluster's own height lands
   its bottom edge exactly on the window bottom, scrollbar or not. The flex
   column stacks the panel over the chip and drops whichever is hidden
   (display:none), so the one visible box is always flush at the bottom;
   the gap only shows between them (the fetch-error state, panel + chip). */
#stagit-ng-statusbar {
	position: fixed;
	/* Pin to the VISUAL viewport bottom. `top: 100vh` positioned against the
	   large (toolbar-hidden) viewport on mobile, so the panel sat below the
	   visible area and slid into view only while scrolling; `bottom` tracks
	   the actual bottom, and the safe-area inset clears a notch/home bar. */
	bottom: env(safe-area-inset-bottom, 0);
	/* clear of the (gutter-stable) vertical scrollbar */
	right: 1.2em;
	display: flex;
	flex-direction: column;
	align-items: flex-end;
	gap: 0.3em;
	z-index: 1;
}

#stagit-ng-status {
	padding: 0.2em 0.6em;
	background-color: #eee;
	color: #555;
	font-family: monospace;
	font-size: 0.85em;
	/* fits "loading… 12345 KiB": the growing byte counter must not
	   resize (and so shift) the box on every fetch */
	min-width: 16ch;
	text-align: right;
	cursor: pointer; /* opens the transfer stats panel */
}

/* idle: the indicator collapses to a small always-present icon (⇅) that
   reopens the stats panel */
#stagit-ng-status.idle {
	min-width: 0;
}

/* per-type transfer stats, stacked above the status chip inside the
   bottom-anchored cluster (#stagit-ng-statusbar); a fixed overlay, so
   opening it never reflows the page */
#stagit-ng-stats {
	padding: 0.4em 0.6em 0.5em 0.8em;
	background-color: #eee;
	color: #333;
	font-family: monospace;
	font-size: 0.85em;
	max-width: min(26em, 85vw);
}
/* The stats line (share bar + net/cache numbers) grows once cache hits add
   "· N hits · P%". As a default <pre> (white-space: pre) it would not wrap,
   so the extra width pushed this fixed, right-anchored panel off the screen
   edge. Wrap it at the ` · ` separators instead — it reflows within the
   panel's own max-width and never overflows the viewport. */
#stagit-ng-stats pre {
	white-space: pre-wrap;
}
/* the stats section owns the close ×, so it anchors HERE (below the downloads
   section) rather than at the panel's top-right, where it would sit over the
   first download row and its own cancel ×. */
.stats-body {
	position: relative;
	padding-right: 1.3em; /* keep the stats text clear of the × */
}
#stagit-ng-stats .close {
	position: absolute;
	top: 0;
	right: 0;
	/* a fat corner target, well clear of the window scrollbar */
	padding: 0.1em 0.4em 0.4em 0.6em;
	cursor: pointer;
	/* button reset — reads like the old click-span */
	background: none;
	border: 0;
	color: inherit;
	font: inherit;
	line-height: 1;
}
#stagit-ng-stats pre {
	margin: 0;
	font: inherit;
}
/* Panel body order (below the download rows): connection health, throughput
   metrics, then a full-width two-tone bar on the bottom edge. The metrics wrap
   at the ` · ` separators so a narrow panel never pushes off-screen. */
#stagit-ng-stats .stats-metrics {
	margin-top: 0.4em;
	line-height: 1.35;
}
/* dark = bytes fetched over the network, light = served from the cache. The
   two spans' widths are set inline (the net/cache byte share). */
#stagit-ng-stats .stats-bar {
	display: flex;
	height: 5px;
	width: 100%;
	margin-top: 0.5em;
	border-radius: 3px;
	overflow: hidden;
	background: #ccc;
}
#stagit-ng-stats .stats-bar-net {
	background: #333;
}
#stagit-ng-stats .stats-bar-cache {
	background: #bbb;
}
/* the cache half of the stats line: matches the ░ bar segment, so the
   muted numbers read as "the gray part" without a legend; #777 keeps
   enough contrast on both the light (#eee) and dark (#111) panel */
#stagit-ng-stats .dim {
	color: #777;
}
#stagit-ng-stats p {
	margin: 0.4em 0 0;
}
#stagit-ng-stats .warn {
	color: #d70;
}
#stagit-ng-stats .retry {
	cursor: pointer;
	text-decoration: underline;
	/* button reset */
	background: none;
	border: 0;
	color: inherit;
	font: inherit;
	padding: 0;
}

/* the downloads section, folded into the top of the stats panel: one row per
   active ?dl download (spinner + name/progress + × cancel). A divider sets it
   off from the network-stats body only when BOTH are shown. */
.dl-list:not(:empty) + .stats-body {
	border-top: 1px solid #ccc;
	margin-top: 0.4em;
	padding-top: 0.4em;
}
.dl-row {
	display: flex;
	align-items: center;
	gap: 0.5em;
	padding: 0.1em 0;
}
/* name + progress stacked in a column, so the name keeps the full width */
.dl-info {
	flex: 1;
	min-width: 0;
	display: flex;
	flex-direction: column;
}
.dl-name {
	overflow: hidden;
	white-space: nowrap;
	text-overflow: ellipsis;
}
/* live "<size> · <rate>/s" readout on its own line; tabular figures so the
   digits don't jitter, muted + slightly smaller so the name stays primary */
.dl-meta {
	color: #666;
	font-size: 0.9em;
	font-variant-numeric: tabular-nums;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
.dl-meta:empty {
	display: none;
}
/* a download currently backing off after a failed request ("retry in Ns"):
   warn-coloured so a stalled/reconnecting download is visibly distinct from
   one that's actively transferring */
.dl-meta.dl-warn {
	color: #b60;
	/* A warning/error is a full sentence, not the tabular "<size> · <rate>"
	   readout: never clip it to one ellipsized line — the actionable tail
	   ("… try again shortly or reload this page") is what the user needs to
	   see. Let it wrap to as many lines as it takes. */
	white-space: normal;
	overflow: visible;
	text-overflow: clip;
}
/* visually hidden, still read by screen readers (the download live-region) */
.sr-only {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border: 0;
}
/* pure-CSS activity spinner (no external asset — CSP forbids them) */
.dl-spin {
	flex: none;
	width: 0.9em;
	height: 0.9em;
	border: 2px solid #999;
	border-top-color: transparent;
	border-radius: 50%;
	animation: dl-spin 0.7s linear infinite;
}
@keyframes dl-spin {
	to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
	.dl-spin { animation-duration: 2s; }
}
.dl-cancel {
	flex: none;
	cursor: pointer;
	padding: 0 0.3em;
	font-weight: bold;
	/* reset the native button so it reads like the old click-span */
	background: none;
	border: 0;
	color: inherit;
	font-family: inherit;
	font-size: inherit;
	line-height: 1;
}

/* Annotated-tag marker on the refs page: a small muted diamond after the
   tag name (title="annotated tag"), so a tag object with a message reads
   differently from a lightweight tag pointing straight at a commit. */
.annot {
	color: #d70;
	font-size: 0.7em;
	vertical-align: super;
	margin-left: 0.25em;
}
/* the appended "(… your time)" half on commit/tag detail dates: muted, so
   the author's own committed time stays the primary reading */
.vtz {
	color: #777;
}

.refs {
	color: #d70;
	font-size: 0.9em;
}

/* rendered markdown (about tab, *.md blobs); the global h1-h6 reset
   flattens headings, so restore a modest hierarchy here */
#readme {
	max-width: 100ch;
}
#readme h1 {
	font-size: 1.3em;
	margin: 1em 0 0.4em;
}
#readme h2 {
	font-size: 1.15em;
	margin: 1em 0 0.4em;
}
#readme h3,
#readme h4,
#readme h5,
#readme h6 {
	font-size: 1em;
	margin: 1em 0 0.4em;
}
#readme img {
	max-width: 100%;
	height: auto;
	border: 1px solid #ccc;
}
#readme pre {
	margin-left: 2ch;
	padding-left: 1ch;
	border-left: 1px solid #999;
}

@media (prefers-color-scheme: dark) {
	.target {
		background-color: #222;
	}
	#stagit-ng-status {
		background-color: #111;
		color: #aaa;
	}
	#stagit-ng-stats {
		background-color: #111;
		color: #aaa;
	}
	.dl-list:not(:empty) + .stats-body {
		border-top-color: #333;
	}
	.dl-meta {
		color: #888;
	}
	.dl-meta.dl-warn {
		color: #e90;
	}
	.dl-spin {
		border-color: #666;
		border-top-color: transparent;
	}
	#stagit-ng-stats .warn {
		color: #e90;
	}
	#stagit-ng-stats .stats-bar {
		background: #333;
	}
	#stagit-ng-stats .stats-bar-net {
		background: #ddd;
	}
	#stagit-ng-stats .stats-bar-cache {
		background: #555;
	}
	.refs {
		color: #e90;
	}
	#readme img {
		border-color: #333;
	}
}

/* first-parent commit graph (graph/<ish>) */
#graph {
	border-collapse: collapse;
}
#graph td {
	padding: 0 0.4em;
	white-space: nowrap;
	height: 18px;
	line-height: 18px;
	vertical-align: middle;
}
#graph td.gc {
	padding: 0;
	/* Fits two lanes. Nested expansion routes reserve their route-derived
	   upper bound below before progressive rows arrive. */
	min-width: 30px;
}
/* Each expansion token can add at most one nested lane. The renderer clamps
   the class at its 24-lane visual limit; ordinary/one-expansion routes carry
   no class and keep the compact 30px minimum. Explicit values avoid relying
   on CSS typed attr() or multiplication, neither of which is portable enough
   for the static frontend. */
#graph.gl2 td.gc { min-width: 44px; }
#graph.gl3 td.gc { min-width: 58px; }
#graph.gl4 td.gc { min-width: 72px; }
#graph.gl5 td.gc { min-width: 86px; }
#graph.gl6 td.gc { min-width: 100px; }
#graph.gl7 td.gc { min-width: 114px; }
#graph.gl8 td.gc { min-width: 128px; }
#graph.gl9 td.gc { min-width: 142px; }
#graph.gl10 td.gc { min-width: 156px; }
#graph.gl11 td.gc { min-width: 170px; }
#graph.gl12 td.gc { min-width: 184px; }
#graph.gl13 td.gc { min-width: 198px; }
#graph.gl14 td.gc { min-width: 212px; }
#graph.gl15 td.gc { min-width: 226px; }
#graph.gl16 td.gc { min-width: 240px; }
#graph.gl17 td.gc { min-width: 254px; }
#graph.gl18 td.gc { min-width: 268px; }
#graph.gl19 td.gc { min-width: 282px; }
#graph.gl20 td.gc { min-width: 296px; }
#graph.gl21 td.gc { min-width: 310px; }
#graph.gl22 td.gc { min-width: 324px; }
#graph.gl23 td.gc { min-width: 338px; }
#graph.gl24 td.gc { min-width: 352px; }
#graph svg.g {
	display: block;
}
#graph .gt {
	stroke: #d1332e;
	stroke-width: 2;
}
#graph .gs {
	fill: none;
	stroke: #0a58ca;
	stroke-width: 1.5;
	opacity: 0.5;
}
#graph .gs2 {
	fill: none;
	stroke: #0a58ca;
	stroke-width: 2;
}
#graph .gdash {
	stroke-dasharray: 2 3;
	opacity: 0.6;
}
#graph .gn {
	fill: #d1332e;
}
#graph .gns {
	fill: #0a58ca;
}
#graph .gmerge {
	fill: #fff;
	stroke: #d1332e;
	stroke-width: 2;
}
#graph .gmerges {
	fill: #fff;
	stroke: #0a58ca;
	stroke-width: 2;
}
#graph .gtag {
	fill: #d70;
	stroke: #fff;
	stroke-width: 1;
}
/* the commit-OID link is the same navigation as in the log/refs tables, so
   it keeps the default link (blue) color there too — no graph-specific
   gray de-emphasis. */
#graph .gmt {
	display: inline-block;
	/* Size to the subject (max-width, not width): a rigid width reserved a
	   full box even for a short subject, so the trailing .refs decoration
	   started at the box's far edge — off the right of the viewport,
	   invisible with no scrollbar. Sizing to content keeps .refs right
	   after the subject and on-screen; a long subject still ellipsizes at
	   the cap. The cap tracks the viewport (shrinks on narrow screens, up
	   to a readable max) but the wide graph lives in an .xscroll box, so
	   the page itself never gains a horizontal scrollbar either way. */
	max-width: clamp(20ch, calc(100vw - 44ch), 90ch);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	vertical-align: bottom;
}
/* Every abbreviated oid has the same character count, but the UI font is
   proportional: a row containing more wide digits can otherwise grow this
   column by a few pixels during progressive paints. Reserve the short-id
   column explicitly just like the merge/date/author columns below. */
#graph .gsha > a {
	display: inline-block;
	width: 12ch;
}
/* Keep the subject and its ref decoration inside one fixed-width flex box.
   Progressive graph paints may discover a longer subject/ref later; without
   this wrapper the browser grows the auto-layout column and visibly shifts all
   following columns. The subject flexes, the decoration ellipsizes, and the
   full subject still reveals out of flow. */
#graph .gmi {
	display: inline-flex;
	width: clamp(20ch, calc(100vw - 44ch), 90ch);
	min-width: 20ch;
	white-space: nowrap;
	vertical-align: bottom;
}
#graph .gmi > .gmt {
	flex: 1 1 auto;
	min-width: 0;
	max-width: none;
}
#graph .gmi > .refs {
	flex: 0 1 auto;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
}
#graph .cga {
	width: 20ch;
}
/* The visible subject must remain in normal flow while hovering: .gmt is
   content-sized, so absolutizing its only child collapses the wrapper and
   makes the browser recompute every graph column. The duplicate full link is
   absent from layout and becomes an overlay only on a real hover device. */
#graph .gmt-full {
	display: none;
}
@media (hover: hover) {
	#graph td.gm:hover .gmt > .gmt-full,
	#graph td.gm:focus-within .gmt > .gmt-full {
		display: block;
		position: absolute;
		top: 0;
		left: 0;
		z-index: 5;
		width: max-content;
		max-width: min(90vw, calc(100vw - 16ch));
		padding: 0 0.4em;
		overflow: visible;
		overflow-wrap: anywhere;
		white-space: normal;
		background-color: #eee;
		-webkit-user-drag: none;
	}
}
/* Touch: wrap the graph subject to full within its max-width box (no hover to
   reveal it). Placed AFTER the base rule above so it wins on source order
   (equal specificity); the subject is present at row paint, so no reflow. */
@media (hover: none) {
	#graph .gmt {
		white-space: normal;
		overflow: visible;
		overflow-wrap: anywhere;
		height: auto;
	}
}
#graph a.gexp {
	text-decoration: none;
	border: 1px solid #9bf;
	border-radius: 8px;
	padding: 0 0.4em;
	font-size: 0.9em;
	white-space: nowrap;
}
#graph a.gexp:hover {
	background-color: #eef4ff;
}
#graph tr:hover td {
	background-color: #eee;
}

@media (prefers-color-scheme: dark) {
	#graph .gmerge,
	#graph .gmerges {
		fill: #000;
	}
	#graph .gtag {
		stroke: #000;
	}
	#graph a.gexp {
		border-color: #356;
	}
	#graph a.gexp:hover {
		background-color: #123;
	}
	#graph tr:hover td {
		background-color: #111;
	}
}

/* standalone mode: remote-repo entry form and recents on the index page */
#remote input {
	font-family: monospace;
	font-size: 1em;
	background-color: inherit;
	color: inherit;
	border: 1px solid #999;
	padding: 0.15em 0.4em;
}
.remote-label {
	margin-bottom: 0.3em;
}
#recent-list,
#examples {
	margin: 0.3em 0;
	padding-left: 2em;
}

@media (prefers-color-scheme: dark) {
	#remote input {
		border-color: #444;
	}
}

/* Narrow viewports (phones). The message columns already shrink with the
   viewport, so most pages fit; a genuinely wide table (the commit graph,
   or a blob with long lines) scrolls horizontally *inside* the content
   area instead of widening the whole page — so the header, nav and the
   rest stay put at device width. */
@media (max-width: 600px) {
	/* long clone URLs / repo descriptions wrap instead of scrolling */
	.desc {
		overflow-wrap: anywhere;
	}
	/* Date + full message + author is three columns too many for a phone.
	   The author is the least essential in a commit log, so drop it and let
	   the date and the (unclipped) message have the room. Fixed layout so
	   the message column is bounded by the viewport and wraps, instead of an
	   auto table sizing to the longest subject and overflowing the page. */
	#log tr > :nth-child(3) {
		display: none;
	}
	#log {
		table-layout: fixed;
		width: 100%;
	}
	#log td:first-child {
		width: 17ch; /* the fixed-format "YYYY-MM-DD HH:MM" date, one line */
	}
}

/* Content that can't wrap — code blobs, diffs, the commit graph — scrolls
   horizontally within its own box, so a long line or a wide graph never
   widens the whole page (which would push every other page's layout too).
   Everything else (the log/summary tables) stays constrained and wraps. */
#content pre,
.xscroll {
	overflow-x: auto;
}
