add PR #3805 saga, unbury script, contributor decay diagram, looping SVG animations, CLAUDE.md style guide

expand "our" section: real Pylons org stats (28 lifetime, 4 active), PR rejection & pivot to downstream migration of 33 repos, BDFL commentary. embed unbury.py source & downloadable file. new contributor-decay diagram. update load-bearing diagram with org-wide data. fix #333333 text on gold nodes. convert all 8 SVG animations from one-shot to infinite loop (13s hold, fade out, restart). add unturf SVG diagram style guide to CLAUDE.md.
This commit is contained in:
russell@unturf.com 2026-03-04 14:22:00 -05:00
parent 66f7f3d9d3
commit cf92f1f613
13 changed files with 938 additions and 212 deletions

View file

@ -221,6 +221,65 @@ venv/bin/python lib/tag_stats.py 5 # only tags with 5+ posts
- Tags should be capitalized (e.g., "Python" not "python")
- Avoid single-use tags; consolidate into broader categories
## UNTURF SVG DIAGRAM STYLE GUIDE
**All diagrams on russell.ballestrini.net & permacomputer sites follow this style.**
### Color Palette (dark/light theme compatible)
- **Background**: always `transparent` (never white or black)
- **Node fills**: medium-dark saturated colors with `fill="white"` text
- Blues: `#4a90d9`, `#1e88e5`, `#0d47a1`, `#1a237e`
- Purples: `#9370db`, `#7b68ee`, `#6c5ce7`, `#4a148c`, `#ab47bc`
- Greens: `#7cb342`, `#1b5e20`, `#00897b`
- Teals: `#00acc1`, `#006064`
- Oranges: `#e65100`, `#e8834a`, `#ff6f00`
- Reds: `#bf360c`, `#b71c1c`, `#880e4f`
- Dark neutrals: `#2c3e50`, `#34495e`, `#546e7a`, `#616161`, `#4e342e`
- **Gold/yellow nodes** (`#f9a825`, `#fdd835`): use `fill="#1a1a1a"` text (NOT `#333333`)
- **Node strokes**: `#999999` (never black)
- **Edges/arrows**: `#aaaaaa` stroke & fill
- **Floating text labels**: `#cccccc` (cluster labels, "translates to", etc.)
- **Accent text**: `#ef5350` (red warnings/claims), `#ff6f00` (orange highlights), `#66ff66` (green positive)
- **Dashed cluster borders**: `stroke="#999999" stroke-dasharray="5,2"`
- **Plaintext labels** (no background): `fill="transparent"` polygon, text `fill="#cccccc"`
### SVG Dimensions
- Root SVG: `width="100%" height="100%"` with `viewBox` (never fixed pt/px)
- RST embed: `:width: 100%` and `:align: center`
### Animation Pattern (CSS only, no JS)
```xml
<defs>
<style type="text/css">
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.anim { opacity: 0; animation: fadeIn 0.8s ease-out forwards; }
.d0 { animation-delay: 0s; }
.d1 { animation-delay: 3s; }
/* ... increment by 2-3s per group */
</style>
</defs>
```
- Wrap each conceptual group in `<g class="anim dN">`
- Groups fade in sequentially (3s apart for major concepts, 1-2s for chain elements)
- Keep total animation under 30s
### Graphviz Dot Conventions
```dot
bgcolor="transparent"
node [fontname="Helvetica" fontsize=12 style="filled,rounded" shape=box color="#999999"]
edge [color="#AAAAAA" arrowsize=0.6 fontcolor="#AAAAAA"]
```
### Post-Processing Checklist
1. Strip `<title>` tags: `sed -i 's/<title>[^<]*<\/title>//g'` (prevents tooltip artifacts)
2. Replace fixed dimensions: `width="100%" height="100%"` keeping viewBox
3. Change cluster label fontcolor from `#aaaaaa` to `#cccccc`
4. Wrap groups with animation classes
5. Verify: no `fill="black"`, no `stroke="black"`, no `fill="#333333"`
## RUSSELL'S PROJECT TECH STACKS
**NEVER assume or guess what language/framework a project uses. Check the actual repo.**

View file

@ -38,7 +38,7 @@ our
"Our" claims ownership without proving it. "Our API handles 10,000 requests per second." Whose API? "Our documentation covers all edge cases." Whose documentation? Documentation never covers all edge cases. That sentence lies twice: once about coverage, once about possession.
"Our" creates false intimacy too. "Our community" implies you belong to something. You cloned a repo. You filed an issue. You didn't join a community. You used software. But "our community" wraps you in belonging whether you asked for it or not. Contributors stay few & far between. Look at `Pyramid <https://trypyramid.com/>`_. A web framework that needs maintainers. `Remarkbox <https://remarkbox.com>`_ & `MakePostSell <https://makepostsell.com>`_ run on it. `RhodeCode <https://rhodecode.com>`_ runs on it. Vital resources like `PyPI <https://pypi.org>`_ itself run on it. "Our framework" carries weight that maybe five active maintainers shoulder.
"Our" creates false intimacy too. "Our community" implies you belong to something. You cloned a repo. You filed an issue. You didn't join a community. You used software. But "our community" wraps you in belonging whether you asked for it or not. Contributors stay few & far between. Look at `Pyramid <https://trypyramid.com/>`_. A web framework that needs maintainers. `Remarkbox <https://remarkbox.com>`_ & `MakePostSell <https://makepostsell.com>`_ run on it. `RhodeCode <https://rhodecode.com>`_ runs on it. Vital resources like `PyPI <https://pypi.org>`_ itself run on it. "Our framework" carries weight that 4 humans shoulder.
.. image:: /uploads/2026/03/xkcd-2347-dependency.png
:alt: xkcd 2347 Dependency — a tower of blocks representing all modern digital infrastructure balanced on a single small block maintained by a random person in Nebraska
@ -51,13 +51,157 @@ our
|
.. image:: /uploads/2026/03/load-bearing.svg
:alt: diagram showing PyPI, RhodeCode, Remarkbox, MakePostSell, thousands of apps all depending on Pyramid with ~5 maintainers, Russell as unemployed volunteer load-bearing block, NASA shut down grants evaporate
:alt: diagram showing PyPI, RhodeCode, Remarkbox, MakePostSell, thousands of apps all depending on Pylons org with 98 repos, 28 lifetime contributors, 4 humans active last 3 months, Russell as only new contributor in the past year with PR 3805, NASA shut down grants evaporate
:align: center
:width: 100%
|
I stepped up to help maintain Pyramid. Unemployed. Because someone has to play `that xkcd 2347 role <https://xkcd.com/2347/>`_, a single load-bearing block propping up everything above it. "Our" framework means I volunteer so "we" can keep a foundation standing. Maybe eventually get paid. Maybe not. NASA just shut down. Federal grants evaporate. "Our" community runs on donated labor & borrowed time.
I wrote a script called ``unbury`` to exhume contributor stats from a graveyard of git repos. Run it against the `Pylons <https://github.com/Pylons>`_ org & the numbers speak for themselves:
.. code-block:: text
scanning 98 repos in ~/git/pylons
──────────────────────────────────────────────────
3 months: 5 unique contributors across 5 active repos
russell@unturf.com 1 repos
dependabot[bot] 1 repos
Michael Merickel 1 repos
Rémi Dubois 1 repos
Gael Pasgrimaud 1 repos
──────────────────────────────────────────────────
1 year: 7 unique contributors across 8 active repos
──────────────────────────────────────────────────
3 years: 15 unique contributors across 31 active repos
──────────────────────────────────────────────────
6 years: 19 unique contributors across 46 active repos
──────────────────────────────────────────────────
lifetime: 28 unique contributors across 97 active repos
Five contributors in 3 months. One of them a bot. I stepped up to help maintain Pyramid. Unemployed. Filed `PR #3805 <https://github.com/Pylons/pyramid/pull/3805>`_ to vendor ``pkg_resources`` & drop a setuptools runtime dependency. 310 lines vendored from setuptools 80.x. 67 new tests at 97.66% coverage. A ``pyramid.compat_resources`` public API so downstream packages could migrate without breaking asset overrides. Audited all 108 Pylons repos. Migrated 30 downstream packages. Ran every test suite. One reviewer approved. The maintainer who holds commit access shipped Pyramid 2.1 with a setuptools version constraint instead & went -1 on the approach. `Rejected <https://russell.ballestrini.net/tips-for-getting-pull-requests-approved/>`_.
So I closed the PR, opened a new terminal, & pivoted. If "we" won't vendor the fix upstream, "I" will do the lifting downstream. Migrated 33 repos in a single session. Replaced ``pkg_resources`` with ``importlib.resources`` & ``importlib.metadata`` across every Pylons package that needed it. Remarkbox. MakePostSell. RhodeCode. Nine Pyramid-dependent packages. Nineteen ``docs/conf.py`` files. Substanced's ``EntryPoint.parse().load()`` hack. Deform's cursed module-level ``resource_filename`` call that fails at import time. All uncommitted, waiting to fork & push across the org.
The only new contributor to the entire Pylons org in the past year. Because someone has to play `that xkcd 2347 role <https://xkcd.com/2347/>`_, a single load-bearing block propping up everything above it. "Our" framework means I volunteer so "we" can keep a foundation standing. Maybe eventually get paid. Maybe not. NASA just shut down. Federal grants evaporate. "Our" community runs on donated labor & borrowed time.
Here sits ``unbury`` in full (`download </uploads/2026/03/unbury.py>`_). A script to exhume contributor stats from a graveyard of git repos:
.. code-block:: python
#!/usr/bin/env python3
"""unbury - exhume contributor stats from a graveyard of git repos.
Usage:
unbury [path] # defaults to current directory
unbury /home/fox/git/pylons
"""
import os
import subprocess
import sys
from collections import defaultdict
from datetime import datetime, timedelta
PERIODS = [
("3 months", 90),
("1 year", 365),
("3 years", 365 * 3),
("6 years", 365 * 6),
("18 years", 365 * 18),
("lifetime", None),
]
def git_contributors(repo_path, since=None):
"""Return set of (name, email) tuples for a repo."""
cmd = ["git", "-C", repo_path, "log", "--format=%aN\t%aE"]
if since:
cmd.append(f"--since={since}")
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
contributors = set()
for line in result.stdout.strip().split("\n"):
if "\t" in line:
name, email = line.split("\t", 1)
contributors.add((name.strip(), email.strip().lower()))
return contributors
except (subprocess.TimeoutExpired, Exception):
return set()
def find_repos(root):
"""Find all git repos (immediate subdirs with .git)."""
repos = []
for entry in sorted(os.listdir(root)):
path = os.path.join(root, entry)
if os.path.isdir(path) and os.path.isdir(os.path.join(path, ".git")):
repos.append(path)
return repos
def main():
root = sys.argv[1] if len(sys.argv) > 1 else os.getcwd()
root = os.path.abspath(root)
repos = find_repos(root)
if not repos:
print(f"no git repos found in {root}")
sys.exit(1)
print(f"scanning {len(repos)} repos in {root}\n")
now = datetime.now()
for label, days in PERIODS:
since = (now - timedelta(days=days)).strftime("%Y-%m-%d") if days else None
all_contributors = set()
repo_counts = {}
for repo in repos:
contribs = git_contributors(repo, since)
if contribs:
repo_name = os.path.basename(repo)
repo_counts[repo_name] = len(contribs)
all_contributors.update(contribs)
active_repos = len(repo_counts)
print(f"{'─' * 50}")
print(f"{label}: {len(all_contributors)} unique contributors"
f" across {active_repos} active repos")
if all_contributors:
person_repos = defaultdict(list)
for repo in repos:
contribs = git_contributors(repo, since)
repo_name = os.path.basename(repo)
for c in contribs:
person_repos[c].append(repo_name)
ranked = sorted(person_repos.items(), key=lambda x: -len(x[1]))
for (name, email), touched in ranked[:10]:
print(f" {name:30s} {len(touched):3d} repos ({email})")
if len(ranked) > 10:
print(f" ... and {len(ranked) - 10} more")
print(f"{'─' * 50}")
if __name__ == "__main__":
main()
|
.. image:: /uploads/2026/03/contributor-decay.svg
:alt: diagram showing Pylons org contributor decay from 28 lifetime contributors across 97 repos down to 4 humans plus 1 bot across 5 repos in the last 3 months, ending with no BDFL, no formal general, snowflake governance
:align: center
:width: 100%
|
Pyramid sits as a microcosm of open source in general. Every org looks like this. 28 humans across a lifetime. 4 active today. No BDFL. No benevolent dictator for life. No formal general directing strategy. Just snowflake governance. Each org a unique crystal of informal agreements, stale GOVERNANCE.md files, & whoever still answers email. Python had Guido. Linux has Linus. Pyramid has nobody. Most open source projects have nobody. They drift on inertia & the stubbornness of whoever remains.
"Our" framework. "Our" community. "Our" roadmap. Strip the pronoun & look at what remains. A handful of volunteers. A bot. Some stale CI configs. The word "our" papers over the structural void where leadership should live. Without a BDFL, "our" becomes a polite fiction. Nobody owns it. Nobody steers it. Everybody claims it.
I catch myself writing "our" in `unturf <https://unturf.com>`_ docs. "Our permacomputer approach." Whose? Mine? Fox's? A philosophy doesn't belong to anyone. "A permacomputer approach" works better. Drops possession entirely. Lets an idea stand on its own merit instead of borrowed authority.

