lvwerra HF Staff commited on
Commit
e5675eb
·
verified ·
1 Parent(s): c5eee7b

Present the tree with a wide title and resource links; remove sweep counters and playback button

Browse files
.gitattributes CHANGED
@@ -49,3 +49,5 @@ sweep-complete.png filter=lfs diff=lfs merge=lfs -text
49
  sweep-detail.png filter=lfs diff=lfs merge=lfs -text
50
  dial-selection.png filter=lfs diff=lfs merge=lfs -text
51
  download-detail.png filter=lfs diff=lfs merge=lfs -text
 
 
 
49
  sweep-detail.png filter=lfs diff=lfs merge=lfs -text
50
  dial-selection.png filter=lfs diff=lfs merge=lfs -text
51
  download-detail.png filter=lfs diff=lfs merge=lfs -text
52
+ presentation-desktop.png filter=lfs diff=lfs merge=lfs -text
53
+ presentation-mobile.png filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -1,5 +1,5 @@
1
  ---
2
- title: Carbon-A Assembly Strands
3
  emoji: 🌿
4
  colorFrom: green
5
  colorTo: blue
@@ -12,9 +12,9 @@ pinned: false
12
 
13
  An interactive proof of concept: a separate, continuous strand for every real assembly in the reusable taxonomy snapshot. Shared ancestors form bundles; zooming separates the strands. Green and blue distinguish assemblies with and without existing annotation evidence under the selected comparison.
14
 
