/* section-tree.css, §7 Species tree (Carbon-derived phylogeny). Layout mirrors §6's editorial card. The grid is a 2-column structure: left = SVG dendrogram spine (Bezier elbows), right = aligned data tracks (italic name + kingdom chip + log count bar + NCBI agreement). Every row gets a subtle kingdom background tint so the eye reads coherent blocks without us drawing extra lines. */ .tree-toolbar { display: flex; align-items: center; flex-wrap: wrap; gap: 12px; font-family: "JetBrains Mono", monospace; font-size: 11px; color: #666; margin-bottom: 14px; } .tree-toolbar .spacer { flex: 1; } /* Pill sizing + spacing live in controls.css now (canonical across demos); we only override the casing here because §7's vocabulary, `ward`, `upgma`, `kingdom-level`, `sister-level`, reads better lowercase than in the global uppercase + tracked treatment. */ .tree-toolbar .pills .pill { text-transform: lowercase; letter-spacing: 0; } /* Headline agreement metric for §7: a percentage in Carbon green next to the raw ratio, with a discrete uppercase caption underneath naming what the score compares against (e.g. "match · ncbi kingdom"). Pure typography, no chrome, no progress bar — sits in the toolbar as a quiet stat block. */ .tree-score { display: inline-flex; flex-direction: column; align-items: flex-end; gap: 2px; font-family: "JetBrains Mono", monospace; color: #1f1f1d; } .tree-score-headline { display: flex; align-items: baseline; gap: 8px; } .tree-score-pct { font-size: 17px; font-weight: 700; line-height: 1; color: #317f3f; font-variant-numeric: tabular-nums; letter-spacing: -0.01em; } .tree-score-ratio { font-size: 11px; color: #888; font-variant-numeric: tabular-nums; white-space: nowrap; } .tree-score-label { font-size: 9px; color: #888; text-transform: uppercase; letter-spacing: 1.2px; } /* Main grid : SVG spine on the left, aligned tracks on the right. The spine's height matches exactly N * row_h so each leaf lands on its track row. We never use grid here (rows differ in semantics); just two columns of equal-height children rendered in JS. */ .tree-grid { display: grid; grid-template-columns: minmax(280px, 1.5fr) minmax(0, 1.6fr); gap: 0; margin-top: 6px; background: #fff; border: 1px solid #e5e3da; } .tree-spine { position: relative; padding: 12px 0 28px 12px; } .tree-spine svg { display: block; width: 100%; height: 100%; overflow: visible; } /* Inline labels at each tip, only used in mobile (where the .tree-rows panel stacks below). On desktop they're rendered but hidden so we don't have to invalidate the SVG on viewport changes. */ .tree-spine svg .leaf-svg-label, .tree-spine svg .leaf-svg-chip { display: none; } .tree-spine svg .leaf-svg-label { font-family: "JetBrains Mono", monospace; font-size: 11px; font-style: italic; dominant-baseline: middle; } .tree-spine .axis-label { position: absolute; bottom: 6px; left: 12px; font-family: "JetBrains Mono", monospace; font-size: 9px; color: #888; text-transform: uppercase; letter-spacing: 1.2px; } .tree-rows { display: flex; flex-direction: column; padding: 12px 0 28px 0; } .tree-row { display: grid; /* chip · name · bar · ncbi, name column is FIXED width so every row's bar starts at the exact same X. (max-content gets broken here by .tree-name's overflow:hidden, which makes each cell size to its own content instead of the column's longest item.) */ grid-template-columns: 10px 115px minmax(60px, 1fr) 24px; gap: 10px; align-items: center; padding: 0 14px 0 12px; height: 22px; /* must equal ROW_H in the JS tree renderer */ transition: background 0.12s ease-out; cursor: default; } .tree-row:hover { background: rgba(31, 31, 29, 0.04); } .tree-row.dim { opacity: 0.35; } /* Subtle background stripe by kingdom, matches the §6 palette */ .tree-row[data-kingdom="vertebrates"] { background-image: linear-gradient(to right, rgba(31, 31, 29, 0.04), transparent 40px); } .tree-row[data-kingdom="invertebrates"] { background-image: linear-gradient(to right, rgba(122, 98, 66, 0.07), transparent 40px); } .tree-row[data-kingdom="plants"] { background-image: linear-gradient(to right, rgba(49, 127, 63, 0.07), transparent 40px); } .tree-row[data-kingdom="fungi"] { background-image: linear-gradient(to right, rgba(169, 118, 47, 0.07), transparent 40px); } .tree-row[data-kingdom="bacteria"] { background-image: linear-gradient(to right, rgba(176, 0, 32, 0.07), transparent 40px); } .tree-row[data-kingdom="viruses"] { background-image: linear-gradient(to right, rgba(44, 90, 160, 0.07), transparent 40px); } .tree-row .tree-name { font-family: "JetBrains Mono", monospace; font-size: 12px; font-style: italic; color: #1f1f1d; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .tree-row[data-kingdom="vertebrates"] .tree-name { color: #1f1f1d; } .tree-row[data-kingdom="invertebrates"] .tree-name { color: #7a6242; } .tree-row[data-kingdom="plants"] .tree-name { color: #317f3f; } .tree-row[data-kingdom="fungi"] .tree-name { color: #a9762f; } .tree-row[data-kingdom="bacteria"] .tree-name { color: #b00020; } .tree-row[data-kingdom="viruses"] .tree-name { color: #2c5aa0; } .tree-row .tree-chip { width: 10px; height: 10px; border-radius: 2px; } .tree-row .tree-bar { /* Two-column inner grid so the rail always spans 1fr (= same start AND same end across rows), and the count sits in a fixed-width right column → chiffres et fin de rail s'alignent partout. */ display: grid; grid-template-columns: 1fr 46px; gap: 8px; align-items: center; height: 22px; } .tree-row .tree-bar .bar-track { position: relative; height: 6px; background: #efece1; border-radius: 1.5px; overflow: hidden; } .tree-row .tree-bar .bar-fill { position: absolute; left: 0; top: 0; bottom: 0; background: #c8c4b3; border-radius: 1.5px; } .tree-row .tree-bar .bar-num { font-family: "JetBrains Mono", monospace; font-size: 9px; color: #888; font-variant-numeric: tabular-nums; white-space: nowrap; text-align: right; } .tree-row .tree-ncbi { text-align: center; font-family: "JetBrains Mono", monospace; font-size: 14px; font-weight: 700; line-height: 1; user-select: none; } .tree-row .tree-ncbi[data-state="match"] { color: #317f3f; } .tree-row .tree-ncbi[data-state="mismatch"] { color: #b00020; } .tree-row .tree-ncbi[data-state="solo"] { color: #c8c5b9; } /* Tooltip floats over the grid on row hover, fed with top-3 NN. */ .tree-tooltip { position: absolute; background: #1f1f1d; color: #f7f5ee; font-family: "JetBrains Mono", monospace; font-size: 10px; padding: 8px 11px; border-radius: 3px; pointer-events: none; z-index: 50; box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18); opacity: 0; transition: opacity 0.1s ease-out; line-height: 1.5; white-space: nowrap; } .tree-tooltip.show { opacity: 1; } .tree-tooltip .tt-title { text-transform: uppercase; letter-spacing: 1.2px; font-size: 9px; color: #888; margin-bottom: 4px; } .tree-tooltip .tt-pair { display: flex; gap: 10px; align-items: baseline; font-variant-numeric: tabular-nums; } .tree-tooltip .tt-glyph { color: #888; width: 14px; } .tree-tooltip .tt-name { color: #f7f5ee; } .tree-tooltip .tt-name.expected { color: #317f3f; font-weight: 600; } .tree-tooltip .tt-dist { color: #888; margin-left: auto; } .tree-frame { position: relative; } /* anchor for the tooltip */ /* Footer legend strip + scoping caption */ .tree-legend { display: flex; gap: 18px; flex-wrap: wrap; margin-top: 10px; font-family: "JetBrains Mono", monospace; font-size: 10px; color: #666; } .tree-legend-item { display: flex; align-items: center; gap: 5px; } .tree-legend-swatch { width: 9px; height: 9px; border-radius: 2px; } .tree-legend-glyph { font-size: 12px; font-weight: 700; line-height: 1; } .tree-caption { margin-top: 6px; font-family: "JetBrains Mono", monospace; font-size: 10px; color: #999; line-height: 1.6; } @media (max-width: 720px) { .tree-grid { grid-template-columns: 1fr; } .tree-spine { border-bottom: 1px solid #e5e3da; padding-right: 12px; } .tree-spine svg .leaf-svg-label, .tree-spine svg .leaf-svg-chip { display: inline; } .tree-row { grid-template-columns: 10px 110px minmax(60px, 1fr) 22px; padding: 0 10px; } }