
@import url('https://fonts.googleapis.com/css2?family=League+Gothic&display=swap');

.hero-standard__content.text-bg-body {
background-color: #0b0b57 !important;
color: #fff !important;
}

.hero-standard__content.hero-title {
font-family: 'League Gothic', 'Bebas Neue', Impact, 'Arial Narrow', sans-serif;
font-weight: 400;
font-size: clamp(3rem, 7vw, 5.5rem);
line-height: 1;
letter-spacing: 0.02em;
text-transform: uppercase;
margin: 0 0 1rem;
position: relative;
color: #b8975a;
}

.hero-standard__content.hero-text {
color: #fff;
font-size: 1.125rem;
line-height: 1.6;
max-width: 40rem;
margin: 0;
}

.hero-title.shimmer-text {
position: relative;
display: inline-block;
}

.hero-title.shimmer-text::after {
content: attr(data-text);
position: absolute;
inset: 0;
pointer-events: none;
background: linear-gradient(110deg, transparent 35%, rgba(255, 245, 200, 0.95) 50%, transparent 65%);
background-size: 250% 100%;
background-position: 100% 0;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}

.hero-title.shimmer-text.is-shimmering::after {
animation: gold-shimmer 1.6s ease-in-out forwards;
}

@keyframes gold-shimmer {
from { background-position: 100% 0; }
to { background-position: 0% 0; }
}

# Restoring Free Enterprise

The Cato Institute’s Restoring Free Enterprise project examines the federal government’s unprecedented involvement in the operations of the firms it regulates and charts a path toward getting Washington out of the boardroom, unwinding state ownership, and strengthening the US economy.

[Read More](https://www.cato.org/restoring-free-enterprise/american-free-enterprise-under-attack)

(function () {
var title = document.querySelector('.hero-title');
if (!title) return;

if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;

var text = title.textContent.trim();
title.innerHTML = '<span class="shimmer-text" data-text="' + text + '">' + text + '';
var span = title.querySelector('.shimmer-text');

function shimmer() {
span.classList.add('is-shimmering');
}

span.addEventListener('animationend', function () {
span.classList.remove('is-shimmering');
});

setTimeout(shimmer, 600);
setInterval(shimmer, 4000);
})();

![RFE Uncle Sam](/sites/cato.org/files/styles/aside_3x/public/2026-09/ChatGPT%20Image%20Sep%2024%2C%202026%20at%2010_33_56%20AM.png?itok=end3IhGI) 

#fed-stakes {
color-scheme: light;
/* Keep the centered layout steady when the grid adds a scrollbar. */
scrollbar-gutter: stable;
/* Closed dropdowns are invisible but still take up layout; on phones they
reach past the screen edge and let the whole page scroll sideways. */
overflow-x: clip;
--foreground: #18181b;
--muted: #71717a;
--subtle: #fafafa;
/* Controls share the cards' border, which matches the site's accordion. */
--border: var(--bs-border-color, rgb(26 23 20 / 0.3));
--radius: 0;
--interface-radius: 0;
--badge-inset: 4px;
--space: 8px;
--secondary: #52525b;
--type-title: 28px;
--type-heading: 18px;
--type-emphasis: 15px;
--type-body: 14px;
--type-label: 13px;
--type-note: 12px;
--icon-arrow: 27px;
--icon-arrow-sm: 22px;
--weight-normal: 400;
--weight-medium: 500;
--weight-strong: 600;
}
#fed-stakes,
#fed-stakes * {
box-sizing: border-box;
}
#fed-stakes [hidden] {
display: none !important;
}
/* Hovering and clicking the chart shouldn't highlight its counts and sector
labels. Text everywhere else stays selectable for copying. */
#fed-stakes #chart {
-webkit-user-select: none;
user-select: none;
}
/* Hold everything back until the chart and cards are built, then show it at once.
The chart keeps its height so the page below doesn't jump. If the script never
finishes, the content still appears after 8 seconds. */
#fed-stakes.is-loading.fed-stakes-layout {
visibility: hidden;
animation: fed-stakes-failsafe 0s 8s forwards;
}
#fed-stakes.is-loading #chart {
min-height: 436px;
}
@keyframes fed-stakes-failsafe {
to {
visibility: visible;
}
}
@keyframes fed-stakes-fade-in {
from {
opacity: 0;
}
}
#fed-stakes:not(.is-loading).fed-stakes-layout {
animation: fed-stakes-fade-in 200ms ease-out;
}
@media (prefers-reduced-motion: reduce) {
#fed-stakes:not(.is-loading).fed-stakes-layout {
animation: none;
}
}
#fed-stakes {
/* Fill the host column even when Drupal's container is flex or grid. */
width: 100%;
margin: 0;
padding: 0;
background: #fff;
color: var(--foreground);
font-family:
'Inter',
ui-sans-serif,
system-ui,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
sans-serif;
-webkit-font-smoothing: antialiased;
}
/* Fill the Drupal column; Drupal sets the max width and side gutters. */
#fed-stakes.fed-stakes-layout {
width: 100%;
margin: 0;
padding: 32px 0;
}
#fed-stakes h1,
#fed-stakes h2,
#fed-stakes h3,
#fed-stakes p {
margin: 0;
}
#fed-stakes h1 {
font-size: var(--type-title);
line-height: 1.15;
letter-spacing: -0.035em;
font-weight: var(--weight-strong);
}
#fed-stakes.hero {
display: flex;
justify-content: space-between;
align-items: center;
gap: 4px;
margin-bottom: 24px;
}
#fed-stakes.hero-copy {
display: flex;
flex-direction: column;
flex: 1;
min-width: 0;
}
#fed-stakes.intro {
margin-top: 8px;
color: var(--secondary);
font-size: var(--type-body);
line-height: 1.6;
}
/* One sentence per line: the date range, then how to read the marks. */
#fed-stakes.intro > span {
display: block;
}
/* A neutral stand-in for the chart's marks; color belongs to sectors. */
#fed-stakes.intro-square {
display: inline-block;
width: 11px;
height: 11px;
margin: 0 3px;
background: var(--secondary);
vertical-align: -0.5px;
}
#fed-stakes.card {
padding: 0;
}
#fed-stakes.chart-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
}
#fed-stakes button,
#fed-stakes input {
font: inherit;
}
#fed-stakes button {
cursor: pointer;
}
#fed-stakes button:focus-visible,
#fed-stakes summary:focus-visible,
#fed-stakes input:focus-visible,
#fed-stakes #chart:focus-visible {
outline: 2px solid #52525b;
outline-offset: 3px;
}
#fed-stakes.segmented {
display: inline-flex;
padding: 3px;
border: 1px solid var(--border);
border-radius: var(--interface-radius);
}
#fed-stakes.segmented button {
padding: 7px 14px;
border: 0;
border-radius: var(--interface-radius);
background: transparent;
color: var(--muted);
font-size: var(--type-body);
font-weight: var(--weight-medium);
}
#fed-stakes.segmented button.active {
background: var(--foreground);
color: #fff;
}
#fed-stakes.company-search {
position: relative;
width: 240px;
}
#fed-stakes.chart-meta {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 12px 20px;
margin-top: 20px;
color: var(--secondary);
font-size: var(--type-label);
}
#fed-stakes.legend {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 10px 14px;
}
#fed-stakes.key {
display: inline-flex;
align-items: center;
gap: 6px;
}
#fed-stakes.key i {
width: 9px;
height: 9px;
border-radius: 1px;
flex-shrink: 0;
}

#fed-stakes #chart {
min-height: 470px;
margin-top: 8px;
overflow-x: auto;
scrollbar-width: thin;
scrollbar-color: #a1a1aa #f4f4f5;
overscroll-behavior-x: contain;
}
#fed-stakes :is(#chart, #chart-export) svg {
display: block;
font-family: inherit;
font-variant-numeric: tabular-nums;
}
#fed-stakes :is(#chart, #chart-export) svg text {
pointer-events: none;
}
#fed-stakes.company-mark {
cursor: pointer;
outline: none;
}
#fed-stakes.mark-focus-ring {
fill: none;
stroke: #18181b;
stroke-width: 2px;
pointer-events: none;
}
#fed-stakes.keyboard-hint rect {
fill: var(--foreground);
}
#fed-stakes.keyboard-hint text {
font-size: var(--type-note);
font-weight: var(--weight-medium);
fill: #fff;
}
#fed-stakes.company-detail {
border-top: 1px solid var(--border);
padding-top: 16px;
}
#fed-stakes #detail-placeholder {
color: var(--muted);
font-size: var(--type-label);
font-weight: var(--weight-normal);
line-height: 1.5;
padding-top: 4px;
}
#fed-stakes.detail-header {
display: flex;
align-items: baseline;
gap: 12px;
}
#fed-stakes.detail-category {
color: var(--muted);
font-size: var(--type-note);
margin-bottom: 4px;
}
#fed-stakes h3 {
font-size: var(--type-heading);
letter-spacing: -0.015em;
font-weight: var(--weight-strong);
}
#fed-stakes.detail-facts {
display: flex;
flex-wrap: wrap;
gap: 12px 28px;
margin: 12px 0 10px;
font-size: var(--type-label);
}
#fed-stakes.detail-facts dt {
color: var(--muted);
font-size: var(--type-note);
margin-bottom: 3px;
}
#fed-stakes.detail-facts dd {
margin: 0;
}
#fed-stakes.detail-summary {
max-width: 900px;
font-size: var(--type-body);
line-height: 1.5;
}
#fed-stakes.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
@media (max-width: 650px) {
#fed-stakes.hero {
display: flex;
gap: 4px;
margin-bottom: 20px;
}
#fed-stakes.chart-toolbar {
flex-wrap: wrap;
gap: 12px;
}
#fed-stakes.company-search {
width: 100%;
}
#fed-stakes.chart-meta {
gap: 12px;
margin-top: 16px;
}
#fed-stakes.legend {
gap: 10px 12px;
}

#fed-stakes.detail-facts {
gap: 12px 20px;
}
}
@media (prefers-reduced-motion: reduce) {
#fed-stakes * {
scroll-behavior: auto !important;
}
}

/* Chart text shares the interface type scale; importance follows the active view. */
#fed-stakes :is(#chart, #chart-export).tick text {
font-size: var(--type-note);
font-weight: var(--weight-normal);
fill: var(--muted);
}
#fed-stakes :is(#chart, #chart-export).top-labels text,
#fed-stakes :is(#chart, #chart-export).addition-labels text {
font-size: var(--type-note);
font-weight: var(--weight-strong);
fill: var(--foreground);
}
#fed-stakes
:is(#chart, #chart-export)
svg[data-mode='cumulative']
.addition-labels
text {
font-weight: var(--weight-medium);
fill: var(--secondary);
}
#fed-stakes :is(#chart, #chart-export).final-label.count {
font-size: var(--type-label);
font-weight: var(--weight-strong);
fill: var(--foreground);
}
#fed-stakes :is(#chart, #chart-export).final-label.unit {
font-size: var(--type-note);
font-weight: var(--weight-normal);
fill: var(--secondary);
}
#fed-stakes :is(#chart, #chart-export).selection-labels text {
font-size: var(--type-note);
font-weight: var(--weight-strong);
fill: var(--foreground);
}

#fed-stakes :is(#chart, #chart-export).total-sector-labels text {
font-size: var(--type-note);
font-weight: 600;
}

#fed-stakes
:is(#chart, #chart-export)
.total-sector-labels
.sector-count {
font-size: var(--type-note);
font-weight: var(--weight-normal);
fill: var(--muted);
}

/* Monthly contribution equation, matching the demo. */
#fed-stakes :is(#chart, #chart-export).final-label.count {
font-size: var(--type-note);
font-weight: var(--weight-strong);
}
#fed-stakes
:is(#chart, #chart-export)
svg[data-mode='cumulative']
.addition-labels
text {
font-size: var(--type-note);
font-weight: var(--weight-normal);
fill: var(--secondary);
}
#fed-stakes :is(#chart, #chart-export).tick.summary-tick text {
font-weight: var(--weight-strong);
}
#fed-stakes :is(#chart, #chart-export).equals-label {
font-size: var(--type-note);
font-weight: var(--weight-strong);
fill: var(--foreground);
}
#fed-stakes
:is(#chart, #chart-export)
svg.arithmetic-labels
.tick.summary-tick
text,
#fed-stakes
:is(#chart, #chart-export)
svg.arithmetic-labels
.equals-label {
font-weight: var(--weight-normal);
}
#fed-stakes
:is(#chart, #chart-export)
svg.arithmetic-labels
.equals-label,
#fed-stakes
:is(#chart, #chart-export)
svg.arithmetic-labels
.final-label
.count {
font-size: var(--type-note);
font-weight: var(--weight-normal);
fill: var(--secondary);
}

#fed-stakes {
--company-card-height: 230px;
}
/* Styles for the company-card exploration copy. */
#fed-stakes.company-directory {
border-top: 0;
padding-top: 20px;
padding-bottom: 0;
}
#fed-stakes.directory-heading {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
}
#fed-stakes.directory-heading h2 {
font-weight: var(--weight-strong);
font-size: var(--type-heading);
letter-spacing: -0.02em;
}
#fed-stakes.directory-heading {
margin-bottom: 20px;
}
#fed-stakes.company-card {
scroll-margin-block: 24px;
cursor: pointer;
position: relative;
min-height: var(--company-card-height);
/* Matches the site's Methodology accordion, which uses Bootstrap's border
color. The fallback approximates it where Bootstrap isn't loaded. */
border: var(--bs-border-width, 1px) solid
var(--bs-border-color, rgb(26 23 20 / 0.3));
/* Square, like the accordion; the site's own radius variable is rounded. */
border-radius: 0;
padding: 16px;
background: #fff;
transition:
background-color 120ms,
box-shadow 120ms;
}
/* Previewed from the chart: tint only, a step below the selected card's edge. */
#fed-stakes.company-card.is-active {
background: #fafafa;
}
#fed-stakes.company-card h3 button {
border: 0;
padding: 0;
background: transparent;
color: inherit;
text-align: left;
font: inherit;
}
#fed-stakes.company-card-header {
display: flex;
align-items: baseline;
justify-content: flex-start;
flex-direction: row;
flex-wrap: wrap;
gap: 4px 8px;
min-height: 38px;
}
#fed-stakes.company-card-header h3 {
flex: 1 1 125px;
min-width: 0;
overflow-wrap: break-word;
font-size: var(--type-heading);
line-height: 1.3;
}
#fed-stakes.company-card.detail-category {
flex: 0 1 auto;
max-width: 100%;
margin: 0;
font-weight: var(--weight-medium);
}
@media (prefers-reduced-motion: reduce) {
#fed-stakes.company-card {
transition: none;
}
}

#fed-stakes.chart-panel {
min-width: 0;
background: #fff;
}
#fed-stakes.search-actions {
display: flex;
align-items: center;
gap: 10px;
min-width: 0;
}
#fed-stakes.chart-explainer,
#fed-stakes.selection-context {
font-size: var(--type-label);
line-height: 1.5;
color: var(--secondary);
margin-top: 12px;
}
#fed-stakes.selection-context {
margin-top: 4px;
min-height: 36px;
}
#fed-stakes.company-count {
color: var(--secondary);
font-size: var(--type-label);
}
#fed-stakes.company-card.detail-facts {
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 10px;
margin: 8px 0 0;
}
#fed-stakes.fact-stage dd {
font-size: var(--type-body);
font-weight: 600;
line-height: 1.4;
}
#fed-stakes.fact-amount dd {
font-size: var(--type-emphasis);
font-weight: 600;
}
#fed-stakes.fact-date {
grid-column: 1 / -1;
}
#fed-stakes.fact-date dt,
#fed-stakes.fact-date dd {
display: inline;
}
#fed-stakes.fact-date dt {
margin-right: 6px;
}
#fed-stakes.selected-label {
display: inline;
margin-left: 8px;
font-size: var(--type-note);
font-weight: 600;
color: #3f3f46;
}
#fed-stakes.selected-label[hidden] {
display: none;
}
#fed-stakes.deal-disclosure {
margin-top: 16px;
}
#fed-stakes.deal-disclosure summary {
cursor: pointer;
font-size: var(--type-label);
color: #3f3f46;
}
#fed-stakes.deal-disclosure.detail-summary {
margin-top: 10px;
}
@media (max-width: 650px) {
#fed-stakes.search-actions {
width: 100%;
}
#fed-stakes.search-actions.company-search {
flex: 1;
min-width: 0;
}
}

#fed-stakes.sector-tabs {
position: relative;
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
padding: 3px 2px;
}
#fed-stakes.sector-tabs select {
min-width: 0;
width: 260px;
max-width: 100%;
flex: 0 1 260px;
min-height: 36px;
border: 1px solid var(--border);
border-radius: var(--interface-radius);
background: white;
padding: 8px 10px;
font: inherit;
font-size: var(--type-label);
color: var(--foreground);
}
#fed-stakes.directory-toolbar button:focus-visible,
#fed-stakes.directory-toolbar select:focus-visible {
outline: 2px solid #71717a;
outline-offset: 3px;
}
#fed-stakes.sector-tab-divider {
flex: 0 0 1px;
align-self: stretch;
margin: 5px 6px;
background: var(--border);
}
#fed-stakes.sector-tabs button {
flex: 0 0 auto;
border: 1px solid var(--border);
border-radius: var(--interface-radius);
background: white;
padding: 8px 10px;
font-size: var(--type-label);
color: var(--secondary);
}
#fed-stakes.sector-tabs button[aria-pressed='true'] {
color: var(--foreground);
border-color: #d4d4d8;
background: #f4f4f5;
font-weight: 600;
}
#fed-stakes.company-carousel {
display: flex;
gap: 16px;
overflow-x: auto;
padding: 4px var(--carousel-end-space, 0px) 4px
var(--carousel-start-space, 0px);
scroll-snap-type: x mandatory;
overscroll-behavior-x: contain;
scrollbar-width: none;
align-items: flex-start;
overflow-y: hidden;
}
#fed-stakes.company-carousel.company-card {
flex: 0 0 var(--carousel-card-width, 256px);
min-width: 0;
scroll-snap-align: start;
}
#fed-stakes.company-carousel.is-centered.company-card {
scroll-snap-align: center;
}
#fed-stakes.company-carousel:focus-visible {
outline: 2px solid #71717a;
outline-offset: 3px;
}
#fed-stakes.company-carousel::-webkit-scrollbar {
display: none;
}
#fed-stakes.carousel-frame {
position: relative;
padding-inline: 0;
}
#fed-stakes.carousel-frame.is-expanded {
padding-inline: 0;
}
#fed-stakes.carousel-arrow {
position: absolute;
top: var(--carousel-arrow-top);
transform: translateY(-50%);
z-index: 2;
display: grid;
place-items: center;
width: 36px;
height: 36px;
padding: 0 0 3px;
border: 1px solid #d4d4d8;
border-radius: var(--interface-radius);
background: #fff;
box-shadow: 0 1px 2px #18181b0d;
color: #27272a;
font-size: var(--icon-arrow);
line-height: 1;
}
#fed-stakes.carousel-arrow:not(:disabled):hover {
background: #f4f4f5;
}
#fed-stakes.carousel-frame.carousel-arrow:disabled {
opacity: 0.35;
cursor: default;
}
#fed-stakes.carousel-frame.carousel-arrow:focus-visible {
outline: 2px solid #71717a;
outline-offset: 3px;
}
#fed-stakes #carousel-previous {
left: 4px;
}
#fed-stakes #carousel-next {
right: 4px;
}
#fed-stakes.company-card.detail-facts dd {
overflow-wrap: anywhere;
}
@media (max-width: 650px) {
#fed-stakes.company-card-header {
min-height: 0;
}
#fed-stakes.company-card.detail-facts {
margin-top: 16px;
}
}

/* Directory review refinements. */
#fed-stakes.chart-takeaway {
margin-top: 12px;
font-size: var(--type-emphasis);
font-weight: 600;
}
#fed-stakes.methodology {
margin-top: 12px;
color: var(--secondary);
font-size: var(--type-label);
line-height: 1.6;
}
#fed-stakes.methodology summary {
cursor: pointer;
width: fit-content;
text-decoration: underline;
text-underline-offset: 3px;
}
#fed-stakes.methodology p {
margin-top: 8px;
max-width: 72ch;
}
#fed-stakes.selection-context {
min-height: 18px;
}
#fed-stakes.chart-meta[hidden] {
display: none;
}
#fed-stakes.directory-heading {
flex-wrap: wrap;
gap: 12px;
margin-bottom: 14px;
}
#fed-stakes.directory-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px 24px;
margin-bottom: 12px;
}
#fed-stakes.directory-controls {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
}
#fed-stakes #carousel-position {
color: var(--secondary);
font-size: var(--type-label);
font-variant-numeric: tabular-nums;
}
#fed-stakes.directory-controls.carousel-arrow {
position: static;
transform: none;
width: 36px;
height: 36px;
box-shadow: none;
border-radius: var(--interface-radius);
font-size: var(--icon-arrow-sm);
}
#fed-stakes.directory-controls.carousel-arrow:disabled {
opacity: 0.35;
cursor: default;
}
#fed-stakes.directory-toolbar button,
#fed-stakes.directory-toolbar select {
height: 36px;
border: 1px solid var(--border);
border-radius: var(--interface-radius);
background: #fff;
color: var(--foreground);
font-family: inherit;
}
#fed-stakes.directory-toolbar button:not(:disabled):hover,
#fed-stakes.directory-toolbar select:hover {
background: #f4f4f5;
}
#fed-stakes.directory-context {
margin: 0 0 12px;
color: var(--secondary);
font-size: var(--type-label);
line-height: 1.5;
}

#fed-stakes.company-carousel.is-expanded {
display: grid;
align-items: stretch;
/* Up to three columns, each at least the card minimum (see CARD_MIN_WIDTH). */
grid-template-columns: repeat(
auto-fill,
minmax(min(100%, max(256px, (100% - 32px) / 3)), 1fr)
);
padding: 4px 0;
overflow: visible;
}
/* Selected: a tinted card with a mid-gray edge; the chart carries the emphasis. */
#fed-stakes.company-card.is-selected {
border-color: #a1a1aa;
background: #fafafa;
}
#fed-stakes.detail-sources {
font-size: var(--type-label);
color: var(--secondary);
margin-top: 12px;
}
#fed-stakes.detail-sources a {
color: inherit;
text-underline-offset: 3px;
display: inline-block;
margin-right: 8px;
}
#fed-stakes.selected-company-link {
font-size: var(--type-label);
margin-top: 8px;
}
#fed-stakes.selected-company-link button {
background: none;
border: 0;
padding: 0;
text-decoration: underline;
text-underline-offset: 3px;
}

#fed-stakes.directory-sector-label {
font-weight: 600;
white-space: nowrap;
}

#fed-stakes :is(#chart, #chart-export).final-label.count {
font-size: var(--type-note);
font-weight: var(--weight-strong);
}

#fed-stakes
:is(#chart, #chart-export)
svg[data-mode='cumulative']
.addition-labels
text {
font-size: var(--type-note);
font-weight: var(--weight-normal);
fill: var(--secondary);
}
#fed-stakes :is(#chart, #chart-export).tick.summary-tick text {
font-weight: var(--weight-strong);
}

#fed-stakes
:is(#chart, #chart-export)
.total-sector-labels
.sector-count {
font-size: var(--type-note);
font-weight: var(--weight-normal);
fill: var(--muted);
/* Counts don't align with other numbers, so tabular 1s only look loose. */
font-variant-numeric: proportional-nums;
}

#fed-stakes :is(#chart, #chart-export).equals-label {
font-size: var(--type-note);
font-weight: var(--weight-strong);
fill: var(--foreground);
}

#fed-stakes
:is(#chart, #chart-export)
svg.arithmetic-labels
.tick.summary-tick
text,
#fed-stakes
:is(#chart, #chart-export)
svg.arithmetic-labels
.equals-label {
font-weight: var(--weight-normal);
}
#fed-stakes
:is(#chart, #chart-export)
svg.arithmetic-labels
.equals-label,
#fed-stakes
:is(#chart, #chart-export)
svg.arithmetic-labels
.final-label
.count {
font-size: var(--type-note);
font-weight: var(--weight-normal);
fill: var(--secondary);
}

/* Monthly totals step back so the final total leads. */
#fed-stakes
:is(#chart, #chart-export)
svg[data-mode='cumulative']
.top-labels
text {
font-weight: var(--weight-normal);
}
#fed-stakes
:is(#chart, #chart-export)
svg[data-mode='cumulative']
.final-label
.count {
font-weight: var(--weight-strong);
}

/* Bring the chart closer to search and soften the headline scale. */
#fed-stakes.hero h1 {
font-size: var(--type-title);
}
#fed-stakes #chart {
margin-top: 4px;
min-height: 458px;
}

#fed-stakes.chart-panel >.exploration-toolbar {
justify-content: flex-start;
}
#fed-stakes.exploration-toolbar {
gap: 12px 32px;
}
#fed-stakes.exploration-filters {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 12px 24px;
flex: 1 1 100%;
width: 100%;
min-width: 0;
}
#fed-stakes.exploration-toolbar.search-actions {
flex: 1 1 0;
width: auto;
min-width: 0;
}
#fed-stakes.exploration-toolbar.sector-tabs {
flex: 1 1 230px;
}
#fed-stakes.exploration-toolbar.sector-tabs select {
width: 180px;
flex: 1 1 180px;
}
#fed-stakes.sector-option-swatch {
width: 9px;
height: 9px;
flex: none;
}
#fed-stakes.sector-option-count {
margin-left: auto;
color: var(--secondary);
}
#fed-stakes.filter-label {
display: block;
margin-bottom: 6px;
font-size: var(--type-label);
font-weight: 500;
color: var(--secondary);
}
#fed-stakes.sector-field {
flex: 1 1 0;
min-width: 0;
}
#fed-stakes.exploration-filters {
align-items: flex-end;
}
#fed-stakes.exploration-toolbar.search-actions {
align-items: flex-end;
}
#fed-stakes.carousel-navigation {
display: flex;
align-items: center;
gap: 8px;
white-space: nowrap;
}
#fed-stakes.exploration-toolbar.directory-controls {
gap: 20px;
flex: 0 0 auto;
flex-wrap: nowrap;
margin-left: auto;
}
@media (max-width: 650px) {
#fed-stakes.exploration-filters {
flex-basis: 100%;
flex-grow: 1;
}
#fed-stakes.exploration-toolbar.directory-controls {
margin-left: 0;
}
}
#fed-stakes.exploration-toolbar.company-search {
flex: 1;
min-width: 0;
}
#fed-stakes.exploration-toolbar.sector-tabs {
padding: 0;
}
/* Compact reading cards: full details, natural height, no action footer. */
#fed-stakes.company-card {
display: flex;
flex-direction: column;
min-height: 0;
padding: 16px;
cursor: pointer;
}
#fed-stakes.company-card-header {
flex-shrink: 0;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 6px;
min-height: 0;
}
#fed-stakes.company-card-header h3 {
flex: 1 1 auto;
display: inline-flex;
align-items: flex-start;
gap: 4px;
min-width: 0;
max-width: 100%;
width: auto;
font-size: var(--type-heading);
line-height: 1.3;
}
#fed-stakes.company-card-header.company-select {
flex: 0 1 auto;
min-width: 0;
overflow-wrap: anywhere;
line-height: inherit;
border-radius: var(--interface-radius);
text-decoration-thickness: 1px;
text-underline-offset: 3px;
}
#fed-stakes.company-select:hover,
#fed-stakes.company-select:focus-visible {
text-decoration-line: underline;
}
#fed-stakes.company-select:focus-visible {
outline: 2px solid #52525b;
outline-offset: 3px;
}
#fed-stakes.company-card-date {
flex: none;
white-space: nowrap;
font-size: var(--type-label);
line-height: 1.5;
color: var(--secondary);
}
#fed-stakes.company-card.detail-category {
margin: 0;
color: var(--secondary);
font-size: var(--type-label);
font-weight: 400;
line-height: 1.5;
overflow-wrap: anywhere;
}
#fed-stakes.company-card.detail-summary {
margin-top: 10px;
font-size: var(--type-body);
line-height: 1.5;
}
#fed-stakes.company-announcement {
display: inline-flex;
align-items: center;
justify-content: center;
position: relative;
margin-left: 4px;
width: 12px;
height: 12px;
vertical-align: middle;
color: var(--muted);
border-radius: var(--interface-radius);
cursor: pointer;
text-decoration: none;
}
/* Expand the hit area without adding height to the description's line box.
Start at the gap's edge so the target does not cover the preceding word. */
#fed-stakes.company-announcement::before {
content: '';
position: absolute;
left: -4px;
top: -10px;
width: 32px;
height: 32px;
border-radius: var(--interface-radius);
}
#fed-stakes.company-announcement:hover,
#fed-stakes.company-announcement:focus-visible {
color: var(--foreground);
background: #f4f4f5;
}
#fed-stakes.company-announcement-ending {
white-space: nowrap;
}
#fed-stakes.company-announcement:focus-visible {
outline: 2px solid #52525b;
outline-offset: 3px;
}
#fed-stakes.company-announcement svg {
width: 12px;
height: 12px;
flex: none;
}
/* Card details: one labeled line each, labels in a narrow column so the
values line up and get the card's full width. */
#fed-stakes.company-card.detail-facts {
grid-template-columns: max-content minmax(0, 1fr);
align-items: baseline;
margin: 12px 0 0;
gap: 4px 12px;
align-content: start;
flex-shrink: 0;
padding-top: 0;
}
#fed-stakes.company-card.detail-facts > div {
display: contents;
}
#fed-stakes.company-card.detail-facts dt {
margin: 0;
color: var(--muted);
font-size: var(--type-note);
line-height: 1.4;
}
#fed-stakes.company-card.detail-facts dd {
font-size: var(--type-label);
line-height: 1.45;
font-weight: 400;
/* Values in the summary's ink, so they stand clear of their labels. */
color: var(--foreground);
overflow-wrap: break-word;
}
/* Space alone sets the summary apart from the header above it. */
#fed-stakes.company-card.detail-summary {
margin-top: 16px;
}
#fed-stakes.company-card.summary-actions {
margin-top: 6px;
}
#fed-stakes.company-card.summary-toggle {
font-family: inherit;
padding: 0;
border: 0;
background: none;
/* Muted so nine of these in the grid don't compete with the text. */
color: var(--muted);
font-size: var(--type-label);
text-decoration: underline;
text-decoration-color: #a1a1aa;
text-underline-offset: 3px;
}
#fed-stakes.company-card.summary-toggle:hover {
color: var(--foreground);
text-decoration-color: currentColor;
}
#fed-stakes.company-card.summary-toggle:focus-visible {
outline: 2px solid #52525b;
outline-offset: 3px;
}
/* Status icons repeat the chart's square fills: solid, half, or pale. */
#fed-stakes.status-icon {
display: inline-block;
width: 10px;
height: 10px;
margin-right: 6px;
vertical-align: -0.5px;
}
#fed-stakes.company-card.card-meta {
display: flex;
justify-content: space-between;
align-self: stretch;
flex-wrap: wrap;
gap: 0 12px;
margin: 0;
color: var(--secondary);
font-size: var(--type-note);
line-height: 1.4;
}
#fed-stakes.company-card :is(.card-sector,.card-agency) {
margin: 0;
font-size: var(--type-label);
line-height: 1.45;
}
/* Dark for content (name, summary), gray for details. The square carries
the sector color, so the line itself stays gray like the agency. */
#fed-stakes.company-card.card-sector {
color: var(--secondary);
}
/* Supporting detail, a step below the summary. */
#fed-stakes.company-card.card-agency {
color: var(--secondary);
}
#fed-stakes.company-card :is(.card-sector-name,.card-status) {
white-space: nowrap;
}
#fed-stakes.company-card.card-divider {
color: #d4d4d8;
font-weight: 400;
}
#fed-stakes.company-card.detail-facts:empty {
display: none;
}
/* A centered text link, not a full-width bar; the padding keeps a
44px tap target. */
#fed-stakes #show-all-cards {
display: block;
min-height: 44px;
margin: 8px auto 0;
padding: 0 12px;
border: 0;
background: none;
color: var(--muted);
font-family: inherit;
font-size: var(--type-label);
text-decoration: underline;
text-underline-offset: 3px;
cursor: pointer;
}
#fed-stakes #show-all-cards:hover {
color: var(--foreground);
}
#fed-stakes #show-all-cards[hidden] {
display: none;
}
#fed-stakes.metadata-stake dd {
font-weight: 600;
}
#fed-stakes.intro.status-icon {
width: 11px;
height: 11px;
margin: 0 4px 0 0;
}
#fed-stakes.intro.status-key {
margin-right: 12px;
white-space: nowrap;
}
#fed-stakes.intro.status-key:last-child {
margin-right: 0;
}
#fed-stakes.company-carousel {
align-items: flex-start;
}
#fed-stakes.company-directory {
padding-top: 0;
padding-bottom: 0;
}
#fed-stakes #chart {
min-height: 0;
}

