Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 187 additions & 0 deletions assets/main.scss
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@ $spatialdatablue: #40a9ff;
$squidpyviolet: #969dea;
$scirpypurple: #da347f;
$scviyellow: #fbb822;
$scversedeepblue: #262fb5; // the darker end of the Join button gradient

$github-black: #171b21;
$twitter-blue: #469be5;
Expand DownExpand Up@@ -1032,6 +1033,192 @@ body {
border-top: 0px;
}

// Specific to /stats

#stats-content {
.stats-updated {
font-size: 0.9rem;
color: $greydesc;
}

// The one number the page leads with
.stats-hero {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 1rem 3.5rem;
margin: 2.5rem 0 1rem;

.stats-hero-figure {
display: flex;
flex-direction: column;
gap: 0.35rem;
}
.stats-hero-figure--second {
padding-left: 3.5rem;
border-left: 1px solid $overline;
}
@media (max-width: 50rem) {
.stats-hero-figure--second {
padding-left: 0;
border-left: 0;
}
}

.stats-hero-value {
font-size: 4.5rem;
font-weight: 800;
line-height: 1;
color: black;
}
.stats-hero-label {
font-size: 1.15rem;
color: $tiletext;
}
.stats-hero-hint {
font-size: 0.9rem;
color: $greydesc;
}
@media (max-width: 50rem) {
.stats-hero-value {
font-size: 3rem;
}
}
}

.stats-tiles {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
gap: 0.75rem;
}

.stats-tile {
display: flex;
flex-direction: column;
gap: 0.3rem;
padding: 1.1rem 1.25rem;
background-color: $tilebg4;
border: 1px solid $overline;
border-radius: 0.75rem;

.stats-tile-value {
font-size: 2rem;
font-weight: 800;
line-height: 1.1;
color: black;
}
.stats-tile-label {
font-size: 0.85rem;
line-height: 1.35;
color: $tiletext2;
}
}

// Only tiles that lead somewhere get the affordance
a.stats-tile {
text-decoration: none;
transition: all 200ms ease-in-out;

&:hover,
&:focus-visible {
background-color: $tilebg;
border-color: $tilebg2;
transform: scale(1.02);

.stats-tile-value {
color: black;
}
.stats-tile-label {
color: $tiletext;
}
}
}

.stats-contributors {
display: grid;
gap: 0.15rem;
margin: 1.5rem 0;
padding: 0;
list-style: none;

a {
display: grid;
grid-template-columns: 2.5rem minmax(7rem, 11rem) 1fr auto;
align-items: center;
gap: 0.85rem;
padding: 0.35rem 0.5rem;
border-radius: 0.5rem;
color: $tiletext;
text-decoration: none;
transition: all 200ms ease-in-out;

&:hover {
color: black;
background-color: $tilebg4;
}
}
img {
width: 2.5rem;
height: 2.5rem;
border-radius: 50%;
}
.stats-contributor-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
// Thin recessive track, value anchored to the baseline with 4px ends
.stats-contributor-bar {
height: 0.5rem;
background-color: $tilebg;
border-radius: 0.25rem;

> span {
display: block;
min-width: 0.15rem;
height: 100%;
// A hue rather than a dark neutral, so extensions that invert the page keep the bar visible
background-color: $scversedeepblue;
border-radius: 0.25rem;
}
}
.stats-contributor-count {
font-size: 0.85rem;
color: $greydesc;
font-variant-numeric: tabular-nums;
}

@media (max-width: 50rem) {
a {
grid-template-columns: 2.5rem 1fr auto;
}
.stats-contributor-bar {
display: none;
}
}
}

.stats-rest {
summary {
width: fit-content;
padding: 0.35rem 0.5rem;
font-size: 0.9rem;
color: $tiletext2;
border-radius: 0.5rem;
cursor: pointer;
transition: all 200ms ease-in-out;

&:hover {
color: black;
background-color: $tilebg4;
}
}
&[open] summary {
margin-bottom: 0.5rem;
}
}
}

// Specific to /tutorials

#tutorials-content {
Expand Down
4 changes: 4 additions & 0 deletions content/stats/_index.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
+++
title = "scverse in numbers"
preface = "How the scverse community and its packages are doing. The numbers cover the core packages and the scverse GitHub organisation, and are refreshed twice a week by <a href='https://github.com/scverse/stats'>scverse/stats</a>."
+++
3 changes: 3 additions & 0 deletions layouts/partials/header.html
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,6 +118,9 @@
<li>
<a class="dropdown-item" href="/about/ai_policy">AI Policy</a>
</li>
<li>
<a class="dropdown-item" href="/stats">Statistics</a>
</li>
<li>
<a class="dropdown-item" href="/design">Media kit</a>
</li>
Expand Down
12 changes: 12 additions & 0 deletions layouts/partials/stats-contributor.html
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
{{ $person := .person }}
{{ $most := .most }}
<li>
<a href="{{ $person.html_url }}" target="_blank" rel="noopener">
<img src="{{ $person.avatar_url }}&s=96" alt="" loading="lazy" width="40" height="40" />
<span class="stats-contributor-name">{{ $person.name | default $person.login }}</span>
<span class="stats-contributor-bar" aria-hidden="true">
<span style="width: {{ math.Round (div (mul (float $person.contributions) 100.0) (float $most)) }}%"></span>
</span>
<span class="stats-contributor-count">{{ lang.FormatNumberCustom 0 $person.contributions }}</span>
</a>
</li>
121 changes: 121 additions & 0 deletions layouts/stats/list.html
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
{{ define "main" }}
<h1>{{ .Title }}</h1>
<article class="post">
<div class="post-content" id="stats-content">
{{ $base := "https://scverse.org/stats/" }}
{{ $stats := transform.Unmarshal (resources.GetRemote (printf "%sstats.json" $base)).Content }}
{{ $contributors := transform.Unmarshal (resources.GetRemote (printf "%scontributors.json" $base)).Content }}
{{ $gh := $stats.github }}


<p>{{ .Params.preface | safeHTML }}</p>

<p class="stats-updated">
Updated
<time datetime="{{ $stats.timestamp }}">{{ (time $stats.timestamp).Format "2 January 2006" }}</time>
</p>

{{/* The one number the page leads with. Everything else is a tile. */}}
<section class="stats-hero">
<div class="stats-hero-figure">
<span class="stats-hero-value">{{ printf "%.1fM" (div (float $stats.pepy_downloads) 1000000.0) }}</span>
<span class="stats-hero-label">downloads of the core packages, all time</span>
<span class="stats-hero-hint">
{{ lang.FormatNumberCustom 0 $stats.pepy_avg_daily_30 }} a day on average over the last 30 days
</span>
</div>
{{ with $stats.citation_count }}
<div class="stats-hero-figure stats-hero-figure--second">
<span class="stats-hero-value">{{ lang.FormatNumberCustom 0 . }}</span>
<span class="stats-hero-label">citations of scverse papers</span>
</div>
{{ end }}
</section>

{{/* A tile only gets a link where the destination genuinely shows the same number. */}}
{{/* LinkedIn is scraped upstream, so any given run may not have it. */}}
{{ $social := slice
(dict "value" $stats.zulip_users "label" "Zulip members" "url" "https://scverse.zulipchat.com/")
(dict "value" $stats.bluesky_followers "label" "Bluesky followers" "url" "https://bsky.app/profile/scverse.bsky.social")
}}
{{ with $stats.linkedin_followers }}
{{ $social = $social | append (dict "value" . "label" "LinkedIn followers" "url" "https://www.linkedin.com/company/scverse/") }}
{{ end }}

{{ $groups := slice
(dict
"name" "Community"
"tiles" ($social | append
(dict "value" $gh.unique_contributors "label" "code contributors" "url" "#contributors")
(dict "value" $gh.organization_members "label" "GitHub organisation members" "url" "https://github.com/orgs/scverse/people")
(dict "value" $stats.core_team_size "label" "core team members" "url" "/people")
)
)
(dict
"name" "Packages and code"
"tiles" (slice
(dict "value" $stats.ecosystem_packages "label" "ecosystem packages" "url" "/packages#ecosystem")
(dict "value" $gh.total_repositories "label" "core repositories" "url" "/packages#core-packages")
(dict "value" $gh.total_stars "label" "GitHub stars" "url" "https://github.com/scverse")
(dict "value" $gh.total_pull_requests_last_year "label" "pull requests in the last year")
(dict "value" $gh.total_pull_requests_closed "label" "pull requests closed, all time")
(dict "value" $gh.total_issues_open "label" "issues open")
(dict "value" $gh.total_issues_closed "label" "issues closed")
)
)
}}

{{ range $groups }}
<h2>{{ .name }}</h2>
<div class="stats-tiles">
{{ range .tiles }}
{{ if .url }}
<a
class="stats-tile"
href="{{ .url }}"
{{ if hasPrefix .url "http" }}target="_blank" rel="noopener"{{ end }}
>
<span class="stats-tile-value">{{ lang.FormatNumberCustom 0 .value }}</span>
<span class="stats-tile-label">{{ .label }}</span>
</a>
{{ else }}
<div class="stats-tile">
<span class="stats-tile-value">{{ lang.FormatNumberCustom 0 .value }}</span>
<span class="stats-tile-label">{{ .label }}</span>
</div>
{{ end }}
{{ end }}
</div>
{{ end }}


<h2 id="contributors">Top contributors</h2>
<p>
{{ lang.FormatNumberCustom 0 $contributors.total_contributors }} people have contributed code to the core
packages, ranked here by number of commits.
</p>

{{ $shown := 24 }}
{{ $top := first $shown $contributors.contributors }}
{{ $rest := after $shown $contributors.contributors }}
{{ $most := (index $top 0).contributions }}
<ol class="stats-contributors">
{{ range $top }}
{{ partial "stats-contributor.html" (dict "person" . "most" $most) }}
{{ end }}
</ol>

{{ with $rest }}
<details class="stats-rest">
<summary>Show the other {{ lang.FormatNumberCustom 0 (len .) }} contributors</summary>
<ol class="stats-contributors" start="{{ add $shown 1 }}">
{{ range . }}
{{ partial "stats-contributor.html" (dict "person" . "most" $most) }}
{{ end }}
</ol>
</details>
{{ end }}

