Spaces:
Running
Running
thibaud frere
commited on
Commit
·
99af53b
1
Parent(s):
8bb224c
update
Browse files- app/.astro/settings.json +3 -5
- app/astro.config.mjs +3 -3
- app/package.json +0 -0
- app/plugins/rehype/{code-copy-and-label.mjs → code-copy.mjs} +4 -2
- app/public/scripts/color-palettes.js +217 -0
- app/scripts/export-pdf.mjs +20 -5
- app/src/components/Accordion.astro +8 -12
- app/src/components/Footer.astro +0 -18
- app/src/components/RawHtml.astro +6 -0
- app/src/components/TableOfContents.astro +3 -16
- app/src/content/assets/images/thumb.png +3 -0
- app/src/content/embeds/against-baselines-deduplicated.html +52 -32
- app/src/content/embeds/against-baselines.html +29 -14
- app/src/content/embeds/all-ratings.html +1 -1
- app/src/content/embeds/banner.html +9 -3
- app/src/content/embeds/d3-pie.html +18 -6
- app/src/content/embeds/filters-quad.html +83 -78
- app/src/content/embeds/formatting-filters.html +1 -1
- app/src/content/embeds/image-correspondence-filters.html +1 -1
- app/src/content/embeds/internal-deduplication.html +22 -9
- app/src/content/embeds/relevance-filters.html +1 -1
- app/src/content/embeds/remove-ch.html +1 -1
- app/src/content/embeds/s25-ratings.html +1 -1
- app/src/content/embeds/ss-vs-s1.html +1 -1
- app/src/content/embeds/visual-dependency-filters.html +22 -9
- app/src/pages/index.astro +1 -1
- app/src/styles/_layout.css +25 -3
- app/src/styles/_print.css +2 -2
- app/src/styles/components/_code.css +28 -1
- app/src/styles/components/_table.css +3 -0
- app/src/styles/components/_tag.css +2 -2
app/.astro/settings.json
CHANGED
@@ -1,5 +1,3 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
4 |
-
}
|
5 |
-
}
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:585e9065073b0b2528747f99b78ccc382984e713ac7e6a5c9f99a47956e3f42e
|
3 |
+
size 58
|
|
|
|
app/astro.config.mjs
CHANGED
@@ -8,7 +8,7 @@ import remarkFootnotes from 'remark-footnotes';
|
|
8 |
import rehypeSlug from 'rehype-slug';
|
9 |
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
|
10 |
import rehypeCitation from 'rehype-citation';
|
11 |
-
import
|
12 |
import rehypeReferencesAndFootnotes from './plugins/rehype/post-citation.mjs';
|
13 |
import remarkIgnoreCitationsInCode from './plugins/remark/ignore-citations-in-code.mjs';
|
14 |
import rehypeRestoreAtInCode from './plugins/rehype/restore-at-in-code.mjs';
|
@@ -53,11 +53,11 @@ export default defineConfig({
|
|
53 |
[rehypeCitation, {
|
54 |
bibliography: 'src/content/bibliography.bib',
|
55 |
linkCitations: true,
|
56 |
-
csl:
|
57 |
}],
|
58 |
rehypeReferencesAndFootnotes,
|
59 |
rehypeRestoreAtInCode,
|
60 |
-
|
61 |
rehypeWrapTables
|
62 |
]
|
63 |
}
|
|
|
8 |
import rehypeSlug from 'rehype-slug';
|
9 |
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
|
10 |
import rehypeCitation from 'rehype-citation';
|
11 |
+
import rehypeCodeCopy from './plugins/rehype/code-copy.mjs';
|
12 |
import rehypeReferencesAndFootnotes from './plugins/rehype/post-citation.mjs';
|
13 |
import remarkIgnoreCitationsInCode from './plugins/remark/ignore-citations-in-code.mjs';
|
14 |
import rehypeRestoreAtInCode from './plugins/rehype/restore-at-in-code.mjs';
|
|
|
53 |
[rehypeCitation, {
|
54 |
bibliography: 'src/content/bibliography.bib',
|
55 |
linkCitations: true,
|
56 |
+
csl: "vancouver"
|
57 |
}],
|
58 |
rehypeReferencesAndFootnotes,
|
59 |
rehypeRestoreAtInCode,
|
60 |
+
rehypeCodeCopy,
|
61 |
rehypeWrapTables
|
62 |
]
|
63 |
}
|
app/package.json
CHANGED
Binary files a/app/package.json and b/app/package.json differ
|
|
app/plugins/rehype/{code-copy-and-label.mjs → code-copy.mjs}
RENAMED
@@ -1,6 +1,6 @@
|
|
1 |
-
// Minimal rehype plugin to wrap code blocks with a copy button
|
2 |
// Exported as a standalone module to keep astro.config.mjs lean
|
3 |
-
export default function
|
4 |
return (tree) => {
|
5 |
// Walk the tree; lightweight visitor to find <pre><code>
|
6 |
const visit = (node, parent) => {
|
@@ -90,3 +90,5 @@ export default function rehypeCodeCopyAndLabel() {
|
|
90 |
}
|
91 |
|
92 |
|
|
|
|
|
|
1 |
+
// Minimal rehype plugin to wrap code blocks with a copy button
|
2 |
// Exported as a standalone module to keep astro.config.mjs lean
|
3 |
+
export default function rehypeCodeCopy() {
|
4 |
return (tree) => {
|
5 |
// Walk the tree; lightweight visitor to find <pre><code>
|
6 |
const visit = (node, parent) => {
|
|
|
90 |
}
|
91 |
|
92 |
|
93 |
+
|
94 |
+
|
app/public/scripts/color-palettes.js
ADDED
@@ -0,0 +1,217 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// Global color palettes generator and watcher
|
2 |
+
// - Observes CSS variable --primary-color and theme changes
|
3 |
+
// - Generates categorical, sequential, and diverging palettes (OKLCH/OKLab)
|
4 |
+
// - Exposes results as CSS variables on :root
|
5 |
+
// - Supports variable color counts per palette via CSS vars
|
6 |
+
// - Dispatches a 'palettes:updated' CustomEvent after each update
|
7 |
+
|
8 |
+
(() => {
|
9 |
+
const MODE = { cssRoot: document.documentElement };
|
10 |
+
|
11 |
+
const getCssVar = (name) => {
|
12 |
+
try { return getComputedStyle(MODE.cssRoot).getPropertyValue(name).trim(); } catch { return ''; }
|
13 |
+
};
|
14 |
+
const getIntFromCssVar = (name, fallback) => {
|
15 |
+
const raw = getCssVar(name);
|
16 |
+
if (!raw) return fallback;
|
17 |
+
const v = parseInt(String(raw), 10);
|
18 |
+
if (Number.isNaN(v)) return fallback;
|
19 |
+
return v;
|
20 |
+
};
|
21 |
+
const clamp = (n, min, max) => Math.max(min, Math.min(max, n));
|
22 |
+
|
23 |
+
// Color math (OKLab/OKLCH)
|
24 |
+
const srgbToLinear = (u) => (u <= 0.04045 ? u / 12.92 : Math.pow((u + 0.055) / 1.055, 2.4));
|
25 |
+
const linearToSrgb = (u) => (u <= 0.0031308 ? 12.92 * u : 1.055 * Math.pow(Math.max(0, u), 1 / 2.4) - 0.055);
|
26 |
+
const rgbToOklab = (r, g, b) => {
|
27 |
+
const rl = srgbToLinear(r), gl = srgbToLinear(g), bl = srgbToLinear(b);
|
28 |
+
const l = Math.cbrt(0.4122214708 * rl + 0.5363325363 * gl + 0.0514459929 * bl);
|
29 |
+
const m = Math.cbrt(0.2119034982 * rl + 0.6806995451 * gl + 0.1073969566 * bl);
|
30 |
+
const s = Math.cbrt(0.0883024619 * rl + 0.2817188376 * gl + 0.6299787005 * bl);
|
31 |
+
const L = 0.2104542553 * l + 0.7936177850 * m - 0.0040720468 * s;
|
32 |
+
const a = 1.9779984951 * l - 2.4285922050 * m + 0.4505937099 * s;
|
33 |
+
const b2 = 0.0259040371 * l + 0.7827717662 * m - 0.8086757660 * s;
|
34 |
+
return { L, a, b: b2 };
|
35 |
+
};
|
36 |
+
const oklabToRgb = (L, a, b) => {
|
37 |
+
const l_ = L + 0.3963377774 * a + 0.2158037573 * b;
|
38 |
+
const m_ = L - 0.1055613458 * a - 0.0638541728 * b;
|
39 |
+
const s_ = L - 0.0894841775 * a - 1.2914855480 * b;
|
40 |
+
const l = l_ * l_ * l_;
|
41 |
+
const m = m_ * m_ * m_;
|
42 |
+
const s = s_ * s_ * s_;
|
43 |
+
const r = linearToSrgb(+4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s);
|
44 |
+
const g = linearToSrgb(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s);
|
45 |
+
const b3 = linearToSrgb(-0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s);
|
46 |
+
return { r, g, b: b3 };
|
47 |
+
};
|
48 |
+
const oklchToOklab = (L, C, hDeg) => { const h = (hDeg * Math.PI) / 180; return { L, a: C * Math.cos(h), b: C * Math.sin(h) }; };
|
49 |
+
const oklabToOklch = (L, a, b) => { const C = Math.sqrt(a*a + b*b); let h = Math.atan2(b, a) * 180 / Math.PI; if (h < 0) h += 360; return { L, C, h }; };
|
50 |
+
const clamp01 = (x) => Math.min(1, Math.max(0, x));
|
51 |
+
const isInGamut = ({ r, g, b }) => r >= 0 && r <= 1 && g >= 0 && g <= 1 && b >= 0 && b <= 1;
|
52 |
+
const toHex = ({ r, g, b }) => { const R = Math.round(clamp01(r)*255), G = Math.round(clamp01(g)*255), B = Math.round(clamp01(b)*255); const h = (n) => n.toString(16).padStart(2,'0'); return `#${h(R)}${h(G)}${h(B)}`.toUpperCase(); };
|
53 |
+
const oklchToHexSafe = (L, C, h) => { let c = C; for (let i=0;i<12;i++){ const { a, b } = oklchToOklab(L,c,h); const rgb = oklabToRgb(L,a,b); if (isInGamut(rgb)) return toHex(rgb); c = Math.max(0, c-0.02);} return toHex(oklabToRgb(L,0,0)); };
|
54 |
+
const parseCssColorToRgb = (css) => { try { const el = document.createElement('span'); el.style.color = css; document.body.appendChild(el); const cs = getComputedStyle(el).color; document.body.removeChild(el); const m = cs.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/i); if (!m) return null; return { r: Number(m[1])/255, g: Number(m[2])/255, b: Number(m[3])/255 }; } catch { return null; } };
|
55 |
+
|
56 |
+
const getPrimaryHex = () => {
|
57 |
+
const css = getCssVar('--primary-color');
|
58 |
+
if (!css) return '#E889AB';
|
59 |
+
if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(css)) return css.toUpperCase();
|
60 |
+
const rgb = parseCssColorToRgb(css);
|
61 |
+
if (rgb) return toHex(rgb);
|
62 |
+
return '#E889AB';
|
63 |
+
};
|
64 |
+
const getCounts = () => {
|
65 |
+
const Fallback = 6;
|
66 |
+
const globalCount = clamp(getIntFromCssVar('--palette-count', Fallback), 1, 12);
|
67 |
+
return {
|
68 |
+
categorical: clamp(getIntFromCssVar('--palette-categorical-count', globalCount), 1, 12),
|
69 |
+
sequential: clamp(getIntFromCssVar('--palette-sequential-count', globalCount), 1, 12),
|
70 |
+
diverging: clamp(getIntFromCssVar('--palette-diverging-count', globalCount), 1, 12),
|
71 |
+
};
|
72 |
+
};
|
73 |
+
|
74 |
+
const generators = {
|
75 |
+
categorical: (baseHex, count) => {
|
76 |
+
const parseHex = (h) => { const s = h.replace('#',''); const v = s.length===3 ? s.split('').map(ch=>ch+ch).join('') : s; return { r: parseInt(v.slice(0,2),16)/255, g: parseInt(v.slice(2,4),16)/255, b: parseInt(v.slice(4,6),16)/255 }; };
|
77 |
+
const { r, g, b } = parseHex(baseHex);
|
78 |
+
const { L, a, b: bb } = rgbToOklab(r,g,b);
|
79 |
+
const { C, h } = oklabToOklch(L,a,bb);
|
80 |
+
const L0 = Math.min(0.85, Math.max(0.4, L));
|
81 |
+
const C0 = Math.min(0.35, Math.max(0.1, C || 0.2));
|
82 |
+
const total = Math.max(1, Math.min(12, count || 6));
|
83 |
+
const hueStep = 360 / total;
|
84 |
+
const results = [];
|
85 |
+
for (let i=0;i<total;i++) { const hDeg = (h + i*hueStep) % 360; const lVar = ((i % 3) - 1) * 0.04; results.push(oklchToHexSafe(Math.max(0.4, Math.min(0.85, L0 + lVar)), C0, hDeg)); }
|
86 |
+
return results;
|
87 |
+
},
|
88 |
+
sequential: (baseHex, count) => {
|
89 |
+
const parseHex = (h) => { const s = h.replace('#',''); const v = s.length===3 ? s.split('').map(ch=>ch+ch).join('') : s; return { r: parseInt(v.slice(0,2),16)/255, g: parseInt(v.slice(2,4),16)/255, b: parseInt(v.slice(4,6),16)/255 }; };
|
90 |
+
const { r, g, b } = parseHex(baseHex);
|
91 |
+
const { L, a, b: bb } = rgbToOklab(r,g,b);
|
92 |
+
const { C, h } = oklabToOklch(L,a,bb);
|
93 |
+
const total = Math.max(1, Math.min(12, count || 6));
|
94 |
+
const startL = Math.max(0.25, L - 0.18);
|
95 |
+
const endL = Math.min(0.92, L + 0.18);
|
96 |
+
const cBase = Math.min(0.33, Math.max(0.08, C * 0.9 + 0.06));
|
97 |
+
const out = [];
|
98 |
+
for (let i=0;i<total;i++) { const t = total===1 ? 0 : i/(total-1); const lNow = startL*(1-t)+endL*t; const cNow = cBase*(0.85 + 0.15*(1 - Math.abs(0.5 - t)*2)); out.push(oklchToHexSafe(lNow, cNow, h)); }
|
99 |
+
return out;
|
100 |
+
},
|
101 |
+
diverging: (baseHex, count) => {
|
102 |
+
const parseHex = (h) => { const s = h.replace('#',''); const v = s.length===3 ? s.split('').map(ch=>ch+ch).join('') : s; return { r: parseInt(v.slice(0,2),16)/255, g: parseInt(v.slice(2,4),16)/255, b: parseInt(v.slice(4,6),16)/255 }; };
|
103 |
+
const { r, g, b } = parseHex(baseHex);
|
104 |
+
const baseLab = rgbToOklab(r,g,b);
|
105 |
+
const baseLch = oklabToOklch(baseLab.L, baseLab.a, baseLab.b);
|
106 |
+
const total = Math.max(1, Math.min(12, count || 6));
|
107 |
+
|
108 |
+
// Left endpoint: EXACT primary color (no darkening)
|
109 |
+
const leftLab = baseLab;
|
110 |
+
// Right endpoint: complement with same L and similar C (clamped safe)
|
111 |
+
const compH = (baseLch.h + 180) % 360;
|
112 |
+
const cSafe = Math.min(0.35, Math.max(0.08, baseLch.C));
|
113 |
+
const rightLab = oklchToOklab(baseLab.L, cSafe, compH);
|
114 |
+
const whiteLab = { L: 0.98, a: 0, b: 0 }; // center near‑white
|
115 |
+
|
116 |
+
const hexFromOKLab = (L, a, b) => toHex(oklabToRgb(L, a, b));
|
117 |
+
const lerp = (a, b, t) => a + (b - a) * t;
|
118 |
+
const lerpOKLabHex = (A, B, t) => hexFromOKLab(lerp(A.L, B.L, t), lerp(A.a, B.a, t), lerp(A.b, B.b, t));
|
119 |
+
|
120 |
+
const out = [];
|
121 |
+
if (total % 2 === 1) {
|
122 |
+
const nSide = (total - 1) >> 1; // items on each side
|
123 |
+
// Left side: include left endpoint exactly at index 0
|
124 |
+
for (let i = 0; i < nSide; i++) {
|
125 |
+
const t = nSide <= 1 ? 0 : (i / (nSide - 1)); // 0 .. 1
|
126 |
+
// Move from leftLab to a value close (but not equal) to white; ensure last before center is lighter
|
127 |
+
const tt = t * 0.9; // keep some distance from pure white before center
|
128 |
+
out.push(lerpOKLabHex(leftLab, whiteLab, tt));
|
129 |
+
}
|
130 |
+
// Center
|
131 |
+
out.push(hexFromOKLab(whiteLab.L, whiteLab.a, whiteLab.b));
|
132 |
+
// Right side: start near white and end EXACTLY at rightLab
|
133 |
+
for (let i = 0; i < nSide; i++) {
|
134 |
+
const t = nSide <= 1 ? 1 : ((i + 1) / nSide); // (1/n)..1
|
135 |
+
const tt = Math.max(0.1, t); // avoid starting at pure white
|
136 |
+
out.push(lerpOKLabHex(whiteLab, rightLab, tt));
|
137 |
+
}
|
138 |
+
// Ensure first and last are exact endpoints
|
139 |
+
if (out.length) { out[0] = hexFromOKLab(leftLab.L, leftLab.a, leftLab.b); out[out.length - 1] = hexFromOKLab(rightLab.L, rightLab.a, rightLab.b); }
|
140 |
+
} else {
|
141 |
+
const nSide = total >> 1;
|
142 |
+
// Left half including left endpoint, approaching white but not reaching it
|
143 |
+
for (let i = 0; i < nSide; i++) {
|
144 |
+
const t = nSide <= 1 ? 0 : (i / (nSide - 1)); // 0 .. 1
|
145 |
+
const tt = t * 0.9;
|
146 |
+
out.push(lerpOKLabHex(leftLab, whiteLab, tt));
|
147 |
+
}
|
148 |
+
// Right half: mirror from near white to exact right endpoint
|
149 |
+
for (let i = 0; i < nSide; i++) {
|
150 |
+
const t = nSide <= 1 ? 1 : ((i + 1) / nSide); // (1/n)..1
|
151 |
+
const tt = Math.max(0.1, t);
|
152 |
+
out.push(lerpOKLabHex(whiteLab, rightLab, tt));
|
153 |
+
}
|
154 |
+
if (out.length) { out[0] = hexFromOKLab(leftLab.L, leftLab.a, leftLab.b); out[out.length - 1] = hexFromOKLab(rightLab.L, rightLab.a, rightLab.b); }
|
155 |
+
}
|
156 |
+
return out;
|
157 |
+
}
|
158 |
+
};
|
159 |
+
|
160 |
+
const setCssVar = (name, value) => { try { MODE.cssRoot.style.setProperty(name, value); } catch {} };
|
161 |
+
const removeCssVar = (name) => { try { MODE.cssRoot.style.removeProperty(name); } catch {} };
|
162 |
+
|
163 |
+
let lastSignature = '';
|
164 |
+
let lastCounts = { categorical: 0, sequential: 0, diverging: 0 };
|
165 |
+
|
166 |
+
const updatePalettes = () => {
|
167 |
+
const primary = getPrimaryHex();
|
168 |
+
const counts = getCounts();
|
169 |
+
const signature = `${primary}|${counts.categorical}|${counts.sequential}|${counts.diverging}`;
|
170 |
+
if (signature === lastSignature) return;
|
171 |
+
|
172 |
+
const out = {};
|
173 |
+
out.categorical = generators.categorical(primary, counts.categorical);
|
174 |
+
out.sequential = generators.sequential(primary, counts.sequential);
|
175 |
+
out.diverging = generators.diverging(primary, counts.diverging);
|
176 |
+
|
177 |
+
setCssVar('--primary-hex', primary);
|
178 |
+
setCssVar('--palette-categorical-count-current', String(out.categorical.length));
|
179 |
+
setCssVar('--palette-sequential-count-current', String(out.sequential.length));
|
180 |
+
setCssVar('--palette-diverging-count-current', String(out.diverging.length));
|
181 |
+
|
182 |
+
const applyList = (key, list, prevCount) => {
|
183 |
+
for (let i=0;i<list.length;i++) setCssVar(`--palette-${key}-${i+1}`, list[i]);
|
184 |
+
for (let i=list.length;i<prevCount;i++) removeCssVar(`--palette-${key}-${i+1}`);
|
185 |
+
setCssVar(`--palette-${key}-json`, JSON.stringify(list));
|
186 |
+
};
|
187 |
+
applyList('categorical', out.categorical, lastCounts.categorical);
|
188 |
+
applyList('sequential', out.sequential, lastCounts.sequential);
|
189 |
+
applyList('diverging', out.diverging, lastCounts.diverging);
|
190 |
+
|
191 |
+
lastCounts = { categorical: out.categorical.length, sequential: out.sequential.length, diverging: out.diverging.length };
|
192 |
+
lastSignature = signature;
|
193 |
+
|
194 |
+
try { document.dispatchEvent(new CustomEvent('palettes:updated', { detail: { primary, counts, palettes: out } })); } catch {}
|
195 |
+
};
|
196 |
+
|
197 |
+
const bootstrap = () => {
|
198 |
+
updatePalettes();
|
199 |
+
const mo = new MutationObserver(() => updatePalettes());
|
200 |
+
mo.observe(MODE.cssRoot, { attributes: true, attributeFilter: ['style', 'data-theme'] });
|
201 |
+
setInterval(updatePalettes, 400);
|
202 |
+
window.ColorPalettes = {
|
203 |
+
refresh: updatePalettes,
|
204 |
+
getColors: (key) => {
|
205 |
+
const count = Number(getCssVar(`--palette-${key}-count-current`)) || 0;
|
206 |
+
const arr = [];
|
207 |
+
for (let i=0;i<count;i++) arr.push(getCssVar(`--palette-${key}-${i+1}`));
|
208 |
+
return arr.filter(Boolean);
|
209 |
+
}
|
210 |
+
};
|
211 |
+
};
|
212 |
+
|
213 |
+
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', bootstrap, { once: true });
|
214 |
+
else bootstrap();
|
215 |
+
})();
|
216 |
+
|
217 |
+
|
app/scripts/export-pdf.mjs
CHANGED
@@ -168,8 +168,19 @@ async function main() {
|
|
168 |
// filename can be provided, else computed from DOM (button) or page title later
|
169 |
let outFileBase = (args.filename && String(args.filename).replace(/\.pdf$/i, '')) || 'article';
|
170 |
|
171 |
-
|
172 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
173 |
|
174 |
console.log('> Starting Astro preview…');
|
175 |
// Start preview in its own process group so we can terminate all children reliably
|
@@ -263,6 +274,8 @@ async function main() {
|
|
263 |
}
|
264 |
function fixSvg(svg){
|
265 |
if (!svg) return;
|
|
|
|
|
266 |
if (isSmallSvg(svg)) { lockSmallSvgSize(svg); return; }
|
267 |
try { svg.removeAttribute('width'); } catch {}
|
268 |
try { svg.removeAttribute('height'); } catch {}
|
@@ -309,7 +322,6 @@ async function main() {
|
|
309 |
// - Ensure an SVG background (CSS background on svg element)
|
310 |
const cssHandle = await page.addStyleTag({ content: `
|
311 |
.hero .points { mix-blend-mode: normal !important; }
|
312 |
-
.d3-galaxy svg { background: var(--surface-bg); }
|
313 |
` });
|
314 |
const thumbPath = resolve(cwd, 'dist', 'thumb.jpg');
|
315 |
await page.screenshot({ path: thumbPath, type: 'jpeg', quality: 85, fullPage: false });
|
@@ -360,6 +372,8 @@ async function main() {
|
|
360 |
}
|
361 |
function fixSvg(svg){
|
362 |
if (!svg) return;
|
|
|
|
|
363 |
if (isSmallSvg(svg)) { lockSmallSvgSize(svg); return; }
|
364 |
try { svg.removeAttribute('width'); } catch {}
|
365 |
try { svg.removeAttribute('height'); } catch {}
|
@@ -412,8 +426,9 @@ async function main() {
|
|
412 |
|
413 |
/* Banner centering & visibility */
|
414 |
.hero .points { mix-blend-mode: normal !important; }
|
415 |
-
|
416 |
-
.d3-galaxy
|
|
|
417 |
` });
|
418 |
} catch {}
|
419 |
await page.pdf({
|
|
|
168 |
// filename can be provided, else computed from DOM (button) or page title later
|
169 |
let outFileBase = (args.filename && String(args.filename).replace(/\.pdf$/i, '')) || 'article';
|
170 |
|
171 |
+
// Build only if dist/ does not exist
|
172 |
+
const distDir = resolve(cwd, 'dist');
|
173 |
+
let hasDist = false;
|
174 |
+
try {
|
175 |
+
const st = await fs.stat(distDir);
|
176 |
+
hasDist = st && st.isDirectory();
|
177 |
+
} catch {}
|
178 |
+
if (!hasDist) {
|
179 |
+
console.log('> Building Astro site…');
|
180 |
+
await run('npm', ['run', 'build']);
|
181 |
+
} else {
|
182 |
+
console.log('> Skipping build (dist/ exists)…');
|
183 |
+
}
|
184 |
|
185 |
console.log('> Starting Astro preview…');
|
186 |
// Start preview in its own process group so we can terminate all children reliably
|
|
|
274 |
}
|
275 |
function fixSvg(svg){
|
276 |
if (!svg) return;
|
277 |
+
// Do not alter banner galaxy SVG sizing; it relies on explicit width/height
|
278 |
+
try { if (svg.closest && svg.closest('.d3-galaxy')) return; } catch {}
|
279 |
if (isSmallSvg(svg)) { lockSmallSvgSize(svg); return; }
|
280 |
try { svg.removeAttribute('width'); } catch {}
|
281 |
try { svg.removeAttribute('height'); } catch {}
|
|
|
322 |
// - Ensure an SVG background (CSS background on svg element)
|
323 |
const cssHandle = await page.addStyleTag({ content: `
|
324 |
.hero .points { mix-blend-mode: normal !important; }
|
|
|
325 |
` });
|
326 |
const thumbPath = resolve(cwd, 'dist', 'thumb.jpg');
|
327 |
await page.screenshot({ path: thumbPath, type: 'jpeg', quality: 85, fullPage: false });
|
|
|
372 |
}
|
373 |
function fixSvg(svg){
|
374 |
if (!svg) return;
|
375 |
+
// Do not alter banner galaxy SVG sizing; it relies on explicit width/height
|
376 |
+
try { if (svg.closest && svg.closest('.d3-galaxy')) return; } catch {}
|
377 |
if (isSmallSvg(svg)) { lockSmallSvgSize(svg); return; }
|
378 |
try { svg.removeAttribute('width'); } catch {}
|
379 |
try { svg.removeAttribute('height'); } catch {}
|
|
|
426 |
|
427 |
/* Banner centering & visibility */
|
428 |
.hero .points { mix-blend-mode: normal !important; }
|
429 |
+
/* Do NOT force a fixed height to avoid clipping in PDF */
|
430 |
+
.d3-galaxy { width: 100% !important; max-width: 980px !important; margin-left: auto !important; margin-right: auto !important; }
|
431 |
+
.d3-galaxy svg { width: 100% !important; height: auto !important; }
|
432 |
` });
|
433 |
} catch {}
|
434 |
await page.pdf({
|
app/src/components/Accordion.astro
CHANGED
@@ -89,23 +89,18 @@ const wrapperClass = ["accordion", className].filter(Boolean).join(" ");
|
|
89 |
}
|
90 |
|
91 |
.accordion__summary {
|
|
|
92 |
list-style: none;
|
93 |
display: flex;
|
94 |
align-items: center;
|
95 |
-
justify-content:
|
96 |
gap: 4px;
|
97 |
-
padding:
|
98 |
cursor: pointer;
|
99 |
color: var(--text-color);
|
100 |
user-select: none;
|
101 |
-
position: relative;
|
102 |
}
|
103 |
|
104 |
-
.accordion[size="big"] .accordion__summary {
|
105 |
-
padding: 16px;
|
106 |
-
}
|
107 |
-
|
108 |
-
|
109 |
/* Remove conditional padding to avoid jump on close */
|
110 |
|
111 |
/* Remove native marker */
|
@@ -116,13 +111,16 @@ const wrapperClass = ["accordion", className].filter(Boolean).join(" ");
|
|
116 |
content: "";
|
117 |
}
|
118 |
|
|
|
|
|
|
|
|
|
119 |
.accordion__title {
|
120 |
font-weight: 600;
|
121 |
}
|
122 |
|
123 |
.accordion__chevron {
|
124 |
-
|
125 |
-
right: 8px;
|
126 |
transition: transform 220ms ease;
|
127 |
opacity: .85;
|
128 |
}
|
@@ -165,7 +163,6 @@ const wrapperClass = ["accordion", className].filter(Boolean).join(" ");
|
|
165 |
padding: 0;
|
166 |
}
|
167 |
|
168 |
-
|
169 |
/* Separator between header and content when open (edge-to-edge) */
|
170 |
.accordion[open] .accordion__content-wrapper::before {
|
171 |
content: "";
|
@@ -184,7 +181,6 @@ const wrapperClass = ["accordion", className].filter(Boolean).join(" ");
|
|
184 |
outline-offset: 3px;
|
185 |
border-radius: 8px;
|
186 |
}
|
187 |
-
|
188 |
|
189 |
|
190 |
</style>
|
|
|
89 |
}
|
90 |
|
91 |
.accordion__summary {
|
92 |
+
margin: 0;
|
93 |
list-style: none;
|
94 |
display: flex;
|
95 |
align-items: center;
|
96 |
+
justify-content: space-between;
|
97 |
gap: 4px;
|
98 |
+
padding: var(--spacing-2) var(--spacing-3);
|
99 |
cursor: pointer;
|
100 |
color: var(--text-color);
|
101 |
user-select: none;
|
|
|
102 |
}
|
103 |
|
|
|
|
|
|
|
|
|
|
|
104 |
/* Remove conditional padding to avoid jump on close */
|
105 |
|
106 |
/* Remove native marker */
|
|
|
111 |
content: "";
|
112 |
}
|
113 |
|
114 |
+
.accordion[size="big"] .accordion__summary {
|
115 |
+
padding: 16px;
|
116 |
+
}
|
117 |
+
|
118 |
.accordion__title {
|
119 |
font-weight: 600;
|
120 |
}
|
121 |
|
122 |
.accordion__chevron {
|
123 |
+
flex: 0 0 auto;
|
|
|
124 |
transition: transform 220ms ease;
|
125 |
opacity: .85;
|
126 |
}
|
|
|
163 |
padding: 0;
|
164 |
}
|
165 |
|
|
|
166 |
/* Separator between header and content when open (edge-to-edge) */
|
167 |
.accordion[open] .accordion__content-wrapper::before {
|
168 |
content: "";
|
|
|
181 |
outline-offset: 3px;
|
182 |
border-radius: 8px;
|
183 |
}
|
|
|
184 |
|
185 |
|
186 |
</style>
|
app/src/components/Footer.astro
CHANGED
@@ -215,24 +215,6 @@ const { citationText, bibtex, licence, doi } = Astro.props as Props;
|
|
215 |
}
|
216 |
}
|
217 |
|
218 |
-
/* Avoid duplicate numbering: hide native list markers for CSL references only */
|
219 |
-
.references-block #references ol,
|
220 |
-
.references-block .references ol,
|
221 |
-
.references-block .bibliography ol {
|
222 |
-
list-style: none;
|
223 |
-
padding-left: 0;
|
224 |
-
margin-left: 0;
|
225 |
-
}
|
226 |
-
|
227 |
-
@media (min-width: 768px) {
|
228 |
-
.references-block #references ol,
|
229 |
-
.references-block .references ol,
|
230 |
-
.references-block .bibliography ol {
|
231 |
-
padding-left: 0;
|
232 |
-
margin-left: 0;
|
233 |
-
}
|
234 |
-
}
|
235 |
-
|
236 |
.references-block li {
|
237 |
margin-bottom: 1em;
|
238 |
}
|
|
|
215 |
}
|
216 |
}
|
217 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
218 |
.references-block li {
|
219 |
margin-bottom: 1em;
|
220 |
}
|
app/src/components/RawHtml.astro
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
---
|
2 |
+
const { html, class: className, ariaLabel } = Astro.props;
|
3 |
+
---
|
4 |
+
<div class={className} role="img" aria-label={ariaLabel} set:html={html} />
|
5 |
+
|
6 |
+
|
app/src/components/TableOfContents.astro
CHANGED
@@ -24,22 +24,8 @@ const { tableOfContentAutoCollapse = false } = Astro.props as Props;
|
|
24 |
const headings = articleRoot.querySelectorAll('h2, h3, h4');
|
25 |
if (!headings.length) return;
|
26 |
|
27 |
-
//
|
28 |
-
const
|
29 |
-
.toLowerCase()
|
30 |
-
.replace(/[^a-z0-9]+/g, ' ')
|
31 |
-
.trim();
|
32 |
-
const isTocLabel = (s) => /^(table\s+of\s+contents?)$|^toc$/i.test(String(s || '').replace(/[^a-zA-Z0-9]+/g, ' ').trim());
|
33 |
-
const shouldSkip = (h) => {
|
34 |
-
const t = h.textContent || '';
|
35 |
-
const id = String(h.id || '');
|
36 |
-
const slug = normalize(t).replace(/\s+/g, '_');
|
37 |
-
if (isTocLabel(t)) return true;
|
38 |
-
if (isTocLabel(id.replace(/[_-]+/g, ' '))) return true;
|
39 |
-
if (isTocLabel(slug.replace(/[_-]+/g, ' '))) return true;
|
40 |
-
return false;
|
41 |
-
};
|
42 |
-
const headingsArr = Array.from(headings).filter(h => !shouldSkip(h));
|
43 |
if (!headingsArr.length) return;
|
44 |
|
45 |
// Ensure unique ids for headings (deduplicate duplicates)
|
@@ -265,6 +251,7 @@ const { tableOfContentAutoCollapse = false } = Astro.props as Props;
|
|
265 |
.table-of-contents {
|
266 |
position: sticky;
|
267 |
top: 32px;
|
|
|
268 |
}
|
269 |
|
270 |
.table-of-contents nav {
|
|
|
24 |
const headings = articleRoot.querySelectorAll('h2, h3, h4');
|
25 |
if (!headings.length) return;
|
26 |
|
27 |
+
// Inclure tous les titres H2/H3/H4 sans filtrer "Table of contents"
|
28 |
+
const headingsArr = Array.from(headings);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
29 |
if (!headingsArr.length) return;
|
30 |
|
31 |
// Ensure unique ids for headings (deduplicate duplicates)
|
|
|
251 |
.table-of-contents {
|
252 |
position: sticky;
|
253 |
top: 32px;
|
254 |
+
margin-top: 12px;
|
255 |
}
|
256 |
|
257 |
.table-of-contents nav {
|
app/src/content/assets/images/thumb.png
ADDED
![]() |
Git LFS Details
|
app/src/content/embeds/against-baselines-deduplicated.html
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
-
<div class="d3-line" style="width:100%;margin:10px 0;"></div>
|
2 |
<style>
|
3 |
-
.d3-
|
4 |
font-size: 12px;
|
5 |
padding: 8px 28px 8px 10px;
|
6 |
border: 1px solid var(--border-color);
|
@@ -17,21 +17,21 @@
|
|
17 |
cursor: pointer;
|
18 |
transition: border-color .15s ease, box-shadow .15s ease;
|
19 |
}
|
20 |
-
[data-theme="dark"] .d3-
|
21 |
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
|
22 |
}
|
23 |
-
.d3-
|
24 |
border-color: var(--primary-color);
|
25 |
}
|
26 |
-
.d3-
|
27 |
border-color: var(--primary-color);
|
28 |
box-shadow: 0 0 0 3px rgba(232,137,171,.25);
|
29 |
outline: none;
|
30 |
}
|
31 |
-
.d3-
|
32 |
|
33 |
/* Range slider themed with --primary-color */
|
34 |
-
.d3-
|
35 |
-webkit-appearance: none;
|
36 |
appearance: none;
|
37 |
width: 100%;
|
@@ -40,12 +40,12 @@
|
|
40 |
background: var(--border-color);
|
41 |
outline: none;
|
42 |
}
|
43 |
-
.d3-
|
44 |
height: 6px;
|
45 |
background: transparent;
|
46 |
border-radius: 999px;
|
47 |
}
|
48 |
-
.d3-
|
49 |
-webkit-appearance: none;
|
50 |
appearance: none;
|
51 |
width: 16px;
|
@@ -56,12 +56,12 @@
|
|
56 |
margin-top: -5px;
|
57 |
cursor: pointer;
|
58 |
}
|
59 |
-
.d3-
|
60 |
height: 6px;
|
61 |
background: transparent;
|
62 |
border-radius: 999px;
|
63 |
}
|
64 |
-
.d3-
|
65 |
width: 16px;
|
66 |
height: 16px;
|
67 |
border-radius: 50%;
|
@@ -70,7 +70,7 @@
|
|
70 |
cursor: pointer;
|
71 |
}
|
72 |
/* Improved line color via CSS */
|
73 |
-
.d3-
|
74 |
</style>
|
75 |
<script>
|
76 |
(() => {
|
@@ -173,24 +173,25 @@
|
|
173 |
const defs = svg.append('defs');
|
174 |
// Clip-path to constrain drawing to chart area
|
175 |
const clipId = `clip-${Math.random().toString(36).slice(2)}`;
|
176 |
-
const clipPath = defs.append('clipPath').attr('id', clipId)
|
|
|
177 |
const clipRect = clipPath.append('rect').attr('x', 0).attr('y', 0).attr('width', 0).attr('height', 0);
|
178 |
|
179 |
// Academic marker shapes
|
180 |
const markerShapes = ['circle', 'square', 'triangle', 'diamond', 'inverted-triangle'];
|
181 |
-
const markerSize =
|
182 |
|
183 |
// Groups
|
184 |
const gRoot = svg.append('g');
|
185 |
const gGrid = gRoot.append('g').attr('class', 'grid');
|
186 |
const gAxes = gRoot.append('g').attr('class', 'axes');
|
187 |
-
|
188 |
-
const
|
189 |
-
const
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
const gHover = gRoot.append('g').attr('class', 'hover');
|
195 |
const gLegend = gRoot.append('foreignObject').attr('class', 'legend');
|
196 |
|
@@ -253,8 +254,8 @@
|
|
253 |
// Scales and layout
|
254 |
let width = 800, height = 360;
|
255 |
let margin = { top: 16, right: 28, bottom: 56, left: 64 };
|
256 |
-
let xScale = d3.scaleLinear();
|
257 |
-
let yScale = d3.scaleLinear();
|
258 |
|
259 |
// Line generators - simple linear connections
|
260 |
const lineGen = d3.line()
|
@@ -318,8 +319,23 @@
|
|
318 |
const innerHeight = height - margin.top - margin.bottom;
|
319 |
gRoot.attr('transform', `translate(${margin.left},${margin.top})`);
|
320 |
|
321 |
-
//
|
322 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
323 |
|
324 |
xScale.range([0, innerWidth]);
|
325 |
yScale.range([innerHeight, 0]);
|
@@ -442,8 +458,13 @@
|
|
442 |
if (!isFinite(minStep) || !isFinite(maxStep)) { return; }
|
443 |
xScale.domain([minStep, maxStep]);
|
444 |
if (isRank) {
|
445 |
-
|
446 |
-
|
|
|
|
|
|
|
|
|
|
|
447 |
} else {
|
448 |
yScale.domain([minVal, maxVal]).nice();
|
449 |
}
|
@@ -483,15 +504,15 @@
|
|
483 |
// Draw lines
|
484 |
const paths = gLines.selectAll('path.run-line').data(series, d=>d.run);
|
485 |
paths.enter().append('path').attr('class','run-line').attr('fill','none').attr('stroke-width',2)
|
486 |
-
.attr('stroke',
|
|
|
487 |
.attr('d', d=>lineGen(d.values))
|
488 |
.merge(paths)
|
489 |
-
.transition().duration(200)
|
490 |
.attr('stroke', d=>d.color)
|
491 |
.attr('d', d=>lineGen(d.values));
|
492 |
paths.exit().remove();
|
493 |
|
494 |
-
// Draw markers for each data point
|
495 |
gPoints.selectAll('*').remove();
|
496 |
series.forEach((s, seriesIndex) => {
|
497 |
const pointGroup = gPoints.selectAll(`.points-${seriesIndex}`)
|
@@ -499,11 +520,10 @@
|
|
499 |
.join('g')
|
500 |
.attr('class', `points-${seriesIndex}`)
|
501 |
.attr('transform', d => `translate(${xScale(d.step)},${yScale(d.value)})`);
|
502 |
-
|
503 |
drawMarker(pointGroup, s.marker, markerSize)
|
504 |
.attr('fill', s.color)
|
505 |
.attr('stroke', s.color)
|
506 |
-
.attr('stroke-width', 1.
|
507 |
.style('cursor', 'crosshair');
|
508 |
});
|
509 |
|
|
|
1 |
+
<div class="d3-line d3-embed--against-baselines-dd" style="width:100%;margin:10px 0;"></div>
|
2 |
<style>
|
3 |
+
.d3-embed--against-baselines-dd .d3-line__controls select {
|
4 |
font-size: 12px;
|
5 |
padding: 8px 28px 8px 10px;
|
6 |
border: 1px solid var(--border-color);
|
|
|
17 |
cursor: pointer;
|
18 |
transition: border-color .15s ease, box-shadow .15s ease;
|
19 |
}
|
20 |
+
[data-theme="dark"] .d3-embed--against-baselines-dd .d3-line__controls select {
|
21 |
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
|
22 |
}
|
23 |
+
.d3-embed--against-baselines-dd .d3-line__controls select:hover {
|
24 |
border-color: var(--primary-color);
|
25 |
}
|
26 |
+
.d3-embed--against-baselines-dd .d3-line__controls select:focus {
|
27 |
border-color: var(--primary-color);
|
28 |
box-shadow: 0 0 0 3px rgba(232,137,171,.25);
|
29 |
outline: none;
|
30 |
}
|
31 |
+
.d3-embed--against-baselines-dd .d3-line__controls label { gap: 8px; }
|
32 |
|
33 |
/* Range slider themed with --primary-color */
|
34 |
+
.d3-embed--against-baselines-dd .d3-line__controls input[type="range"] {
|
35 |
-webkit-appearance: none;
|
36 |
appearance: none;
|
37 |
width: 100%;
|
|
|
40 |
background: var(--border-color);
|
41 |
outline: none;
|
42 |
}
|
43 |
+
.d3-embed--against-baselines-dd .d3-line__controls input[type="range"]::-webkit-slider-runnable-track {
|
44 |
height: 6px;
|
45 |
background: transparent;
|
46 |
border-radius: 999px;
|
47 |
}
|
48 |
+
.d3-embed--against-baselines-dd .d3-line__controls input[type="range"]::-webkit-slider-thumb {
|
49 |
-webkit-appearance: none;
|
50 |
appearance: none;
|
51 |
width: 16px;
|
|
|
56 |
margin-top: -5px;
|
57 |
cursor: pointer;
|
58 |
}
|
59 |
+
.d3-embed--against-baselines-dd .d3-line__controls input[type="range"]::-moz-range-track {
|
60 |
height: 6px;
|
61 |
background: transparent;
|
62 |
border-radius: 999px;
|
63 |
}
|
64 |
+
.d3-embed--against-baselines-dd .d3-line__controls input[type="range"]::-moz-range-thumb {
|
65 |
width: 16px;
|
66 |
height: 16px;
|
67 |
border-radius: 50%;
|
|
|
70 |
cursor: pointer;
|
71 |
}
|
72 |
/* Improved line color via CSS */
|
73 |
+
.d3-embed--against-baselines-dd .lines path.improved { stroke: var(--primary-color); }
|
74 |
</style>
|
75 |
<script>
|
76 |
(() => {
|
|
|
173 |
const defs = svg.append('defs');
|
174 |
// Clip-path to constrain drawing to chart area
|
175 |
const clipId = `clip-${Math.random().toString(36).slice(2)}`;
|
176 |
+
const clipPath = defs.append('clipPath').attr('id', clipId)
|
177 |
+
.attr('clipPathUnits', 'userSpaceOnUse');
|
178 |
const clipRect = clipPath.append('rect').attr('x', 0).attr('y', 0).attr('width', 0).attr('height', 0);
|
179 |
|
180 |
// Academic marker shapes
|
181 |
const markerShapes = ['circle', 'square', 'triangle', 'diamond', 'inverted-triangle'];
|
182 |
+
const markerSize = 5;
|
183 |
|
184 |
// Groups
|
185 |
const gRoot = svg.append('g');
|
186 |
const gGrid = gRoot.append('g').attr('class', 'grid');
|
187 |
const gAxes = gRoot.append('g').attr('class', 'axes');
|
188 |
+
// Dedicated plotting layer that will be clipped
|
189 |
+
const gPlot = gRoot.append('g').attr('class', 'plot');
|
190 |
+
const gAreas = gPlot.append('g').attr('class', 'areas');
|
191 |
+
const gLines = gPlot.append('g').attr('class', 'lines');
|
192 |
+
const gPoints = gPlot.append('g').attr('class', 'points');
|
193 |
+
// Apply clipping to the whole plotting group
|
194 |
+
gPlot.attr('clip-path', `url(#${clipId})`);
|
195 |
const gHover = gRoot.append('g').attr('class', 'hover');
|
196 |
const gLegend = gRoot.append('foreignObject').attr('class', 'legend');
|
197 |
|
|
|
254 |
// Scales and layout
|
255 |
let width = 800, height = 360;
|
256 |
let margin = { top: 16, right: 28, bottom: 56, left: 64 };
|
257 |
+
let xScale = d3.scaleLinear().clamp(true);
|
258 |
+
let yScale = d3.scaleLinear().clamp(true);
|
259 |
|
260 |
// Line generators - simple linear connections
|
261 |
const lineGen = d3.line()
|
|
|
319 |
const innerHeight = height - margin.top - margin.bottom;
|
320 |
gRoot.attr('transform', `translate(${margin.left},${margin.top})`);
|
321 |
|
322 |
+
// Responsive legend layout: wrap to two lines on narrower screens
|
323 |
+
try {
|
324 |
+
const shouldWrapLegend = width < 1300;
|
325 |
+
if (legendInline && legendInline.style) {
|
326 |
+
legendInline.style.flexWrap = shouldWrapLegend ? 'wrap' : 'nowrap';
|
327 |
+
legendInline.style.rowGap = shouldWrapLegend ? '4px' : '';
|
328 |
+
legendInline.style.maxWidth = shouldWrapLegend ? Math.max(280, Math.round(width * 0.62)) + 'px' : 'unset';
|
329 |
+
legendInline.style.alignItems = shouldWrapLegend ? 'flex-start' : 'center';
|
330 |
+
}
|
331 |
+
} catch {}
|
332 |
+
|
333 |
+
// Update clip rect to match inner plotting area (coords local to gRoot)
|
334 |
+
clipRect
|
335 |
+
.attr('x', 0)
|
336 |
+
.attr('y', 0)
|
337 |
+
.attr('width', innerWidth)
|
338 |
+
.attr('height', innerHeight);
|
339 |
|
340 |
xScale.range([0, innerWidth]);
|
341 |
yScale.range([innerHeight, 0]);
|
|
|
458 |
if (!isFinite(minStep) || !isFinite(maxStep)) { return; }
|
459 |
xScale.domain([minStep, maxStep]);
|
460 |
if (isRank) {
|
461 |
+
// Strict rank → arrondi au supérieur entier observé
|
462 |
+
// Average rank → forcer un plancher visuel à 4 pour une petite marge en bas
|
463 |
+
const bottom = isRankStrict
|
464 |
+
? Math.max(1, Math.round(maxVal))
|
465 |
+
: Math.max(4, Math.ceil(maxVal + 0.001));
|
466 |
+
rankTickMax = bottom;
|
467 |
+
yScale.domain([rankTickMax, 1]).nice();
|
468 |
} else {
|
469 |
yScale.domain([minVal, maxVal]).nice();
|
470 |
}
|
|
|
504 |
// Draw lines
|
505 |
const paths = gLines.selectAll('path.run-line').data(series, d=>d.run);
|
506 |
paths.enter().append('path').attr('class','run-line').attr('fill','none').attr('stroke-width',2)
|
507 |
+
.attr('stroke-linejoin','round').attr('stroke-linecap','round')
|
508 |
+
.attr('stroke', d=>d.color).attr('opacity',0.95)
|
509 |
.attr('d', d=>lineGen(d.values))
|
510 |
.merge(paths)
|
|
|
511 |
.attr('stroke', d=>d.color)
|
512 |
.attr('d', d=>lineGen(d.values));
|
513 |
paths.exit().remove();
|
514 |
|
515 |
+
// Draw markers for each data point (clipped with gPlot)
|
516 |
gPoints.selectAll('*').remove();
|
517 |
series.forEach((s, seriesIndex) => {
|
518 |
const pointGroup = gPoints.selectAll(`.points-${seriesIndex}`)
|
|
|
520 |
.join('g')
|
521 |
.attr('class', `points-${seriesIndex}`)
|
522 |
.attr('transform', d => `translate(${xScale(d.step)},${yScale(d.value)})`);
|
|
|
523 |
drawMarker(pointGroup, s.marker, markerSize)
|
524 |
.attr('fill', s.color)
|
525 |
.attr('stroke', s.color)
|
526 |
+
.attr('stroke-width', 1.2)
|
527 |
.style('cursor', 'crosshair');
|
528 |
});
|
529 |
|
app/src/content/embeds/against-baselines.html
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
-
<div class="d3-line" style="width:100%;margin:10px 0;"></div>
|
2 |
<style>
|
3 |
-
.d3-
|
4 |
font-size: 12px;
|
5 |
padding: 8px 28px 8px 10px;
|
6 |
border: 1px solid var(--border-color);
|
@@ -17,21 +17,21 @@
|
|
17 |
cursor: pointer;
|
18 |
transition: border-color .15s ease, box-shadow .15s ease;
|
19 |
}
|
20 |
-
[data-theme="dark"] .d3-
|
21 |
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
|
22 |
}
|
23 |
-
.d3-
|
24 |
border-color: var(--primary-color);
|
25 |
}
|
26 |
-
.d3-
|
27 |
border-color: var(--primary-color);
|
28 |
box-shadow: 0 0 0 3px rgba(232,137,171,.25);
|
29 |
outline: none;
|
30 |
}
|
31 |
-
.d3-
|
32 |
|
33 |
/* Range slider themed with --primary-color */
|
34 |
-
.d3-
|
35 |
-webkit-appearance: none;
|
36 |
appearance: none;
|
37 |
width: 100%;
|
@@ -40,12 +40,12 @@
|
|
40 |
background: var(--border-color);
|
41 |
outline: none;
|
42 |
}
|
43 |
-
.d3-
|
44 |
height: 6px;
|
45 |
background: transparent;
|
46 |
border-radius: 999px;
|
47 |
}
|
48 |
-
.d3-
|
49 |
-webkit-appearance: none;
|
50 |
appearance: none;
|
51 |
width: 16px;
|
@@ -56,12 +56,12 @@
|
|
56 |
margin-top: -5px;
|
57 |
cursor: pointer;
|
58 |
}
|
59 |
-
.d3-
|
60 |
height: 6px;
|
61 |
background: transparent;
|
62 |
border-radius: 999px;
|
63 |
}
|
64 |
-
.d3-
|
65 |
width: 16px;
|
66 |
height: 16px;
|
67 |
border-radius: 50%;
|
@@ -70,7 +70,7 @@
|
|
70 |
cursor: pointer;
|
71 |
}
|
72 |
/* Improved line color via CSS */
|
73 |
-
.d3-
|
74 |
</style>
|
75 |
<script>
|
76 |
(() => {
|
@@ -166,12 +166,16 @@
|
|
166 |
.attr('width', '100%')
|
167 |
.style('display', 'block');
|
168 |
|
169 |
-
// Add marker definitions for
|
170 |
const defs = svg.append('defs');
|
|
|
|
|
|
|
|
|
171 |
|
172 |
// Academic marker shapes
|
173 |
const markerShapes = ['circle', 'square', 'triangle', 'diamond', 'inverted-triangle'];
|
174 |
-
const markerSize =
|
175 |
|
176 |
// Groups
|
177 |
const gRoot = svg.append('g');
|
@@ -181,6 +185,10 @@
|
|
181 |
const gLines = gRoot.append('g').attr('class', 'lines');
|
182 |
const gErrors = gRoot.append('g').attr('class', 'errors');
|
183 |
const gPoints = gRoot.append('g').attr('class', 'points');
|
|
|
|
|
|
|
|
|
184 |
const gHover = gRoot.append('g').attr('class', 'hover');
|
185 |
const gLegend = gRoot.append('foreignObject').attr('class', 'legend');
|
186 |
|
@@ -312,6 +320,13 @@
|
|
312 |
xScale.range([0, innerWidth]);
|
313 |
yScale.range([innerHeight, 0]);
|
314 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
315 |
// Compute Y ticks
|
316 |
let yTicks = [];
|
317 |
if (isRankStrictFlag) {
|
|
|
1 |
+
<div class="d3-line d3-embed--against-baselines" style="width:100%;margin:10px 0;"></div>
|
2 |
<style>
|
3 |
+
.d3-embed--against-baselines .d3-line__controls select {
|
4 |
font-size: 12px;
|
5 |
padding: 8px 28px 8px 10px;
|
6 |
border: 1px solid var(--border-color);
|
|
|
17 |
cursor: pointer;
|
18 |
transition: border-color .15s ease, box-shadow .15s ease;
|
19 |
}
|
20 |
+
[data-theme="dark"] .d3-embed--against-baselines .d3-line__controls select {
|
21 |
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
|
22 |
}
|
23 |
+
.d3-embed--against-baselines .d3-line__controls select:hover {
|
24 |
border-color: var(--primary-color);
|
25 |
}
|
26 |
+
.d3-embed--against-baselines .d3-line__controls select:focus {
|
27 |
border-color: var(--primary-color);
|
28 |
box-shadow: 0 0 0 3px rgba(232,137,171,.25);
|
29 |
outline: none;
|
30 |
}
|
31 |
+
.d3-embed--against-baselines .d3-line__controls label { gap: 8px; }
|
32 |
|
33 |
/* Range slider themed with --primary-color */
|
34 |
+
.d3-embed--against-baselines .d3-line__controls input[type="range"] {
|
35 |
-webkit-appearance: none;
|
36 |
appearance: none;
|
37 |
width: 100%;
|
|
|
40 |
background: var(--border-color);
|
41 |
outline: none;
|
42 |
}
|
43 |
+
.d3-embed--against-baselines .d3-line__controls input[type="range"]::-webkit-slider-runnable-track {
|
44 |
height: 6px;
|
45 |
background: transparent;
|
46 |
border-radius: 999px;
|
47 |
}
|
48 |
+
.d3-embed--against-baselines .d3-line__controls input[type="range"]::-webkit-slider-thumb {
|
49 |
-webkit-appearance: none;
|
50 |
appearance: none;
|
51 |
width: 16px;
|
|
|
56 |
margin-top: -5px;
|
57 |
cursor: pointer;
|
58 |
}
|
59 |
+
.d3-embed--against-baselines .d3-line__controls input[type="range"]::-moz-range-track {
|
60 |
height: 6px;
|
61 |
background: transparent;
|
62 |
border-radius: 999px;
|
63 |
}
|
64 |
+
.d3-embed--against-baselines .d3-line__controls input[type="range"]::-moz-range-thumb {
|
65 |
width: 16px;
|
66 |
height: 16px;
|
67 |
border-radius: 50%;
|
|
|
70 |
cursor: pointer;
|
71 |
}
|
72 |
/* Improved line color via CSS */
|
73 |
+
.d3-embed--against-baselines .lines path.improved { stroke: var(--primary-color); }
|
74 |
</style>
|
75 |
<script>
|
76 |
(() => {
|
|
|
166 |
.attr('width', '100%')
|
167 |
.style('display', 'block');
|
168 |
|
169 |
+
// Add marker definitions and clipPath for chart area
|
170 |
const defs = svg.append('defs');
|
171 |
+
const clipId = `clip-${Math.random().toString(36).slice(2)}`;
|
172 |
+
const clipPath = defs.append('clipPath').attr('id', clipId)
|
173 |
+
.attr('clipPathUnits', 'userSpaceOnUse');
|
174 |
+
const clipRect = clipPath.append('rect').attr('x', 0).attr('y', 0).attr('width', 0).attr('height', 0);
|
175 |
|
176 |
// Academic marker shapes
|
177 |
const markerShapes = ['circle', 'square', 'triangle', 'diamond', 'inverted-triangle'];
|
178 |
+
const markerSize = 5;
|
179 |
|
180 |
// Groups
|
181 |
const gRoot = svg.append('g');
|
|
|
185 |
const gLines = gRoot.append('g').attr('class', 'lines');
|
186 |
const gErrors = gRoot.append('g').attr('class', 'errors');
|
187 |
const gPoints = gRoot.append('g').attr('class', 'points');
|
188 |
+
// Constrain plotted layers to inner chart area
|
189 |
+
gAreas.attr('clip-path', `url(#${clipId})`);
|
190 |
+
gLines.attr('clip-path', `url(#${clipId})`);
|
191 |
+
gPoints.attr('clip-path', `url(#${clipId})`);
|
192 |
const gHover = gRoot.append('g').attr('class', 'hover');
|
193 |
const gLegend = gRoot.append('foreignObject').attr('class', 'legend');
|
194 |
|
|
|
320 |
xScale.range([0, innerWidth]);
|
321 |
yScale.range([innerHeight, 0]);
|
322 |
|
323 |
+
// Update clip rect to match inner plotting area (coords of gRoot after translate)
|
324 |
+
clipRect
|
325 |
+
.attr('x', 0)
|
326 |
+
.attr('y', 0)
|
327 |
+
.attr('width', innerWidth)
|
328 |
+
.attr('height', innerHeight);
|
329 |
+
|
330 |
// Compute Y ticks
|
331 |
let yTicks = [];
|
332 |
if (isRankStrictFlag) {
|
app/src/content/embeds/all-ratings.html
CHANGED
@@ -171,7 +171,7 @@
|
|
171 |
|
172 |
// Academic marker shapes
|
173 |
const markerShapes = ['circle', 'square', 'triangle', 'diamond', 'inverted-triangle'];
|
174 |
-
const markerSize =
|
175 |
|
176 |
// Groups
|
177 |
const gRoot = svg.append('g');
|
|
|
171 |
|
172 |
// Academic marker shapes
|
173 |
const markerShapes = ['circle', 'square', 'triangle', 'diamond', 'inverted-triangle'];
|
174 |
+
const markerSize = 5;
|
175 |
|
176 |
// Groups
|
177 |
const gRoot = svg.append('g');
|
app/src/content/embeds/banner.html
CHANGED
@@ -1,4 +1,4 @@
|
|
1 |
-
<div class="d3-galaxy" style="width:100%;margin:10px 0;aspect-ratio:3/1;min-height:360px;"></div>
|
2 |
<script>
|
3 |
(() => {
|
4 |
const THIS_SCRIPT = document.currentScript;
|
@@ -43,9 +43,15 @@
|
|
43 |
|
44 |
const csvUrl = (container.dataset && container.dataset.src) || '/data/banner_visualisation_data.csv';
|
45 |
|
|
|
|
|
|
|
|
|
|
|
46 |
const svg = d3.select(container).append('svg')
|
47 |
.attr('width', '100%')
|
48 |
-
.style('display', 'block')
|
|
|
49 |
|
50 |
// Tooltip (reuse style from other embeds)
|
51 |
container.style.position = container.style.position || 'relative';
|
@@ -272,7 +278,7 @@
|
|
272 |
|
273 |
tipInner.innerHTML =
|
274 |
`<div style="display:flex; gap:10px; align-items:flex-start;">` +
|
275 |
-
`<img src="${imgSrc}" alt="thumb ${d.original_id}" style="width:120px;height:120px;object-fit:
|
276 |
`<div style="min-width:140px; max-width:200px;">` +
|
277 |
`<div><strong>${d.category || 'Unknown'}</strong></div>` +
|
278 |
`<div style="word-wrap:break-word; line-height:1.3; margin:4px 0;"><strong>Q:</strong> ${userText}</div>` +
|
|
|
1 |
+
<div class="d3-galaxy" style="width:100%;margin:10px 0;aspect-ratio:3/1;min-height:360px; display:flex; align-items:center; justify-content:center;"></div>
|
2 |
<script>
|
3 |
(() => {
|
4 |
const THIS_SCRIPT = document.currentScript;
|
|
|
43 |
|
44 |
const csvUrl = (container.dataset && container.dataset.src) || '/data/banner_visualisation_data.csv';
|
45 |
|
46 |
+
// Assurer un fond transparent pour l'ensemble du composant
|
47 |
+
if (container && container.style) {
|
48 |
+
container.style.background = 'transparent';
|
49 |
+
}
|
50 |
+
|
51 |
const svg = d3.select(container).append('svg')
|
52 |
.attr('width', '100%')
|
53 |
+
.style('display', 'block')
|
54 |
+
.style('background', 'transparent');
|
55 |
|
56 |
// Tooltip (reuse style from other embeds)
|
57 |
container.style.position = container.style.position || 'relative';
|
|
|
278 |
|
279 |
tipInner.innerHTML =
|
280 |
`<div style="display:flex; gap:10px; align-items:flex-start;">` +
|
281 |
+
`<img src="${imgSrc}" alt="thumb ${d.original_id}" style="width:120px;height:120px;object-fit:cover;flex:0 0 auto;border-radius:6px;border:1px solid var(--border-color);" />` +
|
282 |
`<div style="min-width:140px; max-width:200px;">` +
|
283 |
`<div><strong>${d.category || 'Unknown'}</strong></div>` +
|
284 |
`<div style="word-wrap:break-word; line-height:1.3; margin:4px 0;"><strong>Q:</strong> ${userText}</div>` +
|
app/src/content/embeds/d3-pie.html
CHANGED
@@ -93,7 +93,7 @@
|
|
93 |
const CAPTION_GAP = 24; // espace entre titre et donut
|
94 |
const GAP_X = 20; // espace entre colonnes
|
95 |
const GAP_Y = 12; // espace entre lignes
|
96 |
-
const
|
97 |
const TOP_OFFSET = 4; // décalage vertical supplémentaire pour aérer le haut
|
98 |
const updateSize = () => {
|
99 |
width = container.clientWidth || 800;
|
@@ -103,15 +103,27 @@
|
|
103 |
};
|
104 |
|
105 |
function renderLegend(categories, colorOf, innerWidth, legendY){
|
106 |
-
const
|
107 |
-
gLegend.attr('x', 0).attr('y', legendY).attr('width', innerWidth)
|
108 |
const root = gLegend.selectAll('div').data([0]).join('xhtml:div');
|
109 |
root
|
110 |
-
.style('height',
|
111 |
.style('display', 'flex')
|
112 |
.style('align-items', 'center')
|
113 |
.style('justify-content', 'center');
|
114 |
root.html(`<div class="items">${categories.map(c => `<div class="item"><span class="swatch" style="background:${colorOf(c)}"></span><span style="font-weight:500">${c}</span></div>`).join('')}</div>`);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
115 |
}
|
116 |
|
117 |
function drawPies(rows){
|
@@ -174,7 +186,7 @@
|
|
174 |
const legendY = TOP_OFFSET + plotsHeight + 4;
|
175 |
// Légende centrée globalement (pleine largeur du conteneur)
|
176 |
gLegend.attr('x', 0).attr('width', innerWidth);
|
177 |
-
renderLegend(categories, colorOf, innerWidth, legendY);
|
178 |
|
179 |
const captions = new Map(METRICS.map(m => [m.key, `${m.title}`]));
|
180 |
|
@@ -232,7 +244,7 @@
|
|
232 |
});
|
233 |
|
234 |
// Définir la hauteur totale du SVG après avoir placé les éléments
|
235 |
-
const totalHeight = Math.ceil(margin.top + TOP_OFFSET + plotsHeight + 4 +
|
236 |
svg.attr('height', totalHeight);
|
237 |
}
|
238 |
|
|
|
93 |
const CAPTION_GAP = 24; // espace entre titre et donut
|
94 |
const GAP_X = 20; // espace entre colonnes
|
95 |
const GAP_Y = 12; // espace entre lignes
|
96 |
+
const BASE_LEGEND_HEIGHT = 56; // hauteur minimale de la légende (augmentée pour éviter la troncature mobile)
|
97 |
const TOP_OFFSET = 4; // décalage vertical supplémentaire pour aérer le haut
|
98 |
const updateSize = () => {
|
99 |
width = container.clientWidth || 800;
|
|
|
103 |
};
|
104 |
|
105 |
function renderLegend(categories, colorOf, innerWidth, legendY){
|
106 |
+
const minHeight = BASE_LEGEND_HEIGHT;
|
107 |
+
gLegend.attr('x', 0).attr('y', legendY).attr('width', innerWidth);
|
108 |
const root = gLegend.selectAll('div').data([0]).join('xhtml:div');
|
109 |
root
|
110 |
+
.style('min-height', minHeight + 'px')
|
111 |
.style('display', 'flex')
|
112 |
.style('align-items', 'center')
|
113 |
.style('justify-content', 'center');
|
114 |
root.html(`<div class="items">${categories.map(c => `<div class="item"><span class="swatch" style="background:${colorOf(c)}"></span><span style="font-weight:500">${c}</span></div>`).join('')}</div>`);
|
115 |
+
|
116 |
+
// Mesurer la hauteur réelle (contenu potentiellement sur plusieurs lignes en mobile)
|
117 |
+
let measured = minHeight;
|
118 |
+
try {
|
119 |
+
const n = root.node();
|
120 |
+
const h1 = n && n.scrollHeight ? Math.ceil(n.scrollHeight) : 0;
|
121 |
+
const h2 = n ? Math.ceil(n.getBoundingClientRect().height) : 0;
|
122 |
+
measured = Math.max(minHeight, h1, h2);
|
123 |
+
} catch {}
|
124 |
+
gLegend.attr('height', measured);
|
125 |
+
root.style('height', measured + 'px');
|
126 |
+
return measured;
|
127 |
}
|
128 |
|
129 |
function drawPies(rows){
|
|
|
186 |
const legendY = TOP_OFFSET + plotsHeight + 4;
|
187 |
// Légende centrée globalement (pleine largeur du conteneur)
|
188 |
gLegend.attr('x', 0).attr('width', innerWidth);
|
189 |
+
const legendHeightUsed = renderLegend(categories, colorOf, innerWidth, legendY);
|
190 |
|
191 |
const captions = new Map(METRICS.map(m => [m.key, `${m.title}`]));
|
192 |
|
|
|
244 |
});
|
245 |
|
246 |
// Définir la hauteur totale du SVG après avoir placé les éléments
|
247 |
+
const totalHeight = Math.ceil(margin.top + TOP_OFFSET + plotsHeight + 4 + (typeof legendHeightUsed === 'number' ? legendHeightUsed : BASE_LEGEND_HEIGHT) + margin.bottom);
|
248 |
svg.attr('height', totalHeight);
|
249 |
}
|
250 |
|
app/src/content/embeds/filters-quad.html
CHANGED
@@ -15,7 +15,7 @@
|
|
15 |
@media (max-width: 980px) { .filters-quad__grid { grid-template-columns: 1fr; } }
|
16 |
|
17 |
.filters-quad__controls { display:flex; align-items:center; justify-content:center; gap:12px; margin: 6px 0 12px 0; flex-wrap:wrap; }
|
18 |
-
.filters-quad__controls label { font-size:
|
19 |
.filters-quad__controls select {
|
20 |
font-size: 14px; padding: 8px 32px 8px 12px; border: 1px solid var(--border-color); border-radius: 10px;
|
21 |
background-color: var(--surface-bg); color: var(--text-color);
|
@@ -28,7 +28,7 @@
|
|
28 |
.filters-quad__controls select:hover { border-color: var(--primary-color); }
|
29 |
.filters-quad__controls select:focus { border-color: var(--primary-color); box-shadow: 0 0 0 3px rgba(232,137,171,.25); outline: none; }
|
30 |
|
31 |
-
.filters-quad__legend { display:flex; align-items:center; justify-content:center; gap:12px; flex-wrap:wrap; font-size:
|
32 |
.filters-quad__legend .item { display:inline-flex; align-items:center; gap:6px; white-space:nowrap; }
|
33 |
.filters-quad__legend .swatch { width:10px; height:10px; border-radius:50%; display:inline-block; }
|
34 |
|
@@ -73,6 +73,26 @@
|
|
73 |
s.addEventListener('load', onReady, { once: true }); if (window.d3) onReady();
|
74 |
};
|
75 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
76 |
function initRunLine(cell){
|
77 |
const d3 = window.d3;
|
78 |
const csvPath = cell.getAttribute('data-csv');
|
@@ -107,14 +127,12 @@
|
|
107 |
const xScale = d3.scaleLinear(); const yScale = d3.scaleLinear();
|
108 |
const lineGen = d3.line().x(d => xScale(d.step)).y(d => yScale(d.value));
|
109 |
let isRankStrictFlag = false; let rankTickMax = 1;
|
110 |
-
// Optional shared Y domain override (set from outside to sync the 4 charts)
|
111 |
-
let yDomainOverride = null; // { metricKey, domain:[min,max] | [max,1] for rank, isRankStrict, rankTickMax }
|
112 |
|
113 |
// Colors and markers (match original embeds)
|
114 |
const primary = getComputedStyle(document.documentElement).getPropertyValue('--primary-color').trim() || '#E889AB';
|
115 |
const pool = [primary, '#4EA5B7', '#E38A42', '#CEC0FA', ...(d3.schemeTableau10||[])];
|
116 |
const markerShapes = ['circle', 'square', 'triangle', 'diamond', 'inverted-triangle'];
|
117 |
-
const markerSize =
|
118 |
function drawMarker(selection, shape, size) {
|
119 |
const s = size / 2;
|
120 |
switch (shape) {
|
@@ -132,6 +150,24 @@
|
|
132 |
return selection.append('circle').attr('r', s);
|
133 |
}
|
134 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
135 |
// Ready signal for async load completion
|
136 |
let readyResolve = null;
|
137 |
const ready = new Promise((res)=> { readyResolve = res; });
|
@@ -165,8 +201,8 @@
|
|
165 |
gAxes.selectAll('*').remove();
|
166 |
let xAxis = d3.axisBottom(xScale).tickSizeOuter(0); xAxis = xAxis.ticks(8);
|
167 |
const yAxis = d3.axisLeft(yScale).tickValues(yTicks).tickSizeOuter(0).tickFormat(isRankStrictFlag ? d3.format('d') : d3.format('.2f'));
|
168 |
-
gAxes.append('g').attr('transform', `translate(0,${innerHeight})`).call(xAxis).call(g=>{ g.selectAll('path, line').attr('stroke',axisColor); g.selectAll('text').attr('fill',tickColor).style('font-size','
|
169 |
-
gAxes.append('g').call(yAxis).call(g=>{ g.selectAll('path, line').attr('stroke',axisColor); g.selectAll('text').attr('fill',tickColor).style('font-size','
|
170 |
|
171 |
// Legend box (top-right)
|
172 |
// Per-cell legend hidden; global legend is used
|
@@ -185,14 +221,7 @@
|
|
185 |
runs.forEach(r => { (map[r]||[]).forEach(pt => { const v = isRankStrict ? Math.round(pt.value) : pt.value; minStep=Math.min(minStep,pt.step); maxStep=Math.max(maxStep,pt.step); maxVal=Math.max(maxVal,v); minVal=Math.min(minVal,v); }); });
|
186 |
if (!isFinite(minStep) || !isFinite(maxStep)) return;
|
187 |
xScale.domain([minStep, maxStep]); if (isRank) { rankTickMax = Math.max(1, Math.round(maxVal)); yScale.domain([rankTickMax, 1]); } else { yScale.domain([minVal, maxVal]).nice(); }
|
188 |
-
|
189 |
-
if (yDomainOverride && yDomainOverride.metricKey === metricKey && Array.isArray(yDomainOverride.domain)) {
|
190 |
-
isRankStrictFlag = !!yDomainOverride.isRankStrict;
|
191 |
-
rankTickMax = yDomainOverride.rankTickMax != null ? yDomainOverride.rankTickMax : rankTickMax;
|
192 |
-
yScale.domain(yDomainOverride.domain);
|
193 |
-
} else {
|
194 |
-
isRankStrictFlag = isRankStrict;
|
195 |
-
}
|
196 |
|
197 |
const { innerWidth, innerHeight } = updateScales();
|
198 |
|
@@ -237,24 +266,31 @@
|
|
237 |
// Hover
|
238 |
gHover.selectAll('*').remove();
|
239 |
const overlay = gHover.append('rect').attr('fill','transparent').style('cursor','crosshair').attr('x',0).attr('y',0).attr('width', innerWidth).attr('height', innerHeight);
|
240 |
-
const
|
241 |
-
const hoverLine = gHover.append('line').attr('stroke',axisColor).attr('stroke-width',1).attr('y1',0).attr('y2',innerHeight).style('display','none');
|
242 |
const stepSet = new Set(); series.forEach(s=>s.values.forEach(v=>stepSet.add(v.step))); const steps = Array.from(stepSet).sort((a,b)=>a-b);
|
243 |
-
function onMove(ev){
|
|
|
|
|
|
|
|
|
244 |
let html = `<div><strong>${titleText}</strong></div><div><strong>step</strong> ${nearest}</div>`;
|
245 |
-
const
|
246 |
-
|
247 |
-
|
248 |
-
const
|
249 |
-
if(
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
|
256 |
-
|
257 |
-
tipInner.innerHTML = html;
|
|
|
|
|
|
|
|
|
258 |
function onLeave(){ tip.style.opacity='0'; tip.style.transform='translate(-9999px, -9999px)'; hoverLine.style('display','none'); }
|
259 |
overlay.on('mousemove', onMove).on('mouseleave', onLeave);
|
260 |
}
|
@@ -278,7 +314,9 @@
|
|
278 |
metricList = Array.from(new Set(rows.map(r=>r.metric))).sort();
|
279 |
runList = Array.from(new Set(rows.map(r=>r.run))).sort(); runOrder = runList;
|
280 |
metricList.forEach(m => { const map={}; runList.forEach(r=>map[r]=[]); rows.filter(r=>r.metric===m).forEach(r=>{ if(!isNaN(r.step)&&!isNaN(r.value)) map[r.run].push({ step:r.step, value:r.value, stderr:r.stderr }); }); dataByMetric.set(m, map); });
|
281 |
-
|
|
|
|
|
282 |
renderMetric(def);
|
283 |
const ro = window.ResizeObserver ? new ResizeObserver(()=>renderMetric(def)) : null; if (ro) ro.observe(cell);
|
284 |
if (typeof readyResolve === 'function') readyResolve();
|
@@ -293,21 +331,7 @@
|
|
293 |
return {
|
294 |
ready,
|
295 |
getMetrics: () => metricList.slice(),
|
296 |
-
setMetric: (m) => { if (m) renderMetric(m); }
|
297 |
-
// Expose extent for a given metric so the host can compute a shared Y domain
|
298 |
-
getExtent: (metricKey) => {
|
299 |
-
const map = dataByMetric.get(metricKey) || {};
|
300 |
-
const runs = runOrder;
|
301 |
-
let maxVal = -Infinity, minVal = Infinity;
|
302 |
-
const isRank = /rank/i.test(metricKey); const isAverage = /average/i.test(metricKey); const isRankStrict = isRank && !isAverage;
|
303 |
-
runs.forEach(r => { (map[r]||[]).forEach(pt => { const v = isRankStrict ? Math.round(pt.value) : pt.value; if (isFinite(v)) { maxVal=Math.max(maxVal,v); minVal=Math.min(minVal,v); } }); });
|
304 |
-
if (!isFinite(minVal) || !isFinite(maxVal)) return null;
|
305 |
-
return { min: minVal, max: maxVal, isRankStrict };
|
306 |
-
},
|
307 |
-
// Allow host to set a shared Y domain override (persisted across resizes)
|
308 |
-
setYDomainOverride: (metricKey, domain, isRankStrict, sharedRankTickMax) => {
|
309 |
-
yDomainOverride = { metricKey, domain, isRankStrict: !!isRankStrict, rankTickMax: sharedRankTickMax };
|
310 |
-
}
|
311 |
};
|
312 |
}
|
313 |
|
@@ -340,37 +364,19 @@
|
|
340 |
const intersect = (arrs) => arrs.reduce((acc, cur) => acc.filter(x => cur.includes(x)));
|
341 |
let metrics = lists.length ? intersect(lists) : [];
|
342 |
if (!metrics.length) { metrics = lists[0] || []; }
|
343 |
-
|
|
|
344 |
|
345 |
let ctrl = host.querySelector('.filters-quad__controls');
|
346 |
if (!ctrl) { ctrl = document.createElement('div'); ctrl.className = 'filters-quad__controls'; host.insertBefore(ctrl, host.firstChild); }
|
347 |
ctrl.innerHTML = '';
|
348 |
const label = document.createElement('label'); label.textContent = 'Metric';
|
349 |
const select = document.createElement('select');
|
350 |
-
metrics.forEach(m => { const o=document.createElement('option'); o.value=m; o.textContent=m; select.appendChild(o); });
|
351 |
if (def) select.value = def;
|
352 |
label.appendChild(select); ctrl.appendChild(label);
|
353 |
|
354 |
-
const applyAll = (v) =>
|
355 |
-
// Compute shared Y domain across all instances for selected metric
|
356 |
-
const isRank = /rank/i.test(v); const isAverage = /average/i.test(v); const isRankStrict = isRank && !isAverage;
|
357 |
-
const extents = instances.map(i => (i && typeof i.getExtent === 'function') ? i.getExtent(v) : null).filter(Boolean);
|
358 |
-
if (!extents.length) { instances.forEach(i => i && typeof i.setMetric === 'function' && i.setMetric(v)); return; }
|
359 |
-
let globalMin = Math.min(...extents.map(e => e.min));
|
360 |
-
let globalMax = Math.max(...extents.map(e => e.max));
|
361 |
-
let domain, rankTickMax;
|
362 |
-
if (isRankStrict) {
|
363 |
-
rankTickMax = Math.max(1, Math.round(globalMax));
|
364 |
-
domain = [rankTickMax, 1];
|
365 |
-
} else {
|
366 |
-
domain = [globalMin, globalMax];
|
367 |
-
}
|
368 |
-
// Apply override then render
|
369 |
-
instances.forEach(i => {
|
370 |
-
if (i && typeof i.setYDomainOverride === 'function') i.setYDomainOverride(v, domain, isRankStrict, rankTickMax);
|
371 |
-
if (i && typeof i.setMetric === 'function') i.setMetric(v);
|
372 |
-
});
|
373 |
-
};
|
374 |
if (def) applyAll(def);
|
375 |
select.addEventListener('change', () => applyAll(select.value));
|
376 |
|
@@ -383,19 +389,18 @@
|
|
383 |
if (r.ok && window.d3 && window.d3.csvParse) {
|
384 |
const txt = await r.text();
|
385 |
const rows = window.d3.csvParse(txt);
|
386 |
-
|
387 |
-
if (runList.includes('FineVision')) { runList = ['FineVision', ...runList.filter(r => r !== 'FineVision')]; }
|
388 |
const primary = getComputedStyle(document.documentElement).getPropertyValue('--primary-color').trim() || '#E889AB';
|
389 |
const pool = [primary, '#4EA5B7', '#E38A42', '#CEC0FA', ...((window.d3 && window.d3.schemeTableau10) ? window.d3.schemeTableau10 : ['#4e79a7','#f28e2b','#e15759','#76b7b2','#59a14f','#edc948','#b07aa1','#ff9da7','#9c755f','#bab0ab'])];
|
390 |
const markerShapes = ['circle', 'square', 'triangle', 'diamond', 'inverted-triangle'];
|
391 |
const shapeSVG = (shape, color) => {
|
392 |
-
const size =
|
393 |
-
if (shape === 'circle') return `<svg width="
|
394 |
-
if (shape === 'square') return `<svg width="
|
395 |
-
if (shape === 'triangle') return `<svg width="
|
396 |
-
if (shape === 'diamond') return `<svg width="
|
397 |
-
if (shape === 'inverted-triangle') return `<svg width="
|
398 |
-
return `<svg width="
|
399 |
};
|
400 |
legendHost.innerHTML = runList.map((name, i)=> {
|
401 |
const color = pool[i % pool.length];
|
|
|
15 |
@media (max-width: 980px) { .filters-quad__grid { grid-template-columns: 1fr; } }
|
16 |
|
17 |
.filters-quad__controls { display:flex; align-items:center; justify-content:center; gap:12px; margin: 6px 0 12px 0; flex-wrap:wrap; }
|
18 |
+
.filters-quad__controls label { font-size:14px; color: var(--text-color); font-weight:600; display:flex; align-items:center; gap:8px; }
|
19 |
.filters-quad__controls select {
|
20 |
font-size: 14px; padding: 8px 32px 8px 12px; border: 1px solid var(--border-color); border-radius: 10px;
|
21 |
background-color: var(--surface-bg); color: var(--text-color);
|
|
|
28 |
.filters-quad__controls select:hover { border-color: var(--primary-color); }
|
29 |
.filters-quad__controls select:focus { border-color: var(--primary-color); box-shadow: 0 0 0 3px rgba(232,137,171,.25); outline: none; }
|
30 |
|
31 |
+
.filters-quad__legend { display:flex; align-items:center; justify-content:center; gap:12px; flex-wrap:wrap; font-size:12px; color: var(--text-color); margin: 2px 0 10px 0; }
|
32 |
.filters-quad__legend .item { display:inline-flex; align-items:center; gap:6px; white-space:nowrap; }
|
33 |
.filters-quad__legend .swatch { width:10px; height:10px; border-radius:50%; display:inline-block; }
|
34 |
|
|
|
73 |
s.addEventListener('load', onReady, { once: true }); if (window.d3) onReady();
|
74 |
};
|
75 |
|
76 |
+
// Mapping métrique -> libellé lisible (aligné avec les autres embeds)
|
77 |
+
const metricTitleMapping = {
|
78 |
+
'docvqa_val_anls': 'DocVQA',
|
79 |
+
'infovqa_val_anls': 'InfoVQA',
|
80 |
+
'mme_total_score': 'MME Total',
|
81 |
+
'mmmu_val_mmmu_acc': 'MMMU',
|
82 |
+
'mmstar_average': 'MMStar',
|
83 |
+
'ocrbench_ocrbench_accuracy': 'OCRBench',
|
84 |
+
'scienceqa_exact_match': 'ScienceQA',
|
85 |
+
'textvqa_val_exact_match': 'TextVQA',
|
86 |
+
'average': 'Average',
|
87 |
+
'average_rank': 'Average Rank',
|
88 |
+
'ai2d_exact_match': 'AI2D',
|
89 |
+
'chartqa_relaxed_overall': 'ChartQA',
|
90 |
+
'seedbench_seed_all': 'SeedBench'
|
91 |
+
};
|
92 |
+
function getMetricDisplayName(metricKey){
|
93 |
+
return metricTitleMapping[metricKey] || metricKey;
|
94 |
+
}
|
95 |
+
|
96 |
function initRunLine(cell){
|
97 |
const d3 = window.d3;
|
98 |
const csvPath = cell.getAttribute('data-csv');
|
|
|
127 |
const xScale = d3.scaleLinear(); const yScale = d3.scaleLinear();
|
128 |
const lineGen = d3.line().x(d => xScale(d.step)).y(d => yScale(d.value));
|
129 |
let isRankStrictFlag = false; let rankTickMax = 1;
|
|
|
|
|
130 |
|
131 |
// Colors and markers (match original embeds)
|
132 |
const primary = getComputedStyle(document.documentElement).getPropertyValue('--primary-color').trim() || '#E889AB';
|
133 |
const pool = [primary, '#4EA5B7', '#E38A42', '#CEC0FA', ...(d3.schemeTableau10||[])];
|
134 |
const markerShapes = ['circle', 'square', 'triangle', 'diamond', 'inverted-triangle'];
|
135 |
+
const markerSize = 5;
|
136 |
function drawMarker(selection, shape, size) {
|
137 |
const s = size / 2;
|
138 |
switch (shape) {
|
|
|
150 |
return selection.append('circle').attr('r', s);
|
151 |
}
|
152 |
}
|
153 |
+
// Small SVG markup for marker shape (used in hover)
|
154 |
+
function shapeSvgMarkup(shape, color) {
|
155 |
+
const stroke = color;
|
156 |
+
switch (shape) {
|
157 |
+
case 'circle':
|
158 |
+
return `<svg width="14" height="12" viewBox="-6 -6 12 12" aria-hidden="true"><circle r="5" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
|
159 |
+
case 'square':
|
160 |
+
return `<svg width="14" height="12" viewBox="-6 -6 12 12" aria-hidden="true"><rect x="-5" y="-5" width="10" height="10" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
|
161 |
+
case 'triangle':
|
162 |
+
return `<svg width="14" height="12" viewBox="-6 -6 12 12" aria-hidden="true"><path d="M0,-6 L5,3 L-5,3 Z" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
|
163 |
+
case 'diamond':
|
164 |
+
return `<svg width="14" height="12" viewBox="-6 -6 12 12" aria-hidden="true"><path d="M0,-6 L6,0 L0,6 L-6,0 Z" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
|
165 |
+
case 'inverted-triangle':
|
166 |
+
return `<svg width="14" height="12" viewBox="-6 -6 12 12" aria-hidden="true"><path d="M0,6 L5,-3 L-5,-3 Z" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
|
167 |
+
default:
|
168 |
+
return `<svg width="14" height="12" viewBox="-6 -6 12 12" aria-hidden="true"><circle r="5" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
|
169 |
+
}
|
170 |
+
}
|
171 |
// Ready signal for async load completion
|
172 |
let readyResolve = null;
|
173 |
const ready = new Promise((res)=> { readyResolve = res; });
|
|
|
201 |
gAxes.selectAll('*').remove();
|
202 |
let xAxis = d3.axisBottom(xScale).tickSizeOuter(0); xAxis = xAxis.ticks(8);
|
203 |
const yAxis = d3.axisLeft(yScale).tickValues(yTicks).tickSizeOuter(0).tickFormat(isRankStrictFlag ? d3.format('d') : d3.format('.2f'));
|
204 |
+
gAxes.append('g').attr('transform', `translate(0,${innerHeight})`).call(xAxis).call(g=>{ g.selectAll('path, line').attr('stroke',axisColor); g.selectAll('text').attr('fill',tickColor).style('font-size','11px'); });
|
205 |
+
gAxes.append('g').call(yAxis).call(g=>{ g.selectAll('path, line').attr('stroke',axisColor); g.selectAll('text').attr('fill',tickColor).style('font-size','11px'); });
|
206 |
|
207 |
// Legend box (top-right)
|
208 |
// Per-cell legend hidden; global legend is used
|
|
|
221 |
runs.forEach(r => { (map[r]||[]).forEach(pt => { const v = isRankStrict ? Math.round(pt.value) : pt.value; minStep=Math.min(minStep,pt.step); maxStep=Math.max(maxStep,pt.step); maxVal=Math.max(maxVal,v); minVal=Math.min(minVal,v); }); });
|
222 |
if (!isFinite(minStep) || !isFinite(maxStep)) return;
|
223 |
xScale.domain([minStep, maxStep]); if (isRank) { rankTickMax = Math.max(1, Math.round(maxVal)); yScale.domain([rankTickMax, 1]); } else { yScale.domain([minVal, maxVal]).nice(); }
|
224 |
+
isRankStrictFlag = isRankStrict;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
225 |
|
226 |
const { innerWidth, innerHeight } = updateScales();
|
227 |
|
|
|
266 |
// Hover
|
267 |
gHover.selectAll('*').remove();
|
268 |
const overlay = gHover.append('rect').attr('fill','transparent').style('cursor','crosshair').attr('x',0).attr('y',0).attr('width', innerWidth).attr('height', innerHeight);
|
269 |
+
const hoverLine = gHover.append('line').attr('stroke','rgba(0,0,0,0.25)').attr('stroke-width',1).attr('y1',0).attr('y2',innerHeight).style('display','none');
|
|
|
270 |
const stepSet = new Set(); series.forEach(s=>s.values.forEach(v=>stepSet.add(v.step))); const steps = Array.from(stepSet).sort((a,b)=>a-b);
|
271 |
+
function onMove(ev){
|
272 |
+
const [mx,my] = d3.pointer(ev, overlay.node());
|
273 |
+
const nearest = steps.reduce((best,s)=> Math.abs(s - xScale.invert(mx)) < Math.abs(best - xScale.invert(mx)) ? s : best, steps[0]);
|
274 |
+
const xpx = xScale(nearest);
|
275 |
+
hoverLine.attr('x1',xpx).attr('x2',xpx).style('display',null);
|
276 |
let html = `<div><strong>${titleText}</strong></div><div><strong>step</strong> ${nearest}</div>`;
|
277 |
+
const items = [];
|
278 |
+
series.forEach(s => {
|
279 |
+
const m = new Map(s.values.map(v=>[v.step, v]));
|
280 |
+
const pt = m.get(nearest);
|
281 |
+
if (pt && pt.value != null) items.push({ s, pt });
|
282 |
+
});
|
283 |
+
const fmt = (vv)=> (isRankStrictFlag ? d3.format('d')(vv) : (+vv).toFixed(4));
|
284 |
+
items.sort((a,b)=> isRankStrictFlag ? (b.pt.value - a.pt.value) : (a.pt.value - b.pt.value));
|
285 |
+
items.forEach(({ s, pt }) => {
|
286 |
+
const err = (pt.stderr!=null && isFinite(pt.stderr) && pt.stderr>0) ? ` ± ${fmt(pt.stderr)}` : '';
|
287 |
+
html += `<div style=\"display:flex;align-items:center;gap:6px;\">${shapeSvgMarkup(s.marker, s.color)}<strong>${s.run}</strong> ${fmt(pt.value)}${err}</div>`;
|
288 |
+
});
|
289 |
+
tipInner.innerHTML = html;
|
290 |
+
const offsetX=12, offsetY=12;
|
291 |
+
tip.style.opacity='1';
|
292 |
+
tip.style.transform=`translate(${Math.round(mx+offsetX+margin.left)}px, ${Math.round(my+offsetY+margin.top)}px)`;
|
293 |
+
}
|
294 |
function onLeave(){ tip.style.opacity='0'; tip.style.transform='translate(-9999px, -9999px)'; hoverLine.style('display','none'); }
|
295 |
overlay.on('mousemove', onMove).on('mouseleave', onLeave);
|
296 |
}
|
|
|
314 |
metricList = Array.from(new Set(rows.map(r=>r.metric))).sort();
|
315 |
runList = Array.from(new Set(rows.map(r=>r.run))).sort(); runOrder = runList;
|
316 |
metricList.forEach(m => { const map={}; runList.forEach(r=>map[r]=[]); rows.filter(r=>r.metric===m).forEach(r=>{ if(!isNaN(r.step)&&!isNaN(r.value)) map[r.run].push({ step:r.step, value:r.value, stderr:r.stderr }); }); dataByMetric.set(m, map); });
|
317 |
+
// Par défaut, privilégier average_rank, sinon premier dispo
|
318 |
+
const preferred = metricList.find(m => /average_rank/i.test(m)) || metricList.find(m => m === 'ai2d_exact_match');
|
319 |
+
const def = preferred || metricList[0];
|
320 |
renderMetric(def);
|
321 |
const ro = window.ResizeObserver ? new ResizeObserver(()=>renderMetric(def)) : null; if (ro) ro.observe(cell);
|
322 |
if (typeof readyResolve === 'function') readyResolve();
|
|
|
331 |
return {
|
332 |
ready,
|
333 |
getMetrics: () => metricList.slice(),
|
334 |
+
setMetric: (m) => { if (m) renderMetric(m); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
335 |
};
|
336 |
}
|
337 |
|
|
|
364 |
const intersect = (arrs) => arrs.reduce((acc, cur) => acc.filter(x => cur.includes(x)));
|
365 |
let metrics = lists.length ? intersect(lists) : [];
|
366 |
if (!metrics.length) { metrics = lists[0] || []; }
|
367 |
+
// Par défaut: average_rank si présent (pas AI2D)
|
368 |
+
const def = (metrics.find(m => /average_rank/i.test(m)) || metrics[0] || '');
|
369 |
|
370 |
let ctrl = host.querySelector('.filters-quad__controls');
|
371 |
if (!ctrl) { ctrl = document.createElement('div'); ctrl.className = 'filters-quad__controls'; host.insertBefore(ctrl, host.firstChild); }
|
372 |
ctrl.innerHTML = '';
|
373 |
const label = document.createElement('label'); label.textContent = 'Metric';
|
374 |
const select = document.createElement('select');
|
375 |
+
metrics.forEach(m => { const o=document.createElement('option'); o.value=m; o.textContent=getMetricDisplayName(m); select.appendChild(o); });
|
376 |
if (def) select.value = def;
|
377 |
label.appendChild(select); ctrl.appendChild(label);
|
378 |
|
379 |
+
const applyAll = (v) => instances.forEach(i => i && typeof i.setMetric === 'function' && i.setMetric(v));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
380 |
if (def) applyAll(def);
|
381 |
select.addEventListener('change', () => applyAll(select.value));
|
382 |
|
|
|
389 |
if (r.ok && window.d3 && window.d3.csvParse) {
|
390 |
const txt = await r.text();
|
391 |
const rows = window.d3.csvParse(txt);
|
392 |
+
const runList = Array.from(new Set(rows.map(row => String(row.run||'').trim()).filter(Boolean)));
|
|
|
393 |
const primary = getComputedStyle(document.documentElement).getPropertyValue('--primary-color').trim() || '#E889AB';
|
394 |
const pool = [primary, '#4EA5B7', '#E38A42', '#CEC0FA', ...((window.d3 && window.d3.schemeTableau10) ? window.d3.schemeTableau10 : ['#4e79a7','#f28e2b','#e15759','#76b7b2','#59a14f','#edc948','#b07aa1','#ff9da7','#9c755f','#bab0ab'])];
|
395 |
const markerShapes = ['circle', 'square', 'triangle', 'diamond', 'inverted-triangle'];
|
396 |
const shapeSVG = (shape, color) => {
|
397 |
+
const size = 12; const s = size/2; const stroke = color;
|
398 |
+
if (shape === 'circle') return `<svg width="12" height="12" viewBox="-6 -6 12 12" aria-hidden="true"><circle r="5" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
|
399 |
+
if (shape === 'square') return `<svg width="12" height="12" viewBox="-6 -6 12 12" aria-hidden="true"><rect x="-5" y="-5" width="10" height="10" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
|
400 |
+
if (shape === 'triangle') return `<svg width="12" height="12" viewBox="-6 -6 12 12" aria-hidden="true"><path d="M0,-6 L5,3 L-5,3 Z" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
|
401 |
+
if (shape === 'diamond') return `<svg width="12" height="12" viewBox="-6 -6 12 12" aria-hidden="true"><path d="M0,-6 L6,0 L0,6 L-6,0 Z" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
|
402 |
+
if (shape === 'inverted-triangle') return `<svg width="12" height="12" viewBox="-6 -6 12 12" aria-hidden="true"><path d="M0,6 L5,-3 L-5,-3 Z" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
|
403 |
+
return `<svg width="12" height="12" viewBox="-6 -6 12 12" aria-hidden="true"><circle r="5" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
|
404 |
};
|
405 |
legendHost.innerHTML = runList.map((name, i)=> {
|
406 |
const color = pool[i % pool.length];
|
app/src/content/embeds/formatting-filters.html
CHANGED
@@ -171,7 +171,7 @@
|
|
171 |
|
172 |
// Academic marker shapes
|
173 |
const markerShapes = ['circle', 'square', 'triangle', 'diamond', 'inverted-triangle'];
|
174 |
-
const markerSize =
|
175 |
|
176 |
// Groups
|
177 |
const gRoot = svg.append('g');
|
|
|
171 |
|
172 |
// Academic marker shapes
|
173 |
const markerShapes = ['circle', 'square', 'triangle', 'diamond', 'inverted-triangle'];
|
174 |
+
const markerSize = 5;
|
175 |
|
176 |
// Groups
|
177 |
const gRoot = svg.append('g');
|
app/src/content/embeds/image-correspondence-filters.html
CHANGED
@@ -171,7 +171,7 @@
|
|
171 |
|
172 |
// Academic marker shapes
|
173 |
const markerShapes = ['circle', 'square', 'triangle', 'diamond', 'inverted-triangle'];
|
174 |
-
const markerSize =
|
175 |
|
176 |
// Groups
|
177 |
const gRoot = svg.append('g');
|
|
|
171 |
|
172 |
// Academic marker shapes
|
173 |
const markerShapes = ['circle', 'square', 'triangle', 'diamond', 'inverted-triangle'];
|
174 |
+
const markerSize = 5;
|
175 |
|
176 |
// Groups
|
177 |
const gRoot = svg.append('g');
|
app/src/content/embeds/internal-deduplication.html
CHANGED
@@ -166,20 +166,26 @@
|
|
166 |
.attr('width', '100%')
|
167 |
.style('display', 'block');
|
168 |
|
169 |
-
// Add marker definitions for
|
170 |
const defs = svg.append('defs');
|
|
|
|
|
|
|
|
|
171 |
|
172 |
// Academic marker shapes
|
173 |
const markerShapes = ['circle', 'square', 'triangle', 'diamond', 'inverted-triangle'];
|
174 |
-
const markerSize =
|
175 |
|
176 |
// Groups
|
177 |
const gRoot = svg.append('g');
|
178 |
const gGrid = gRoot.append('g').attr('class', 'grid');
|
179 |
const gAxes = gRoot.append('g').attr('class', 'axes');
|
180 |
-
|
181 |
-
const
|
182 |
-
const
|
|
|
|
|
183 |
const gHover = gRoot.append('g').attr('class', 'hover');
|
184 |
const gLegend = gRoot.append('foreignObject').attr('class', 'legend');
|
185 |
|
@@ -242,8 +248,8 @@
|
|
242 |
// Scales and layout
|
243 |
let width = 800, height = 360;
|
244 |
let margin = { top: 16, right: 28, bottom: 56, left: 64 };
|
245 |
-
let xScale = d3.scaleLinear();
|
246 |
-
let yScale = d3.scaleLinear();
|
247 |
|
248 |
// Line generators - simple linear connections
|
249 |
const lineGen = d3.line()
|
@@ -310,6 +316,13 @@
|
|
310 |
xScale.range([0, innerWidth]);
|
311 |
yScale.range([innerHeight, 0]);
|
312 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
313 |
// Compute Y ticks
|
314 |
let yTicks = [];
|
315 |
if (isRankStrictFlag) {
|
@@ -428,8 +441,8 @@
|
|
428 |
if (!isFinite(minStep) || !isFinite(maxStep)) { return; }
|
429 |
xScale.domain([minStep, maxStep]);
|
430 |
if (isRank) {
|
431 |
-
rankTickMax = Math.max(1, Math.round(maxVal));
|
432 |
-
yScale.domain([rankTickMax, 1]);
|
433 |
} else {
|
434 |
yScale.domain([minVal, maxVal]).nice();
|
435 |
}
|
|
|
166 |
.attr('width', '100%')
|
167 |
.style('display', 'block');
|
168 |
|
169 |
+
// Add marker definitions and clipPath for chart area
|
170 |
const defs = svg.append('defs');
|
171 |
+
const clipId = `clip-${Math.random().toString(36).slice(2)}`;
|
172 |
+
const clipPath = defs.append('clipPath').attr('id', clipId)
|
173 |
+
.attr('clipPathUnits', 'userSpaceOnUse');
|
174 |
+
const clipRect = clipPath.append('rect').attr('x', 0).attr('y', 0).attr('width', 0).attr('height', 0);
|
175 |
|
176 |
// Academic marker shapes
|
177 |
const markerShapes = ['circle', 'square', 'triangle', 'diamond', 'inverted-triangle'];
|
178 |
+
const markerSize = 5;
|
179 |
|
180 |
// Groups
|
181 |
const gRoot = svg.append('g');
|
182 |
const gGrid = gRoot.append('g').attr('class', 'grid');
|
183 |
const gAxes = gRoot.append('g').attr('class', 'axes');
|
184 |
+
// Dedicated plotting layer that will be clipped
|
185 |
+
const gPlot = gRoot.append('g').attr('class', 'plot');
|
186 |
+
const gAreas = gPlot.append('g').attr('class', 'areas');
|
187 |
+
const gLines = gPlot.append('g').attr('class', 'lines');
|
188 |
+
const gPoints = gPlot.append('g').attr('class', 'points');
|
189 |
const gHover = gRoot.append('g').attr('class', 'hover');
|
190 |
const gLegend = gRoot.append('foreignObject').attr('class', 'legend');
|
191 |
|
|
|
248 |
// Scales and layout
|
249 |
let width = 800, height = 360;
|
250 |
let margin = { top: 16, right: 28, bottom: 56, left: 64 };
|
251 |
+
let xScale = d3.scaleLinear().clamp(true);
|
252 |
+
let yScale = d3.scaleLinear().clamp(true);
|
253 |
|
254 |
// Line generators - simple linear connections
|
255 |
const lineGen = d3.line()
|
|
|
316 |
xScale.range([0, innerWidth]);
|
317 |
yScale.range([innerHeight, 0]);
|
318 |
|
319 |
+
// Update clip rect to match inner plotting area (coords local to gRoot)
|
320 |
+
clipRect
|
321 |
+
.attr('x', 0)
|
322 |
+
.attr('y', 0)
|
323 |
+
.attr('width', innerWidth)
|
324 |
+
.attr('height', innerHeight);
|
325 |
+
|
326 |
// Compute Y ticks
|
327 |
let yTicks = [];
|
328 |
if (isRankStrictFlag) {
|
|
|
441 |
if (!isFinite(minStep) || !isFinite(maxStep)) { return; }
|
442 |
xScale.domain([minStep, maxStep]);
|
443 |
if (isRank) {
|
444 |
+
rankTickMax = isRankStrict ? Math.max(1, Math.round(maxVal)) : Math.max(1, maxVal);
|
445 |
+
yScale.domain([rankTickMax, 1]).nice();
|
446 |
} else {
|
447 |
yScale.domain([minVal, maxVal]).nice();
|
448 |
}
|
app/src/content/embeds/relevance-filters.html
CHANGED
@@ -171,7 +171,7 @@
|
|
171 |
|
172 |
// Academic marker shapes
|
173 |
const markerShapes = ['circle', 'square', 'triangle', 'diamond', 'inverted-triangle'];
|
174 |
-
const markerSize =
|
175 |
|
176 |
// Groups
|
177 |
const gRoot = svg.append('g');
|
|
|
171 |
|
172 |
// Academic marker shapes
|
173 |
const markerShapes = ['circle', 'square', 'triangle', 'diamond', 'inverted-triangle'];
|
174 |
+
const markerSize = 5;
|
175 |
|
176 |
// Groups
|
177 |
const gRoot = svg.append('g');
|
app/src/content/embeds/remove-ch.html
CHANGED
@@ -171,7 +171,7 @@
|
|
171 |
|
172 |
// Academic marker shapes
|
173 |
const markerShapes = ['circle', 'square', 'triangle', 'diamond', 'inverted-triangle'];
|
174 |
-
const markerSize =
|
175 |
|
176 |
// Groups
|
177 |
const gRoot = svg.append('g');
|
|
|
171 |
|
172 |
// Academic marker shapes
|
173 |
const markerShapes = ['circle', 'square', 'triangle', 'diamond', 'inverted-triangle'];
|
174 |
+
const markerSize = 5;
|
175 |
|
176 |
// Groups
|
177 |
const gRoot = svg.append('g');
|
app/src/content/embeds/s25-ratings.html
CHANGED
@@ -171,7 +171,7 @@
|
|
171 |
|
172 |
// Academic marker shapes
|
173 |
const markerShapes = ['circle', 'square', 'triangle', 'diamond', 'inverted-triangle'];
|
174 |
-
const markerSize =
|
175 |
|
176 |
// Groups
|
177 |
const gRoot = svg.append('g');
|
|
|
171 |
|
172 |
// Academic marker shapes
|
173 |
const markerShapes = ['circle', 'square', 'triangle', 'diamond', 'inverted-triangle'];
|
174 |
+
const markerSize = 5;
|
175 |
|
176 |
// Groups
|
177 |
const gRoot = svg.append('g');
|
app/src/content/embeds/ss-vs-s1.html
CHANGED
@@ -171,7 +171,7 @@
|
|
171 |
|
172 |
// Academic marker shapes
|
173 |
const markerShapes = ['circle', 'square', 'triangle', 'diamond', 'inverted-triangle'];
|
174 |
-
const markerSize =
|
175 |
|
176 |
// Groups
|
177 |
const gRoot = svg.append('g');
|
|
|
171 |
|
172 |
// Academic marker shapes
|
173 |
const markerShapes = ['circle', 'square', 'triangle', 'diamond', 'inverted-triangle'];
|
174 |
+
const markerSize = 5;
|
175 |
|
176 |
// Groups
|
177 |
const gRoot = svg.append('g');
|
app/src/content/embeds/visual-dependency-filters.html
CHANGED
@@ -166,20 +166,26 @@
|
|
166 |
.attr('width', '100%')
|
167 |
.style('display', 'block');
|
168 |
|
169 |
-
// Add marker definitions for
|
170 |
const defs = svg.append('defs');
|
|
|
|
|
|
|
|
|
171 |
|
172 |
// Academic marker shapes
|
173 |
const markerShapes = ['circle', 'square', 'triangle', 'diamond', 'inverted-triangle'];
|
174 |
-
const markerSize =
|
175 |
|
176 |
// Groups
|
177 |
const gRoot = svg.append('g');
|
178 |
const gGrid = gRoot.append('g').attr('class', 'grid');
|
179 |
const gAxes = gRoot.append('g').attr('class', 'axes');
|
180 |
-
|
181 |
-
const
|
182 |
-
const
|
|
|
|
|
183 |
const gHover = gRoot.append('g').attr('class', 'hover');
|
184 |
const gLegend = gRoot.append('foreignObject').attr('class', 'legend');
|
185 |
|
@@ -242,8 +248,8 @@
|
|
242 |
// Scales and layout
|
243 |
let width = 800, height = 360;
|
244 |
let margin = { top: 16, right: 28, bottom: 56, left: 64 };
|
245 |
-
let xScale = d3.scaleLinear();
|
246 |
-
let yScale = d3.scaleLinear();
|
247 |
|
248 |
// Line generators - simple linear connections
|
249 |
const lineGen = d3.line()
|
@@ -310,6 +316,13 @@
|
|
310 |
xScale.range([0, innerWidth]);
|
311 |
yScale.range([innerHeight, 0]);
|
312 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
313 |
// Compute Y ticks
|
314 |
let yTicks = [];
|
315 |
if (isRankStrictFlag) {
|
@@ -428,8 +441,8 @@
|
|
428 |
if (!isFinite(minStep) || !isFinite(maxStep)) { return; }
|
429 |
xScale.domain([minStep, maxStep]);
|
430 |
if (isRank) {
|
431 |
-
rankTickMax = Math.max(1, Math.round(maxVal));
|
432 |
-
yScale.domain([rankTickMax, 1]);
|
433 |
} else {
|
434 |
yScale.domain([minVal, maxVal]).nice();
|
435 |
}
|
|
|
166 |
.attr('width', '100%')
|
167 |
.style('display', 'block');
|
168 |
|
169 |
+
// Add marker definitions and clipPath for chart area
|
170 |
const defs = svg.append('defs');
|
171 |
+
const clipId = `clip-${Math.random().toString(36).slice(2)}`;
|
172 |
+
const clipPath = defs.append('clipPath').attr('id', clipId)
|
173 |
+
.attr('clipPathUnits', 'userSpaceOnUse');
|
174 |
+
const clipRect = clipPath.append('rect').attr('x', 0).attr('y', 0).attr('width', 0).attr('height', 0);
|
175 |
|
176 |
// Academic marker shapes
|
177 |
const markerShapes = ['circle', 'square', 'triangle', 'diamond', 'inverted-triangle'];
|
178 |
+
const markerSize = 5;
|
179 |
|
180 |
// Groups
|
181 |
const gRoot = svg.append('g');
|
182 |
const gGrid = gRoot.append('g').attr('class', 'grid');
|
183 |
const gAxes = gRoot.append('g').attr('class', 'axes');
|
184 |
+
// Dedicated plotting layer that will be clipped
|
185 |
+
const gPlot = gRoot.append('g').attr('class', 'plot');
|
186 |
+
const gAreas = gPlot.append('g').attr('class', 'areas');
|
187 |
+
const gLines = gPlot.append('g').attr('class', 'lines');
|
188 |
+
const gPoints = gPlot.append('g').attr('class', 'points');
|
189 |
const gHover = gRoot.append('g').attr('class', 'hover');
|
190 |
const gLegend = gRoot.append('foreignObject').attr('class', 'legend');
|
191 |
|
|
|
248 |
// Scales and layout
|
249 |
let width = 800, height = 360;
|
250 |
let margin = { top: 16, right: 28, bottom: 56, left: 64 };
|
251 |
+
let xScale = d3.scaleLinear().clamp(true);
|
252 |
+
let yScale = d3.scaleLinear().clamp(true);
|
253 |
|
254 |
// Line generators - simple linear connections
|
255 |
const lineGen = d3.line()
|
|
|
316 |
xScale.range([0, innerWidth]);
|
317 |
yScale.range([innerHeight, 0]);
|
318 |
|
319 |
+
// Update clip rect to match inner plotting area (coords local to gRoot)
|
320 |
+
clipRect
|
321 |
+
.attr('x', 0)
|
322 |
+
.attr('y', 0)
|
323 |
+
.attr('width', innerWidth)
|
324 |
+
.attr('height', innerHeight);
|
325 |
+
|
326 |
// Compute Y ticks
|
327 |
let yTicks = [];
|
328 |
if (isRankStrictFlag) {
|
|
|
441 |
if (!isFinite(minStep) || !isFinite(maxStep)) { return; }
|
442 |
xScale.domain([minStep, maxStep]);
|
443 |
if (isRank) {
|
444 |
+
rankTickMax = isRankStrict ? Math.max(1, Math.round(maxVal)) : Math.max(1, maxVal);
|
445 |
+
yScale.domain([rankTickMax, 1]).nice();
|
446 |
} else {
|
447 |
yScale.domain([minVal, maxVal]).nice();
|
448 |
}
|
app/src/pages/index.astro
CHANGED
@@ -186,7 +186,7 @@ const licence = (articleFM as any)?.licence ?? (articleFM as any)?.license ?? (a
|
|
186 |
</script>
|
187 |
|
188 |
<script>
|
189 |
-
// Delegate copy clicks for code blocks injected by
|
190 |
document.addEventListener('click', async (e) => {
|
191 |
const target = e.target instanceof Element ? e.target : null;
|
192 |
const btn = target ? target.closest('.code-copy') : null;
|
|
|
186 |
</script>
|
187 |
|
188 |
<script>
|
189 |
+
// Delegate copy clicks for code blocks injected by rehypeCodeCopy
|
190 |
document.addEventListener('click', async (e) => {
|
191 |
const target = e.target instanceof Element ? e.target : null;
|
192 |
const btn = target ? target.closest('.code-copy') : null;
|
app/src/styles/_layout.css
CHANGED
@@ -69,9 +69,9 @@
|
|
69 |
}
|
70 |
|
71 |
.wide {
|
72 |
-
/* Target up to ~
|
73 |
-
width: min(
|
74 |
-
margin-left: 50
|
75 |
transform: translateX(-50%);
|
76 |
}
|
77 |
|
@@ -128,4 +128,26 @@
|
|
128 |
}
|
129 |
|
130 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
131 |
|
|
|
69 |
}
|
70 |
|
71 |
.wide {
|
72 |
+
/* Target up to ~1100px while staying within viewport minus page gutters */
|
73 |
+
width: min(1100px, 100vw - 32px);
|
74 |
+
margin-left: calc(50% + var(--content-padding-x) * 2);
|
75 |
transform: translateX(-50%);
|
76 |
}
|
77 |
|
|
|
128 |
}
|
129 |
|
130 |
|
131 |
+
/* ------------------------------------------------------------------------- */
|
132 |
+
/* D3 neural embed responsiveness */
|
133 |
+
/* Stack canvas (left) over network (right) on small screens */
|
134 |
+
/* ------------------------------------------------------------------------- */
|
135 |
+
@media (--bp-md) {
|
136 |
+
.d3-neural .panel {
|
137 |
+
flex-direction: column;
|
138 |
+
}
|
139 |
+
|
140 |
+
.d3-neural .panel .left {
|
141 |
+
flex: 0 0 auto;
|
142 |
+
width: 100%;
|
143 |
+
}
|
144 |
+
|
145 |
+
.d3-neural .panel .right {
|
146 |
+
flex: 0 0 auto;
|
147 |
+
width: 100%;
|
148 |
+
min-width: 0;
|
149 |
+
}
|
150 |
+
}
|
151 |
+
|
152 |
+
|
153 |
|
app/src/styles/_print.css
CHANGED
@@ -40,8 +40,8 @@
|
|
40 |
/* Avoid page breaks inside complex visual blocks */
|
41 |
.hero,
|
42 |
.hero-banner,
|
43 |
-
.d3-
|
44 |
-
.d3-
|
45 |
.html-embed__card,
|
46 |
.html-embed__card,
|
47 |
.js-plotly-plot,
|
|
|
40 |
/* Avoid page breaks inside complex visual blocks */
|
41 |
.hero,
|
42 |
.hero-banner,
|
43 |
+
.d3-banner,
|
44 |
+
.d3-banner svg,
|
45 |
.html-embed__card,
|
46 |
.html-embed__card,
|
47 |
.js-plotly-plot,
|
app/src/styles/components/_code.css
CHANGED
@@ -12,6 +12,12 @@ code {
|
|
12 |
line-height: 1.5;
|
13 |
}
|
14 |
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
/* ============================================================================ */
|
16 |
/* Shiki code blocks */
|
17 |
/* ============================================================================ */
|
@@ -142,6 +148,9 @@ section.content-grid pre code {
|
|
142 |
/* ============================================================================ */
|
143 |
/* Inside Accordions: remove padding and border on code containers */
|
144 |
.accordion .astro-code { padding: 0; border: none; }
|
|
|
|
|
|
|
145 |
|
146 |
/* ============================================================================ */
|
147 |
/* Language/extension vignette (bottom-right, discreet) */
|
@@ -163,6 +172,24 @@ section.content-grid pre code {
|
|
163 |
padding: 4px 6px;
|
164 |
pointer-events: none;
|
165 |
} */
|
166 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
167 |
/* In Accordions, keep same bottom-right placement */
|
168 |
.accordion .astro-code::after { right: 0; bottom: 0; }
|
|
|
12 |
line-height: 1.5;
|
13 |
}
|
14 |
|
15 |
+
p code, .note code {
|
16 |
+
white-space: nowrap;
|
17 |
+
padding: calc(var(--spacing-1)/3) calc(var(--spacing-1)/2);
|
18 |
+
}
|
19 |
+
|
20 |
+
|
21 |
/* ============================================================================ */
|
22 |
/* Shiki code blocks */
|
23 |
/* ============================================================================ */
|
|
|
148 |
/* ============================================================================ */
|
149 |
/* Inside Accordions: remove padding and border on code containers */
|
150 |
.accordion .astro-code { padding: 0; border: none; }
|
151 |
+
.accordion .astro-code { margin-bottom: 0 !important; }
|
152 |
+
.accordion pre { margin-bottom: 0 !important; }
|
153 |
+
.accordion .code-card pre { margin: 0 !important; }
|
154 |
|
155 |
/* ============================================================================ */
|
156 |
/* Language/extension vignette (bottom-right, discreet) */
|
|
|
172 |
padding: 4px 6px;
|
173 |
pointer-events: none;
|
174 |
} */
|
175 |
+
|
176 |
+
/* Fallback if Shiki uses data-lang instead of data-language */
|
177 |
+
/* .astro-code[data-lang]::after { content: attr(data-lang); }
|
178 |
+
.astro-code[data-language="typescript"]::after { content: "ts"; }
|
179 |
+
.astro-code[data-language="tsx"]::after { content: "tsx"; }
|
180 |
+
.astro-code[data-language="javascript"]::after,
|
181 |
+
.astro-code[data-language="node"]::after,
|
182 |
+
.astro-code[data-language="jsx"]::after { content: "js"; }
|
183 |
+
.astro-code[data-language="python"]::after { content: "py"; }
|
184 |
+
.astro-code[data-language="bash"]::after,
|
185 |
+
.astro-code[data-language="shell"]::after,
|
186 |
+
.astro-code[data-language="sh"]::after { content: "sh"; }
|
187 |
+
.astro-code[data-language="markdown"]::after { content: "md"; }
|
188 |
+
.astro-code[data-language="yaml"]::after,
|
189 |
+
.astro-code[data-language="yml"]::after { content: "yml"; }
|
190 |
+
.astro-code[data-language="html"]::after { content: "html"; }
|
191 |
+
.astro-code[data-language="css"]::after { content: "css"; }
|
192 |
+
.astro-code[data-language="json"]::after { content: "json"; } */
|
193 |
+
|
194 |
/* In Accordions, keep same bottom-right placement */
|
195 |
.accordion .astro-code::after { right: 0; bottom: 0; }
|
app/src/styles/components/_table.css
CHANGED
@@ -77,7 +77,10 @@
|
|
77 |
border: none;
|
78 |
border-radius: 0;
|
79 |
margin: 0;
|
|
|
80 |
}
|
|
|
|
|
81 |
.accordion .accordion__content .table-scroll > table thead th:first-child,
|
82 |
.accordion .accordion__content .table-scroll > table thead th:last-child,
|
83 |
.accordion .accordion__content .table-scroll > table tbody tr:last-child td:first-child,
|
|
|
77 |
border: none;
|
78 |
border-radius: 0;
|
79 |
margin: 0;
|
80 |
+
margin-bottom: 0 !important;
|
81 |
}
|
82 |
+
/* Ensure no bottom margin even if table isn't wrapped (fallback) */
|
83 |
+
.accordion .accordion__content table { margin: 0 !important; }
|
84 |
.accordion .accordion__content .table-scroll > table thead th:first-child,
|
85 |
.accordion .accordion__content .table-scroll > table thead th:last-child,
|
86 |
.accordion .accordion__content .table-scroll > table tbody tr:last-child td:first-child,
|
app/src/styles/components/_tag.css
CHANGED
@@ -4,10 +4,10 @@
|
|
4 |
display: inline-flex;
|
5 |
align-items: center;
|
6 |
gap: 6px;
|
7 |
-
padding:
|
8 |
font-size: 12px;
|
9 |
line-height: 1;
|
10 |
-
border-radius:
|
11 |
background: var(--surface-bg);
|
12 |
border: 1px solid var(--border-color);
|
13 |
color: var(--text-color);
|
|
|
4 |
display: inline-flex;
|
5 |
align-items: center;
|
6 |
gap: 6px;
|
7 |
+
padding: 8px 12px;
|
8 |
font-size: 12px;
|
9 |
line-height: 1;
|
10 |
+
border-radius: var(--button-radius);
|
11 |
background: var(--surface-bg);
|
12 |
border: 1px solid var(--border-color);
|
13 |
color: var(--text-color);
|