/* Chart first; compact filters sit between the chart and the cards. */
#fed-stakes.hero {
margin-bottom: 4px;
}
#fed-stakes.chart-panel {
display: flex;
flex-direction: column;
}
#fed-stakes.chart-panel >.exploration-toolbar {
order: 1;
margin: 12px 0 24px;
}
#fed-stakes.rfe-controls.combobox > input[role='combobox'] {
height: 30px;
font-size: var(--type-label);
}
/* A committed sector keeps its legend swatch in the field. */
#fed-stakes.rfe-controls.combobox.has-swatch {
position: relative;
}
#fed-stakes.rfe-controls.combobox.has-swatch::before {
content: '';
position: absolute;
top: 50%;
left: 11px;
transform: translateY(-50%);
width: 9px;
height: 9px;
background: var(--swatch-color);
pointer-events: none;
z-index: 1;
}
#fed-stakes
.rfe-controls
.combobox.has-swatch
> input[role='combobox'] {
padding-inline-start: 28px;
}
/* Filters share the directory toolbar row with pagination. */
#fed-stakes.company-directory {
margin-top: 28px;
}
#fed-stakes.directory-heading > h2 {
flex: 1 1 100%;
margin: 0;
}
/* Inputs shrink toward 160px before the pager wraps to its own line. */
#fed-stakes.directory-filters {
flex: 1 1 504px;
max-width: 960px;
min-width: 0;
}
/* When the pager wraps under the filters, it sits at the right, over the
cards it moves. The dashboard keeps Download at the left; see below. */
/* The grid shows every match, so the count and pager are not needed. */
#fed-stakes.toolbar-actions.directory-controls {
display: none;
}
#fed-stakes:not(.is-dashboard).toolbar-actions {
margin-left: auto;
}
#fed-stakes.directory-filters.exploration-filters {
flex-wrap: nowrap;
gap: 12px;
width: 100%;
}
#fed-stakes.directory-filters.sector-field,
#fed-stakes.directory-filters.search-actions {
flex: 1 1 0;
width: auto;
max-width: 260px;
}
/* space-between keeps the pager right on a shared row and left once it wraps. */
#fed-stakes.directory-heading.directory-controls {
margin-left: 0;
}
/* Breakpoints follow the embed's column, not the window beside Drupal's sidebar. */
#fed-stakes.company-directory {
container-type: inline-size;
}
@container (max-width: 820px) {
#fed-stakes.directory-filters {
flex-basis: 100%;
max-width: none;
}
#fed-stakes.directory-filters.sector-field,
#fed-stakes.directory-filters.search-actions {
max-width: none;
}
/* Company search takes its own row; the three dropdowns share the next. */
#fed-stakes.directory-filters.exploration-filters {
flex-wrap: wrap;
}
#fed-stakes.directory-filters.search-actions {
flex-basis: 100%;
}
#fed-stakes.directory-filters.sector-field.status-field {
flex: 1 1 0;
min-width: 0;
}
}
/* Wide layouts tuck the title into the chart's empty upper-left corner. */
#fed-stakes.fed-stakes-layout {
container-type: inline-size;
}
@container (min-width: 750px) {
#fed-stakes.hero {
position: relative;
z-index: 1;
max-width: 46%;
margin-bottom: -96px;
}
#fed-stakes.hero h1 {
white-space: nowrap;
}
}
/* Medium layouts keep the title above but tuck the keys into the chart's
empty corner when placeIntro() finds room. */
#fed-stakes.intro-inset.hero {
position: relative;
z-index: 1;
}
#fed-stakes.intro-inset.intro {
position: absolute;
top: 100%;
left: 0;
}
/* On their own row, the date range and key run on as two sentences. */
#fed-stakes.intro-inline.intro > span {
display: inline;
}
#fed-stakes.intro-inline.intro > span::after {
content: '.';
}
/* With the key on its own rows, the chart's empty headroom above its
tallest column (about 45px) would sit under the key as a gap. Pull the
chart up into it, leaving about 25px above the "36". */
#fed-stakes.intro-inline #chart {
margin-top: -26px;
}
/* The three status keys move as a block: if they don't fit after the
lead-in, all of them wrap together. On a very narrow screen the block
can still break between keys, never inside one. */
#fed-stakes.intro-inline.intro > #status-key {
display: inline-block;
max-width: 100%;
}
/* The lead-in ends in a colon when the status key follows it. */
#fed-stakes.intro-inline
.intro
> #chart-key:has(+ #status-key:not([hidden]))::after {
content: none;
}
/* The dashboard page supplies its own heading, so only the title drops
away. The key stays, tucked into the chart's empty corner as on the
main page. */
#fed-stakes.is-dashboard.hero h1 {
display: none;
}
/* Without the title, the wide layout's -96px pull (sized for title plus
key) lifts the chart about 40px above the key, leaving an empty band.
Pull it up by the key's own height instead, so the key sits on the
chart's top edge. */
@container (min-width: 750px) {
#fed-stakes.is-dashboard.hero {
margin-bottom: -56px;
}
}
/* Drupal already spaces the page intro from the embed, so ours drops to zero. */
#fed-stakes.is-dashboard.fed-stakes-layout {
padding-top: 0;
}
/* The dashboard is grid-only, so the count and layout toggle drop away too. */
#fed-stakes.is-dashboard.directory-controls {
display: none;
}
/* Below the one-row width, the controls pair up: Company and Sector, then
Agency, Status and Download. The wrappers step aside so the fields and
Download share one grid. */
@container (max-width: 820px) {
#fed-stakes:not(.is-dashboard).directory-heading {
display: grid;
/* Two equal columns, like the card grid; Download shares Status's cell. */
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
grid-template-areas:
'company sector'
'agency status';
/* Matches the card grid's gap, so the columns line up. */
gap: 16px;
}
#fed-stakes:not(.is-dashboard)
:is(.directory-filters,.exploration-filters,.toolbar-actions) {
display: contents;
}
#fed-stakes:not(.is-dashboard).directory-filters.search-actions {
grid-area: company;
}
#fed-stakes:not(.is-dashboard).directory-filters.sector-field {
grid-area: sector;
min-width: 0;
}
#fed-stakes:not(.is-dashboard).directory-filters.agency-field {
grid-area: agency;
}
#fed-stakes:not(.is-dashboard)
.directory-filters
.status-field:not(.agency-field) {
grid-area: status;
/* Room for the 36px Download button and a 12px gap. */
margin-right: 48px;
}
#fed-stakes:not(.is-dashboard).directory-heading.download-menu {
grid-area: status;
justify-self: end;
}
}
/* The dashboard keeps its own phone layout; there the fields stack. */
@container (max-width: 460px) {
#fed-stakes.is-dashboard.directory-filters.exploration-filters {
flex-wrap: wrap;
}
#fed-stakes.is-dashboard.directory-filters.sector-field,
#fed-stakes.is-dashboard.directory-filters.search-actions {
flex-basis: 100%;
}
}