</div>
</article>
{{ end }}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 187 additions & 0 deletions assets/main.scss
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@ $spatialdatablue: #40a9ff;
$squidpyviolet: #969dea;
$scirpypurple: #da347f;
$scviyellow: #fbb822;
$scversedeepblue: #262fb5; // the darker end of the Join button gradient

$github-black: #171b21;
$twitter-blue: #469be5;
Expand DownExpand Up@@ -1032,6 +1033,192 @@ body {
border-top: 0px;
}

// Specific to /stats

#stats-content {
.stats-updated {
font-size: 0.9rem;
color: $greydesc;
}

// The one number the page leads with
.stats-hero {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 1rem 3.5rem;
margin: 2.5rem 0 1rem;

.stats-hero-figure {
display: flex;
flex-direction: column;
gap: 0.35rem;
}
.stats-hero-figure--second {
padding-left: 3.5rem;
border-left: 1px solid $overline;
}
@media (max-width: 50rem) {
.stats-hero-figure--second {
padding-left: 0;
border-left: 0;
}
}

.stats-hero-value {
font-size: 4.5rem;
font-weight: 800;
line-height: 1;
color: black;
}
.stats-hero-label {
font-size: 1.15rem;
color: $tiletext;
}
.stats-hero-hint {
font-size: 0.9rem;
color: $greydesc;
}
@media (max-width: 50rem) {
.stats-hero-value {
font-size: 3rem;
}
}
}

.stats-tiles {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
gap: 0.75rem;
}

.stats-tile {
display: flex;
flex-direction: column;
gap: 0.3rem;
padding: 1.1rem 1.25rem;
background-color: $tilebg4;
border: 1px solid $overline;
border-radius: 0.75rem;

.stats-tile-value {
font-size: 2rem;
font-weight: 800;
line-height: 1.1;
color: black;
}
.stats-tile-label {
font-size: 0.85rem;
line-height: 1.35;
color: $tiletext2;
}
}

// Only tiles that lead somewhere get the affordance
a.stats-tile {
text-decoration: none;
transition: all 200ms ease-in-out;

&:hover,
&:focus-visible {
background-color: $tilebg;
border-color: $tilebg2;
transform: scale(1.02);

.stats-tile-value {
color: black;
}
.stats-tile-label {
color: $tiletext;
}
}
}

.stats-contributors {
display: grid;
gap: 0.15rem;
margin: 1.5rem 0;
padding: 0;
list-style: none;

a {
display: grid;
grid-template-columns: 2.5rem minmax(7rem, 11rem) 1fr auto;
align-items: center;
gap: 0.85rem;
padding: 0.35rem 0.5rem;
border-radius: 0.5rem;
color: $tiletext;
text-decoration: none;
transition: all 200ms ease-in-out;

&:hover {
color: black;
background-color: $tilebg4;
}
}
img {
width: 2.5rem;
height: 2.5rem;
border-radius: 50%;
}
.stats-contributor-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
// Thin recessive track, value anchored to the baseline with 4px ends
.stats-contributor-bar {
height: 0.5rem;
background-color: $tilebg;
border-radius: 0.25rem;

> span {
display: block;
min-width: 0.15rem;
height: 100%;
// A hue rather than a dark neutral, so extensions that invert the page keep the bar visible
background-color: $scversedeepblue;
border-radius: 0.25rem;
}
}
.stats-contributor-count {
font-size: 0.85rem;
color: $greydesc;
font-variant-numeric: tabular-nums;
}

@media (max-width: 50rem) {
a {
grid-template-columns: 2.5rem 1fr auto;
}
.stats-contributor-bar {
display: none;
}
}
}

.stats-rest {
summary {
width: fit-content;
padding: 0.35rem 0.5rem;
font-size: 0.9rem;
color: $tiletext2;
border-radius: 0.5rem;
cursor: pointer;
transition: all 200ms ease-in-out;

&:hover {
color: black;
background-color: $tilebg4;
}
}
&[open] summary {
margin-bottom: 0.5rem;
}
}
}

// Specific to /tutorials

#tutorials-content {
Expand Down
4 changes: 4 additions & 0 deletions content/stats/_index.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
+++
title = "scverse in numbers"
preface = "How the scverse community and its packages are doing. The numbers cover the core packages and the scverse GitHub organisation, and are refreshed twice a week by <a href='https://github.com/scverse/stats'>scverse/stats</a>."
+++
3 changes: 3 additions & 0 deletions layouts/partials/header.html
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,6 +118,9 @@
<li>
<a class="dropdown-item" href="/about/ai_policy">AI Policy</a>
</li>
<li>
<a class="dropdown-item" href="/stats">Statistics</a>
</li>
<li>
<a class="dropdown-item" href="/design">Media kit</a>
</li>
Expand Down
12 changes: 12 additions & 0 deletions layouts/partials/stats-contributor.html
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
{{ $person := .person }}
{{ $most := .most }}
<li>
<a href="{{ $person.html_url }}" target="_blank" rel="noopener">
<img src="{{ $person.avatar_url }}&s=96" alt="" loading="lazy" width="40" height="40" />
<span class="stats-contributor-name">{{ $person.name | default $person.login }}</span>
<span class="stats-contributor-bar" aria-hidden="true">
<span style="width: {{ math.Round (div (mul (float $person.contributions) 100.0) (float $most)) }}%"></span>
</span>
<span class="stats-contributor-count">{{ lang.FormatNumberCustom 0 $person.contributions }}</span>
</a>
</li>
121 changes: 121 additions & 0 deletions layouts/stats/list.html
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
{{ define "main" }}
<h1>{{ .Title }}</h1>
<article class="post">
<div class="post-content" id="stats-content">
{{ $base := "https://scverse.org/stats/" }}
{{ $stats := transform.Unmarshal (resources.GetRemote (printf "%sstats.json" $base)).Content }}
{{ $contributors := transform.Unmarshal (resources.GetRemote (printf "%scontributors.json" $base)).Content }}
{{ $gh := $stats.github }}


<p>{{ .Params.preface | safeHTML }}</p>

<p class="stats-updated">
Updated
<time datetime="{{ $stats.timestamp }}">{{ (time $stats.timestamp).Format "2 January 2006" }}</time>
</p>

{{/* The one number the page leads with. Everything else is a tile. */}}
<section class="stats-hero">
<div class="stats-hero-figure">
<span class="stats-hero-value">{{ printf "%.1fM" (div (float $stats.pepy_downloads) 1000000.0) }}</span>
<span class="stats-hero-label">downloads of the core packages, all time</span>
<span class="stats-hero-hint">
{{ lang.FormatNumberCustom 0 $stats.pepy_avg_daily_30 }} a day on average over the last 30 days
</span>
</div>
{{ with $stats.citation_count }}
<div class="stats-hero-figure stats-hero-figure--second">
<span class="stats-hero-value">{{ lang.FormatNumberCustom 0 . }}</span>
<span class="stats-hero-label">citations of scverse papers</span>
</div>
{{ end }}
</section>

{{/* A tile only gets a link where the destination genuinely shows the same number. */}}
{{/* LinkedIn is scraped upstream, so any given run may not have it. */}}
{{ $social := slice
(dict "value" $stats.zulip_users "label" "Zulip members" "url" "https://scverse.zulipchat.com/")
(dict "value" $stats.bluesky_followers "label" "Bluesky followers" "url" "https://bsky.app/profile/scverse.bsky.social")
}}
{{ with $stats.linkedin_followers }}
{{ $social = $social | append (dict "value" . "label" "LinkedIn followers" "url" "https://www.linkedin.com/company/scverse/") }}
{{ end }}

{{ $groups := slice
(dict
"name" "Community"
"tiles" ($social | append
(dict "value" $gh.unique_contributors "label" "code contributors" "url" "#contributors")
(dict "value" $gh.organization_members "label" "GitHub organisation members" "url" "https://github.com/orgs/scverse/people")
(dict "value" $stats.core_team_size "label" "core team members" "url" "/people")
)
)
(dict
"name" "Packages and code"
"tiles" (slice
(dict "value" $stats.ecosystem_packages "label" "ecosystem packages" "url" "/packages#ecosystem")
(dict "value" $gh.total_repositories "label" "core repositories" "url" "/packages#core-packages")
(dict "value" $gh.total_stars "label" "GitHub stars" "url" "https://github.com/scverse")
(dict "value" $gh.total_pull_requests_last_year "label" "pull requests in the last year")
(dict "value" $gh.total_pull_requests_closed "label" "pull requests closed, all time")
(dict "value" $gh.total_issues_open "label" "issues open")
(dict "value" $gh.total_issues_closed "label" "issues closed")
)
)
}}

{{ range $groups }}
<h2>{{ .name }}</h2>
<div class="stats-tiles">
{{ range .tiles }}
{{ if .url }}
<a
class="stats-tile"
href="{{ .url }}"
{{ if hasPrefix .url "http" }}target="_blank" rel="noopener"{{ end }}
>
<span class="stats-tile-value">{{ lang.FormatNumberCustom 0 .value }}</span>
<span class="stats-tile-label">{{ .label }}</span>
</a>
{{ else }}
<div class="stats-tile">
<span class="stats-tile-value">{{ lang.FormatNumberCustom 0 .value }}</span>
<span class="stats-tile-label">{{ .label }}</span>
</div>
{{ end }}
{{ end }}
</div>
{{ end }}


<h2 id="contributors">Top contributors</h2>
<p>
{{ lang.FormatNumberCustom 0 $contributors.total_contributors }} people have contributed code to the core
packages, ranked here by number of commits.
</p>

{{ $shown := 24 }}
{{ $top := first $shown $contributors.contributors }}
{{ $rest := after $shown $contributors.contributors }}
{{ $most := (index $top 0).contributions }}
<ol class="stats-contributors">
{{ range $top }}
{{ partial "stats-contributor.html" (dict "person" . "most" $most) }}
{{ end }}
</ol>

{{ with $rest }}
<details class="stats-rest">
<summary>Show the other {{ lang.FormatNumberCustom 0 (len .) }} contributors</summary>
<ol class="stats-contributors" start="{{ add $shown 1 }}">
{{ range . }}
{{ partial "stats-contributor.html" (dict "person" . "most" $most) }}
{{ end }}
</ol>
</details>
{{ end }}