View file

@ -0,0 +1,27 @@
digraph contributor_decay {
bgcolor="transparent"
rankdir=LR
node [fontname="Helvetica" fontsize=11 style="filled,rounded" shape=box color="#999999"]
edge [color="#AAAAAA" arrowsize=0.5]
subgraph cluster_time {
label="Pylons org: contributor decay" fontname="Helvetica Bold" fontsize=12 fontcolor="#CCCCCC"
style=dashed color="#999999"
lifetime [label="lifetime\n28 humans\n97 repos" fillcolor="#1b5e20" fontcolor="white"]
y18 [label="18 years\n28 humans\n97 repos" fillcolor="#2e7d32" fontcolor="white"]
y6 [label="6 years\n19 humans\n46 repos" fillcolor="#0d47a1" fontcolor="white"]
y3 [label="3 years\n15 humans\n31 repos" fillcolor="#1565c0" fontcolor="white"]
y1 [label="1 year\n7 humans\n8 repos" fillcolor="#6a1b9a" fontcolor="white"]
m3 [label="3 months\n4 humans\n+ 1 bot\n5 repos" fillcolor="#bf360c" fontcolor="white"]
}
lifetime -> y18 [label=" same" fontcolor="#CCCCCC"]
y18 -> y6 [label=" -9" fontcolor="#ef5350" color="#ef5350"]
y6 -> y3 [label=" -4" fontcolor="#ef5350" color="#ef5350"]
y3 -> y1 [label=" -8" fontcolor="#ef5350" color="#ef5350"]
y1 -> m3 [label=" -3" fontcolor="#ef5350" color="#ef5350"]
no_bdfl [label="no BDFL\nno formal general\nsnowflake governance" fillcolor="#4e342e" fontcolor="white" shape=note]
m3 -> no_bdfl [style=dashed color="#ef5350" fontcolor="#ef5350" label=" result"]
}

View file

@ -0,0 +1,157 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%"
viewBox="0.00 0.00 996.00 117.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes loop0 {
0% { opacity: 0; }
2.5% { opacity: 1; }
89.2% { opacity: 1; }
93.8%, 100% { opacity: 0; }
}
@keyframes loop1 {
0%, 6.2% { opacity: 0; }
8.7% { opacity: 1; }
89.2% { opacity: 1; }
93.8%, 100% { opacity: 0; }
}
@keyframes loop2 {
0%, 12.4% { opacity: 0; }
14.9% { opacity: 1; }
89.2% { opacity: 1; }
93.8%, 100% { opacity: 0; }
}
@keyframes loop3 {
0%, 18.6% { opacity: 0; }
21.1% { opacity: 1; }
89.2% { opacity: 1; }
93.8%, 100% { opacity: 0; }
}
@keyframes loop4 {
0%, 24.8% { opacity: 0; }
27.2% { opacity: 1; }
89.2% { opacity: 1; }
93.8%, 100% { opacity: 0; }
}
@keyframes loop5 {
0%, 31.0% { opacity: 0; }
33.4% { opacity: 1; }
89.2% { opacity: 1; }
93.8%, 100% { opacity: 0; }
}
@keyframes loop6 {
0%, 37.2% { opacity: 0; }
39.6% { opacity: 1; }
89.2% { opacity: 1; }
93.8%, 100% { opacity: 0; }
}
@keyframes loop7 {
0%, 46.4% { opacity: 0; }
48.9% { opacity: 1; }
89.2% { opacity: 1; }
93.8%, 100% { opacity: 0; }
}
.anim { opacity: 0; }
.d0 { animation: loop0 32.3s ease-out infinite; }
.d1 { animation: loop1 32.3s ease-out infinite; }
.d2 { animation: loop2 32.3s ease-out infinite; }
.d3 { animation: loop3 32.3s ease-out infinite; }
.d4 { animation: loop4 32.3s ease-out infinite; }
.d5 { animation: loop5 32.3s ease-out infinite; }
.d6 { animation: loop6 32.3s ease-out infinite; }
.d7 { animation: loop7 32.3s ease-out infinite; }
</style>
</defs>
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 113)">
<!-- d0: cluster box -->
<g class="anim d0">
<polygon fill="transparent" stroke="#999999" stroke-dasharray="5,2" points="8,-8 8,-101 773,-101 773,-8 8,-8"/>
<text text-anchor="middle" x="390.5" y="-87.4" font-family="Helvetica Bold" font-size="12.00" fill="#cccccc">Pylons org: contributor decay</text>
</g>
<!-- d1: lifetime -->
<g class="anim d1">
<path fill="#1b5e20" stroke="#999999" d="M81,-66C81,-66 28,-66 28,-66 22,-66 16,-60 16,-54 16,-54 16,-34 16,-34 16,-28 22,-22 28,-22 28,-22 81,-22 81,-22 87,-22 93,-28 93,-34 93,-34 93,-54 93,-54 93,-60 87,-66 81,-66"/>
<text text-anchor="middle" x="54.5" y="-53.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="white">lifetime</text>
<text text-anchor="middle" x="54.5" y="-41.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="white">28 humans</text>
<text text-anchor="middle" x="54.5" y="-29.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="white">97 repos</text>
</g>
<!-- d2: 18 years + edge -->
<g class="anim d2">
<path fill="#2e7d32" stroke="#999999" d="M237,-66C237,-66 184,-66 184,-66 178,-66 172,-60 172,-54 172,-54 172,-34 172,-34 172,-28 178,-22 184,-22 184,-22 237,-22 237,-22 243,-22 249,-28 249,-34 249,-34 249,-54 249,-54 249,-60 243,-66 237,-66"/>
<text text-anchor="middle" x="210.5" y="-53.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="white">18 years</text>
<text text-anchor="middle" x="210.5" y="-41.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="white">28 humans</text>
<text text-anchor="middle" x="210.5" y="-29.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="white">97 repos</text>
<path fill="none" stroke="#aaaaaa" d="M93.17,-44C115.44,-44 143.71,-44 166.8,-44"/>
<polygon fill="#aaaaaa" stroke="#aaaaaa" points="166.95,-45.75 171.95,-44 166.95,-42.25 166.95,-45.75"/>
<text text-anchor="middle" x="132.5" y="-47.8" font-family="Times,serif" font-size="14.00" fill="#cccccc"> same</text>
</g>
<!-- d3: 6 years + edge -->
<g class="anim d3">
<path fill="#0d47a1" stroke="#999999" d="M369,-66C369,-66 316,-66 316,-66 310,-66 304,-60 304,-54 304,-54 304,-34 304,-34 304,-28 310,-22 316,-22 316,-22 369,-22 369,-22 375,-22 381,-28 381,-34 381,-34 381,-54 381,-54 381,-60 375,-66 369,-66"/>
<text text-anchor="middle" x="342.5" y="-53.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="white">6 years</text>
<text text-anchor="middle" x="342.5" y="-41.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="white">19 humans</text>
<text text-anchor="middle" x="342.5" y="-29.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="white">46 repos</text>
<path fill="none" stroke="#ef5350" d="M249.1,-44C264.6,-44 282.64,-44 298.64,-44"/>
<polygon fill="#ef5350" stroke="#ef5350" points="298.98,-45.75 303.98,-44 298.98,-42.25 298.98,-45.75"/>
<text text-anchor="middle" x="276.5" y="-47.8" font-family="Times,serif" font-size="14.00" fill="#ef5350"> &#45;9</text>
</g>
<!-- d4: 3 years + edge -->
<g class="anim d4">
<path fill="#1565c0" stroke="#999999" d="M501,-66C501,-66 448,-66 448,-66 442,-66 436,-60 436,-54 436,-54 436,-34 436,-34 436,-28 442,-22 448,-22 448,-22 501,-22 501,-22 507,-22 513,-28 513,-34 513,-34 513,-54 513,-54 513,-60 507,-66 501,-66"/>
<text text-anchor="middle" x="474.5" y="-53.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="white">3 years</text>
<text text-anchor="middle" x="474.5" y="-41.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="white">15 humans</text>
<text text-anchor="middle" x="474.5" y="-29.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="white">31 repos</text>
<path fill="none" stroke="#ef5350" d="M381.1,-44C396.6,-44 414.64,-44 430.64,-44"/>
<polygon fill="#ef5350" stroke="#ef5350" points="430.98,-45.75 435.98,-44 430.98,-42.25 430.98,-45.75"/>
<text text-anchor="middle" x="408.5" y="-47.8" font-family="Times,serif" font-size="14.00" fill="#ef5350"> &#45;4</text>
</g>
<!-- d5: 1 year + edge -->
<g class="anim d5">
<path fill="#6a1b9a" stroke="#999999" d="M627,-66C627,-66 580,-66 580,-66 574,-66 568,-60 568,-54 568,-54 568,-34 568,-34 568,-28 574,-22 580,-22 580,-22 627,-22 627,-22 633,-22 639,-28 639,-34 639,-34 639,-54 639,-54 639,-60 633,-66 627,-66"/>
<text text-anchor="middle" x="603.5" y="-53.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="white">1 year</text>
<text text-anchor="middle" x="603.5" y="-41.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="white">7 humans</text>
<text text-anchor="middle" x="603.5" y="-29.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="white">8 repos</text>
<path fill="none" stroke="#ef5350" d="M513.26,-44C528.76,-44 546.73,-44 562.46,-44"/>
<polygon fill="#ef5350" stroke="#ef5350" points="562.7,-45.75 567.7,-44 562.7,-42.25 562.7,-45.75"/>
<text text-anchor="middle" x="540.5" y="-47.8" font-family="Times,serif" font-size="14.00" fill="#ef5350"> &#45;8</text>
</g>
<!-- d6: 3 months + edge -->
<g class="anim d6">
<path fill="#bf360c" stroke="#999999" d="M753,-72C753,-72 706,-72 706,-72 700,-72 694,-66 694,-60 694,-60 694,-28 694,-28 694,-22 700,-16 706,-16 706,-16 753,-16 753,-16 759,-16 765,-22 765,-28 765,-28 765,-60 765,-60 765,-66 759,-72 753,-72"/>
<text text-anchor="middle" x="729.5" y="-59.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="white">3 months</text>
<text text-anchor="middle" x="729.5" y="-47.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="white">4 humans</text>
<text text-anchor="middle" x="729.5" y="-35.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="white">+ 1 bot</text>
<text text-anchor="middle" x="729.5" y="-23.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="white">5 repos</text>
<path fill="none" stroke="#ef5350" d="M639.04,-44C654.36,-44 672.49,-44 688.41,-44"/>
<polygon fill="#ef5350" stroke="#ef5350" points="688.71,-45.75 693.71,-44 688.71,-42.25 688.71,-45.75"/>
<text text-anchor="middle" x="666.5" y="-47.8" font-family="Times,serif" font-size="14.00" fill="#ef5350"> &#45;3</text>
</g>
<!-- d7: no BDFL + edge -->
<g class="anim d7">
<polygon fill="#4e342e" stroke="#999999" points="982,-66 848,-66 848,-22 988,-22 988,-60 982,-66"/>
<polyline fill="none" stroke="#999999" points="982,-66 982,-60 "/>
<polyline fill="none" stroke="#999999" points="988,-60 982,-60 "/>
<text text-anchor="middle" x="918" y="-53.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="white">no BDFL</text>
<text text-anchor="middle" x="918" y="-41.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="white">no formal general</text>
<text text-anchor="middle" x="918" y="-29.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="white">snowflake governance</text>
<path fill="none" stroke="#ef5350" stroke-dasharray="5,2" d="M765.14,-44C786.87,-44 815.73,-44 842.46,-44"/>
<polygon fill="#ef5350" stroke="#ef5350" points="842.77,-45.75 847.77,-44 842.77,-42.25 842.77,-45.75"/>
<text text-anchor="middle" x="806.5" y="-47.8" font-family="Times,serif" font-size="14.00" fill="#ef5350"> result</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.8 KiB