#fed-stakes.rfe-controls.combobox {
--font-sans:
'Inter', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji',
'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
--font-mono:
'Geist Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
'Liberation Mono', 'Courier New', monospace;
--color-black: #000;
--color-white: #fff;
--spacing: 0.25rem;
--container-xs: 20rem;
--container-sm: 24rem;
--container-md: 28rem;
--container-lg: 32rem;
--text-xs: var(--type-label);
--text-xs--line-height: calc(1 / 0.75);
--text-sm: var(--type-body);
--text-sm--line-height: calc(1.25 / 0.875);
--text-base: 1rem;
--text-base--line-height: calc(1.5 / 1);
--text-lg: 1.125rem;
--text-lg--line-height: calc(1.75 / 1.125);
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--tracking-tight: -0.025em;
--tracking-widest: 0.1em;
--leading-snug: 1.375;
--leading-normal: 1.5;
--leading-relaxed: 1.625;
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--radius-2xl: 1rem;
--radius-4xl: 2rem;
--ease-out: cubic-bezier(0, 0, 0.2, 1);
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
--animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
--blur-xs: 4px;
--aspect-video: 16 / 9;
--default-transition-duration: 0.15s;
--default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
--default-font-family: var(--font-sans);
--default-mono-font-family: var(--font-mono);
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
--radius: 0.625rem;
--background: oklch(100% 0 0);
--foreground: oklch(14.5% 0 0);
--card: oklch(100% 0 0);
--card-foreground: oklch(14.5% 0 0);
--popover: oklch(100% 0 0);
--popover-foreground: oklch(14.5% 0 0);
--primary: oklch(20.5% 0 0);
--primary-foreground: oklch(98.5% 0 0);
--secondary: oklch(97% 0 0);
--secondary-foreground: oklch(20.5% 0 0);
--muted: oklch(97% 0 0);
--muted-foreground: oklch(55.6% 0 0);
--accent: oklch(97% 0 0);
--accent-foreground: oklch(20.5% 0 0);
--destructive: oklch(57.7% 0.245 27.325);
--border: oklch(92.2% 0 0);
--input: oklch(92.2% 0 0);
--ring: oklch(70.8% 0 0);
--chart-1: #93c5fd;
--chart-2: #3b82f6;
--chart-3: #2563eb;
--chart-4: #1d4ed8;
--chart-5: #1e40af;
--sidebar: oklch(98.5% 0 0);
--sidebar-foreground: oklch(14.5% 0 0);
--sidebar-primary: oklch(20.5% 0 0);
--sidebar-primary-foreground: oklch(98.5% 0 0);
--sidebar-accent: oklch(97% 0 0);
--sidebar-accent-foreground: oklch(20.5% 0 0);
--sidebar-border: oklch(92.2% 0 0);
--sidebar-ring: oklch(70.8% 0 0);
--sidebar-width: 16rem;
--sidebar-mobile-width: 18rem;
--scrollbar-track: transparent;
--scrollbar-thumb: var(--border);
--scrollbar-width: 0.625rem;
--scrollbar-sm-width: 0.375rem;
--scrollbar-radius: 9999px;
--chevron-down-icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="oklch(0.556 0 0)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down-icon lucide-chevron-down"><path d="m6 9 6 6 6-6"/>');
--chevron-down-icon-50: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="oklch(0.556 0 0 / 0.5)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down-icon lucide-chevron-down"><path d="m6 9 6 6 6-6"/>');
--check-icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="oklch(0.556 0 0)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check-icon lucide-check"><path d="M20 6 9 17l-5-5"/>');
}
#fed-stakes
.rfe-controls
.button-group:has(
> :is(.dropdown-menu,.popover,.select)
> [data-popover][aria-hidden='false']
) {
z-index: 50;
position: relative;
}
#fed-stakes.rfe-controls.combobox {
flex-direction: column;
width: fit-content;
display: inline-flex;
position: relative;
}
#fed-stakes.rfe-controls.combobox > input[role='combobox'] {
--tw-outline-style: none;
outline-style: none;
width: 100%;
min-width: 0;
}
@media (forced-colors: active) {
#fed-stakes.rfe-controls.combobox > input[role='combobox'] {
outline-offset: 2px;
outline: 2px solid #0000;
}
}
#fed-stakes
.rfe-controls
.combobox
> input[role='combobox']::placeholder {
color: var(--color-muted-foreground);
}
#fed-stakes.rfe-controls.combobox > input[role='combobox']:disabled {
pointer-events: none;
cursor: not-allowed;
opacity: 0.5;
}
#fed-stakes.rfe-controls.combobox.input-group input[role='combobox'],
#fed-stakes
.rfe-controls
.combobox
[data-popover]
input[role='combobox'] {
--tw-outline-style: none;
outline-style: none;
min-width: 0;
}
@media (forced-colors: active) {
#fed-stakes
.rfe-controls
.combobox
.input-group
input[role='combobox'],
#fed-stakes
.rfe-controls
.combobox
[data-popover]
input[role='combobox'] {
outline-offset: 2px;
outline: 2px solid #0000;
}
}
#fed-stakes
.rfe-controls
:is(
.combobox.input-group input[role='combobox'],
.combobox [data-popover] input[role='combobox']
)::placeholder {
color: var(--color-muted-foreground);
}
#fed-stakes
.rfe-controls
:is(
.combobox.input-group input[role='combobox'],
.combobox [data-popover] input[role='combobox']
):disabled {
pointer-events: none;
cursor: not-allowed;
opacity: 0.5;
}
#fed-stakes.rfe-controls.combobox > svg,
#fed-stakes.rfe-controls.combobox-trigger-icon {
pointer-events: none;
flex-shrink: 0;
position: absolute;
}
#fed-stakes
.rfe-controls
.combobox:has(>.combobox-chips)
>.combobox-trigger-icon {
display: none;
}
#fed-stakes.rfe-controls.combobox [data-clear] {
width: calc(var(--spacing) * 6);
height: calc(var(--spacing) * 6);
cursor: default;
appearance: none;
border-style: var(--tw-border-style);
color: currentColor;
background-color: #0000;
border-width: 0;
flex-shrink: 0;
justify-content: center;
align-items: center;
padding: 0;
display: inline-flex;
}
#fed-stakes.rfe-controls.combobox [data-clear]:disabled {
pointer-events: none;
opacity: 0.5;
}
#fed-stakes.rfe-controls.combobox [data-clear][hidden],
#fed-stakes
.rfe-controls
.combobox
.input-group:has([data-clear]:not([hidden]))
button[aria-haspopup='listbox'] {
display: none;
}
#fed-stakes.rfe-controls.combobox [data-popover] {
isolation: isolate;
min-width: max(100%, 9rem);
max-width: calc(100vw - 32px);
overflow: hidden;
}
#fed-stakes.rfe-controls.combobox [role='listbox'] {
--tw-outline-style: none;
outline-style: none;
overflow: hidden auto;
}
@media (forced-colors: active) {
#fed-stakes.rfe-controls.combobox [role='listbox'] {
outline-offset: 2px;
outline: 2px solid #0000;
}
}
#fed-stakes
.rfe-controls
.combobox
[role='listbox']
[role='group']:not(:has([role='option']:not([aria-hidden='true']))) {
display: none;
}
#fed-stakes
.rfe-controls
.combobox
[role='listbox']:not(
:has([data-value]:not([aria-hidden='true']))
):before {
text-overflow: ellipsis;
white-space: nowrap;
justify-content: center;
align-items: center;
display: flex;
overflow: hidden;
}
#fed-stakes
.rfe-controls
.combobox
[role='listbox'][data-empty]:not(
:has([data-value]:not([aria-hidden='true']))
):before {
--tw-content: attr(data-empty);
content: var(--tw-content);
}
#fed-stakes
.rfe-controls
.combobox
[role='listbox']:not([data-empty]):not(
:has([data-value]:not([aria-hidden='true']))
):before {
--tw-content: 'No results found';
content: var(--tw-content);
}
#fed-stakes.rfe-controls.combobox [role='option'] {
cursor: default;
text-overflow: ellipsis;
white-space: nowrap;
--tw-outline-style: none;
outline-style: none;
align-items: center;
width: 100%;
display: flex;
position: relative;
overflow: hidden;
}
@media (forced-colors: active) {
#fed-stakes.rfe-controls.combobox [role='option'] {
outline-offset: 2px;
outline: 2px solid #0000;
}
}
#fed-stakes.rfe-controls.combobox [role='option'] {
-webkit-user-select: none;
user-select: none;
}
#fed-stakes.rfe-controls.combobox [role='option']:disabled,
#fed-stakes
.rfe-controls
.combobox
[role='option'][aria-disabled='true'] {
pointer-events: none;
opacity: 0.5;
}
#fed-stakes
.rfe-controls
.combobox
[role='option']:is([aria-hidden='true'], [hidden]) {
display: none;
}
#fed-stakes.rfe-controls.combobox [role='option'] svg {
pointer-events: none;
flex-shrink: 0;
}
#fed-stakes
.rfe-controls
.combobox
[role='option'][aria-selected='true'] {
background-image: var(--check-icon);
background-position: right 0.5rem center;
background-repeat: no-repeat;
background-size: 0.875rem;
}
#fed-stakes
.rfe-controls
.combobox
[role='option'][aria-selected='true']:dir(rtl) {
background-position: 0.5rem;
}
#fed-stakes.rfe-controls.combobox [role='separator'] {
border-style: var(--tw-border-style);
border-width: 0;
}
#fed-stakes.rfe-controls.combobox [role='option'].active,
#fed-stakes.rfe-controls.combobox [role='option']:focus-visible,
#fed-stakes
.rfe-controls
.combobox:not([data-combobox-initialized])
[role='option']:not([aria-disabled='true']):not(:disabled):hover {
background-color: var(--color-accent);
color: var(--color-accent-foreground);
}
#fed-stakes.rfe-controls.combobox-chips {
flex-wrap: wrap;
align-items: center;
width: 100%;
min-width: 0;
display: flex;
}
#fed-stakes.rfe-controls.combobox-chips > input[role='combobox'] {
min-width: calc(var(--spacing) * 16);
--tw-outline-style: none;
background-color: #0000;
outline-style: none;
flex: 1;
}
@media (forced-colors: active) {
#fed-stakes.rfe-controls.combobox-chips > input[role='combobox'] {
outline-offset: 2px;
outline: 2px solid #0000;
}
}
#fed-stakes
.rfe-controls
.combobox-chips
> input[role='combobox']::placeholder {
color: var(--color-muted-foreground);
}
#fed-stakes
.rfe-controls
.combobox-chips
> input[role='combobox']:disabled {
pointer-events: none;
cursor: not-allowed;
opacity: 0.5;
}
#fed-stakes.rfe-controls.combobox-chip {
white-space: nowrap;
flex-shrink: 0;
align-items: center;
width: fit-content;
display: inline-flex;
}
#fed-stakes.rfe-controls.combobox-chip-remove {
flex-shrink: 0;
justify-content: center;
align-items: center;
display: inline-flex;
}
#fed-stakes.rfe-controls.dropdown-menu > [data-popover] {
min-width: anchor-size(width);
}
#fed-stakes.rfe-controls [data-popover] {
visibility: visible;
z-index: 50;
--tw-scale-x: 100%;
--tw-scale-y: 100%;
--tw-scale-z: 100%;
width: max-content;
min-width: 100%;
scale: var(--tw-scale-x) var(--tw-scale-y);
opacity: 1;
--tw-outline-style: none;
outline-style: none;
position: absolute;
overflow: hidden auto;
}
@media (forced-colors: active) {
#fed-stakes.rfe-controls [data-popover] {
outline-offset: 2px;
outline: 2px solid #0000;
}
}
#fed-stakes.rfe-controls [data-popover] {
transition-property: all;
transition-timing-function: var(
--tw-ease,
var(--default-transition-timing-function)
);
transition-duration: var(
--tw-duration,
var(--default-transition-duration)
);
}
#fed-stakes.rfe-controls [data-popover][aria-hidden='true'] {
visibility: hidden;
--tw-scale-x: 95%;
--tw-scale-y: 95%;
--tw-scale-z: 95%;
scale: var(--tw-scale-x) var(--tw-scale-y);
opacity: 0;
}
#fed-stakes
.rfe-controls
[data-popover][aria-hidden='true']:not([data-side]),
#fed-stakes
.rfe-controls
[data-popover][aria-hidden='true'][data-side='bottom'] {
--tw-translate-y: calc(var(--spacing) * -2);
translate: var(--tw-translate-x) var(--tw-translate-y);
}
#fed-stakes
.rfe-controls
[data-popover][aria-hidden='true'][data-side='top'] {
--tw-translate-y: calc(var(--spacing) * 2);
translate: var(--tw-translate-x) var(--tw-translate-y);
}
#fed-stakes
.rfe-controls
[data-popover][aria-hidden='true'][data-side='left'] {
--tw-translate-x: calc(var(--spacing) * 2);
translate: var(--tw-translate-x) var(--tw-translate-y);
}
#fed-stakes
.rfe-controls
[data-popover][aria-hidden='true'][data-side='right'] {
--tw-translate-x: calc(var(--spacing) * -2);
translate: var(--tw-translate-x) var(--tw-translate-y);
}
#fed-stakes
.rfe-controls
[data-popover][aria-hidden='true'][data-side='inline-start'] {
--tw-translate-x: calc(var(--spacing) * 2);
translate: var(--tw-translate-x) var(--tw-translate-y);
}
#fed-stakes
.rfe-controls
[data-popover][aria-hidden='true'][data-side='inline-start']:dir(
rtl
) {
--tw-translate-x: calc(var(--spacing) * -2);
translate: var(--tw-translate-x) var(--tw-translate-y);
}
#fed-stakes
.rfe-controls
[data-popover][aria-hidden='true'][data-side='inline-end'] {
--tw-translate-x: calc(var(--spacing) * -2);
translate: var(--tw-translate-x) var(--tw-translate-y);
}
#fed-stakes
.rfe-controls
[data-popover][aria-hidden='true'][data-side='inline-end']:dir(rtl) {
--tw-translate-x: calc(var(--spacing) * 2);
translate: var(--tw-translate-x) var(--tw-translate-y);
}
#fed-stakes.rfe-controls [data-popover]:not([data-side]),
#fed-stakes.rfe-controls [data-popover][data-side='bottom'] {
margin-top: var(--spacing);
top: 100%;
}
#fed-stakes.rfe-controls [data-popover][data-side='top'] {
margin-bottom: var(--spacing);
bottom: 100%;
}
#fed-stakes.rfe-controls [data-popover][data-side='left'] {
margin-inline-end: var(--spacing);
inset-inline-end: 100%;
}
#fed-stakes.rfe-controls [data-popover][data-side='right'] {
margin-inline-start: var(--spacing);
inset-inline-start: 100%;
}
#fed-stakes.rfe-controls [data-popover][data-side='inline-start'] {
margin-inline-end: var(--spacing);
inset-inline-end: 100%;
}
#fed-stakes.rfe-controls [data-popover][data-side='inline-end'] {
margin-inline-start: var(--spacing);
inset-inline-start: 100%;
}
#fed-stakes
.rfe-controls
:is(
[data-popover]:not([data-side]),
[data-popover][data-side='bottom'],
[data-popover][data-side='top']
):not([data-align]),
#fed-stakes
.rfe-controls
:is(
[data-popover]:not([data-side]),
[data-popover][data-side='bottom'],
[data-popover][data-side='top']
)[data-align='start'] {
inset-inline-start: calc(var(--spacing) * 0);
}
#fed-stakes
.rfe-controls
:is(
[data-popover]:not([data-side]),
[data-popover][data-side='bottom'],
[data-popover][data-side='top']
)[data-align='end'] {
inset-inline-end: calc(var(--spacing) * 0);
}
#fed-stakes
.rfe-controls
:is(
[data-popover]:not([data-side]),
[data-popover][data-side='bottom'],
[data-popover][data-side='top']
)[data-align='center'] {
--tw-translate-x: calc(calc(1 / 2 * 100%) * -1);
translate: var(--tw-translate-x) var(--tw-translate-y);
inset-inline-start: 50%;
}
#fed-stakes
.rfe-controls
:is(
[data-popover][data-side='left'],
[data-popover][data-side='right'],
[data-popover][data-side='inline-start'],
[data-popover][data-side='inline-end']
):not([data-align]),
#fed-stakes
.rfe-controls
:is(
[data-popover][data-side='left'],
[data-popover][data-side='right'],
[data-popover][data-side='inline-start'],
[data-popover][data-side='inline-end']
)[data-align='start'] {
top: 0;
}
#fed-stakes
.rfe-controls
:is(
[data-popover][data-side='left'],
[data-popover][data-side='right'],
[data-popover][data-side='inline-start'],
[data-popover][data-side='inline-end']
)[data-align='end'] {
bottom: 0;
}
#fed-stakes
.rfe-controls
:is(
[data-popover][data-side='left'],
[data-popover][data-side='right'],
[data-popover][data-side='inline-start'],
[data-popover][data-side='inline-end']
)[data-align='center'] {
--tw-translate-y: calc(calc(1 / 2 * 100%) * -1);
translate: var(--tw-translate-x) var(--tw-translate-y);
top: 50%;
}
#fed-stakes.rfe-controls.select:not(select) [data-popover] {
isolation: isolate;
min-width: max(100%, 9rem);
overflow-x: hidden;
}
#fed-stakes.rfe-controls.combobox > input[role='combobox'] {
height: calc(var(--spacing) * 9);
border-radius: var(--radius-md);
border-style: var(--tw-border-style);
border-width: 1px;
border-color: var(--color-input);
padding-block: calc(var(--spacing) * 2);
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
--tw-shadow: 0 1px 2px 0 var(--tw-shadow-color, #0000000d);
box-shadow:
var(--tw-inset-shadow), var(--tw-inset-ring-shadow),
var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
var(--tw-shadow);
transition-property: color, box-shadow;
transition-timing-function: var(
--tw-ease,
var(--default-transition-timing-function)
);
transition-duration: var(
--tw-duration,
var(--default-transition-duration)
);
background-color: #0000;
padding-inline-start: calc(var(--spacing) * 2.5);
padding-inline-end: calc(var(--spacing) * 8);
}
#fed-stakes
.rfe-controls
.combobox
> input[role='combobox']:focus-visible {
border-color: var(--color-ring);
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0
calc(3px + var(--tw-ring-offset-width))
var(--tw-ring-color, currentcolor);
box-shadow:
var(--tw-inset-shadow), var(--tw-inset-ring-shadow),
var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
var(--tw-shadow);
--tw-ring-color: var(--color-ring);
}
@supports (color: color-mix(in lab, red, red)) {
#fed-stakes
.rfe-controls
.combobox
> input[role='combobox']:focus-visible {
--tw-ring-color: color-mix(
in oklab,
var(--color-ring) 50%,
transparent
);
}
}
#fed-stakes
.rfe-controls
.combobox
> input[role='combobox'][aria-invalid='true'] {
border-color: var(--color-destructive);
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0
calc(3px + var(--tw-ring-offset-width))
var(--tw-ring-color, currentcolor);
box-shadow:
var(--tw-inset-shadow), var(--tw-inset-ring-shadow),
var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
var(--tw-shadow);
--tw-ring-color: var(--color-destructive);
}
@supports (color: color-mix(in lab, red, red)) {
#fed-stakes
.rfe-controls
.combobox
> input[role='combobox'][aria-invalid='true'] {
--tw-ring-color: color-mix(
in oklab,
var(--color-destructive) 20%,
transparent
);
}
}
#fed-stakes.rfe-controls.combobox > svg,
#fed-stakes.rfe-controls.combobox-trigger-icon {
width: calc(var(--spacing) * 4);
height: calc(var(--spacing) * 4);
--tw-translate-y: calc(calc(1 / 2 * 100%) * -1);
translate: var(--tw-translate-x) var(--tw-translate-y);
color: var(--color-muted-foreground);
inset-inline-end: calc(var(--spacing) * 2.5);
top: 50%;
}
#fed-stakes.rfe-controls.combobox [data-popover] {
max-height: calc(var(--spacing) * 72);
border-radius: var(--radius-md);
background-color: var(--color-popover);
color: var(--color-popover-foreground);
--tw-shadow:
0 4px 6px -1px var(--tw-shadow-color, #0000001a),
0 2px 4px -2px var(--tw-shadow-color, #0000001a);
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0
calc(1px + var(--tw-ring-offset-width))
var(--tw-ring-color, currentcolor);
box-shadow:
var(--tw-inset-shadow), var(--tw-inset-ring-shadow),
var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
var(--tw-shadow);
--tw-ring-color: color-mix(
in oklab,
var(--color-foreground) 10%,
transparent
);
--tw-duration: 0.1s;
gap: 0;
padding: 0;
transition-duration: 0.1s;
}
#fed-stakes.rfe-controls.combobox [role='listbox'] {
max-height: min(18rem, var(--available-height, 18rem));
scroll-padding-block: var(--spacing);
padding: var(--spacing);
overflow-y: auto;
}
#fed-stakes
.rfe-controls
.combobox
[role='listbox']:not(:has([data-value]:not([aria-hidden='true']))) {
padding: 0;
}
#fed-stakes
.rfe-controls
.combobox
[role='listbox']:not(
:has([data-value]:not([aria-hidden='true']))
):before {
padding-inline: calc(var(--spacing) * 3);
padding-block: calc(var(--spacing) * 2);
text-align: center;
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
color: var(--color-muted-foreground);
}
#fed-stakes.rfe-controls.combobox [role='heading'] {
padding-inline: calc(var(--spacing) * 2);
padding-block: calc(var(--spacing) * 1.5);
font-size: var(--text-xs);
line-height: var(--tw-leading, var(--text-xs--line-height));
color: var(--color-muted-foreground);
}
#fed-stakes.rfe-controls.combobox [role='option'] {
gap: calc(var(--spacing) * 2);
border-radius: var(--radius-sm);
padding-block: calc(var(--spacing) * 1.5);
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
padding-inline-start: calc(var(--spacing) * 2);
padding-inline-end: calc(var(--spacing) * 8);
}
#fed-stakes
.rfe-controls
.combobox
[role='option']
svg:not([class*='size-']) {
width: calc(var(--spacing) * 4);
height: calc(var(--spacing) * 4);
}
#fed-stakes.rfe-controls.combobox [role='option'].active,
#fed-stakes.rfe-controls.combobox [role='option']:focus-visible {
background-color: var(--color-accent);
color: var(--color-accent-foreground);
}
#fed-stakes
.rfe-controls
:is(
.combobox [role='option'].active,
.combobox [role='option']:focus-visible
)
svg {
color: var(--color-accent-foreground);
}
#fed-stakes.rfe-controls.combobox [role='group'] {
scroll-margin-block: var(--spacing);
padding: var(--spacing);
}
#fed-stakes.rfe-controls.combobox [role='separator'] {
margin-inline: calc(var(--spacing) * -1);
margin-block: var(--spacing);
background-color: var(--color-border);
height: 1px;
}
#fed-stakes.rfe-controls.combobox-chips {
min-height: calc(var(--spacing) * 9);
gap: calc(var(--spacing) * 1.5);
border-radius: var(--radius-md);
border-style: var(--tw-border-style);
border-width: 1px;
border-color: var(--color-input);
padding-inline: calc(var(--spacing) * 1.5);
padding-block: calc(var(--spacing) * 1.5);
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
--tw-shadow: 0 1px 2px 0 var(--tw-shadow-color, #0000000d);
box-shadow:
var(--tw-inset-shadow), var(--tw-inset-ring-shadow),
var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
var(--tw-shadow);
transition-property: color, box-shadow;
transition-timing-function: var(
--tw-ease,
var(--default-transition-timing-function)
);
transition-duration: var(
--tw-duration,
var(--default-transition-duration)
);
background-color: #0000;
background-clip: padding-box;
}
#fed-stakes.rfe-controls.combobox-chips:focus-within {
border-color: var(--color-ring);
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0
calc(3px + var(--tw-ring-offset-width))
var(--tw-ring-color, currentcolor);
box-shadow:
var(--tw-inset-shadow), var(--tw-inset-ring-shadow),
var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
var(--tw-shadow);
--tw-ring-color: var(--color-ring);
}
@supports (color: color-mix(in lab, red, red)) {
#fed-stakes.rfe-controls.combobox-chips:focus-within {
--tw-ring-color: color-mix(
in oklab,
var(--color-ring) 50%,
transparent
);
}
}
#fed-stakes.rfe-controls.combobox-chips:has([aria-invalid='true']) {
border-color: var(--color-destructive);
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0
calc(3px + var(--tw-ring-offset-width))
var(--tw-ring-color, currentcolor);
box-shadow:
var(--tw-inset-shadow), var(--tw-inset-ring-shadow),
var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
var(--tw-shadow);
--tw-ring-color: var(--color-destructive);
}
@supports (color: color-mix(in lab, red, red)) {
#fed-stakes.rfe-controls.combobox-chips:has([aria-invalid='true']) {
--tw-ring-color: color-mix(
in oklab,
var(--color-destructive) 20%,
transparent
);
}
}
#fed-stakes.rfe-controls.combobox-chip {
height: calc(var(--spacing) * 5.5);
gap: var(--spacing);
border-radius: var(--radius-sm);
background-color: var(--color-muted);
padding-inline: calc(var(--spacing) * 1.5);
font-size: var(--text-xs);
line-height: var(--tw-leading, var(--text-xs--line-height));
--tw-font-weight: var(--font-weight-medium);
font-weight: var(--font-weight-medium);
color: var(--color-foreground);
}
#fed-stakes.rfe-controls.combobox-chip:has(>.combobox-chip-remove) {
padding-inline-end: 0;
}
#fed-stakes.rfe-controls.combobox-chip-remove {
opacity: 0.5;
margin-inline-start: calc(var(--spacing) * -1);
}
@media (hover: hover) {
#fed-stakes.rfe-controls.combobox-chip-remove:hover {
opacity: 1;
}
}
#fed-stakes.rfe-controls.combobox-chip-remove svg {
width: calc(var(--spacing) * 3.5);
height: calc(var(--spacing) * 3.5);
}
#fed-stakes.rfe-controls.combobox [data-popover] >.input-group {
margin: var(--spacing);
height: calc(var(--spacing) * 8);
border-color: var(--color-input);
width: auto;
margin-bottom: 0;
}
@supports (color: color-mix(in lab, red, red)) {
#fed-stakes.rfe-controls.combobox [data-popover] >.input-group {
border-color: color-mix(
in oklab,
var(--color-input) 30%,
transparent
);
}
}
#fed-stakes.rfe-controls.combobox [data-popover] >.input-group {
background-color: var(--color-input);
}
@supports (color: color-mix(in lab, red, red)) {
#fed-stakes.rfe-controls.combobox [data-popover] >.input-group {
background-color: color-mix(
in oklab,
var(--color-input) 30%,
transparent
);
}
}
#fed-stakes.rfe-controls.combobox [data-popover] >.input-group {
--tw-shadow: 0 0 #0000;
box-shadow:
var(--tw-inset-shadow), var(--tw-inset-ring-shadow),
var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
var(--tw-shadow);
}
#fed-stakes
.rfe-controls
.combobox
[data-popover]
>.input-group:focus-within {
border-color: inherit;
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0
calc(0px + var(--tw-ring-offset-width))
var(--tw-ring-color, currentcolor);
box-shadow:
var(--tw-inset-shadow), var(--tw-inset-ring-shadow),
var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
var(--tw-shadow);
}
#fed-stakes
.rfe-controls
.combobox
[data-popover]
> input[role='combobox'] {
margin-bottom: var(--spacing);
width: 100%;
}
#fed-stakes.rfe-controls.select:not(select) [data-popover] {
border-radius: var(--radius-md);
background-color: var(--color-popover);
padding: var(--spacing);
color: var(--color-popover-foreground);
--tw-shadow:
0 4px 6px -1px var(--tw-shadow-color, #0000001a),
0 2px 4px -2px var(--tw-shadow-color, #0000001a);
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0
calc(1px + var(--tw-ring-offset-width))
var(--tw-ring-color, currentcolor);
box-shadow:
var(--tw-inset-shadow), var(--tw-inset-ring-shadow),
var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
var(--tw-shadow);
--tw-ring-color: color-mix(
in oklab,
var(--color-foreground) 10%,
transparent
);
--tw-duration: 0.1s;
transition-duration: 0.1s;
}
#fed-stakes.rfe-controls.dropdown-menu > [data-popover] {
border-radius: var(--radius-md);
background-color: var(--color-popover);
padding: var(--spacing);
color: var(--color-popover-foreground);
--tw-shadow:
0 4px 6px -1px var(--tw-shadow-color, #0000001a),
0 2px 4px -2px var(--tw-shadow-color, #0000001a);
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0
calc(1px + var(--tw-ring-offset-width))
var(--tw-ring-color, currentcolor);
box-shadow:
var(--tw-inset-shadow), var(--tw-inset-ring-shadow),
var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
var(--tw-shadow);
--tw-ring-color: color-mix(
in oklab,
var(--color-foreground) 10%,
transparent
);
--tw-duration: 0.1s;
gap: 0;
transition-duration: 0.1s;
overflow: hidden auto;
}
#fed-stakes.rfe-controls [data-popover] {
gap: calc(var(--spacing) * 4);
border-radius: var(--radius-md);
background-color: var(--color-popover);
padding: calc(var(--spacing) * 4);
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
color: var(--color-popover-foreground);
--tw-shadow:
0 4px 6px -1px var(--tw-shadow-color, #0000001a),
0 2px 4px -2px var(--tw-shadow-color, #0000001a);
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0
calc(1px + var(--tw-ring-offset-width))
var(--tw-ring-color, currentcolor);
box-shadow:
var(--tw-inset-shadow), var(--tw-inset-ring-shadow),
var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
var(--tw-shadow);
--tw-ring-color: color-mix(
in oklab,
var(--color-foreground) 10%,
transparent
);
--tw-duration: 0.1s;
flex-direction: column;
transition-duration: 0.1s;
display: flex;
}
#fed-stakes.rfe-controls.popover > [data-popover] > header,
#fed-stakes.rfe-controls.popover > [data-popover] > [data-header] {
gap: var(--spacing);
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
flex-direction: column;
display: flex;
}
#fed-stakes
.rfe-controls
.popover
> [data-popover]
> header
> :is(h1, h2, h3, h4, h5, h6),
#fed-stakes
.rfe-controls
.popover
> [data-popover]
> :is(h1, h2, h3, h4, h5, h6, [data-title]) {
--tw-font-weight: var(--font-weight-medium);
font-weight: var(--font-weight-medium);
}
#fed-stakes.rfe-controls.popover > [data-popover] > header > p,
#fed-stakes
.rfe-controls
.popover
> [data-popover]
> [data-description] {
color: var(--color-muted-foreground);
}
@property --tw-border-style {
syntax: '*';
inherits: false;
initial-value: solid;
}
@property --tw-content {
syntax: '*';
inherits: false;
initial-value: '';
}
@property --tw-shadow {
syntax: '*';
inherits: false;
initial-value: 0 0 #0000;
}
@property --tw-shadow-color {
syntax: '*';
inherits: false;
}
@property --tw-shadow-alpha {
syntax: '<percentage>';
inherits: false;
initial-value: 100%;
}
@property --tw-inset-shadow {
syntax: '*';
inherits: false;
initial-value: 0 0 #0000;
}
@property --tw-inset-shadow-color {
syntax: '*';
inherits: false;
}
@property --tw-inset-shadow-alpha {
syntax: '<percentage>';
inherits: false;
initial-value: 100%;
}
@property --tw-ring-color {
syntax: '*';
inherits: false;
}
@property --tw-ring-shadow {
syntax: '*';
inherits: false;
initial-value: 0 0 #0000;
}
@property --tw-inset-ring-color {
syntax: '*';
inherits: false;
}
@property --tw-inset-ring-shadow {
syntax: '*';
inherits: false;
initial-value: 0 0 #0000;
}
@property --tw-ring-inset {
syntax: '*';
inherits: false;
}
@property --tw-ring-offset-width {
syntax: '<length>';
inherits: false;
initial-value: 0;
}
@property --tw-ring-offset-color {
syntax: '*';
inherits: false;
initial-value: #fff;
}
@property --tw-ring-offset-shadow {
syntax: '*';
inherits: false;
initial-value: 0 0 #0000;
}
@property --tw-space-x-reverse {
syntax: '*';
inherits: false;
initial-value: 0;
}
@property --tw-leading {
syntax: '*';
inherits: false;
}
@property --tw-translate-x {
syntax: '*';
inherits: false;
initial-value: 0;
}
@property --tw-translate-y {
syntax: '*';
inherits: false;
initial-value: 0;
}
@property --tw-translate-z {
syntax: '*';
inherits: false;
initial-value: 0;
}
@property --tw-duration {
syntax: '*';
inherits: false;
}
@property --tw-ease {
syntax: '*';
inherits: false;
}
@property --tw-font-weight {
syntax: '*';
inherits: false;
}
@property --tw-ordinal {
syntax: '*';
inherits: false;
}
@property --tw-slashed-zero {
syntax: '*';
inherits: false;
}
@property --tw-numeric-figure {
syntax: '*';
inherits: false;
}
@property --tw-numeric-spacing {
syntax: '*';
inherits: false;
}
@property --tw-numeric-fraction {
syntax: '*';
inherits: false;
}
@property --tw-scale-x {
syntax: '*';
inherits: false;
initial-value: 1;
}
@property --tw-scale-y {
syntax: '*';
inherits: false;
initial-value: 1;
}
@property --tw-scale-z {
syntax: '*';
inherits: false;
initial-value: 1;
}
@property --tw-tracking {
syntax: '*';
inherits: false;
}
@property --tw-outline-style {
syntax: '*';
inherits: false;
initial-value: solid;
}
@property --tw-backdrop-blur {
syntax: '*';
inherits: false;
}
@property --tw-backdrop-brightness {
syntax: '*';
inherits: false;
}
@property --tw-backdrop-contrast {
syntax: '*';
inherits: false;
}
@property --tw-backdrop-grayscale {
syntax: '*';
inherits: false;
}
@property --tw-backdrop-hue-rotate {
syntax: '*';
inherits: false;
}
@property --tw-backdrop-invert {
syntax: '*';
inherits: false;
}
@property --tw-backdrop-opacity {
syntax: '*';
inherits: false;
}
@property --tw-backdrop-saturate {
syntax: '*';
inherits: false;
}
@property --tw-backdrop-sepia {
syntax: '*';
inherits: false;
}

/* Project sizing; Basecoat owns the controls and option states. */
#fed-stakes.rfe-controls.combobox {
width: 100%;
min-width: 0;
--radius: var(--interface-radius);
--radius-md: var(--interface-radius);
--radius-sm: var(--interface-radius);
--input: var(--bs-border-color, rgb(26 23 20 / 0.3));
--tw-border-style: solid;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-inset: initial;
--tw-shadow: 0 0 #0000;
--tw-inset-shadow: 0 0 #0000;
--tw-inset-ring-shadow: 0 0 #0000;
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
}
#fed-stakes.rfe-controls.combobox > input[role='combobox'] {
height: 36px;
padding-inline: 11px 64px;
font: inherit;
font-size: var(--type-label);
}
/* iOS Safari zooms into any field under 16px on focus and never zooms back. */
@media (pointer: coarse) {
#fed-stakes.rfe-controls.combobox > input[role='combobox'],
#fed-stakes.rfe-controls.combobox-chips > input[role='combobox'] {
font-size: 16px;
}
}
#fed-stakes.rfe-controls.combobox >.combobox-trigger-icon {
width: 16px;
height: 16px;
right: 11px;
top: 50%;
translate: 0 -50%;
}
#fed-stakes.rfe-controls.combobox > [data-clear] {
position: absolute;
right: 30px;
top: 4px;
width: 28px;
height: 28px;
padding: 0;
border: 0;
border-radius: var(--interface-radius);
background: transparent;
color: var(--color-muted-foreground);
}
#fed-stakes.rfe-controls.combobox > [data-clear]:hover {
background: var(--color-accent);
}
#fed-stakes.rfe-controls.combobox > [data-clear] svg {
width: 14px;
height: 14px;
}
#fed-stakes.rfe-controls.combobox [role='listbox'] {
max-height: 260px;
}
#fed-stakes.rfe-controls.combobox [role='option'] {
font-size: var(--type-label);
gap: 8px;
}
#fed-stakes.rfe-controls.combobox [data-popover] {
max-width: min(360px, 90vw);
/* The dropdown's outline uses the shared border color too. */
--tw-ring-color: var(--input);
}
/* On phones Sector sits in the right column, flush with the component's
edge, so its menu lines up on the right and grows leftward. */
@media (max-width: 650px) {
#fed-stakes.rfe-controls #sector-popover {
inset-inline-start: auto;
inset-inline-end: 0;
}
}
#fed-stakes.rfe-controls.combobox.sector-option-count {
color: var(--color-muted-foreground);
}
#fed-stakes.rfe-controls.combobox [role='status'] {
padding: 6px 8px;
margin: 0;
font-size: var(--type-note);
color: var(--color-muted-foreground);
}

/* Download menu: the last action in the filter toolbar, after the pager. */
#fed-stakes.download-menu {
position: relative;
flex: none;
}
/* The pager and Download share the toolbar's right-hand group. */
#fed-stakes.toolbar-actions {
display: flex;
align-items: center;
gap: 20px;
}
#fed-stakes #download-toggle {
display: inline-flex;
align-items: center;
gap: 6px;
height: 36px;
padding: 0 12px;
border: 1px solid var(--border);
border-radius: var(--interface-radius);
background: #fff;
/* Same ink as the toolbar's other buttons (arrows). */
color: var(--foreground);
font-size: var(--type-label);
}
#fed-stakes #download-toggle:hover,
#fed-stakes #download-toggle[aria-expanded='true'] {
background: #f4f4f5;
}
/* Outside the dashboard, Download is a quiet icon in the filters' outline,
with its label kept for screen readers and the tooltip. */
#fed-stakes:not(.is-dashboard) #download-toggle {
width: 36px;
padding: 0;
justify-content: center;
color: var(--muted);
}
#fed-stakes:not(.is-dashboard) #download-toggle:hover,
#fed-stakes:not(.is-dashboard) #download-toggle[aria-expanded='true'] {
color: var(--foreground);
}
#fed-stakes:not(.is-dashboard) #download-toggle.download-label {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
}
#fed-stakes #download-options {
position: absolute;
right: 0;
top: calc(100% + 4px);
z-index: 20;
display: flex;
flex-direction: column;
min-width: 180px;
padding: 4px;
/* A real border, not a 1px shadow ring: the ring sat outside the box
and was clipped where the menu meets the component's right edge. */
border: 1px solid var(--border);
border-radius: var(--interface-radius);
background: #fff;
box-shadow:
0 4px 6px -1px #0000001a,
0 2px 4px -2px #0000001a;
}
#fed-stakes #download-options button {
height: auto;
padding: 6px 8px;
border: 0;
border-radius: var(--interface-radius);
background: transparent;
color: var(--foreground);
font-size: var(--type-label);
text-align: left;
white-space: nowrap;
}
#fed-stakes #download-options button:hover,
#fed-stakes #download-options button:focus-visible {
background: #f4f4f5;
outline: none;
}
/* Narrow columns: an icon-only square, so the toolbar doesn't wrap again. */
@container (max-width: 640px) {
#fed-stakes #download-toggle {
width: 36px;
padding: 0;
justify-content: center;
}
#fed-stakes #download-toggle.download-label {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
}
/* The dashboard's Download wraps to its own row: a labelled button under
the filters, with the menu opening from its left edge. */
#fed-stakes.is-dashboard #download-toggle {
width: auto;
padding: 0 12px;
}
#fed-stakes.is-dashboard #download-toggle.download-label {
position: static;
width: auto;
height: auto;
overflow: visible;
clip: auto;
}
#fed-stakes.is-dashboard #download-options {
left: 0;
right: auto;
}
}
#fed-stakes #download-options button:disabled {
color: var(--muted);
cursor: progress;
}
/* Narrow screens open on the total; a soft left fade and a short cue say
earlier months are off to the left. Both go once the reader reaches the start. */
#fed-stakes #chart.has-earlier {
-webkit-mask-image: linear-gradient(to right, #0006, #000 28px);
mask-image: linear-gradient(to right, #0006, #000 28px);
}
/* The cue sits in the empty space under the staircase, just above the axis,
pinned to this frame so it stays put while the months scroll. */
#fed-stakes.chart-frame {
position: relative;
}
#fed-stakes.scroll-cue {
position: absolute;
left: 0;
top: 352px;
padding: 2px 4px 2px 0;
border: 0;
background: none;
color: var(--muted);
font-size: var(--type-note);
transition: opacity 200ms;
}
#fed-stakes.scroll-cue.is-done {
opacity: 0;
visibility: hidden;
}
/* The inline-key layout pulls the chart up 26px, so the cue follows. */
#fed-stakes.intro-inline.scroll-cue {
top: 326px;
}
@media (prefers-reduced-motion: reduce) {
#fed-stakes.scroll-cue {
transition: none;
}
}

# The US Government’s Equity Portfolio

Each square is one company with a federal equity position

← Swipe for earlier months 

## Company details

Company 

Sector

Agency

Status

‹ › 

Download Chart image (PNG) Data (CSV) 

No companies match these filters. Clear the search or choose another sector.

(() => {
const componentRegistry = {};
let observer = null;

const registerComponent = (name, selectorOrOptions, initFunction) => {
const options =
typeof selectorOrOptions === 'object'
? selectorOrOptions
: { selector: selectorOrOptions, init: initFunction };

componentRegistry[name] = {
selector: options.selector,
init: options.init,
refresh: options.refresh,
};
};

const initComponent = (element, componentName) => {
const component = componentRegistry[componentName];
if (!component) return;

try {
component.init(element);
if (element.hasAttribute(`data-${componentName}-initialized`)) {
element.dataset.basecoatComponent = componentName;
}
} catch (error) {
console.error(`Failed to initialize ${componentName}:`, error);
if (typeof element._destroy === 'function') {
try {
element._destroy();
} catch (destroyError) {
console.error(
`Failed to clean up ${componentName} after initialization error:`,
destroyError,
);
}
}
delete element._destroy;
element.removeAttribute(`data-${componentName}-initialized`);
delete element.dataset.basecoatComponent;
}
};

const destroyComponent = element => {
if (!element || element.nodeType !== Node.ELEMENT_NODE) return;
const componentName = element.dataset?.basecoatComponent;

if (typeof element._destroy === 'function') {
try {
element._destroy();
} catch (error) {
console.error('Failed to destroy Basecoat component:', error);
}
}

delete element._destroy;
if (componentName)
element.removeAttribute(`data-${componentName}-initialized`);
delete element.dataset.basecoatComponent;
};

const destroyRemovedComponents = node => {
if (node.nodeType !== Node.ELEMENT_NODE) return;
if (node.isConnected) return;

if (node.dataset?.basecoatComponent) destroyComponent(node);
node
.querySelectorAll('[data-basecoat-component]')
.forEach(destroyComponent);
};

const uniqueElements = elements => Array.from(new Set(elements));

const getComponentElements = (
componentName,
selector,
force = false,
) => {
const elements = Array.from(document.querySelectorAll(selector));
if (force) {
elements.push(
...document.querySelectorAll(
`[data-basecoat-component="${componentName}"]`,
),
);
}
return uniqueElements(elements);
};

const initAllComponents = (options = {}) => {
const force = options.force === true;
Object.entries(componentRegistry).forEach(
([name, { selector }]) => {
getComponentElements(name, selector, force).forEach(element => {
const wasComponent =
element.dataset?.basecoatComponent === name;
if (force) destroyComponent(element);
if (wasComponent || element.matches(selector))
initComponent(element, name);
});
},
);
};

const initNewComponents = node => {
if (node.nodeType !== Node.ELEMENT_NODE) return;

Object.entries(componentRegistry).forEach(
([name, { selector }]) => {
if (node.matches(selector)) initComponent(node, name);
node
.querySelectorAll(selector)
.forEach(element => initComponent(element, name));
},
);
};

const refreshComponent = element => {
if (!element) return;
if (typeof element.refresh === 'function') {
element.refresh();
return;
}

const componentName = element.dataset?.basecoatComponent;
const component = componentName
? componentRegistry[componentName]
: null;
if (component?.refresh) {
component.refresh(element);
}
};

const startObserver = () => {
if (observer) return;

observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(initNewComponents);
mutation.removedNodes.forEach(destroyRemovedComponents);
});
});

observer.observe(document.body, { childList: true, subtree: true });
};

const stopObserver = () => {
if (!observer) return;
observer.disconnect();
observer = null;
};

const initRegisteredComponent = (componentName, options = {}) => {
const component = componentRegistry[componentName];
if (!component) {
console.warn(
`Component '${componentName}' not found in registry`,
);
return;
}

const force = options.force === true;
getComponentElements(
componentName,
component.selector,
force,
).forEach(element => {
const wasComponent =
element.dataset?.basecoatComponent === componentName;
if (force) destroyComponent(element);
if (wasComponent || element.matches(component.selector))
initComponent(element, componentName);
});
};

const initAllRegisteredComponents = (options = {}) => {
initAllComponents(options);
};
window.basecoat = {
register: registerComponent,
init: initRegisteredComponent,
initAll: initAllRegisteredComponents,
refresh: refreshComponent,
start: startObserver,
stop: stopObserver,
};

document.addEventListener('DOMContentLoaded', () => {
initAllComponents();
startObserver();
});
})();