</div>
</article>
{{ end }}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 187 additions & 0 deletions assets/main.scss
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@ $spatialdatablue: #40a9ff;
$squidpyviolet: #969dea;
$scirpypurple: #da347f;
$scviyellow: #fbb822;
$scversedeepblue: #262fb5; // the darker end of the Join button gradient

$github-black: #171b21;
$twitter-blue: #469be5;
Expand DownExpand Up@@ -1032,6 +1033,192 @@ body {
border-top: 0px;
}

// Specific to /stats

#stats-content {
.stats-updated {
font-size: 0.9rem;
color: $greydesc;
}

// The one number the page leads with
.stats-hero {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 1rem 3.5rem;
margin: 2.5rem 0 1rem;

.stats-hero-figure {
display: flex;
flex-direction: column;
gap: 0.35rem;
}
.stats-hero-figure--second {
padding-left: 3.5rem;
border-left: 1px solid $overline;
}
@media (max-width: 50rem) {
.stats-hero-figure--second {
padding-left: 0;
border-left: 0;
}
}

.stats-hero-value {
font-size: 4.5rem;
font-weight: 800;
line-height: 1;
color: black;
}
.stats-hero-label {
font-size: 1.15rem;
color: $tiletext;
}
.stats-hero-hint {
font-size: 0.9rem;
color: $greydesc;
}
@media (max-width: 50rem) {
.stats-hero-value {
font-size: 3rem;
}
}
}

.stats-tiles {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
gap: 0.75rem;
}

.stats-tile {
display: flex;
flex-direction: column;
gap: 0.3rem;
padding: 1.1rem 1.25rem;
background-color: $tilebg4;
border: 1px solid $overline;
border-radius: 0.75rem;

.stats-tile-value {
font-size: 2rem;
font-weight: 800;
line-height: 1.1;
color: black;
}
.stats-tile-label {
font-size: 0.85rem;
line-height: 1.35;
color: $tiletext2;
}
}

// Only tiles that lead somewhere get the affordance
a.stats-tile {
text-decoration: none;
transition: all 200ms ease-in-out;

&:hover,
&:focus-visible {
background-color: $tilebg;
border-color: $tilebg2;
transform: scale(1.02);

.stats-tile-value {
color: black;
}
.stats-tile-label {
color: $tiletext;
}
}
}

.stats-contributors {
display: grid;
gap: 0.15rem;
margin: 1.5rem 0;
padding: 0;
list-style: none;

a {
display: grid;
grid-template-columns: 2.5rem minmax(7rem, 11rem) 1fr auto;
align-items: center;
gap: 0.85rem;
padding: 0.35rem 0.5rem;
border-radius: 0.5rem;
color: $tiletext;
text-decoration: none;
transition: all 200ms ease-in-out;

&:hover {
color: black;
background-color: $tilebg4;
}
}
img {
width: 2.5rem;
height: 2.5rem;
border-radius: 50%;
}
.stats-contributor-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
// Thin recessive track, value anchored to the baseline with 4px ends
.stats-contributor-bar {
height: 0.5rem;
background-color: $tilebg;
border-radius: 0.25rem;

> span {
display: block;
min-width: 0.15rem;
height: 100%;
// A hue rather than a dark neutral, so extensions that invert the page keep the bar visible
background-color: $scversedeepblue;
border-radius: 0.25rem;
}
}
.stats-contributor-count {
font-size: 0.85rem;
color: $greydesc;
font-variant-numeric: tabular-nums;
}

@media (max-width: 50rem) {
a {
grid-template-columns: 2.5rem 1fr auto;
}
.stats-contributor-bar {
display: none;
}
}
}

.stats-rest {
summary {
width: fit-content;
padding: 0.35rem 0.5rem;
font-size: 0.9rem;
color: $tiletext2;
border-radius: 0.5rem;
cursor: pointer;
transition: all 200ms ease-in-out;

&:hover {
color: black;
background-color: $tilebg4;
}
}
&[open] summary {
margin-bottom: 0.5rem;
}
}
}

// Specific to /tutorials

#tutorials-content {
Expand Down
4 changes: 4 additions & 0 deletions content/stats/_index.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
+++
title = "scverse in numbers"
preface = "How the scverse community and its packages are doing. The numbers cover the core packages and the scverse GitHub organisation, and are refreshed twice a week by <a href='https://github.com/scverse/stats'>scverse/stats</a>."
+++
3 changes: 3 additions & 0 deletions layouts/partials/header.html
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,6 +118,9 @@
<li>
<a class="dropdown-item" href="/about/ai_policy">AI Policy</a>
</li>
<li>
<a class="dropdown-item" href="/stats">Statistics</a>
</li>
<li>
<a class="dropdown-item" href="/design">Media kit</a>
</li>
Expand Down
12 changes: 12 additions & 0 deletions layouts/partials/stats-contributor.html
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
{{ $person := .person }}
{{ $most := .most }}
<li>
<a href="{{ $person.html_url }}" target="_blank" rel="noopener">
<img src="{{ $person.avatar_url }}&s=96" alt="" loading="lazy" width="40" height="40" />
<span class="stats-contributor-name">{{ $person.name | default $person.login }}</span>
<span class="stats-contributor-bar" aria-hidden="true">
<span style="width: {{ math.Round (div (mul (float $person.contributions) 100.0) (float $most)) }}%"></span>
</span>
<span class="stats-contributor-count">{{ lang.FormatNumberCustom 0 $person.contributions }}</span>
</a>
</li>
121 changes: 121 additions & 0 deletions layouts/stats/list.html
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
{{ define "main" }}
<h1>{{ .Title }}</h1>
<article class="post">
<div class="post-content" id="stats-content">
{{ $base := "https://scverse.org/stats/" }}
{{ $stats := transform.Unmarshal (resources.GetRemote (printf "%sstats.json" $base)).Content }}
{{ $contributors := transform.Unmarshal (resources.GetRemote (printf "%scontributors.json" $base)).Content }}
{{ $gh := $stats.github }}


<p>{{ .Params.preface | safeHTML }}</p>

<p class="stats-updated">
Updated
<time datetime="{{ $stats.timestamp }}">{{ (time $stats.timestamp).Format "2 January 2006" }}</time>
</p>

{{/* The one number the page leads with. Everything else is a tile. */}}
<section class="stats-hero">
<div class="stats-hero-figure">
<span class="stats-hero-value">{{ printf "%.1fM" (div (float $stats.pepy_downloads) 1000000.0) }}</span>
<span class="stats-hero-label">downloads of the core packages, all time</span>
<span class="stats-hero-hint">
{{ lang.FormatNumberCustom 0 $stats.pepy_avg_daily_30 }} a day on average over the last 30 days
</span>
</div>
{{ with $stats.citation_count }}
<div class="stats-hero-figure stats-hero-figure--second">
<span class="stats-hero-value">{{ lang.FormatNumberCustom 0 . }}</span>
<span class="stats-hero-label">citations of scverse papers</span>
</div>
{{ end }}
</section>

{{/* A tile only gets a link where the destination genuinely shows the same number. */}}
{{/* LinkedIn is scraped upstream, so any given run may not have it. */}}
{{ $social := slice
(dict "value" $stats.zulip_users "label" "Zulip members" "url" "https://scverse.zulipchat.com/")
(dict "value" $stats.bluesky_followers "label" "Bluesky followers" "url" "https://bsky.app/profile/scverse.bsky.social")
}}
{{ with $stats.linkedin_followers }}
{{ $social = $social | append (dict "value" . "label" "LinkedIn followers" "url" "https://www.linkedin.com/company/scverse/") }}
{{ end }}

{{ $groups := slice
(dict
"name" "Community"
"tiles" ($social | append
(dict "value" $gh.unique_contributors "label" "code contributors" "url" "#contributors")
(dict "value" $gh.organization_members "label" "GitHub organisation members" "url" "https://github.com/orgs/scverse/people")
(dict "value" $stats.core_team_size "label" "core team members" "url" "/people")
)
)
(dict
"name" "Packages and code"
"tiles" (slice
(dict "value" $stats.ecosystem_packages "label" "ecosystem packages" "url" "/packages#ecosystem")
(dict "value" $gh.total_repositories "label" "core repositories" "url" "/packages#core-packages")
(dict "value" $gh.total_stars "label" "GitHub stars" "url" "https://github.com/scverse")
(dict "value" $gh.total_pull_requests_last_year "label" "pull requests in the last year")
(dict "value" $gh.total_pull_requests_closed "label" "pull requests closed, all time")
(dict "value" $gh.total_issues_open "label" "issues open")
(dict "value" $gh.total_issues_closed "label" "issues closed")
)
)
}}

{{ range $groups }}
<h2>{{ .name }}</h2>
<div class="stats-tiles">
{{ range .tiles }}
{{ if .url }}
<a
class="stats-tile"
href="{{ .url }}"
{{ if hasPrefix .url "http" }}target="_blank" rel="noopener"{{ end }}
>
<span class="stats-tile-value">{{ lang.FormatNumberCustom 0 .value }}</span>
<span class="stats-tile-label">{{ .label }}</span>
</a>
{{ else }}
<div class="stats-tile">
<span class="stats-tile-value">{{ lang.FormatNumberCustom 0 .value }}</span>
<span class="stats-tile-label">{{ .label }}</span>
</div>
{{ end }}
{{ end }}
</div>
{{ end }}


<h2 id="contributors">Top contributors</h2>
<p>
{{ lang.FormatNumberCustom 0 $contributors.total_contributors }} people have contributed code to the core
packages, ranked here by number of commits.
</p>

{{ $shown := 24 }}
{{ $top := first $shown $contributors.contributors }}
{{ $rest := after $shown $contributors.contributors }}
{{ $most := (index $top 0).contributions }}
<ol class="stats-contributors">
{{ range $top }}
{{ partial "stats-contributor.html" (dict "person" . "most" $most) }}
{{ end }}
</ol>

{{ with $rest }}
<details class="stats-rest">
<summary>Show the other {{ lang.FormatNumberCustom 0 (len .) }} contributors</summary>
<ol class="stats-contributors" start="{{ add $shown 1 }}">
{{ range . }}
{{ partial "stats-contributor.html" (dict "person" . "most" $most) }}
{{ end }}
</ol>
</details>
{{ end }}

