diff --git a/src/App.svelte b/src/App.svelte
index ea472a7..b07f449 100644
--- a/src/App.svelte
+++ b/src/App.svelte
@@ -6,6 +6,7 @@
import SamplePreview from "./components/SamplePreview.svelte";
import DailyTrackingButton from "./components/DailyTrackingButton.svelte";
import ResultsView from "./components/ResultsView.svelte";
+ import SiteFooter from "./components/SiteFooter.svelte";
import TagInput from "./components/TagInput.svelte";
import ThemeToggle from "./components/ThemeToggle.svelte";
import TrustGlossary from "./components/TrustGlossary.svelte";
@@ -618,15 +619,7 @@
-
+
{
afterEach(() => vi.unstubAllGlobals());
+ test("links to the source repository from the footer", () => {
+ render(App);
+
+ expect(screen.getByRole("link", { name: "npm.report on GitHub" })).toHaveAttribute(
+ "href",
+ "https://github.com/serhalp/npm.report",
+ );
+ });
+
test("validates required organizations before running", async () => {
const user = userEvent.setup();
render(App);
diff --git a/src/SharedReport.svelte b/src/SharedReport.svelte
index f981cd7..79670e0 100644
--- a/src/SharedReport.svelte
+++ b/src/SharedReport.svelte
@@ -3,6 +3,7 @@
import HistoryPanel from "./components/HistoryPanel.svelte";
import DailyTrackingButton from "./components/DailyTrackingButton.svelte";
import ResultsView from "./components/ResultsView.svelte";
+ import SiteFooter from "./components/SiteFooter.svelte";
import ThemeToggle from "./components/ThemeToggle.svelte";
import TrustGlossary from "./components/TrustGlossary.svelte";
import Logo from "./components/Logo.svelte";
@@ -211,6 +212,8 @@
{/if}
+
+
{
expect(screen.getByText("netlify")).toBeInTheDocument();
expect(screen.getByText(/generated 2026-06-27/)).toBeInTheDocument();
expect(screen.getAllByText("last 12 months").length).toBeGreaterThan(0);
+ expect(screen.getByRole("link", { name: "npm.report on GitHub" })).toHaveAttribute(
+ "href",
+ "https://github.com/serhalp/npm.report",
+ );
expect(fetch).toHaveBeenCalledWith("/api/reports/report%2Fid");
});
diff --git a/src/components/SiteFooter.svelte b/src/components/SiteFooter.svelte
new file mode 100644
index 0000000..5e87872
--- /dev/null
+++ b/src/components/SiteFooter.svelte
@@ -0,0 +1,27 @@
+
diff --git a/src/styles.css b/src/styles.css
index c2b4b6c..074dd3a 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -1991,8 +1991,10 @@ table.data tbody tr:hover td {
.footer {
display: flex;
- flex-direction: column;
- gap: 0.85rem;
+ align-items: center;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ gap: var(--space-md) var(--space-xl);
margin-top: 3.75rem;
padding-top: 1.375rem;
border-top: 1px solid var(--line);
@@ -2004,10 +2006,37 @@ table.data tbody tr:hover td {
.footer p {
margin: 0;
}
+.footer__brand {
+ flex: 1 1 32rem;
+}
+.footer__credits {
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
+ margin-inline-start: auto;
+ white-space: nowrap;
+}
.footer a {
text-decoration: underline;
text-underline-offset: 0.18em;
}
+.footer__github {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 2.75rem;
+ height: 2.75rem;
+ border-radius: var(--radius);
+ color: var(--fg-dim);
+ text-decoration: none;
+}
+.footer__github:hover {
+ color: var(--gold);
+}
+.footer__github:focus-visible {
+ outline: 2px solid var(--gold-dim);
+ outline-offset: 2px;
+}
.footer__mark {
color: var(--fg-dim);
}
@@ -2025,6 +2054,7 @@ table.data tbody tr:hover td {
.report-frame > [role="tabpanel"]:focus-visible,
.history-stack:focus-visible,
.trust-trend__target:focus-visible,
+ .footer__github:focus-visible,
table.data th .sort-button:focus-visible {
outline: 2px solid Highlight;
outline-offset: 2px;