View file

@ -9,17 +9,77 @@
from { opacity: 0; }
to { opacity: 1; }
}
.anim { opacity: 0; animation: fadeIn 0.8s ease-out forwards; }
.d0 { animation-delay: 0s; }
.d1 { animation-delay: 2s; }
.d2 { animation-delay: 4s; }
.d3 { animation-delay: 6s; }
.d4 { animation-delay: 8s; }
.d5 { animation-delay: 10s; }
.d6 { animation-delay: 12s; }
.d7 { animation-delay: 15s; }
.d8 { animation-delay: 18s; }
.d9 { animation-delay: 21s; }
@keyframes loop0 {
0% { opacity: 0; }
2.1% { opacity: 1; }
90.9% { opacity: 1; }
94.8%, 100% { opacity: 0; }
}
@keyframes loop1 {
0%, 5.2% { opacity: 0; }
7.3% { opacity: 1; }
90.9% { opacity: 1; }
94.8%, 100% { opacity: 0; }
}
@keyframes loop2 {
0%, 10.4% { opacity: 0; }
12.5% { opacity: 1; }
90.9% { opacity: 1; }
94.8%, 100% { opacity: 0; }
}
@keyframes loop3 {
0%, 15.7% { opacity: 0; }
17.8% { opacity: 1; }
90.9% { opacity: 1; }
94.8%, 100% { opacity: 0; }
}
@keyframes loop4 {
0%, 20.9% { opacity: 0; }
23.0% { opacity: 1; }
90.9% { opacity: 1; }
94.8%, 100% { opacity: 0; }
}
@keyframes loop5 {
0%, 26.1% { opacity: 0; }
28.2% { opacity: 1; }
90.9% { opacity: 1; }
94.8%, 100% { opacity: 0; }
}
@keyframes loop6 {
0%, 31.3% { opacity: 0; }
33.4% { opacity: 1; }
90.9% { opacity: 1; }
94.8%, 100% { opacity: 0; }
}
@keyframes loop7 {
0%, 39.2% { opacity: 0; }
41.3% { opacity: 1; }
90.9% { opacity: 1; }
94.8%, 100% { opacity: 0; }
}
@keyframes loop8 {
0%, 47.0% { opacity: 0; }
49.1% { opacity: 1; }
90.9% { opacity: 1; }
94.8%, 100% { opacity: 0; }
}
@keyframes loop9 {
0%, 54.8% { opacity: 0; }
56.9% { opacity: 1; }
90.9% { opacity: 1; }
94.8%, 100% { opacity: 0; }
}
.anim { opacity: 0; }
.d0 { animation: loop0 38.3s ease-out infinite; }
.d1 { animation: loop1 38.3s ease-out infinite; }
.d2 { animation: loop2 38.3s ease-out infinite; }
.d3 { animation: loop3 38.3s ease-out infinite; }
.d4 { animation: loop4 38.3s ease-out infinite; }
.d5 { animation: loop5 38.3s ease-out infinite; }
.d6 { animation: loop6 38.3s ease-out infinite; }
.d7 { animation: loop7 38.3s ease-out infinite; }
.d8 { animation: loop8 38.3s ease-out infinite; }
.d9 { animation: loop9 38.3s ease-out infinite; }
</style>
</defs>
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 1167)">
@ -27,7 +87,7 @@
<!-- cluster box -->
<g class="anim d0">
<polygon fill="transparent" stroke="#999999" stroke-dasharray="5,2" points="8,-281 8,-1100 169,-1100 169,-281 8,-281"/>
<text text-anchor="middle" x="88.5" y="-1087.2" font-family="Helvetica Bold" font-size="11.00" fill="#aaaaaa">Neville&#39;s Seven Eyes of God</text>
<text text-anchor="middle" x="88.5" y="-1087.2" font-family="Helvetica Bold" font-size="11.00" fill="#cccccc">Neville&#39;s Seven Eyes of God</text>
</g>
<!-- separation label -->
@ -115,10 +175,10 @@
<path fill="none" stroke="#aaaaaa" stroke-width="2" d="M120.73,-433.8C120.6,-415.52 120.45,-393.59 120.32,-374.56"/>
<polygon fill="#aaaaaa" stroke="#aaaaaa" stroke-width="2" points="122.41,-374.21 120.27,-368.23 118.21,-374.24 122.41,-374.21"/>
<ellipse fill="#f9a825" stroke="#999999" cx="120" cy="-328.5" rx="39.5" ry="39.5"/>
<text text-anchor="middle" x="120" y="-341.3" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#333333">7</text>
<text text-anchor="middle" x="120" y="-331.3" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#333333">Jesus</text>
<text text-anchor="middle" x="120" y="-321.3" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#333333">trinity</text>
<text text-anchor="middle" x="120" y="-311.3" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#333333">man↔God</text>
<text text-anchor="middle" x="120" y="-341.3" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#1a1a1a">7</text>
<text text-anchor="middle" x="120" y="-331.3" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#1a1a1a">Jesus</text>
<text text-anchor="middle" x="120" y="-321.3" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#1a1a1a">trinity</text>
<text text-anchor="middle" x="120" y="-311.3" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#1a1a1a">man↔God</text>
</g>
<!-- 8 Christ consciousness + edge + labels -->

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before After
Before After

View file

@ -15,9 +15,9 @@ digraph load_bearing {
apps [label="thousands of\nPyramid apps" fillcolor="#546E7A" fontcolor="white"]
}
pyramid [label="Pyramid\n~5 maintainers" fillcolor="#BF360C" fontcolor="white" fontsize=13]
pyramid [label="Pylons org: 98 repos\n28 lifetime contributors\n4 humans active last 3mo" fillcolor="#BF360C" fontcolor="white" fontsize=13]
russell [label="Russell\nunemployed\nvolunteer" fillcolor="#E65100" fontcolor="white" shape=ellipse]
russell [label="Russell\nunemployed volunteer\nonly new contributor\nin the past year\nPR #3805" fillcolor="#E65100" fontcolor="white" shape=ellipse]
pypi -> pyramid
rhodecode -> pyramid

View file