</div>
</article>
{{ end }}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 187 additions & 0 deletions assets/main.scss
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@ $spatialdatablue: #40a9ff;
$squidpyviolet: #969dea;
$scirpypurple: #da347f;
$scviyellow: #fbb822;
$scversedeepblue: #262fb5; // the darker end of the Join button gradient

$github-black: #171b21;
$twitter-blue: #469be5;
Expand DownExpand Up@@ -1032,6 +1033,192 @@ body {
border-top: 0px;
}

// Specific to /stats

#stats-content {
.stats-updated {
font-size: 0.9rem;
color: $greydesc;
}

// The one number the page leads with
.stats-hero {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 1rem 3.5rem;
margin: 2.5rem 0 1rem;

.stats-hero-figure {
display: flex;
flex-direction: column;
gap: 0.35rem;
}
.stats-hero-figure--second {
padding-left: 3.5rem;
border-left: 1px solid $overline;
}
@media (max-width: 50rem) {
.stats-hero-figure--second {
padding-left: 0;
border-left: 0;
}
}

.stats-hero-value {
font-size: 4.5rem;
font-weight: 800;
line-height: 1;
color: black;
}
.stats-hero-label {
font-size: 1.15rem;
color: $tiletext;
}
.stats-hero-hint {
font-size: 0.9rem;
color: $greydesc;
}
@media (max-width: 50rem) {
.stats-hero-value {
font-size: 3rem;
}
}
}

.stats-tiles {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
gap: 0.75rem;
}

.stats-tile {
display: flex;
flex-direction: column;
gap: 0.3rem;
padding: 1.1rem 1.25rem;
background-color: $tilebg4;
border: 1px solid $overline;
border-radius: 0.75rem;

.stats-tile-value {
font-size: 2rem;
font-weight: 800;
line-height: 1.1;
color: black;
}
.stats-tile-label {
font-size: 0.85rem;
line-height: 1.35;
color: $tiletext2;
}
}

// Only tiles that lead somewhere get the affordance
a.stats-tile {
text-decoration: none;
transition: all 200ms ease-in-out;

&:hover,
&:focus-visible {
background-color: $tilebg;
border-color: $tilebg2;
transform: scale(1.02);

.stats-tile-value {
color: black;
}
.stats-tile-label {
color: $tiletext;
}
}
}

.stats-contributors {
display: grid;
gap: 0.15rem;
margin: 1.5rem 0;
padding: 0;
list-style: none;

a {
display: grid;
grid-template-columns: 2.5rem minmax(7rem, 11rem) 1fr auto;
align-items: center;
gap: 0.85rem;
padding: 0.35rem 0.5rem;
border-radius: 0.5rem;
color: $tiletext;
text-decoration: none;
transition: all 200ms ease-in-out;

&:hover {
color: black;
background-color: $tilebg4;
}
}
img {
width: 2.5rem;
height: 2.5rem;
border-radius: 50%;
}
.stats-contributor-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
// Thin recessive track, value anchored to the baseline with 4px ends
.stats-contributor-bar {
height: 0.5rem;
background-color: $tilebg;
border-radius: 0.25rem;

> span {
display: block;
min-width: 0.15rem;
height: 100%;
// A hue rather than a dark neutral, so extensions that invert the page keep the bar visible
background-color: $scversedeepblue;
border-radius: 0.25rem;
}
}
.stats-contributor-count {
font-size: 0.85rem;
color: $greydesc;
font-variant-numeric: tabular-nums;
}

@media (max-width: 50rem) {
a {
grid-template-columns: 2.5rem 1fr auto;
}
.stats-contributor-bar {
display: none;
}
}
}

.stats-rest {
summary {
width: fit-content;
padding: 0.35rem 0.5rem;
font-size: 0.9rem;
color: $tiletext2;
border-radius: 0.5rem;
cursor: pointer;
transition: all 200ms ease-in-out;

&:hover {
color: black;
background-color: $tilebg4;
}
}
&[open] summary {
margin-bottom: 0.5rem;
}
}
}

// Specific to /tutorials

#tutorials-content {
Expand Down
4 changes: 4 additions & 0 deletions content/stats/_index.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
+++
title = "scverse in numbers"
preface = "How the scverse community and its packages are doing. The numbers cover the core packages and the scverse GitHub organisation, and are refreshed twice a week by <a href='https://github.com/scverse/stats'>scverse/stats</a>."
+++
3 changes: 3 additions & 0 deletions layouts/partials/header.html
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,6 +118,9 @@
<li>
<a class="dropdown-item" href="/about/ai_policy">AI Policy</a>
</li>
<li>
<a class="dropdown-item" href="/stats">Statistics</a>
</li>
<li>
<a class="dropdown-item" href="/design">Media kit</a>
</li>
Expand Down
12 changes: 12 additions & 0 deletions layouts/partials/stats-contributor.html
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
{{ $person := .person }}
{{ $most := .most }}
<li>
<a href="{{ $person.html_url }}" target="_blank" rel="noopener">
<img src="{{ $person.avatar_url }}&s=96" alt="" loading="lazy" width="40" height="40" />
<span class="stats-contributor-name">{{ $person.name | default $person.login }}</span>
<span class="stats-contributor-bar" aria-hidden="true">
<span style="width: {{ math.Round (div (mul (float $person.contributions) 100.0) (float $most)) }}%"></span>
</span>
<span class="stats-contributor-count">{{ lang.FormatNumberCustom 0 $person.contributions }}</span>
</a>
</li>
121 changes: 121 additions & 0 deletions layouts/stats/list.html
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
{{ define "main" }}
<h1>{{ .Title }}</h1>
<article class="post">
<div class="post-content" id="stats-content">
{{ $base := "https://scverse.org/stats/" }}
{{ $stats := transform.Unmarshal (resources.GetRemote (printf "%sstats.json" $base)).Content }}
{{ $contributors := transform.Unmarshal (resources.GetRemote (printf "%scontributors.json" $base)).Content }}
{{ $gh := $stats.github }}


<p>{{ .Params.preface | safeHTML }}</p>

<p class="stats-updated">
Updated
<time datetime="{{ $stats.timestamp }}">{{ (time $stats.timestamp).Format "2 January 2006" }}</time>
</p>

{{/* The one number the page leads with. Everything else is a tile. */}}
<section class="stats-hero">
<div class="stats-hero-figure">
<span class="stats-hero-value">{{ printf "%.1fM" (div (float $stats.pepy_downloads) 1000000.0) }}</span>
<span class="stats-hero-label">downloads of the core packages, all time</span>
<span class="stats-hero-hint">
{{ lang.FormatNumberCustom 0 $stats.pepy_avg_daily_30 }} a day on average over the last 30 days
</span>
</div>
{{ with $stats.citation_count }}
<div class="stats-hero-figure stats-hero-figure--second">
<span class="stats-hero-value">{{ lang.FormatNumberCustom 0 . }}</span>
<span class="stats-hero-label">citations of scverse papers</span>
</div>
{{ end }}
</section>

{{/* A tile only gets a link where the destination genuinely shows the same number. */}}
{{/* LinkedIn is scraped upstream, so any given run may not have it. */}}
{{ $social := slice
(dict "value" $stats.zulip_users "label" "Zulip members" "url" "https://scverse.zulipchat.com/")
(dict "value" $stats.bluesky_followers "label" "Bluesky followers" "url" "https://bsky.app/profile/scverse.bsky.social")
}}
{{ with $stats.linkedin_followers }}
{{ $social = $social | append (dict "value" . "label" "LinkedIn followers" "url" "https://www.linkedin.com/company/scverse/") }}
{{ end }}

{{ $groups := slice
(dict
"name" "Community"
"tiles" ($social | append
(dict "value" $gh.unique_contributors "label" "code contributors" "url" "#contributors")
(dict "value" $gh.organization_members "label" "GitHub organisation members" "url" "https://github.com/orgs/scverse/people")
(dict "value" $stats.core_team_size "label" "core team members" "url" "/people")
)
)
(dict
"name" "Packages and code"
"tiles" (slice
(dict "value" $stats.ecosystem_packages "label" "ecosystem packages" "url" "/packages#ecosystem")
(dict "value" $gh.total_repositories "label" "core repositories" "url" "/packages#core-packages")
(dict "value" $gh.total_stars "label" "GitHub stars" "url" "https://github.com/scverse")
(dict "value" $gh.total_pull_requests_last_year "label" "pull requests in the last year")
(dict "value" $gh.total_pull_requests_closed "label" "pull requests closed, all time")
(dict "value" $gh.total_issues_open "label" "issues open")
(dict "value" $gh.total_issues_closed "label" "issues closed")
)
)
}}

{{ range $groups }}
<h2>{{ .name }}</h2>
<div class="stats-tiles">
{{ range .tiles }}
{{ if .url }}
<a
class="stats-tile"
href="{{ .url }}"
{{ if hasPrefix .url "http" }}target="_blank" rel="noopener"{{ end }}
>
<span class="stats-tile-value">{{ lang.FormatNumberCustom 0 .value }}</span>
<span class="stats-tile-label">{{ .label }}</span>
</a>
{{ else }}
<div class="stats-tile">
<span class="stats-tile-value">{{ lang.FormatNumberCustom 0 .value }}</span>
<span class="stats-tile-label">{{ .label }}</span>
</div>
{{ end }}
{{ end }}
</div>
{{ end }}


<h2 id="contributors">Top contributors</h2>
<p>
{{ lang.FormatNumberCustom 0 $contributors.total_contributors }} people have contributed code to the core
packages, ranked here by number of commits.
</p>

{{ $shown := 24 }}
{{ $top := first $shown $contributors.contributors }}
{{ $rest := after $shown $contributors.contributors }}
{{ $most := (index $top 0).contributions }}
<ol class="stats-contributors">
{{ range $top }}
{{ partial "stats-contributor.html" (dict "person" . "most" $most) }}
{{ end }}
</ol>

{{ with $rest }}
<details class="stats-rest">
<summary>Show the other {{ lang.FormatNumberCustom 0 (len .) }} contributors</summary>
<ol class="stats-contributors" start="{{ add $shown 1 }}">
{{ range . }}
{{ partial "stats-contributor.html" (dict "person" . "most" $most) }}
{{ end }}
</ol>
</details>
{{ end }}

