Skip to content
187 changes: 187 additions & 0 deletions assets/main.scss
Original file line number Diff line number Diff 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 Down Expand Up @@ -1321,6 +1322,192 @@ body {
}
}

// 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 number Diff line number Diff 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 number Diff line number Diff line change
Expand Up @@ -127,6 +127,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 number Diff line number Diff 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>
2 changes: 1 addition & 1 deletion layouts/people/list.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
{{ $packages = sort $packages }}

{{ $contributors := slice }}
{{ with resources.GetRemote "https://scverse.org/stats/contributors.json" }}
{{ with (try (resources.GetRemote "https://scverse-stats-dev.s3.eu-west-1.amazonaws.com/contributors.json")).Value }}
{{ $contributors = (. | transform.Unmarshal).contributors | default slice }}
{{ end }}

Expand Down
2 changes: 1 addition & 1 deletion layouts/shortcodes/stats.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
{{ $pkgs = transform.Unmarshal .Content }}
{{ end }}
{{ $contributors := slice }}
{{ with resources.GetRemote "https://scverse.org/stats/contributors.json" }}
{{ with (try (resources.GetRemote "https://scverse-stats-dev.s3.eu-west-1.amazonaws.com/contributors.json")).Value }}
{{ $contributors = (. | transform.Unmarshal).contributors | default slice }}
{{ end }}

Expand Down
132 changes: 132 additions & 0 deletions layouts/stats/list.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
{{ define "main" }}
<h1>{{ .Title }}</h1>
<article class="post">
<div class="post-content" id="stats-content">
{{ $base := "https://scverse-stats-dev.s3.eu-west-1.amazonaws.com/" }}
{{ $stats := dict }}
{{ $contributors := dict }}
{{ range $name := slice "stats" "contributors" }}
{{ $url := printf "%s%s.json" $base $name }}
{{ $res := try (resources.GetRemote $url) }}
{{ if or $res.Err (not $res.Value) }}
{{ errorf "%s is unreachable. scverse/stats publishes it; see scverse/stats#13." $url }}
{{ else if eq $name "stats" }}
{{ $stats = transform.Unmarshal $res.Value.Content }}
{{ else }}
{{ $contributors = transform.Unmarshal $res.Value.Content }}
{{ end }}
{{ end }}
{{ $gh := $stats.github | default dict }}


<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 }}