(() => {
const states = new WeakMap();

const getElements = root => {
const popover = root.querySelector(':scope > [data-popover]');
const input =
root.querySelector(
':scope > input[role="combobox"], :scope >.input-group input[role="combobox"], :scope >.combobox-chips input[role="combobox"]',
) || popover?.querySelector('input[role="combobox"]');
const chips = root.querySelector(':scope >.combobox-chips');
const popupTrigger = root.querySelector(
':scope > button[aria-haspopup="listbox"]',
);
const trigger =
popupTrigger ||
root.querySelector(
':scope >.input-group button[aria-haspopup="listbox"]',
);
const clearButton = root.querySelector('[data-clear]');
const valueTarget =
popupTrigger?.querySelector('[data-value]') ||
(popupTrigger?.matches('[data-value]') ? popupTrigger : null);
const listbox = popover
? popover.querySelector('[role="listbox"]')
: null;
const hiddenInput = root.querySelector(
':scope > input[type="hidden"]',
);
return {
input,
chips,
trigger,
clearButton,
valueTarget,
popover,
listbox,
hiddenInput,
};
};

const ensureMultipleInputSurface = (root, elements) => {
if (
elements.listbox?.getAttribute('aria-multiselectable') !==
'true' ||
elements.chips ||
!elements.input
)
return elements;
if (elements.input.parentElement !== root) return elements;

const chips = document.createElement('div');
chips.className = 'combobox-chips';
root.insertBefore(chips, elements.input);
chips.appendChild(elements.input);

return getElements(root);
};

const getValue = option =>
option.dataset.value ?? option.textContent.trim();
const getLabel = option =>
option.dataset.label || option.textContent.trim();
const getFormat = root =>
root.dataset.format === 'object' ? 'object' : 'value';
const isDisabled = option =>
option.getAttribute('aria-disabled') === 'true';
const toSelected = option => ({
value: getValue(option),
label: getLabel(option),
});
const normalizeEntry = entry => {
if (entry && typeof entry === 'object') {
const value = entry.value == null ? '' : String(entry.value);
return value
? { value, label: String(entry.label ?? entry.value) }
: null;
}
const value = entry == null ? '' : String(entry);
return value ? { value, label: value } : null;
};

const getOptions = listbox => {
const allOptions = Array.from(
listbox.querySelectorAll('[role="option"]'),
);
return {
allOptions,
options: allOptions.filter(option => !isDisabled(option)),
};
};

const getSelection = state => Array.from(state.selected.values());
const getCanonicalValue = state =>
state.isMultiple
? getSelection(state).map(item => item.value)
: getSelection(state)[0]?.value || '';
const getSelectedDetail = state =>
state.isMultiple
? getSelection(state)
: getSelection(state)[0] || null;

const serializeSelection = state => {
const selected = getSelection(state);
if (state.format === 'object') {
return JSON.stringify(
state.isMultiple ? selected : selected[0] || null,
);
}
const value = selected.map(item => item.value);
return state.isMultiple ? JSON.stringify(value) : value[0] || '';
};

const parseStoredSelection = (storedValue, inputValue, state) => {
if (state.isMultiple) {
let parsed = [];
try {
parsed = JSON.parse(storedValue || '[]');
} catch (_) {
parsed = [];
}
if (!Array.isArray(parsed)) return [];
return parsed
.map(item => {
const entry = normalizeEntry(
state.format === 'object'
? item
: {
value: item,
label:
state.selected.get(String(item))?.label ?? item,
},
);
if (!entry) return null;
const option = state.options.find(
opt => getValue(opt) === entry.value,
);
return option ? toSelected(option) : entry;
})
.filter(Boolean);
}

if (state.format === 'object') {
try {
const entry = normalizeEntry(JSON.parse(storedValue || 'null'));
if (!entry) return [];
const option = state.options.find(
opt => getValue(opt) === entry.value,
);
return [option ? toSelected(option) : entry];
} catch (_) {
return [];
}
}

const value = storedValue || '';
if (!value) return [];
const option = state.options.find(opt => getValue(opt) === value);
return [
option
? toSelected(option)
: {
value,
label:
state.selected.get(value)?.label || inputValue || value,
},
];
};

const scrollOptionIntoListbox = (state, option) => {
const optionRect = option.getBoundingClientRect();
const listboxRect = state.listbox.getBoundingClientRect();

if (optionRect.top < listboxRect.top) {
state.listbox.scrollTop -= listboxRect.top - optionRect.top;
} else if (optionRect.bottom > listboxRect.bottom) {
state.listbox.scrollTop += optionRect.bottom - listboxRect.bottom;
}
};

const setActiveOption = (state, index) => {
if (state.activeIndex > -1 && state.options[state.activeIndex]) {
state.options[state.activeIndex].classList.remove('active');
}

state.activeIndex = index;

if (state.activeIndex > -1) {
const activeOption = state.options[state.activeIndex];
activeOption.classList.add('active');
if (!activeOption.id)
activeOption.id = `${state.listbox.id || state.root.id || 'combobox'}-option-${state.activeIndex}`;
state.input.setAttribute(
'aria-activedescendant',
activeOption.id,
);
} else {
state.input.removeAttribute('aria-activedescendant');
}
};

const syncEmptyState = state => {
state.popover.dataset.empty = String(
state.visibleOptions.length === 0,
);
};

const filterOptions = (
state,
{ preserveActive = false, search: forcedSearch } = {},
) => {
const previousActive =
state.activeIndex > -1 ? state.options[state.activeIndex] : null;
state.visibleOptions = [];

if (state.manualFilter) {
state.visibleOptions = state.options.filter(
option => option.getAttribute('aria-hidden') !== 'true',
);

if (
preserveActive &&
previousActive &&
state.visibleOptions.includes(previousActive)
) {
setActiveOption(state, state.options.indexOf(previousActive));
} else {
setActiveOption(
state,
state.autoHighlight && state.visibleOptions.length > 0
? state.options.indexOf(state.visibleOptions[0])
: -1,
);
}
syncEmptyState(state);
return;
}

const search = (forcedSearch ?? state.input.value)
.trim()
.toLowerCase();

state.allOptions.forEach(option => {
if (option.hasAttribute('data-force')) {
option.setAttribute('aria-hidden', 'false');
if (state.options.includes(option))
state.visibleOptions.push(option);
return;
}

const optionText = (
option.dataset.filter ||
option.dataset.label ||
option.textContent
)
.trim()
.toLowerCase();
const keywords = (option.dataset.keywords || '')
.toLowerCase()
.split(/[\s,]+/)
.filter(Boolean);
const matches =
!search ||
optionText.includes(search) ||
keywords.some(keyword => keyword.includes(search));
option.setAttribute('aria-hidden', String(!matches));
if (matches && state.options.includes(option))
state.visibleOptions.push(option);
});

if (
preserveActive &&
previousActive &&
state.visibleOptions.includes(previousActive)
) {
setActiveOption(state, state.options.indexOf(previousActive));
} else {
setActiveOption(
state,
state.autoHighlight && state.visibleOptions.length > 0
? state.options.indexOf(state.visibleOptions[0])
: -1,
);
}
syncEmptyState(state);
};

const renderChips = root => {
const state = states.get(root);
if (!state.chips) return;

state.chips
.querySelectorAll('.combobox-chip')
.forEach(chip => chip.remove());

getSelection(state).forEach(entry => {
const chip = document.createElement('span');
chip.className = 'combobox-chip';
chip.dataset.value = entry.value;

const label = document.createElement('span');
label.textContent = entry.label;

const remove = document.createElement('button');
remove.type = 'button';
remove.className = 'combobox-chip-remove btn';
remove.dataset.variant = 'ghost';
remove.dataset.size = 'icon-xs';
remove.setAttribute('aria-label', `Remove ${entry.label}`);
remove.innerHTML =
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/>';
remove.addEventListener('click', event => {
event.stopPropagation();
root.deselect(entry.value);
state.input.focus();
});

chip.appendChild(label);
chip.appendChild(remove);
state.chips.insertBefore(chip, state.input);
});
};

const syncTriggerValue = state => {
if (!state.valueTarget) return;
const selected = getSelection(state);
state.valueTarget.textContent = state.isMultiple
? selected.map(entry => entry.label).join(', ')
: selected[0]?.label ||
state.valueTarget.dataset.placeholder ||
'';
};

const syncClearButton = state => {
if (!state.clearButton) return;
state.clearButton.hidden =
getSelection(state).length === 0 && state.input.value === '';
};

const syncSelectedOptions = state => {
state.options.forEach(option => {
if (state.selected.has(getValue(option))) {
option.setAttribute('aria-selected', 'true');
} else {
option.removeAttribute('aria-selected');
}
});
};

const setSelected = (root, entries, triggerEvent = true) => {
const state = states.get(root);
const normalized = (Array.isArray(entries) ? entries : [entries])
.map(normalizeEntry)
.filter(Boolean);

state.selected.clear();
if (state.isMultiple) {
normalized.forEach(entry =>
state.selected.set(entry.value, entry),
);
state.input.value = '';
} else if (normalized[0]) {
state.selected.set(normalized[0].value, normalized[0]);
state.input.value = state.valueTarget ? '' : normalized[0].label;
} else {
state.input.value = '';
}

state.hiddenInput.value = serializeSelection(state);
syncSelectedOptions(state);
if (state.isMultiple) {
renderChips(root);
filterOptions(state, { preserveActive: true });
}
syncTriggerValue(state);
syncClearButton(state);

if (triggerEvent) {
root.dispatchEvent(
new CustomEvent('change', {
detail: {
value: getCanonicalValue(state),
selected: getSelectedDetail(state),
},
bubbles: true,
}),
);
}
};

const closePopover = (state, focusInput = false) => {
if (state.popover.getAttribute('aria-hidden') === 'true') return;
state.popover.setAttribute('aria-hidden', 'true');
state.input.setAttribute('aria-expanded', 'false');
state.trigger?.setAttribute('aria-expanded', 'false');
setActiveOption(state, -1);
if (focusInput) {
state.skipOpenOnFocus = true;
state.input.focus();
requestAnimationFrame(() => {
state.skipOpenOnFocus = false;
});
}
};

const openPopover = state => {
const { root } = state;
if (state.popover.getAttribute('aria-hidden') === 'false') return;
document.dispatchEvent(
new CustomEvent('basecoat:popover', { detail: { source: root } }),
);
root.refresh();
const selected = getSelection(state)[0];
if (state.valueTarget) state.input.value = '';
const search =
!state.isMultiple &&
selected &&
state.input.value === selected.label
? ''
: undefined;
filterOptions(state, { search });
state.popover.setAttribute('aria-hidden', 'false');
state.input.setAttribute('aria-expanded', 'true');
state.trigger?.setAttribute('aria-expanded', 'true');
if (state.trigger) state.input.focus();
};

const refreshCombobox = root => {
const state = states.get(root);
if (!state) return;

let elements = getElements(root);
if (
!elements.input ||
!elements.popover ||
!elements.listbox ||
!elements.hiddenInput
) {
const missing = [];
if (!elements.input) missing.push('input');
if (!elements.popover) missing.push('popover');
if (!elements.listbox) missing.push('listbox');
if (!elements.hiddenInput) missing.push('hidden input');
console.error(
`Combobox refresh failed. Missing element(s): ${missing.join(', ')}`,
root,
);
return;
}

elements = ensureMultipleInputSurface(root, elements);
const previousValue = elements.hiddenInput.value;
const previousInputValue = elements.input.value;
Object.assign(state, elements, getOptions(elements.listbox));
state.isMultiple =
state.listbox.getAttribute('aria-multiselectable') === 'true';
state.closeOnSelect = root.dataset.closeOnSelect === 'true';
state.autoHighlight = root.dataset.autoHighlight === 'true';
state.manualFilter = root.dataset.filter === 'manual';
state.format = getFormat(root);

const stored = parseStoredSelection(
previousValue,
previousInputValue,
state,
);
if (stored.length > 0) {
setSelected(root, stored, false);
} else {
const selectedOptions = state.options
.filter(
option => option.getAttribute('aria-selected') === 'true',
)
.map(toSelected);
if (selectedOptions.length > 0) {
setSelected(
root,
state.isMultiple ? selectedOptions : selectedOptions[0],
false,
);
} else if (state.isMultiple) {
setSelected(root, [], false);
} else {
state.input.value = previousInputValue;
state.selected.clear();
state.hiddenInput.value = '';
syncSelectedOptions(state);
filterOptions(state, { preserveActive: true });
}
}
syncTriggerValue(state);
syncClearButton(state);
};

const resolveEntry = (state, value) => {
if (value && typeof value === 'object') {
const entry = normalizeEntry(value);
if (!entry) return null;
const option = state.options.find(
opt => getValue(opt) === entry.value,
);
return option ? toSelected(option) : entry;
}

const option = state.options.find(opt => getValue(opt) === value);
if (option) return toSelected(option);
const existing = state.selected.get(String(value));
return existing || null;
};

const selectValue = (root, value) => {
const state = states.get(root);
const entry = resolveEntry(state, value);
if (!entry) return;

if (state.isMultiple) {
setSelected(root, [
...getSelection(state).filter(
item => item.value !== entry.value,
),
entry,
]);
if (state.closeOnSelect) root.close(true);
} else {
setSelected(root, entry);
root.close(state.trigger ? false : true);
state.trigger?.focus();
}
};

const deselectValue = (root, value) => {
const state = states.get(root);
if (!state.isMultiple) return;
const normalized = String(value);
if (!state.selected.has(normalized)) return;
setSelected(
root,
getSelection(state).filter(item => item.value !== normalized),
);
};

const clearValue = root => {
const state = states.get(root);
setSelected(root, [], true);
state.input.value = '';
filterOptions(state);
syncClearButton(state);
if (state.popover.getAttribute('aria-hidden') === 'false')
state.input.focus();
};

const handleKeydown = (event, root) => {
const state = states.get(root);
if (
![
'ArrowDown',
'ArrowUp',
'Enter',
'Home',
'End',
'Escape',
'Backspace',
].includes(event.key)
)
return;

const isOpen =
state.popover.getAttribute('aria-hidden') === 'false';

if (
event.key === 'Backspace' &&
state.isMultiple &&
state.input.value === ''
) {
const selected = getSelection(state);
const last = selected[selected.length - 1];
if (last) root.deselect(last.value);
return;
}

if (event.key === 'Escape') {
root.close(true);
return;
}

if (
!isOpen &&
['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key)
) {
event.preventDefault();
openPopover(state);
}

if (state.popover.getAttribute('aria-hidden') === 'true') return;

if (event.key === 'Enter') {
if (state.activeIndex > -1) {
event.preventDefault();
const option = state.options[state.activeIndex];
state.isMultiple
? root.toggle(getValue(option))
: root.select(getValue(option));
}
return;
}

if (
!['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key) ||
state.visibleOptions.length === 0
)
return;
event.preventDefault();

const currentVisibleIndex =
state.activeIndex > -1
? state.visibleOptions.indexOf(state.options[state.activeIndex])
: -1;
let nextVisibleIndex = currentVisibleIndex;

if (event.key === 'ArrowDown')
nextVisibleIndex = Math.min(
currentVisibleIndex + 1,
state.visibleOptions.length - 1,
);
if (event.key === 'ArrowUp')
nextVisibleIndex =
currentVisibleIndex <= 0 ? 0 : currentVisibleIndex - 1;
if (event.key === 'Home') nextVisibleIndex = 0;
if (event.key === 'End')
nextVisibleIndex = state.visibleOptions.length - 1;

const nextOption = state.visibleOptions[nextVisibleIndex];
setActiveOption(state, state.options.indexOf(nextOption));
scrollOptionIntoListbox(state, nextOption);
};

const initCombobox = root => {
if (root.dataset.comboboxInitialized) return;

const state = {
root,
activeIndex: -1,
allOptions: [],
options: [],
visibleOptions: [],
selected: new Map(),
format: 'value',
manualFilter: false,
skipOpenOnFocus: false,
};
states.set(root, state);
root.refresh = () => refreshCombobox(root);

refreshCombobox(root);
if (
!state.input ||
!state.popover ||
!state.listbox ||
!state.hiddenInput
) {
states.delete(root);
delete root.refresh;
return;
}

root.close = (focusInput = false) =>
closePopover(state, focusInput);

root.select = value => selectValue(root, value);
root.selectByValue = root.select;
root.setValue = value => {
const entries = state.isMultiple
? Array.isArray(value)
? value
: value == null
? []
: [value]
: [value];
const resolved = entries
.map(
entry => resolveEntry(state, entry) || normalizeEntry(entry),
)
.filter(Boolean);
setSelected(root, state.isMultiple ? resolved : resolved[0]);
};
if (state.isMultiple) {
root.deselect = value => deselectValue(root, value);
root.toggle = value => {
const entry = resolveEntry(state, value);
if (!entry) return;
state.selected.has(entry.value)
? root.deselect(entry.value)
: root.select(entry);
};
root.selectAll = () =>
setSelected(root, state.options.map(toSelected));
root.selectNone = () => setSelected(root, []);
}

const handleInputFocus = () => {
if (state.skipOpenOnFocus) return;
openPopover(state);
};
const handleInputClick = () => openPopover(state);
const handleInput = () => {
openPopover(state);
filterOptions(state);
if (!state.isMultiple) {
state.hiddenInput.value = '';
state.selected.clear();
syncSelectedOptions(state);
syncTriggerValue(state);
syncClearButton(state);
}
};
const handleTriggerClick = () => {
if (state.popover.getAttribute('aria-hidden') === 'false') {
root.close(false);
} else {
openPopover(state);
}
};
const handleClearClick = event => {
event.preventDefault();
event.stopPropagation();
clearValue(root);
};
const handleInputKeydown = event => handleKeydown(event, root);
const handleListboxMousedown = event => {
// Options use aria-activedescendant: focus stays on the input. Otherwise
// focusout can dismiss the popup between mouse press and click.
if (
event.button === 0 &&
event.target.closest('[role="option"]')
) {
event.preventDefault();
}
};
const handleListboxMousemove = event => {
const option = event.target.closest('[role="option"]');
if (option && state.visibleOptions.includes(option))
setActiveOption(state, state.options.indexOf(option));
};
const handleListboxClick = event => {
const option = event.target.closest('[role="option"]');
if (!option || !state.options.includes(option)) return;
state.isMultiple
? root.toggle(getValue(option))
: root.select(getValue(option));
if (state.isMultiple && !state.closeOnSelect) state.input.focus();
};
const handleDocumentClick = event => {
if (!root.contains(event.target)) root.close(false);
};
const handleDocumentPopover = event => {
if (event.detail.source !== root) root.close(false);
};

state.input.addEventListener('focus', handleInputFocus);
state.input.addEventListener('click', handleInputClick);
state.input.addEventListener('input', handleInput);
state.input.addEventListener('keydown', handleInputKeydown);
state.trigger?.addEventListener('click', handleTriggerClick);
state.clearButton?.addEventListener('click', handleClearClick);
state.listbox.addEventListener('mousedown', handleListboxMousedown);
state.listbox.addEventListener('mousemove', handleListboxMousemove);
state.listbox.addEventListener('click', handleListboxClick);
document.addEventListener('click', handleDocumentClick);
document.addEventListener(
'basecoat:popover',
handleDocumentPopover,
);

root._destroy = () => {
state.input.removeEventListener('focus', handleInputFocus);
state.input.removeEventListener('click', handleInputClick);
state.input.removeEventListener('input', handleInput);
state.input.removeEventListener('keydown', handleInputKeydown);
state.trigger?.removeEventListener('click', handleTriggerClick);
state.clearButton?.removeEventListener('click', handleClearClick);
state.listbox.removeEventListener(
'mousedown',
handleListboxMousedown,
);
state.listbox.removeEventListener(
'mousemove',
handleListboxMousemove,
);
state.listbox.removeEventListener('click', handleListboxClick);
document.removeEventListener('click', handleDocumentClick);
document.removeEventListener(
'basecoat:popover',
handleDocumentPopover,
);
state.chips
?.querySelectorAll('.combobox-chip')
.forEach(chip => chip.remove());
states.delete(root);
delete root.refresh;
delete root.close;
delete root.select;
delete root.selectByValue;
delete root.setValue;
delete root.clear;
delete root.deselect;
delete root.toggle;
delete root.selectAll;
delete root.selectNone;
};

state.popover.setAttribute('aria-hidden', 'true');
state.input.setAttribute('aria-expanded', 'false');
state.trigger?.setAttribute('aria-expanded', 'false');
root.clear = () => clearValue(root);

Object.defineProperty(root, 'value', {
configurable: true,
get: () => getCanonicalValue(state),
set: value => root.setValue(value),
});

Object.defineProperty(root, 'selected', {
configurable: true,
get: () => getSelectedDetail(state),
});

root.dataset.comboboxInitialized = 'true';
root.dispatchEvent(new CustomEvent('basecoat:initialized'));
};

if (window.basecoat) {
window.basecoat.register('combobox', {
selector: '.combobox:not([data-combobox-initialized])',
init: initCombobox,
refresh: refreshCombobox,
});
}
})();

/* ANIMATION STORYBOARD
* 0ms Hide plateau lines, total column, and selection annotations; move company marks.
* 600ms After the marks settle, reveal cumulative totals and restore annotations.
* Interrupted transitions never reveal stale totals. Reduced motion settles immediately.
*/
/* CAROUSEL STORYBOARD
* On selection: keep All active or activate the company's sector, then center its card among muted neighbors, clamped at the list edges.
* Hover never navigates. Reduced motion aligns immediately.
*/
// On the Government Ownership Dashboard page, Drupal's page title replaces ours.
// It also opens the company cards as a grid instead of a carousel.
// Any path containing "dashboard" counts, so a migrated URL still matches.
// Add ?view=dashboard to any URL (e.g. the dev environment) to preview it.
const isDashboard =
window.location.pathname.toLowerCase().includes('dashboard') ||
new URLSearchParams(window.location.search).get('view') ===
'dashboard';
if (isDashboard)
document.querySelector('#fed-stakes').classList.add('is-dashboard');
// COMPANY EXPORT SWITCH
// When on, selecting a company adds "Chart image, highlighting <company>" to
// the Download menu: an image with the title and that company's detail card
// in the upper left. To hide it, change true to false.
const COMPANY_EXPORT = true;
// Show the component once it's ready, or once there's an error to show.
const revealComponent = () => {
const root = document.querySelector('#fed-stakes');
root.classList.remove('is-loading');
root.removeAttribute('aria-busy');
};
// D3 loads from jsDelivr; if it's blocked or down, say so instead of leaving a blank chart.
if (typeof d3 === 'undefined') {
revealComponent();
const chart = document.querySelector('#fed-stakes #chart');
chart.setAttribute('role', 'alert');
chart.textContent =
'Unable to load the chart. Check your connection and refresh the page.';
document.querySelector('#fed-stakes.company-directory').hidden =
true;
// The title normally overlaps the chart's empty corner; keep it clear of the message.
document.querySelector('#fed-stakes.hero').style.marginBottom =
'16px';
throw new Error('D3 failed to load from jsDelivr.');
}
let activeSector = 'All';
let activeStatus = 'All';
let activeAgency = 'All';
// A deal can name several agencies, separated by commas or semicolons.
const unitAgencies = unit =>
unit.agency
.trim()
.split(/\s{2,}|\s*[,;]\s*/)
.filter(Boolean);
// Short names for links and file names: "commerce", not
// "department-of-commerce". Menus and image text use the full name.
const agencyLabel = name => name.replace(/^Department of (the )?/, '');
let carouselNavigated = false;
let carouselCentered = false;
// Every screen gets the grid. With no filter on, it starts at two rows
// (PHONE_PREVIEW cards on phones) with a button for the rest.
const phoneLayout = window.matchMedia('(max-width: 650px)');
const PHONE_PREVIEW = 3;
const PREVIEW_ROWS = 2;
let showAllCards = false;
let matchedCount = 0; // Cards matching the filters, shown or not.
let expandedDirectory = true;
function applyDirectoryLayout() {
const carousel = document.querySelector('#company-cards');
carousel.classList.toggle('is-expanded', expandedDirectory);
carousel.parentElement.classList.toggle(
'is-expanded',
expandedDirectory,
);
carousel.setAttribute(
'aria-roledescription',
expandedDirectory ? 'company grid' : 'carousel',
);
}
applyDirectoryLayout();

let sectorGroups = [];
const TIMING = { settleMarks: 600 }; // Reveal totals only after this movement completes.
const CHART = {
// Use the available desktop width; retain the original mobile minimum.
minWidth: 659,
height: 436,
margin: { top: 14, right: 0, bottom: 48, left: 0 },
edgeInset: 28,
totalExtraGap: 18,
};
const TOTAL_LABELS = {
fallbackGutter: 150,
edgePadding: 8,
gap: 6, // Clear space between neighboring labels.
offset: 15,
lineHeight: 13,
countGap: 3, // Extra space between a sector name and its count.
};
const SELECTION = {
mutedMark: 0.25,
mutedConnector: 0.25,
mutedLabel: 0.35,
mutedText: '#a1a1aa', // Faded sector names in the Total column.
};
// Fixed colors by sector. Table order breaks ties when stacking.
const SECTOR_COLORS = {
Steel: '#64748b',
Minerals: '#d97706',
'Energy (nuclear)': '#34b887',
'Quantum computing': '#9c27b0',
'Rocket motors': '#b42318',
Semiconductors: '#2563eb',
'Energy (oil)': '#164e63',
};
// Deal status, read from the sheet's optional "status" column. Color stays with
// sector, so status shows as the square's fill: solid, half, or pale.
// "Open" in the sheet counts as Announced. Blank or unknown values draw solid
// with no status shown.
const STATUSES = [
{ name: 'Closed', fill: 'solid', aliases: ['closed'] },
{
name: 'Partially closed',
fill: 'half',
aliases: ['partially closed', 'partly closed', 'partial'],
},
{ name: 'Announced', fill: 'pale', aliases: ['announced', 'open'] },
];
const statusInfo = name => STATUSES.find(item => item.name === name);
// Unknown statuses draw solid, so they sort with Closed.
const statusRank = name =>
Math.max(
0,
STATUSES.findIndex(item => item.name === name),
);
function normalizeStatus(raw = '') {
const value = raw.trim().toLowerCase();
if (!value) return null;
const match = STATUSES.find(item => item.aliases.includes(value));
if (!match)
console.warn(`Unknown status "${raw}"; drawing it solid.`);
return match?.name || null;
}
// Closed deals draw solid, announced ones a pale tint of the sector color,
// and partially closed ones split corner to corner: solid lower left, pale
// upper right. No outlines, so every square keeps its full size.
const PALE_TINT = 0.4;
// Per-hue overrides: light hues need more color to show at all; mint
// needs less so it doesn't pass for solid; dark teal needs more so it
// doesn't turn gray.
const PALE_TINTS = {
'#facc15': 0.6,
'#06b6d4': 0.6,
'#d4d4d8': 0.6,
'#34b887': 0.45,
'#164e63': 0.55,
};
const statusFill = status => statusInfo(status)?.fill || 'solid';
const paleColor = color =>
d3.interpolateRgb('#fff', color)(PALE_TINTS[color] ?? PALE_TINT);
// A small inline square matching the chart mark for a status.
function statusIcon(status, color = '#52525b') {
const fill = statusFill(status);
const shape =
fill === 'solid'
? `<rect width="10" height="10" fill="${color}"/>`
: `<rect width="10" height="10" fill="${paleColor(color)}"/>${fill === 'half' ? `<path d="M0 0V10H10Z" fill="${color}"/>` : ''}`;
return `<svg class="status-icon" viewBox="0 0 10 10" aria-hidden="true" focusable="false">${shape}`;
}
// New sectors take these in order; all are color-blind checked against the fixed set.
const SPARE_SECTOR_COLORS = ['#facc15', '#06b6d4'];
const UNASSIGNED_SECTOR = 'Unassigned';
const UNASSIGNED_COLOR = '#d4d4d8';
// Quieter text hues preserve category matching without competing with totals.
const categoryTextColors = {
'#64748b': '#475569',
'#d97706': '#92400e',
'#4f46e5': '#3730a3',
'#059669': '#065f46',
'#34b887': '#065f46',
'#b42318': '#8a1c13',
'#9333ea': '#6b21a8',
'#0e7490': '#155e75',
'#2563eb': '#1e40af',
'#9c27b0': '#6a1b7a',
'#facc15': '#a16207',
'#06b6d4': '#155e75',
'#d4d4d8': '#52525b',
};
const chart = document.querySelector('#chart');
const searchInput = document.querySelector('#company-search');
const number = d3.format(',');
const billions = value => `$${number(value)}B`;
const formatDate = value =>
d3.timeFormat('%b %Y')(d3.timeParse('%Y-%m')(value.slice(0, 7)));
const amountLabel = unit =>
unit.value === null
? 'Not specified'
: unit.value === 0
? '$0 federal cash'
: unit.value < 1
? `$${d3.format(',.3~f')(unit.value * 1000)}M`
: billions(unit.value);
const reducedMotion = () =>
window.matchMedia('(prefers-reduced-motion: reduce)').matches;
let categories = [];
let mode = 'cumulative';
let directLabels = true;
const arithmeticLabels = true;
let companyQuery = '';
let selectedId = null;
let urlReady = false; // Hold URL writes until a shared link has been read.
let previewId = null;
let data;
let scene;
let lastChartWidth = 0;
let detailId = null;
const activeId = () => previewId || selectedId;
// URL-safe names: "Energy (nuclear)" -> "energy-nuclear", "Intel" -> "intel".
const slug = text =>
text
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-|-$/g, '');

function parse(source) {
const parsed = d3.csvParse(source.replace(/^\uFEFF/, ''));
// Skip blank rows an editor may leave in the sheet.
const rows = parsed.filter(row => row.company?.trim());
rows.columns = parsed.columns;
const required = [
'company',
'sector',
'agency',
'announced',
'summary',
'source',
];
if (required.some(column => !rows.columns.includes(column)))
throw new Error(`CSV must include: ${required.join(', ')}.`);
if (!rows.length) throw new Error('The CSV contains no companies.');
const ids = new Set();
const parseDay = d3.timeParse('%Y-%m-%d');
const parseMonth = d3.timeParse('%Y-%m');
const units = rows.map((row, index) => {
const announced = row.announced.trim();
const exact = parseDay(announced) || parseMonth(announced);
// Chart squares stack by month; the full date only orders and labels them.
const date = exact && d3.timeMonth.floor(exact);
const id = slug(row.company);
if (!id || ids.has(id) || !date)
throw new Error(
`Check CSV row ${index + 2}: unique company and valid announcement date (YYYY-MM-DD or YYYY-MM) required.`,
);
ids.add(id);
return {
id,
company: row.company.trim(),
category: row.sector.trim() || UNASSIGNED_SECTOR,
date,
announced: d3.timeFormat(
announced.length > 7 ? '%Y-%m-%d' : '%Y-%m',
)(exact),
value: null,
summary: row.summary.trim(),
agency: row.agency.trim(),
source: row.source.trim(),
status: normalizeStatus(row.status),
// Optional sheet column: a short answer to "what does the government own?"
stake: (row.stake || '').trim(),
};
});
// Order companies within a month by day, then by name.
units.sort(
(a, b) =>
a.announced.localeCompare(b.announced) ||
a.company.localeCompare(b.company),
);
// Alternate single-company sectors with larger ones so the singles
// separate the blocks and every label has room; Unassigned goes last.
const knownSectors = Object.keys(SECTOR_COLORS);
const rank = name =>
knownSectors.includes(name)
? knownSectors.indexOf(name)
: knownSectors.length;
const sectorCounts = d3.rollup(
units,
v => v.length,
unit => unit.category,
);
const sectors = [...sectorCounts.keys()]
.filter(name => name !== UNASSIGNED_SECTOR)
.sort((a, b) => rank(a) - rank(b));
const singles = sectors.filter(name => sectorCounts.get(name) === 1);
const blocks = sectors.filter(name => sectorCounts.get(name) > 1);
const stackOrder = [];
while (singles.length || blocks.length) {
if (singles.length) stackOrder.push(singles.shift());
if (blocks.length) stackOrder.push(blocks.shift());
}
if (sectorCounts.has(UNASSIGNED_SECTOR))
stackOrder.push(UNASSIGNED_SECTOR);
let spareIndex = 0;
categories = stackOrder.map(name => {
if (name === UNASSIGNED_SECTOR)
return { name, color: UNASSIGNED_COLOR };
if (SECTOR_COLORS[name])
return { name, color: SECTOR_COLORS[name] };
if (spareIndex >= SPARE_SECTOR_COLORS.length)
console.warn(
`No spare color left for sector "${name}"; reusing a color. Merge sectors or add a color.`,
);
const palette = [
...SPARE_SECTOR_COLORS,
...Object.values(SECTOR_COLORS),
];
return { name, color: palette[spareIndex++ % palette.length] };
});
const byMonth = d3.group(units, unit => +unit.date);
const months = d3.timeMonth.range(
d3.min(units, unit => unit.date),
d3.timeMonth.offset(
d3.max(units, unit => unit.date),
1,
),
);
let total = 0;
let count = 0;
return months.map(date => {
const values = byMonth.get(+date) || [];
const addition = d3.sum(values, unit => unit.value);
const start = total;
total += addition;
count += values.length;
return { date, values, addition, start, total, count };
});
}

function prepare() {
const units = data.flatMap(event => {
let cumulative = event.start;
let local = 0;
// Grouped by sector as in the Total column, closed deals at the bottom.
// Dates are by month, so this drops no order within a stack.
const ordered = [...event.values].sort(
(a, b) =>
categories.findIndex(item => item.name === a.category) -
categories.findIndex(item => item.name === b.category) ||
statusRank(a.status) - statusRank(b.status),
);
return ordered.map((unit, index) => {
const { value, category } = unit;
const start = cumulative;
const localStart = local;
cumulative += value;
local += value;
const countStart = event.count - event.values.length + index;
return {
...unit,
date: event.date,
start,
end: cumulative,
localStart,
localEnd: local,
countStart,
countEnd: countStart + 1,
localCountStart: index,
localCountEnd: index + 1,
value,
category,
index: index + 1,
};
});
});
const totalDate = d3.timeMonth.offset(data.at(-1).date, 1);
const sectorRank = name =>
categories.findIndex(category => category.name === name);
let level = 0;
const totalUnits = [...units]
.sort(
(a, b) =>
sectorRank(a.category) - sectorRank(b.category) ||
// Within a sector, closed deals sit at the bottom, announced on top.
statusRank(a.status) - statusRank(b.status) ||
b.value - a.value,
)
.map((unit, index) => {
const start = level;
level += unit.value;
return {
...unit,
sourceDate: unit.date,
date: totalDate,
start,
end: level,
countStart: index,
countEnd: index + 1,
};
});
return {
units,
totalDate,
totalUnits,
lineData: [...data, {...data.at(-1), date: totalDate }],
};
}

// The page chart fills #chart. The PNG export passes its own container and
// height to draw an offscreen copy without touching the page or its state.
function buildChart(
width,
{ height = CHART.height, target = chart } = {},
) {
const offscreen = target !== chart;
const { margin } = CHART;
const { units, totalDate, totalUnits } = prepare();
const color = d3.scaleOrdinal(
categories.map(d => d.name),
categories.map(d => d.color),
);
const x = d3
.scaleTime()
.range([CHART.edgeInset, width - CHART.edgeInset]);
const y = d3
.scaleLinear()
.range([height - margin.bottom, margin.top]);
const svg = d3
.create('svg')
.attr('viewBox', [0, 0, width, height])
.attr('width', width)
.attr('height', height)
.attr('role', 'group')
.attr(
'aria-label',
'Company marks. Use arrow keys to move between companies. Use enter to select.',
);
// Half squares need a pattern: one per sector, sized to each square.
// The export copy gets its own ids.
const defs = svg.append('defs');
const halfId = category =>
`${offscreen ? 'export-' : ''}status-half-${categories.findIndex(c => c.name === category)}`;
for (const category of categories) {
const pattern = defs
.append('pattern')
.attr('id', halfId(category.name))
.attr('patternUnits', 'objectBoundingBox')
.attr('patternContentUnits', 'objectBoundingBox')
.attr('width', 1)
.attr('height', 1);
pattern
.append('rect')
.attr('width', 1)
.attr('height', 1)
.attr('fill', paleColor(category.color));
pattern
.append('path')
.attr('d', 'M0 0V1H1Z')
.attr('fill', category.color);
}
const markFill = d => {
const fill = statusFill(d.status);
return fill === 'half'
? `url(#${halfId(d.category)})`
: fill === 'pale'
? paleColor(color(d.category))
: color(d.category);
};
const xAxis = svg
.append('g')
.attr('transform', `translate(0,${y(0)})`)
.attr('aria-hidden', 'true');
const connector = svg
.append('path')
.attr('class', 'plateau-lines')
.attr('fill', 'none')
// A quiet guide: the squares and totals carry the reading.
.attr('stroke', '#a1a1aa')
.attr('stroke-width', 1)
.attr('pointer-events', 'none');
// Invisible targets behind each square reach sideways into the column
// gaps, so a near miss still hovers and clicks. Stacked squares touch,
// so the targets only widen, never grow taller.
const hitPad = () => {
const lefts = [
...data.map(d => x(d.date)),
...(cumulative() ? [x(totalDate)] : []),
].sort((a, b) => a - b);
const step = d3.min(d3.pairs(lefts), ([a, b]) => b - a) ?? Infinity;
return Math.max(
0,
Math.min(8, Math.floor((step - squareSize) / 2) - 1),
);
};
const hits = svg
.append('g')
.attr('aria-hidden', 'true')
.selectAll('rect')
.data(units, d => d.id)
.join('rect')
.attr('fill', 'none')
.attr('pointer-events', 'all');
const bars = svg
.append('g')
.selectAll('rect')
.data(units, d => d.id)
.join('rect')
.attr('class', 'company-mark')
.attr('data-company-id', d => d.id)
.attr('fill', markFill);
const separators = svg
.append('g')
.attr('pointer-events', 'none')
.selectAll('line')
.data(
units.filter(
d =>
d.index <
data.find(event => +event.date === +d.date).values.length,
),
)
.join('line')
.attr('stroke', '#fff')
.attr('stroke-width', 1);
const totalGroup = svg.append('g').attr('class', 'total-column');
const totalHits = totalGroup
.append('g')
.attr('aria-hidden', 'true')
.selectAll('rect')
.data(totalUnits, d => d.id)
.join('rect')
.attr('fill', 'none')
.attr('pointer-events', 'all');
const totalBars = totalGroup
.selectAll('rect.company-mark')
.data(totalUnits, d => d.id)
.join('rect')
.attr('class', 'company-mark')
.attr('data-company-id', d => d.id)
.attr('fill', markFill);
const totalSeparators = totalGroup
.append('g')
.attr('pointer-events', 'none')
.selectAll('line')
.data(totalUnits.slice(0, -1))
.join('line')
.attr('stroke', '#fff')
.attr('stroke-width', 1);
const topLabels = svg
.append('g')
.attr('class', 'top-labels')
.selectAll('text')
.data(data)
.join('text');
const additions = svg
.append('g')
.attr('class', 'addition-labels')
.selectAll('text')
.data(data)
.join('text');
for (const labels of [topLabels, additions])
labels.attr('text-anchor', 'middle');
const finalLabel = totalGroup
.append('text')
.attr('class', 'final-label')
.attr('text-anchor', 'middle');
finalLabel
.append('tspan')
.attr('class', 'count')
.text(number(units.length));
const equalsLabel = totalGroup
.append('text')
.attr('class', 'equals-label')
.attr('text-anchor', 'middle')
.text('=');
const sectorLabels = totalGroup
.append('g')
.attr('class', 'total-sector-labels')
.attr('pointer-events', 'none')
.selectAll('g')
.data(
categories.map(category => {
const members = totalUnits.filter(
unit => unit.category === category.name,
);
const nameLines = category.name === 'Quantum computing' ? 2 : 1;
return {
...category,
count: members.length,
// Rough block height, used to space neighboring labels.
labelHeight:
(nameLines + (members.length > 1 ? 1 : 0)) *
TOTAL_LABELS.lineHeight,
midpoint:
(members[0].countStart + members.at(-1).countEnd) / 2,
};
}),
)
.join('g');
sectorLabels
.append('text')
.attr('fill', d => categoryTextColors[d.color] || d.color)
.attr(
'aria-label',
d =>
`${d.name}: ${d.count} ${d.count === 1 ? 'company' : 'companies'}`,
)
.each(function (d) {
const label = d3.select(this);
const nameLines =
d.name === 'Quantum computing'
? ['Quantum', 'computing']
: [d.name];
// Center the whole block, including the extra gap above the count.
const countStep = TOTAL_LABELS.lineHeight + TOTAL_LABELS.countGap;
// In the export, a wrapped name takes the same line step as its count.
const nameStep =
target === chart ? TOTAL_LABELS.lineHeight : countStep;
const blockHeight =
(nameLines.length - 1) * nameStep +
(d.count > 1 ? countStep : 0);
nameLines.forEach((line, index) => {
label
.append('tspan')
.attr('class', 'sector-name')
.attr('dy', index === 0 ? -blockHeight / 2 : nameStep)
.text(line);
});
if (d.count > 1)
label
.append('tspan')
.attr('class', 'sector-count')
.attr('dy', countStep)
.text(`${number(d.count)} companies`);
});
const selectionLabels = svg
.append('g')
.attr('class', 'selection-labels')
.attr('pointer-events', 'none');
let squareSize;
let layoutVersion = 0;
let stage = 0; // 0: marks moving; 1: marks settled and totals may be shown.
const cumulative = () => mode === 'cumulative';
const start = unit =>
cumulative() ? unit.countStart : unit.localCountStart;
const end = unit =>
cumulative() ? unit.countEnd : unit.localCountEnd;

function reveal(id) {
const unit = units.find(d => d.id === id);
if (!unit || chart.scrollWidth <= chart.clientWidth) return;
const center = x(unit.date);
if (
center < chart.scrollLeft + 44 ||
center > chart.scrollLeft + chart.clientWidth - 44
) {
chart.scrollTo({
left: center - chart.clientWidth / 2,
behavior: reducedMotion() ? 'instant' : 'smooth',
});
}
}

function applySelection() {
const id = activeId();
const needle = companyQuery.toLowerCase();
const matches = new Set(
units.filter(matchesFilters).map(d => d.id),
);
const filtering =
Boolean(needle) ||
activeSector !== 'All' ||
activeStatus !== 'All' ||
activeAgency !== 'All';
const dim = d => (id ? d.id !== id : !matches.has(d.id));
// While focus is outside the chart, Tab enters on the selected
// company, or the first one when nothing is selected.
if (!document.activeElement?.classList.contains('company-mark')) {
const stopId = selectedId ?? units[0].id;
bars.attr('tabindex', unit => (unit.id === stopId ? 0 : -1));
}
for (const marks of [bars, totalBars]) {
marks
.attr('opacity', d => (dim(d) ? SELECTION.mutedMark : 1))
.attr('aria-pressed', d => String(d.id === selectedId));
}
connector.attr(
'opacity',
id || filtering ? SELECTION.mutedConnector : 1,
);
for (const marks of [topLabels, additions, finalLabel, equalsLabel])
marks.attr('opacity', id || filtering ? SELECTION.mutedLabel : 1);
const source = units.find(d => d.id === id);
// Faded sector names turn one neutral gray rather than a pastel of their
// color, so the active sector is the only colored text in the column.
const sectorMuted = d =>
source
? source.category !== d.name
: filtering &&
!units.some(
unit => unit.category === d.name && matches.has(unit.id),
);
sectorLabels
.select('text')
.attr('fill', d =>
sectorMuted(d)
? SELECTION.mutedText
: categoryTextColors[d.color] || d.color,
);
sectorLabels
.selectAll('.sector-count')
.style('fill', d =>
sectorMuted(d) ? SELECTION.mutedText : null,
);
const labels = source ? [source] : [];
selectionLabels
.attr('visibility', stage >= 1 ? null : 'hidden')
.selectAll('text')
.data(labels)
.join('text')
.attr(
'x',
d =>
x(d.date) +
(x(d.date) > width / 2 ? -1 : 1) * (squareSize / 2 + 8),
)
.attr('y', d => y((start(d) + end(d)) / 2))
.attr('dominant-baseline', 'central')
.attr('text-anchor', d =>
x(d.date) > width / 2 ? 'end' : 'start',
)
.attr('stroke', '#fff')
.attr('stroke-width', 4)
.attr('paint-order', 'stroke')
.text(d => d.company);
if (!offscreen) showDetails(source);
}

// Keyboard focus gets a dark ring drawn outside the square, on top
// of the white separators, so it never reads as another gap.
const focusRing = svg
.append('rect')
.attr('class', 'mark-focus-ring')
.attr('aria-hidden', 'true')
.attr('display', 'none');
function showFocusRing(mark) {
if (!mark.matches(':focus-visible')) return;
const box = mark.getBBox();
const m = svg
.node()
.getScreenCTM()
.inverse()
.multiply(mark.getScreenCTM());
const gap = 2;
focusRing
.attr('x', m.e + box.x * m.a - gap)
.attr('y', m.f + box.y * m.d - gap)
.attr('width', box.width * m.a + gap * 2)
.attr('height', box.height * m.d + gap * 2)
.attr('display', null)
.raise();
if (!hintDismissed) showKeyboardHint(mark);
}
// Sighted keyboard users don't hear the aria-label, so the first
// keyboard visit shows how to move; it goes once an arrow is used.
let hintDismissed = false;
const keyboardHint = svg
.append('g')
.attr('class', 'keyboard-hint')
.attr('aria-hidden', 'true')
.attr('pointer-events', 'none')
.attr('display', 'none');
const hintBox = keyboardHint.append('rect');
const hintText = keyboardHint
.append('text')
.attr('dominant-baseline', 'central')
.attr('text-anchor', 'middle')
.text('← → to move. Use enter to select');
function showKeyboardHint(mark) {
keyboardHint.attr('display', null).raise();
const ring = focusRing.node().getBBox();
const text = hintText.node().getBBox();
const w = text.width + 16;
const h = 24;
const cx = Math.max(
w / 2 + 4,
Math.min(width - w / 2 - 4, ring.x + ring.width / 2),
);
const top =
ring.y - h - 6 >= 0 ? ring.y - h - 6 : ring.y + ring.height + 6;
hintBox
.attr('x', cx - w / 2)
.attr('y', top)
.attr('width', w)
.attr('height', h);
hintText.attr('x', cx).attr('y', top + h / 2);
}
function hideKeyboardHint(dismiss = false) {
if (dismiss) hintDismissed = true;
keyboardHint.attr('display', 'none');
}
for (const marks of [hits, totalHits]) {
marks
.on('pointerenter', (event, d) => {
if (event.pointerType === 'touch') return;
previewId = d.id;
applySelection();
})
.on('pointerleave', () => {
previewId = null;
applySelection();
})
.on('click', (event, d) => toggleCompany(d.id));
}
for (const marks of [bars, totalBars]) {
marks
.attr('role', 'button')
.attr('aria-controls', 'company-cards')
.attr(
'aria-label',
d =>
`${d.company}, ${d.category}, announced ${formatDate(d.announced)}, ${d.agency}${d.status ? `, status: ${d.status}` : ''}`,
)
.attr('tabindex', (d, i) => (marks === bars && i === 0 ? 0 : -1))
.on('pointerenter', (event, d) => {
if (event.pointerType === 'touch') return;
previewId = d.id;
applySelection();
})
.on('pointerleave', () => {
previewId = null;
applySelection();
})
.on('focus', (event, d) => {
showFocusRing(event.currentTarget);
bars.attr('tabindex', unit => (unit.id === d.id ? 0 : -1));
previewId = d.id;
reveal(d.id);
applySelection();
})
.on('blur', () => {
focusRing.attr('display', 'none');
hideKeyboardHint();
previewId = null;
applySelection();
})
.on('click', (event, d) => toggleCompany(d.id))
.on('keydown', (event, d) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
toggleCompany(d.id);
} else if (
[
'ArrowRight',
'ArrowLeft',
'ArrowUp',
'ArrowDown',
'Home',
'End',
].includes(event.key)
) {
event.preventDefault();
hideKeyboardHint(true);
const index = units.findIndex(unit => unit.id === d.id);
const next =
event.key === 'Home'
? 0
: event.key === 'End'
? units.length - 1
: (index +
(['ArrowRight', 'ArrowDown'].includes(event.key)
? 1
: -1) +
units.length) %
units.length;
bars
.filter(unit => unit.id === units[next].id)
.node()
.focus({ preventScroll: true });
}
});
}