</div>
</article>
{{ end }}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 187 additions & 0 deletions assets/main.scss
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@ $spatialdatablue: #40a9ff;
$squidpyviolet: #969dea;
$scirpypurple: #da347f;
$scviyellow: #fbb822;
$scversedeepblue: #262fb5; // the darker end of the Join button gradient

$github-black: #171b21;
$twitter-blue: #469be5;
Expand DownExpand Up@@ -1032,6 +1033,192 @@ body {
border-top: 0px;
}

// Specific to /stats

#stats-content {
.stats-updated {
font-size: 0.9rem;
color: $greydesc;
}

// The one number the page leads with
.stats-hero {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 1rem 3.5rem;
margin: 2.5rem 0 1rem;

.stats-hero-figure {
display: flex;
flex-direction: column;
gap: 0.35rem;
}
.stats-hero-figure--second {
padding-left: 3.5rem;
border-left: 1px solid $overline;
}
@media (max-width: 50rem) {
.stats-hero-figure--second {
padding-left: 0;
border-left: 0;
}
}

.stats-hero-value {
font-size: 4.5rem;
font-weight: 800;
line-height: 1;
color: black;
}
.stats-hero-label {
font-size: 1.15rem;
color: $tiletext;
}
.stats-hero-hint {
font-size: 0.9rem;
color: $greydesc;
}
@media (max-width: 50rem) {
.stats-hero-value {
font-size: 3rem;
}
}
}

.stats-tiles {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
gap: 0.75rem;
}

.stats-tile {
display: flex;
flex-direction: column;
gap: 0.3rem;
padding: 1.1rem 1.25rem;
background-color: $tilebg4;
border: 1px solid $overline;
border-radius: 0.75rem;

.stats-tile-value {
font-size: 2rem;
font-weight: 800;
line-height: 1.1;
color: black;
}
.stats-tile-label {
font-size: 0.85rem;
line-height: 1.35;
color: $tiletext2;
}
}

// Only tiles that lead somewhere get the affordance
a.stats-tile {
text-decoration: none;
transition: all 200ms ease-in-out;

&:hover,
&:focus-visible {
background-color: $tilebg;
border-color: $tilebg2;
transform: scale(1.02);

.stats-tile-value {
color: black;
}
.stats-tile-label {
color: $tiletext;
}
}
}

.stats-contributors {
display: grid;
gap: 0.15rem;
margin: 1.5rem 0;
padding: 0;
list-style: none;

a {
display: grid;
grid-template-columns: 2.5rem minmax(7rem, 11rem) 1fr auto;
align-items: center;
gap: 0.85rem;
padding: 0.35rem 0.5rem;
border-radius: 0.5rem;
color: $tiletext;
text-decoration: none;
transition: all 200ms ease-in-out;

&:hover {
color: black;
background-color: $tilebg4;
}
}
img {
width: 2.5rem;
height: 2.5rem;
border-radius: 50%;
}
.stats-contributor-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
// Thin recessive track, value anchored to the baseline with 4px ends
.stats-contributor-bar {
height: 0.5rem;
background-color: $tilebg;
border-radius: 0.25rem;

> span {
display: block;
min-width: 0.15rem;
height: 100%;
// A hue rather than a dark neutral, so extensions that invert the page keep the bar visible
background-color: $scversedeepblue;
border-radius: 0.25rem;
}
}
.stats-contributor-count {
font-size: 0.85rem;
color: $greydesc;
font-variant-numeric: tabular-nums;
}

@media (max-width: 50rem) {
a {
grid-template-columns: 2.5rem 1fr auto;
}
.stats-contributor-bar {
display: none;
}
}
}

.stats-rest {
summary {
width: fit-content;
padding: 0.35rem 0.5rem;
font-size: 0.9rem;
color: $tiletext2;
border-radius: 0.5rem;
cursor: pointer;
transition: all 200ms ease-in-out;

&:hover {
color: black;
background-color: $tilebg4;
}
}
&[open] summary {
margin-bottom: 0.5rem;
}
}
}

// Specific to /tutorials

#tutorials-content {
Expand Down
4 changes: 4 additions & 0 deletions content/stats/_index.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
+++
title = "scverse in numbers"
preface = "How the scverse community and its packages are doing. The numbers cover the core packages and the scverse GitHub organisation, and are refreshed twice a week by <a href='https://github.com/scverse/stats'>scverse/stats</a>."
+++
3 changes: 3 additions & 0 deletions layouts/partials/header.html
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,6 +118,9 @@
<li>
<a class="dropdown-item" href="/about/ai_policy">AI Policy</a>
</li>
<li>
<a class="dropdown-item" href="/stats">Statistics</a>
</li>
<li>
<a class="dropdown-item" href="/design">Media kit</a>
</li>
Expand Down
12 changes: 12 additions & 0 deletions layouts/partials/stats-contributor.html
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
{{ $person := .person }}
{{ $most := .most }}
<li>
<a href="{{ $person.html_url }}" target="_blank" rel="noopener">
<img src="{{ $person.avatar_url }}&s=96" alt="" loading="lazy" width="40" height="40" />
<span class="stats-contributor-name">{{ $person.name | default $person.login }}</span>
<span class="stats-contributor-bar" aria-hidden="true">
<span style="width: {{ math.Round (div (mul (float $person.contributions) 100.0) (float $most)) }}%"></span>
</span>
<span class="stats-contributor-count">{{ lang.FormatNumberCustom 0 $person.contributions }}</span>
</a>
</li>
121 changes: 121 additions & 0 deletions layouts/stats/list.html
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
{{ define "main" }}
<h1>{{ .Title }}</h1>
<article class="post">
<div class="post-content" id="stats-content">
{{ $base := "https://scverse.org/stats/" }}
{{ $stats := transform.Unmarshal (resources.GetRemote (printf "%sstats.json" $base)).Content }}
{{ $contributors := transform.Unmarshal (resources.GetRemote (printf "%scontributors.json" $base)).Content }}
{{ $gh := $stats.github }}


<p>{{ .Params.preface | safeHTML }}</p>

<p class="stats-updated">
Updated
<time datetime="{{ $stats.timestamp }}">{{ (time $stats.timestamp).Format "2 January 2006" }}</time>
</p>

{{/* The one number the page leads with. Everything else is a tile. */}}
<section class="stats-hero">
<div class="stats-hero-figure">
<span class="stats-hero-value">{{ printf "%.1fM" (div (float $stats.pepy_downloads) 1000000.0) }}</span>
<span class="stats-hero-label">downloads of the core packages, all time</span>
<span class="stats-hero-hint">
{{ lang.FormatNumberCustom 0 $stats.pepy_avg_daily_30 }} a day on average over the last 30 days
</span>
</div>
{{ with $stats.citation_count }}
<div class="stats-hero-figure stats-hero-figure--second">
<span class="stats-hero-value">{{ lang.FormatNumberCustom 0 . }}</span>
<span class="stats-hero-label">citations of scverse papers</span>
</div>
{{ end }}
</section>

{{/* A tile only gets a link where the destination genuinely shows the same number. */}}
{{/* LinkedIn is scraped upstream, so any given run may not have it. */}}
{{ $social := slice
(dict "value" $stats.zulip_users "label" "Zulip members" "url" "https://scverse.zulipchat.com/")
(dict "value" $stats.bluesky_followers "label" "Bluesky followers" "url" "https://bsky.app/profile/scverse.bsky.social")
}}
{{ with $stats.linkedin_followers }}
{{ $social = $social | append (dict "value" . "label" "LinkedIn followers" "url" "https://www.linkedin.com/company/scverse/") }}
{{ end }}

{{ $groups := slice
(dict
"name" "Community"
"tiles" ($social | append
(dict "value" $gh.unique_contributors "label" "code contributors" "url" "#contributors")
(dict "value" $gh.organization_members "label" "GitHub organisation members" "url" "https://github.com/orgs/scverse/people")
(dict "value" $stats.core_team_size "label" "core team members" "url" "/people")
)
)
(dict
"name" "Packages and code"
"tiles" (slice
(dict "value" $stats.ecosystem_packages "label" "ecosystem packages" "url" "/packages#ecosystem")
(dict "value" $gh.total_repositories "label" "core repositories" "url" "/packages#core-packages")
(dict "value" $gh.total_stars "label" "GitHub stars" "url" "https://github.com/scverse")
(dict "value" $gh.total_pull_requests_last_year "label" "pull requests in the last year")
(dict "value" $gh.total_pull_requests_closed "label" "pull requests closed, all time")
(dict "value" $gh.total_issues_open "label" "issues open")
(dict "value" $gh.total_issues_closed "label" "issues closed")
)
)
}}

{{ range $groups }}
<h2>{{ .name }}</h2>
<div class="stats-tiles">
{{ range .tiles }}
{{ if .url }}
<a
class="stats-tile"
href="{{ .url }}"
{{ if hasPrefix .url "http" }}target="_blank" rel="noopener"{{ end }}
>
<span class="stats-tile-value">{{ lang.FormatNumberCustom 0 .value }}</span>
<span class="stats-tile-label">{{ .label }}</span>
</a>
{{ else }}
<div class="stats-tile">
<span class="stats-tile-value">{{ lang.FormatNumberCustom 0 .value }}</span>
<span class="stats-tile-label">{{ .label }}</span>
</div>
{{ end }}
{{ end }}
</div>
{{ end }}


<h2 id="contributors">Top contributors</h2>
<p>
{{ lang.FormatNumberCustom 0 $contributors.total_contributors }} people have contributed code to the core
packages, ranked here by number of commits.
</p>

{{ $shown := 24 }}
{{ $top := first $shown $contributors.contributors }}
{{ $rest := after $shown $contributors.contributors }}
{{ $most := (index $top 0).contributions }}
<ol class="stats-contributors">
{{ range $top }}
{{ partial "stats-contributor.html" (dict "person" . "most" $most) }}
{{ end }}
</ol>

{{ with $rest }}
<details class="stats-rest">
<summary>Show the other {{ lang.FormatNumberCustom 0 (len .) }} contributors</summary>
<ol class="stats-contributors" start="{{ add $shown 1 }}">
{{ range . }}
{{ partial "stats-contributor.html" (dict "person" . "most" $most) }}
{{ end }}
</ol>
</details>
{{ end }}