@ -1,172 +1,163 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.43.0 (0)
-->
<!-- Title: load_bearing Pages: 1 -->
<svg width="100%" height="100%"
viewBox="0.00 0.00 530.00 388.47" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
viewBox="0.00 0.00 530.00 439.24" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.anim { opacity: 0; animation: fadeIn 0.8s ease-out forwards; }
.d0 { animation-delay: 0s; }
.d1 { animation-delay: 3s; }
.d2 { animation-delay: 6s; }
.d3 { animation-delay: 9s; }
.d4 { animation-delay: 12s; }
.d5 { animation-delay: 15s; }
.d6 { animation-delay: 18s; }
.d7 { animation-delay: 21s; }
.d8 { animation-delay: 24s; }
@keyframes loop0 {
0% { opacity: 0; }
1.9% { opacity: 1; }
91.5% { opacity: 1; }
95.2%, 100% { opacity: 0; }
}
@keyframes loop1 {
0%, 7.3% { opacity: 0; }
9.2% { opacity: 1; }
91.5% { opacity: 1; }
95.2%, 100% { opacity: 0; }
}
@keyframes loop2 {
0%, 14.5% { opacity: 0; }
16.5% { opacity: 1; }
91.5% { opacity: 1; }
95.2%, 100% { opacity: 0; }
}
@keyframes loop3 {
0%, 21.8% { opacity: 0; }
23.7% { opacity: 1; }
91.5% { opacity: 1; }
95.2%, 100% { opacity: 0; }
}
@keyframes loop4 {
0%, 29.1% { opacity: 0; }
31.0% { opacity: 1; }
91.5% { opacity: 1; }
95.2%, 100% { opacity: 0; }
}
@keyframes loop5 {
0%, 36.3% { opacity: 0; }
38.3% { opacity: 1; }
91.5% { opacity: 1; }
95.2%, 100% { opacity: 0; }
}
@keyframes loop6 {
0%, 43.6% { opacity: 0; }
45.5% { opacity: 1; }
91.5% { opacity: 1; }
95.2%, 100% { opacity: 0; }
}
@keyframes loop7 {
0%, 50.8% { opacity: 0; }
52.8% { opacity: 1; }
91.5% { opacity: 1; }
95.2%, 100% { opacity: 0; }
}
@keyframes loop8 {
0%, 58.1% { opacity: 0; }
60.0% { opacity: 1; }
91.5% { opacity: 1; }
95.2%, 100% { opacity: 0; }
}
.anim { opacity: 0; }
.d0 { animation: loop0 41.3s ease-out infinite; }
.d1 { animation: loop1 41.3s ease-out infinite; }
.d2 { animation: loop2 41.3s ease-out infinite; }
.d3 { animation: loop3 41.3s ease-out infinite; }
.d4 { animation: loop4 41.3s ease-out infinite; }
.d5 { animation: loop5 41.3s ease-out infinite; }
.d6 { animation: loop6 41.3s ease-out infinite; }
.d7 { animation: loop7 41.3s ease-out infinite; }
.d8 { animation: loop8 41.3s ease-out infinite; }
</style>
</defs>
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 384.47)">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 435.24)">
<!-- d0: cluster box + "our framework" label -->
<!-- d0: cluster box -->
<g class="anim d0">
<g id="clust1" class="cluster">
<polygon fill="transparent" stroke="#999999" stroke-dasharray="5,2" points="8,-299.47 8,-372.47 514,-372.47 514,-299.47 8,-299.47"/>
<text text-anchor="middle" x="261" y="-358.87" font-family="Helvetica Bold" font-size="12.00" fill="#aaaaaa">&quot;our&quot; framework</text>
</g>
<polygon fill="transparent" stroke="#999999" stroke-dasharray="5,2" points="8,-350.24 8,-423.24 514,-423.24 514,-350.24 8,-350.24"/>
<text text-anchor="middle" x="261" y="-409.64" font-family="Helvetica Bold" font-size="12.00" fill="#cccccc">&quot;our&quot; framework</text>
</g>
<!-- d1: PyPI node -->
<!-- d1: PyPI -->
<g class="anim d1">
<!-- pypi -->
<g id="node1" class="node">
<path fill="#3775a9" stroke="#999999" d="M58,-343.47C58,-343.47 28,-343.47 28,-343.47 22,-343.47 16,-337.47 16,-331.47 16,-331.47 16,-319.47 16,-319.47 16,-313.47 22,-307.47 28,-307.47 28,-307.47 58,-307.47 58,-307.47 64,-307.47 70,-313.47 70,-319.47 70,-319.47 70,-331.47 70,-331.47 70,-337.47 64,-343.47 58,-343.47"/>
<text text-anchor="middle" x="43" y="-322.37" font-family="Helvetica,sans-Serif" font-size="12.00" fill="white">PyPI</text>
</g>
<path fill="#3775a9" stroke="#999999" d="M58,-394.24C58,-394.24 28,-394.24 28,-394.24 22,-394.24 16,-388.24 16,-382.24 16,-382.24 16,-370.24 16,-370.24 16,-364.24 22,-358.24 28,-358.24 28,-358.24 58,-358.24 58,-358.24 64,-358.24 70,-364.24 70,-370.24 70,-370.24 70,-382.24 70,-382.24 70,-388.24 64,-394.24 58,-394.24"/>
<text text-anchor="middle" x="43" y="-373.14" font-family="Helvetica,sans-Serif" font-size="12.00" fill="white">PyPI</text>
</g>
<!-- d2: RhodeCode node -->
<!-- d2: RhodeCode -->
<g class="anim d2">
<!-- rhodecode -->
<g id="node2" class="node">
<path fill="#259bce" stroke="#999999" d="M162,-343.47C162,-343.47 100,-343.47 100,-343.47 94,-343.47 88,-337.47 88,-331.47 88,-331.47 88,-319.47 88,-319.47 88,-313.47 94,-307.47 100,-307.47 100,-307.47 162,-307.47 162,-307.47 168,-307.47 174,-313.47 174,-319.47 174,-319.47 174,-331.47 174,-331.47 174,-337.47 168,-343.47 162,-343.47"/>
<text text-anchor="middle" x="131" y="-322.37" font-family="Helvetica,sans-Serif" font-size="12.00" fill="white">RhodeCode</text>
</g>
<path fill="#259bce" stroke="#999999" d="M162,-394.24C162,-394.24 100,-394.24 100,-394.24 94,-394.24 88,-388.24 88,-382.24 88,-382.24 88,-370.24 88,-370.24 88,-364.24 94,-358.24 100,-358.24 100,-358.24 162,-358.24 162,-358.24 168,-358.24 174,-364.24 174,-370.24 174,-370.24 174,-382.24 174,-382.24 174,-388.24 168,-394.24 162,-394.24"/>
<text text-anchor="middle" x="131" y="-373.14" font-family="Helvetica,sans-Serif" font-size="12.00" fill="white">RhodeCode</text>
</g>
<!-- d3: Remarkbox node -->
<!-- d3: Remarkbox -->
<g class="anim d3">
<!-- remarkbox -->
<g id="node3" class="node">
<path fill="#6c5ce7" stroke="#999999" d="M264,-343.47C264,-343.47 204,-343.47 204,-343.47 198,-343.47 192,-337.47 192,-331.47 192,-331.47 192,-319.47 192,-319.47 192,-313.47 198,-307.47 204,-307.47 204,-307.47 264,-307.47 264,-307.47 270,-307.47 276,-313.47 276,-319.47 276,-319.47 276,-331.47 276,-331.47 276,-337.47 270,-343.47 264,-343.47"/>
<text text-anchor="middle" x="234" y="-322.37" font-family="Helvetica,sans-Serif" font-size="12.00" fill="white">Remarkbox</text>
</g>
<path fill="#6c5ce7" stroke="#999999" d="M264,-394.24C264,-394.24 204,-394.24 204,-394.24 198,-394.24 192,-388.24 192,-382.24 192,-382.24 192,-370.24 192,-370.24 192,-364.24 198,-358.24 204,-358.24 204,-358.24 264,-358.24 264,-358.24 270,-358.24 276,-364.24 276,-370.24 276,-370.24 276,-382.24 276,-382.24 276,-388.24 270,-394.24 264,-394.24"/>
<text text-anchor="middle" x="234" y="-373.14" font-family="Helvetica,sans-Serif" font-size="12.00" fill="white">Remarkbox</text>
</g>
<!-- d4: MakePostSell node -->
<!-- d4: MakePostSell -->
<g class="anim d4">
<!-- mps -->
<g id="node4" class="node">
<path fill="#00897b" stroke="#999999" d="M377.5,-343.47C377.5,-343.47 306.5,-343.47 306.5,-343.47 300.5,-343.47 294.5,-337.47 294.5,-331.47 294.5,-331.47 294.5,-319.47 294.5,-319.47 294.5,-313.47 300.5,-307.47 306.5,-307.47 306.5,-307.47 377.5,-307.47 377.5,-307.47 383.5,-307.47 389.5,-313.47 389.5,-319.47 389.5,-319.47 389.5,-331.47 389.5,-331.47 389.5,-337.47 383.5,-343.47 377.5,-343.47"/>
<text text-anchor="middle" x="342" y="-322.37" font-family="Helvetica,sans-Serif" font-size="12.00" fill="white">MakePostSell</text>
</g>
<path fill="#00897b" stroke="#999999" d="M377.5,-394.24C377.5,-394.24 306.5,-394.24 306.5,-394.24 300.5,-394.24 294.5,-388.24 294.5,-382.24 294.5,-382.24 294.5,-370.24 294.5,-370.24 294.5,-364.24 300.5,-358.24 306.5,-358.24 306.5,-358.24 377.5,-358.24 377.5,-358.24 383.5,-358.24 389.5,-364.24 389.5,-370.24 389.5,-370.24 389.5,-382.24 389.5,-382.24 389.5,-388.24 383.5,-394.24 377.5,-394.24"/>
<text text-anchor="middle" x="342" y="-373.14" font-family="Helvetica,sans-Serif" font-size="12.00" fill="white">MakePostSell</text>
</g>
<!-- d5: "thousands of Pyramid apps" node -->
<!-- d5: thousands of Pyramid apps -->
<g class="anim d5">
<!-- apps -->
<g id="node5" class="node">
<path fill="#546e7a" stroke="#999999" d="M494,-343.47C494,-343.47 420,-343.47 420,-343.47 414,-343.47 408,-337.47 408,-331.47 408,-331.47 408,-319.47 408,-319.47 408,-313.47 414,-307.47 420,-307.47 420,-307.47 494,-307.47 494,-307.47 500,-307.47 506,-313.47 506,-319.47 506,-319.47 506,-331.47 506,-331.47 506,-337.47 500,-343.47 494,-343.47"/>
<text text-anchor="middle" x="457" y="-328.87" font-family="Helvetica,sans-Serif" font-size="12.00" fill="white">thousands of</text>
<text text-anchor="middle" x="457" y="-315.87" font-family="Helvetica,sans-Serif" font-size="12.00" fill="white">Pyramid apps</text>
</g>
<path fill="#546e7a" stroke="#999999" d="M494,-394.24C494,-394.24 420,-394.24 420,-394.24 414,-394.24 408,-388.24 408,-382.24 408,-382.24 408,-370.24 408,-370.24 408,-364.24 414,-358.24 420,-358.24 420,-358.24 494,-358.24 494,-358.24 500,-358.24 506,-364.24 506,-370.24 506,-370.24 506,-382.24 506,-382.24 506,-388.24 500,-394.24 494,-394.24"/>
<text text-anchor="middle" x="457" y="-379.64" font-family="Helvetica,sans-Serif" font-size="12.00" fill="white">thousands of</text>
<text text-anchor="middle" x="457" y="-366.64" font-family="Helvetica,sans-Serif" font-size="12.00" fill="white">Pyramid apps</text>
</g>
<!-- d6: All edges from apps to Pyramid + Pyramid node -->
<!-- d6: Pylons org node + all edges -->
<g class="anim d6">
<!-- pyramid -->
<g id="node6" class="node">
<path fill="#bf360c" stroke="#999999" d="M281.5,-270.47C281.5,-270.47 186.5,-270.47 186.5,-270.47 180.5,-270.47 174.5,-264.47 174.5,-258.47 174.5,-258.47 174.5,-246.47 174.5,-246.47 174.5,-240.47 180.5,-234.47 186.5,-234.47 186.5,-234.47 281.5,-234.47 281.5,-234.47 287.5,-234.47 293.5,-240.47 293.5,-246.47 293.5,-246.47 293.5,-258.47 293.5,-258.47 293.5,-264.47 287.5,-270.47 281.5,-270.47"/>
<text text-anchor="middle" x="234" y="-256.07" font-family="Helvetica,sans-Serif" font-size="13.00" fill="white">Pyramid</text>
<text text-anchor="middle" x="234" y="-242.07" font-family="Helvetica,sans-Serif" font-size="13.00" fill="white">~5 maintainers</text>
</g>
<!-- pypi&#45;&gt;pyramid -->
<g id="edge1" class="edge">
<path fill="none" stroke="#aaaaaa" d="M65.18,-307.45C69.61,-304.52 74.34,-301.71 79,-299.47 107.11,-285.97 140.01,-275.52 168.26,-268.02"/>
<polygon fill="#aaaaaa" stroke="#aaaaaa" points="168.94,-270.01 174.21,-266.47 167.87,-265.95 168.94,-270.01"/>
</g>
<!-- rhodecode&#45;&gt;pyramid -->
<g id="edge2" class="edge">
<path fill="none" stroke="#aaaaaa" d="M155.67,-307.46C170.22,-297.44 188.72,-284.68 204.09,-274.09"/>
<polygon fill="#aaaaaa" stroke="#aaaaaa" points="205.43,-275.72 209.17,-270.58 203.04,-272.26 205.43,-275.72"/>
</g>
<!-- remarkbox&#45;&gt;pyramid -->
<g id="edge3" class="edge">
<path fill="none" stroke="#aaaaaa" d="M234,-307.28C234,-298.04 234,-286.51 234,-276.5"/>
<polygon fill="#aaaaaa" stroke="#aaaaaa" points="236.1,-276.5 234,-270.5 231.9,-276.5 236.1,-276.5"/>
</g>
<!-- mps&#45;&gt;pyramid -->
<g id="edge4" class="edge">
<path fill="none" stroke="#aaaaaa" d="M316.13,-307.46C300.88,-297.44 281.48,-284.68 265.36,-274.09"/>
<polygon fill="#aaaaaa" stroke="#aaaaaa" points="266.2,-272.12 260.03,-270.58 263.89,-275.63 266.2,-272.12"/>
</g>
<!-- apps&#45;&gt;pyramid -->
<g id="edge5" class="edge">
<path fill="none" stroke="#aaaaaa" d="M419.54,-307.45C412.77,-304.62 405.72,-301.84 399,-299.47 366.8,-288.1 330.28,-277.67 299.8,-269.63"/>
<polygon fill="#aaaaaa" stroke="#aaaaaa" points="300.13,-267.54 293.79,-268.05 299.06,-271.61 300.13,-267.54"/>
</g>
<path fill="#bf360c" stroke="#999999" d="M316.5,-321.24C316.5,-321.24 151.5,-321.24 151.5,-321.24 145.5,-321.24 139.5,-315.24 139.5,-309.24 139.5,-309.24 139.5,-283.24 139.5,-283.24 139.5,-277.24 145.5,-271.24 151.5,-271.24 151.5,-271.24 316.5,-271.24 316.5,-271.24 322.5,-271.24 328.5,-277.24 328.5,-283.24 328.5,-283.24 328.5,-309.24 328.5,-309.24 328.5,-315.24 322.5,-321.24 316.5,-321.24"/>
<text text-anchor="middle" x="234" y="-306.84" font-family="Helvetica,sans-Serif" font-size="13.00" fill="white">Pylons org: 98 repos</text>
<text text-anchor="middle" x="234" y="-292.84" font-family="Helvetica,sans-Serif" font-size="13.00" fill="white">28 lifetime contributors</text>
<text text-anchor="middle" x="234" y="-278.84" font-family="Helvetica,sans-Serif" font-size="13.00" fill="white">4 humans active last 3mo</text>
<path fill="none" stroke="#aaaaaa" d="M65.65,-358.11C69.95,-355.26 74.52,-352.5 79,-350.24 99.38,-339.93 122.24,-330.87 144,-323.29"/>
<polygon fill="#aaaaaa" stroke="#aaaaaa" points="144.81,-325.23 149.81,-321.3 143.45,-321.26 144.81,-325.23"/>
<path fill="none" stroke="#aaaaaa" d="M153.6,-358.12C166.37,-348.45 182.65,-336.13 197.21,-325.1"/>
<polygon fill="#aaaaaa" stroke="#aaaaaa" points="198.56,-326.71 202.07,-321.42 196.02,-323.36 198.56,-326.71"/>
<path fill="none" stroke="#aaaaaa" d="M234,-357.93C234,-349.01 234,-337.87 234,-327.57"/>
<polygon fill="#aaaaaa" stroke="#aaaaaa" points="236.1,-327.3 234,-321.3 231.9,-327.3 236.1,-327.3"/>
<path fill="none" stroke="#aaaaaa" d="M318.3,-358.12C304.91,-348.45 287.85,-336.13 272.58,-325.1"/>
<polygon fill="#aaaaaa" stroke="#aaaaaa" points="273.57,-323.23 267.48,-321.42 271.11,-326.63 273.57,-323.23"/>
<path fill="none" stroke="#aaaaaa" d="M418.84,-358.17C412.26,-355.41 405.47,-352.66 399,-350.24 373.6,-340.71 345.61,-331.32 320.02,-323.15"/>
<polygon fill="#aaaaaa" stroke="#aaaaaa" points="320.62,-321.13 314.26,-321.31 319.34,-325.13 320.62,-321.13"/>
</g>
<!-- d7: Russell node + xkcd edge -->
<g class="anim d7">
<!-- russell -->
<g id="node7" class="node">
<ellipse fill="#e65100" stroke="#999999" cx="234" cy="-120.23" rx="64.19" ry="33.47"/>
<text text-anchor="middle" x="234" y="-130.13" font-family="Helvetica,sans-Serif" font-size="12.00" fill="white">Russell</text>
<text text-anchor="middle" x="234" y="-117.13" font-family="Helvetica,sans-Serif" font-size="12.00" fill="white">unemployed</text>
<text text-anchor="middle" x="234" y="-104.13" font-family="Helvetica,sans-Serif" font-size="12.00" fill="white">volunteer</text>
</g>
<!-- pyramid&#45;&gt;russell -->
<g id="edge6" class="edge">
<path fill="none" stroke="#ef5350" stroke-width="2" d="M234,-234.44C234,-215.56 234,-184.5 234,-159.69"/>
<polygon fill="#ef5350" stroke="#ef5350" stroke-width="2" points="236.1,-159.52 234,-153.52 231.9,-159.52 236.1,-159.52"/>
<text text-anchor="middle" x="283.5" y="-205.27" font-family="Times,serif" font-size="14.00" fill="#ef5350"> &#160;xkcd 2347</text>
<text text-anchor="middle" x="283.5" y="-190.27" font-family="Times,serif" font-size="14.00" fill="#ef5350"> &#160;load&#45;bearing</text>
<text text-anchor="middle" x="283.5" y="-175.27" font-family="Times,serif" font-size="14.00" fill="#ef5350"> &#160;block</text>
</g>
<ellipse fill="#e65100" stroke="#999999" cx="234" cy="-138.62" rx="107.46" ry="51.74"/>
<text text-anchor="middle" x="234" y="-161.52" font-family="Helvetica,sans-Serif" font-size="12.00" fill="white">Russell</text>
<text text-anchor="middle" x="234" y="-148.52" font-family="Helvetica,sans-Serif" font-size="12.00" fill="white">unemployed volunteer</text>
<text text-anchor="middle" x="234" y="-135.52" font-family="Helvetica,sans-Serif" font-size="12.00" fill="white">only new contributor</text>
<text text-anchor="middle" x="234" y="-122.52" font-family="Helvetica,sans-Serif" font-size="12.00" fill="white">in the past year</text>
<text text-anchor="middle" x="234" y="-109.52" font-family="Helvetica,sans-Serif" font-size="12.00" fill="white">PR #3805</text>
<path fill="none" stroke="#ef5350" stroke-width="2" d="M234,-270.92C234,-251.06 234,-222.18 234,-196.56"/>
<polygon fill="#ef5350" stroke="#ef5350" stroke-width="2" points="236.1,-196.47 234,-190.47 231.9,-196.47 236.1,-196.47"/>
<text text-anchor="middle" x="283.5" y="-242.04" font-family="Times,serif" font-size="14.00" fill="#ef5350"> &#160;xkcd 2347</text>
<text text-anchor="middle" x="283.5" y="-227.04" font-family="Times,serif" font-size="14.00" fill="#ef5350"> &#160;load&#45;bearing</text>
<text text-anchor="middle" x="283.5" y="-212.04" font-family="Times,serif" font-size="14.00" fill="#ef5350"> &#160;block</text>
</g>
<!-- d8: NASA note + no funding edge -->
<!-- d8: NASA + no funding edge -->
<g class="anim d8">
<!-- nasa -->
<g id="node8" class="node">
<polygon fill="#b71c1c" stroke="#999999" points="288,-36 174,-36 174,0 294,0 294,-30 288,-36"/>
<polyline fill="none" stroke="#999999" points="288,-36 288,-30 "/>
<polyline fill="none" stroke="#999999" points="294,-30 288,-30 "/>
<text text-anchor="middle" x="234" y="-21.4" font-family="Helvetica,sans-Serif" font-size="12.00" fill="white">NASA shut down</text>
<text text-anchor="middle" x="234" y="-8.4" font-family="Helvetica,sans-Serif" font-size="12.00" fill="white">grants evaporate</text>
</g>
<!-- russell&#45;&gt;nasa -->
<g id="edge7" class="edge">
<path fill="none" stroke="#ef5350" stroke-dasharray="5,2" d="M234,-86.97C234,-72.41 234,-55.54 234,-42.25"/>
<polygon fill="#ef5350" stroke="#ef5350" points="236.1,-42.04 234,-36.04 231.9,-42.04 236.1,-42.04"/>
<path fill="none" stroke="#ef5350" stroke-dasharray="5,2" d="M234,-86.97C234,-71.53 234,-55.28 234,-42.55"/>
<polygon fill="#ef5350" stroke="#ef5350" points="236.1,-42.23 234,-36.23 231.9,-42.23 236.1,-42.23"/>
<text text-anchor="middle" x="277" y="-57.8" font-family="Times,serif" font-size="14.00" fill="#ef5350"> &#160;no funding</text>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