function setMode(next, animate = true) {
mode = next;
svg.attr('data-mode', mode);
previewId = null;
const version = ++layoutVersion;
stage = 0;
svg.interrupt('layout');
svg.selectAll('*').interrupt('layout');
connector.attr('visibility', 'hidden');
totalGroup.attr('visibility', 'hidden').attr('aria-hidden', 'true');
selectionLabels.attr('visibility', 'hidden');
const duration =
animate && !reducedMotion() ? TIMING.settleMarks : 0;
if (!offscreen)
document.querySelector('.chart-meta').hidden =
cumulative() && directLabels;
sectorLabels.attr('display', directLabels ? null : 'none');
equalsLabel.attr('display', 'none').attr('y', y(0) + 40);
svg.classed('arithmetic-labels', false);
// Size the gutter to the longest rendered line, including wrapped names and counts.
const labelLineWidth =
cumulative() && directLabels
? d3.max(sectorLabels.selectAll('tspan').nodes(), node =>
node.getComputedTextLength(),
)
: 0;
const labelGutter =
labelLineWidth > 0
? Math.ceil(
labelLineWidth +
TOTAL_LABELS.offset +
TOTAL_LABELS.edgePadding,
)
: TOTAL_LABELS.fallbackGutter;
x.range([
CHART.edgeInset,
width -
(cumulative() && directLabels ? labelGutter : CHART.edgeInset),
]);
x.domain([
data[0].date,
cumulative() ? totalDate : data.at(-1).date,
]);
if (cumulative() && data.length > 1) {
// Reserve extra space for the summary while preserving the monthly timeline.
const [left, right] = x.range();
const lastMonthX = x(data.at(-1).date) - CHART.totalExtraGap;
x.domain([data[0].date, data.at(-1).date, totalDate]).range([
left,
lastMonthX,
right,
]);
}
y.domain([
0,
cumulative()
? units.length * 1.04
: d3.max(data, d => d.values.length) + 2,
]);
// Snap squares to whole pixels: a fractional size blurs every edge into
// a faint double row, which reads as a stroke around each stack.
{
const base = Math.round(height - margin.bottom);
const step = Math.max(
1,
Math.floor((base - margin.top) / y.domain()[1]),
);
y.range([base, base - step * y.domain()[1]]);
}
squareSize = y(0) - y(1);
const markLeft = date => Math.round(x(date) - squareSize / 2);
const t = svg
.transition('layout')
.duration(duration)
.ease(d3.easeCubicInOut);
const plotWidth = cumulative()
? x(totalDate) + squareSize / 2
: width - margin.left - margin.right;
xAxis.attr('transform', `translate(0,${y(0)})`).call(
d3
.axisBottom(x)
.tickValues([
...data.map(d => d.date),
...(cumulative() ? [totalDate] : []),
])
.tickFormat(d => {
if (+d === +totalDate)
return arithmeticLabels ? 'Total' : 'All companies';
return d3.timeFormat('%b')(d);
})
.tickSize(0)
.tickPadding(14),
);
xAxis
.selectAll('.tick')
.classed('summary-tick', d => cumulative() && +d === +totalDate);
xAxis.selectAll('.tick text').each(function (d) {
if (+d === +totalDate) return;
if (+d === +data[0].date || d.getMonth() === 0) {
d3.select(this)
.append('tspan')
.attr('x', 0)
.attr('dy', '1.2em')
.text(d3.timeFormat('%Y')(d));
}
});
xAxis
.select('.domain')
.attr('stroke', '#a1a1aa')
.attr('d', `M${margin.left},0.5H${margin.left + plotWidth}`);
const pad = hitPad();
hits
.transition(t)
.attr('x', d => markLeft(d.date) - pad)
.attr('width', squareSize + pad * 2)
.attr('height', squareSize)
.attr('y', d => y(end(d)));
bars
.transition(t)
.attr('x', d => markLeft(d.date))
.attr('width', squareSize)
.attr('height', squareSize)
.attr('y', d => y(end(d)));
separators
.transition(t)
.attr('x1', d => markLeft(d.date))
.attr('x2', d => markLeft(d.date) + squareSize)
.attr('y1', d => Math.round(y(end(d))) + 0.5)
.attr('y2', d => Math.round(y(end(d))) + 0.5);
topLabels
.text(d =>
cumulative() && d.values.length && d.count < units.length
? number(d.count)
: '',
)
.transition(t)
.attr('x', d => x(d.date))
.attr('y', d => y(cumulative() ? d.count : d.values.length) - 8);
// Final column edges and label tops, for placeIntro() to test against.
if (!offscreen)
columnTops = data.map(d => ({
left: x(d.date) - squareSize / 2,
top:
y(cumulative() ? d.count : d.values.length) -
8 -
TOTAL_LABELS.lineHeight -
4,
}));
// Label larger monthly additions beside the center of their stacks.
additions
.text(d =>
d.values.length > 1 ? `+${number(d.values.length)}` : '',
)
.attr('text-anchor', 'start')
.attr('dominant-baseline', 'central')
.transition(t)
.attr('x', d => x(d.date) + squareSize / 2 + 6)
.attr('y', d =>
y(
cumulative()
? d.count - d.values.length / 2
: d.values.length / 2,
),
);
// Each plateau runs only through the gap between one stack and the next,
// so it never shows as a hairline along a stack's top or bottom edge.
// Empty months have no stack, so the line passes straight through them.
const stacks = [
...data.filter(d => d.values.length).map(d => markLeft(d.date)),
markLeft(totalDate),
];
const levels = data
.filter(d => d.values.length)
.map(d => Math.round(y(d.count)));
connector.attr(
'd',
levels
.map(
(level, i) =>
`M${stacks[i] + squareSize},${level + 0.5}H${stacks[i + 1]}`,
)
.join(''),
);
totalHits
.attr('x', markLeft(totalDate) - pad)
.attr('width', squareSize + pad * 2)
.attr('height', squareSize)
.attr('y', d => y(d.countEnd));
totalBars
.attr('x', markLeft(totalDate))
.attr('width', squareSize)
.attr('height', squareSize)
.attr('y', d => y(d.countEnd));
totalSeparators
.attr('x1', markLeft(totalDate))
.attr('x2', markLeft(totalDate) + squareSize)
.attr('y1', d => Math.round(y(d.countEnd)) + 0.5)
.attr('y2', d => Math.round(y(d.countEnd)) + 0.5);
// Place sector names beside their segment, separating neighboring one-company sectors.
// Labels that would collide form a run spaced one gap apart, centered on
// their segments so no label drifts far from its own squares.
const runs = [];
[...sectorLabels.data()]
.sort((a, b) => b.midpoint - a.midpoint)
.forEach(d => {
let run = {
items: [
{
name: d.name,
targetY: y(d.midpoint),
height: d.labelHeight,
},
],
};
for (;;) {
// Each label's distance below the run's first label.
let offset = 0;
run.items.forEach((item, i) => {
if (i)
offset +=
(run.items[i - 1].height + item.height) / 2 +
TOTAL_LABELS.gap;
item.offset = offset;
});
run.startY = Math.max(
TOTAL_LABELS.edgePadding,
d3.mean(run.items, item => item.targetY - item.offset),
);
const previous = runs.at(-1);
const last = previous?.items.at(-1);
if (
!previous ||
previous.startY +
last.offset +
(last.height + run.items[0].height) / 2 +
TOTAL_LABELS.gap <=
run.startY
)
break;
runs.pop();
run = { items: [...previous.items,...run.items] };
}
runs.push(run);
});
const positions = new Map(
runs.flatMap(run =>
run.items.map(item => [
item.name,
{
targetY: item.targetY,
labelY: run.startY + item.offset,
},
]),
),
);
const labelX = x(totalDate) + TOTAL_LABELS.offset;
sectorLabels
.select('text')
.attr('x', labelX)
.attr('y', d => positions.get(d.name).labelY)
.attr('dominant-baseline', 'central');
sectorLabels.selectAll('tspan').attr('x', labelX);

// Keep the centered total label inside the SVG even at its right edge.
const labelHalfWidth =
d3.max(finalLabel.selectAll('tspan').nodes(), node =>
node.getComputedTextLength(),
) / 2;
equalsLabel.attr('x', (x(data.at(-1).date) + x(totalDate)) / 2);
finalLabel
.attr('y', y(units.length) - 8)
.selectAll('tspan')
.attr('x', Math.min(x(totalDate), width - labelHalfWidth - 2));
if (!offscreen)
chart.setAttribute(
'aria-label',
`${cumulative() ? 'Cumulative' : 'Monthly'} company announcements. One mark per company.`,
);
applySelection();
if (selectedId && !offscreen) reveal(selectedId);
const settle = () => {
if (version !== layoutVersion || !svg.node().isConnected) return;
stage = 1;
connector.attr('visibility', cumulative() ? null : 'hidden');
totalGroup
.attr('visibility', cumulative() ? null : 'hidden')
.attr('aria-hidden', String(!cumulative()));
selectionLabels.attr('visibility', null);
applySelection();
};
if (duration) {
// Await the actual mark transition, rather than racing a separate timeout.
bars
.transition(t)
.end()
.then(settle, () => {});
} else settle();
}
target.replaceChildren(svg.node());
if (offscreen) {
setMode(mode, false);
return svg.node();
}
scene = { setMode, applySelection, reveal, units };
setMode(mode, false);
}