</div>
</article>
{{ end }}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 187 additions & 0 deletions assets/main.scss
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@ $spatialdatablue: #40a9ff;
$squidpyviolet: #969dea;
$scirpypurple: #da347f;
$scviyellow: #fbb822;
$scversedeepblue: #262fb5; // the darker end of the Join button gradient

$github-black: #171b21;
$twitter-blue: #469be5;
Expand DownExpand Up@@ -1032,6 +1033,192 @@ body {
border-top: 0px;
}

// Specific to /stats

#stats-content {
.stats-updated {
font-size: 0.9rem;
color: $greydesc;
}

// The one number the page leads with
.stats-hero {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 1rem 3.5rem;
margin: 2.5rem 0 1rem;

.stats-hero-figure {
display: flex;
flex-direction: column;
gap: 0.35rem;
}
.stats-hero-figure--second {
padding-left: 3.5rem;
border-left: 1px solid $overline;
}
@media (max-width: 50rem) {
.stats-hero-figure--second {
padding-left: 0;
border-left: 0;
}
}

.stats-hero-value {
font-size: 4.5rem;
font-weight: 800;
line-height: 1;
color: black;
}
.stats-hero-label {
font-size: 1.15rem;
color: $tiletext;
}
.stats-hero-hint {
font-size: 0.9rem;
color: $greydesc;
}
@media (max-width: 50rem) {
.stats-hero-value {
font-size: 3rem;
}
}
}

.stats-tiles {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
gap: 0.75rem;
}

.stats-tile {
display: flex;
flex-direction: column;
gap: 0.3rem;
padding: 1.1rem 1.25rem;
background-color: $tilebg4;
border: 1px solid $overline;
border-radius: 0.75rem;

.stats-tile-value {
font-size: 2rem;
font-weight: 800;
line-height: 1.1;
color: black;
}
.stats-tile-label {
font-size: 0.85rem;
line-height: 1.35;
color: $tiletext2;
}
}

// Only tiles that lead somewhere get the affordance
a.stats-tile {
text-decoration: none;
transition: all 200ms ease-in-out;

&:hover,
&:focus-visible {
background-color: $tilebg;
border-color: $tilebg2;
transform: scale(1.02);

.stats-tile-value {
color: black;
}
.stats-tile-label {
color: $tiletext;
}
}
}

.stats-contributors {
display: grid;
gap: 0.15rem;
margin: 1.5rem 0;
padding: 0;
list-style: none;

a {
display: grid;
grid-template-columns: 2.5rem minmax(7rem, 11rem) 1fr auto;
align-items: center;
gap: 0.85rem;
padding: 0.35rem 0.5rem;
border-radius: 0.5rem;
color: $tiletext;
text-decoration: none;
transition: all 200ms ease-in-out;

&:hover {
color: black;
background-color: $tilebg4;
}
}
img {
width: 2.5rem;
height: 2.5rem;
border-radius: 50%;
}
.stats-contributor-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
// Thin recessive track, value anchored to the baseline with 4px ends
.stats-contributor-bar {
height: 0.5rem;
background-color: $tilebg;
border-radius: 0.25rem;

> span {
display: block;
min-width: 0.15rem;
height: 100%;
// A hue rather than a dark neutral, so extensions that invert the page keep the bar visible
background-color: $scversedeepblue;
border-radius: 0.25rem;
}
}
.stats-contributor-count {
font-size: 0.85rem;
color: $greydesc;
font-variant-numeric: tabular-nums;
}

@media (max-width: 50rem) {
a {
grid-template-columns: 2.5rem 1fr auto;
}
.stats-contributor-bar {
display: none;
}
}
}

.stats-rest {
summary {
width: fit-content;
padding: 0.35rem 0.5rem;
font-size: 0.9rem;
color: $tiletext2;
border-radius: 0.5rem;
cursor: pointer;
transition: all 200ms ease-in-out;

&:hover {
color: black;
background-color: $tilebg4;
}
}
&[open] summary {
margin-bottom: 0.5rem;
}
}
}

// Specific to /tutorials

#tutorials-content {
Expand Down
4 changes: 4 additions & 0 deletions content/stats/_index.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
+++
title = "scverse in numbers"
preface = "How the scverse community and its packages are doing. The numbers cover the core packages and the scverse GitHub organisation, and are refreshed twice a week by <a href='https://github.com/scverse/stats'>scverse/stats</a>."
+++
3 changes: 3 additions & 0 deletions layouts/partials/header.html
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,6 +118,9 @@
<li>
<a class="dropdown-item" href="/about/ai_policy">AI Policy</a>
</li>
<li>
<a class="dropdown-item" href="/stats">Statistics</a>
</li>
<li>
<a class="dropdown-item" href="/design">Media kit</a>
</li>
Expand Down
12 changes: 12 additions & 0 deletions layouts/partials/stats-contributor.html
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
{{ $person := .person }}
{{ $most := .most }}
<li>
<a href="{{ $person.html_url }}" target="_blank" rel="noopener">
<img src="{{ $person.avatar_url }}&s=96" alt="" loading="lazy" width="40" height="40" />
<span class="stats-contributor-name">{{ $person.name | default $person.login }}</span>
<span class="stats-contributor-bar" aria-hidden="true">
<span style="width: {{ math.Round (div (mul (float $person.contributions) 100.0) (float $most)) }}%"></span>
</span>
<span class="stats-contributor-count">{{ lang.FormatNumberCustom 0 $person.contributions }}</span>
</a>
</li>
121 changes: 121 additions & 0 deletions layouts/stats/list.html
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
{{ define "main" }}
<h1>{{ .Title }}</h1>
<article class="post">
<div class="post-content" id="stats-content">
{{ $base := "https://scverse.org/stats/" }}
{{ $stats := transform.Unmarshal (resources.GetRemote (printf "%sstats.json" $base)).Content }}
{{ $contributors := transform.Unmarshal (resources.GetRemote (printf "%scontributors.json" $base)).Content }}
{{ $gh := $stats.github }}


<p>{{ .Params.preface | safeHTML }}</p>

<p class="stats-updated">
Updated
<time datetime="{{ $stats.timestamp }}">{{ (time $stats.timestamp).Format "2 January 2006" }}</time>
</p>

{{/* The one number the page leads with. Everything else is a tile. */}}
<section class="stats-hero">
<div class="stats-hero-figure">
<span class="stats-hero-value">{{ printf "%.1fM" (div (float $stats.pepy_downloads) 1000000.0) }}</span>
<span class="stats-hero-label">downloads of the core packages, all time</span>
<span class="stats-hero-hint">
{{ lang.FormatNumberCustom 0 $stats.pepy_avg_daily_30 }} a day on average over the last 30 days
</span>
</div>
{{ with $stats.citation_count }}
<div class="stats-hero-figure stats-hero-figure--second">
<span class="stats-hero-value">{{ lang.FormatNumberCustom 0 . }}</span>
<span class="stats-hero-label">citations of scverse papers</span>
</div>
{{ end }}
</section>

{{/* A tile only gets a link where the destination genuinely shows the same number. */}}
{{/* LinkedIn is scraped upstream, so any given run may not have it. */}}
{{ $social := slice
(dict "value" $stats.zulip_users "label" "Zulip members" "url" "https://scverse.zulipchat.com/")
(dict "value" $stats.bluesky_followers "label" "Bluesky followers" "url" "https://bsky.app/profile/scverse.bsky.social")
}}
{{ with $stats.linkedin_followers }}
{{ $social = $social | append (dict "value" . "label" "LinkedIn followers" "url" "https://www.linkedin.com/company/scverse/") }}
{{ end }}

{{ $groups := slice
(dict
"name" "Community"
"tiles" ($social | append
(dict "value" $gh.unique_contributors "label" "code contributors" "url" "#contributors")
(dict "value" $gh.organization_members "label" "GitHub organisation members" "url" "https://github.com/orgs/scverse/people")
(dict "value" $stats.core_team_size "label" "core team members" "url" "/people")
)
)
(dict
"name" "Packages and code"
"tiles" (slice
(dict "value" $stats.ecosystem_packages "label" "ecosystem packages" "url" "/packages#ecosystem")
(dict "value" $gh.total_repositories "label" "core repositories" "url" "/packages#core-packages")
(dict "value" $gh.total_stars "label" "GitHub stars" "url" "https://github.com/scverse")
(dict "value" $gh.total_pull_requests_last_year "label" "pull requests in the last year")
(dict "value" $gh.total_pull_requests_closed "label" "pull requests closed, all time")
(dict "value" $gh.total_issues_open "label" "issues open")
(dict "value" $gh.total_issues_closed "label" "issues closed")
)
)
}}

{{ range $groups }}
<h2>{{ .name }}</h2>
<div class="stats-tiles">
{{ range .tiles }}
{{ if .url }}
<a
class="stats-tile"
href="{{ .url }}"
{{ if hasPrefix .url "http" }}target="_blank" rel="noopener"{{ end }}
>
<span class="stats-tile-value">{{ lang.FormatNumberCustom 0 .value }}</span>
<span class="stats-tile-label">{{ .label }}</span>
</a>
{{ else }}
<div class="stats-tile">
<span class="stats-tile-value">{{ lang.FormatNumberCustom 0 .value }}</span>
<span class="stats-tile-label">{{ .label }}</span>
</div>
{{ end }}
{{ end }}
</div>
{{ end }}


<h2 id="contributors">Top contributors</h2>
<p>
{{ lang.FormatNumberCustom 0 $contributors.total_contributors }} people have contributed code to the core
packages, ranked here by number of commits.
</p>

{{ $shown := 24 }}
{{ $top := first $shown $contributors.contributors }}
{{ $rest := after $shown $contributors.contributors }}
{{ $most := (index $top 0).contributions }}
<ol class="stats-contributors">
{{ range $top }}
{{ partial "stats-contributor.html" (dict "person" . "most" $most) }}
{{ end }}
</ol>

{{ with $rest }}
<details class="stats-rest">
<summary>Show the other {{ lang.FormatNumberCustom 0 (len .) }} contributors</summary>
<ol class="stats-contributors" start="{{ add $shown 1 }}">
{{ range . }}
{{ partial "stats-contributor.html" (dict "person" . "most" $most) }}
{{ end }}
</ol>
</details>
{{ end }}

