File size: 8,419 Bytes
5ea40ce
39a61da
 
 
 
 
 
 
 
 
 
 
 
 
 
b64beb5
5ea40ce
 
b64beb5
39a61da
 
b64beb5
39a61da
f5d37e0
 
 
 
39a61da
f5d37e0
 
39a61da
 
 
f5d37e0
 
 
 
 
 
39a61da
f5d37e0
 
 
 
 
 
 
 
39a61da
f5d37e0
 
39a61da
 
 
 
5ea40ce
39a61da
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5ea40ce
39a61da
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5ea40ce
39a61da
 
 
 
 
 
 
 
 
 
 
 
 
5ea40ce
39a61da
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
/* 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; }
}