// Below the wide layout, the keys sit inside the chart when they
// clear every month's stack and label; otherwise they stay under the title.
// Once the chart scrolls sideways, they always take their own row.
let columnTops = [];
function placeIntro() {
const root = document.querySelector('#fed-stakes');
const layout = root.querySelector('.fed-stakes-layout');
const style = getComputedStyle(layout);
const layoutWidth =
layout.clientWidth -
parseFloat(style.paddingLeft) -
parseFloat(style.paddingRight);
root.classList.remove('intro-inset', 'intro-inline');
if (layoutWidth >= 750) return;
if (chart.scrollWidth > chart.clientWidth) {
root.classList.add('intro-inline');
return;
}
root.classList.add('intro-inset');
const frame = chart.getBoundingClientRect();
const intro = root.querySelector('.intro').getBoundingClientRect();
const right = intro.right - frame.left + 16;
const bottom = intro.bottom - frame.top + 8;
const fits = columnTops.every(
column => column.left > right || column.top > bottom,
);
if (!fits) root.classList.replace('intro-inset', 'intro-inline');
}

function createAnnouncementLink(unit) {
let url;
try {
url = new URL(unit.source);
} catch {
return null;
}
if (!['https:', 'http:'].includes(url.protocol)) return null;
const link = document.createElement('a');
link.className = 'company-announcement';
link.href = url.href;
link.target = '_blank';
link.rel = 'noopener noreferrer';
const label = `Read the announcement for ${unit.company} (opens in a new tab)`;
link.setAttribute('aria-label', label);
link.title = label;
// Lucide-style external-link icon; inline SVG avoids another runtime dependency.
link.innerHTML =
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M15 3h6v6"/><path d="M10 14 21 3"/><path d="M21 14v5a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5"/>';
return link;
}

const SUMMARY_LINES = 3; // Lines of summary a card shows before Show more.
const summaryObserver = new ResizeObserver(entries => {
// Showing or hiding Show more changes a card's height, so the row is
// measured again; otherwise its fixed height clips the cards.
let changed = false;
for (const { target } of entries)
changed = target.syncClamp() || changed;
if (changed)
requestAnimationFrame(() =>
updateCarouselControls({ updateRange: false }),
);
});

function buildCompanyCards() {
const container = document.querySelector('#company-cards');
if (container.children.length) return;
for (const unit of [...scene.units].sort(
(a, b) =>
a.announced.localeCompare(b.announced) ||
a.company.localeCompare(b.company),
)) {
const card = document.createElement('article');
card.className = 'company-card';
card.id = `company-${unit.id}`;
card.dataset.companyId = unit.id;
card.dataset.category = unit.category;
const sectorColor = categories.find(
c => c.name === unit.category,
).color;
const heading = document.createElement('h3');
const selectButton = document.createElement('button');
selectButton.type = 'button';
selectButton.className = 'company-select';
selectButton.textContent = unit.company;
selectButton.setAttribute(
'aria-label',
`Highlight ${unit.company} on the chart`,
);
selectButton.setAttribute('aria-controls', 'chart');
selectButton.setAttribute('aria-pressed', 'false');
heading.append(selectButton);
const announcementLink = createAnnouncementLink(unit);
card.addEventListener('click', event => {
// Keep other controls independent of selection.
const control = event.target.closest(
'button, a, input, select, textarea',
);
if (control && control !== selectButton) return;
const textSelection = window.getSelection();
if (
!control &&
!textSelection.isCollapsed &&
card.contains(textSelection.anchorNode)
)
return;
toggleCompany(unit.id);
});
const header = document.createElement('div');
header.className = 'company-card-header';
header.append(heading);
// One labeled line each: Sector, Status, Date, Agency. Values get the
// card's width, so they rarely wrap even on narrow cards.
const facts = document.createElement('dl');
facts.className = 'detail-facts';
const agencies = unitAgencies(unit);
const fact = (className, label, value) => {
const pair = document.createElement('div');
const term = document.createElement('dt');
const description = document.createElement('dd');
pair.className = className;
term.textContent = label;
description.append(...value);
pair.append(term, description);
return pair;
};
const date = document.createElement('time');
date.dateTime = unit.announced;
date.textContent = d3.timeFormat('%B %Y')(
d3.timeParse('%Y-%m')(unit.announced.slice(0, 7)),
);
// One agency per line, so a name never breaks across the comma.
const agencyLines = agencies.flatMap((agency, index) =>
index ? [document.createElement('br'), agency] : [agency],
);
// The date above the name; sector and status below it, then the agency.
const meta = document.createElement('p');
meta.className = 'card-meta';
meta.append(date);
const sector = document.createElement('p');
sector.className = 'card-sector';
// The card's square is the company's chart mark: sector color with the
// status fill. The text names both, split at a pipe so a wrap breaks there.
const sectorName = document.createElement('span');
sectorName.className = 'card-sector-name';
sectorName.innerHTML = statusIcon(unit.status, sectorColor);
sectorName.append(unit.category);
sector.append(sectorName);
if (unit.status) {
const divider = document.createElement('span');
divider.className = 'card-divider';
divider.setAttribute('aria-hidden', 'true');
divider.textContent = ' | ';
const status = document.createElement('span');
status.className = 'card-status';
status.textContent = unit.status;
sector.append(divider, status);
}
const agency = document.createElement('p');
agency.className = 'card-agency';
agency.append(...agencyLines);
header.prepend(meta);
header.append(sector, agency);
facts.append(
...(unit.stake
? [fact('metadata-stake', 'Stake', [unit.stake])]
: []),
);
const summary = document.createElement('p');
summary.className = 'detail-summary';
summary.id = `summary-${unit.id}`;
summary.textContent = unit.summary;
if (announcementLink) {
// The source icon ends the full text, kept with the final word. The
// cut preview leaves it out, so Show more only ever expands.
const ending = document.createElement('span');
ending.className = 'company-announcement-ending';
const parts = unit.summary.match(/^(.*\s)?(\S+)\s*$/s);
summary.textContent = parts ? parts[1] || '' : '';
ending.append(parts ? parts[2] : unit.summary, announcementLink);
summary.append(ending);
}
// Long summaries show a preview cut at the last whole word that fits
// in SUMMARY_LINES, ending "… Show more" on that same line. Show more
// swaps in the full text, with Show less below it.
const more = document.createElement('button');
more.type = 'button';
more.className = 'summary-toggle';
more.setAttribute('aria-controls', summary.id);
const preview = document.createElement('p');
preview.className = 'detail-summary summary-preview';
const actions = document.createElement('div');
actions.className = 'summary-actions';
const words = unit.summary.split(/\s+/);
const summaryWrap = document.createElement('div');
summaryWrap.className = 'summary-wrap';
summaryWrap.append(summary, preview, actions);
let open = false;
let mode = '';
const fillPreview = count => {
preview.textContent = `${words
.slice(0, count)
.join(' ')
.replace(/[\s,;:.]+$/, '')}… `;
preview.append(more);
};
summaryWrap.syncClamp = () => {
const previous = mode;
summary.hidden = false;
preview.hidden = true;
actions.hidden = true;
if (open) {
more.textContent = 'Show less';
actions.append(more);
actions.hidden = false;
mode = 'open';
} else {
const style = getComputedStyle(summary);
const limit =
summary.offsetHeight -
summary.clientHeight +
parseFloat(style.paddingTop) +
parseFloat(style.lineHeight) * SUMMARY_LINES +
1;
if (!summary.offsetHeight || summary.offsetHeight <= limit) {
mode = summary.offsetHeight ? 'full' : '';
} else {
more.textContent = 'Show more';
summary.hidden = true;
preview.hidden = false;
// The most whole words that still fit, found by halving.
let low = 1;
let high = words.length - 1;
while (low < high) {
const middle = Math.ceil((low + high) / 2);
fillPreview(middle);
if (preview.offsetHeight <= limit) low = middle;
else high = middle - 1;
}
fillPreview(low);
mode = `cut-${low}`;
}
}
more.setAttribute('aria-expanded', String(open));
return mode !== previous;
};
more.addEventListener('click', () => {
open = !open;
card.classList.toggle('is-open', open);
summaryWrap.syncClamp();
more.focus();
updateCarouselControls({ updateRange: false });
});
// Measured whenever the card is laid out or resized.
summaryObserver.observe(summaryWrap);
card.append(header, facts, summaryWrap);
container.append(card);
}
buildSectorTabs();
setSector(activeSector || sectorGroups[0].name);
// Inter can arrive after the first measure and rewrap the text.
document.fonts?.ready.then(() =>
updateCarouselControls({ updateRange: false }),
);
let carouselWidth = container.clientWidth;
new ResizeObserver(() => {
if (container.clientWidth === carouselWidth) return;
carouselWidth = container.clientWidth;
sizeCarousel();
const selected = visibleCards().find(
card => card.dataset.companyId === selectedId,
);
if (carouselNavigated)
alignCard(selected || visibleCards()[0], false);
else updateCarouselControls();
}).observe(container);
let rangeUpdateTimer;
container.addEventListener(
'scroll',
() => {
updateCarouselControls({ updateRange: false });
// Fallback for browsers without scrollend support.
if (!('onscrollend' in container)) {
clearTimeout(rangeUpdateTimer);
rangeUpdateTimer = setTimeout(updateCarouselControls, 150);
}
},
{ passive: true },
);
container.addEventListener('scrollend', () =>
updateCarouselControls(),
);
}

function showDetails(unit) {
document.querySelectorAll('.company-card').forEach(card => {
card.classList.toggle(
'is-active',
card.dataset.companyId === unit?.id,
);
card.classList.toggle(
'is-muted',
Boolean(selectedId) && card.dataset.companyId !== selectedId,
);
card.classList.toggle(
'is-selected',
card.dataset.companyId === selectedId,
);
card
.querySelector('.company-select')
.setAttribute(
'aria-pressed',
String(card.dataset.companyId === selectedId),
);
});
const selected = scene?.units.find(item => item.id === selectedId);
document.querySelector('#selection-context').textContent =
previewId && previewId !== selectedId
? `Preview: ${unit?.company || ''} (${unit?.category || ''})${selected ? ` · Selected: ${selected.company} (${selected.category})` : ''}`
: selected
? `Selected: ${selected.company} (${selected.category})`
: 'Hover or focus to preview. Click to select.';
if ((unit?.id || null) === detailId) return;
detailId = unit?.id || null;
document.querySelector('#selection-announcement').textContent = unit
? `${unit.company}. ${unit.category}. ${unit.agency}. ${unit.summary}`
: '';
}

const matchesStatus = unit =>
activeStatus === 'All' || unit.status === activeStatus;
const matchesAgency = unit =>
activeAgency === 'All' || unitAgencies(unit).includes(activeAgency);
const matchesFilters = unit =>
(activeSector === 'All' || unit.category === activeSector) &&
matchesStatus(unit) &&
matchesAgency(unit) &&
unit.company.toLowerCase().includes(companyQuery.toLowerCase());

function filterCompanyCards({ resetScroll = true } = {}) {
const matches = new Set(
(scene?.units || [])
.filter(
unit =>
(activeSector === 'All' || unit.category === activeSector) &&
matchesStatus(unit) &&
matchesAgency(unit) &&
// The carousel keeps neighbors in view around a selection; the grid
// narrows to the selected card so it isn't buried down the page.
(selectedId
? !expandedDirectory || unit.id === selectedId
: matchesFilters(unit)),
)
.map(unit => unit.id),
);
document
.querySelectorAll('#company-cards.company-card')
.forEach(card => {
card.hidden = !matches.has(card.dataset.companyId);
});
// With no filter or selection, the list starts at two rows. Any filter
// shows every match.
matchedCount = matches.size;
const filtering =
Boolean(companyQuery) ||
activeSector !== 'All' ||
activeStatus !== 'All' ||
activeAgency !== 'All';
const preview = previewCount();
const long = !selectedId && !filtering && matches.size > preview;
if (long && !showAllCards)
visibleCards()
.slice(preview)
.forEach(card => (card.hidden = true));
const showAll = document.querySelector('#show-all-cards');
showAll.hidden = !long;
showAll.textContent = showAllCards
? '− Show less'
: `+ Show all ${matches.size} companies`;
showAll.setAttribute('aria-expanded', String(showAllCards));
sizeCarousel();
document.querySelector('#directory-empty').hidden = matches.size > 0;
if (selectedId && !matches.has(selectedId)) {
selectedId = null;
syncUrl();
}
if (resetScroll) {
document
.querySelector('#company-cards')
.scrollTo({ left: 0, behavior: 'instant' });
}
updateCarouselControls();
updateFilterOptions();
}

// Each filter menu counts only the companies the other filters allow, and
// drops options with none. The current choice stays listed so it can be seen.
function updateFilterOptions() {
if (!scene) return;
const inSector = unit =>
activeSector === 'All' || unit.category === activeSector;
const menus = [
[
'#sector-combobox',
activeSector,
(unit, name) =>
unit.category === name &&
matchesStatus(unit) &&
matchesAgency(unit),
],
[
'#agency-combobox',
activeAgency,
(unit, name) =>
unitAgencies(unit).includes(name) &&
inSector(unit) &&
matchesStatus(unit),
],
[
'#status-combobox',
activeStatus,
(unit, name) =>
unit.status === name && inSector(unit) && matchesAgency(unit),
],
];
menus.forEach(([selector, active, matches]) => {
const combo = document.querySelector(selector);
if (!combo) return;
combo.querySelectorAll('[role="option"]').forEach(option => {
const name = option.dataset.value;
const count = scene.units.filter(unit =>
matches(unit, name),
).length;
option.querySelector('.sector-option-count').textContent = count;
const empty = count === 0 && name !== active;
option.hidden = empty;
option.setAttribute('aria-disabled', String(empty));
});
combo.refresh?.();
});
}

// Two full rows at the grid's current column count.
function previewCount() {
if (phoneLayout.matches) return PHONE_PREVIEW;
const columns = getComputedStyle(
document.querySelector('#company-cards'),
).gridTemplateColumns.split(' ').length;
return Math.max(1, columns) * PREVIEW_ROWS;
}
let previewSize = 0;
window.addEventListener('resize', () => {
const count = previewCount();
if (count === previewSize) return;
previewSize = count;
filterCompanyCards({ resetScroll: false });
});

const visibleCards = () =>
[...document.querySelectorAll('#company-cards.company-card')].filter(
card => !card.hidden,
);