</div>
</article>
{{ end }}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 187 additions & 0 deletions assets/main.scss
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@ $spatialdatablue: #40a9ff;
$squidpyviolet: #969dea;
$scirpypurple: #da347f;
$scviyellow: #fbb822;
$scversedeepblue: #262fb5; // the darker end of the Join button gradient

$github-black: #171b21;
$twitter-blue: #469be5;
Expand DownExpand Up@@ -1032,6 +1033,192 @@ body {
border-top: 0px;
}

// Specific to /stats

#stats-content {
.stats-updated {
font-size: 0.9rem;
color: $greydesc;
}

// The one number the page leads with
.stats-hero {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 1rem 3.5rem;
margin: 2.5rem 0 1rem;

.stats-hero-figure {
display: flex;
flex-direction: column;
gap: 0.35rem;
}
.stats-hero-figure--second {
padding-left: 3.5rem;
border-left: 1px solid $overline;
}
@media (max-width: 50rem) {
.stats-hero-figure--second {
padding-left: 0;
border-left: 0;
}
}

.stats-hero-value {
font-size: 4.5rem;
font-weight: 800;
line-height: 1;
color: black;
}
.stats-hero-label {
font-size: 1.15rem;
color: $tiletext;
}
.stats-hero-hint {
font-size: 0.9rem;
color: $greydesc;
}
@media (max-width: 50rem) {
.stats-hero-value {
font-size: 3rem;
}
}
}

.stats-tiles {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
gap: 0.75rem;
}

.stats-tile {
display: flex;
flex-direction: column;
gap: 0.3rem;
padding: 1.1rem 1.25rem;
background-color: $tilebg4;
border: 1px solid $overline;
border-radius: 0.75rem;

.stats-tile-value {
font-size: 2rem;
font-weight: 800;
line-height: 1.1;
color: black;
}
.stats-tile-label {
font-size: 0.85rem;
line-height: 1.35;
color: $tiletext2;
}
}

// Only tiles that lead somewhere get the affordance
a.stats-tile {
text-decoration: none;
transition: all 200ms ease-in-out;

&:hover,
&:focus-visible {
background-color: $tilebg;
border-color: $tilebg2;
transform: scale(1.02);

.stats-tile-value {
color: black;
}
.stats-tile-label {
color: $tiletext;
}
}
}

.stats-contributors {
display: grid;
gap: 0.15rem;
margin: 1.5rem 0;
padding: 0;
list-style: none;

a {
display: grid;
grid-template-columns: 2.5rem minmax(7rem, 11rem) 1fr auto;
align-items: center;
gap: 0.85rem;
padding: 0.35rem 0.5rem;
border-radius: 0.5rem;
color: $tiletext;
text-decoration: none;
transition: all 200ms ease-in-out;

&:hover {
color: black;
background-color: $tilebg4;
}
}
img {
width: 2.5rem;
height: 2.5rem;
border-radius: 50%;
}
.stats-contributor-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
// Thin recessive track, value anchored to the baseline with 4px ends
.stats-contributor-bar {
height: 0.5rem;
background-color: $tilebg;
border-radius: 0.25rem;

> span {
display: block;
min-width: 0.15rem;
height: 100%;
// A hue rather than a dark neutral, so extensions that invert the page keep the bar visible
background-color: $scversedeepblue;
border-radius: 0.25rem;
}
}
.stats-contributor-count {
font-size: 0.85rem;
color: $greydesc;
font-variant-numeric: tabular-nums;
}

@media (max-width: 50rem) {
a {
grid-template-columns: 2.5rem 1fr auto;
}
.stats-contributor-bar {
display: none;
}
}
}

.stats-rest {
summary {
width: fit-content;
padding: 0.35rem 0.5rem;
font-size: 0.9rem;
color: $tiletext2;
border-radius: 0.5rem;
cursor: pointer;
transition: all 200ms ease-in-out;

&:hover {
color: black;
background-color: $tilebg4;
}
}
&[open] summary {
margin-bottom: 0.5rem;
}
}
}

// Specific to /tutorials

#tutorials-content {
Expand Down
4 changes: 4 additions & 0 deletions content/stats/_index.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
+++
title = "scverse in numbers"
preface = "How the scverse community and its packages are doing. The numbers cover the core packages and the scverse GitHub organisation, and are refreshed twice a week by <a href='https://github.com/scverse/stats'>scverse/stats</a>."
+++
3 changes: 3 additions & 0 deletions layouts/partials/header.html
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,6 +118,9 @@
<li>
<a class="dropdown-item" href="/about/ai_policy">AI Policy</a>
</li>
<li>
<a class="dropdown-item" href="/stats">Statistics</a>
</li>
<li>
<a class="dropdown-item" href="/design">Media kit</a>
</li>
Expand Down
12 changes: 12 additions & 0 deletions layouts/partials/stats-contributor.html
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
{{ $person := .person }}
{{ $most := .most }}
<li>
<a href="{{ $person.html_url }}" target="_blank" rel="noopener">
<img src="{{ $person.avatar_url }}&s=96" alt="" loading="lazy" width="40" height="40" />
<span class="stats-contributor-name">{{ $person.name | default $person.login }}</span>
<span class="stats-contributor-bar" aria-hidden="true">
<span style="width: {{ math.Round (div (mul (float $person.contributions) 100.0) (float $most)) }}%"></span>
</span>
<span class="stats-contributor-count">{{ lang.FormatNumberCustom 0 $person.contributions }}</span>
</a>
</li>
121 changes: 121 additions & 0 deletions layouts/stats/list.html
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
{{ define "main" }}
<h1>{{ .Title }}</h1>
<article class="post">
<div class="post-content" id="stats-content">
{{ $base := "https://scverse.org/stats/" }}
{{ $stats := transform.Unmarshal (resources.GetRemote (printf "%sstats.json" $base)).Content }}
{{ $contributors := transform.Unmarshal (resources.GetRemote (printf "%scontributors.json" $base)).Content }}
{{ $gh := $stats.github }}


<p>{{ .Params.preface | safeHTML }}</p>

<p class="stats-updated">
Updated
<time datetime="{{ $stats.timestamp }}">{{ (time $stats.timestamp).Format "2 January 2006" }}</time>
</p>

{{/* The one number the page leads with. Everything else is a tile. */}}
<section class="stats-hero">
<div class="stats-hero-figure">
<span class="stats-hero-value">{{ printf "%.1fM" (div (float $stats.pepy_downloads) 1000000.0) }}</span>
<span class="stats-hero-label">downloads of the core packages, all time</span>
<span class="stats-hero-hint">
{{ lang.FormatNumberCustom 0 $stats.pepy_avg_daily_30 }} a day on average over the last 30 days
</span>
</div>
{{ with $stats.citation_count }}
<div class="stats-hero-figure stats-hero-figure--second">
<span class="stats-hero-value">{{ lang.FormatNumberCustom 0 . }}</span>
<span class="stats-hero-label">citations of scverse papers</span>
</div>
{{ end }}
</section>

{{/* A tile only gets a link where the destination genuinely shows the same number. */}}
{{/* LinkedIn is scraped upstream, so any given run may not have it. */}}
{{ $social := slice
(dict "value" $stats.zulip_users "label" "Zulip members" "url" "https://scverse.zulipchat.com/")
(dict "value" $stats.bluesky_followers "label" "Bluesky followers" "url" "https://bsky.app/profile/scverse.bsky.social")
}}
{{ with $stats.linkedin_followers }}
{{ $social = $social | append (dict "value" . "label" "LinkedIn followers" "url" "https://www.linkedin.com/company/scverse/") }}
{{ end }}

{{ $groups := slice
(dict
"name" "Community"
"tiles" ($social | append
(dict "value" $gh.unique_contributors "label" "code contributors" "url" "#contributors")
(dict "value" $gh.organization_members "label" "GitHub organisation members" "url" "https://github.com/orgs/scverse/people")
(dict "value" $stats.core_team_size "label" "core team members" "url" "/people")
)
)
(dict
"name" "Packages and code"
"tiles" (slice
(dict "value" $stats.ecosystem_packages "label" "ecosystem packages" "url" "/packages#ecosystem")
(dict "value" $gh.total_repositories "label" "core repositories" "url" "/packages#core-packages")
(dict "value" $gh.total_stars "label" "GitHub stars" "url" "https://github.com/scverse")
(dict "value" $gh.total_pull_requests_last_year "label" "pull requests in the last year")
(dict "value" $gh.total_pull_requests_closed "label" "pull requests closed, all time")
(dict "value" $gh.total_issues_open "label" "issues open")
(dict "value" $gh.total_issues_closed "label" "issues closed")
)
)
}}

{{ range $groups }}
<h2>{{ .name }}</h2>
<div class="stats-tiles">
{{ range .tiles }}
{{ if .url }}
<a
class="stats-tile"
href="{{ .url }}"
{{ if hasPrefix .url "http" }}target="_blank" rel="noopener"{{ end }}
>
<span class="stats-tile-value">{{ lang.FormatNumberCustom 0 .value }}</span>
<span class="stats-tile-label">{{ .label }}</span>
</a>
{{ else }}
<div class="stats-tile">
<span class="stats-tile-value">{{ lang.FormatNumberCustom 0 .value }}</span>
<span class="stats-tile-label">{{ .label }}</span>
</div>
{{ end }}
{{ end }}
</div>
{{ end }}


<h2 id="contributors">Top contributors</h2>
<p>
{{ lang.FormatNumberCustom 0 $contributors.total_contributors }} people have contributed code to the core
packages, ranked here by number of commits.
</p>

{{ $shown := 24 }}
{{ $top := first $shown $contributors.contributors }}
{{ $rest := after $shown $contributors.contributors }}
{{ $most := (index $top 0).contributions }}
<ol class="stats-contributors">
{{ range $top }}
{{ partial "stats-contributor.html" (dict "person" . "most" $most) }}
{{ end }}
</ol>

{{ with $rest }}
<details class="stats-rest">
<summary>Show the other {{ lang.FormatNumberCustom 0 (len .) }} contributors</summary>
<ol class="stats-contributors" start="{{ add $shown 1 }}">
{{ range . }}
{{ partial "stats-contributor.html" (dict "person" . "most" $most) }}
{{ end }}
</ol>
</details>
{{ end }}