15
- The PoC contains **33,588 unique assemblies across 16,976 taxa**, taken from the existing [GenBank annotation explorer](https://huggingface.co/spaces/HuggingFaceBio/genbank-annotation-explorer). The full release totals supplied on 8 October 2026 are **48,167 assemblies, 22,617 taxa, 566.34M coding loci and 27.18 Tbp**. Those totals are displayed separately. The new radial bars use published per-assembly `gene_count` values wherever available; missing counts are explicitly marked.
16
 
17
- Two comparison modes are available:
18
 
19
  - **NCBI annotation records:** blue means a named annotation provider appears in the cached NCBI GenBank assembly summary. Missing records are not proof that an assembly has never been annotated.
20
  - **RefSeq comparison corpus:** blue means an exact versioned match between the listed paired RefSeq accession and the project's 2,055-assembly comparison corpus. This corpus is not the whole of RefSeq.
@@ -27,17 +27,19 @@ The human assembly detail uses actual published GFF3 coordinates, with 23,767 pr
27
 
28
  ## Locus bars and atlas silhouettes
29
 
30
- The outer annulus aligns to the exact same ordered assembly tips as the tree. Published counts are available for **17,828 of the 33,588 displayed assemblies**, summing to **212,905,534 predicted coding loci**; the other 15,760 assemblies have unavailable counts in this metadata inventory. The default sums available locus counts across **5 consecutive assemblies**; 20-, 50- and 200-assembly bins and individual assembly bars are selectable. Bins partition taxonomic display order, and can straddle named taxon boundaries. Bar length uses a linear scale, recalculated for each bin size and labelled above the tree. Hover reveals the count and number of measured assemblies. Clicking a bin switches to individual bars and zooms into its strands.
31
 
32
  Grey marks at the bar bases indicate missing counts. For bins, the grey mark spans the fraction of assemblies without measurements. A bin sums only available values: missing counts are not imputed, treated as measured zero, or scaled up to the full release. Green locus bars count all predicted loci in the covered assemblies; the blue/green colouring of the tree still describes annotation records at assembly level, not whether individual loci are new.
33
 
34
- The original **Genome Atlas silhouettes**, without circle backgrounds, sit on their corresponding taxonomic nodes. Clicking an icon focuses the branch. Icons appear progressively with zoom and are hidden when they would overlap; the checkbox hides them all. All original PhyloPic source links and licence/attribution records are retained in the interface under **About the data → Icon credits and licences** and in the source data. The original SVG artwork is preserved; its displayed fill is adapted to each theme with an alpha mask, without backgrounds.
35
 
36
- ## Sweep animation
37
 
38
- **Animate sweep** returns to the whole tree and makes one clockwise turn in 4 seconds. The thin centre hand follows the changing highlighted strand. Species, assembly and coding-locus counters accumulate as it moves. Every crossed assembly is counted, including those passed between animation frames; each species is counted once using its NCBI species-rank ancestor. Missing locus counts remain excluded. The completed subset totals are **16,374 species, 33,588 assemblies and 212,905,534 available coding loci** (across 17,828 assemblies with counts). Species-rank ancestors are deduplicated separately from the 16,976 submitted taxon IDs.
39
 
40
- Replay starts the counters from zero. The button stops a running sweep; Escape, zooming or direct chart interaction also stop it. Normal hover resumes afterwards. A separate interaction canvas draws the hand and exact strand highlight, leaving the detailed static tree in place throughout playback.
 
 
41
 
42
  ## Layout and implementation
43
 
@@ -59,10 +61,10 @@ Assemblies without a recorded gene-file path offer **Check annotation files** in
59
 
60
  ## Full-release migration status
61
 
62
- The full release inventory has not yet been located. A fresh, dated directory audit is saved in `data/release-source-audit.json`: the published gene-file bucket currently exposes **18,662 assembly directories**, and the older explorer provides the **33,588** strands shown here. Neither is the announced **48,167-assembly** release manifest. The drawing and sweep therefore remain explicitly labelled as a subset, without scaling or synthetic rows. Completing the migration requires the full per-assembly inventory, taxon IDs and candidate-locus counts (plus annotation paths where available).
63
 
64
  ## Rebuild
65
 
66
- `python prepare_data.py` reproduces the compact dataset from the copied taxonomy database and cached NCBI summary (paths at the top of the script). `python build.py` builds the self-contained HTML from `template.html`, `app.js`, `layers.js`, `theme.js`, `sweep.js`, `access.js`, the data files, and the existing release font assets. `python check_dial.py` verifies centre hover, pinning, dragging without panning, zoomed coordinates, touch, the top seam and takeover from playback. `python check_sweep.py` verifies hand alignment, cumulative counts, replay, cancellation, timing and stationary background rendering. `python check_hover_theme.py` verifies hover previews, frozen selections, the empty centre, theme persistence and continuous subpixel bars. `python check_selection.py` verifies neutral/selection/drag cursor states, the human comparison counts, and alignment of the Canvas highlight with the rendered WebGL path. `python check.py` runs the base browser checks; `python check_layers.py` checks bin totals, missing-count handling, raw/binned switching, clickable bars, node icons and attribution links. `python fetch_loci.py` refreshes the available metadata counts and `python prepare_icons.py` prepares the atlas icons.
67
 
68
- Data provenance and limitations are recorded in `provenance.json`. The original release video and explorer were not modified.
 
1
  ---
2
+ title: Carbon-A — Across the tree of life
3
  emoji: 🌿
4
  colorFrom: green
5
  colorTo: blue
 
12
 
13
  An interactive proof of concept: a separate, continuous strand for every real assembly in the reusable taxonomy snapshot. Shared ancestors form bundles; zooming separates the strands. Green and blue distinguish assemblies with and without existing annotation evidence under the selected comparison.
14
 
15
+ The PoC contains **33,588 unique assemblies across 16,976 taxa**, taken from the existing [GenBank annotation explorer](https://huggingface.co/spaces/HuggingFaceBio/genbank-annotation-explorer). The full release totals supplied on 8 October 2026 are **48,167 assemblies, 22,617 taxa, 566.34M coding loci and 27.18 Tbp**. These supplied totals are recorded in About the data; the presentation does not display an aggregate counter. The new radial bars use published per-assembly `gene_count` values wherever available; missing counts are explicitly marked.
16
 
17
+ Two comparison modes are available under **View options**:
18
 
19
  - **NCBI annotation records:** blue means a named annotation provider appears in the cached NCBI GenBank assembly summary. Missing records are not proof that an assembly has never been annotated.
20
  - **RefSeq comparison corpus:** blue means an exact versioned match between the listed paired RefSeq accession and the project's 2,055-assembly comparison corpus. This corpus is not the whole of RefSeq.
 
27
 
28
  ## Locus bars and atlas silhouettes
29
 
30
+ The outer annulus aligns to the exact same ordered assembly tips as the tree. Published counts are available for **17,828 of the 33,588 displayed assemblies**, summing to **212,905,534 predicted coding loci**; the other 15,760 assemblies have unavailable counts in this metadata inventory. The default sums available locus counts across **5 consecutive assemblies**; 20-, 50- and 200-assembly bins and individual assembly bars are selectable in View options. Bins partition taxonomic display order, and can straddle named taxon boundaries. Bar length uses a linear scale, recalculated for each bin size and labelled above the tree. Hover reveals the count and number of measured assemblies. Clicking a bin switches to individual bars and zooms into its strands.
31
 
32
  Grey marks at the bar bases indicate missing counts. For bins, the grey mark spans the fraction of assemblies without measurements. A bin sums only available values: missing counts are not imputed, treated as measured zero, or scaled up to the full release. Green locus bars count all predicted loci in the covered assemblies; the blue/green colouring of the tree still describes annotation records at assembly level, not whether individual loci are new.
33
 
34
+ The original **Genome Atlas silhouettes**, without circle backgrounds, sit on their corresponding taxonomic nodes. Clicking an icon focuses the branch. Icons appear progressively with zoom and are hidden when they would overlap; the checkbox in View options hides them all. All original PhyloPic source links and licence/attribution records are retained in the interface under **About the data → Icon credits and licences** and in the source data. The original SVG artwork is preserved; its displayed fill is adapted to each theme with an alpha mask, without backgrounds.
35
 
36
+ ## Presentation and video capture
37
 
38
+ The Space uses a single wide title, a centred full-width tree and five resource buttons: **Model, Database, Explorer, Paper and Blog**. The sidebar, animation button and all cumulative sweep counters have been removed. Search, bar settings, comparison modes and zoom controls are available under **View options**. **About the data** retains the subset limitations and icon credits.
39
 
40
+ Click the page background, then press **Space** to start or replay a four-second clockwise sweep; Space again or Escape stops it. A thin centre hand follows the selected strand. Playback has no aggregate counters. Only the interaction canvas redraws each frame; the detailed tree stays cached.
41
+
42
+ For browser automation, `POC.startSweep()` and `POC.stopSweep(false)` provide the same controls. On the app URL, `?record=1` hides the small utility controls and suppresses mouse interactions for a clean recording; adding `&animate=1` starts one sweep a second after loading. Resource links and the title remain visible. These parameters apply to the app itself; the normal private Space page remains the entry point for signed-in users.
43
 
44
  ## Layout and implementation
45
 
 
61
 
62
  ## Full-release migration status
63
 
64
+ This layout change retains the 33,588 real assembly strands and available per-assembly locus counts; it does not claim that the drawing covers the complete release. The newer October 7 prediction index was found in the same bucket and contains 49,829 assemblies, but its coverage still needs reconciliation with the announced 48,167-assembly release. Published and intermediate gene calls can be reused, and a reconstruction test matched 120 published loci exactly. No full-dataset reconstruction is required or launched for this presentation update.
65
 
66
  ## Rebuild
67
 
68
+ `python build.py` builds the self-contained HTML from the template, JavaScript, compact data and embedded release fonts. `python check_presentation.py` checks desktop/mobile layout, resource buttons, search, frozen selection, annotation access, themes and the counter-free sweep. `python export_still.py` exports the current presentation as a 2160 × 2700 PNG. `python verify_live.py` repeats the current checks against the published private Space.
69
 
70
+ Earlier exploratory checks are retained for reference; their sidebar and counter expectations describe previous revisions. Data provenance and limitations are recorded in `provenance.json`. The original release video and database explorer are separate projects.
app.js CHANGED
@@ -88,7 +88,8 @@ function nearest(x,y,radius=7){
88
  if(d<best){best=d;found=leaves[hitOwner[k/8]];}
89
  }return found;
90
  }
91
- function focusAssembly(a,z=70){stopSweep(false);selected=a;zoom=z;let s=baseScale*zoom;pan={x:-a.tip[0]*s,y:-a.tip[1]*s};selectAssembly(a);schedule()}
 
92
  function clearSelection(){selected=pinnedAssembly=hoveredAssembly=null;$('#selection').hidden=true;scheduleInteraction()}
93
  function hoverAssembly(a){
94
  if(sweep.running||pinnedAssembly)return;
@@ -105,7 +106,7 @@ function selectAssembly(a,pin=true){if(!a)return;if(pin)pinnedAssembly=a;selecte
105
  updateDataAccess(a);$('#sel-loci').hidden=a.accession!=='GCA_000001405.29';scheduleInteraction()}
106
  function updateLegend(){let n=mode?D.meta.refseq_matches:D.meta.classification_counts['1'];$('#blue-count').textContent=fmt(n);$('#green-count').textContent=fmt(leaves.length-n);$('#blue-label').textContent=mode?'In RefSeq comparison corpus':'GenBank annotation record';$('#green-label').textContent=mode?'Outside that comparison corpus':'No GenBank record listed';$('#scope-note').textContent=mode?'Counts are assemblies, not genes. Blue marks exact paired-accession matches to the project’s 2,055-assembly RefSeq corpus, not all RefSeq annotations. Green does not imply new genes.':'Counts are assemblies, not genes. This view checks GenBank submission metadata; it does not include annotations stored only on paired RefSeq records. Missing metadata is not proof of no prior annotation.';if(selected&&!sweep.running)selectAssembly(selected,false);schedule()}
107
  function search(q){q=q.trim().toLowerCase();let out=[];if(q.length>1){out=leaves.filter(a=>a.accession.toLowerCase().includes(q)||a.name.toLowerCase().includes(q)||(nodes.get(a.taxid).common||'').toLowerCase().includes(q)).slice(0,7);}$('#results').replaceChildren(...out.map(a=>{let b=document.createElement('button');b.innerHTML=`<strong>${esc(a.name)}</strong><span>${esc(a.accession)}</span>`;b.onclick=()=>{focusAssembly(a);$('#results').replaceChildren();$('#search').value=''};return b}));if(q.length>1&&!out.length)$('#results').textContent='No match in this PoC subset.';}
108
- function geneModal(){stopSweep();const g=D.example;$('#gene-dialog').showModal();$('#gene-summary').textContent=fmt(g.gene_count)+' candidate coding loci in this assembly';const svg=$('#gene-svg');let start=0,end=1000000,width=1100,left=30,right=1080;let x=p=>left+(p-start)/(end-start)*(right-left);let parts=[];for(let p=0;p<=end;p+=100000){parts.push(`<line x1="${x(p)}" y1="38" x2="${x(p)}" y2="198" stroke="${palette.grid}"/><text x="${x(p)}" y="24" text-anchor="middle">${p/1e6} Mb</text>`)}
109
  for(let gene of g.genes){if(gene.start>end)continue;let y=gene.strand==='+'?92:158;parts.push(`<g><title>${esc(gene.id)} · ${fmt(gene.start)}–${fmt(gene.end)} bp · ${gene.strand}</title><line x1="${x(gene.start)}" x2="${x(Math.min(end,gene.end))}" y1="${y}" y2="${y}" stroke="${palette.bar}"/>`);for(let [s,e] of gene.cds)if(s<=end)parts.push(`<rect x="${x(s)}" y="${y-7}" width="${Math.max(.6,x(Math.min(end,e))-x(s))}" height="14" fill="${palette.green}"/>`);parts.push('</g>')};svg.innerHTML=parts.join('');$('#gene-count-window').textContent=g.genes.filter(g=>g.start<=end).length+' candidate loci in this 1 Mb window. Bars show predicted coding segments; connecting lines span intervening sequence.';
110
  }
111
  async function init(){try{const raw=Uint8Array.from(atob($('#dataset').textContent.trim()),c=>c.charCodeAt(0));D=JSON.parse(await new Response(new Blob([raw]).stream().pipeThrough(new DecompressionStream('gzip'))).text());prepare();await Promise.all([document.fonts.ready,loadAtlasIcons()]);setupGL();prepareSweep();setTheme(theme);makeLocusBins();resize();updateLegend();$('#loading').remove();$('#shown').textContent=fmt(leaves.length);$('#taxa-shown').textContent=fmt(D.meta.taxa);$('#search').disabled=false;
@@ -131,8 +132,11 @@ async function init(){try{const raw=Uint8Array.from(atob($('#dataset').textConte
131
  $('#selection').addEventListener('pointerleave',e=>{if(e.relatedTarget!==canvas)hoverAssembly(null)});
132
  $('#sel-download').onclick=()=>showDataAccess(selected);$('#data-close').onclick=()=>$('#data-dialog').close();$('#data-copy').onclick=copyDataCode;
133
  $('#theme-toggle').onclick=()=>setTheme(theme==='dark'?'light':'dark');
 
134
  $('#locus-mode').onchange=e=>{locusMode=e.target.value;makeLocusBins()};$('#show-icons').onchange=e=>{showIcons=e.target.checked;schedule()};$('#credits-open').onclick=()=>$('#credits-dialog').showModal();$('#credits-close').onclick=()=>$('#credits-dialog').close();
135
  stage.addEventListener('dblclick',e=>{let b=canvas.getBoundingClientRect(),x=e.clientX-b.left,y=e.clientY-b.top;if(!hitDial(x,y))zoomAt(3,x,y)});
136
  $('#zoomin').onclick=()=>zoomAt(2);$('#zoomout').onclick=()=>zoomAt(.5);$('#reset').onclick=reset;$('#search').addEventListener('input',e=>search(e.target.value));$('#search').addEventListener('keydown',e=>{if(e.key==='Escape')$('#results').replaceChildren();if(e.key==='Enter')$('#results button')?.click()});$('#scope').onchange=e=>{mode=Number(e.target.value);updateLegend()};$('#existing-only').onchange=e=>{view=e.target.checked?'existing':'all';schedule()};$('#human').onclick=()=>focusAssembly(leaves.find(a=>a.accession==='GCA_000001405.29'));$('#detail').onclick=()=>focusAssembly(leaves.find(a=>a.accession==='GCA_000001405.29'),95);$('#sel-loci').onclick=geneModal;$('#gene-open').onclick=geneModal;$('#gene-close').onclick=()=>$('#gene-dialog').close();$('#sel-close').onclick=()=>{clearSelection();schedule()};$('#sel-focus').onclick=()=>focusAssembly(selected,100);$('#about').onclick=()=>{stopSweep();$('#about-dialog').showModal()};$('#about-close').onclick=()=>$('#about-dialog').close();
137
- }catch(e){$('#loading').textContent='Unable to load the tree: '+e.message;console.error(e)}}
 
 
138
  init();
 
88
  if(d<best){best=d;found=leaves[hitOwner[k/8]];}
89
  }return found;
90
  }
91
+ function closeViewOptions(){if($('#view-dialog').open)$('#view-dialog').close()}
92
+ function focusAssembly(a,z=70){closeViewOptions();stopSweep(false);selected=a;zoom=z;let s=baseScale*zoom;pan={x:-a.tip[0]*s,y:-a.tip[1]*s};selectAssembly(a);schedule()}
93
  function clearSelection(){selected=pinnedAssembly=hoveredAssembly=null;$('#selection').hidden=true;scheduleInteraction()}
94
  function hoverAssembly(a){
95
  if(sweep.running||pinnedAssembly)return;
 
106
  updateDataAccess(a);$('#sel-loci').hidden=a.accession!=='GCA_000001405.29';scheduleInteraction()}
107
  function updateLegend(){let n=mode?D.meta.refseq_matches:D.meta.classification_counts['1'];$('#blue-count').textContent=fmt(n);$('#green-count').textContent=fmt(leaves.length-n);$('#blue-label').textContent=mode?'In RefSeq comparison corpus':'GenBank annotation record';$('#green-label').textContent=mode?'Outside that comparison corpus':'No GenBank record listed';$('#scope-note').textContent=mode?'Counts are assemblies, not genes. Blue marks exact paired-accession matches to the project’s 2,055-assembly RefSeq corpus, not all RefSeq annotations. Green does not imply new genes.':'Counts are assemblies, not genes. This view checks GenBank submission metadata; it does not include annotations stored only on paired RefSeq records. Missing metadata is not proof of no prior annotation.';if(selected&&!sweep.running)selectAssembly(selected,false);schedule()}
108
  function search(q){q=q.trim().toLowerCase();let out=[];if(q.length>1){out=leaves.filter(a=>a.accession.toLowerCase().includes(q)||a.name.toLowerCase().includes(q)||(nodes.get(a.taxid).common||'').toLowerCase().includes(q)).slice(0,7);}$('#results').replaceChildren(...out.map(a=>{let b=document.createElement('button');b.innerHTML=`<strong>${esc(a.name)}</strong><span>${esc(a.accession)}</span>`;b.onclick=()=>{focusAssembly(a);$('#results').replaceChildren();$('#search').value=''};return b}));if(q.length>1&&!out.length)$('#results').textContent='No match in this PoC subset.';}
109
+ function geneModal(){closeViewOptions();stopSweep();const g=D.example;$('#gene-dialog').showModal();$('#gene-summary').textContent=fmt(g.gene_count)+' candidate coding loci in this assembly';const svg=$('#gene-svg');let start=0,end=1000000,width=1100,left=30,right=1080;let x=p=>left+(p-start)/(end-start)*(right-left);let parts=[];for(let p=0;p<=end;p+=100000){parts.push(`<line x1="${x(p)}" y1="38" x2="${x(p)}" y2="198" stroke="${palette.grid}"/><text x="${x(p)}" y="24" text-anchor="middle">${p/1e6} Mb</text>`)}
110
  for(let gene of g.genes){if(gene.start>end)continue;let y=gene.strand==='+'?92:158;parts.push(`<g><title>${esc(gene.id)} · ${fmt(gene.start)}–${fmt(gene.end)} bp · ${gene.strand}</title><line x1="${x(gene.start)}" x2="${x(Math.min(end,gene.end))}" y1="${y}" y2="${y}" stroke="${palette.bar}"/>`);for(let [s,e] of gene.cds)if(s<=end)parts.push(`<rect x="${x(s)}" y="${y-7}" width="${Math.max(.6,x(Math.min(end,e))-x(s))}" height="14" fill="${palette.green}"/>`);parts.push('</g>')};svg.innerHTML=parts.join('');$('#gene-count-window').textContent=g.genes.filter(g=>g.start<=end).length+' candidate loci in this 1 Mb window. Bars show predicted coding segments; connecting lines span intervening sequence.';
111
  }
112
  async function init(){try{const raw=Uint8Array.from(atob($('#dataset').textContent.trim()),c=>c.charCodeAt(0));D=JSON.parse(await new Response(new Blob([raw]).stream().pipeThrough(new DecompressionStream('gzip'))).text());prepare();await Promise.all([document.fonts.ready,loadAtlasIcons()]);setupGL();prepareSweep();setTheme(theme);makeLocusBins();resize();updateLegend();$('#loading').remove();$('#shown').textContent=fmt(leaves.length);$('#taxa-shown').textContent=fmt(D.meta.taxa);$('#search').disabled=false;
 
132
  $('#selection').addEventListener('pointerleave',e=>{if(e.relatedTarget!==canvas)hoverAssembly(null)});
133
  $('#sel-download').onclick=()=>showDataAccess(selected);$('#data-close').onclick=()=>$('#data-dialog').close();$('#data-copy').onclick=copyDataCode;
134
  $('#theme-toggle').onclick=()=>setTheme(theme==='dark'?'light':'dark');
135
+ $('#view-options').onclick=()=>{stopSweep(false);$('#view-dialog').showModal()};$('#view-close').onclick=closeViewOptions;
136
  $('#locus-mode').onchange=e=>{locusMode=e.target.value;makeLocusBins()};$('#show-icons').onchange=e=>{showIcons=e.target.checked;schedule()};$('#credits-open').onclick=()=>$('#credits-dialog').showModal();$('#credits-close').onclick=()=>$('#credits-dialog').close();
137
  stage.addEventListener('dblclick',e=>{let b=canvas.getBoundingClientRect(),x=e.clientX-b.left,y=e.clientY-b.top;if(!hitDial(x,y))zoomAt(3,x,y)});
138
  $('#zoomin').onclick=()=>zoomAt(2);$('#zoomout').onclick=()=>zoomAt(.5);$('#reset').onclick=reset;$('#search').addEventListener('input',e=>search(e.target.value));$('#search').addEventListener('keydown',e=>{if(e.key==='Escape')$('#results').replaceChildren();if(e.key==='Enter')$('#results button')?.click()});$('#scope').onchange=e=>{mode=Number(e.target.value);updateLegend()};$('#existing-only').onchange=e=>{view=e.target.checked?'existing':'all';schedule()};$('#human').onclick=()=>focusAssembly(leaves.find(a=>a.accession==='GCA_000001405.29'));$('#detail').onclick=()=>focusAssembly(leaves.find(a=>a.accession==='GCA_000001405.29'),95);$('#sel-loci').onclick=geneModal;$('#gene-open').onclick=geneModal;$('#gene-close').onclick=()=>$('#gene-dialog').close();$('#sel-close').onclick=()=>{clearSelection();schedule()};$('#sel-focus').onclick=()=>focusAssembly(selected,100);$('#about').onclick=()=>{stopSweep();$('#about-dialog').showModal()};$('#about-close').onclick=()=>$('#about-dialog').close();
139
+ const params=new URLSearchParams(location.search);
140
+ if(params.get('animate')==='1')setTimeout(startSweep,1000);
141
+ }catch(e){if($('#loading'))$('#loading').textContent='Unable to load the tree: '+e.message;console.error(e)}}
142
  init();
carbon-a-strand-poc.png CHANGED

Git LFS Details

  • SHA256: 21735fc52668014bd7fe05d8d3af0f653a63edb1ac6724f7b3f7f75492ada8fc
  • Pointer size: 132 Bytes
  • Size of remote file: 3.09 MB

Git LFS Details

  • SHA256: a43c4e37205668709d1ad6a0b7d808d20c29982f4161bdf94640151331c86da5
  • Pointer size: 132 Bytes
  • Size of remote file: 2.81 MB
carbon-a-strand-source.zip CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:c446ad54fe42e83f65f9d92400a44b00685ea5344771d88ed6e0fbefc90a906e
3
- size 15178513
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:88535e54b44f8f77a6db8355ad074bd5c4d55864579803461804134000c12aa9
3
+ size 15180542
check_presentation.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Browser verification for the presentation layout and counter-free sweep."""
2
+ from pathlib import Path
3
+ import sys,json,math
4
+ sys.path.insert(0,'/home/node/local/carbon-a-render/python')
5
+ from playwright.sync_api import sync_playwright
6
+ R=Path(__file__).parent
7
+ BROWSER='/opt/pw-browsers/chromium_headless_shell-1234/chrome-headless-shell-linux64/chrome-headless-shell'
8
+ ARGS=['--no-sandbox','--enable-webgl','--use-gl=angle','--use-angle=swiftshader','--enable-unsafe-swiftshader','--disable-renderer-backgrounding','--disable-background-timer-throttling','--disable-backgrounding-occluded-windows']
9
+
10
+ def check(url=None,headers=None):
11
+ with sync_playwright() as p:
12
+ browser=p.chromium.launch(executable_path=BROWSER,args=ARGS)
13
+ page=browser.new_page(viewport={'width':1500,'height':1050},extra_http_headers=headers or {})
14
+ errors=[];page.on('pageerror',lambda e:errors.append(str(e)))
15
+ page.goto(url or (R/'index.html').as_uri(),wait_until='domcontentloaded')
16
+ page.wait_for_function('window.POC && !document.querySelector("#loading")',timeout=60000)
17
+ assert page.evaluate('POC.assemblies')==33588
18
+ assert page.locator('aside,#animate-sweep,.sweep-meter,#seen-species,#seen-assemblies,#seen-loci').count()==0
19
+ assert page.locator('.resource-links a').all_text_contents()==['Model↗','Database↗','Explorer↗','Paper↗','Blog↗']
20
+ assert page.locator('h1').inner_text()=='Carbon-A · Across the tree of life'
21
+ assert page.locator('.wordmark em').evaluate('e=>getComputedStyle(e).color')=='rgb(41, 233, 154)'
22
+ assert page.locator('#view-dialog').is_hidden()
23
+ assert page.locator('footer').bounding_box()['y']>=page.locator('.stage-wrap').bounding_box()['y']+page.locator('.stage-wrap').bounding_box()['height']
24
+ page.screenshot(path=str(R/'presentation-desktop.png'))
25
+ # Hover, frozen selection, and outside release still use the same geometry.
26
+ def point(i):
27
+ return page.evaluate('''i=>{const q=screen(polar(62,leaves[i].angle)),b=stage.getBoundingClientRect();return [b.x+q[0],b.y+q[1]]}''',i)
28
+ page.mouse.move(*point(12345));page.wait_for_function('selected?.i===12345')
29
+ assert page.locator('#selection').is_visible()
30
+ page.mouse.click(*point(12345));page.mouse.move(*point(25000));page.wait_for_timeout(60)
31
+ assert page.evaluate('pinnedAssembly.i')==12345
32
+ page.mouse.click(*point(25000));assert page.evaluate('selected.i')==12345
33
+ box=page.locator('#stage').bounding_box();page.mouse.click(box['x']+2,box['y']+2)
34
+ assert page.evaluate('pinnedAssembly===null')
35
+ # Search is accessible through the dialog; selecting a result closes it.
36
+ page.locator('#view-options').click();page.locator('#search').fill('GCA_000001405.29')
37
+ page.locator('#results button').first.click()
38
+ assert page.locator('#view-dialog').is_hidden()
39
+ assert page.locator('#sel-id').inner_text()=='GCA_000001405.29'
40
+ assert page.locator('#sel-files').get_attribute('href').endswith('/GCA_000001405.29')
41
+ page.locator('#sel-download').click();compile(page.locator('#data-code').inner_text(),'download_example','exec')
42
+ page.locator('#data-close').click();page.locator('#sel-close').click()
43
+ page.locator('#view-options').click();page.locator('#scope').select_option('1');page.locator('#locus-mode').select_option('20')
44
+ assert page.evaluate('mode')==1 and page.evaluate('locusMode')=='20'
45
+ page.locator('#scope').select_option('0');page.locator('#locus-mode').select_option('5');page.locator('#reset').click();page.locator('#view-close').click()
46
+ # Space inside a search input must never start animation.
47
+ page.locator('#view-options').click();page.locator('#search').focus();page.keyboard.press('Space')
48
+ assert not page.evaluate('sweep.running')
49
+ page.locator('#view-close').click();page.locator('#theme-toggle').click();assert page.evaluate('theme')=='light'
50
+ page.locator('#theme-toggle').click()
51
+ # Test capture from a fresh load, matching the recording entry point.
52
+ # Headless Chromium may stop compositor frames after repeated modal closures.
53
+ page.reload(wait_until='domcontentloaded')
54
+ page.wait_for_function('window.POC && !document.querySelector("#loading")',timeout=60000)
55
+ # Keyboard playback, smooth full turn, exact highlighted geometry and no UI counters.
56
+ page.evaluate('''()=>{document.activeElement.blur();window.framesSeen=[];window.treeRedraws=0;const oldDraw=gl.drawArrays.bind(gl);gl.drawArrays=(...a)=>{treeRedraws++;oldDraw(...a)};const oldUpdate=updateSweep;updateSweep=t=>{oldUpdate(t);framesSeen.push({t:performance.now(),p:sweep.progress,angle:sweep.angle,selected:selected?.i??null})}}''')
57
+ page.keyboard.press('Space');page.wait_for_function('sweep.running && sweep.progress>.1')
58
+ assert page.locator('#selection').is_hidden()
59
+ try: page.wait_for_function('sweep.status==="complete"',timeout=12000)
60
+ except Exception:
61
+ print('sweep failure',page.evaluate('({sweep,hidden:document.hidden,frames:window.framesSeen?.length,focus:document.activeElement.tagName,now:performance.now(),redraws:treeRedraws,pending,zoom})'),errors,flush=True);page.screenshot(path=str(R/'sweep-failure.png'));print('after screenshot',page.evaluate('sweep'),flush=True);raise
62
+ result=page.evaluate('({frames:framesSeen,redraws:treeRedraws,angle:sweep.angle,selected,zoom})')
63
+ frames=result['frames'];duration=frames[-1]['t']-frames[1]['t']
64
+ assert 3900<=duration<4700,duration
65
+ assert len(frames)>35,len(frames)
66
+ assert result['redraws']<6,result['redraws']
67
+ assert abs(result['angle']-1.5*math.pi)<1e-9
68
+ assert result['selected'] is None and result['zoom']==1
69
+ assert all(a['angle']<=b['angle'] for a,b in zip(frames,frames[1:]))
70
+ page.keyboard.press('Space');page.wait_for_function('sweep.progress>.1 && sweep.running');page.keyboard.press('Escape')
71
+ assert not page.evaluate('sweep.running')
72
+ # A phone layout has no horizontal overflow and retains all resource buttons.
73
+ page.evaluate('reset()');page.mouse.move(0,0);page.set_viewport_size({'width':390,'height':844})
74
+ page.wait_for_timeout(150)
75
+ assert page.evaluate('document.documentElement.scrollWidth')==390
76
+ assert page.locator('footer').bounding_box()['y']+page.locator('footer').bounding_box()['height']<=845
77
+ assert all(x.is_visible() for x in page.locator('.resource-links a').all())
78
+ page.screenshot(path=str(R/'presentation-mobile.png'))
79
+ assert not errors,errors
80
+ browser.close()
81
+ return {'sweep_duration_ms':round(duration),'sweep_frames':len(frames),'background_redraws':result['redraws'],'layout':'desktop and mobile passed','hover_freeze_and_release':'passed','search_and_downloads':'passed','resource_links':5,'counter_and_button':'removed','errors':errors}
82
+
83
+ if __name__=='__main__':
84
+ result=check();(R/'presentation-verification.json').write_text(json.dumps(result,indent=2));print(json.dumps(result))
export_still.py CHANGED
@@ -1,13 +1,15 @@
 
1
  from pathlib import Path
2
  import sys
3
  sys.path.insert(0,'/home/node/local/carbon-a-render/python')
4
  from playwright.sync_api import sync_playwright
 
5
  R=Path(__file__).parent
6
  with sync_playwright() as p:
7
- b=p.chromium.launch(executable_path='/opt/pw-browsers/chromium_headless_shell-1234/chrome-headless-shell-linux64/chrome-headless-shell',headless=True,args=['--no-sandbox','--enable-webgl','--use-gl=angle','--use-angle=swiftshader','--enable-unsafe-swiftshader'])
8
- page=b.new_page(viewport={'width':1200,'height':1420},device_scale_factor=2)
9
- page.goto((R/'index.html').as_uri());page.wait_for_function('window.POC && !document.querySelector("#loading")')
10
- page.add_style_tag(content='''header{height:90px;padding:0 50px}.brand{font-size:29px}.header-right button{display:none!important}main{display:block;height:1330px;min-height:1330px}aside,.topbar,.controls,.help,#selection,.sweep-meter{display:none!important}.stage-wrap{height:100%}#stage{inset:132px 22px 120px}.poster-heading{position:absolute;left:50px;top:35px}.poster-heading h1{font-size:39px;letter-spacing:-1.5px;margin-bottom:11px}.poster-heading p{font-size:15px;color:var(--muted);margin:0}.poster-footer{position:absolute;bottom:20px;left:50px;right:50px;border-top:1px solid var(--rule);padding-top:22px}.poster-key{display:flex;justify-content:space-between;font-size:16px;font-weight:500}.poster-key span{display:flex;align-items:center;gap:12px}.poster-key i{width:34px;height:2px;background:var(--green);display:inline-block}.poster-key i.blue{background:var(--blue)}.poster-footer p{font-size:11px;line-height:1.7;color:var(--muted);margin-top:18px}''')
11
- page.evaluate('''() => {document.querySelector('.stage-wrap').insertAdjacentHTML('beforeend',`<div class="poster-heading"><h1>One assembly. One strand.</h1><p>33,588 assemblies · outer bars sum predicted coding loci across 5 adjacent strands.</p></div><div class="poster-footer"><div class="poster-key"><span><i></i>29,350 with no GenBank annotation record</span><span><i class="blue"></i>4,238 with GenBank annotation records</span></div><p>Proof of concept: 33,588 of the release’s 48,167 assemblies. Colours reflect GenBank annotation records in the 7 October 2026 snapshot.<br>Outer bars sum available loci across up to 5 assembly strands; grey base marks show missing counts. Branch length is not evolutionary time.<br>Genome Atlas silhouettes: PhyloPic. Original image credits and licences are included in the interactive preview.</p></div>`)}''')
12
- page.evaluate("document.querySelector('.poster-heading p').textContent='33,588 assemblies · '+shortCount(POC.locusTotal)+' predicted loci across '+fmt(POC.locusAvailable)+' assemblies with counts.'");page.wait_for_timeout(800);page.screenshot(path=str(R/'carbon-a-strand-poc.png'));b.close()
13
- print(R/'carbon-a-strand-poc.png')
 
1
+ """Export the current presentation at 2× resolution, without capture controls."""
2
  from pathlib import Path
3
  import sys
4
  sys.path.insert(0,'/home/node/local/carbon-a-render/python')
5
  from playwright.sync_api import sync_playwright
6
+ from check_presentation import BROWSER,ARGS
7
  R=Path(__file__).parent
8
  with sync_playwright() as p:
9
+ b=p.chromium.launch(executable_path=BROWSER,args=ARGS)
10
+ page=b.new_page(viewport={'width':1080,'height':1350},device_scale_factor=2)
11
+ page.goto((R/'index.html').as_uri()+'?record=1')
12
+ page.wait_for_function('window.POC && !document.querySelector("#loading")',timeout=60000)
13
+ page.screenshot(path=str(R/'carbon-a-strand-poc.png'))
14
+ b.close()
15
+ print(R/'carbon-a-strand-poc.png')
index.html CHANGED
The diff for this file is too large to render. See raw diff
 
layers.js CHANGED
@@ -86,7 +86,7 @@ function hoverLayers(e){if(sweep.running)return;if(pinnedAssembly){$('#layer-too
86
  if(!ic&&locusMode==='raw'){tip.hidden=true;if(hoverBin!==bin){hoverBin=bin;schedule()}return}
87
  if(ic){tip.innerHTML=`<b>${esc(ic.n.common||ic.n.name)}</b><span>${esc(ic.n.name)} · ${fmt(ic.n.count)} assemblies</span><span>${fmt(mode?ic.n.refMatches:ic.n.existing)} ${mode?'match the RefSeq comparison corpus':'have GenBank annotation records'}</span><small>Assembly counts, not gene counts · click to explore</small>`}
88
  else{const n=bin.hi-bin.lo,one=n===1,a=leaves[bin.lo];tip.innerHTML=`<b>${one?esc(a.name):shortCount(bin.value)+' predicted coding loci'}</b><span>${one?(bin.known?fmt(bin.value)+' predicted coding loci':'Locus count unavailable'):fmt(bin.known)+' of '+fmt(n)+' assemblies have counts'}</span><small>${one?esc(a.accession):'Sum of available counts · click to inspect individual bars'}</small>`;if(hoverBin!==bin){hoverBin=bin;schedule()}}
89
- tip.hidden=false;tip.style.left=Math.max(8,Math.min(W-270,x+16))+14+'px';tip.style.top=Math.max(65,Math.min(H-75,y+15))+64+'px';
90
  }
91
  function focusNode(n){stopSweep(false);let pts=[point(n,n.mid)];for(let j=0;j<=16;j++)pts.push(polar(R,START+SPAN*(n.lo+(n.hi-n.lo)*j/16)/leaves.length));let xs=pts.map(p=>p[0]),ys=pts.map(p=>p[1]),minx=Math.min(...xs),maxx=Math.max(...xs),miny=Math.min(...ys),maxy=Math.max(...ys);zoom=Math.max(1,Math.min(35,Math.min(W/(maxx-minx+140),H/(maxy-miny+140))/baseScale));pan={x:-(minx+maxx)/2*baseScale*zoom,y:-(miny+maxy)/2*baseScale*zoom};clearSelection();$('#layer-tooltip').hidden=true;schedule()}
92
  function clickLayers(x,y){const ic=hitIcon(x,y);if(ic){focusNode(ic.n);return true}const b=hitBar(x,y);if(!b)return false;
 
86
  if(!ic&&locusMode==='raw'){tip.hidden=true;if(hoverBin!==bin){hoverBin=bin;schedule()}return}
87
  if(ic){tip.innerHTML=`<b>${esc(ic.n.common||ic.n.name)}</b><span>${esc(ic.n.name)} · ${fmt(ic.n.count)} assemblies</span><span>${fmt(mode?ic.n.refMatches:ic.n.existing)} ${mode?'match the RefSeq comparison corpus':'have GenBank annotation records'}</span><small>Assembly counts, not gene counts · click to explore</small>`}
88
  else{const n=bin.hi-bin.lo,one=n===1,a=leaves[bin.lo];tip.innerHTML=`<b>${one?esc(a.name):shortCount(bin.value)+' predicted coding loci'}</b><span>${one?(bin.known?fmt(bin.value)+' predicted coding loci':'Locus count unavailable'):fmt(bin.known)+' of '+fmt(n)+' assemblies have counts'}</span><small>${one?esc(a.accession):'Sum of available counts · click to inspect individual bars'}</small>`;if(hoverBin!==bin){hoverBin=bin;schedule()}}
89
+ tip.hidden=false;const wrap=$('.stage-wrap').getBoundingClientRect();tip.style.left=box.left-wrap.left+Math.max(8,Math.min(W-tip.offsetWidth-8,x+16))+'px';tip.style.top=box.top-wrap.top+Math.max(8,Math.min(H-tip.offsetHeight-8,y+15))+'px';
90
  }
91
  function focusNode(n){stopSweep(false);let pts=[point(n,n.mid)];for(let j=0;j<=16;j++)pts.push(polar(R,START+SPAN*(n.lo+(n.hi-n.lo)*j/16)/leaves.length));let xs=pts.map(p=>p[0]),ys=pts.map(p=>p[1]),minx=Math.min(...xs),maxx=Math.max(...xs),miny=Math.min(...ys),maxy=Math.max(...ys);zoom=Math.max(1,Math.min(35,Math.min(W/(maxx-minx+140),H/(maxy-miny+140))/baseScale));pan={x:-(minx+maxx)/2*baseScale*zoom,y:-(miny+maxy)/2*baseScale*zoom};clearSelection();$('#layer-tooltip').hidden=true;schedule()}
92
  function clickLayers(x,y){const ic=hitIcon(x,y);if(ic){focusNode(ic.n);return true}const b=hitBar(x,y);if(!b)return false;
presentation-desktop.png ADDED

Git LFS Details

  • SHA256: c0b8714dde18dcacb7969e46b14cfecba49965f9b9603cabe8fab61ec84cfedc
  • Pointer size: 131 Bytes
  • Size of remote file: 584 kB
presentation-mobile.png ADDED

Git LFS Details

  • SHA256: c8bb6a25d205dc799f5ea44d1f6c37a1d7eccc00357c61c90c536ce04cecb330
  • Pointer size: 131 Bytes
  • Size of remote file: 151 kB
presentation-verification.json ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "sweep_duration_ms": 4008,
3
+ "sweep_frames": 73,
4
+ "background_redraws": 0,
5
+ "layout": "desktop and mobile passed",
6
+ "hover_freeze_and_release": "passed",
7
+ "search_and_downloads": "passed",
8
+ "resource_links": 5,
9
+ "counter_and_button": "removed",
10
+ "errors": []
11
+ }
provenance.json CHANGED
@@ -67,5 +67,11 @@
67
  "bucket": "HuggingFaceBio/genbank-annotations",
68
  "paths": "Embedded from per-assembly metadata source paths",
69
  "generated_example": "GCA_000001405.29; all 4 files downloaded, annotation/sequence hashes match metadata"
 
 
 
 
 
 
70
  }
71
  }
 
67
  "bucket": "HuggingFaceBio/genbank-annotations",
68
  "paths": "Embedded from per-assembly metadata source paths",
69
  "generated_example": "GCA_000001405.29; all 4 files downloaded, annotation/sequence hashes match metadata"
70
+ },
71
+ "presentation": {
72
+ "updated_at": "2026-10-12",
73
+ "layout": "Wide title, centered tree and five resource buttons; no sidebar",
74
+ "animation": "4-second sweep; no counters or visible playback button; keyboard Space or POC.startSweep()",
75
+ "data_changed": false
76
  }
77
  }
sweep.js CHANGED
@@ -1,22 +1,16 @@
1
- // The sweep counts every crossed strand, not just the ones displayed in frames.
2
  const SWEEP_DURATION=4000,NOON=-Math.PI/2;
3
- const sweep={running:false,status:'ready',progress:0,angle:NOON,count:0,species:0,loci:0,known:0,frame:0,startedAt:0};
4
- let sweepSpecies,sweepLoci,sweepKnown,interactionPending=false;
5
 
6
  function prepareSweep(){
7
- const n=leaves.length,seen=new Set();
8
- sweepSpecies=new Uint32Array(n+1);sweepLoci=new Float64Array(n+1);sweepKnown=new Uint32Array(n+1);
9
- for(let i=0;i<n;i++){
10
- const a=leaves[i],species=a.lineage.find(n=>n.rank==='species');
11
- if(species)seen.add(species.id);
12
- sweepSpecies[i+1]=seen.size;
13
- sweepLoci[i+1]=sweepLoci[i]+(a.loci??0);
14
- sweepKnown[i+1]=sweepKnown[i]+(a.loci!==null?1:0);
15
- }
16
- POC.sweep=sweep;POC.sweepTotals={species:seen.size,assemblies:n,loci:sweepLoci[n],known:sweepKnown[n]};
17
- $('#animate-sweep').disabled=false;
18
- $('#animate-sweep').onclick=()=>sweep.running?stopSweep():startSweep();
19
- document.addEventListener('keydown',e=>{if(e.key==='Escape'&&sweep.running)stopSweep()});
20
  document.addEventListener('visibilitychange',()=>{if(document.hidden&&sweep.running)stopSweep()});
21
  }
22
 
@@ -47,17 +41,10 @@ function updateSweep(progress){
47
  const t=sweep.progress,eased=t*t*(3-2*t);
48
  sweep.angle=NOON+TAU*eased;
49
  const n=leaves.length,count=Math.max(0,Math.min(n,Math.floor((sweep.angle-START)/SPAN*n+.5)));
50
- sweep.count=count;sweep.species=sweepSpecies[count];sweep.loci=sweepLoci[count];sweep.known=sweepKnown[count];
51
  selected=count&&sweep.angle<=START+SPAN?leaves[count-1]:null;
52
- $('#seen-species').textContent=fmt(sweep.species);
53
- $('#seen-assemblies').textContent=fmt(count);
54
- $('#seen-loci').textContent=fmt(sweep.loci);
55
- $('#seen-loci').title=fmt(sweep.known)+' assemblies with published counts; missing counts are excluded.';
56
  }
57
 
58
- function sweepButton(label){
59
- $('#sweep-button-label').textContent=label;
60
- $('#animate-sweep').setAttribute('aria-label',sweep.running?'Stop the sweep':'Animate a 4-second sweep of the whole tree');
61
  $('.stage-wrap').dataset.sweeping=String(sweep.running);
62
  }
63
 
@@ -73,7 +60,7 @@ function startSweep(){
73
  zoom=1;pan={x:0,y:0};clearSelection();hoverBin=null;$('#layer-tooltip').hidden=true;
74
  $('#results').replaceChildren();$('#selection').hidden=true;
75
  sweep.running=true;sweep.status='running';sweep.startedAt=0;
76
- updateSweep(0);sweepButton('Stop sweep');$('#sweep-status').textContent='Scanning the tree';
77
  // Render the stationary tree once. Only the thin interaction canvas moves.
78
  if(redraw)draw();else{cacheSweepTree();drawInteraction()}
79
  sweep.frame=requestAnimationFrame(function step(){
@@ -84,8 +71,8 @@ function startSweep(){
84
  if(sweep.progress<1){sweep.frame=requestAnimationFrame(step);return}
85
  sweep.running=false;sweep.status='complete';sweep.frame=0;
86
  selected=pinnedAssembly=hoveredAssembly=null;
87
- sweepButton('Replay sweep');$('#sweep-status').textContent='Full tree seen';
88
- $('#sweep-announcement').textContent=fmt(sweep.species)+' species, '+fmt(sweep.count)+' assemblies and '+fmt(sweep.loci)+' coding loci seen.';
89
  drawInteraction();
90
  });
91
  }
@@ -93,7 +80,7 @@ function startSweep(){
93
  function stopSweep(pin=true){
94
  if(!sweep.running)return;
95
  cancelAnimationFrame(sweep.frame);sweep.frame=0;sweep.running=false;sweep.status='stopped';
96
- sweepButton('Replay sweep');$('#sweep-status').textContent='Sweep stopped';
97
  if(pin&&selected)selectAssembly(selected);else selected=pinnedAssembly=hoveredAssembly=null;
98
  scheduleInteraction();
99
  }
 
1
+ // A four-second sweep for video capture, with no counters or on-page controls.
2
  const SWEEP_DURATION=4000,NOON=-Math.PI/2;
3
+ const sweep={running:false,status:'ready',progress:0,angle:NOON,frame:0,startedAt:0};
4
+ let interactionPending=false;
5
 
6
  function prepareSweep(){
7
+ Object.assign(POC,{sweep,startSweep,stopSweep});
8
+ document.addEventListener('keydown',e=>{
9
+ if(e.key==='Escape'&&sweep.running)stopSweep();
10
+ if(e.code!=='Space'||e.repeat||e.altKey||e.ctrlKey||e.metaKey)return;
11
+ if(e.target.closest('input,textarea,select,button,a,[contenteditable]')||document.querySelector('dialog[open]'))return;
12
+ e.preventDefault();sweep.running?stopSweep(false):startSweep();
13
+ });
 
 
 
 
 
 
14
  document.addEventListener('visibilitychange',()=>{if(document.hidden&&sweep.running)stopSweep()});
15
  }
16
 
 
41
  const t=sweep.progress,eased=t*t*(3-2*t);
42
  sweep.angle=NOON+TAU*eased;
43
  const n=leaves.length,count=Math.max(0,Math.min(n,Math.floor((sweep.angle-START)/SPAN*n+.5)));
 
44
  selected=count&&sweep.angle<=START+SPAN?leaves[count-1]:null;
 
 
 
 
45
  }
46
 
47
+ function updateSweepState(){
 
 
48
  $('.stage-wrap').dataset.sweeping=String(sweep.running);
49
  }
50
 
 
60
  zoom=1;pan={x:0,y:0};clearSelection();hoverBin=null;$('#layer-tooltip').hidden=true;
61
  $('#results').replaceChildren();$('#selection').hidden=true;
62
  sweep.running=true;sweep.status='running';sweep.startedAt=0;
63
+ updateSweep(0);updateSweepState();
64
  // Render the stationary tree once. Only the thin interaction canvas moves.
65
  if(redraw)draw();else{cacheSweepTree();drawInteraction()}
66
  sweep.frame=requestAnimationFrame(function step(){
 
71
  if(sweep.progress<1){sweep.frame=requestAnimationFrame(step);return}
72
  sweep.running=false;sweep.status='complete';sweep.frame=0;
73
  selected=pinnedAssembly=hoveredAssembly=null;
74
+ updateSweepState();
75
+ $('#sweep-announcement').textContent='Sweep complete.';
76
  drawInteraction();
77
  });
78
  }
 
80
  function stopSweep(pin=true){
81
  if(!sweep.running)return;
82
  cancelAnimationFrame(sweep.frame);sweep.frame=0;sweep.running=false;sweep.status='stopped';
83
+ updateSweepState();$('#sweep-announcement').textContent='Sweep stopped.';
84
  if(pin&&selected)selectAssembly(selected);else selected=pinnedAssembly=hoveredAssembly=null;
85
  scheduleInteraction();
86
  }
template.html CHANGED
@@ -1,41 +1,44 @@
1
- <!doctype html><html lang="en" data-theme="dark"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Carbon-A — Assembly strands</title><script>try{const t=localStorage.getItem('carbon-a-theme');if(t==='light'||t==='dark')document.documentElement.dataset.theme=t}catch{}</script><style>
2
  __FONTS__
3
  :root{color-scheme:dark;--green:#29e99a;--blue:#789dff;--ink:#e6f0ed;--muted:#91a5a4;--rule:#233038;--bg:#080e13;--panel:#0d151c;--hover:#16252c;--secondary:#bccdc7;--shadow:#00000050}
4
- :root[data-theme="light"]{color-scheme:light;--green:#00b86b;--blue:#4167ec;--ink:#14221c;--muted:#6e7b73;--rule:#e6ece8;--bg:#fff;--panel:#fff;--hover:#f6faf7;--secondary:#435a4c;--shadow:#2035280c}*{box-sizing:border-box}body{margin:0;color:var(--ink);background:var(--bg);font-family:Inter,Arial,sans-serif;font-size:13px}button,input,select{font:inherit;color:inherit}button,a,input,select{-webkit-tap-highlight-color:transparent}button{cursor:pointer;color:inherit;background:var(--bg);border:1px solid var(--rule);padding:9px 13px}button:hover{border-color:#80978a;background:var(--hover)}button:focus-visible,a:focus-visible,input:focus-visible,select:focus-visible{outline:2px solid var(--green);outline-offset:3px}button:disabled{opacity:.35;cursor:default}a{color:inherit}header{height:87px;padding:0 40px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--rule)}.brand{font-family:Manrope,Arial,sans-serif;font-weight:800;font-size:26px;letter-spacing:-1px}.brand em{font-style:normal;color:var(--green)}.tag{font-size:10px;letter-spacing:1.3px;color:var(--muted);text-transform:uppercase}.header-right{display:flex;gap:24px;align-items:center}main{display:grid;grid-template-columns:320px 1fr;height:calc(100vh - 87px);min-height:720px}aside{padding:34px 28px 20px 40px;position:relative;z-index:3;border-right:1px solid var(--rule);overflow-y:auto}h1{font-family:Manrope,Arial,sans-serif;font-size:32px;letter-spacing:-1.5px;line-height:1.12;font-weight:700;margin:0 0 16px}.intro{font-size:13px;line-height:1.6;color:var(--muted);margin:0 0 24px}.release{border-top:1px solid var(--rule);padding-top:19px;margin-bottom:24px}.release .big{font-family:Manrope;font-size:38px;letter-spacing:-1.7px;font-weight:700;margin-top:10px;line-height:1.1}.release .big em{font-style:normal;color:var(--green)}.release .sub{font-size:12px;color:var(--muted);margin-top:5px}.stats{display:grid;grid-template-columns:1fr 1fr;gap:14px 18px;margin-top:20px}.stats b{font-family:Manrope;font-size:21px;display:block;letter-spacing:-.6px}.stats span{font-size:10px;color:var(--muted)}.stat-wide{grid-column:1/-1}.legend{border-top:1px solid var(--rule);padding-top:20px}.legend select{width:100%;border:0;border-bottom:1px solid var(--rule);padding:7px 0 9px;margin:6px 0 12px;background:var(--bg);font-size:12px;font-weight:500}.key{display:flex;align-items:center;gap:8px;font-size:11px;margin-bottom:10px}.key strong{margin-left:auto;font-weight:500;font-variant-numeric:tabular-nums}.sample-line{height:2px;width:21px;display:inline-block;background:var(--green);flex-shrink:0}.sample-line.blue{background:var(--blue)}.scope-note{font-size:10px;color:var(--muted);line-height:1.5;margin:10px 0 13px}.checkbox{font-size:11px;display:flex;gap:7px;align-items:center}.checkbox input{accent-color:var(--blue)}.subset{font-size:10px;line-height:1.6;color:var(--muted);margin-top:20px;padding-top:16px;border-top:1px solid var(--rule)}.subset b{color:var(--secondary);font-weight:500}.quick{display:flex;gap:6px;margin:20px 0 6px}.quick button{font-size:11px;padding:8px 10px}.stage-wrap{position:relative;min-width:0;overflow:hidden}#stage{position:absolute;inset:64px 14px 44px}canvas{position:absolute;width:100%;height:100%;inset:0}#tree{cursor:default;touch-action:none}#overlay{pointer-events:none}.topbar{position:absolute;top:18px;left:25px;right:25px;display:flex;align-items:center;justify-content:space-between;gap:10px;z-index:4}.searchbox{position:relative;width:280px}#search{width:100%;padding:10px 12px;border:1px solid var(--rule);background:var(--bg);font-size:11px}#results{position:absolute;top:39px;width:100%;background:var(--bg);box-shadow:0 10px 30px var(--shadow);font-size:11px}#results:empty{display:none}#results button{display:block;width:100%;text-align:left;padding:11px;border:0;border-bottom:1px solid var(--rule)}#results strong,#results span{display:block;font-size:11px;font-weight:500}#results span{font-size:10px;color:var(--muted);margin-top:3px}.one-line{font-size:11px;color:var(--muted)}.controls{display:flex;align-items:center;gap:4px;position:absolute;right:26px;bottom:21px;z-index:3}.controls button{padding:7px 12px}.controls span{font-size:10px;padding:0 7px;min-width:42px;text-align:center;color:var(--muted)}.help{position:absolute;bottom:25px;left:25px;font-size:10px;line-height:1.7;color:var(--muted);pointer-events:none}.help b{color:var(--secondary);font-weight:500}#selection{position:absolute;right:22px;top:69px;width:295px;background:var(--bg);border:1px solid var(--rule);box-shadow:0 8px 40px var(--shadow);z-index:5;padding:23px}#sel-close{position:absolute;top:6px;right:8px;border:0;background:transparent;font-size:20px;padding:0 4px;color:var(--muted)}#sel-name{font-size:18px;letter-spacing:-.5px;font-family:Manrope;margin:0 0 6px;line-height:1.25}#sel-id{font-size:11px;color:var(--green);font-weight:500;text-decoration:none}#sel-lineage{font-size:10px;line-height:1.7;color:var(--muted);margin-top:15px}#sel-status{font-size:11px;line-height:1.6;border-top:1px solid var(--rule);padding-top:13px;margin-top:14px}#sel-status small{font-size:10px;color:var(--muted);margin-left:13px}.dot{display:inline-block;width:6px;height:6px;background:var(--green);border-radius:50%;margin-right:6px}.dot.blue{background:var(--blue)}#sel-data{display:grid;grid-template-columns:1fr;gap:3px;margin:16px 0;font-size:10px}#sel-data span{color:var(--muted);margin-top:7px}#sel-data b{font-weight:500;font-size:12px}#selection button:not(#sel-close){font-size:10px;margin:4px 0;padding:8px 10px}#loading{position:absolute;inset:0;display:grid;place-content:center;background:var(--bg);color:var(--muted);z-index:10}dialog{background:var(--panel);border:1px solid var(--rule);padding:36px;max-width:calc(100vw - 40px);width:1000px;color:var(--ink)}dialog::backdrop{background:#00000060;backdrop-filter:blur(3px)}dialog h2{font-family:Manrope;font-size:28px;letter-spacing:-1px;margin:0 0 7px}dialog p{font-size:13px;line-height:1.7;color:var(--muted)}.dialog-close{float:right;border:0;font-size:22px;padding:0 4px}.gene-label{font-size:11px;margin-top:28px;color:var(--muted)}#gene-svg{width:100%;height:auto;font:11px Inter;fill:var(--muted);margin:16px 0}.gene-notes{border-top:1px solid var(--rule);padding-top:15px;font-size:11px}#about-dialog{width:680px}#about-dialog p{font-size:12px}#about-dialog ul{padding-left:18px;font-size:12px;line-height:1.9;color:var(--muted)}
5
- @media(min-width:1600px){main{grid-template-columns:365px 1fr}aside{padding:40px}.release .big{font-size:44px}h1{font-size:38px}}
6
- @media(max-width:850px){header{padding:0 20px;height:72px}.brand{font-size:22px}.header-right .tag{display:none}main{height:auto;min-height:0;display:flex;flex-direction:column}aside{padding:25px 22px;border-right:0;border-bottom:1px solid var(--rule);overflow:visible}h1{font-size:30px;max-width:420px}.intro{max-width:520px;margin-bottom:18px}.release{display:none}.legend{padding-top:10px;border-top:0;max-width:520px}.legend .tag{display:none}.scope-note{max-width:500px}.subset{margin-top:15px;padding-top:10px}.quick{margin-top:15px}.stage-wrap{height:740px}.topbar{left:20px;right:20px}.one-line{display:none}#stage{inset:68px 0 85px}.help{left:20px;bottom:62px}.controls{left:20px;right:auto}#selection{right:15px;top:68px;width:275px}dialog{padding:25px}.header-right{gap:10px}}
7
- .subset{margin:0 0 14px;padding:0;border-top:0}.locus-controls{border-top:1px solid var(--rule);padding-top:15px;margin:16px 0 20px}.locus-controls select{width:100%;padding:8px 0;border:0;border-bottom:1px solid var(--rule);background:var(--bg);font-size:12px}.locus-controls p{font-size:10px;line-height:1.5;color:var(--muted);margin:8px 0}.locus-controls strong{display:block;font-size:11px;font-weight:500;margin-top:10px}.icon-toggle{margin-top:12px}.icon-toggle input{accent-color:var(--green)}#layer-tooltip{position:absolute;z-index:8;width:250px;pointer-events:none;padding:13px 15px;background:var(--bg);border:1px solid var(--rule);box-shadow:0 5px 25px var(--shadow);font-size:11px}#layer-tooltip b,#layer-tooltip span,#layer-tooltip small{display:block}#layer-tooltip b{font-weight:600;margin-bottom:6px}#layer-tooltip span{line-height:1.5}#layer-tooltip small{font-size:10px;color:var(--muted);margin-top:6px}#credits-dialog{width:740px}#icon-credits{max-height:60vh;overflow:auto}#icon-credits table{border-collapse:collapse;width:100%;font-size:11px;text-align:left}#icon-credits th,#icon-credits td{padding:10px 8px;border-bottom:1px solid var(--rule)}#icon-credits a{color:var(--green)}.release{padding-top:13px;margin-bottom:17px}.release .big{font-size:31px;margin-top:7px}.stats{gap:10px;margin-top:12px}.stats .stat-wide{grid-column:auto}.stats b{font-size:18px}.intro{margin-bottom:17px}h1{font-size:30px}
8
-
9
- header,aside{background:var(--panel)}
10
- #selection,#layer-tooltip,#results{background:var(--panel)}
11
- input::placeholder{color:var(--muted);opacity:1}
12
- button:hover{border-color:var(--muted)}
13
- #theme-toggle{display:grid;place-items:center;width:36px;height:36px;padding:8px;flex-shrink:0}
14
- #theme-toggle svg{width:17px;height:17px;stroke:currentColor;stroke-width:1.5;fill:none}
15
- [data-theme="dark"] .theme-moon,[data-theme="light"] .theme-sun{display:none}
16
- #sel-state{font-size:9px;letter-spacing:1.2px;text-transform:uppercase;color:var(--muted);margin:0 0 12px}
17
- #selection[data-pinned="true"] #sel-state{color:var(--green)}
18
- @media(max-width:850px){.brand{font-size:20px;white-space:nowrap}.header-right{gap:6px}.header-right #about{font-size:10px;padding:9px 7px}header{gap:10px}#theme-toggle{width:32px;height:32px}}
19
- @media(prefers-reduced-motion:no-preference){button{transition:background .15s,border-color .15s}}
20
-
21
- #stage{touch-action:none}
22
- #interaction,#tree-still{pointer-events:none}
23
- #tree-still{visibility:hidden}
24
- [data-sweeping="true"] #tree,[data-sweeping="true"] #overlay{visibility:hidden}
25
- [data-sweeping="true"] #tree-still{visibility:visible}
26
- #animate-sweep{display:flex;align-items:center;justify-content:center;gap:8px;color:var(--green);min-width:146px;font-size:11px;padding:10px 12px}
27
- #animate-sweep svg{width:12px;height:12px;fill:currentColor}
28
- .sweep-stop{display:none}
29
- [data-sweeping="true"] .sweep-play{display:none}
30
- [data-sweeping="true"] .sweep-stop{display:block}
31
- .sweep-meter{position:absolute;left:25px;right:25px;top:70px;display:grid;grid-template-columns:1fr 1fr 1.3fr;gap:0 24px;max-width:650px;z-index:2}
32
- .sweep-meter strong{display:block;font-family:Manrope,Arial,sans-serif;font-size:26px;font-weight:600;font-variant-numeric:tabular-nums;letter-spacing:-.7px;line-height:1.15}
33
- .sweep-meter>div:last-of-type strong{color:var(--green)}
34
- .sweep-meter>div>span{display:block;font-size:10px;color:var(--muted);margin-top:5px}
35
- .sweep-meter p{grid-column:1/-1;display:flex;gap:18px;font-size:9px;color:var(--muted);margin:12px 0 0;line-height:1.4}
36
- #sweep-status{color:var(--secondary)}
37
- #stage{top:159px}#selection{top:159px}
38
  .visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
39
- @media(max-width:850px){.searchbox{width:auto;min-width:0;flex:1}#animate-sweep{min-width:129px;padding:10px 9px;font-size:10px}.sweep-meter{left:20px;right:20px;gap:0 12px;top:74px}.sweep-meter strong{font-size:21px}.sweep-meter p{display:block;font-size:8px}.sweep-meter p span{display:block}.sweep-meter p span+span{margin-top:3px}#stage{top:171px}#selection{top:171px}}
40
- .data-access{border-top:1px solid var(--rule);padding-top:10px;margin-top:12px}.data-access button,.data-access a{color:var(--green)}.data-access a{font-size:11px;line-height:1.8;text-decoration:none}#sel-availability{font-size:10px;line-height:1.5;color:var(--muted);margin:3px 0 8px}#selection{max-height:calc(100% - 205px);overflow:auto}#data-dialog{width:780px}#data-dialog pre{max-height:45vh;overflow:auto;border:1px solid var(--rule);background:var(--bg);padding:18px;font-size:11px;line-height:1.7;white-space:pre}#data-dialog a{color:var(--green)}#copy-status{font-size:11px;margin-left:12px;color:var(--green)}#data-dialog .data-setup{font-size:11px}@media(max-width:850px){#selection{max-height:490px}}
41
- </style></head><body><header><div class="brand">Carbon-<em>A</em> Database</div><div class="header-right"><span class="tag">Assembly strands · Proof of concept</span><button id="about">About the data</button><button id="theme-toggle" aria-label="Switch to light mode" title="Switch to light mode"><svg class="theme-sun" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 1v3m0 16v3M1 12h3m16 0h3M4.2 4.2l2.1 2.1m11.4 11.4 2.1 2.1M4.2 19.8l2.1-2.1M17.7 6.3l2.1-2.1"/></svg><svg class="theme-moon" viewBox="0 0 24 24" aria-hidden="true"><path d="M20.5 14.5A9 9 0 0 1 9.5 3.5a9 9 0 1 0 11 11Z"/></svg></button></div></header><main><aside><h1>The scale is<br>in the tree.</h1><p class="intro">One strand for every assembly.<br>Shared ancestry brings the strands together. Zoom in to follow them apart.</p><p class="subset"><b>PoC subset · <span id="shown">33,588</span> assemblies · <span id="taxa-shown">16,976</span> taxa.</b><br>The full release contains 48,167 assemblies.</p><section class="locus-controls"><label class="tag" for="locus-mode">Coding loci at the strand tips</label><select id="locus-mode"><option value="5">Binned · 5 assemblies per bar</option><option value="20">Binned · 20 assemblies per bar</option><option value="50">Binned · 50 assemblies per bar</option><option value="200">Binned · 200 assemblies per bar</option><option value="raw">Individual assemblies</option><option value="off">Hide locus bars</option></select><strong id="locus-readout"></strong><p id="locus-explain"></p><label class="checkbox icon-toggle"><input type="checkbox" id="show-icons" checked>Genome Atlas silhouettes</label></section><div class="release"><span class="tag">Full release</span><div class="big">566.34<em>M</em></div><div class="sub">candidate coding loci</div><div class="stats"><div><b>48,167</b><span>annotated assemblies</span></div><div><b>22,617</b><span>annotated taxa</span></div><div class="stat-wide"><b>27.18 Tbp</b><span>across included assemblies</span></div></div></div><section class="legend"><label class="tag" for="scope">Compare assembly records</label><select id="scope"><option value="0">GenBank annotation records</option><option value="1">RefSeq comparison corpus</option></select><div class="key"><i class="sample-line blue"></i><span id="blue-label"></span><strong id="blue-count"></strong></div><div class="key"><i class="sample-line"></i><span id="green-label"></span><strong id="green-count"></strong></div><label class="checkbox"><input id="existing-only" type="checkbox">Emphasise matching assemblies</label><p id="scope-note" class="scope-note"></p></section><div class="quick"><button id="human">Find a human assembly ↗</button></div><button id="gene-open" style="font-size:11px;border:0;padding:6px 0;color:var(--green)">See its coding regions →</button></aside><section class="stage-wrap"><div class="topbar"><div class="searchbox"><input id="search" type="search" aria-label="Search assemblies or species" placeholder="Search a species or assembly…" disabled autocomplete="off"><div id="results"></div></div><button id="animate-sweep" disabled aria-label="Animate a 4-second sweep of the whole tree"><svg class="sweep-play" viewBox="0 0 16 16" aria-hidden="true"><path d="M4 2.5 13 8 4 13.5Z"/></svg><svg class="sweep-stop" viewBox="0 0 16 16" aria-hidden="true"><path d="M4 4h8v8H4Z"/></svg><span id="sweep-button-label">Animate sweep</span></button></div><div class="sweep-meter" aria-label="Cumulative counts in the current sweep"><div><strong id="seen-species">0</strong><span>Species seen</span></div><div><strong id="seen-assemblies">0</strong><span>Assemblies seen</span></div><div><strong id="seen-loci">0</strong><span>Coding loci seen</span></div><p><span id="sweep-status">Ready · 4 seconds</span><span>Shown subset · unique species · available locus counts</span></p></div><span id="sweep-announcement" class="visually-hidden" role="status"></span><div id="stage"><canvas id="tree" aria-label="Interactive taxonomic tree of 33,588 assembly strands. Use search or the human assembly button to inspect individual records."></canvas><canvas id="tree-still" aria-hidden="true"></canvas><canvas id="overlay" aria-hidden="true"></canvas><canvas id="interaction" aria-hidden="true"></canvas><div id="loading">Preparing 33,588 assembly strands…</div></div><div id="layer-tooltip" hidden></div><section id="selection" hidden><button id="sel-close" aria-label="Close assembly details">×</button><div id="sel-state">Hover preview</div><h2 id="sel-name"></h2><a id="sel-id" target="_blank" rel="noopener"></a><div id="sel-lineage"></div><div id="sel-status"></div><div id="sel-data"></div><div class="data-access"><a id="sel-files" target="_blank" rel="noopener">Annotation files ↗</a><br><button id="sel-download">Get annotation data ↗</button><p id="sel-availability"></p></div><button id="sel-focus">Follow this strand ↗</button><button id="sel-loci" hidden>See coding regions →</button></section><div class="help"><b>Hover centre or strands · Click to freeze · Click outside to release · Scroll to zoom · Drag to pan</b><br>Topology follows taxonomy. Branch length does not represent evolutionary time.</div><div class="controls"><button id="detail">Strand detail</button><button id="zoomout" aria-label="Zoom out">−</button><span id="zoom-readout">1.0×</span><button id="zoomin" aria-label="Zoom in">+</button><button id="reset">Whole tree</button></div></section></main><dialog id="gene-dialog"><button class="dialog-close" id="gene-close" aria-label="Close coding-region view">×</button><span class="tag">From an assembly to its annotations</span><h2 style="margin-top:16px">Homo sapiens</h2><p id="gene-summary"></p><div class="gene-label">GCA_000001405.29 · chromosome 1, CM000663.2 · first 1,000,000 bases<br>Upper track: forward strand · lower track: reverse strand</div><svg id="gene-svg" viewBox="0 0 1110 210" role="img" aria-label="Actual predicted coding segments in the first one million bases of human chromosome 1"></svg><p id="gene-count-window"></p><p class="gene-notes">Real coordinates from the published Carbon-A GFF3. These are model predictions, not experimentally confirmed genes. The blue/green tree colours describe annotation records for an assembly; they do not classify individual loci as known or new.</p></dialog><dialog id="about-dialog"><button class="dialog-close" id="about-close" aria-label="Close data information">×</button><h2>What is being drawn</h2><p>Each of the 33,588 strands is a distinct accession from the explorer’s covered-assembly table, mapped through the NCBI taxonomy. No assemblies or taxonomic branches have been invented to fill out the release total.</p><ul><li>Tree and common names: the existing HuggingFaceBio GenBank annotation explorer, 7 October 2026.</li><li>Existing annotation evidence: the cached NCBI GenBank assembly summary, 7 October 2026; a named annotation provider is required.</li><li>RefSeq mode counts matching assemblies, not genes: exact versioned matches between an assembly’s paired GCF accession and the project’s 2,055-assembly comparison corpus. It does not cover all RefSeq annotations.</li><li>Gene detail: actual published GFF3 coordinates and metadata for GCA_000001405.29.</li><li>Release totals supplied on 8 October 2026: 48,167 assemblies, 22,617 taxa, 566.34M coding loci and 27.18 Tbp.</li></ul><p>The centre hand points to the selected strand. Hover or drag inside the centre to select by direction; clicking pins the selection. Animate sweep makes a full clockwise turn in 4 seconds and counts every assembly passed, including strands crossed between animation frames. Species are deduplicated by their NCBI species-rank ancestor; coding loci sum only published counts. A complete sweep of this subset reaches 16,374 species, 33,588 assemblies and 212,905,534 loci. The counters reset on replay.</p><p>Equal-width assembly strands preserve count through shared ancestors. At overview scale many strands fall within a pixel; zoom reveals them separately. Radius and branch length are layout choices, not dates or evolutionary distances.</p><p>For example, the human branch contains 1,412 assemblies: 2 match the project’s RefSeq comparison corpus, while 4 have annotation-provider records in this GenBank snapshot. These small counts describe assembly records, not human genes. RefSeq annotations are maintained separately from GenBank submission annotations. <a href="https://www.ncbi.nlm.nih.gov/refseq/about/human/" target="_blank" rel="noopener">NCBI’s explanation of human reference annotation</a>.</p><p>The outer bars sum measured gene_count values from published per-assembly metadata. Bins contain consecutive assembly tips in taxonomy order. Grey base marks indicate the fraction of assemblies without counts; these are not treated as measured zeros. The radial scale is linear and recalculated for each bin size. Colours within the tree classify assemblies; the green outer bars count all predicted loci, regardless of prior annotation status.</p><p>Silhouettes are the original PhyloPic assets and taxon assignments used by the Genome Atlas. Click an icon to explore its branch. <button id="credits-open">Icon credits and licences</button></p><p><a href="https://huggingface.co/spaces/HuggingFaceBio/genbank-annotation-explorer" target="_blank" rel="noopener">Source explorer ↗</a> · <a href="https://www.ncbi.nlm.nih.gov/datasets/docs/v2/reference-docs/ftp-browsing/" target="_blank" rel="noopener">NCBI metadata ↗</a></p></dialog><dialog id="credits-dialog"><button id="credits-close" class="dialog-close" aria-label="Close icon credits">×</button><h2>Genome Atlas silhouettes</h2><p>Original PhyloPic silhouettes, reused with the atlas’s taxonomic assignments. Their fill colour is adapted to the selected theme; original SVG files are preserved in the source data. Each image’s source page and licence are linked below.</p><div id="icon-credits"></div></dialog><dialog id="data-dialog"><button id="data-close" class="dialog-close" aria-label="Close download instructions">×</button><h2>Get annotation data</h2><p id="data-assembly"></p><p id="data-description"></p><p><a id="data-browse" target="_blank" rel="noopener">Browse annotation files ↗</a></p><p class="data-setup">Install <code>huggingface_hub</code> and run <code>hf auth login</code> with an account that can access the bucket.</p><pre><code id="data-code"></code></pre><button id="data-copy">Copy Python code</button><span id="copy-status" role="status"></span><p><a href="https://huggingface.co/docs/huggingface_hub/guides/buckets#download-files" target="_blank" rel="noopener">Hugging Face download documentation ↗</a></p></dialog><script id="dataset" type="application/octet-stream">__DATA__</script><script>__SCRIPT__</script></body></html>
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html><html lang="en" data-theme="dark"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Carbon-A — Across the tree of life</title><script>try{const t=localStorage.getItem('carbon-a-theme');if(t==='light'||t==='dark')document.documentElement.dataset.theme=t}catch{}if(new URLSearchParams(location.search).get('record')==='1')document.documentElement.dataset.recording='true';</script><style>
2
  __FONTS__
3
  :root{color-scheme:dark;--green:#29e99a;--blue:#789dff;--ink:#e6f0ed;--muted:#91a5a4;--rule:#233038;--bg:#080e13;--panel:#0d151c;--hover:#16252c;--secondary:#bccdc7;--shadow:#00000050}
4
+ :root[data-theme="light"]{color-scheme:light;--green:#00b86b;--blue:#4167ec;--ink:#14221c;--muted:#6e7b73;--rule:#e6ece8;--bg:#fff;--panel:#fff;--hover:#f6faf7;--secondary:#435a4c;--shadow:#2035280c}
5
+ *{box-sizing:border-box}body{margin:0;color:var(--ink);background:var(--bg);font:13px Inter,Arial,sans-serif}button,input,select{font:inherit;color:inherit}button,a,input,select{-webkit-tap-highlight-color:transparent}button{cursor:pointer;background:var(--bg);border:1px solid var(--rule);padding:9px 13px}button:hover{border-color:var(--muted);background:var(--hover)}button:focus-visible,a:focus-visible,input:focus-visible,select:focus-visible{outline:2px solid var(--green);outline-offset:3px}button:disabled{opacity:.35;cursor:default}a{color:inherit}input::placeholder{color:var(--muted);opacity:1}[hidden]{display:none!important}
6
+ .presentation{height:100svh;min-height:650px;display:grid;grid-template-rows:auto minmax(360px,1fr) auto}
7
+ .hero{padding:30px 28px 8px;text-align:center}
8
+ h1{font-family:Manrope,Arial,sans-serif;font-size:clamp(29px,3.4vw,55px);font-weight:500;line-height:1.2;letter-spacing:-1.9px;margin:0}
9
+ .wordmark{font-weight:800;white-space:nowrap}.wordmark em{font-style:normal;color:var(--green)}.title-separator{color:var(--muted);font-weight:400;margin:0 .24em}
10
+ .stage-wrap{position:relative;min-width:0;min-height:0;width:100%;max-width:1600px;justify-self:center;overflow:hidden}
11
+ #stage{position:absolute;inset:6px 24px 0;touch-action:none}canvas{position:absolute;width:100%;height:100%;inset:0}#tree{cursor:default;touch-action:none}#overlay,#interaction,#tree-still{pointer-events:none}#tree-still{visibility:hidden}[data-sweeping="true"] #tree,[data-sweeping="true"] #overlay{visibility:hidden}[data-sweeping="true"] #tree-still{visibility:visible}
12
+ .resources{padding:12px 24px 18px;text-align:center}
13
+ .resource-links{display:flex;justify-content:center;gap:10px;flex-wrap:wrap}
14
+ .resource-links a{display:flex;align-items:center;justify-content:space-between;gap:28px;padding:12px 16px;min-width:120px;border:1px solid var(--rule);border-radius:4px;text-decoration:none;font-size:13px;font-weight:500;background:transparent}
15
+ .resource-links a span{color:var(--green);font-size:15px}.resource-links a:hover{border-color:var(--green);background:var(--hover)}
16
+ .utilities{display:flex;justify-content:center;align-items:center;gap:14px;margin-top:9px}
17
+ .utilities button{border:0;background:transparent;color:var(--muted);padding:6px 2px;font-size:10px}.utilities button:hover{color:var(--ink)}
18
+ #theme-toggle{display:grid;place-items:center;width:26px;height:26px;padding:4px;flex-shrink:0}#theme-toggle svg{width:15px;height:15px;stroke:currentColor;stroke-width:1.5;fill:none}[data-theme="dark"] .theme-moon,[data-theme="light"] .theme-sun{display:none}
19
+ [data-recording="true"] .utilities{visibility:hidden}[data-recording="true"] #stage{pointer-events:none}[data-recording="true"] #selection,[data-recording="true"] #layer-tooltip{display:none!important}
20
+ .tag{font-size:10px;letter-spacing:1.3px;color:var(--muted);text-transform:uppercase}
21
+ #loading{position:absolute;inset:0;display:grid;place-content:center;background:var(--bg);color:var(--muted);z-index:10}
22
+ #selection{position:absolute;right:24px;top:18px;width:295px;max-height:calc(100% - 36px);overflow:auto;background:var(--panel);border:1px solid var(--rule);box-shadow:0 8px 40px var(--shadow);z-index:5;padding:23px}
23
+ #sel-close{position:absolute;top:6px;right:8px;border:0;background:transparent;font-size:20px;padding:0 4px;color:var(--muted)}#sel-name{font:600 18px/1.25 Manrope,Arial,sans-serif;letter-spacing:-.5px;margin:0 0 6px}#sel-id{font-size:11px;color:var(--green);font-weight:500;text-decoration:none}#sel-lineage{font-size:10px;line-height:1.7;color:var(--muted);margin-top:15px}#sel-status{font-size:11px;line-height:1.6;border-top:1px solid var(--rule);padding-top:13px;margin-top:14px}#sel-status small{font-size:10px;color:var(--muted);margin-left:13px}.dot{display:inline-block;width:6px;height:6px;background:var(--green);border-radius:50%;margin-right:6px}.dot.blue{background:var(--blue)}#sel-data{display:grid;gap:3px;margin:16px 0;font-size:10px}#sel-data span{color:var(--muted);margin-top:7px}#sel-data b{font-weight:500;font-size:12px}#selection button:not(#sel-close){font-size:10px;margin:4px 0;padding:8px 10px}#sel-state{font-size:9px;letter-spacing:1.2px;text-transform:uppercase;color:var(--muted);margin:0 0 12px}#selection[data-pinned="true"] #sel-state{color:var(--green)}
24
+ #layer-tooltip{position:absolute;z-index:8;width:250px;pointer-events:none;padding:13px 15px;background:var(--panel);border:1px solid var(--rule);box-shadow:0 5px 25px var(--shadow);font-size:11px}#layer-tooltip b,#layer-tooltip span,#layer-tooltip small{display:block}#layer-tooltip b{font-weight:600;margin-bottom:6px}#layer-tooltip span{line-height:1.5}#layer-tooltip small{font-size:10px;color:var(--muted);margin-top:6px}
25
+ .data-access{border-top:1px solid var(--rule);padding-top:10px;margin-top:12px}.data-access button,.data-access a{color:var(--green)}.data-access a{font-size:11px;line-height:1.8;text-decoration:none}#sel-availability{font-size:10px;line-height:1.5;color:var(--muted);margin:3px 0 8px}
26
+ dialog{background:var(--panel);border:1px solid var(--rule);padding:32px;max-width:calc(100vw - 32px);max-height:85svh;width:1000px;color:var(--ink)}dialog::backdrop{background:#00000070;backdrop-filter:blur(3px)}dialog h2{font:600 28px/1.2 Manrope,Arial,sans-serif;letter-spacing:-1px;margin:0 0 15px}dialog p{font-size:12px;line-height:1.7;color:var(--muted)}dialog ul{padding-left:18px;font-size:12px;line-height:1.9;color:var(--muted)}.dialog-close{float:right;border:0;font-size:22px;padding:0 4px}
27
+ #view-dialog{width:520px}#about-dialog{width:680px}#credits-dialog{width:740px}#data-dialog{width:780px}
28
+ .searchbox{position:relative;margin:22px 0 18px}#search{width:100%;padding:12px;border:1px solid var(--rule);background:var(--bg);font-size:12px}#results{position:absolute;top:40px;width:100%;background:var(--panel);box-shadow:0 10px 30px var(--shadow);font-size:11px;z-index:4}#results:empty{display:none}#results button{display:block;width:100%;text-align:left;padding:11px;border:0;border-bottom:1px solid var(--rule)}#results strong,#results span{display:block;font-size:11px;font-weight:500}#results span{font-size:10px;color:var(--muted);margin-top:3px}
29
+ .legend,.locus-controls{border-top:1px solid var(--rule);padding-top:18px;margin-top:18px}.legend select,.locus-controls select{width:100%;border:0;border-bottom:1px solid var(--rule);padding:9px 0;margin:6px 0 12px;background:var(--panel);font-size:12px}.key{display:flex;align-items:center;gap:8px;font-size:11px;margin-bottom:10px}.key strong{margin-left:auto;font-weight:500;font-variant-numeric:tabular-nums}.sample-line{height:2px;width:21px;display:inline-block;background:var(--green);flex-shrink:0}.sample-line.blue{background:var(--blue)}.scope-note,.locus-controls p{font-size:10px;color:var(--muted);line-height:1.5;margin:10px 0 13px}.checkbox{font-size:11px;display:flex;gap:7px;align-items:center}.checkbox input{accent-color:var(--green)}.locus-controls strong{display:block;font-size:11px;font-weight:500;margin-top:10px}.icon-toggle{margin-top:12px}.quick{display:flex;gap:6px;margin:20px 0 6px}.quick button{font-size:11px;padding:8px 10px}.controls{display:flex;align-items:center;gap:5px;flex-wrap:wrap;margin-top:18px}.controls button{font-size:11px;padding:7px 10px}.controls span{font-size:10px;padding:0 5px;min-width:42px;text-align:center;color:var(--muted)}
30
+ #icon-credits{max-height:60vh;overflow:auto}#icon-credits table{border-collapse:collapse;width:100%;font-size:11px;text-align:left}#icon-credits th,#icon-credits td{padding:10px 8px;border-bottom:1px solid var(--rule)}#icon-credits a,#data-dialog a{color:var(--green)}.gene-label{font-size:11px;margin-top:28px;color:var(--muted)}#gene-svg{width:100%;height:auto;font:11px Inter;fill:var(--muted);margin:16px 0}.gene-notes{border-top:1px solid var(--rule);padding-top:15px;font-size:11px}#data-dialog pre{max-height:45vh;overflow:auto;border:1px solid var(--rule);background:var(--bg);padding:18px;font-size:11px;line-height:1.7;white-space:pre}#copy-status{font-size:11px;margin-left:12px;color:var(--green)}#data-dialog .data-setup{font-size:11px}
 
 
 
 
 
 
 
31
  .visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
32
+ @media(max-width:700px){.presentation{min-height:650px}.hero{padding:25px 22px 4px}h1{font-size:clamp(26px,5.8vw,38px);letter-spacing:-1px}.title-separator{display:none}.title-topic{display:block;margin-top:6px;font-size:.8em}#stage{inset:0 4px}.resource-links{gap:7px}.resource-links a{min-width:0;padding:10px 11px;gap:12px;font-size:11px}.resources{padding:8px 12px 14px}.utilities{margin-top:8px}#selection{width:275px;right:12px;top:10px;max-height:calc(100% - 20px)}dialog{padding:24px}}
33
+ @media(prefers-reduced-motion:no-preference){button,.resource-links a{transition:background .15s,border-color .15s}}
34
+ </style></head><body>
35
+ <main class="presentation">
36
+ <header class="hero"><h1><span class="wordmark">Carbon-<em>A</em></span><span class="title-separator" aria-hidden="true"> · </span><span class="title-topic">Across the tree of life</span></h1></header>
37
+ <section class="stage-wrap" aria-label="Carbon-A assembly tree">
38
+ <span id="sweep-announcement" class="visually-hidden" role="status"></span>
39
+ <div id="stage"><canvas id="tree" aria-label="Interactive taxonomic tree of assembly strands. Hover to inspect, click to freeze, and scroll to zoom. Search is in View options."></canvas><canvas id="tree-still" aria-hidden="true"></canvas><canvas id="overlay" aria-hidden="true"></canvas><canvas id="interaction" aria-hidden="true"></canvas><div id="loading">Preparing the assembly tree…</div></div>
40
+ <div id="layer-tooltip" hidden></div><section id="selection" hidden><button id="sel-close" aria-label="Close assembly details">×</button><div id="sel-state">Hover preview</div><h2 id="sel-name"></h2><a id="sel-id" target="_blank" rel="noopener"></a><div id="sel-lineage"></div><div id="sel-status"></div><div id="sel-data"></div><div class="data-access"><a id="sel-files" target="_blank" rel="noopener">Annotation files ↗</a><br><button id="sel-download">Get annotation data ↗</button><p id="sel-availability"></p></div><button id="sel-focus">Follow this strand ↗</button><button id="sel-loci" hidden>See coding regions →</button></section></section>
41
+ <footer class="resources"><nav class="resource-links" aria-label="Carbon-A resources"><a href="https://huggingface.co/HuggingFaceBio/Carbon-A-1.2B" target="_blank" rel="noopener">Model<span aria-hidden="true">↗</span></a><a href="https://huggingface.co/buckets/HuggingFaceBio/genbank-annotations" target="_blank" rel="noopener">Database<span aria-hidden="true">↗</span></a><a href="https://huggingface.co/spaces/HuggingFaceBio/carbon-a-database-explorer" target="_blank" rel="noopener">Explorer<span aria-hidden="true">↗</span></a><a href="https://huggingface.co/buckets/HuggingFaceBio/Carbon-A-Technical-Report" target="_blank" rel="noopener">Paper<span aria-hidden="true">↗</span></a><a href="https://huggingface.co/blog/HuggingFaceBio/carbon-annotator-genbank-genome-annotation" target="_blank" rel="noopener">Blog<span aria-hidden="true">↗</span></a></nav><div class="utilities"><button id="view-options">View options</button><button id="about">About the data</button><button id="theme-toggle" aria-label="Switch to light mode" title="Switch to light mode"><svg class="theme-sun" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 1v3m0 16v3M1 12h3m16 0h3M4.2 4.2l2.1 2.1m11.4 11.4 2.1 2.1M4.2 19.8l2.1-2.1M17.7 6.3l2.1-2.1"/></svg><svg class="theme-moon" viewBox="0 0 24 24" aria-hidden="true"><path d="M20.5 14.5A9 9 0 0 1 9.5 3.5a9 9 0 1 0 11 11Z"/></svg></button></div></footer>
42
+ </main>
43
+ <dialog id="view-dialog"><button id="view-close" class="dialog-close" aria-label="Close view options">×</button><h2>Explore the tree</h2><div class="searchbox"><input id="search" type="search" aria-label="Search assemblies or species" placeholder="Search a species or assembly…" disabled autocomplete="off"><div id="results"></div></div><section class="locus-controls"><label class="tag" for="locus-mode">Coding loci at the strand tips</label><select id="locus-mode"><option value="5">Binned · 5 assemblies per bar</option><option value="20">Binned · 20 assemblies per bar</option><option value="50">Binned · 50 assemblies per bar</option><option value="200">Binned · 200 assemblies per bar</option><option value="raw">Individual assemblies</option><option value="off">Hide locus bars</option></select><strong id="locus-readout"></strong><p id="locus-explain"></p><label class="checkbox icon-toggle"><input type="checkbox" id="show-icons" checked>Genome Atlas silhouettes</label></section><section class="legend"><label class="tag" for="scope">Compare assembly records</label><select id="scope"><option value="0">GenBank annotation records</option><option value="1">RefSeq comparison corpus</option></select><div class="key"><i class="sample-line blue"></i><span id="blue-label"></span><strong id="blue-count"></strong></div><div class="key"><i class="sample-line"></i><span id="green-label"></span><strong id="green-count"></strong></div><label class="checkbox"><input id="existing-only" type="checkbox">Emphasise matching assemblies</label><p id="scope-note" class="scope-note"></p></section><div class="controls"><button id="detail">Strand detail</button><button id="zoomout" aria-label="Zoom out">−</button><span id="zoom-readout">1.0×</span><button id="zoomin" aria-label="Zoom in">+</button><button id="reset">Whole tree</button></div><div class="quick"><button id="human">Find a human assembly ↗</button><button id="gene-open">Its coding regions →</button></div><p>Hover to inspect · Click to freeze · Click outside to release.<br>Scroll to zoom · Drag to pan · Space to animate.</p></dialog>
44
+ <dialog id="gene-dialog"><button class="dialog-close" id="gene-close" aria-label="Close coding-region view">×</button><span class="tag">From an assembly to its annotations</span><h2 style="margin-top:16px">Homo sapiens</h2><p id="gene-summary"></p><div class="gene-label">GCA_000001405.29 · chromosome 1, CM000663.2 · first 1,000,000 bases<br>Upper track: forward strand · lower track: reverse strand</div><svg id="gene-svg" viewBox="0 0 1110 210" role="img" aria-label="Actual predicted coding segments in the first one million bases of human chromosome 1"></svg><p id="gene-count-window"></p><p class="gene-notes">Real coordinates from the published Carbon-A GFF3. These are model predictions, not experimentally confirmed genes. The blue/green tree colours describe annotation records for an assembly; they do not classify individual loci as known or new.</p></dialog><dialog id="about-dialog"><button class="dialog-close" id="about-close" aria-label="Close data information">×</button><h2>What is being drawn</h2><p>Each of the <span id="shown">33,588</span> strands across <span id="taxa-shown">16,976</span> taxa is a distinct accession from the explorer’s covered-assembly table, mapped through the NCBI taxonomy. This is a subset of the release. No assemblies or taxonomic branches have been invented to fill out the release total.</p><ul><li>Tree and common names: the existing HuggingFaceBio GenBank annotation explorer, 7 October 2026.</li><li>Existing annotation evidence: the cached NCBI GenBank assembly summary, 7 October 2026; a named annotation provider is required.</li><li>RefSeq mode counts matching assemblies, not genes: exact versioned matches between an assembly’s paired GCF accession and the project’s 2,055-assembly comparison corpus. It does not cover all RefSeq annotations.</li><li>Gene detail: actual published GFF3 coordinates and metadata for GCA_000001405.29.</li><li>Release totals supplied on 8 October 2026: 48,167 assemblies, 22,617 taxa, 566.34M coding loci and 27.18 Tbp.</li></ul><p>The centre hand points to the selected strand. Hover or drag inside the centre to select by direction; click to freeze and click outside the tree to release. For video capture, press Space to start or replay a four-second clockwise sweep. Escape stops it. The sweep has no counters.</p><p>Equal-width assembly strands preserve count through shared ancestors. At overview scale many strands fall within a pixel; zoom reveals them separately. Radius and branch length are layout choices, not dates or evolutionary distances.</p><p>For example, the human branch contains 1,412 assemblies: 2 match the project’s RefSeq comparison corpus, while 4 have annotation-provider records in this GenBank snapshot. These small counts describe assembly records, not human genes. RefSeq annotations are maintained separately from GenBank submission annotations. <a href="https://www.ncbi.nlm.nih.gov/refseq/about/human/" target="_blank" rel="noopener">NCBI’s explanation of human reference annotation</a>.</p><p>The outer bars sum measured gene_count values from published per-assembly metadata. Bins contain consecutive assembly tips in taxonomy order. Grey base marks indicate the fraction of assemblies without counts; these are not treated as measured zeros. The radial scale is linear and recalculated for each bin size. Colours within the tree classify assemblies; the green outer bars count all predicted loci, regardless of prior annotation status.</p><p>Silhouettes are the original PhyloPic assets and taxon assignments used by the Genome Atlas. Click an icon to explore its branch. <button id="credits-open">Icon credits and licences</button></p><p><a href="https://huggingface.co/spaces/HuggingFaceBio/carbon-a-database-explorer" target="_blank" rel="noopener">Source explorer ↗</a> · <a href="https://www.ncbi.nlm.nih.gov/datasets/docs/v2/reference-docs/ftp-browsing/" target="_blank" rel="noopener">NCBI metadata ↗</a></p></dialog><dialog id="credits-dialog"><button id="credits-close" class="dialog-close" aria-label="Close icon credits">×</button><h2>Genome Atlas silhouettes</h2><p>Original PhyloPic silhouettes, reused with the atlas’s taxonomic assignments. Their fill colour is adapted to the selected theme; original SVG files are preserved in the source data. Each image’s source page and licence are linked below.</p><div id="icon-credits"></div></dialog><dialog id="data-dialog"><button id="data-close" class="dialog-close" aria-label="Close download instructions">×</button><h2>Get annotation data</h2><p id="data-assembly"></p><p id="data-description"></p><p><a id="data-browse" target="_blank" rel="noopener">Browse annotation files ↗</a></p><p class="data-setup">Install <code>huggingface_hub</code> and run <code>hf auth login</code> with an account that can access the bucket.</p><pre><code id="data-code"></code></pre><button id="data-copy">Copy Python code</button><span id="copy-status" role="status"></span><p><a href="https://huggingface.co/docs/huggingface_hub/guides/buckets#download-files" target="_blank" rel="noopener">Hugging Face download documentation ↗</a></p></dialog><script id="dataset" type="application/octet-stream">__DATA__</script><script>__SCRIPT__</script></body></html>
verify_live.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+ import json,hashlib,requests
3
+ from huggingface_hub import HfApi,hf_hub_download,get_token
4
+ from check_presentation import check
5
+ R=Path(__file__).parent;repo='HuggingFaceBio/carbon-a-tree-preview';api=HfApi()
6
+ p=json.loads((R/'publication.json').read_text());info=api.repo_info(repo,repo_type='space')
7
+ assert info.private and info.sha==p['commit']
8
+ for name in ['index.html','carbon-a-strand-poc.png','provenance.json']:
9
+ downloaded=Path(hf_hub_download(repo,name,repo_type='space',revision=p['commit']))
10
+ assert hashlib.sha256(downloaded.read_bytes()).digest()==hashlib.sha256((R/name).read_bytes()).digest(),name
11
+ print('verified',name,flush=True)
12
+ url='https://huggingfacebio-carbon-a-tree-preview.static.hf.space/'
13
+ headers={'Authorization':'Bearer '+get_token()}
14
+ r=requests.get(url,headers=headers,timeout=30);r.raise_for_status()
15
+ assert r.content==(R/'index.html').read_bytes(),'Static app is still serving a different revision.'
16
+ result=check(url,headers)
17
+ p['verified']={'private':True,'runtime':api.get_space_runtime(repo).stage,'live_http_status':r.status_code,'files_match':True,'browser':result}
18
+ (R/'publication.json').write_text(json.dumps(p,indent=2));print(json.dumps(p['verified']))