function buildSectorTabs() {
const tabs = document.querySelector('#sector-tabs');

sectorGroups = [...new Set(categories.map(category => category.name))]
.map(name => ({
name,
color: categories.find(category => category.name === name).color,
count: scene.units.filter(unit => unit.category === name).length,
}))
.sort((a, b) =>
a.name.localeCompare(b.name, 'en', { sensitivity: 'base' }),
);
tabs.replaceChildren();
const all = document.createElement('button');
all.type = 'button';
all.id = 'sector-all';
all.textContent = 'All';
all.setAttribute('aria-controls', 'company-cards');
all.style.setProperty('--sector-color', '#3f3f46');
all.addEventListener('click', () => setSector('All'));
const divider = document.createElement('span');
divider.className = 'sector-tab-divider';
divider.setAttribute('aria-hidden', 'true');
const select = document.createElement('select');
select.id = 'sector-select';
select.setAttribute('aria-labelledby', 'sector-field-label');
select.setAttribute('aria-controls', 'company-cards');
select.add(new Option('Choose a sector…', 'All'));
sectorGroups.forEach(category => {
select.add(
new Option(`${category.name} · ${category.count}`, category.name),
);
});
select.addEventListener('change', () => setSector(select.value));
const classic = document.createElement('div');
classic.id = 'classic-sector-controls';
classic.className = 'sector-tabs';
classic.style.flex = '1';
classic.append(all, divider, select);
const template = document.createElement('template');
template.innerHTML = `<div id="sector-combobox" class="combobox" data-auto-highlight="true">
<input id="sector-combo-input" type="text" role="combobox" aria-labelledby="sector-field-label" aria-autocomplete="list" aria-expanded="false" aria-controls="sector-options" placeholder="Sector" autocomplete="off" autocorrect="off" spellcheck="false" />
<svg class="combobox-trigger-icon" aria-hidden="true" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6" />
<button id="clear-sector" type="button" data-clear aria-label="Clear sector filter" hidden><svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M18 6 6 18M6 6l12 12" />
<div id="sector-popover" data-popover aria-hidden="true">
<div id="sector-options" role="listbox" aria-label="Sectors" aria-orientation="vertical" data-empty="No sectors found.">

&#96;; const combo = template.content.firstElementChild; const list = combo.querySelector('[role="listbox"]'); sectorGroups.forEach(group =&gt; { const option = document.createElement('div'); option.setAttribute('role', 'option'); option.dataset.value = group.name; option.dataset.label = group.name; const swatch = document.createElement('span'); swatch.className = 'sector-option-swatch'; swatch.style.background = group.color; swatch.setAttribute('aria-hidden', 'true'); const count = document.createElement('span'); count.className = 'sector-option-count'; count.textContent = group.count; option.append(swatch, document.createTextNode(group.name), count); list.append(option); }); tabs.append(classic, combo); window.basecoat.initAll(); combo.addEventListener('change', event =&gt; { if (event.target !== combo &#124;&#124; syncingComboboxes &#124;&#124; !event.detail) return; setSector(event.detail.value &#124;&#124; 'All'); }); connectComboboxFocus(combo, () =&gt; activeSector === 'All' ? '' : activeSector, ); syncSectorControlStyle(); buildAgencyFilter(); buildStatusFilter(); } // Agencies, most deals first; a deal with two agencies counts for each. function buildAgencyFilter() { const counts = d3.rollup( scene.units.flatMap(unitAgencies), values =&gt; values.length, name =&gt; name, ); const groups = [...counts].map(([name, count]) =&gt; ({ name, count })).sort((a, b) =&gt; b.count - a.count &#124;&#124; a.name.localeCompare(b.name)); document.querySelector('.agency-field').hidden = groups.length &lt; 2; if (groups.length &lt; 2) return; const template = document.createElement('template'); template.innerHTML = &#96; 

&#96;; const combo = template.content.firstElementChild; const list = combo.querySelector('[role="listbox"]'); groups.forEach(group =&gt; { const option = document.createElement('div'); option.setAttribute('role', 'option'); option.dataset.value = group.name; // Full names in the menu; short names stay in links and file names. option.dataset.label = group.name; const count = document.createElement('span'); count.className = 'sector-option-count'; count.textContent = group.count; option.append(document.createTextNode(group.name), count); list.append(option); }); document.querySelector('#agency-filter').replaceChildren(combo); window.basecoat.initAll(); combo.addEventListener('change', event =&gt; { if (event.target !== combo &#124;&#124; syncingComboboxes &#124;&#124; !event.detail) return; setAgency(event.detail.value &#124;&#124; 'All'); }); connectComboboxFocus(combo, () =&gt; activeAgency === 'All' ? '' : activeAgency, ); } function setAgency(name) { const selectedCompany = scene?.units.find( unit =&gt; unit.id === selectedId, ); if ( selectedCompany &amp;&amp; name !== 'All' &amp;&amp; !unitAgencies(selectedCompany).includes(name) ) { selectedId = null; companyQuery = ''; searchInput.value = ''; closeCompanyOptions(); } activeAgency = name; syncComboboxValue( document.querySelector('#agency-combobox'), name === 'All' ? '' : name, ); // setSector redraws the cards, chart, company list, and URL. setSector(activeSector); } // A third combobox, shown only when the sheet has statuses. Options keep the // table's order (Closed, Partially closed, Announced) rather than A–Z. function buildStatusFilter() { const groups = STATUSES.map(status =&gt; ({...status, count: scene.units.filter(unit =&gt; unit.status === status.name).length, })).filter(group =&gt; group.count); document.querySelector('.status-field:not(.agency-field)').hidden = !groups.length; if (!groups.length) return; const template = document.createElement('template'); template.innerHTML = &#96; 

&#96;; const combo = template.content.firstElementChild; const list = combo.querySelector('[role="listbox"]'); groups.forEach(group =&gt; { const option = document.createElement('div'); option.setAttribute('role', 'option'); option.dataset.value = group.name; option.dataset.label = group.name; option.innerHTML = statusIcon(group.name); option.querySelector('svg').style.marginRight = '0'; const count = document.createElement('span'); count.className = 'sector-option-count'; count.textContent = group.count; option.append(document.createTextNode(group.name), count); list.append(option); }); document.querySelector('#status-filter').replaceChildren(combo); window.basecoat.initAll(); combo.addEventListener('change', event =&gt; { if (event.target !== combo &#124;&#124; syncingComboboxes &#124;&#124; !event.detail) return; setStatus(event.detail.value &#124;&#124; 'All'); }); connectComboboxFocus(combo, () =&gt; activeStatus === 'All' ? '' : activeStatus, ); } function setStatus(name) { const selectedCompany = scene?.units.find( unit =&gt; unit.id === selectedId, ); if ( selectedCompany &amp;&amp; name !== 'All' &amp;&amp; selectedCompany.status !== name ) { selectedId = null; companyQuery = ''; searchInput.value = ''; closeCompanyOptions(); } activeStatus = name; syncComboboxValue( document.querySelector('#status-combobox'), name === 'All' ? '' : name, ); // setSector redraws the cards, chart, company list, and URL. setSector(activeSector); } function syncSectorControlStyle() { const classic = false; document.querySelector('#classic-sector-controls').hidden = !classic; document.querySelector('#sector-combobox').hidden = classic; } // Narrowest a card gets before the carousel drops to fewer cards (3 → 2 → 1). // The grid view's CSS uses the same 280px. const CARD_MIN_WIDTH = 256; function sizeCarousel() { const carousel = document.querySelector('#company-cards'); const width = carousel.clientWidth; // As many cards as fit at their minimum width, up to three. const gap = 16; const columns = Math.max( 1, Math.min(3, Math.floor((width + gap) / (CARD_MIN_WIDTH + gap))), ); const cardWidth = columns === 1 ? Math.min(320, width - 32) : (width - (columns - 1) * gap) / columns; carousel.style.setProperty('--carousel-card-width', &#96;${cardWidth}px&#96;); carousel.classList.toggle('is-centered', carouselCentered); // Selected cards center where possible; the list edges stay flush with the viewport. const edgeSpace = Math.max(0, width - cardWidth); carousel.style.setProperty('--carousel-start-space', '0px'); carousel.style.setProperty( '--carousel-end-space', &#96;${carouselCentered ? 0 : edgeSpace}px&#96;, ); } function setSector(name) { const selectedCompany = scene?.units.find( unit =&gt; unit.id === selectedId, ); if ( selectedCompany &amp;&amp; name !== 'All' &amp;&amp; selectedCompany.category !== name ) { selectedId = null; companyQuery = ''; searchInput.value = ''; closeCompanyOptions(); syncUrl(); } activeSector = name; carouselNavigated = false; carouselCentered = Boolean(selectedId); previewId = null; const carousel = document.querySelector('#company-cards'); document.querySelector('#sector-all').setAttribute('aria-pressed', String(name === 'All')); document.querySelector('#sector-select').value = name; syncComboboxValue( document.querySelector('#sector-combobox'), name === 'All' ? '' : name, ); carousel.setAttribute( 'aria-label', &#96;${name === 'All' ? 'Companies in all sectors' : &#96;Companies in ${name}&#96;}${activeStatus === 'All' ? '' : &#96;, ${activeStatus.toLowerCase()}&#96;}&#96;, ); filterCompanyCards(); sizeCarousel(); carousel.scrollTo({ left: 0, behavior: 'instant' }); if (selectedId) alignCard( visibleCards().find( card =&gt; card.dataset.companyId === selectedId, ), false, ); updateCarouselControls(); scene?.applySelection(); refreshCompanyOptions(); syncUrl(); } function alignCard(card, animate = true) { if (!card &#124;&#124; expandedDirectory) return; carouselNavigated = true; sizeCarousel(); const carousel = document.querySelector('#company-cards'); const box = card.getBoundingClientRect(); const frame = carousel.getBoundingClientRect(); carousel.scrollTo({ left: carousel.scrollLeft + box.left - frame.left - (carouselCentered ? (carousel.clientWidth - box.width) / 2 : 0), behavior: animate &amp;&amp; !reducedMotion() ? 'smooth' : 'instant', }); updateCarouselControls(); } function matchVisibleCardHeights(cards) { document.querySelectorAll('#company-cards.company-card').forEach(card =&gt; { card.style.removeProperty('min-height'); }); const carousel = document.querySelector('#company-cards'); carousel.style.removeProperty('height'); for (const part of ['.company-card-header', '.detail-facts']) document.querySelectorAll(&#96;#company-cards ${part}&#96;).forEach(node =&gt; node.style.removeProperty('min-height')); if (expandedDirectory &#124;&#124; !cards.length) return; // Names and fact grids take the tallest in view, so summaries and // Show more line up across the row even when an agency name wraps. for (const part of ['.company-card-header', '.detail-facts']) { const nodes = cards.map(card =&gt; card.querySelector(part)); const tallest = Math.ceil( Math.max(...nodes.map(node =&gt; node.getBoundingClientRect().height), ), ); nodes.forEach(node =&gt; { node.style.minHeight = &#96;${tallest}px&#96;; }); } // An opened card grows on its own; the rest match each other. const closed = cards.filter( card =&gt; !card.classList.contains('is-open'), ); const tallest = list =&gt; Math.ceil( Math.max( 0,...list.map(card =&gt; card.getBoundingClientRect().height), ), ); const matched = tallest(closed); closed.forEach(card =&gt; { card.style.minHeight = &#96;${matched}px&#96;; }); const height = Math.max(matched, tallest(cards)); // Size the row to the cards in view, not the tallest card off-screen. const style = getComputedStyle(carousel); const padding = style.boxSizing === 'border-box' ? parseFloat(style.paddingTop) + parseFloat(style.paddingBottom) : 0; carousel.style.height = &#96;${height + padding}px&#96;; } function updateCarouselControls({ updateRange = true } = {}) { const carousel = document.querySelector('#company-cards'); const cards = visibleCards(); const frame = carousel.getBoundingClientRect(); const shown = cards.map((card, index) =&gt; ({ box: card.getBoundingClientRect(), index, })).filter( ({ box }) =&gt; box.right &gt; frame.left + 2 &amp;&amp; box.left &lt; frame.right - 2, ); // Count only cards that are mostly in view, so a peeking card isn't "shown". const counted = shown.filter( ({ box }) =&gt; Math.min(box.right, frame.right) - Math.max(box.left, frame.left) &gt;= box.width * 0.6, ); const range = counted.length ? counted : shown; matchVisibleCardHeights(shown.map(({ index }) =&gt; cards[index])); if (updateRange) document.querySelector('#carousel-position').textContent = expandedDirectory ? &#96;${matchedCount} ${matchedCount === 1 ? 'company' : 'companies'}&#96; : range.length &gt; 1 ? &#96;${range[0].index + 1}–${range.at(-1).index + 1} of ${cards.length}&#96; : range.length ? &#96;${range[0].index + 1} of ${cards.length}&#96; : &#96;0 of ${cards.length}&#96;; for (const [id, unavailable] of [['carousel-previous', carousel.scrollLeft &lt; 2], [ 'carousel-next', carousel.scrollLeft &gt;= carousel.scrollWidth - carousel.clientWidth - 2, ], ]) { const button = document.getElementById(id); button.hidden = expandedDirectory &#124;&#124; cards.length &lt;= 1; button.disabled = unavailable; } } phoneLayout.addEventListener('change', () =&gt; filterCompanyCards({ resetScroll: false }), ); document.querySelector('#show-all-cards').addEventListener('click', () =&gt; { showAllCards = !showAllCards; filterCompanyCards({ resetScroll: false }); // After collapsing, keep the button in view instead of leaving the // reader far down the page. if (!showAllCards) document.querySelector('#show-all-cards').scrollIntoView({ block: 'nearest' }); }); function stepCarousel(direction) { const carousel = document.querySelector('#company-cards'); const frame = carousel.getBoundingClientRect(); const cards = visibleCards(); const current = cards.reduce( (best, card, index) =&gt; { const box = card.getBoundingClientRect(); const distance = Math.abs( box.left - frame.left - (carouselCentered ? (carousel.clientWidth - box.width) / 2 : 0), ); return distance &lt; best.distance ? { index, distance } : best; }, { index: 0, distance: Infinity }, ); alignCard( cards[ Math.max(0, Math.min(cards.length - 1, current.index + direction)) ], ); } document.querySelector('#carousel-previous').addEventListener('click', () =&gt; stepCarousel(-1)); document.querySelector('#carousel-next').addEventListener('click', () =&gt; stepCarousel(1)); document.querySelector('#company-cards').addEventListener('keydown', event =&gt; { if ( expandedDirectory &#124;&#124; event.target !== event.currentTarget &#124;&#124; !['ArrowLeft', 'ArrowRight'].includes(event.key) ) return; event.preventDefault(); stepCarousel(event.key === 'ArrowRight' ? 1 : -1); }); function toggleCompany(id) { if (selectedId === id) clearSelection(); else selectCompany(id, true); } function selectCompany(id, reveal = false, syncInput = true) { selectedId = id; carouselCentered = true; previewId = null; if (syncInput) { const selectedCompany = scene?.units.find(unit =&gt; unit.id === id); if (selectedCompany) { searchInput.value = selectedCompany.company; companyQuery = selectedCompany.company; closeCompanyOptions(); } } const unit = scene?.units.find(unit =&gt; unit.id === id); if (unit) { // Picking a company outside the status filter lifts the filter, so its // card stays in view. if (!matchesStatus(unit)) { activeStatus = 'All'; syncComboboxValue(document.querySelector('#status-combobox'), ''); refreshCompanyOptions(); } if (!matchesAgency(unit)) { activeAgency = 'All'; syncComboboxValue(document.querySelector('#agency-combobox'), ''); refreshCompanyOptions(); } if (activeSector !== 'All' &amp;&amp; activeSector !== unit.category) setSector(unit.category); // Keep the current position so nearby selections only travel the distance between cards. filterCompanyCards({ resetScroll: false }); alignCard( visibleCards().find(card =&gt; card.dataset.companyId === id), ); } scene?.applySelection(); if (reveal) scene?.reveal(id); syncUrl(); } // Keep ?sector= and ?company= in the address bar so a copied URL opens on the same view. // replaceState swaps the URL in place, so Back still leaves the page in one step. // Other parameters (e.g. ?view=dashboard, Drupal's own) are left untouched. function syncUrl() { if (!urlReady) return; const url = new URL(window.location.href); if (activeSector !== 'All') url.searchParams.set('sector', slug(activeSector)); else url.searchParams.delete('sector'); if (activeStatus !== 'All') url.searchParams.set('status', slug(activeStatus)); else url.searchParams.delete('status'); if (activeAgency !== 'All') url.searchParams.set('agency', slug(agencyLabel(activeAgency))); else url.searchParams.delete('agency'); if (selectedId) url.searchParams.set('company', selectedId); else url.searchParams.delete('company'); if (url.href !== window.location.href) history.replaceState(history.state, '', url); } function clearSelection() { const carousel = document.querySelector('#company-cards'); const scrollLeft = carousel.scrollLeft; selectedId = previewId = null; companyQuery = ''; searchInput.value = ''; filterCompanyCards({ resetScroll: false }); closeCompanyOptions(); scene?.applySelection(); syncUrl(); // Preserve the centered view and snap geometry when clearing selection. carousel.scrollTo({ left: scrollLeft, behavior: 'instant' }); updateCarouselControls(); } // Basecoat owns option filtering, popup visibility, and keyboard navigation. // Guard its change event when chart/card interactions update the control. let syncingComboboxes = false; function syncComboboxValue(combo, value) { if (!combo?.setValue) return; syncingComboboxes = true; try { combo.setValue(value); } finally { syncingComboboxes = false; } syncComboboxSwatch(combo); } // Show the committed sector's color; typing a new search hides it. // The company field skips it: its highlighted card already shows the color. function syncComboboxSwatch(combo) { if (combo.id !== 'sector-combobox') return; const sector = activeSector !== 'All' &amp;&amp; activeSector; const color = categories.find( category =&gt; category.name === sector, )?.color; combo.classList.toggle('has-swatch', Boolean(color)); if (color) combo.style.setProperty('--swatch-color', color); } document.addEventListener('input', event =&gt; { const combo = event.target.closest?.('.rfe-controls.combobox'); if (combo &amp;&amp; event.target.matches('[role=combobox]')) combo.classList.remove('has-swatch'); }); function closeCompanyOptions() { const combo = document.querySelector('#company-combobox'); const selected = scene?.units.find(unit =&gt; unit.id === selectedId); syncComboboxValue( combo, selected ? { value: selected.id, label: selected.company } : '', ); combo.close?.(); } function refreshCompanyOptions() { if (!scene) return; const combo = document.querySelector('#company-combobox'); const list = document.querySelector('#search-matches'); const choices = scene.units.filter( unit =&gt; (activeSector === 'All' &#124;&#124; unit.category === activeSector) &amp;&amp; matchesStatus(unit) &amp;&amp; matchesAgency(unit), ).sort((a, b) =&gt; a.company.localeCompare(b.company, 'en', { sensitivity: 'base' }), ); list.replaceChildren(...choices.map(unit =&gt; { const option = document.createElement('div'); option.setAttribute('role', 'option'); option.dataset.value = unit.id; option.dataset.label = unit.company; option.textContent = unit.company; return option; }), ); combo.refresh?.(); const selected = scene.units.find(unit =&gt; unit.id === selectedId); syncComboboxValue( combo, selected ? { value: selected.id, label: selected.company } : '', ); } function connectComboboxFocus(combo, committedValue) { // A dismissed search restores the committed filter. Tab also closes the popup. combo.addEventListener('focusout', () =&gt; { requestAnimationFrame(() =&gt; { if (combo.contains(document.activeElement)) return; combo.close(); syncComboboxValue(combo, committedValue()); }); }); combo.addEventListener( 'keydown', event =&gt; { if ( event.key === 'Escape' &amp;&amp; combo.querySelector('[role=combobox]').getAttribute('aria-expanded') === 'true' ) { // Dismiss the search first; a second Escape can clear chart selection. event.preventDefault(); event.stopPropagation(); combo.close(true); syncComboboxValue(combo, committedValue()); } }, true, ); } let chartOpenedAtEnd = false; // Leave 2px for embedded-page rounding; preserve scrolling below 659px. function render() { const width = Math.max(CHART.minWidth, chart.clientWidth - 2); lastChartWidth = width; // Stay pinned to the end unless the reader has scrolled back in time. const pinnedToEnd = !chartOpenedAtEnd &#124;&#124; chart.scrollLeft &gt;= chart.scrollWidth - chart.clientWidth - 4; buildChart(width); buildCompanyCards(); scene.applySelection(); // On narrow screens, open on the running total; readers scroll back in time. if (pinnedToEnd &amp;&amp; chart.scrollWidth &gt; chart.clientWidth) { chart.scrollLeft = chart.scrollWidth; chartOpenedAtEnd = true; } const legend = document.querySelector('#legend'); legend.replaceChildren(); for (const { name, color } of categories) { const key = document.createElement('span'); key.className = 'key'; const swatch = document.createElement('i'); swatch.style.background = color; swatch.setAttribute('aria-hidden', 'true'); key.append(swatch, document.createTextNode(name)); legend.append(key); } updateScrollCue(); placeIntro(); } // Shown only when the chart overflows. It fades out for good as soon as the // reader swipes, since the earlier months then slide in underneath it. // The left-edge fade stays until they reach the first month. const scrollCue = document.querySelector('#scroll-cue'); let cueDismissed = false; function updateScrollCue() { const earlier = chart.scrollLeft &gt; 4; if ( chartOpenedAtEnd &amp;&amp; chart.scrollLeft &lt; chart.scrollWidth - chart.clientWidth - 4 ) cueDismissed = true; scrollCue.hidden = chart.scrollWidth &lt;= chart.clientWidth; scrollCue.classList.toggle('is-done', !earlier &#124;&#124; cueDismissed); chart.classList.toggle('has-earlier', earlier); } chart.addEventListener('scroll', updateScrollCue, { passive: true }); // A tap or click steps back most of a screen. scrollCue.addEventListener('click', () =&gt; chart.scrollBy({ left: -chart.clientWidth * 0.8, behavior: reducedMotion() ? 'instant' : 'smooth', }), ); window.basecoat.initAll(); const companyCombo = document.querySelector('#company-combobox'); companyCombo.addEventListener('change', event =&gt; { if ( event.target !== companyCombo &#124;&#124; syncingComboboxes &#124;&#124; !event.detail ) return; if (event.detail.value) selectCompany(event.detail.value, true); else clearSelection(); }); connectComboboxFocus(companyCombo, () =&gt; { const unit = scene?.units.find(unit =&gt; unit.id === selectedId); return unit ? { value: unit.id, label: unit.company } : ''; }); // One Escape action clears pinned selections, hover previews, and company searches. document.addEventListener('keydown', event =&gt; { if ( event.key !== 'Escape' &#124;&#124; event.defaultPrevented &#124;&#124; event.isComposing ) return; const results = document.querySelector('#search-results'); if ( !selectedId &amp;&amp; !previewId &amp;&amp; !companyQuery &amp;&amp; results.getAttribute('aria-hidden') === 'true' ) return; event.preventDefault(); const restoreSearchFocus = document.activeElement?.closest( '#search-results, #detail-content', ); clearSelection(); if (restoreSearchFocus) searchInput.focus({ preventScroll: true }); companyCombo.close(); document.querySelector('#selection-announcement').textContent = 'Selection cleared.'; }); new ResizeObserver(() =&gt; { if (!data) return; const width = Math.max(CHART.minWidth, chart.clientWidth - 2); if (width !== lastChartWidth) render(); else { scene.setMode(mode, false); placeIntro(); } }).observe(chart); // Published Google Sheet (companies tab). The embedded copy below is the fallback. const SHEET_CSV_URL = 'https://docs.google.com/spreadsheets/d/e/2PACX-1vQasIorR3ELmmDuONGc45OTTSdAS8NRVOdl0U4y4VJdZxVjr-uW_K7ac0X_L99tXeG3elpn_ZCMuf8N/pub?gid=570003253&amp;single=true&amp;output=csv'; const embeddedCsv = 'company,sector,agency,announced,summary,source,status\nU.S. Steel/Nippon Steel,Steel,Committee on Foreign Investment,2025-06-01,"One non-economic Class G preferred share (the “golden share”), issued under the CFIUS National Security Agreement. It gives the U.S. government the right to appoint a director and veto specified decisions on capital investment, headquarters, production, plant closures or idling, overseas jobs, and certain acquisitions, trade, labor, and sourcing matters.",https://www.nipponsteel.com/common/secure/en/news/20250618_100.pdf,Closed\nMP Materials,Minerals,Department of Defense,2025-07-01,"$400 million DoD preferred-stock purchase, with a 7% payment-in-kind dividend and initial conversion into 13.3 million common shares at $30.03, plus a 10-year warrant for 11.2 million shares at $30.03. Separate $150 million OSC direct loan. The package includes a 10-year $110/kg NdPr price floor and a 10-year magnet offtake commitment with a $140 million annual EBITDA floor. Preferred shares remained outstanding and the warrant unexercised June 30, 2026.",https://investors.mpmaterials.com/investor-news/news-details/2025/MP-Materials-Announces-Transformational-Public-Private-Partnership-with-the-Department-of-Defense-to-Accelerate-U-S--Rare-Earth-Magnet-Independence/default.aspx,Closed\nIntel,Semiconductors,Department of Commerce,2025-08-01,"$8.9 billion in Commerce funding ($5.7 billion CHIPS incentives plus up to $3.2 billion Secure Enclave) for up to 433.3 million common shares; Intel reported 8.4% ownership in March 2026 assuming full escrow release, versus 9.9% initially announced. The transaction closed Aug. 27, 2025; 143 million shares remained escrowed June 27, 2026, pending Secure Enclave payments. Passive holding with no board seat. A five-year $20 warrant covers up to 240.5 million shares if Intel’s foundry ownership falls below 51%; Intel may settle it in net cash or shares.",https://www.intc.com/news-events/press-releases/detail/1748/intel-and-trump-administration-reach-historic-agreement-to,Closed\nTrilogy Metals,Minerals,Department of Defense,2025-10-01,"$35.6 million DoD IBAS investment, split equally between new Trilogy shares and shares purchased from South32, closed September 11, 2026. DoD holds approximately 9.1% of outstanding common shares and has 10-year rights to acquire another 12.3 million shares at $0.01 each, split between company warrants and South32 options and subject to specified conditions. DoD can nominate an independent director and appoint a board observer.",https://www.sec.gov/Archives/edgar/data/1543418/000127956925001094/ex991.pdf,Closed\nLithium Americas,Minerals,Department of Energy,2025-10-01,"$184 million DOE ATVM debt-service deferral under the $2.23 billion Thacker Pass loan. DOE received 10-year warrants on January 30, 2026 for 18.3 million company shares at $0.01 per share and a nonvoting Thacker Pass JV interest at $0.0001 per unit, each initially representing 5%. Both warrants remained unexercised as of August 12, 2026; cumulative loan advances reached $1.2 billion by June 30.",https://www.sec.gov/Archives/edgar/data/1966983/000119312525233937/d10878d8k.htm,Closed\nWestinghouse,Energy (nuclear),Department of Commerce,2025-10-01,"Conditional Commerce participation right to 20% of distributions above $17.5 billion, vesting only after binding commitments for at least $80 billion in new U.S. reactor construction before January 2029. The arrangement provides for an IPO-linked, five-year warrant based on 20% of equity value above $17.5 billion; the government can require an IPO only if the interest has vested and the IPO valuation is at least $30 billion.",https://www.sec.gov/Archives/edgar/data/1009001/000119312525260490/d23402dex992.htm,Announced\nEos Energy Enterprises,Energy (storage),Department of Energy,2025-11-01,"Eos sought to raise money from private investors in November 2025. Its existing agreement for a $303.5 million DOE-guaranteed loan, finalized in November 2024, required DOE’s approval for the financing. DOE conditioned approval on Eos granting warrants for 570,000 common shares at $0.01 per share, issued November 21, 2025. The private financing raised $600 million in convertible notes and $458.2 million in common stock. Unexercised warrants automatically exercise on November 21, 2030, or earlier if the preceding 15-trading-day volume-weighted average share price exceeds $30 at semiannual checkpoints beginning one year after issuance. Eos may settle in cash instead of shares; voting rights arise only upon receipt of shares. ",,Closed\nVulcan Elements,Minerals,"Department of Commerce, Department of Defense",2025-11-01,"$50 million finalized Commerce CHIPS award, with Commerce receiving equity under undisclosed ownership and share terms. A separate $620 million OSC conditional loan commitment includes warrants for DoD; warrant coverage and exercise terms remain undisclosed, and a loan closing has not been publicly confirmed.",https://www.war.gov/News/Releases/Release/Article/4339788/office-of-strategic-capital-agrees-to-joint-700m-conditional-loan-commitment-wi/,Partially closed\nReElement Technologies,Minerals,Department of Defense,2025-11-01,"The original conditional $80 million OSC loan with warrants was withdrawn. DoD subsequently announced a $25 million EDU investment through the Industrial Base Fund for the Marion, Indiana, refinery. The replacement investment’s form and any federal ownership or warrant rights remain undisclosed; a completed federal equity acquisition has not been publicly verified.",https://www.sec.gov/Archives/edgar/data/1590715/000165495425012526/arec_ex991.htm,Announced\n"Korea Zinc/Crucible Metals, LLC",Minerals,"Department of Commerce, Department of Defense",2025-12-01,"$210 million Commerce CHIPS award to Crucible Metals. Commerce holds 23.3% and DoD 16.7% of voting interests in Crucible JV, which acquired 10.59% of Korea Zinc. DoD exercises the combined 40% JV voting rights and supplied $150 million in equity plus a $1.25 billion OSC loan. Separately, DoD committed up to $2.4 billion in project loans to Crucible Metals with proposed warrants. The credit agreement remained unsigned as of June 30, 2026, according to financial statements repeated in an October 12 filing.",https://finance.yahoo.com/news/factbox-trump-administration-pivots-buying-132228071.html,Partially closed\nxLight,Semiconductors,Department of Commerce,2025-12-01,"$150 million in Commerce CHIPS R&amp;D funding for a free-electron laser prototype in Albany, New York, under a final award signed June 2, 2026. The original LOI specified $150 million of equity for Commerce; the ownership percentage, share class and governance terms of the finalized arrangement remain undisclosed.",https://www.nist.gov/news-events/news/2025/12/department-commerce-and-nist-announce-chips-research-and-development-letter,Closed\nL3Harris/Missile Solutions,Rocket motors,Department of Defense,2026-01-01,"$1 billion DoD IBAS investment, closed in April 2026, in Missile Solutions through redeemable convertible preferred stock accruing 7% annual dividends. Preferred stock converts into Missile Solutions common equity at a 20% discount to a qualifying IPO price; L3Harris estimates roughly 10% ownership, depending on valuation. DoD also received warrants for 3% of fully diluted Missile Solutions common stock, subject to contractual exercise conditions.",https://www.war.gov/News/Releases/Release/Article/4376463/department-of-war-announces-1-billion-direct-to-supplier-investment-to-secure-t/,Closed\n"Atlantic Alumina Company, LLC",Minerals,Department of Defense,2026-01-01,"$400 million in DoD IBAS preferred equity, including a $100 million follow-on announced August 28, 2026, through Class A Preferred Units. The company reports $800 million in combined federal and private funding commitments. The federal ownership percentage and detailed governance terms remain undisclosed.",https://www.reuters.com/business/energy/usa-rare-earth-shares-surge-report-trump-administration-investment-2026-01-26/,Closed\nUSA Rare Earth,Minerals,Department of Commerce,2026-01-01,"Up to $277 million in Commerce CHIPS direct funding and $1.3 billion in federally guaranteed borrowing, tied to milestones. Commerce received 16.1 million common shares and a 10-year warrant for 17.6 million shares at $17.17 on June 3, 2026; the warrant becomes exercisable after one year. Commerce retains the securities regardless of funding disbursements or clawbacks. Separately, a prospectus filed September 17 reports DFC holding 15.2 million USAR common shares following the Serra Verde acquisition.",https://www.usare.com/article?i=161514,Closed\nGlobalFoundries,Quantum computing,Department of Commerce,2026-05-01,"Commerce finalized a $375 million CHIPS R&amp;D quantum award in September 2026, acquiring 9.9 million shares at $37.85 (about 1.8% of shares outstanding September 11), with limited voting rights. A separate $300 million photonics award and approximately 1% stake remain announced. On October 14, Commerce announced an additional approximately 4% stake alongside reallocation of $955 million in existing CHIPS funding and Japan-funded factories. The CEO said the new agreement would bring total U.S. ownership to about 7%; closing of the additional stake has not been disclosed.",https://www.nist.gov/news-events/news/2026/05/department-commerce-announces-letters-intent-9-companies-2-billion,Partially closed\nIBM/Anderon,Quantum computing,Department of Commerce,2026-05-01,"Final Commerce CHIPS R&amp;D award of up to $1 billion to Anderon, IBM’s quantum-foundry subsidiary, signed in September 2026. IBM is contributing $1 billion plus assets and intellectual property. The final public announcements do not disclose federal ownership terms or confirm equity issuance.",https://www.nist.gov/news-events/news/2026/05/department-commerce-announces-letters-intent-9-companies-2-billion,Announced\nAtom Computing,Quantum computing,Department of Commerce,2026-05-01,"Proposed $100 million in Commerce CHIPS R&amp;D funding under a May 2026 letter of intent, in exchange for a minority, non-controlling equity stake. Final equity percentage and share terms remain undisclosed.",https://www.nist.gov/news-events/news/2026/05/department-commerce-announces-letters-intent-9-companies-2-billion,Announced\nDiraq,Quantum computing,Department of Commerce,2026-05-01,"Proposed up to $38 million in Commerce CHIPS R&amp;D funding under a May 2026 letter of intent, in exchange for a minority, non-controlling equity stake. Final ownership percentage and share terms remain undisclosed.",https://www.nist.gov/news-events/news/2026/05/department-commerce-announces-letters-intent-9-companies-2-billion,Announced\nD-Wave,Quantum computing,Department of Commerce,2026-05-01,"Final Commerce CHIPS R&amp;D award of up to $100 million over up to five years; 7,095,721 common shares issued to Commerce on September 8, 2026, at $14.093 per share. Approximately $53.6 million is available initially, with the balance tied to milestones. Voting rights are restricted, with exceptions including mergers and changes affecting share rights.",https://www.nist.gov/news-events/news/2026/05/department-commerce-announces-letters-intent-9-companies-2-billion,Closed\nInfleqtion,Quantum computing,Department of Commerce,2026-05-01,"Proposed up to $100 million in Commerce CHIPS R&amp;D funding, with common stock issued at final award at a 15% discount to the lower of the LOI-date or definitive-agreement closing price. Funding would be partly upfront and partly milestone-based. Definitive agreements and government approvals remain unconfirmed.",https://www.nist.gov/news-events/news/2026/05/department-commerce-announces-letters-intent-9-companies-2-billion,Announced\nPsiQuantum,Quantum computing,Department of Commerce,2026-05-01,"Final Commerce CHIPS R&amp;D award of up to $100 million, signed in September 2026. Commerce’s original announcement called for a minority, non-controlling equity stake. The final award announcements do not disclose the ownership percentage, share terms, or whether the equity has been issued.",https://www.nist.gov/news-events/news/2026/05/department-commerce-announces-letters-intent-9-companies-2-billion,Closed\nQuantinuum,Quantum computing,Department of Commerce,2026-05-01,"Final Commerce CHIPS R&amp;D award of up to $100 million over up to five years; Commerce received 2,369,528 Class A common shares on September 8, 2026. The award makes $56 million available initially and $44 million after milestones. Commerce generally cannot vote the shares, except on specified changes to the share class.",https://www.nist.gov/news-events/news/2026/05/department-commerce-announces-letters-intent-9-companies-2-billion,Closed\nRigetti,Quantum computing,Department of Commerce,2026-05-01,"Final Commerce CHIPS R&amp;D award of up to $100 million over up to five years, with 7,739,938 common shares at an implied $12.92 per share under a September 8, 2026 securities agreement. Funding comprises $43.9 million initially and $56.1 million tied to milestones. Voting rights are restricted, with exceptions including mergers and changes affecting share rights.",https://www.nist.gov/news-events/news/2026/05/department-commerce-announces-letters-intent-9-companies-2-billion,Closed\nSandboxAQ,Semiconductors,Department of Commerce,2026-06-01,"$500 million Commerce CHIPS R&amp;D award under a definitive agreement signed June 17, 2026, in exchange for a minority, non-controlling, non-voting equity stake, with no board seat. Commerce also receives royalties if award-funded materials are successfully developed and licensed for commercial production. The ownership percentage and royalty rates are undisclosed.",https://www.nist.gov/news-events/news/2026/06/department-commerce-announces-definitive-agreement-sandboxaq-500-million,Closed\nI-Pulse,Semiconductors,Department of Commerce,2026-06-01,"$250 million Commerce CHIPS R&amp;D award under a definitive agreement signed June 25, 2026. Commerce received a minority, non-controlling equity stake in I-Pulse. The federal ownership percentage, share class, valuation, and any additional governance rights have not been publicly disclosed.",https://www.nist.gov/news-events/news/2026/06/department-commerce-announces-definitive-agreement-i-pulse-250-million,Closed\nKepler Computing,Semiconductors,Department of Commerce,2026-07-01,"Proposed up to $245 million in CHIPS R&amp;D funding under a July 2026 letter of intent, with Commerce receiving a minority, non-controlling equity stake. Ownership percentage and detailed share terms undisclosed.",https://www.nist.gov/news-events/news/2026/07/department-commerce-announces-letters-intent-7-companies-874-million,Announced\nMultibeam Corporation,Semiconductors,Department of Commerce,2026-07-01,"Proposed up to $140 million in CHIPS R&amp;D funding under a July 2026 letter of intent, with Commerce receiving a minority, non-controlling equity stake. Ownership percentage and detailed share terms undisclosed.",https://www.nist.gov/news-events/news/2026/07/department-commerce-announces-letters-intent-7-companies-874-million,Announced\nExtropic,Semiconductors,Department of Commerce,2026-07-01,"Proposed up to $75 million in CHIPS R&amp;D funding under a July 2026 letter of intent, with Commerce receiving a minority, non-controlling equity stake. Ownership percentage and detailed share terms undisclosed.",https://www.nist.gov/news-events/news/2026/07/department-commerce-announces-letters-intent-7-companies-874-million,Announced\nThintronics,Semiconductors,Department of Commerce,2026-07-01,"Proposed up to $50 million in CHIPS R&amp;D funding under a July 2026 letter of intent, with Commerce receiving a minority, non-controlling equity stake. Ownership percentage and detailed share terms undisclosed.",https://www.nist.gov/news-events/news/2026/07/department-commerce-announces-letters-intent-7-companies-874-million,Announced\nOBSIDIA Semiconductors,Semiconductors,Department of Commerce,2026-07-01,"Proposed up to $34 million in CHIPS R&amp;D funding under a July 2026 letter of intent, with Commerce receiving a minority, non-controlling equity stake. Ownership percentage and detailed share terms undisclosed.",https://www.nist.gov/news-events/news/2026/07/department-commerce-announces-letters-intent-7-companies-874-million,Announced\nAeluma,Semiconductors,Department of Commerce,2026-07-01,"Proposed up to $30 million in CHIPS R&amp;D funding, with Commerce receiving a minority, non-controlling equity stake valued at the final award amount. Funding would be partly upfront and partly milestone-based. Final award documents remain under negotiation; ownership percentage and share price undisclosed.",https://www.nist.gov/news-events/news/2026/07/department-commerce-announces-letters-intent-7-companies-874-million,Announced\n"Strategic Bauxite USA, LLC",Minerals,Department of Defense,2026-08-01,$85.5 million DoD IBAS equity investment agreement alongside $64.5 million in private co-investment to acquire and expand First Bauxite’s Guyana operations and develop downstream processing. The federal ownership percentage and share terms remain undisclosed; a separate closing of the federal investment has not been publicly confirmed.,https://www.war.gov/News/Releases/Release/Article/4566604/department-of-war-announces-an-855-million-agreement-with-strategic-bauxite-usa/,Announced\nNorth American Blue Energy Partners (NABEP),Energy (oil),"Department of Defense, Department of State",2026-09-01,"Announced DoD/OSC rights to a 35% stake in NABEP’s parent, reportedly through penny warrants with anti-dilution protection and dividend rights before exercise. The arrangement includes U.S. approval of all board appointments and State Department rights to buy 20% of production at cost, after PDVSA receives its share, with first refusal on the remainder at market prices. Final closing has not been publicly confirmed.",https://www.whitehouse.gov/fact-sheets/2026/08/fact-sheet-president-donald-j-trump-announces-historic-oil-agreement-to-secure-american-energy-dominance-and-drive-venezuelas-economic-recovery/,Announced\nElmet Group,Minerals,Department of Defense,2026-09-01,"$450 million DoD IBAS commitment for redeemable preferred stock with a 5.5% payment-in-kind dividend: $200 million closed September 14, 2026; up to $250 million remains in conditional tranches. DoD received 10-year warrants for up to 19.9% of post-deal common stock, exercisable from September 2027 (75% at $0.001 per share; 25% at $15.92), plus 19.9% initial voting rights, one director and a board observer. A separate DLA contract includes a $150 million purchase commitment within a $2 billion ceiling.",https://www.war.gov/News/Releases/Release/Article/4599882/department-of-war-announces-a-450-million-investment-in-the-elmet-group-to-secu/,Closed\nHertha Metals,Minerals,Department of Defense,2026-09-01,"$65 million DoD equity investment through IBAS, in partnership with EDU, included in a closed $133.65 million Series A financing round announced September 29, 2026. The federal ownership percentage, share class and other share terms remain undisclosed.",https://www.businesswire.com/news/home/20260929199684/en/Hertha-Metals-Secures-%24133.65M-Series-A-to-Produce-High-Purity-Iron-for-Domestic-Rare-Earth-Magnets,Closed\nWolfspeed,Semiconductors,Department of Defense,2026-10-07,"Conditional DoD OSC commitment for a 30-year loan of up to $1.5 billion, with 10-year warrants for up to 7.5% of fully diluted equity, issued proportionally as loan tranches are funded at agreed volume-weighted-average exercise prices. The proposed $600 million initial tranche would refinance existing secured debt; up to $900 million would support subsequent project spending. Definitive agreements, approvals and financing conditions remain outstanding.",https://www.wolfspeed.com/company/news-events/news/wolfspeed-announces-conditional-30-year-loan-commitment-from-us-department-of-war-to-advance-domestic-wide-bandgap-supply-chain/,Announced\n'; async function fetchSheet() { try { const response = await fetch(SHEET_CSV_URL, { cache: 'no-cache' }); if (!response.ok) throw new Error(&#96;HTTP ${response.status}&#96;); const source = await response.text(); parse(source); return source; } catch (cause) { console.warn('Using embedded data; sheet fetch failed:', cause); return embeddedCsv; } } async function loadData() { try { csvSource = await fetchSheet(); data = parse(csvSource); // Key the square fills to the statuses this sheet actually uses. const statusKey = document.querySelector('#status-key'); const usedStatuses = STATUSES.filter(status =&gt; data.some(month =&gt; month.values.some(unit =&gt; unit.status === status.name), ), ); statusKey.hidden = !usedStatuses.length; // With a status key below, the lead-in drops its stand-in square, which // would otherwise read as a fourth kind of mark (or as Closed). // The non-breaking space keeps "position:" from wrapping alone. if (usedStatuses.length) document.querySelector('#chart-key').textContent = 'Each square is one company with a federal equity\u00a0position:'; statusKey.innerHTML = usedStatuses.map( status =&gt; &#96;${statusIcon(status.name)}${status.name}&#96;, ).join(''); render(); refreshCompanyOptions(); // Open a shared link. Unknown or renamed names are skipped, never an error, // and a company outside the shared sector is dropped so the sector holds. const params = new URLSearchParams(window.location.search); const sharedSector = sectorGroups.find( group =&gt; slug(group.name) === params.get('sector'), )?.name; const sharedStatus = STATUSES.find( status =&gt; slug(status.name) === params.get('status') &amp;&amp; scene.units.some(unit =&gt; unit.status === status.name), )?.name; const sharedAgency = [...new Set(scene.units.flatMap(unitAgencies)), ].find(name =&gt; slug(agencyLabel(name)) === params.get('agency')); const sharedCompany = scene.units.find( unit =&gt; unit.id === params.get('company') &amp;&amp; (!sharedSector &#124;&#124; unit.category === sharedSector) &amp;&amp; (!sharedStatus &#124;&#124; unit.status === sharedStatus) &amp;&amp; (!sharedAgency &#124;&#124; unitAgencies(unit).includes(sharedAgency)), ); urlReady = true; if (sharedStatus) activeStatus = sharedStatus; if (sharedStatus) syncComboboxValue( document.querySelector('#status-combobox'), sharedStatus, ); if (sharedAgency) { activeAgency = sharedAgency; syncComboboxValue( document.querySelector('#agency-combobox'), sharedAgency, ); } if (sharedSector &#124;&#124; sharedStatus &#124;&#124; sharedAgency) setSector(sharedSector &#124;&#124; activeSector); if (sharedCompany) selectCompany(sharedCompany.id, true); syncUrl(); } catch (cause) { chart.setAttribute('role', 'alert'); chart.removeAttribute('aria-label'); chart.textContent = &#96;Unable to load the chart: ${cause.message} Refresh the page to retry.&#96;; } finally { revealComponent(); } } // Downloads: a golden-ratio PNG of the full chart with title, source, and logo, and the raw CSV. // The PNG is drawn from an offscreen copy at a fixed size, so every reader // gets the same clean, unselected image whatever their screen. let csvSource = ''; const CATO_LOGO_SVG = &#96;&#96;; const EXPORT = { companyHighlight: COMPANY_EXPORT, // See COMPANY EXPORT SWITCH near the top. // Layout size in CSS pixels; scale sets the PNG's pixel density. // Golden ratio (1.618:1) at 2.16x gives 2160 x 1335, which social feeds show // uncropped and which drops into slides. For a square, try 800 x 800 at 2.7x, // though the months get crowded at that width. width: 1000, height: 618, scale: 2.16, padding: 24, // Larger type than the page, since the image is usually seen shrunk in a feed. chartTextSize: 15, titleSize: 26, cardNameSize: 22, // Company name on a card, clearly above its sector line. cardDetailSize: 12, // Sector, date and agency lines; matches the summary. cardSummarySize: 12, cardWidth: 430, // Company card text width; the chart's upper-left is empty here. textSize: 16, sourceSize: 14, logoHeight: 36, source: 'Source: cato.org/restoring-free-enterprise', }; const fileStamp = () =&gt; d3.timeFormat('%Y-%m-%d')(new Date()); function saveBlob(blob, filename) { const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = filename; document.body.append(link); link.click(); link.remove(); setTimeout(() =&gt; URL.revokeObjectURL(link.href), 1000); } const loadImage = src =&gt; new Promise((resolve, reject) =&gt; { const image = new Image(); image.onload = () =&gt; resolve(image); image.onerror = () =&gt; reject(new Error('Image failed to load.')); image.src = src; }); const toDataUrl = blob =&gt; new Promise(resolve =&gt; { const reader = new FileReader(); reader.onload = () =&gt; resolve(reader.result); reader.readAsDataURL(blob); }); // An SVG drawn as an image can't see the page's webfonts, so embed Inter's // Latin subset. If that fails, the chart text falls back to a system sans. let interFontCss; function embeddedInterCss() { interFontCss ??= (async () =&gt; { const css = await ( await fetch( 'https://fonts.googleapis.com/css2?family=Inter:wght@400..600&amp;display=swap', ) ).text(); const latin = css.split('/* latin */')[1] &#124;&#124; css; const url = latin.match(/url\((https:[^)]+\.woff2)\)/)[1]; const font = await toDataUrl(await (await fetch(url)).blob()); return &#96;@font-face{font-family:'Inter';font-weight:400 600;src:url(${font}) format('woff2');}&#96;; })().catch(error =&gt; { console.warn('Exporting without embedded Inter:', error); interFontCss = undefined; return ''; }); return interFontCss; } // The committed selection the export can highlight: a picked company, else a // chosen sector. Hover previews and half-typed searches don't count. function exportHighlight() { const unit = EXPORT.companyHighlight &amp;&amp; scene?.units.find(item =&gt; item.id === selectedId); if (unit) return { name: unit.company, line: &#96;Highlighted: ${unit.company} · ${unit.category} · ${d3.timeFormat('%B %Y')(d3.timeParse('%Y-%m')(unit.announced.slice(0, 7)))}&#96;, file: unit.id, unit, }; if ( activeSector === 'All' &amp;&amp; activeStatus === 'All' &amp;&amp; activeAgency === 'All' ) return null; const count = scene.units.filter( item =&gt; (activeSector === 'All' &#124;&#124; item.category === activeSector) &amp;&amp; matchesStatus(item) &amp;&amp; matchesAgency(item), ).length; const parts = agency =&gt; [ activeAgency !== 'All' &amp;&amp; agency, activeSector !== 'All' &amp;&amp; activeSector, activeStatus !== 'All' &amp;&amp; (activeSector === 'All' ? activeStatus : activeStatus.toLowerCase()), ].filter(Boolean).join(', '); const name = parts(activeAgency); return { name, line: &#96;Highlighted: ${name} (${count} ${count === 1 ? 'company' : 'companies'})&#96;, file: slug(parts(agencyLabel(activeAgency))), }; } async function chartImage(width, height, highlight) { const holder = document.createElement('div'); holder.id = 'chart-export'; holder.setAttribute('aria-hidden', 'true'); // Offscreen but laid out, so text can be measured for the label gutter. Object.assign(holder.style, { position: 'absolute', left: '-10000px', top: '0', visibility: 'hidden', }); for (const [name, value] of [ ['--type-note', &#96;${EXPORT.chartTextSize}px&#96;], ['--type-label', &#96;${EXPORT.chartTextSize}px&#96;], ]) holder.style.setProperty(name, value); document.querySelector('#fed-stakes').append(holder); let copy; let obstacles = []; // The offscreen chart reads the page's selection state; set it to what // this image should show, then put the reader's state back. const saved = { selectedId, previewId, companyQuery, activeSector, activeStatus, activeAgency, }; previewId = null; companyQuery = ''; if (!highlight &#124;&#124; highlight.unit) { activeSector = 'All'; activeStatus = 'All'; activeAgency = 'All'; } if (!highlight?.unit) selectedId = null; try { const live = buildChart(width, { height, target: holder }); // Even instant D3 transitions apply on the next frame; run them now. d3.timerFlush(); if (highlight?.unit) { // A company's image drops the monthly "+n" counts, which the // running totals already imply, and the on-chart name label: // the card names the company and its full-color square marks it. live.querySelectorAll('.addition-labels,.selection-labels').forEach(node =&gt; node.remove()); } copy = live.cloneNode(true); // Boxes of the marks and their labels, so a company card's text can // flow around the staircase instead of running into it. obstacles = [...live.querySelectorAll( 'rect.company-mark,.top-labels text,.addition-labels text,.final-label', ), ].map(node =&gt; node.getBBox()).filter(box =&gt; box.width &amp;&amp; box.height); // Classes carry the chart's type styles; copy the computed values inline. const liveText = live.querySelectorAll('text, tspan'); copy.querySelectorAll('text, tspan').forEach((node, index) =&gt; { const style = getComputedStyle(liveText[index]); for (const property of ['fill', 'font-size', 'font-weight']) node.style.setProperty( property, style.getPropertyValue(property), ); }); } finally { holder.remove(); ({ selectedId, previewId, companyQuery, activeSector, activeStatus, activeAgency, } = saved); } copy.querySelectorAll('.mark-focus-ring,.keyboard-hint').forEach(node =&gt; node.remove()); if (highlight?.unit) { // Outline the company's squares so the card and chart point at each other. copy.querySelectorAll( &#96;rect.company-mark[data-company-id="${CSS.escape(highlight.unit.id)}"]&#96;, ).forEach(node =&gt; { // Drawn inside the square, so the white gaps between squares // and the square's size stay as they are. const ring = node.cloneNode(false); const inset = 0.8; for (const [key, shift] of [ ['x', inset], ['y', inset], ['width', -2 * inset], ['height', -2 * inset], ]) ring.setAttribute(key, +node.getAttribute(key) + shift); ring.removeAttribute('data-company-id'); ring.setAttribute('fill', 'none'); ring.setAttribute('stroke', '#18181b'); ring.setAttribute('stroke-width', 1); // Drawn after the chart's layers, so the white separator lines // (a later layer for the month columns) can't cover its edges. // Neither mark group is transformed, so positions carry over. node.parentNode.parentNode.append(ring); }); // Muted bars stay pale, but the totals stay readable. copy.querySelectorAll( '.top-labels,.addition-labels,.final-label,.equals-label', ).forEach(node =&gt; node.setAttribute('opacity', 0.75)); // Faded sector names drop to regular weight, so the company's sector // is the only bold label in the Total column. copy.querySelectorAll('.total-sector-labels text').forEach(node =&gt; { if (node.getAttribute('fill') === SELECTION.mutedText) node.querySelectorAll('.sector-name').forEach(name =&gt; name.style.setProperty('font-weight', '400'), ); }); } // Sector counts don't align with other numbers, so tabular 1s only look loose. copy.querySelectorAll('.sector-count').forEach(node =&gt; node.style.setProperty( 'font-variant-numeric', 'proportional-nums', ), ); copy.setAttribute('xmlns', 'http://www.w3.org/2000/svg'); copy.setAttribute( 'style', "font-family:'Inter',system-ui,sans-serif;font-variant-numeric:tabular-nums", ); const style = document.createElementNS( 'http://www.w3.org/2000/svg', 'style', ); style.textContent = await embeddedInterCss(); copy.prepend(style); const markup = new XMLSerializer().serializeToString(copy); const image = await loadImage( &#96;data:image/svg+xml;charset=utf-8,${encodeURIComponent(markup)}&#96;, ); return { image, obstacles }; } // Wraps text to a width; returns the lines. function wrapLines(context, text, maxWidth) { const lines = []; let line = ''; for (const word of text.split(/\s+/)) { const next = line ? &#96;${line} ${word}&#96; : word; if (line &amp;&amp; context.measureText(next).width &gt; maxWidth) { lines.push(line); line = word; } else line = next; } if (line) lines.push(line); return lines; } // A single company's export adds its card below the title block, laid out // like the chart-side card design: the company name, its chart mark with sector and status, // labeled Date and Agency columns, then the full summary. // The summary is never cut, since the image travels without the page. function drawCompanyCard(context, unit, top, obstacles = []) { const { padding, cardWidth, cardNameSize, cardDetailSize, cardSummarySize, } = EXPORT; const textSize = cardDetailSize; const font = (weight, size) =&gt; &#96;${weight} ${size}px Inter, system-ui, sans-serif&#96;; const color = categories.find(c =&gt; c.name === unit.category).color; let y = top + textSize * 1.2; // Company name first; long names wrap. context.fillStyle = '#18181b'; context.font = font(600, cardNameSize); for (const line of wrapLines(context, unit.company, cardWidth)) { y += cardNameSize * 1.15; context.fillText(line, padding, y); } // Below it, the page card's line: the company's chart mark (sector // color, status fill), the sector, a pipe, then the status. y += textSize * 1.75; const mark = Math.round(textSize * 0.7); const markTop = y - mark; const fill = statusFill(unit.status); context.fillStyle = fill === 'solid' ? color : paleColor(color); context.fillRect(padding, markTop, mark, mark); if (fill === 'half') { context.fillStyle = color; context.beginPath(); context.moveTo(padding, markTop); context.lineTo(padding, markTop + mark); context.lineTo(padding + mark, markTop + mark); context.closePath(); context.fill(); } let x = padding + mark + textSize * 0.45; for (const [text, weight, ink] of [ [unit.category, 400, '#52525b'],...(unit.status ? [ [' &#124; ', 400, '#d4d4d8'], [unit.status, 400, '#52525b'], ] : []), ]) { context.font = font(weight, textSize); context.fillStyle = ink; context.fillText(text, x, y); x += context.measureText(text).width; } // Date and Agency columns: quiet labels over secondary-gray values, // so the details stay below the summary in weight. const announced = d3.timeFormat('%B %Y')( d3.timeParse('%Y-%m')(unit.announced.slice(0, 7)), ); const agencies = unitAgencies(unit); context.font = font(400, textSize); const agencyX = padding + Math.max( context.measureText('Date').width, context.measureText(announced).width, ) + textSize * 3; y += textSize * 2.2; context.fillStyle = '#71717a'; context.fillText('Date', padding, y); context.fillText('Agency', agencyX, y); y += textSize * 1.45; context.fillStyle = '#52525b'; context.fillText(announced, padding, y); // Agencies share a line when they fit; otherwise one per line, so a // name never breaks partway ("Department of / State"). const agencyWidth = cardWidth - (agencyX - padding); const joined = agencies.join(', '); (context.measureText(joined).width &lt;= agencyWidth ? [joined] : agencies.flatMap(agency =&gt; wrapLines(context, agency, agencyWidth), ) ).forEach((line, index) =&gt; { if (index) y += textSize * 1.45; context.fillText(line, agencyX, y); }); // The deal summary, in full, in the cards' dark gray. Each line's width // stops short of any chart mark or label at that height, so long // summaries wrap around the staircase. If lines would get too narrow, // the summary steps down in size until it fits. const summaryTop = y + cardSummarySize * 0.6; const gap = 12; const widthAt = (baseline, size) =&gt; obstacles.reduce((width, box) =&gt; { const overlaps = box.y &lt; baseline + size * 0.35 &amp;&amp; box.y + box.height &gt; baseline - size; return overlaps ? Math.min(width, box.x - padding - gap) : width; }, cardWidth); const flow = size =&gt; { context.font = font(400, size); const words = unit.summary.split(/\s+/); const lines = []; let baseline = summaryTop; let narrowest = cardWidth; while (words.length) { baseline += size * 1.5; const width = widthAt(baseline, size); narrowest = Math.min(narrowest, width); if (width &lt; size * 6) { // Nothing fits beside the chart here; the size is too large. if (baseline &gt; EXPORT.height) break; continue; } let line = words.shift(); while ( words.length &amp;&amp; context.measureText(&#96;${line} ${words[0]}&#96;).width &lt;= width ) line += &#96; ${words.shift()}&#96;; lines.push({ line, baseline }); } return { lines, narrowest, fits: !words.length }; }; let layout; for (let size = cardSummarySize; size &gt;= 10.5; size -= 0.5) { layout = flow(size); layout.size = size; if (layout.fits &amp;&amp; layout.narrowest &gt;= cardWidth * 0.6) break; } context.fillStyle = '#3f3f46'; context.font = font(400, layout.size); for (const { line, baseline } of layout.lines) context.fillText(line, padding, baseline); } function drawStatusSquare(context, fill, x, y, size, color) { context.fillStyle = fill === 'solid' ? color : paleColor(color); context.fillRect(x, y, size, size); if (fill !== 'half') return; context.fillStyle = color; context.beginPath(); context.moveTo(x, y); context.lineTo(x, y + size); context.lineTo(x + size, y + size); context.closePath(); context.fill(); } async function downloadPng(highlight = null) { await document.fonts.ready; const { width, height, padding, scale, logoHeight } = EXPORT; const footerHeight = logoHeight; const { titleSize, textSize } = EXPORT; const chartWidth = width - padding * 2; // The overview's title sits in the chart's empty upper-left corner, as on // the page. A company's image moves the title above the chart instead, so // the card gets the whole corner. const titleBand = highlight?.unit ? Math.round(titleSize * 1.6) : 0; const chartTop = padding + titleBand; const chartHeight = height - padding * 2 - footerHeight - titleBand; const { image: chartPicture, obstacles } = await chartImage( chartWidth, chartHeight, highlight, ); const canvas = document.createElement('canvas'); canvas.width = Math.round(width * scale); canvas.height = Math.round(height * scale); const context = canvas.getContext('2d'); context.imageSmoothingQuality = 'high'; context.scale(scale, scale); context.fillStyle = '#fff'; context.fillRect(0, 0, width, height); context.textBaseline = 'alphabetic'; const font = (weight, size) =&gt; &#96;${weight} ${size}px Inter, system-ui, sans-serif&#96;; context.drawImage( chartPicture, padding, chartTop, chartWidth, chartHeight, ); // Header: title, date range, and how to read the marks. A company's // image keeps only the title, with the card in the chart's corner. context.fillStyle = '#18181b'; const pageTitle = document.querySelector('#page-title').textContent.trim(); // The card names the company, so the title stays the chart's own. context.font = font(600, titleSize); context.fillText( pageTitle, padding - titleSize * 0.04, // optical pull-back for the large title padding + titleSize * 0.9, ); if (highlight?.unit) drawCompanyCard( context, highlight.unit, chartTop, // Chart coordinates, shifted to where the chart sits on the canvas. obstacles.map(box =&gt; ({ x: box.x + padding, y: box.y + chartTop, width: box.width, height: box.height, })), ); else { context.fillStyle = '#52525b'; context.font = font(400, textSize); const keyY = padding + titleSize * 0.9 + textSize * 1.9; const square = Math.round(textSize * 0.75); // Status key: the same solid, half, and pale squares as the chart. const usedStatuses = STATUSES.filter(status =&gt; scene.units.some(unit =&gt; unit.status === status.name), ); if (usedStatuses.length) context.fillText( 'Each square is one company with a federal equity position:', padding, keyY, ); else { context.fillText('Each', padding, keyY); const squareX = padding + context.measureText('Each').width + 7; context.fillRect(squareX, keyY - square + 1, square, square); context.fillText( 'is one company with a federal equity position', squareX + square + 7, keyY, ); } let nextY = keyY + textSize * 1.6; if (usedStatuses.length) { let keyX = padding; for (const status of usedStatuses) { drawStatusSquare( context, status.fill, keyX, nextY - square + 1, square, '#52525b', ); context.fillStyle = '#52525b'; context.fillText(status.name, keyX + square + 6, nextY); keyX += square + 6 + context.measureText(status.name).width + 16; } nextY += textSize * 1.6; } if (highlight) { context.fillStyle = '#18181b'; context.font = font(600, textSize); context.fillText(highlight.line, padding, nextY); } } // Footer: source on the left, logo on the right. const footerTop = height - padding - logoHeight; context.fillStyle = '#71717a'; context.font = font(400, EXPORT.sourceSize); context.fillText(EXPORT.source, padding, footerTop + logoHeight - 3); // Fill the logo's vector paths directly so it stays crisp at any scale. const logoScale = logoHeight / 278; context.save(); context.translate(width - padding - 551 * logoScale, footerTop); context.scale(logoScale, logoScale); context.fillStyle = '#3043B4'; for (const [, path] of CATO_LOGO_SVG.matchAll(/ d="([^"]+)"/g)) context.fill(new Path2D(path)); context.restore(); const blob = await new Promise(resolve =&gt; canvas.toBlob(resolve, 'image/png'), ); saveBlob( blob, &#96;government-equity-portfolio-${highlight ? &#96;${highlight.file}-&#96; : ''}${fileStamp()}.png&#96;, ); } function downloadCsv() { // The BOM lets Excel read dashes and accents as UTF-8. const text = &#96;﻿${csvSource.replace(/^﻿/, '')}&#96;; saveBlob( new Blob([text], { type: 'text/csv;charset=utf-8' }), &#96;government-equity-portfolio-${fileStamp()}.csv&#96;, ); } // Menu button pattern: arrows move between items; Escape and Tab close. const downloadToggle = document.querySelector('#download-toggle'); const downloadOptions = document.querySelector('#download-options'); const highlightItem = downloadOptions.querySelector( '[data-download="png-highlight"]', ); let downloadItems = []; function setDownloadMenu(open, focusItem = 0) { if (open) { // Offer a highlighted image only while a company or sector is chosen. const highlight = exportHighlight(); highlightItem.hidden = !highlight; if (highlight) highlightItem.textContent = &#96;Chart image, highlighting ${highlight.name}&#96;; downloadItems = [...downloadOptions.querySelectorAll('button:not([hidden])'), ]; if (focusItem &lt; 0) focusItem = downloadItems.length - 1; } downloadOptions.hidden = !open; downloadToggle.setAttribute('aria-expanded', String(open)); if (open) downloadItems[focusItem].focus(); } downloadToggle.addEventListener('click', () =&gt; setDownloadMenu(downloadOptions.hidden), ); downloadToggle.addEventListener('keydown', event =&gt; { if (!['ArrowDown', 'ArrowUp'].includes(event.key)) return; event.preventDefault(); setDownloadMenu(true, event.key === 'ArrowUp' ? -1 : 0); }); downloadOptions.addEventListener('keydown', event =&gt; { const index = downloadItems.indexOf(document.activeElement); if (event.key === 'Escape') { // Close the menu only; don't also clear the chart selection. event.preventDefault(); event.stopPropagation(); setDownloadMenu(false); downloadToggle.focus(); } else if (['ArrowDown', 'ArrowUp'].includes(event.key)) { event.preventDefault(); const step = event.key === 'ArrowDown' ? 1 : -1; downloadItems[ (index + step + downloadItems.length) % downloadItems.length].focus(); } else if (event.key === 'Tab') setDownloadMenu(false); }); document.addEventListener('click', event =&gt; { if (!event.target.closest('.download-menu')) setDownloadMenu(false); }); downloadOptions.addEventListener('click', async event =&gt; { const item = event.target.closest('[data-download]'); if (!item &#124;&#124; !data) return; setDownloadMenu(false); downloadToggle.focus(); if (item.dataset.download === 'csv') return downloadCsv(); item.disabled = true; try { await downloadPng( item === highlightItem ? exportHighlight() : null, ); } catch (error) { console.error('PNG export failed:', error); document.querySelector('#selection-announcement').textContent = 'Sorry, the chart image could not be created.'; } finally { item.disabled = false; } }); loadData(); 

#### Methodology 

The Government Ownership Dashboard tracks the second Trump administration’s expansion of federal ownership and ownership rights in private businesses. It covers arrangements announced or completed since January 20, 2025, within the scope described below. The dashboard counts companies covered by those arrangements, including pending deals.

**What we include**

We include common and preferred stock, as well as warrants, options, and convertible securities that give the federal government a right to acquire an ownership interest. These rights may depend on future events. An unexercised warrant or option is identified as such and does not mean the government already owns the underlying shares.

We also include special ownership instruments that confer company-specific control. The US Steel golden share, for example, gives the federal government veto rights over certain business decisions despite carrying no conventional economic interest.

**How we count companies**

Each company is counted once, even when it receives multiple awards, has arrangements with several federal agencies, or issues more than one type of ownership instrument. Both publicly traded and privately held companies, including companies headquartered outside the United States, are eligible.

Company-level interests can be held directly or through a transaction-specific intermediary that owns shares in an identified operating company. We distinguish these arrangements from stakes confined to an individual project, joint venture, investment fund, or financing vehicle that convey no interest in the sponsoring company. Those standalone arrangements are outside the main company count.

When one package includes both company and project interests, we describe both in the company’s entry without counting the project as another company. Entries identify the business in which the ownership rights actually arise.

**Development Finance Corporation investments**

Investments under the US International Development Finance Corporation’s [standing statutory equity authority](https://www.law.cornell.edu/uscode/text/22/9621) are excluded from the dashboard because Congress expressly authorized DFC minority equity investments in the 2018 BUILD Act. The dashboard focuses on the administration’s acquisition of ownership interests and rights in companies outside the established DFC equity program. This exclusion does not imply that DFC ownership raises no economic, governance, or legal concerns. It reflects a fundamental difference between DFC’s established, congressionally authorized equity investment program and the administration’s use of company ownership as a regular economic policy tool outside a broader financial crisis.

That said, DFC’s participation does not prevent a company or transaction from being included in the dashboard where another agency acquires a separate qualifying ownership interest. Arrangements involving DFC but executed on behalf of another agency or under other authorities are assessed based on their actual structure and the authority being exercised.

### Featured Essays

---

[![economics, United States Capitol](/sites/cato.org/files/styles/content_teaser/public/2026-08/GettyImages-2259928473.jpg?itok=ykh_nbhO)](https://www.cato.org/restoring-free-enterprise/american-free-enterprise-under-attack) 

Publications 

### [Introduction](/restoring-free-enterprise/american-free-enterprise-under-attack) 

By [Scott Lincicome](https://www.cato.org/people/scott-lincicome) 

From steel and semiconductors to defense and mining, the federal government is increasingly intertwined with private firms. This new era of “state corporatism” threatens the market-driven dynamism and innovation that have long underpinned American prosperity.

[![Money stealing from American flag.](/sites/cato.org/files/styles/content_teaser/public/2026-06/GettyImages-2199640081.jpg?itok=c3khTpfp)](https://www.cato.org/restoring-free-enterprise/monetizing-executive-discretion) 

Publications 

### [Monetizing Executive Discretion](/restoring-free-enterprise/monetizing-executive-discretion) 

By [Adam N. Michel](https://www.cato.org/people/adam-n-michel) and [Santiago Forster](https://www.cato.org/people/santiago-forster) 

The Trump administration has added a new facet to its state involvement in which executive discretion itself is a monetizable asset for the Treasury.

[![Congress and money](/sites/cato.org/files/styles/content_teaser/public/2026-09/GettyImages-2148276062.jpg?itok=rO4qa4dx)](https://www.cato.org/restoring-free-enterprise/free-enterprise-made-america-rich) 

Publications 

### [Free Enterprise Made America Rich](/restoring-free-enterprise/free-enterprise-made-america-rich) 

By [Jeremy Horpedahl](https://www.cato.org/people/jeremy-horpedahl) 

The United States has reached a critical juncture regarding government involvement in the private economy. Recent federal interventions have marked a clear departure from the nation’s historical framework of economic freedom.

[Browse Essay Library](https://www.cato.org/restoring-free-enterprise/rfe-essay-library) 

### Other Featured Content

---

##### Blog Posts 

##### [Trump Equity Portfolio Hits 40—Democrats Have Plans for It](/blog/trump-equity-portfolio-hits-40-democrats-have-plans-it) 

By [Tad DeHaven](https://www.cato.org/people/tad-dehaven) 

##### [A Libertarian Warned of the New Right’s Authoritarianism 40 Years Ago—Today’s National Conservatives Are Proving Him Right](/blog/libertarian-warned-new-rights-authoritarianism-40-years-ago-todays-national-conservatives-are) 

By [Michael Chapman](https://www.cato.org/people/michael-chapman) 

##### [The Government’s Unprecedented Equity Portfolio Has a Distressingly Weak Legal Foundation](/blog/governments-unprecedented-equity-portfolio-has-distressingly-weak-legal-foundation) 

By [Scott Lincicome](https://www.cato.org/people/scott-lincicome) 

##### [Introducing Restoring Free Enterprise](/blog/introducing-restoring-free-enterprise) 

By [Tad DeHaven](https://www.cato.org/people/tad-dehaven) 

##### [Trump Administration’s Equity Portfolio Reaches 34 Deals](/blog/trump-administrations-equity-portfolio-reaches-34-deals) 

By [Tad DeHaven](https://www.cato.org/people/tad-dehaven) and [Chad Smitson](https://www.cato.org/people/chad-smitson) 

## [Uncle Sam Owning Private Companies?](https://www.cato.org/multimedia/cato-weekly-podcast/uncle-sam-owning-private-companies)

In an unprecedented expansion of its role in the marketplace, the Trump administration has acquired ownership interests in dozens of firms. Cato’s Scott Lincicome joins Laura Bondank-Harmon to discuss this radical move from the US government, the seen and unseen costs of intervening in the private sector, and how should Congress act if they want to put a stop to it before it’s too late.

#### Happening Now