</div>
</article>
{{ end }}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 187 additions & 0 deletions assets/main.scss
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@ $spatialdatablue: #40a9ff;
$squidpyviolet: #969dea;
$scirpypurple: #da347f;
$scviyellow: #fbb822;
$scversedeepblue: #262fb5; // the darker end of the Join button gradient

$github-black: #171b21;
$twitter-blue: #469be5;
Expand DownExpand Up@@ -1032,6 +1033,192 @@ body {
border-top: 0px;
}

// Specific to /stats

#stats-content {
.stats-updated {
font-size: 0.9rem;
color: $greydesc;
}

// The one number the page leads with
.stats-hero {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 1rem 3.5rem;
margin: 2.5rem 0 1rem;

.stats-hero-figure {
display: flex;
flex-direction: column;
gap: 0.35rem;
}
.stats-hero-figure--second {
padding-left: 3.5rem;
border-left: 1px solid $overline;
}
@media (max-width: 50rem) {
.stats-hero-figure--second {
padding-left: 0;
border-left: 0;
}
}

.stats-hero-value {
font-size: 4.5rem;
font-weight: 800;
line-height: 1;
color: black;
}
.stats-hero-label {
font-size: 1.15rem;
color: $tiletext;
}
.stats-hero-hint {
font-size: 0.9rem;
color: $greydesc;
}
@media (max-width: 50rem) {
.stats-hero-value {
font-size: 3rem;
}
}
}

.stats-tiles {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
gap: 0.75rem;
}

.stats-tile {
display: flex;
flex-direction: column;
gap: 0.3rem;
padding: 1.1rem 1.25rem;
background-color: $tilebg4;
border: 1px solid $overline;
border-radius: 0.75rem;

.stats-tile-value {
font-size: 2rem;
font-weight: 800;
line-height: 1.1;
color: black;
}
.stats-tile-label {
font-size: 0.85rem;
line-height: 1.35;
color: $tiletext2;
}
}

// Only tiles that lead somewhere get the affordance
a.stats-tile {
text-decoration: none;
transition: all 200ms ease-in-out;

&:hover,
&:focus-visible {
background-color: $tilebg;
border-color: $tilebg2;
transform: scale(1.02);

.stats-tile-value {
color: black;
}
.stats-tile-label {
color: $tiletext;
}
}
}

.stats-contributors {
display: grid;
gap: 0.15rem;
margin: 1.5rem 0;
padding: 0;
list-style: none;

a {
display: grid;
grid-template-columns: 2.5rem minmax(7rem, 11rem) 1fr auto;
align-items: center;
gap: 0.85rem;
padding: 0.35rem 0.5rem;
border-radius: 0.5rem;
color: $tiletext;
text-decoration: none;
transition: all 200ms ease-in-out;

&:hover {
color: black;
background-color: $tilebg4;
}
}
img {
width: 2.5rem;
height: 2.5rem;
border-radius: 50%;
}
.stats-contributor-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
// Thin recessive track, value anchored to the baseline with 4px ends
.stats-contributor-bar {
height: 0.5rem;
background-color: $tilebg;
border-radius: 0.25rem;

> span {
display: block;
min-width: 0.15rem;
height: 100%;
// A hue rather than a dark neutral, so extensions that invert the page keep the bar visible
background-color: $scversedeepblue;
border-radius: 0.25rem;
}
}
.stats-contributor-count {
font-size: 0.85rem;
color: $greydesc;
font-variant-numeric: tabular-nums;
}

@media (max-width: 50rem) {
a {
grid-template-columns: 2.5rem 1fr auto;
}
.stats-contributor-bar {
display: none;
}
}
}

.stats-rest {
summary {
width: fit-content;
padding: 0.35rem 0.5rem;
font-size: 0.9rem;
color: $tiletext2;
border-radius: 0.5rem;
cursor: pointer;
transition: all 200ms ease-in-out;

&:hover {
color: black;
background-color: $tilebg4;
}
}
&[open] summary {
margin-bottom: 0.5rem;
}
}
}

// Specific to /tutorials

#tutorials-content {
Expand Down
4 changes: 4 additions & 0 deletions content/stats/_index.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
+++
title = "scverse in numbers"
preface = "How the scverse community and its packages are doing. The numbers cover the core packages and the scverse GitHub organisation, and are refreshed twice a week by <a href='https://github.com/scverse/stats'>scverse/stats</a>."
+++
3 changes: 3 additions & 0 deletions layouts/partials/header.html
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,6 +118,9 @@
<li>
<a class="dropdown-item" href="/about/ai_policy">AI Policy</a>
</li>
<li>
<a class="dropdown-item" href="/stats">Statistics</a>
</li>
<li>
<a class="dropdown-item" href="/design">Media kit</a>
</li>
Expand Down
12 changes: 12 additions & 0 deletions layouts/partials/stats-contributor.html
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
{{ $person := .person }}
{{ $most := .most }}
<li>
<a href="{{ $person.html_url }}" target="_blank" rel="noopener">
<img src="{{ $person.avatar_url }}&s=96" alt="" loading="lazy" width="40" height="40" />
<span class="stats-contributor-name">{{ $person.name | default $person.login }}</span>
<span class="stats-contributor-bar" aria-hidden="true">
<span style="width: {{ math.Round (div (mul (float $person.contributions) 100.0) (float $most)) }}%"></span>
</span>
<span class="stats-contributor-count">{{ lang.FormatNumberCustom 0 $person.contributions }}</span>
</a>
</li>
121 changes: 121 additions & 0 deletions layouts/stats/list.html
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
{{ define "main" }}
<h1>{{ .Title }}</h1>
<article class="post">
<div class="post-content" id="stats-content">
{{ $base := "https://scverse.org/stats/" }}
{{ $stats := transform.Unmarshal (resources.GetRemote (printf "%sstats.json" $base)).Content }}
{{ $contributors := transform.Unmarshal (resources.GetRemote (printf "%scontributors.json" $base)).Content }}
{{ $gh := $stats.github }}


<p>{{ .Params.preface | safeHTML }}</p>

<p class="stats-updated">
Updated
<time datetime="{{ $stats.timestamp }}">{{ (time $stats.timestamp).Format "2 January 2006" }}</time>
</p>

{{/* The one number the page leads with. Everything else is a tile. */}}
<section class="stats-hero">
<div class="stats-hero-figure">
<span class="stats-hero-value">{{ printf "%.1fM" (div (float $stats.pepy_downloads) 1000000.0) }}</span>
<span class="stats-hero-label">downloads of the core packages, all time</span>
<span class="stats-hero-hint">
{{ lang.FormatNumberCustom 0 $stats.pepy_avg_daily_30 }} a day on average over the last 30 days
</span>
</div>
{{ with $stats.citation_count }}
<div class="stats-hero-figure stats-hero-figure--second">
<span class="stats-hero-value">{{ lang.FormatNumberCustom 0 . }}</span>
<span class="stats-hero-label">citations of scverse papers</span>
</div>
{{ end }}
</section>

{{/* A tile only gets a link where the destination genuinely shows the same number. */}}
{{/* LinkedIn is scraped upstream, so any given run may not have it. */}}
{{ $social := slice
(dict "value" $stats.zulip_users "label" "Zulip members" "url" "https://scverse.zulipchat.com/")
(dict "value" $stats.bluesky_followers "label" "Bluesky followers" "url" "https://bsky.app/profile/scverse.bsky.social")
}}
{{ with $stats.linkedin_followers }}
{{ $social = $social | append (dict "value" . "label" "LinkedIn followers" "url" "https://www.linkedin.com/company/scverse/") }}
{{ end }}

{{ $groups := slice
(dict
"name" "Community"
"tiles" ($social | append
(dict "value" $gh.unique_contributors "label" "code contributors" "url" "#contributors")
(dict "value" $gh.organization_members "label" "GitHub organisation members" "url" "https://github.com/orgs/scverse/people")
(dict "value" $stats.core_team_size "label" "core team members" "url" "/people")
)
)
(dict
"name" "Packages and code"
"tiles" (slice
(dict "value" $stats.ecosystem_packages "label" "ecosystem packages" "url" "/packages#ecosystem")
(dict "value" $gh.total_repositories "label" "core repositories" "url" "/packages#core-packages")
(dict "value" $gh.total_stars "label" "GitHub stars" "url" "https://github.com/scverse")
(dict "value" $gh.total_pull_requests_last_year "label" "pull requests in the last year")
(dict "value" $gh.total_pull_requests_closed "label" "pull requests closed, all time")
(dict "value" $gh.total_issues_open "label" "issues open")
(dict "value" $gh.total_issues_closed "label" "issues closed")
)
)
}}

{{ range $groups }}
<h2>{{ .name }}</h2>
<div class="stats-tiles">
{{ range .tiles }}
{{ if .url }}
<a
class="stats-tile"
href="{{ .url }}"
{{ if hasPrefix .url "http" }}target="_blank" rel="noopener"{{ end }}
>
<span class="stats-tile-value">{{ lang.FormatNumberCustom 0 .value }}</span>
<span class="stats-tile-label">{{ .label }}</span>
</a>
{{ else }}
<div class="stats-tile">
<span class="stats-tile-value">{{ lang.FormatNumberCustom 0 .value }}</span>
<span class="stats-tile-label">{{ .label }}</span>
</div>
{{ end }}
{{ end }}
</div>
{{ end }}


<h2 id="contributors">Top contributors</h2>
<p>
{{ lang.FormatNumberCustom 0 $contributors.total_contributors }} people have contributed code to the core
packages, ranked here by number of commits.
</p>

{{ $shown := 24 }}
{{ $top := first $shown $contributors.contributors }}
{{ $rest := after $shown $contributors.contributors }}
{{ $most := (index $top 0).contributions }}
<ol class="stats-contributors">
{{ range $top }}
{{ partial "stats-contributor.html" (dict "person" . "most" $most) }}
{{ end }}
</ol>

{{ with $rest }}
<details class="stats-rest">
<summary>Show the other {{ lang.FormatNumberCustom 0 (len .) }} contributors</summary>
<ol class="stats-contributors" start="{{ add $shown 1 }}">
{{ range . }}
{{ partial "stats-contributor.html" (dict "person" . "most" $most) }}
{{ end }}
</ol>
</details>
{{ end }}

</div>
</article>
{{ end }}