Before After
Before After

View file

@ -12,13 +12,49 @@
from { opacity: 0; }
to { opacity: 1; }
}
.anim { opacity: 0; animation: fadeIn 0.8s ease-out forwards; }
.d0 { animation-delay: 0s; }
.d1 { animation-delay: 3s; }
.d2 { animation-delay: 6s; }
.d3 { animation-delay: 9s; }
.d4 { animation-delay: 12s; }
.d5 { animation-delay: 15s; }
@keyframes loop0 {
0% { opacity: 0; }
2.5% { opacity: 1; }
89.2% { opacity: 1; }
93.8%, 100% { opacity: 0; }
}
@keyframes loop1 {
0%, 9.3% { opacity: 0; }
11.8% { opacity: 1; }
89.2% { opacity: 1; }
93.8%, 100% { opacity: 0; }
}
@keyframes loop2 {
0%, 18.6% { opacity: 0; }
21.1% { opacity: 1; }
89.2% { opacity: 1; }
93.8%, 100% { opacity: 0; }
}
@keyframes loop3 {
0%, 27.9% { opacity: 0; }
30.3% { opacity: 1; }
89.2% { opacity: 1; }
93.8%, 100% { opacity: 0; }
}
@keyframes loop4 {
0%, 37.2% { opacity: 0; }
39.6% { opacity: 1; }
89.2% { opacity: 1; }
93.8%, 100% { opacity: 0; }
}
@keyframes loop5 {
0%, 46.4% { opacity: 0; }
48.9% { opacity: 1; }
89.2% { opacity: 1; }
93.8%, 100% { opacity: 0; }
}
.anim { opacity: 0; }
.d0 { animation: loop0 32.3s ease-out infinite; }
.d1 { animation: loop1 32.3s ease-out infinite; }
.d2 { animation: loop2 32.3s ease-out infinite; }
.d3 { animation: loop3 32.3s ease-out infinite; }
.d4 { animation: loop4 32.3s ease-out infinite; }
.d5 { animation: loop5 32.3s ease-out infinite; }
</style>
</defs>
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 202)">
@ -49,7 +85,7 @@
<path fill="none" stroke="#aaaaaa" d="M80.14,-120.69C86.05,-123.65 92.13,-126.51 98,-129 128.91,-142.09 164.41,-153.96 193.68,-162.95"/>
<polygon fill="#aaaaaa" stroke="#aaaaaa" points="192.76,-166.33 203.35,-165.89 194.79,-159.63 192.76,-166.33"/>
<text text-anchor="middle" x="135" y="-158" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#aaaaaa"> &#160;dissolves into</text>
<text text-anchor="middle" x="135" y="-158" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#cccccc"> &#160;dissolves into</text>
</g>
</g>
@ -67,7 +103,7 @@
<path fill="none" stroke="#aaaaaa" d="M80.47,-104.02C110.08,-107.78 151.31,-113.02 186.1,-117.44"/>
<polygon fill="#aaaaaa" stroke="#aaaaaa" points="185.84,-120.93 196.2,-118.72 186.72,-113.99 185.84,-120.93"/>
<text text-anchor="middle" x="135" y="-117" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#aaaaaa"> &#160;dissolves into</text>
<text text-anchor="middle" x="135" y="-117" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#cccccc"> &#160;dissolves into</text>
</g>
</g>
@ -85,7 +121,7 @@
<path fill="none" stroke="#aaaaaa" d="M80.24,-93.4C86.18,-92.58 92.25,-91.75 98,-91 124.66,-87.5 153.93,-83.87 179.8,-80.73"/>
<polygon fill="#aaaaaa" stroke="#aaaaaa" points="180.41,-84.18 189.92,-79.51 179.57,-77.23 180.41,-84.18"/>
<text text-anchor="middle" x="135" y="-94" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#aaaaaa"> &#160;dissolves into</text>
<text text-anchor="middle" x="135" y="-94" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#cccccc"> &#160;dissolves into</text>
</g>
</g>
@ -103,7 +139,7 @@
<path fill="none" stroke="#aaaaaa" d="M79.02,-75.42C85.24,-72.02 91.71,-68.75 98,-66 124.86,-54.26 155.49,-44.15 182.43,-36.28"/>
<polygon fill="#aaaaaa" stroke="#aaaaaa" points="183.52,-39.61 192.17,-33.48 181.59,-32.88 183.52,-39.61"/>
<text text-anchor="middle" x="135" y="-69" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#aaaaaa"> &#160;dissolves into</text>
<text text-anchor="middle" x="135" y="-69" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#cccccc"> &#160;dissolves into</text>
</g>
</g>
@ -113,9 +149,9 @@
<g id="node6" class="node">
<path fill="#f9a825" stroke="#999999" d="M528,-127C528,-127 418,-127 418,-127 412,-127 406,-121 406,-115 406,-115 406,-89 406,-89 406,-83 412,-77 418,-77 418,-77 528,-77 528,-77 534,-77 540,-83 540,-89 540,-89 540,-115 540,-115 540,-121 534,-127 528,-127"/>
<text text-anchor="middle" x="473" y="-112.6" font-family="Helvetica,sans-Serif" font-size="13.00" fill="#333333">&quot;I AM&quot;</text>
<text text-anchor="middle" x="473" y="-98.6" font-family="Helvetica,sans-Serif" font-size="13.00" fill="#333333">singular grammar</text>
<text text-anchor="middle" x="473" y="-84.6" font-family="Helvetica,sans-Serif" font-size="13.00" fill="#333333">infinite referent</text>
<text text-anchor="middle" x="473" y="-112.6" font-family="Helvetica,sans-Serif" font-size="13.00" fill="#1a1a1a">&quot;I AM&quot;</text>
<text text-anchor="middle" x="473" y="-98.6" font-family="Helvetica,sans-Serif" font-size="13.00" fill="#1a1a1a">singular grammar</text>
<text text-anchor="middle" x="473" y="-84.6" font-family="Helvetica,sans-Serif" font-size="13.00" fill="#1a1a1a">infinite referent</text>
</g>
<!-- sufi&#45;&gt;iam -->
<!-- buddhist&#45;&gt;iam -->
@ -124,14 +160,14 @@
<path fill="none" stroke="#aaaaaa" d="M319.29,-80.84C343.18,-84.15 370.58,-87.95 395.51,-91.4"/>
<polygon fill="#aaaaaa" stroke="#aaaaaa" points="395.28,-94.9 405.66,-92.81 396.24,-87.97 395.28,-94.9"/>
<text text-anchor="middle" x="362.5" y="-92" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#aaaaaa"> &#160;becomes</text>
<text text-anchor="middle" x="362.5" y="-92" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#cccccc"> &#160;becomes</text>
</g>
<!-- christ&#45;&gt;iam -->
<g id="edge8" class="edge">
<path fill="none" stroke="#aaaaaa" d="M304.06,-36.01C329.18,-45.4 360.29,-57.17 388,-68 392.24,-69.66 396.59,-71.37 400.97,-73.11"/>
<polygon fill="#aaaaaa" stroke="#aaaaaa" points="400.01,-76.5 410.59,-76.95 402.6,-69.99 400.01,-76.5"/>
<text text-anchor="middle" x="362.5" y="-71" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#aaaaaa"> &#160;becomes</text>
<text text-anchor="middle" x="362.5" y="-71" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#cccccc"> &#160;becomes</text>
</g>
</g>

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Before After
Before After

