Something went wrong. Try again.
Central platform for European atproto.<cc> country community websites
Something went wrong. Try again.
11 kB · 322 lines
Astro
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323---// What the accounts themselves say: when they were created, what they run on, and which// lexicons they use beyond Bluesky. All of it read from the PLC directory and the repos,// so it is history we could read today rather than history we have to accumulate.//// Every block is conditional. Before the first collection run these render nothing at// all rather than empty axes, and they populate on their own once the data lands.import { nlStats } from '../../data/nl-stats';import EuroskyCta from '../EuroskyCta.astro';import { nlFacts, hasCreation, hasLexicons, hasPds, hasAnyFacts, creationSeries,} from '../../data/nl-facts';
interface Props { lang: 'en' | 'nl';}const { lang } = Astro.props;const isNl = lang === 'nl';const nf = (n: number) => n.toLocaleString('nl-NL');
const series = creationSeries();const peak = Math.max(1, ...series.map((s) => s.count));const lex = (nlFacts.lexicons.byApp ?? []).slice(0, 14);const lexPeak = Math.max(1, ...lex.map((l) => l.accounts));// The record types behind the bars, for anyone who wants the literal answer.const lexDetail = nlFacts.lexicons.byNsid.slice(0, 30);// The bars rank the apps against each other, which hides how small the whole group is.// Stated once, above them, so the ranking is read in proportion.const confirmedTotal = nlStats.identified.total;const beyondPct = confirmedTotal ? Math.round((nlFacts.lexicons.accountsUsingSomething / confirmedTotal) * 100) : 0;const pdsTotal = nlFacts.pds.blueskyHosted + nlFacts.pds.elsewhere;const elsewherePct = pdsTotal ? Math.round((nlFacts.pds.elsewhere / pdsTotal) * 1000) / 10 : 0;const bskyPct = pdsTotal ? Math.round((nlFacts.pds.blueskyHosted / pdsTotal) * 1000) / 10 : 0;
// Label roughly every sixth month, so the axis stays readable at any range length.const tickEvery = Math.max(1, Math.ceil(series.length / 8));
const t = { h2: isNl ? 'Wat deze accounts laten zien' : 'What these accounts show', intro: isNl ? `Elke did:plc-identiteit heeft een openbaar logboek, en elke repo vertelt welke collecties erin zitten. Daaruit valt af te lezen wanneer deze accounts zijn aangemaakt, waar ze draaien en welke apps ze gebruiken. Dit gaat over de ${nf(nlStats.identified.total)} bevestigde accounts, niet over alle Nederlandse accounts.` : `Every did:plc identity has a public log, and every repo lists the collections it holds. That tells us when these accounts were created, where they run, and which apps they use. This covers the ${nf(nlStats.identified.total)} confirmed accounts, not every Dutch account.`, h3created: isNl ? 'Wanneer deze accounts zijn aangemaakt' : 'When these accounts were created', createdCap: isNl ? `Aanmaakdatum uit de PLC-directory, per maand. Dit is wanneer het account is aangemaakt, niet wanneer wij het herkenden.` : `Creation date from the PLC directory, by month. This is when the account was made, not when we identified it.`, h3lex: isNl ? 'Welke apps ze gebruiken, naast Bluesky' : 'Which apps they use, beyond Bluesky', lexCap: isNl ? `Aantal accounts met records van die app in hun repo, elk account één keer per app geteld. Bluesky's eigen collecties zijn weggelaten: bijna iedereen heeft die. Dit telt accounts, geen records.` : `Accounts holding records from that app in their repo, counted once per app however many record types they hold. Bluesky's own collections are left out, since nearly everyone has them. This counts accounts, not records.`, lexBlind: isNl ? `Dit ziet alleen apps die hun eigen records schrijven. Een client die Bluesky-data alleen leest, zoals Skywalker of Graysky, laat niets achter in een repo en ontbreekt hier dus, hoe vaak hij ook gebruikt wordt.` : `This only sees apps that write their own records. A client that just reads Bluesky data, like Skywalker or Graysky, leaves nothing in a repo and so cannot appear here, however heavily it is used.`, lexDetail: isNl ? 'Per recordtype' : 'By record type', beyondStat: isNl ? `Maar ${nf(nlFacts.lexicons.accountsUsingSomething)} accounts (${beyondPct}%) van de ${nf(confirmedTotal)} gebruiken iets anders dan Bluesky. Als ze dat doen, is dit wat ze gebruiken:` : `Only ${nf(nlFacts.lexicons.accountsUsingSomething)} accounts (${beyondPct}%) of the ${nf(confirmedTotal)} use anything other than Bluesky. When they do, this is what they use:`, lexNone: isNl ? 'Nog niet genoeg repos gelezen om hier iets zinnigs te tonen.' : 'Not enough repos read yet to show anything meaningful here.', h3pds: isNl ? 'Waar ze draaien' : 'Where they run', pdsCap: isNl ? `${nf(nlFacts.pds.elsewhere)} accounts (${elsewherePct}%) staan op een PDS buiten Bluesky's eigen hosting.` : `${nf(nlFacts.pds.elsewhere)} accounts (${elsewherePct}%) live on a PDS outside Bluesky's own hosting.`, bskyHosted: isNl ? 'Bluesky-hosting' : 'Bluesky hosted', elsewhere: isNl ? 'elders' : 'elsewhere', accounts: isNl ? 'accounts' : 'accounts',};---
{ hasAnyFacts && ( <section class="section prose" id="facts"> <h2>{t.h2}</h2> <p>{t.intro}</p>
{hasCreation && ( <> <h3>{t.h3created}</h3> <figure class="chart"> <div class="cols" role="img" aria-label={t.createdCap}> {series.map((point) => ( <div class="col" title={`${point.month}: ${nf(point.count)}`}> <div class="col-bar" style={`height:${(point.count / peak) * 100}%`} /> </div> ))} </div> <div class="axis"> {series.map((point, i) => ( <span class="tick">{i % tickEvery === 0 ? point.month.replace('-', '/') : ''}</span> ))} </div> <figcaption>{t.createdCap}</figcaption> </figure> </> )}
{hasLexicons && ( <> <h3>{t.h3lex}</h3> <p class="beyond-stat">{t.beyondStat}</p> <div class="lex"> {lex.map((row) => ( <div class="lex-row"> <span class="lex-name">{row.app}</span> <span class="lex-track"> <span class="lex-fill" style={`width:${(row.accounts / lexPeak) * 100}%`} /> </span> <span class="lex-num">{nf(row.accounts)}</span> </div> ))} </div> <p class="chart-note">{t.lexCap} {t.lexBlind}</p> {lexDetail.length > 0 && ( <details class="lex-detail"> <summary>{t.lexDetail}</summary> <ul class="hosts"> {lexDetail.map((row) => ( <li> <code>{row.nsid}</code>{' '} <span class="lex-nsid"> {nf(row.accounts)} {t.accounts} </span> </li> ))} </ul> </details> )} </> )}
{hasPds && ( <> <h3>{t.h3pds}</h3> <div class="split" role="img" aria-label={t.pdsCap}> <span class="split-bsky" style={`width:${(nlFacts.pds.blueskyHosted / pdsTotal) * 100}%`} /> <span class="split-else" style={`width:${(nlFacts.pds.elsewhere / pdsTotal) * 100}%`} /> </div> <div class="split-key"> <span><i class="key-bsky" /> {t.bskyHosted} {bskyPct}%</span> <span><i class="key-else" /> {t.elsewhere} {elsewherePct}%</span> </div> <p class="chart-note">{t.pdsCap}</p> {nlFacts.pds.topOtherHosts.length > 0 && ( <ul class="hosts" data-hosts> {nlFacts.pds.topOtherHosts.map((h) => ( <li> <code>{h.host}</code> <span class="lex-nsid">{nf(h.accounts)} {t.accounts}</span> {h.host === 'eurosky.social' && <EuroskyCta lang={lang} />} </li> ))} </ul> )} </> )} </section> )}
<style> .chart { margin: var(--space-5) 0 var(--space-6); } .cols { display: flex; align-items: flex-end; gap: 2px; height: 180px; border-bottom: 1px solid var(--rule); } .col { flex: 1 1 0; min-width: 0; height: 100%; display: flex; align-items: flex-end; } .col-bar { width: 100%; background: var(--brand); border-radius: 2px 2px 0 0; min-height: 1px; } .axis { display: flex; gap: 2px; margin-top: var(--space-2); } .tick { flex: 1 1 0; min-width: 0; font-family: var(--mono); font-size: 0.6875rem; color: var(--muted); white-space: nowrap; } figcaption, .chart-note { font-family: var(--mono); font-size: 0.75rem; color: var(--muted); margin-top: var(--space-2); max-width: 42em; } .lex { display: grid; gap: var(--space-2); margin: var(--space-5) 0 0; } .lex-row { display: grid; grid-template-columns: minmax(0, 16rem) 1fr 4rem; gap: var(--space-4); align-items: center; } .lex-name { font-family: var(--mono); font-size: 0.875rem; color: var(--inkstrong); overflow-wrap: anywhere; } .beyond-stat { margin: var(--space-3) 0 0; max-width: 42em; } .lex-detail summary { font-family: var(--mono); font-size: 0.75rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); cursor: pointer; margin-top: var(--space-3); } .lex-nsid { color: var(--muted); font-size: 0.75rem; } .lex-track { display: block; height: 10px; border-radius: var(--radius-pill); background: color-mix(in oklab, var(--brand) 12%, transparent); overflow: hidden; } .lex-fill { display: block; height: 100%; background: var(--brand); border-radius: var(--radius-pill); } .lex-num { font-family: var(--mono); font-size: 0.875rem; text-align: right; font-variant-numeric: tabular-nums; color: var(--inkstrong); } .split { display: flex; height: 14px; border-radius: var(--radius-pill); overflow: hidden; margin: var(--space-5) 0 var(--space-2); } .split-bsky { background: color-mix(in oklab, var(--brand) 38%, transparent); } .split-else { background: var(--accent); } .split-key { display: flex; flex-wrap: wrap; gap: var(--space-4); font-family: var(--mono); font-size: 0.75rem; color: var(--muted); } .split-key span { display: inline-flex; align-items: center; gap: var(--space-2); } .split-key i { width: 14px; height: 10px; border-radius: var(--radius-pill); } .key-bsky { background: color-mix(in oklab, var(--brand) 38%, transparent); } .key-else { background: var(--accent); } .hosts { list-style: none; padding: 0; margin: var(--space-3) 0 0; display: grid; gap: var(--space-1); font-size: 0.875rem; } @media (max-width: 720px) { .lex-row { grid-template-columns: 1fr 4rem; } .lex-track { grid-column: 1 / -1; } .cols { height: 130px; } }</style>