View file

@ -12,12 +12,42 @@
from { opacity: 0; }
to { opacity: 1; }
}
.anim { opacity: 0; animation: fadeIn 0.8s ease-out forwards; }
.d0 { animation-delay: 0s; }
.d1 { animation-delay: 3s; }
.d2 { animation-delay: 6s; }
.d3 { animation-delay: 9s; }
.d4 { animation-delay: 12s; }
@keyframes loop0 {
0% { opacity: 0; }
2.7% { opacity: 1; }
88.1% { opacity: 1; }
93.2%, 100% { opacity: 0; }
}
@keyframes loop1 {
0%, 10.2% { opacity: 0; }
13.0% { opacity: 1; }
88.1% { opacity: 1; }
93.2%, 100% { opacity: 0; }
}
@keyframes loop2 {
0%, 20.5% { opacity: 0; }
23.2% { opacity: 1; }
88.1% { opacity: 1; }
93.2%, 100% { opacity: 0; }
}
@keyframes loop3 {
0%, 30.7% { opacity: 0; }
33.4% { opacity: 1; }
88.1% { opacity: 1; }
93.2%, 100% { opacity: 0; }
}
@keyframes loop4 {
0%, 41.0% { opacity: 0; }
43.7% { opacity: 1; }
88.1% { opacity: 1; }
93.2%, 100% { opacity: 0; }
}
.anim { opacity: 0; }
.d0 { animation: loop0 29.3s ease-out infinite; }
.d1 { animation: loop1 29.3s ease-out infinite; }
.d2 { animation: loop2 29.3s ease-out infinite; }
.d3 { animation: loop3 29.3s ease-out infinite; }
.d4 { animation: loop4 29.3s ease-out infinite; }
</style>
</defs>
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 400)">
@ -45,7 +75,7 @@
<path fill="none" stroke="#aaaaaa" stroke-width="2" d="M59.5,-359.61C59.5,-348.77 59.5,-334.6 59.5,-322.29"/>
<polygon fill="#aaaaaa" stroke="#aaaaaa" stroke-width="2" points="63,-322.08 59.5,-312.08 56,-322.08 63,-322.08"/>
<text text-anchor="middle" x="109" y="-333.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#aaaaaa"> &#160;closeness grows</text>
<text text-anchor="middle" x="109" y="-333.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#cccccc"> &#160;closeness grows</text>
</g>
</g>
@ -62,7 +92,7 @@
<path fill="none" stroke="#aaaaaa" stroke-width="2" d="M59.5,-275.61C59.5,-264.77 59.5,-250.6 59.5,-238.29"/>
<polygon fill="#aaaaaa" stroke="#aaaaaa" stroke-width="2" points="63,-238.08 59.5,-228.08 56,-238.08 63,-238.08"/>
<text text-anchor="middle" x="97.5" y="-249.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#aaaaaa"> &#160;trust earned</text>
<text text-anchor="middle" x="97.5" y="-249.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#cccccc"> &#160;trust earned</text>
</g>
</g>
@ -79,7 +109,7 @@
<path fill="none" stroke="#aaaaaa" stroke-width="2" d="M59.5,-191.61C59.5,-180.77 59.5,-166.6 59.5,-154.29"/>
<polygon fill="#aaaaaa" stroke="#aaaaaa" stroke-width="2" points="63,-154.08 59.5,-144.08 56,-154.08 63,-154.08"/>
<text text-anchor="middle" x="107.5" y="-165.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#aaaaaa"> &#160;family accepted</text>
<text text-anchor="middle" x="107.5" y="-165.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#cccccc"> &#160;family accepted</text>
</g>
</g>
@ -96,9 +126,9 @@
<path fill="none" stroke="#aaaaaa" stroke-width="2" d="M59.5,-107.97C59.5,-91.38 59.5,-65.88 59.5,-46.43"/>
<polygon fill="#aaaaaa" stroke="#aaaaaa" stroke-width="2" points="63,-46.34 59.5,-36.34 56,-46.34 63,-46.34"/>
<text text-anchor="middle" x="99" y="-81.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#aaaaaa"> &#160;affection</text>
<text text-anchor="middle" x="99" y="-69.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#aaaaaa"> &#160;hides inside</text>
<text text-anchor="middle" x="99" y="-57.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#aaaaaa"> &#160;fake demand</text>
<text text-anchor="middle" x="99" y="-81.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#cccccc"> &#160;affection</text>
<text text-anchor="middle" x="99" y="-69.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#cccccc"> &#160;hides inside</text>
<text text-anchor="middle" x="99" y="-57.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#cccccc"> &#160;fake demand</text>
</g>
</g>

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Before After
Before After

View file

@ -9,18 +9,84 @@
from { opacity: 0; }
to { opacity: 1; }
}
.anim { opacity: 0; animation: fadeIn 0.8s ease-out forwards; }
.d0 { animation-delay: 0s; }
.d1 { animation-delay: 3s; }
.d2 { animation-delay: 6s; }
.d3 { animation-delay: 7s; }
.d4 { animation-delay: 8s; }
.d5 { animation-delay: 9s; }
.d6 { animation-delay: 10s; }
.d7 { animation-delay: 11s; }
.d8 { animation-delay: 12s; }
.d9 { animation-delay: 13s; }
.d10 { animation-delay: 15s; }
@keyframes loop0 {
0% { opacity: 0; }
2.5% { opacity: 1; }
89.2% { opacity: 1; }
93.8%, 100% { opacity: 0; }
}
@keyframes loop1 {
0%, 9.3% { opacity: 0; }
11.8% { opacity: 1; }
89.2% { opacity: 1; }
93.8%, 100% { opacity: 0; }
}
@keyframes loop2 {
0%, 18.6% { opacity: 0; }
21.1% { opacity: 1; }
89.2% { opacity: 1; }
93.8%, 100% { opacity: 0; }
}
@keyframes loop3 {
0%, 21.7% { opacity: 0; }
24.1% { opacity: 1; }
89.2% { opacity: 1; }
93.8%, 100% { opacity: 0; }
}
@keyframes loop4 {
0%, 24.8% { opacity: 0; }
27.2% { opacity: 1; }
89.2% { opacity: 1; }
93.8%, 100% { opacity: 0; }
}
@keyframes loop5 {
0%, 27.9% { opacity: 0; }
30.3% { opacity: 1; }
89.2% { opacity: 1; }
93.8%, 100% { opacity: 0; }
}
@keyframes loop6 {
0%, 31.0% { opacity: 0; }
33.4% { opacity: 1; }
89.2% { opacity: 1; }
93.8%, 100% { opacity: 0; }
}
@keyframes loop7 {
0%, 34.1% { opacity: 0; }
36.5% { opacity: 1; }
89.2% { opacity: 1; }
93.8%, 100% { opacity: 0; }
}
@keyframes loop8 {
0%, 37.2% { opacity: 0; }
39.6% { opacity: 1; }
89.2% { opacity: 1; }
93.8%, 100% { opacity: 0; }
}
@keyframes loop9 {
0%, 40.2% { opacity: 0; }
42.7% { opacity: 1; }
89.2% { opacity: 1; }
93.8%, 100% { opacity: 0; }
}
@keyframes loop10 {
0%, 46.4% { opacity: 0; }
48.9% { opacity: 1; }
89.2% { opacity: 1; }
93.8%, 100% { opacity: 0; }
}
.anim { opacity: 0; }
.d0 { animation: loop0 32.3s ease-out infinite; }
.d1 { animation: loop1 32.3s ease-out infinite; }
.d2 { animation: loop2 32.3s ease-out infinite; }
.d3 { animation: loop3 32.3s ease-out infinite; }
.d4 { animation: loop4 32.3s ease-out infinite; }
.d5 { animation: loop5 32.3s ease-out infinite; }
.d6 { animation: loop6 32.3s ease-out infinite; }
.d7 { animation: loop7 32.3s ease-out infinite; }
.d8 { animation: loop8 32.3s ease-out infinite; }
.d9 { animation: loop9 32.3s ease-out infinite; }
.d10 { animation: loop10 32.3s ease-out infinite; }
</style>
</defs>
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 701.82)">
@ -28,13 +94,13 @@
<!-- cluster: pretends to contain -->
<g class="anim d1">
<polygon fill="transparent" stroke="#999999" stroke-dasharray="5,2" points="8,-533.91 8,-620.82 162,-620.82 162,-533.91 8,-533.91"/>
<text text-anchor="middle" x="85" y="-608.02" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#aaaaaa">pretends to contain</text>
<text text-anchor="middle" x="85" y="-608.02" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#cccccc">pretends to contain</text>
</g>
<!-- cluster: actually contains -->
<g class="anim d2">
<polygon fill="transparent" stroke="#999999" stroke-dasharray="5,2" points="170,-8 170,-613.37 318,-613.37 318,-8 170,-8"/>
<text text-anchor="middle" x="244" y="-600.57" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#aaaaaa">actually contains</text>
<text text-anchor="middle" x="244" y="-600.57" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#cccccc">actually contains</text>
</g>
<!-- "one" node -->
@ -67,13 +133,13 @@
<g class="anim d2">
<path fill="none" stroke="#aaaaaa" d="M159.03,-661.56C172.35,-642.46 193.86,-611.6 208.45,-590.67"/>
<polygon fill="#aaaaaa" stroke="#aaaaaa" points="210.56,-591.93 212.56,-584.78 206.54,-589.13 210.56,-591.93"/>
<text text-anchor="middle" x="205" y="-632.62" font-family="Times,serif" font-size="14.00" fill="#aaaaaa">holds</text>
<text text-anchor="middle" x="205" y="-632.62" font-family="Times,serif" font-size="14.00" fill="#cccccc">holds</text>
</g>
<!-- photons -->
<g class="anim d3">
<ellipse fill="#fdd835" stroke="#999999" cx="229" cy="-486.91" rx="44.44" ry="18"/>
<text text-anchor="middle" x="229" y="-483.51" font-family="Helvetica,sans-Serif" font-size="13.00" fill="#333333">photons</text>
<text text-anchor="middle" x="229" y="-483.51" font-family="Helvetica,sans-Serif" font-size="13.00" fill="#1a1a1a">photons</text>
</g>
<!-- quantum fields -->
@ -126,9 +192,9 @@
<!-- as above / so below -->
<g class="anim d10">
<polygon fill="transparent" stroke="transparent" points="392.5,-697.82 325.5,-697.82 325.5,-661.82 392.5,-661.82 392.5,-697.82"/>
<text text-anchor="middle" x="359" y="-677.02" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#aaaaaa">as above</text>
<text text-anchor="middle" x="359" y="-677.02" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#cccccc">as above</text>
<polygon fill="transparent" stroke="transparent" points="391.5,-585.37 326.5,-585.37 326.5,-549.37 391.5,-549.37 391.5,-585.37"/>
<text text-anchor="middle" x="359" y="-564.57" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#aaaaaa">so below</text>
<text text-anchor="middle" x="359" y="-564.57" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#cccccc">so below</text>
<path fill="none" stroke="#aaaaaa" stroke-dasharray="1,5" d="M359,-654.29C359,-636.02 359,-611.17 359,-592.9"/>
<polygon fill="#aaaaaa" stroke="#aaaaaa" points="356.55,-654.56 359,-661.56 361.45,-654.56 356.55,-654.56"/>
<polygon fill="#aaaaaa" stroke="#aaaaaa" points="361.45,-592.51 359,-585.51 356.55,-592.51 361.45,-592.51"/>

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Before After
Before After

View file

@ -9,11 +9,35 @@
from { opacity: 0; }
to { opacity: 1; }
}
.anim { opacity: 0; animation: fadeIn 0.8s ease-out forwards; }
.d0 { animation-delay: 0s; }
.d1 { animation-delay: 3s; }
.d2 { animation-delay: 6s; }
.d3 { animation-delay: 9s; }
@keyframes loop0 {
0% { opacity: 0; }
3.0% { opacity: 1; }
86.7% { opacity: 1; }
92.4%, 100% { opacity: 0; }
}
@keyframes loop1 {
0%, 11.4% { opacity: 0; }
14.4% { opacity: 1; }
86.7% { opacity: 1; }
92.4%, 100% { opacity: 0; }
}
@keyframes loop2 {
0%, 22.8% { opacity: 0; }
25.9% { opacity: 1; }
86.7% { opacity: 1; }
92.4%, 100% { opacity: 0; }
}
@keyframes loop3 {
0%, 34.2% { opacity: 0; }
37.3% { opacity: 1; }
86.7% { opacity: 1; }
92.4%, 100% { opacity: 0; }
}
.anim { opacity: 0; }
.d0 { animation: loop0 26.3s ease-out infinite; }
.d1 { animation: loop1 26.3s ease-out infinite; }
.d2 { animation: loop2 26.3s ease-out infinite; }
.d3 { animation: loop3 26.3s ease-out infinite; }
</style>
</defs>
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 262)">
@ -21,9 +45,9 @@
<!-- clusters (always visible) -->
<g class="anim d0">
<polygon fill="transparent" stroke="#999999" stroke-dasharray="5,2" points="0,-12 0,-248 112,-248 112,-12 0,-12"/>
<text text-anchor="middle" x="56" y="-233.6" font-family="Helvetica Bold" font-size="13.00" fill="#aaaaaa">what they write</text>
<text text-anchor="middle" x="56" y="-233.6" font-family="Helvetica Bold" font-size="13.00" fill="#cccccc">what they write</text>
<polygon fill="transparent" stroke="#999999" stroke-dasharray="5,2" points="207,-8 207,-250 377,-250 377,-8 207,-8"/>
<text text-anchor="middle" x="292" y="-235.6" font-family="Helvetica Bold" font-size="13.00" fill="#aaaaaa">what they mean</text>
<text text-anchor="middle" x="292" y="-235.6" font-family="Helvetica Bold" font-size="13.00" fill="#cccccc">what they mean</text>
</g>
<!-- "we" -> I but lonelier -->
@ -32,7 +56,7 @@
<text text-anchor="middle" x="56" y="-196.3" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">&quot;we&quot;</text>
<path fill="none" stroke="#aaaaaa" d="M83.39,-200.23C118.02,-200.52 179.94,-201.05 227.18,-201.45"/>
<polygon fill="#aaaaaa" stroke="#aaaaaa" points="227.29,-204.96 237.32,-201.54 227.35,-197.96 227.29,-204.96"/>
<text text-anchor="middle" x="159.5" y="-204.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#aaaaaa"> translates to</text>
<text text-anchor="middle" x="159.5" y="-204.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#cccccc"> translates to</text>
<path fill="#2c3e50" stroke="#999999" d="M334.5,-220C334.5,-220 249.5,-220 249.5,-220 243.5,-220 237.5,-214 237.5,-208 237.5,-208 237.5,-196 237.5,-196 237.5,-190 243.5,-184 249.5,-184 249.5,-184 334.5,-184 334.5,-184 340.5,-184 346.5,-190 346.5,-196 346.5,-196 346.5,-208 346.5,-208 346.5,-214 340.5,-220 334.5,-220"/>
<text text-anchor="middle" x="292" y="-198.3" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">I, but lonelier</text>
</g>
@ -43,7 +67,7 @@
<text text-anchor="middle" x="56" y="-142.3" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">&quot;our&quot;</text>
<path fill="none" stroke="#aaaaaa" d="M83.39,-146.11C112.71,-146.24 161.6,-146.45 204.57,-146.63"/>
<polygon fill="#aaaaaa" stroke="#aaaaaa" points="204.77,-150.13 214.78,-146.67 204.8,-143.13 204.77,-150.13"/>
<text text-anchor="middle" x="159.5" y="-149.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#aaaaaa"> translates to</text>
<text text-anchor="middle" x="159.5" y="-149.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#cccccc"> translates to</text>
<path fill="#2c3e50" stroke="#999999" d="M357,-166C357,-166 227,-166 227,-166 221,-166 215,-160 215,-154 215,-154 215,-140 215,-140 215,-134 221,-128 227,-128 227,-128 357,-128 357,-128 363,-128 369,-134 369,-140 369,-140 369,-154 369,-154 369,-160 363,-166 357,-166"/>
<text text-anchor="middle" x="292" y="-150.8" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">mine, but I want</text>
<text text-anchor="middle" x="292" y="-135.8" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">you to feel invested</text>
@ -55,7 +79,7 @@
<text text-anchor="middle" x="56" y="-88.3" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">&quot;one&quot;</text>
<path fill="none" stroke="#aaaaaa" d="M84.3,-91.88C122.38,-91.72 192.13,-91.42 239.99,-91.22"/>
<polygon fill="#aaaaaa" stroke="#aaaaaa" points="240.16,-94.72 250.15,-91.17 240.13,-87.72 240.16,-94.72"/>
<text text-anchor="middle" x="159.5" y="-94.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#aaaaaa"> translates to</text>
<text text-anchor="middle" x="159.5" y="-94.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#cccccc"> translates to</text>
<path fill="#2c3e50" stroke="#999999" d="M321.5,-110C321.5,-110 262.5,-110 262.5,-110 256.5,-110 250.5,-104 250.5,-98 250.5,-98 250.5,-84 250.5,-84 250.5,-78 256.5,-72 262.5,-72 262.5,-72 321.5,-72 321.5,-72 327.5,-72 333.5,-78 333.5,-84 333.5,-84 333.5,-98 333.5,-98 333.5,-104 327.5,-110 321.5,-110"/>
<text text-anchor="middle" x="292" y="-94.8" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">I&#39;m afraid</text>
<text text-anchor="middle" x="292" y="-79.8" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">to say I</text>
@ -67,7 +91,7 @@
<text text-anchor="middle" x="56" y="-34.3" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">&quot;you&quot;</text>
<path fill="none" stroke="#aaaaaa" d="M84.3,-37.65C113.99,-37.27 162.94,-36.64 205.75,-36.09"/>
<polygon fill="#aaaaaa" stroke="#aaaaaa" points="205.96,-39.59 215.91,-35.96 205.87,-32.59 205.96,-39.59"/>
<text text-anchor="middle" x="159.5" y="-40.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#aaaaaa"> translates to</text>
<text text-anchor="middle" x="159.5" y="-40.2" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#cccccc"> translates to</text>
<path fill="#2c3e50" stroke="#999999" d="M356,-54C356,-54 228,-54 228,-54 222,-54 216,-48 216,-42 216,-42 216,-28 216,-28 216,-22 222,-16 228,-16 228,-16 356,-16 356,-16 362,-16 368,-22 368,-28 368,-28 368,-42 368,-42 368,-48 362,-54 356,-54"/>
<text text-anchor="middle" x="292" y="-38.8" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">talking to myself</text>
<text text-anchor="middle" x="292" y="-23.8" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">6 months from now</text>

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Before After
Before After

View file

@ -12,12 +12,42 @@
from { opacity: 0; }
to { opacity: 1; }
}
.anim { opacity: 0; animation: fadeIn 0.8s ease-out forwards; }
.d0 { animation-delay: 0s; }
.d1 { animation-delay: 3s; }
.d2 { animation-delay: 6s; }
.d3 { animation-delay: 9s; }
.d4 { animation-delay: 12s; }
@keyframes loop0 {
0% { opacity: 0; }
2.7% { opacity: 1; }
88.1% { opacity: 1; }
93.2%, 100% { opacity: 0; }
}
@keyframes loop1 {
0%, 10.2% { opacity: 0; }
13.0% { opacity: 1; }
88.1% { opacity: 1; }
93.2%, 100% { opacity: 0; }
}
@keyframes loop2 {
0%, 20.5% { opacity: 0; }
23.2% { opacity: 1; }
88.1% { opacity: 1; }
93.2%, 100% { opacity: 0; }
}
@keyframes loop3 {
0%, 30.7% { opacity: 0; }
33.4% { opacity: 1; }
88.1% { opacity: 1; }
93.2%, 100% { opacity: 0; }
}
@keyframes loop4 {
0%, 41.0% { opacity: 0; }
43.7% { opacity: 1; }
88.1% { opacity: 1; }
93.2%, 100% { opacity: 0; }
}
.anim { opacity: 0; }
.d0 { animation: loop0 29.3s ease-out infinite; }
.d1 { animation: loop1 29.3s ease-out infinite; }
.d2 { animation: loop2 29.3s ease-out infinite; }
.d3 { animation: loop3 29.3s ease-out infinite; }
.d4 { animation: loop4 29.3s ease-out infinite; }
</style>
</defs>
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 364)">
@ -70,7 +100,7 @@
<path fill="none" stroke="#aaaaaa" stroke-dasharray="1,5" d="M100.5,-321.66C96.53,-308.43 91.05,-290.18 86.29,-274.29"/>
<polygon fill="#aaaaaa" stroke="#aaaaaa" points="89.5,-272.83 83.28,-264.26 82.8,-274.84 89.5,-272.83"/>
<text text-anchor="middle" x="113.5" y="-290.5" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#aaaaaa">implies</text>
<text text-anchor="middle" x="113.5" y="-290.5" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#cccccc">implies</text>
</g>
</g>
@ -79,7 +109,7 @@
<g id="clust1" class="cluster">
<polygon fill="transparent" stroke="#999999" stroke-dasharray="5,2" points="87,-8 87,-177 225,-177 225,-8 87,-8"/>
<text text-anchor="middle" x="156" y="-164.2" font-family="Helvetica Bold" font-size="11.00" fill="#aaaaaa">behind the curtain</text>
<text text-anchor="middle" x="156" y="-164.2" font-family="Helvetica Bold" font-size="11.00" fill="#cccccc">behind the curtain</text>
</g>
<!-- ml -->
<g id="node4" class="node">
@ -95,7 +125,7 @@
<path fill="none" stroke="#aaaaaa" d="M192.71,-204.92C191.08,-201.92 189.49,-198.92 188,-196 181.85,-183.93 175.61,-170.43 170.29,-158.47"/>
<polygon fill="#aaaaaa" stroke="#aaaaaa" points="189.72,-206.75 197.64,-213.79 195.84,-203.35 189.72,-206.75"/>
<polygon fill="#aaaaaa" stroke="#aaaaaa" points="173.48,-157.01 166.25,-149.26 167.07,-159.82 173.48,-157.01"/>
<text text-anchor="middle" x="213.5" y="-188" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#aaaaaa">prompting</text>
<text text-anchor="middle" x="213.5" y="-188" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#cccccc">prompting</text>
</g>
</g>
@ -112,14 +142,14 @@
<path fill="none" stroke="#aaaaaa" d="M230.8,-213.83C234.17,-208.26 237.19,-202.15 239,-196 254.94,-141.72 251.95,-117.35 221,-70 216.89,-63.72 211.22,-58.3 205.11,-53.74"/>
<polygon fill="#aaaaaa" stroke="#aaaaaa" points="206.87,-50.7 196.61,-48.04 202.97,-56.51 206.87,-50.7"/>
<text text-anchor="middle" x="263.5" y="-121.5" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#aaaaaa">typing</text>
<text text-anchor="middle" x="263.5" y="-121.5" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#cccccc">typing</text>
</g>
<!-- ml&#45;&gt;terminal -->
<g id="edge5" class="edge">
<path fill="none" stroke="#aaaaaa" d="M159.28,-98.95C160.83,-87.58 162.69,-73.94 164.29,-62.19"/>
<polygon fill="#aaaaaa" stroke="#aaaaaa" points="167.79,-62.43 165.68,-52.05 160.86,-61.48 167.79,-62.43"/>
<text text-anchor="middle" x="190.5" y="-73" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#aaaaaa">generating</text>
<text text-anchor="middle" x="190.5" y="-73" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#cccccc">generating</text>
</g>
</g>

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Before After
Before After

102
content/uploads/2026/03/unbury.py Executable file
View file

@ -0,0 +1,102 @@
#!/usr/bin/env python3
"""unbury - exhume contributor stats from a graveyard of git repos.
Usage:
unbury [path] # defaults to current directory
unbury /home/fox/git/pylons
"""
import os
import subprocess
import sys
from collections import defaultdict
from datetime import datetime, timedelta
PERIODS = [
("3 months", 90),
("1 year", 365),
("3 years", 365 * 3),
("6 years", 365 * 6),
("18 years", 365 * 18),
("lifetime", None),
]
def git_contributors(repo_path, since=None):
"""Return set of (name, email) tuples for a repo."""
cmd = ["git", "-C", repo_path, "log", "--format=%aN\t%aE"]
if since:
cmd.append(f"--since={since}")
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
contributors = set()
for line in result.stdout.strip().split("\n"):
if "\t" in line:
name, email = line.split("\t", 1)
contributors.add((name.strip(), email.strip().lower()))
return contributors
except (subprocess.TimeoutExpired, Exception):
return set()
def find_repos(root):
"""Find all git repos (immediate subdirs with .git)."""
repos = []
for entry in sorted(os.listdir(root)):
path = os.path.join(root, entry)
if os.path.isdir(path) and os.path.isdir(os.path.join(path, ".git")):
repos.append(path)
return repos
def main():
root = sys.argv[1] if len(sys.argv) > 1 else os.getcwd()
root = os.path.abspath(root)
repos = find_repos(root)
if not repos:
print(f"no git repos found in {root}")
sys.exit(1)
print(f"scanning {len(repos)} repos in {root}\n")
now = datetime.now()
for label, days in PERIODS:
since = (now - timedelta(days=days)).strftime("%Y-%m-%d") if days else None
all_contributors = set()
repo_counts = {}
for repo in repos:
contribs = git_contributors(repo, since)
if contribs:
repo_name = os.path.basename(repo)
repo_counts[repo_name] = len(contribs)
all_contributors.update(contribs)
active_repos = len(repo_counts)
print(f"{'' * 50}")
print(f"{label}: {len(all_contributors)} unique contributors across {active_repos} active repos")
if all_contributors:
# count contributions per person across repos
person_repos = defaultdict(list)
for repo in repos:
contribs = git_contributors(repo, since)
repo_name = os.path.basename(repo)
for c in contribs:
person_repos[c].append(repo_name)
# sort by number of repos touched
ranked = sorted(person_repos.items(), key=lambda x: -len(x[1]))
top = ranked[:10]
for (name, email), touched in top:
print(f" {name:30s} {len(touched):3d} repos ({email})")
if len(ranked) > 10:
print(f" ... and {len(ranked) - 10} more")
print(f"{'' * 50}")
if __name__ == "__main__":
main()