thibaud frere commited on
Commit
b9e7b9b
·
1 Parent(s): 98af9a5
Files changed (36) hide show
  1. .gitignore +2 -0
  2. README.md +10 -2
  3. app/.astro/astro/content.d.ts +17 -1
  4. app/astro.config.mjs +5 -4
  5. app/package-lock.json +0 -0
  6. app/package.json +0 -0
  7. app/scripts/export-pdf.mjs +78 -0
  8. app/src/assets/images/{placeholder.jpg → placeholder.png} +2 -2
  9. app/src/components/MermaidDemo.astro +11 -0
  10. app/src/components/Meta.astro +14 -3
  11. app/src/components/Note.astro +23 -0
  12. app/src/content/article.mdx +50 -330
  13. app/src/content/chapters/best-pratices.mdx +46 -0
  14. app/src/content/chapters/writing-you-content.mdx +370 -0
  15. app/src/content/fragments/banner.html +2 -11
  16. app/src/content/fragments/bar.html +1 -22
  17. app/src/content/fragments/color-picker.html +213 -0
  18. app/src/content/fragments/d3-bar.html +200 -0
  19. app/src/content/fragments/d3-line.html +429 -0
  20. app/src/content/fragments/heatmap.html +159 -1
  21. app/src/content/fragments/line.html +84 -1
  22. app/src/content/fragments/palettes.html +40 -7
  23. app/src/pages/index.astro +1 -1
  24. app/src/styles/{_base.scss → _base.css} +59 -61
  25. app/src/styles/{_layout.scss → _layout.css} +24 -22
  26. app/src/styles/{_variables.scss → _variables.css} +22 -13
  27. app/src/styles/components/{_code.scss → _code.css} +41 -5
  28. app/src/styles/components/{_footer.scss → _footer.css} +5 -3
  29. app/src/styles/components/_poltly.css +45 -0
  30. app/src/styles/{global.scss → global.css} +16 -33
  31. fragments/d3js/banner.html +34 -30
  32. fragments/d3js/line.html +356 -0
  33. fragments/plotly/banner.py +1 -1
  34. fragments/plotly/bar.py +147 -90
  35. fragments/plotly/heatmap.py +1 -1
  36. fragments/plotly/line.py +82 -54
.gitignore CHANGED
@@ -21,3 +21,5 @@ node_modules/
21
 
22
  # PDF export
23
  app/public/*.pdf
 
 
 
21
 
22
  # PDF export
23
  app/public/*.pdf
24
+ app/public/*.png
25
+ app/public/*.jpg
README.md CHANGED
@@ -7,5 +7,13 @@ sdk: docker
7
  pinned: false
8
  header: mini
9
  app_port: 8080
10
- thumbnail: https://huggingface.co/spaces/HuggingFaceFW/blogpost-fine-tasks/resolve/main/app/assets/images/banner.png
11
- ---
 
 
 
 
 
 
 
 
 
7
  pinned: false
8
  header: mini
9
  app_port: 8080
10
+ thumbnail: https://huggingface.co/spaces/tfrere/research-paper-template/thumb.jpg
11
+ ---
12
+
13
+ TO DO :
14
+
15
+ - fix banner pour l'export pdf et la thumb
16
+ - rename le titre ?
17
+ - Vérifier la biliographie comment elle marche
18
+
19
+ - deploy
app/.astro/astro/content.d.ts CHANGED
@@ -151,7 +151,23 @@ declare module 'astro:content' {
151
  >;
152
 
153
  type ContentEntryMap = {
154
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  };
156
 
157
  type DataEntryMap = {
 
151
  >;
152
 
153
  type ContentEntryMap = {
154
+ "chapters": {
155
+ "best-pratices.mdx": {
156
+ id: "best-pratices.mdx";
157
+ slug: "best-pratices";
158
+ body: string;
159
+ collection: "chapters";
160
+ data: any
161
+ } & { render(): Render[".mdx"] };
162
+ "writing-you-content.mdx": {
163
+ id: "writing-you-content.mdx";
164
+ slug: "writing-you-content";
165
+ body: string;
166
+ collection: "chapters";
167
+ data: any
168
+ } & { render(): Render[".mdx"] };
169
+ };
170
+
171
  };
172
 
173
  type DataEntryMap = {
app/astro.config.mjs CHANGED
@@ -1,5 +1,6 @@
1
  import { defineConfig } from 'astro/config';
2
  import mdx from '@astrojs/mdx';
 
3
  import remarkMath from 'remark-math';
4
  import rehypeKatex from 'rehype-katex';
5
  import remarkToc from 'remark-toc';
@@ -11,8 +12,7 @@ import rehypeCitation from 'rehype-citation';
11
 
12
  export default defineConfig({
13
  output: 'static',
14
- integrations: [mdx()]
15
- ,
16
  devToolbar: {
17
  enabled: false
18
  },
@@ -23,9 +23,10 @@ export default defineConfig({
23
  dark: 'github-dark'
24
  },
25
  defaultColor: false,
26
- wrap: true,
27
  langAlias: {
28
- mdx: 'js'
 
29
  }
30
  },
31
  remarkPlugins: [
 
1
  import { defineConfig } from 'astro/config';
2
  import mdx from '@astrojs/mdx';
3
+ import mermaid from 'astro-mermaid';
4
  import remarkMath from 'remark-math';
5
  import rehypeKatex from 'rehype-katex';
6
  import remarkToc from 'remark-toc';
 
12
 
13
  export default defineConfig({
14
  output: 'static',
15
+ integrations: [mermaid({ theme: 'forest', autoTheme: true }), mdx()],
 
16
  devToolbar: {
17
  enabled: false
18
  },
 
23
  dark: 'github-dark'
24
  },
25
  defaultColor: false,
26
+ wrap: false,
27
  langAlias: {
28
+ // Map MDX fences to TSX for better JSX tokenization
29
+ mdx: 'tsx'
30
  }
31
  },
32
  remarkPlugins: [
app/package-lock.json CHANGED
Binary files a/app/package-lock.json and b/app/package-lock.json differ
 
app/package.json CHANGED
Binary files a/app/package.json and b/app/package.json differ
 
app/scripts/export-pdf.mjs CHANGED
@@ -120,6 +120,29 @@ async function waitForPlotly(page, timeoutMs = 20000) {
120
  }, timeoutMs);
121
  }
122
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  async function waitForStableLayout(page, timeoutMs = 5000) {
124
  const start = Date.now();
125
  let last = await page.evaluate(() => document.scrollingElement ? document.scrollingElement.scrollHeight : document.body.scrollHeight);
@@ -195,6 +218,9 @@ async function main() {
195
  if (wait === 'images' || wait === 'full') {
196
  await waitForImages(page);
197
  }
 
 
 
198
  if (wait === 'plotly' || wait === 'full') {
199
  await waitForPlotly(page);
200
  }
@@ -202,13 +228,65 @@ async function main() {
202
  await waitForStableLayout(page);
203
  }
204
  await page.emulateMedia({ media: 'print' });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
  const outPath = resolve(cwd, 'dist', `${outFileBase}.pdf`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  await page.pdf({
207
  path: outPath,
208
  format,
209
  printBackground: true,
210
  margin
211
  });
 
212
  console.log(`✅ PDF generated: ${outPath}`);
213
 
214
  // Copy into public only under the slugified name
 
120
  }, timeoutMs);
121
  }
122
 
123
+ async function waitForD3(page, timeoutMs = 20000) {
124
+ await page.evaluate(async (timeout) => {
125
+ const start = Date.now();
126
+ const isReady = () => {
127
+ // Prioritize hero banner if present
128
+ const hero = document.querySelector('.hero .d3-galaxy') || document.querySelector('.d3-galaxy');
129
+ if (hero) {
130
+ return !!hero.querySelector('svg circle, svg path, svg rect, svg g');
131
+ }
132
+ // Else require all D3 containers on page to have shapes
133
+ const containers = [
134
+ ...Array.from(document.querySelectorAll('.d3-line')),
135
+ ...Array.from(document.querySelectorAll('.d3-bar'))
136
+ ];
137
+ if (!containers.length) return true;
138
+ return containers.every(c => c.querySelector('svg circle, svg path, svg rect, svg g'));
139
+ };
140
+ while (!isReady() && (Date.now() - start) < timeout) {
141
+ await new Promise(r => setTimeout(r, 200));
142
+ }
143
+ }, timeoutMs);
144
+ }
145
+
146
  async function waitForStableLayout(page, timeoutMs = 5000) {
147
  const start = Date.now();
148
  let last = await page.evaluate(() => document.scrollingElement ? document.scrollingElement.scrollHeight : document.body.scrollHeight);
 
218
  if (wait === 'images' || wait === 'full') {
219
  await waitForImages(page);
220
  }
221
+ if (wait === 'd3' || wait === 'full') {
222
+ await waitForD3(page);
223
+ }
224
  if (wait === 'plotly' || wait === 'full') {
225
  await waitForPlotly(page);
226
  }
 
228
  await waitForStableLayout(page);
229
  }
230
  await page.emulateMedia({ media: 'print' });
231
+
232
+ // Generate OG thumbnail (1200x630)
233
+ try {
234
+ const ogW = 1200, ogH = 630;
235
+ await page.setViewportSize({ width: ogW, height: ogH });
236
+ // Give layout a tick to adjust
237
+ await page.waitForTimeout(200);
238
+ // Ensure layout & D3 re-rendered after viewport change
239
+ await page.evaluate(() => { window.scrollTo(0, 0); window.dispatchEvent(new Event('resize')); });
240
+ try { await waitForD3(page, 8000); } catch {}
241
+
242
+ // Temporarily improve visibility for light theme thumbnails
243
+ // - Force normal blend for points
244
+ // - Ensure an SVG background (CSS background on svg element)
245
+ const cssHandle = await page.addStyleTag({ content: `
246
+ .hero .points { mix-blend-mode: normal !important; }
247
+ .d3-galaxy svg { background: var(--surface-bg); }
248
+ ` });
249
+ const thumbPath = resolve(cwd, 'dist', 'thumb.jpg');
250
+ await page.screenshot({ path: thumbPath, type: 'jpeg', quality: 85, fullPage: false });
251
+ // Also emit PNG for compatibility if needed
252
+ const thumbPngPath = resolve(cwd, 'dist', 'thumb.png');
253
+ await page.screenshot({ path: thumbPngPath, type: 'png', fullPage: false });
254
+ const publicThumb = resolve(cwd, 'public', 'thumb.jpg');
255
+ const publicThumbPng = resolve(cwd, 'public', 'thumb.png');
256
+ try { await fs.copyFile(thumbPath, publicThumb); } catch {}
257
+ try { await fs.copyFile(thumbPngPath, publicThumbPng); } catch {}
258
+ // Remove temporary style so PDF is unaffected
259
+ try { await cssHandle.evaluate((el) => el.remove()); } catch {}
260
+ console.log(`✅ OG thumbnail generated: ${thumbPath}`);
261
+ } catch (e) {
262
+ console.warn('Unable to generate OG thumbnail:', e?.message || e);
263
+ }
264
  const outPath = resolve(cwd, 'dist', `${outFileBase}.pdf`);
265
+ // Restore viewport to printable width before PDF (thumbnail changed it)
266
+ try {
267
+ const fmt2 = getFormatSizeMm(format);
268
+ const mw2 = fmt2.w - cssLengthToMm(margin.left) - cssLengthToMm(margin.right);
269
+ const printableWidthPx2 = Math.max(320, Math.round((mw2 / 25.4) * 96));
270
+ await page.setViewportSize({ width: printableWidthPx2, height: 1400 });
271
+ await page.evaluate(() => { window.scrollTo(0, 0); window.dispatchEvent(new Event('resize')); });
272
+ try { await waitForD3(page, 8000); } catch {}
273
+ await waitForStableLayout(page);
274
+ } catch {}
275
+ // Temporarily make D3 banner reliably visible for PDF
276
+ let pdfCssHandle = null;
277
+ try {
278
+ pdfCssHandle = await page.addStyleTag({ content: `
279
+ .hero .points { mix-blend-mode: normal !important; }
280
+ .d3-galaxy svg { background: var(--surface-bg); }
281
+ ` });
282
+ } catch {}
283
  await page.pdf({
284
  path: outPath,
285
  format,
286
  printBackground: true,
287
  margin
288
  });
289
+ try { if (pdfCssHandle) await pdfCssHandle.evaluate((el) => el.remove()); } catch {}
290
  console.log(`✅ PDF generated: ${outPath}`);
291
 
292
  // Copy into public only under the slugified name
app/src/assets/images/{placeholder.jpg → placeholder.png} RENAMED
File without changes
app/src/components/MermaidDemo.astro ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ export interface Props {
3
+ code?: string;
4
+ }
5
+ const { code = `graph TD\n A[Start] --> B{Is it working?}\n B -- Yes --> C[Great!]\n B -- No --> D[Fix it]\n D --> B` } = Astro.props;
6
+ ---
7
+
8
+ <pre class="mermaid">{code}</pre>
9
+
10
+
11
+ <style>.mermaid { max-width: 100%; }</style>
app/src/components/Meta.astro CHANGED
@@ -6,6 +6,16 @@ interface Props {
6
  published?: string;
7
  }
8
  const { title, authors = [], affiliation, published } = Astro.props as Props;
 
 
 
 
 
 
 
 
 
 
9
  ---
10
  <header class="meta">
11
  <div class="meta-container">
@@ -29,7 +39,7 @@ const { title, authors = [], affiliation, published } = Astro.props as Props;
29
  )}
30
  <div class="meta-container-cell">
31
  <h3>PDF</h3>
32
- <p><button id="download-pdf-btn" type="button">Download PDF</button></p>
33
  </div>
34
  </div>
35
  </header>
@@ -42,8 +52,9 @@ const { title, authors = [], affiliation, published } = Astro.props as Props;
42
  if (!btn) return;
43
  btn.addEventListener('click', () => {
44
  const a = document.createElement('a');
45
- a.href = '/article.pdf';
46
- a.setAttribute('download', 'article.pdf');
 
47
  document.body.appendChild(a);
48
  a.click();
49
  a.remove();
 
6
  published?: string;
7
  }
8
  const { title, authors = [], affiliation, published } = Astro.props as Props;
9
+ function slugify(text: string): string {
10
+ return String(text || '')
11
+ .normalize('NFKD')
12
+ .replace(/\p{Diacritic}+/gu, '')
13
+ .toLowerCase()
14
+ .replace(/[^a-z0-9]+/g, '-')
15
+ .replace(/^-+|-+$/g, '')
16
+ .slice(0, 120) || 'article';
17
+ }
18
+ const pdfFilename = `${slugify(title)}.pdf`;
19
  ---
20
  <header class="meta">
21
  <div class="meta-container">
 
39
  )}
40
  <div class="meta-container-cell">
41
  <h3>PDF</h3>
42
+ <p><button id="download-pdf-btn" data-pdf-filename={pdfFilename}>Download PDF</button></p>
43
  </div>
44
  </div>
45
  </header>
 
52
  if (!btn) return;
53
  btn.addEventListener('click', () => {
54
  const a = document.createElement('a');
55
+ const pdf = btn.getAttribute('data-pdf-filename') || 'article.pdf';
56
+ a.href = `/${pdf}`;
57
+ a.setAttribute('download', pdf);
58
  document.body.appendChild(a);
59
  a.click();
60
  a.remove();
app/src/components/Note.astro ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ const { title = "Note", emoji = "📝", class: className, ...props } = Astro.props;
3
+ const wrapperClass = ["note", className].filter(Boolean).join(" ");
4
+ ---
5
+ <div class={wrapperClass} {...props}>
6
+ <div class="note__header">
7
+ <span class="note__emoji">{emoji}</span>
8
+ {title && <span class="note__title">{title}</span>}
9
+ </div>
10
+ <div class="note__content">
11
+ <slot />
12
+ </div>
13
+ </div>
14
+
15
+ <style>
16
+ .note { background: var(--surface-bg); border-left: 2px solid rgba(0, 0, 0, 0.1); border-radius: 4px; padding: 10px 14px; margin: 12px 0; }
17
+ .note__header { display: flex; align-items: center; gap: 6px; font-weight: 600; color: var(--text-color); margin-bottom: 6px; }
18
+ .note__emoji { font-size: 24px; line-height: 1; }
19
+ .note__title { font-size: 13px; letter-spacing: .2px; }
20
+ .note__content { color: var(--text-color); font-size: 0.95rem; }
21
+ </style>
22
+
23
+
app/src/content/article.mdx CHANGED
@@ -12,17 +12,19 @@ published: "Feb 19, 2025"
12
  tags:
13
  - research
14
  - template
15
- ogImage: "https://example.com/your-og-image.png"
16
  ---
17
 
18
  import HtmlFragment from "../components/HtmlFragment.astro";
19
  import Wide from "../components/Wide.astro";
20
  import FullBleed from "../components/FullBleed.astro";
21
  import { Image } from 'astro:assets';
22
- import placeholder from "../assets/images/placeholder.jpg";
23
  import audioDemo from "../assets/audio/audio-example.wav";
24
  import Aside from "../components/Aside.astro";
25
  import visualPoster from "../assets/images/visual-vocabulary-poster.png";
 
 
26
 
27
  <Aside>
28
  Welcome to this single-page research article template built with **Markdown**.
@@ -34,15 +36,13 @@ import visualPoster from "../assets/images/visual-vocabulary-poster.png";
34
  In this guide, you’ll learn how to install the template,
35
  write content (math, citations, images, code, asides, interactive fragments),
36
  customize styles and behavior, and follow a few **best practices** for publishing.
37
- <Fragment slot="aside">
38
- If you have questions or remarks open a discussion on the <a href="https://huggingface.co/spaces/tfrere/science-blog-template/discussions?status=open&type=discussion">Community tab</a>!
39
- </Fragment>
40
  </Aside>
41
 
42
- This template is inspired by [**Distill**](https://distill.pub); we aim to preserve the best of it while modernizing the stack. Their work is highly inspiring.
43
 
44
  #### Features
45
 
 
46
  <div className="tag-list">
47
  <span className="tag">Markdown based</span>
48
  <span className="tag">KaTeX math</span>
@@ -55,24 +55,64 @@ This template is inspired by [**Distill**](https://distill.pub); we aim to prese
55
  <span className="tag">Plotly ready</span>
56
  <span className="tag">D3.js ready</span>
57
  <span className="tag">SEO Friendly</span>
 
58
  <span className="tag">Lightweight bundle</span>
59
  <span className="tag">Aside notes</span>
60
  <span className="tag">Mobile friendly</span>
61
  <span className="tag">Optimized images</span>
62
  <span className="tag">Medium like zoomable images</span>
63
  <span className="tag">PDF export</span>
 
 
64
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
  ## Getting Started
67
 
68
  ### Installation
69
 
 
70
  ```bash
71
  git lfs install
72
  git lfs pull
73
  cd app
74
  npm install
75
  ```
 
 
 
 
76
 
77
 
78
  ### Development
@@ -89,6 +129,8 @@ npm run build
89
 
90
  Serving the `dist/` directory on any static host is enough to deliver the site.
91
 
 
 
92
  ### Deploy
93
 
94
  The easiest way to get online is to clone [this Hugging Face Space](https://huggingface.co/spaces/tfrere/science-blog-template) and push your changes; every push triggers an automatic build and deploy.
@@ -98,331 +140,9 @@ The easiest way to get online is to clone [this Hugging Face Space](https://hugg
98
  **Track binaries** (e.g., `.png`, `.wav`) with **Git LFS** to keep the repository lean. This project is preconfigured to store such files via **LFS**.
99
 
100
 
101
- ## Writing Your Content
102
-
103
- ### Introduction
104
-
105
- Your article lives in two places:
106
-
107
- - `app/src/content/` — where you can find the article.mdx, bibliography.bib and html fragments.
108
- - `app/src/assets/` — images, audio, and other static assets. (handled by git lfs)
109
-
110
- This is MDX, its basically a markdown file with html and astro components.
111
- The **initial skeleton** of an article looks like this.
112
-
113
- ```mdx
114
- {/* HEADER */}
115
- ---
116
- title: "This is the main title"
117
- subtitle: "This will be displayed just below the banner"
118
- description: "A modern, MDX-first research article template with math, citations, and interactive figures."
119
- authors:
120
- - "John Doe"
121
- - "Alice Martin"
122
- - "Robert Brown"
123
- affiliation: "Hugging Face"
124
- published: "Feb 19, 2025"
125
- tags:
126
- - research
127
- - template
128
- ogImage: "https://example.com/your-og-image.png"
129
- ---
130
-
131
- {/* IMPORTS */}
132
- import { Image } from 'astro:assets';
133
- import placeholder from '../assets/images/placeholder.jpg';
134
-
135
- {/* CONTENT */}
136
- # Hello, world
137
-
138
- This is a short paragraph written in Markdown. Below is an example image:
139
-
140
- <Image src={placeholder} alt="Example image" />
141
- ```
142
-
143
-
144
-
145
- **Available blocks**:
146
-
147
- <div className="tag-list">
148
- <a className="tag" href="#math">Math</a>
149
- <a className="tag" href="#images">Images</a>
150
- <a className="tag" href="#code-blocks">Code</a>
151
- <a className="tag" href="#citations-and-notes">Citations & notes</a>
152
- <a className="tag" href="#asides">Asides</a>
153
- <a className="tag" href="#minimal-table">Array</a>
154
- <a className="tag" href="#audio">Audio</a>
155
- <a className="tag" href="#interactive-fragments">Interactive fragments</a>
156
- <a className="tag" href="#tracking-training-metrics-with-trackio">TrackIO</a>
157
- </div>
158
-
159
- ### Math
160
-
161
- **Inline**
162
-
163
- $x^2 + y^2 = z^2$.
164
-
165
- ```mdx
166
- $x^2 + y^2 = z^2$
167
- ```
168
-
169
- **Block**
170
-
171
- $$
172
- \mathrm{Attention}(Q,K,V)=\mathrm{softmax}\!\left(\frac{QK^\top}{\sqrt{d_k}}\right) V
173
- $$
174
-
175
- ```mdx
176
- $$
177
- \mathrm{Attention}(Q,K,V)=\mathrm{softmax}\!\left(\frac{QK^\top}{\sqrt{d_k}}\right) V
178
- $$
179
- ```
180
-
181
- ### Images
182
-
183
- **Responsive images** automatically generate an optimized `srcset` and `sizes` so the browser downloads the most appropriate file for the current viewport and DPR. You can also request multiple output formats (e.g., **AVIF**, **WebP**, fallback **PNG/JPEG**) and control **lazy loading/decoding** for better **performance**.
184
-
185
- **Optional:** Zoomable (Medium-like lightbox): add `data-zoomable` to opt-in. Only images with this attribute will open full-screen on click.
186
-
187
- **Optional:** Lazy loading: add `loading="lazy"` to opt-in.
188
-
189
- **Optional:** Figcaption and credits: add a `figcaption` element with a `span` containing the credit.
190
-
191
-
192
-
193
- <figure>
194
- <Image
195
- src={placeholder}
196
- data-zoomable
197
- alt="Example with caption and credit"
198
- />
199
- <figcaption>
200
- Optimized image with a descriptive caption.
201
- <span className="image-credit">Credit: Photo by <a href="https://example.com">Author</a></span>
202
- </figcaption>
203
- </figure>
204
-
205
- ```mdx
206
- import { Image } from 'astro:assets'
207
- import myImage from '../assets/images/placeholder.jpg'
208
-
209
- <Image src={myImage} alt="Responsive, optimized example image" />
210
-
211
- <figure>
212
- <Image src={myImage} data-zoomable alt="Example with caption and credit" loading="lazy" />
213
- <figcaption>
214
- Optimized image with a descriptive caption.
215
- <span className="image-credit">Credit: Photo by <a href="https://example.com">Author</a></span>
216
- </figcaption>
217
- </figure>
218
- ```
219
-
220
-
221
- ### Code blocks
222
-
223
- Use fenced code blocks with a language for syntax highlighting.
224
-
225
- Python block example:
226
-
227
- ```python
228
- def greet(name: str) -> None:
229
- print(f"Hello, {name}!")
230
-
231
- greet("Astro")
232
- ```
233
-
234
- How to write it in MDX:
235
-
236
- ````mdx
237
- ```python
238
- def greet(name: str) -> None:
239
- print(f"Hello, {name}!")
240
-
241
- greet("Astro")
242
- ```
243
- ````
244
-
245
- ### Citations and notes
246
-
247
- Here are a few variations using the same bibliography:
248
-
249
- 1) **In-text citation** with brackets: [@example2023].
250
-
251
- 2) **Narrative citation**: As shown by @vaswani2017attention, transformers enable efficient sequence modeling.
252
-
253
- 3) **Multiple citations** and a **footnote** together: see [@vaswani2017attention; @example2023] for related work. Also note this footnote[^f1].
254
-
255
- [^f1]: Footnote attached to the sentence above.
256
-
257
- ```mdx
258
- 1) In-text citation with brackets: [@example2023].
259
-
260
- 2) Narrative citation: As shown by @vaswani2017attention, transformers enable efficient sequence modeling.
261
-
262
- 3) Multiple citations and a footnote together: see [@vaswani2017attention; @example2023] for related work. Also note this footnote[^f1].
263
-
264
- [^f1]: Footnote attached to the sentence above.
265
- ```
266
-
267
-
268
- ### Asides
269
-
270
- <Aside>
271
- This paragraph presents a **key idea** concisely.
272
- <Fragment slot="aside">
273
- **Side note** for brief context or a definition.
274
- </Fragment>
275
- </Aside>
276
-
277
- ```mdx
278
- import Aside from '../components/Aside.astro'
279
-
280
- <Aside>
281
- Main paragraph with the core idea.
282
- <Fragment slot="aside">Short side note.</Fragment>
283
- </Aside>
284
- ```
285
-
286
- ### Width helpers
287
-
288
- Use these helpers to expand content beyond the main column when needed. They will always be centered and displayed above every other content.
289
-
290
- #### Wide example
291
-
292
- <Wide>
293
- <div className="demo-wide">demo wide</div>
294
- </Wide>
295
-
296
- ```mdx
297
- import Wide from '../components/Wide.astro'
298
-
299
- <Wide>
300
- Your content here...
301
- </Wide>
302
- ```
303
-
304
- #### Full-bleed example
305
-
306
- <FullBleed>
307
- <div className="demo-full-bleed">demo full-bleed</div>
308
- </FullBleed>
309
-
310
- ```mdx
311
- import FullBleed from '../components/FullBleed.astro'
312
-
313
- <FullBleed>
314
- Your content here...
315
- </FullBleed>
316
- ```
317
-
318
-
319
- ### Minimal table
320
-
321
- | Method | Score |
322
- |---|---|
323
- | A | 0.78 |
324
- | B | 0.86 |
325
-
326
- ```mdx
327
- | Method | Score |
328
- | --- | --- |
329
- | A | 0.78 |
330
- | B | 0.86 |
331
- ```
332
-
333
- ### Audio
334
-
335
- <audio controls src={audioDemo}>
336
- Your browser does not support the audio element.
337
- </audio>
338
-
339
- ```mdx
340
- import audioDemo from '../assets/audio/audio-example.wav'
341
-
342
- <audio controls src={audioDemo}>
343
- Your browser does not support the audio element.
344
- </audio>
345
- ```
346
-
347
-
348
-
349
- ### Embeds
350
-
351
-
352
- #### Html Fragments
353
-
354
- The main purpose of the ```HtmlFragment``` component is to embed a **Plotly** or **D3.js** chart in your article. Libraries are already imported in the template.
355
-
356
- <div className="plot-card">
357
- <HtmlFragment src="line.html" />
358
- </div>
359
-
360
- ```mdx
361
- import HtmlFragment from '../components/HtmlFragment.astro'
362
-
363
- <HtmlFragment src="line.html" />
364
- ```
365
-
366
- #### Iframes
367
-
368
- You can embed external content in your article using **iframes**. For example, **TrackIO**—a lightweight dashboard to monitor machine learning experiments—can be used this way. The example below opens a demo project and displays a couple of metrics. You can customize the **query parameters** (`project`, `metrics`, `sidebar`, etc.) to fit your needs.
369
-
370
- <iframe className="plot-card" src="https://trackio-documentation.hf.space/?project=fake-training-750735&metrics=train_loss,train_accuracy&sidebar=hidden&lang=en" width="100%" height="600" frameborder="0"></iframe>
371
-
372
- To embed TrackIO in your own page, copy the following HTML and adjust the `src` parameters:
373
-
374
- #### GitHub code embeds
375
-
376
- Finally, if you want to include code from GitHub you can use emgithub.com and, for example, create a collapsible widget like this:
377
-
378
-
379
- <div class="code-embed-container"></div>
380
- <script
381
- src="https://emgithub.com/embed-v2.js?target=https%3A%2F%2Fgithub.com%2Fhuggingface%2Fpicotron%2Fblob%2F0035cce0e04afd6192763b11efe50010d8ad0f71%2Fpicotron%2Fdata_parallel%2Fdata_parallel.py%23L10-L60&style=github&type=code&showBorder=off&showLineNumbers=on&showFileMeta=on&showCopy=on&showFullPath=on">
382
- </script>
383
-
384
- ```html
385
- <div class="code-embed-container"></div>
386
- <script
387
- src="https://emgithub.com/embed-v2.js?target=https%3A%2F%2Fgithub.com%2Fhuggingface%2Fpicotron%2Fblob%2F0035cce0e04afd6192763b11efe50010d8ad0f71%2Fpicotron%2Fdata_parallel%2Fdata_parallel.py%23L10-L60&style=github&type=code&showBorder=off&showLineNumbers=on&showFileMeta=on&showCopy=on&showFullPath=on">
388
- </script>
389
- ```
390
-
391
-
392
- ## Best Practices
393
-
394
- ### Short sections
395
- Break content into **small, purpose‑driven sections**. Each section should answer a **single question** or support one idea. This improves **scanability**, helps readers navigate with the TOC, and makes later edits safer.
396
-
397
- ### Clear, minimal annotations
398
- Favor **concise captions** and callouts that clarify what to look at and why it matters. In code, **highlight just the lines** that carry the idea; avoid verbose commentary. **Precision beats volume**.
399
-
400
- ### Explain math notation
401
- **Introduce symbols and variables** the first time they appear, and prefer **well‑known identities** over custom shorthand. When formulas carry the message, add one sentence of **plain‑language interpretation** right after.
402
-
403
- ### Use the right color scale
404
-
405
- A **palette** encodes **meaning** (categories, magnitudes, oppositions), preserves **readability** and **accessibility** (**sufficient contrast**, **color‑vision safety**), and ensures **perceptually smooth transitions**.
406
-
407
- The three families below illustrate when to use **categorical**, **sequential**, or **diverging** colors and how they evolve from the same **reference hue**.
408
-
409
- <div className="">
410
- <HtmlFragment src="palettes.html" />
411
- </div>
412
-
413
- ### Use the right chart
414
-
415
- Picking the right visualization depends on your goal (compare values, show distribution, part-to-whole, trends, relationships, etc.). The Visual Vocabulary poster below provides a concise mapping from **analytical task** to **chart types**.
416
 
417
- <figure>
418
- <a href={visualPoster.src} target="_blank" rel="noopener noreferrer">
419
- <Image src={visualPoster} alt="Visual Vocabulary: choosing the right chart by task" />
420
- </a>
421
- <figcaption>
422
- A handy reference to select chart types by purpose. Click to enlarge.
423
- — <a href="https://ft-interactive.github.io/visual-vocabulary/" target="_blank" rel="noopener noreferrer">Website</a>
424
- </figcaption>
425
- </figure>
426
 
427
 
428
  ## Conclusions
 
12
  tags:
13
  - research
14
  - template
15
+ ogImage: "/thumb.jpg"
16
  ---
17
 
18
  import HtmlFragment from "../components/HtmlFragment.astro";
19
  import Wide from "../components/Wide.astro";
20
  import FullBleed from "../components/FullBleed.astro";
21
  import { Image } from 'astro:assets';
22
+ import placeholder from "../assets/images/placeholder.png";
23
  import audioDemo from "../assets/audio/audio-example.wav";
24
  import Aside from "../components/Aside.astro";
25
  import visualPoster from "../assets/images/visual-vocabulary-poster.png";
26
+ import BestPractices from "./chapters/best-pratices.mdx";
27
+ import WritingYourContent from "./chapters/writing-you-content.mdx";
28
 
29
  <Aside>
30
  Welcome to this single-page research article template built with **Markdown**.
 
36
  In this guide, you’ll learn how to install the template,
37
  write content (math, citations, images, code, asides, interactive fragments),
38
  customize styles and behavior, and follow a few **best practices** for publishing.
39
+
 
 
40
  </Aside>
41
 
 
42
 
43
  #### Features
44
 
45
+ <Aside>
46
  <div className="tag-list">
47
  <span className="tag">Markdown based</span>
48
  <span className="tag">KaTeX math</span>
 
55
  <span className="tag">Plotly ready</span>
56
  <span className="tag">D3.js ready</span>
57
  <span className="tag">SEO Friendly</span>
58
+ <span className="tag">Mermaid diagrams</span>
59
  <span className="tag">Lightweight bundle</span>
60
  <span className="tag">Aside notes</span>
61
  <span className="tag">Mobile friendly</span>
62
  <span className="tag">Optimized images</span>
63
  <span className="tag">Medium like zoomable images</span>
64
  <span className="tag">PDF export</span>
65
+ <span className="tag">To do: Accordion</span>
66
+ <span className="tag">To do: dataviz color palette generator</span>
67
  </div>
68
+ <Fragment slot="aside">
69
+ If you have questions or remarks open a discussion on the <a href="https://huggingface.co/spaces/tfrere/research-blog-template/discussions?status=open&type=discussion">Community tab</a>!
70
+ </Fragment>
71
+ </Aside>
72
+
73
+ ## Introduction
74
+ A modern medium for presenting research
75
+ The web enables forms of explanation that static PDFs cannot: **reactive diagrams**, progressive notation, and **exploratory views** that reveal how ideas behave. Use **interactive fragments** to let readers hover, scrub, and inspect—building **intuition**, not just reading results.
76
+
77
+ New ways of thinking enable new discoveries
78
+ Careful notation, **well‑chosen visual encodings**, and **small interactive experiments** deepen understanding. By making these artifacts **first‑class**—alongside text, math, and code—this template helps your audience grasp mechanisms, limits, and trade‑offs.
79
+
80
+ Machine learning needs more transparency
81
+ Clear, **inspectable examples** make methods safer and more comprehensible. Embed live widgets, reveal **intermediate states**, and link to sources so readers can verify claims and **reproduce results**.
82
+
83
+ Legitimacy for non‑traditional research artifacts
84
+ Not every contribution fits a PDF. Treat demos, visualizations, and interactive write‑ups as **real scholarship**: cite them, version them, and ship them together.
85
+
86
+ This project is heavely inspired by [**Distill**](https://distill.pub) (2016–2021), which championed clear, web‑native scholarship.
87
+
88
+
89
+ ### Notable examples of excellent scientific articles
90
+
91
+ A short, curated list of well‑designed and often interactive work:
92
+
93
+ - **Distill — The Building Blocks of Interpretability**: [distill.pub/2018/building-blocks](https://distill.pub/2018/building-blocks/)
94
+ - **R2D3 — A Visual Introduction to Machine Learning (Part 1)**: [r2d3.us/visual-intro-to-machine-learning-part-1](http://www.r2d3.us/visual-intro-to-machine-learning-part-1/)
95
+ - **Seeing Theory — An interactive introduction to probability and statistics**: [seeing-theory.brown.edu](https://seeing-theory.brown.edu/)
96
+ - **ConvNetJS — Neural networks in the browser**: [cs.stanford.edu/people/karpathy/convnetjs](http://cs.stanford.edu/people/karpathy/convnetjs/)
97
+ - **Explorable Explanations — Collection**: [explorableexplanations.com](http://explorableexplanations.com/)
98
+ - **Distill — Why Momentum Really Works**: [distill.pub/2017/momentum](https://distill.pub/2017/momentum/)
99
+
100
 
101
  ## Getting Started
102
 
103
  ### Installation
104
 
105
+ <Aside>
106
  ```bash
107
  git lfs install
108
  git lfs pull
109
  cd app
110
  npm install
111
  ```
112
+ <Fragment slot="aside">
113
+ You can use yarn alternatively to npm.
114
+ </Fragment>
115
+ </Aside>
116
 
117
 
118
  ### Development
 
129
 
130
  Serving the `dist/` directory on any static host is enough to deliver the site.
131
 
132
+ A [slug-title].pdf and thumb.jpg are also generated at build time. You can find them in the public folder.
133
+
134
  ### Deploy
135
 
136
  The easiest way to get online is to clone [this Hugging Face Space](https://huggingface.co/spaces/tfrere/science-blog-template) and push your changes; every push triggers an automatic build and deploy.
 
140
  **Track binaries** (e.g., `.png`, `.wav`) with **Git LFS** to keep the repository lean. This project is preconfigured to store such files via **LFS**.
141
 
142
 
143
+ <WritingYourContent />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
 
145
+ <BestPractices />
 
 
 
 
 
 
 
 
146
 
147
 
148
  ## Conclusions
app/src/content/chapters/best-pratices.mdx ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import { Image } from 'astro:assets';
3
+ import visualPoster from '../../assets/images/visual-vocabulary-poster.png';
4
+
5
+
6
+ ## Best Practices
7
+
8
+ ### Short sections
9
+ Break content into **small, purpose‑driven sections**. Each section should answer a **single question** or support one idea. This improves **scanability**, helps readers navigate with the TOC, and makes later edits safer.
10
+
11
+ ### Clear, minimal annotations
12
+ Favor **concise captions** and callouts that clarify what to look at and why it matters. In code, **highlight just the lines** that carry the idea; avoid verbose commentary. **Precision beats volume**.
13
+
14
+ ### Explain math notation
15
+ **Introduce symbols and variables** the first time they appear, and prefer **well‑known identities** over custom shorthand. When formulas carry the message, add one sentence of **plain‑language interpretation** right after.
16
+
17
+
18
+ {/* ### Use the right color
19
+ A palette encodes **meaning** (categories, magnitudes, oppositions), preserves **readability** and **accessibility** (sufficient contrast, color‑vision safety), and ensures **perceptually smooth transitions**. The three families below illustrate when to use **categorical**, **sequential**, or **diverging** colors and how they evolve from the same **reference hue**.
20
+
21
+ <Aside>
22
+ <div className="">
23
+ <HtmlFragment src="palettes.html" />
24
+ </div>
25
+ <Fragment slot="aside">
26
+ You can choose a color from the palette to update palettes and copy them to your clipboard.
27
+ </Fragment>
28
+ <Fragment slot="aside">
29
+ It will be applied to the <a href="/" target="_blank">whole page</a>.
30
+ </Fragment>
31
+ </Aside> */}
32
+
33
+ ### Use the right chart
34
+
35
+ Picking the right visualization depends on your goal (compare values, show distribution, part-to-whole, trends, relationships, etc.). The Visual Vocabulary poster below provides a concise mapping from **analytical task** to **chart types**.
36
+
37
+ <figure>
38
+ <a href={visualPoster.src} target="_blank" rel="noopener noreferrer">
39
+ <Image src={visualPoster} alt="Visual Vocabulary: choosing the right chart by task" />
40
+ </a>
41
+ <figcaption>
42
+ A handy reference to select chart types by purpose. Click to enlarge.
43
+ — <a href="https://ft-interactive.github.io/visual-vocabulary/" target="_blank" rel="noopener noreferrer">Website</a>
44
+ </figcaption>
45
+ </figure>
46
+
app/src/content/chapters/writing-you-content.mdx ADDED
@@ -0,0 +1,370 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {/* IMPORTS */}
2
+ import { Image } from 'astro:assets';
3
+ import placeholder from '../../assets/images/placeholder.png';
4
+ import Aside from '../../components/Aside.astro';
5
+ import Wide from '../../components/Wide.astro';
6
+ import FullBleed from '../../components/FullBleed.astro';
7
+ import HtmlFragment from '../../components/HtmlFragment.astro';
8
+ import audioDemo from '../../assets/audio/audio-example.wav';
9
+
10
+ ## Writing Your Content
11
+
12
+ ### Introduction
13
+
14
+ Your article lives in two places:
15
+
16
+ - `app/src/content/` — where you can find the article.mdx, bibliography.bib and html fragments.
17
+ - `app/src/assets/` — images, audio, and other static assets. (handled by git lfs)
18
+
19
+ This is MDX, its basically a markdown file with html and astro components.
20
+ The **initial skeleton** of an article looks like this.
21
+
22
+ ```mdx
23
+ {/* HEADER */}
24
+ ---
25
+ title: "This is the main title"
26
+ subtitle: "This will be displayed just below the banner"
27
+ description: "A modern, MDX-first research article template with math, citations, and interactive figures."
28
+ authors:
29
+ - "John Doe"
30
+ - "Alice Martin"
31
+ - "Robert Brown"
32
+ affiliation: "Hugging Face"
33
+ published: "Feb 19, 2025"
34
+ tags:
35
+ - research
36
+ - template
37
+ ogImage: "https://example.com/your-og-image.png"
38
+ ---
39
+
40
+ {/* IMPORTS */}
41
+ import { Image } from 'astro:assets';
42
+ import placeholder from '../assets/images/placeholder.jpg';
43
+
44
+ {/* CONTENT */}
45
+ # Hello, world
46
+
47
+ This is a short paragraph written in Markdown. Below is an example image:
48
+
49
+ <Image src={placeholder} alt="Example image" />
50
+ ```
51
+
52
+ ### Chapters
53
+
54
+
55
+ **If** your article becomes **too long**, you can **organize** it into **separate chapters**.
56
+
57
+ Simply **create a new file** in the `app/src/content/chapters` **directory**.
58
+ Then, **include** your new chapter in the main article by adding the following lines:
59
+
60
+ ```mdx
61
+ import MyChapter from './chapters/my-chapter.mdx';
62
+ <MyChapter />
63
+ ```
64
+
65
+ You can see an example of this in the <a href="">`app/src/content/chapters/best-pratices.mdx`</a> file.
66
+
67
+ ### Theme
68
+
69
+ All **interactive elements** (buttons, inputs, cards, etc.) are themed with the **primary color** you choose. Feel free to update this color to match your **brand**.
70
+
71
+ You can **override** the theme by changing the main variable in the `app/src/styles/_variables.css` file.
72
+
73
+ You can use the **color picker** below to choose the right color.
74
+ <Aside>
75
+ <div className="">
76
+ <HtmlFragment src="color-picker.html" />
77
+ </div>
78
+ <Fragment slot="aside">
79
+ There is also a color <a href="#use-the-right-color">palette generator</a> that will help you choose the right color for your data visualizations.
80
+ </Fragment>
81
+ </Aside>
82
+
83
+
84
+ ### Available blocks
85
+
86
+ All the following blocks are available in the article.mdx file. You can also create your own blocks by creating a new component in the components folder.
87
+
88
+ <br/>
89
+ <div className="button-group">
90
+ <a className="button" href="#math">Math</a>
91
+ <a className="button" href="#images">Images</a>
92
+ <a className="button" href="#code-blocks">Code</a>
93
+ <a className="button" href="#citations-and-notes">Citations & notes</a>
94
+ <a className="button" href="#asides">Asides</a>
95
+ <a className="button" href="#minimal-table">Table</a>
96
+ <a className="button" href="#audio">Audio</a>
97
+ <a className="button" href="#embeds">Embeds</a>
98
+ </div>
99
+
100
+ ### Math
101
+
102
+ KaTeX is used for math rendering.
103
+
104
+ **Inline**
105
+
106
+ This is an inline math equation: $x^2 + y^2 = z^2$.
107
+
108
+
109
+ <small className="muted">Example</small>
110
+ ```mdx
111
+ $x^2 + y^2 = z^2$
112
+ ```
113
+
114
+ **Block**
115
+
116
+ $$
117
+ \mathrm{Attention}(Q,K,V)=\mathrm{softmax}\!\left(\frac{QK^\top}{\sqrt{d_k}}\right) V
118
+ $$
119
+
120
+ <small className="muted">Example</small>
121
+ ```mdx
122
+ $$
123
+ \mathrm{Attention}(Q,K,V)=\mathrm{softmax}\!\left(\frac{QK^\top}{\sqrt{d_k}}\right) V
124
+ $$
125
+ ```
126
+
127
+ ### Images
128
+
129
+ **Responsive images** automatically generate an optimized `srcset` and `sizes` so the browser downloads the most appropriate file for the current viewport and DPR. You can also request multiple output formats (e.g., **AVIF**, **WebP**, fallback **PNG/JPEG**) and control **lazy loading/decoding** for better **performance**.
130
+
131
+ **Optional:** Zoomable (Medium-like lightbox): add `data-zoomable` to opt-in. Only images with this attribute will open full-screen on click.
132
+
133
+ **Optional:** Lazy loading: add `loading="lazy"` to opt-in.
134
+
135
+ **Optional:** Figcaption and credits: add a `figcaption` element with a `span` containing the credit.
136
+
137
+
138
+
139
+ <figure>
140
+ <Image
141
+ src={placeholder}
142
+ data-zoomable
143
+ alt="Tensor parallelism in a transformer block"
144
+ />
145
+ <figcaption>
146
+ Tensor parallelism in a transformer block
147
+ <span className="image-credit">Original work on <a target="_blank" href="https://huggingface.co/spaces/nanotron/ultrascale-playbook?section=tensor_parallelism_in_a_transformer_block">Ultrascale Playbook</a></span>
148
+ </figcaption>
149
+ </figure>
150
+
151
+ <small className="muted">Example</small>
152
+ ```mdx
153
+ import { Image } from 'astro:assets'
154
+ import myImage from '../assets/images/placeholder.jpg'
155
+
156
+ <Image src={myImage} alt="Responsive, optimized example image" />
157
+
158
+ <figure>
159
+ <Image src={myImage} data-zoomable alt="Example with caption and credit" loading="lazy" />
160
+ <figcaption>
161
+ Optimized image with a descriptive caption.
162
+ <span className="image-credit">Credit: Photo by <a href="https://example.com">Author</a></span>
163
+ </figcaption>
164
+ </figure>
165
+ ```
166
+
167
+
168
+ ### Code blocks
169
+
170
+ Use fenced code blocks with a language for syntax highlighting.
171
+
172
+ ```python
173
+ def greet(name: str) -> None:
174
+ print(f"Hello, {name}!")
175
+
176
+ greet("Astro")
177
+ ```
178
+
179
+ <small className="muted">Example</small>
180
+ ````mdx
181
+ ```python
182
+ def greet(name: str) -> None:
183
+ print(f"Hello, {name}!")
184
+
185
+ greet("Astro")
186
+ ```
187
+ ````
188
+
189
+ ### Mermaid diagrams
190
+
191
+ Native mermaid diagrams are supported. You can use the <a target="_blank" href="https://mermaid.live/edit#pako:eNpVjUFPg0AQhf_KZk6a0AYsCywHE0u1lyZ66EnoYQMDSyy7ZFlSK_DfXWiMOqd58773ZoBcFQgxlGd1yQXXhhx3mSR2ntJE6LozDe9OZLV6HPdoSKMkXkeyvdsr0gnVtrWs7m_8doZIMhxmDIkRtfyYblay5F8ljmSXHnhrVHv66xwvaiTPaf0mbP1_R2i0qZe05HHJVznXJOF6QcCBStcFxEb36ECDuuGzhGF2MzACG8wgtmuBJe_PJoNMTjbWcvmuVPOT1KqvBNj6c2dV3xbc4K7mlea_CMoCdaJ6aSCm3lIB8QCfED94dM2o77ssjFzK3MiBq2WCNWUeiza-H26YvU8OfC0_3XVII9eLQuYFIaVBGEzfyTJ22g"> live editor</a> to create your diagram and copy the code to your article.
192
+
193
+ ```mermaid
194
+ graph TD
195
+ A[Start] --> B{Is it working?}
196
+ B -- Yes --> C[Great!]
197
+ B -- No --> D[Fix it]
198
+ D --> B
199
+ ```
200
+
201
+ <small className="muted">Example</small>
202
+ ````mdx
203
+ ```mermaid
204
+ graph TD
205
+ A[Start] --> B{Is it working?}
206
+ B -- Yes --> C[Great!]
207
+ B -- No --> D[Fix it]
208
+ D --> B
209
+ ```
210
+ ````
211
+
212
+ ### Citations and notes
213
+
214
+ Here are a few variations using the same bibliography:
215
+
216
+ 1) **In-text citation** with brackets: [@example2023].
217
+
218
+ 2) **Narrative citation**: As shown by @vaswani2017attention, transformers enable efficient sequence modeling.
219
+
220
+ 3) **Multiple citations** and a **footnote** together: see [@vaswani2017attention; @example2023] for related work. Also note this footnote[^f1].
221
+
222
+ [^f1]: Footnote attached to the sentence above.
223
+
224
+ <small className="muted">Example</small>
225
+ ```mdx
226
+ 1) In-text citation with brackets: [@example2023].
227
+
228
+ 2) Narrative citation: As shown by @vaswani2017attention, transformers enable efficient sequence modeling.
229
+
230
+ 3) Multiple citations and a footnote together: see [@vaswani2017attention; @example2023] for related work. Also note this footnote[^f1].
231
+
232
+ [^f1]: Footnote attached to the sentence above.
233
+ ```
234
+
235
+
236
+ ### Placement
237
+
238
+ #### Asides
239
+
240
+ <Aside>
241
+ This paragraph presents a **key idea** concisely.
242
+ <Fragment slot="aside">
243
+ **Side note** for brief context or a definition.
244
+ </Fragment>
245
+ </Aside>
246
+
247
+ <small className="muted">Example</small>
248
+ ```mdx
249
+ import Aside from '../components/Aside.astro'
250
+
251
+ <Aside>
252
+ Main paragraph with the core idea.
253
+ <Fragment slot="aside">Short side note.</Fragment>
254
+ </Aside>
255
+ ```
256
+
257
+ Use these helpers to expand content beyond the main column when needed. They will always be centered and displayed above every other content.
258
+
259
+ #### Wide example
260
+
261
+ <Wide>
262
+ <div className="demo-wide">demo wide</div>
263
+ </Wide>
264
+
265
+ <small className="muted">Example</small>
266
+ ```mdx
267
+ import Wide from '../components/Wide.astro'
268
+
269
+ <Wide>
270
+ Your content here...
271
+ </Wide>
272
+ ```
273
+
274
+ #### Full-bleed example
275
+
276
+ <FullBleed>
277
+ <div className="demo-full-bleed">demo full-bleed</div>
278
+ </FullBleed>
279
+
280
+ <small className="muted">Example</small>
281
+ ```mdx
282
+ import FullBleed from '../components/FullBleed.astro'
283
+
284
+ <FullBleed>
285
+ Your content here...
286
+ </FullBleed>
287
+ ```
288
+
289
+
290
+ ### Minimal table
291
+
292
+ | Method | Score |
293
+ |---|---|
294
+ | A | 0.78 |
295
+ | B | 0.86 |
296
+
297
+ <small className="muted">Example</small>
298
+ ```mdx
299
+ | Method | Score |
300
+ | --- | --- |
301
+ | A | 0.78 |
302
+ | B | 0.86 |
303
+ ```
304
+
305
+ ### Audio
306
+
307
+ <audio controls src={audioDemo}>
308
+ Your browser does not support the audio element.
309
+ </audio>
310
+
311
+ <small className="muted">Example</small>
312
+ ```mdx
313
+ import audioDemo from '../assets/audio/audio-example.wav'
314
+
315
+ <audio controls src={audioDemo}>
316
+ Your browser does not support the audio element.
317
+ </audio>
318
+ ```
319
+
320
+
321
+
322
+ ### Embeds
323
+
324
+
325
+ #### Html Fragments
326
+
327
+ The main purpose of the ```HtmlFragment``` component is to **embed** a **Plotly** or **D3.js** chart in your article. **Libraries** are already imported in the template.
328
+
329
+ They exist in the `app/src/content/fragments` folder.
330
+
331
+ Here are some examples of the two **libraries** in the template:
332
+
333
+ D3 version
334
+ <div className="plot-card">
335
+ <HtmlFragment src="d3-line.html" />
336
+ </div>
337
+
338
+ <div className="plot-card">
339
+ <HtmlFragment src="d3-bar.html" />
340
+ </div>
341
+
342
+
343
+ Plotly version
344
+ <div className="plot-card">
345
+ <HtmlFragment src="line.html" />
346
+ </div>
347
+ <div className="plot-card">
348
+ <HtmlFragment src="bar.html" />
349
+ </div>
350
+
351
+ <small className="muted">Example</small>
352
+ ```mdx
353
+ import HtmlFragment from '../components/HtmlFragment.astro'
354
+
355
+ <HtmlFragment src="line.html" />
356
+ ```
357
+
358
+ #### Iframes
359
+
360
+ You can embed external content in your article using **iframes**. For example, **TrackIO or github code embeds** can be used this way.
361
+
362
+ <iframe frameborder="0" scrolling="no" style="width:100%; height:292px;" allow="clipboard-write" src="https://emgithub.com/iframe.html?target=https%3A%2F%2Fgithub.com%2Fhuggingface%2Fpicotron%2Fblob%2F1004ae37b87887cde597c9060fb067faa060bafe%2Fsetup.py&style=default&type=code&showBorder=on&showLineNumbers=on"></iframe>
363
+
364
+ <iframe className="plot-card" src="https://trackio-documentation.hf.space/?project=fake-training-750735&metrics=train_loss,train_accuracy&sidebar=hidden&lang=en" width="100%" height="660" frameborder="0"></iframe>
365
+
366
+ <small className="muted">Example</small>
367
+ ```mdx
368
+ <iframe frameborder="0" scrolling="no" style="width:100%; height:292px;" allow="clipboard-write" src="https://emgithub.com/iframe.html?target=https%3A%2F%2Fgithub.com%2Fhuggingface%2Fpicotron%2Fblob%2F1004ae37b87887cde597c9060fb067faa060bafe%2Fsetup.py&style=default&type=code&showBorder=on&showLineNumbers=on"></iframe>
369
+ <iframe src="https://trackio-documentation.hf.space/?project=fake-training-750735&metrics=train_loss,train_accuracy&sidebar=hidden&lang=en" width="100%" height="600" frameborder="0"></iframe>
370
+ ```
app/src/content/fragments/banner.html CHANGED
@@ -1,4 +1,4 @@
1
- <div class="d3-galaxy" style="width:100%;margin:10px 0;"></div>
2
  <script>
3
  (() => {
4
  const ensureD3 = (cb) => {
@@ -105,15 +105,6 @@
105
  const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
106
  const strokeColor = isDark ? 'rgba(255,255,255,0.18)' : 'rgba(0,0,0,0.12)';
107
 
108
- // Background rect using gradient
109
- const bg = svg.selectAll('rect.d3-bg').data([0]);
110
- bg.join('rect')
111
- .attr('class', 'd3-bg')
112
- .attr('x', 0)
113
- .attr('y', 0)
114
- .attr('width', width)
115
- .attr('height', height)
116
- .attr('fill', 'url(#spaceBg)');
117
 
118
  // Group with blend mode so points softly accumulate light
119
  const g = svg.selectAll('g.points').data([0]).join('g').attr('class', 'points').style('mix-blend-mode', 'screen');
@@ -152,7 +143,7 @@
152
  }
153
 
154
  // Final filter: remove small dots very close to the galaxy center (after placement)
155
- const centerHoleRadius = 0.08; // elliptical radius threshold
156
  const smallSizeThreshold = 7.5; // same notion as Python size cut
157
  const rTotal = idx.map((i) => Math.sqrt(((X[i] - cx) / a) ** 2 + ((Y[i] - cy) / b) ** 2));
158
  const idxFiltered = idx.filter((i, k) => !(rTotal[k] <= centerHoleRadius && sizesPx[i] < smallSizeThreshold));
 
1
+ <div class="d3-galaxy" style="width:100%;margin:10px 0;aspect-ratio:3/1;min-height:260px;"></div>
2
  <script>
3
  (() => {
4
  const ensureD3 = (cb) => {
 
105
  const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
106
  const strokeColor = isDark ? 'rgba(255,255,255,0.18)' : 'rgba(0,0,0,0.12)';
107
 
 
 
 
 
 
 
 
 
 
108
 
109
  // Group with blend mode so points softly accumulate light
110
  const g = svg.selectAll('g.points').data([0]).join('g').attr('class', 'points').style('mix-blend-mode', 'screen');
 
143
  }
144
 
145
  // Final filter: remove small dots very close to the galaxy center (after placement)
146
+ const centerHoleRadius = 0.48; // elliptical radius threshold
147
  const smallSizeThreshold = 7.5; // same notion as Python size cut
148
  const rTotal = idx.map((i) => Math.sqrt(((X[i] - cx) / a) ** 2 + ((Y[i] - cy) / b) ** 2));
149
  const idxFiltered = idx.filter((i, k) => !(rTotal[k] <= centerHoleRadius && sizesPx[i] < smallSizeThreshold));
app/src/content/fragments/bar.html CHANGED
@@ -1,22 +1 @@
1
- <div> <div id="fd0d6e2f-9418-45a8-acf0-f9bc9952180e" class="plotly-graph-div" style="height:100%; width:100%;"></div> <script type="text/javascript"> window.PLOTLYENV=window.PLOTLYENV || {}; if (document.getElementById("fd0d6e2f-9418-45a8-acf0-f9bc9952180e")) { Plotly.newPlot( "fd0d6e2f-9418-45a8-acf0-f9bc9952180e", [{"hovertemplate":"\u003cb\u003e%{x}\u003c\u002fb\u003e\u003cbr\u003e%{fullData.name}: %{y:.3f}\u003cextra\u003e\u003c\u002fextra\u003e","marker":{"color":"#64748b"},"name":"Baseline","offsetgroup":"grp","x":["A","B","C","D","E"],"y":[0.52,0.61,0.67,0.73,0.78],"type":"bar"},{"hovertemplate":"\u003cb\u003e%{x}\u003c\u002fb\u003e\u003cbr\u003e%{fullData.name}: %{y:.3f}\u003cextra\u003e\u003c\u002fextra\u003e","marker":{"color":"#2563eb"},"name":"Improved","offsetgroup":"grp","x":["A","B","C","D","E"],"y":[0.58,0.66,0.72,0.79,0.86],"type":"bar"},{"hovertemplate":"\u003cb\u003e%{x}\u003c\u002fb\u003e\u003cbr\u003e%{fullData.name}: %{y:.3f}\u003cextra\u003e\u003c\u002fextra\u003e","marker":{"color":"#4b5563","line":{"color":"#4b5563","width":1},"opacity":0.65},"name":"Target","offsetgroup":"grp","x":["A","B","C","D","E"],"y":[0.6,0.68,0.75,0.82,0.9],"type":"bar"}], {"template":{"data":{"histogram2dcontour":[{"type":"histogram2dcontour","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"choropleth":[{"type":"choropleth","colorbar":{"outlinewidth":0,"ticks":""}}],"histogram2d":[{"type":"histogram2d","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"heatmap":[{"type":"heatmap","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"heatmapgl":[{"type":"heatmapgl","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"contourcarpet":[{"type":"contourcarpet","colorbar":{"outlinewidth":0,"ticks":""}}],"contour":[{"type":"contour","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"surface":[{"type":"surface","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"mesh3d":[{"type":"mesh3d","colorbar":{"outlinewidth":0,"ticks":""}}],"scatter":[{"fillpattern":{"fillmode":"overlay","size":10,"solidity":0.2},"type":"scatter"}],"parcoords":[{"type":"parcoords","line":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scatterpolargl":[{"type":"scatterpolargl","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"bar":[{"error_x":{"color":"#2a3f5f"},"error_y":{"color":"#2a3f5f"},"marker":{"line":{"color":"#E5ECF6","width":0.5},"pattern":{"fillmode":"overlay","size":10,"solidity":0.2}},"type":"bar"}],"scattergeo":[{"type":"scattergeo","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scatterpolar":[{"type":"scatterpolar","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"histogram":[{"marker":{"pattern":{"fillmode":"overlay","size":10,"solidity":0.2}},"type":"histogram"}],"scattergl":[{"type":"scattergl","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scatter3d":[{"type":"scatter3d","line":{"colorbar":{"outlinewidth":0,"ticks":""}},"marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scattermapbox":[{"type":"scattermapbox","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scatterternary":[{"type":"scatterternary","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scattercarpet":[{"type":"scattercarpet","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"carpet":[{"aaxis":{"endlinecolor":"#2a3f5f","gridcolor":"white","linecolor":"white","minorgridcolor":"white","startlinecolor":"#2a3f5f"},"baxis":{"endlinecolor":"#2a3f5f","gridcolor":"white","linecolor":"white","minorgridcolor":"white","startlinecolor":"#2a3f5f"},"type":"carpet"}],"table":[{"cells":{"fill":{"color":"#EBF0F8"},"line":{"color":"white"}},"header":{"fill":{"color":"#C8D4E3"},"line":{"color":"white"}},"type":"table"}],"barpolar":[{"marker":{"line":{"color":"#E5ECF6","width":0.5},"pattern":{"fillmode":"overlay","size":10,"solidity":0.2}},"type":"barpolar"}],"pie":[{"automargin":true,"type":"pie"}]},"layout":{"autotypenumbers":"strict","colorway":["#636efa","#EF553B","#00cc96","#ab63fa","#FFA15A","#19d3f3","#FF6692","#B6E880","#FF97FF","#FECB52"],"font":{"color":"#2a3f5f"},"hovermode":"closest","hoverlabel":{"align":"left"},"paper_bgcolor":"white","plot_bgcolor":"#E5ECF6","polar":{"bgcolor":"#E5ECF6","angularaxis":{"gridcolor":"white","linecolor":"white","ticks":""},"radialaxis":{"gridcolor":"white","linecolor":"white","ticks":""}},"ternary":{"bgcolor":"#E5ECF6","aaxis":{"gridcolor":"white","linecolor":"white","ticks":""},"baxis":{"gridcolor":"white","linecolor":"white","ticks":""},"caxis":{"gridcolor":"white","linecolor":"white","ticks":""}},"coloraxis":{"colorbar":{"outlinewidth":0,"ticks":""}},"colorscale":{"sequential":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"sequentialminus":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"diverging":[[0,"#8e0152"],[0.1,"#c51b7d"],[0.2,"#de77ae"],[0.3,"#f1b6da"],[0.4,"#fde0ef"],[0.5,"#f7f7f7"],[0.6,"#e6f5d0"],[0.7,"#b8e186"],[0.8,"#7fbc41"],[0.9,"#4d9221"],[1,"#276419"]]},"xaxis":{"gridcolor":"white","linecolor":"white","ticks":"","title":{"standoff":15},"zerolinecolor":"white","automargin":true,"zerolinewidth":2},"yaxis":{"gridcolor":"white","linecolor":"white","ticks":"","title":{"standoff":15},"zerolinecolor":"white","automargin":true,"zerolinewidth":2},"scene":{"xaxis":{"backgroundcolor":"#E5ECF6","gridcolor":"white","linecolor":"white","showbackground":true,"ticks":"","zerolinecolor":"white","gridwidth":2},"yaxis":{"backgroundcolor":"#E5ECF6","gridcolor":"white","linecolor":"white","showbackground":true,"ticks":"","zerolinecolor":"white","gridwidth":2},"zaxis":{"backgroundcolor":"#E5ECF6","gridcolor":"white","linecolor":"white","showbackground":true,"ticks":"","zerolinecolor":"white","gridwidth":2}},"shapedefaults":{"line":{"color":"#2a3f5f"}},"annotationdefaults":{"arrowcolor":"#2a3f5f","arrowhead":0,"arrowwidth":1},"geo":{"bgcolor":"white","landcolor":"#E5ECF6","subunitcolor":"white","showland":true,"showlakes":true,"lakecolor":"white"},"title":{"x":0.05},"mapbox":{"style":"light"}}},"margin":{"l":28,"r":12,"t":8,"b":28},"legend":{"orientation":"h","yanchor":"bottom","y":1.02,"xanchor":"left","x":0},"xaxis":{"tickfont":{"size":12,"color":"rgba(0,0,0,0.65)"},"showgrid":false,"zeroline":false,"showline":true,"linecolor":"rgba(0,0,0,0.25)","linewidth":1,"ticks":"outside","ticklen":6,"tickcolor":"rgba(0,0,0,0.25)","title":{},"automargin":true,"fixedrange":true},"yaxis":{"tickfont":{"size":12,"color":"rgba(0,0,0,0.65)"},"showgrid":false,"zeroline":false,"showline":true,"linecolor":"rgba(0,0,0,0.25)","linewidth":1,"ticks":"outside","ticklen":6,"tickcolor":"rgba(0,0,0,0.25)","title":{},"tickformat":".2f","automargin":true,"fixedrange":true},"barmode":"group","autosize":true,"paper_bgcolor":"rgba(0,0,0,0)","plot_bgcolor":"rgba(0,0,0,0)","hovermode":"x unified"}, {"displayModeBar": false, "responsive": true, "scrollZoom": false, "doubleClick": false, "modeBarButtonsToRemove": ["zoom2d", "pan2d", "select2d", "lasso2d", "zoomIn2d", "zoomOut2d", "autoScale2d", "resetScale2d", "toggleSpikelines"]} ).then(function(){
2
-
3
- (function(){
4
- var plots = document.querySelectorAll('.js-plotly-plot');
5
- plots.forEach(function(gd){
6
- function round(){
7
- try {
8
- var root = gd && gd.parentNode ? gd.parentNode : document;
9
- var rects = root.querySelectorAll('.hoverlayer .hovertext rect');
10
- rects.forEach(function(r){ r.setAttribute('rx', 8); r.setAttribute('ry', 8); });
11
- } catch(e) {}
12
- }
13
- if (gd && gd.on){
14
- gd.on('plotly_hover', round);
15
- gd.on('plotly_unhover', round);
16
- gd.on('plotly_relayout', round);
17
- }
18
- setTimeout(round, 0);
19
- });
20
- })();
21
-
22
- }) }; </script> </div>
 
1
+ <div> <div id="3e4ed4fe-23b4-4ec1-810e-20d6fce0209b" class="plotly-graph-div" style="height:100%; width:100%;"></div> <script type="text/javascript"> window.PLOTLYENV=window.PLOTLYENV || {}; if (document.getElementById("3e4ed4fe-23b4-4ec1-810e-20d6fce0209b")) { Plotly.newPlot( "3e4ed4fe-23b4-4ec1-810e-20d6fce0209b", [{"hovertemplate":"Seq len=%{x}\u003cbr\u003eMem=%{y:.1f}GB\u003cbr\u003e%{data.name}\u003cextra\u003e\u003c\u002fextra\u003e","marker":{"color":"rgb(78, 165, 183)"},"name":"parameters","showlegend":true,"visible":true,"x":["1024","2048","4096","8192"],"y":[4.0,4.0,4.0,4.0],"type":"bar"},{"hovertemplate":"Seq len=%{x}\u003cbr\u003eMem=%{y:.1f}GB\u003cbr\u003e%{data.name}\u003cextra\u003e\u003c\u002fextra\u003e","marker":{"color":"rgb(227, 138, 66)"},"name":"gradients","showlegend":true,"visible":true,"x":["1024","2048","4096","8192"],"y":[4.0,4.0,4.0,4.0],"type":"bar"},{"hovertemplate":"Seq len=%{x}\u003cbr\u003eMem=%{y:.1f}GB\u003cbr\u003e%{data.name}\u003cextra\u003e\u003c\u002fextra\u003e","marker":{"color":"rgb(232, 137, 171)"},"name":"optimizer","showlegend":true,"visible":true,"x":["1024","2048","4096","8192"],"y":[8.0,8.0,8.0,8.0],"type":"bar"},{"hovertemplate":"Seq len=%{x}\u003cbr\u003eMem=%{y:.1f}GB\u003cbr\u003e%{data.name}\u003cextra\u003e\u003c\u002fextra\u003e","marker":{"color":"rgb(206, 192, 250)"},"name":"activations","showlegend":true,"visible":true,"x":["1024","2048","4096","8192"],"y":[3.6,14.4,57.6,230.4],"type":"bar"},{"hovertemplate":"Seq len=%{x}\u003cbr\u003eMem=%{y:.1f}GB\u003cbr\u003e%{data.name}\u003cextra\u003e\u003c\u002fextra\u003e","marker":{"color":"rgb(78, 165, 183)"},"name":"parameters","showlegend":true,"visible":false,"x":["1024","2048","4096","8192"],"y":[13.3,13.3,13.3,13.3],"type":"bar"},{"hovertemplate":"Seq len=%{x}\u003cbr\u003eMem=%{y:.1f}GB\u003cbr\u003e%{data.name}\u003cextra\u003e\u003c\u002fextra\u003e","marker":{"color":"rgb(227, 138, 66)"},"name":"gradients","showlegend":true,"visible":false,"x":["1024","2048","4096","8192"],"y":[13.3,13.3,13.3,13.3],"type":"bar"},{"hovertemplate":"Seq len=%{x}\u003cbr\u003eMem=%{y:.1f}GB\u003cbr\u003e%{data.name}\u003cextra\u003e\u003c\u002fextra\u003e","marker":{"color":"rgb(232, 137, 171)"},"name":"optimizer","showlegend":true,"visible":false,"x":["1024","2048","4096","8192"],"y":[26.6,26.6,26.6,26.6],"type":"bar"},{"hovertemplate":"Seq len=%{x}\u003cbr\u003eMem=%{y:.1f}GB\u003cbr\u003e%{data.name}\u003cextra\u003e\u003c\u002fextra\u003e","marker":{"color":"rgb(206, 192, 250)"},"name":"activations","showlegend":true,"visible":false,"x":["1024","2048","4096","8192"],"y":[9.3,37.2,148.8,595.2],"type":"bar"},{"hovertemplate":"Seq len=%{x}\u003cbr\u003eMem=%{y:.1f}GB\u003cbr\u003e%{data.name}\u003cextra\u003e\u003c\u002fextra\u003e","marker":{"color":"rgb(78, 165, 183)"},"name":"parameters","showlegend":true,"visible":false,"x":["1024","2048","4096","8192"],"y":[26.0,26.0,26.0,26.0],"type":"bar"},{"hovertemplate":"Seq len=%{x}\u003cbr\u003eMem=%{y:.1f}GB\u003cbr\u003e%{data.name}\u003cextra\u003e\u003c\u002fextra\u003e","marker":{"color":"rgb(227, 138, 66)"},"name":"gradients","showlegend":true,"visible":false,"x":["1024","2048","4096","8192"],"y":[26.0,26.0,26.0,26.0],"type":"bar"},{"hovertemplate":"Seq len=%{x}\u003cbr\u003eMem=%{y:.1f}GB\u003cbr\u003e%{data.name}\u003cextra\u003e\u003c\u002fextra\u003e","marker":{"color":"rgb(232, 137, 171)"},"name":"optimizer","showlegend":true,"visible":false,"x":["1024","2048","4096","8192"],"y":[52.0,52.0,52.0,52.0],"type":"bar"},{"hovertemplate":"Seq len=%{x}\u003cbr\u003eMem=%{y:.1f}GB\u003cbr\u003e%{data.name}\u003cextra\u003e\u003c\u002fextra\u003e","marker":{"color":"rgb(206, 192, 250)"},"name":"activations","showlegend":true,"visible":false,"x":["1024","2048","4096","8192"],"y":[46.2,184.8,739.2,2956.8],"type":"bar"},{"hovertemplate":"Seq len=%{x}\u003cbr\u003eMem=%{y:.1f}GB\u003cbr\u003e%{data.name}\u003cextra\u003e\u003c\u002fextra\u003e","marker":{"color":"rgb(78, 165, 183)"},"name":"parameters","showlegend":true,"visible":false,"x":["1024","2048","4096","8192"],"y":[244.0,244.0,244.0,244.0],"type":"bar"},{"hovertemplate":"Seq len=%{x}\u003cbr\u003eMem=%{y:.1f}GB\u003cbr\u003e%{data.name}\u003cextra\u003e\u003c\u002fextra\u003e","marker":{"color":"rgb(227, 138, 66)"},"name":"gradients","showlegend":true,"visible":false,"x":["1024","2048","4096","8192"],"y":[244.0,244.0,244.0,244.0],"type":"bar"},{"hovertemplate":"Seq len=%{x}\u003cbr\u003eMem=%{y:.1f}GB\u003cbr\u003e%{data.name}\u003cextra\u003e\u003c\u002fextra\u003e","marker":{"color":"rgb(232, 137, 171)"},"name":"optimizer","showlegend":true,"visible":false,"x":["1024","2048","4096","8192"],"y":[488.0,488.0,488.0,488.0],"type":"bar"},{"hovertemplate":"Seq len=%{x}\u003cbr\u003eMem=%{y:.1f}GB\u003cbr\u003e%{data.name}\u003cextra\u003e\u003c\u002fextra\u003e","marker":{"color":"rgb(206, 192, 250)"},"name":"activations","showlegend":true,"visible":false,"x":["1024","2048","4096","8192"],"y":[145.7,582.8,2331.2,9324.8],"type":"bar"},{"hovertemplate":"Seq len=%{x}\u003cbr\u003eMem=%{y:.1f}GB\u003cbr\u003e%{data.name}\u003cextra\u003e\u003c\u002fextra\u003e","marker":{"color":"rgb(78, 165, 183)"},"name":"parameters","showlegend":true,"visible":false,"x":["1024","2048","4096","8192"],"y":[1520.0,1520.0,1520.0,1520.0],"type":"bar"},{"hovertemplate":"Seq len=%{x}\u003cbr\u003eMem=%{y:.1f}GB\u003cbr\u003e%{data.name}\u003cextra\u003e\u003c\u002fextra\u003e","marker":{"color":"rgb(227, 138, 66)"},"name":"gradients","showlegend":true,"visible":false,"x":["1024","2048","4096","8192"],"y":[1520.0,1520.0,1520.0,1520.0],"type":"bar"},{"hovertemplate":"Seq len=%{x}\u003cbr\u003eMem=%{y:.1f}GB\u003cbr\u003e%{data.name}\u003cextra\u003e\u003c\u002fextra\u003e","marker":{"color":"rgb(232, 137, 171)"},"name":"optimizer","showlegend":true,"visible":false,"x":["1024","2048","4096","8192"],"y":[3040.0,3040.0,3040.0,3040.0],"type":"bar"},{"hovertemplate":"Seq len=%{x}\u003cbr\u003eMem=%{y:.1f}GB\u003cbr\u003e%{data.name}\u003cextra\u003e\u003c\u002fextra\u003e","marker":{"color":"rgb(206, 192, 250)"},"name":"activations","showlegend":true,"visible":false,"x":["1024","2048","4096","8192"],"y":[1519.9,6079.6,24318.4,97273.6],"type":"bar"}], {"template":{"data":{"histogram2dcontour":[{"type":"histogram2dcontour","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"choropleth":[{"type":"choropleth","colorbar":{"outlinewidth":0,"ticks":""}}],"histogram2d":[{"type":"histogram2d","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"heatmap":[{"type":"heatmap","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"heatmapgl":[{"type":"heatmapgl","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"contourcarpet":[{"type":"contourcarpet","colorbar":{"outlinewidth":0,"ticks":""}}],"contour":[{"type":"contour","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"surface":[{"type":"surface","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"mesh3d":[{"type":"mesh3d","colorbar":{"outlinewidth":0,"ticks":""}}],"scatter":[{"fillpattern":{"fillmode":"overlay","size":10,"solidity":0.2},"type":"scatter"}],"parcoords":[{"type":"parcoords","line":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scatterpolargl":[{"type":"scatterpolargl","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"bar":[{"error_x":{"color":"#2a3f5f"},"error_y":{"color":"#2a3f5f"},"marker":{"line":{"color":"#E5ECF6","width":0.5},"pattern":{"fillmode":"overlay","size":10,"solidity":0.2}},"type":"bar"}],"scattergeo":[{"type":"scattergeo","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scatterpolar":[{"type":"scatterpolar","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"histogram":[{"marker":{"pattern":{"fillmode":"overlay","size":10,"solidity":0.2}},"type":"histogram"}],"scattergl":[{"type":"scattergl","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scatter3d":[{"type":"scatter3d","line":{"colorbar":{"outlinewidth":0,"ticks":""}},"marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scattermapbox":[{"type":"scattermapbox","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scatterternary":[{"type":"scatterternary","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scattercarpet":[{"type":"scattercarpet","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"carpet":[{"aaxis":{"endlinecolor":"#2a3f5f","gridcolor":"white","linecolor":"white","minorgridcolor":"white","startlinecolor":"#2a3f5f"},"baxis":{"endlinecolor":"#2a3f5f","gridcolor":"white","linecolor":"white","minorgridcolor":"white","startlinecolor":"#2a3f5f"},"type":"carpet"}],"table":[{"cells":{"fill":{"color":"#EBF0F8"},"line":{"color":"white"}},"header":{"fill":{"color":"#C8D4E3"},"line":{"color":"white"}},"type":"table"}],"barpolar":[{"marker":{"line":{"color":"#E5ECF6","width":0.5},"pattern":{"fillmode":"overlay","size":10,"solidity":0.2}},"type":"barpolar"}],"pie":[{"automargin":true,"type":"pie"}]},"layout":{"autotypenumbers":"strict","colorway":["#636efa","#EF553B","#00cc96","#ab63fa","#FFA15A","#19d3f3","#FF6692","#B6E880","#FF97FF","#FECB52"],"font":{"color":"#2a3f5f"},"hovermode":"closest","hoverlabel":{"align":"left"},"paper_bgcolor":"white","plot_bgcolor":"#E5ECF6","polar":{"bgcolor":"#E5ECF6","angularaxis":{"gridcolor":"white","linecolor":"white","ticks":""},"radialaxis":{"gridcolor":"white","linecolor":"white","ticks":""}},"ternary":{"bgcolor":"#E5ECF6","aaxis":{"gridcolor":"white","linecolor":"white","ticks":""},"baxis":{"gridcolor":"white","linecolor":"white","ticks":""},"caxis":{"gridcolor":"white","linecolor":"white","ticks":""}},"coloraxis":{"colorbar":{"outlinewidth":0,"ticks":""}},"colorscale":{"sequential":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"sequentialminus":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"diverging":[[0,"#8e0152"],[0.1,"#c51b7d"],[0.2,"#de77ae"],[0.3,"#f1b6da"],[0.4,"#fde0ef"],[0.5,"#f7f7f7"],[0.6,"#e6f5d0"],[0.7,"#b8e186"],[0.8,"#7fbc41"],[0.9,"#4d9221"],[1,"#276419"]]},"xaxis":{"gridcolor":"white","linecolor":"white","ticks":"","title":{"standoff":15},"zerolinecolor":"white","automargin":true,"zerolinewidth":2},"yaxis":{"gridcolor":"white","linecolor":"white","ticks":"","title":{"standoff":15},"zerolinecolor":"white","automargin":true,"zerolinewidth":2},"scene":{"xaxis":{"backgroundcolor":"#E5ECF6","gridcolor":"white","linecolor":"white","showbackground":true,"ticks":"","zerolinecolor":"white","gridwidth":2},"yaxis":{"backgroundcolor":"#E5ECF6","gridcolor":"white","linecolor":"white","showbackground":true,"ticks":"","zerolinecolor":"white","gridwidth":2},"zaxis":{"backgroundcolor":"#E5ECF6","gridcolor":"white","linecolor":"white","showbackground":true,"ticks":"","zerolinecolor":"white","gridwidth":2}},"shapedefaults":{"line":{"color":"#2a3f5f"}},"annotationdefaults":{"arrowcolor":"#2a3f5f","arrowhead":0,"arrowwidth":1},"geo":{"bgcolor":"white","landcolor":"#E5ECF6","subunitcolor":"white","showland":true,"showlakes":true,"lakecolor":"white"},"title":{"x":0.05},"mapbox":{"style":"light"}}},"margin":{"l":40,"r":28,"t":20,"b":40},"legend":{"orientation":"h","yanchor":"bottom","y":1.02,"xanchor":"left","x":0},"xaxis":{"title":{"text":"Sequence Length"},"fixedrange":true},"yaxis":{"title":{"text":"Memory (GB)"},"fixedrange":true},"barmode":"stack","autosize":true,"paper_bgcolor":"rgba(0,0,0,0)","plot_bgcolor":"rgba(0,0,0,0)","hovermode":"x unified","updatemenus":[{"active":0,"buttons":[{"args":[{"visible":[true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false]},{"yaxis":{"range":[0,258.72]}}],"label":"1B","method":"update"},{"args":[{"visible":[false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false]},{"yaxis":{"range":[0,680.8200000000002]}}],"label":"3B","method":"update"},{"args":[{"visible":[false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false]},{"yaxis":{"range":[0,3213.84]}}],"label":"8B","method":"update"},{"args":[{"visible":[false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false]},{"yaxis":{"range":[0,10815.84]}}],"label":"70B","method":"update"},{"args":[{"visible":[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true]},{"yaxis":{"range":[0,108521.28000000001]}}],"label":"405B","method":"update"}],"showactive":true,"type":"dropdown","x":1.03,"xanchor":"left","y":0.6,"yanchor":"top"},{"active":0,"buttons":[{"args":[{"y":[[4.0,4.0,4.0,4.0],[4.0,4.0,4.0,4.0],[8.0,8.0,8.0,8.0],[3.6,14.4,57.6,230.4],[13.3,13.3,13.3,13.3],[13.3,13.3,13.3,13.3],[26.6,26.6,26.6,26.6],[9.3,37.2,148.8,595.2],[26.0,26.0,26.0,26.0],[26.0,26.0,26.0,26.0],[52.0,52.0,52.0,52.0],[46.2,184.8,739.2,2956.8],[244.0,244.0,244.0,244.0],[244.0,244.0,244.0,244.0],[488.0,488.0,488.0,488.0],[145.7,582.8,2331.2,9324.8],[1520.0,1520.0,1520.0,1520.0],[1520.0,1520.0,1520.0,1520.0],[3040.0,3040.0,3040.0,3040.0],[1519.9,6079.6,24318.4,97273.6]]},{"yaxis":{"range":[0,108521.28000000001]}}],"label":"None","method":"update"},{"args":[{"y":[[4.0,4.0,4.0,4.0],[4.0,4.0,4.0,4.0],[8.0,8.0,8.0,8.0],[0.9,3.6,14.4,57.6],[13.3,13.3,13.3,13.3],[13.3,13.3,13.3,13.3],[26.6,26.6,26.6,26.6],[2.325,9.3,37.2,148.8],[26.0,26.0,26.0,26.0],[26.0,26.0,26.0,26.0],[52.0,52.0,52.0,52.0],[11.55,46.2,184.8,739.2],[244.0,244.0,244.0,244.0],[244.0,244.0,244.0,244.0],[488.0,488.0,488.0,488.0],[36.425,145.7,582.8,2331.2],[1520.0,1520.0,1520.0,1520.0],[1520.0,1520.0,1520.0,1520.0],[3040.0,3040.0,3040.0,3040.0],[379.975,1519.9,6079.6,24318.4]]},{"yaxis":{"range":[0,31918.320000000003]}}],"label":"selective","method":"update"},{"args":[{"y":[[4.0,4.0,4.0,4.0],[4.0,4.0,4.0,4.0],[8.0,8.0,8.0,8.0],[0.225,0.9,3.6,14.4],[13.3,13.3,13.3,13.3],[13.3,13.3,13.3,13.3],[26.6,26.6,26.6,26.6],[0.58125,2.325,9.3,37.2],[26.0,26.0,26.0,26.0],[26.0,26.0,26.0,26.0],[52.0,52.0,52.0,52.0],[2.8875,11.55,46.2,184.8],[244.0,244.0,244.0,244.0],[244.0,244.0,244.0,244.0],[488.0,488.0,488.0,488.0],[9.10625,36.425,145.7,582.8],[1520.0,1520.0,1520.0,1520.0],[1520.0,1520.0,1520.0,1520.0],[3040.0,3040.0,3040.0,3040.0],[94.99375,379.975,1519.9,6079.6]]},{"yaxis":{"range":[0,12767.580000000002]}}],"label":"full","method":"update"}],"showactive":true,"type":"dropdown","x":1.03,"xanchor":"left","y":0.4,"yanchor":"top"}],"annotations":[{"showarrow":false,"text":"Model Size:","x":1.03,"xanchor":"left","xref":"paper","y":0.6,"yanchor":"bottom","yref":"paper"},{"showarrow":false,"text":"Recomputation:","x":1.03,"xanchor":"left","xref":"paper","y":0.4,"yanchor":"bottom","yref":"paper"}]}, {"displayModeBar": false, "responsive": true, "scrollZoom": false} ) }; </script> </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/src/content/fragments/color-picker.html ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div class="color-picker" style="width:100%; margin: 10px 0;">
2
+ <style>
3
+ .color-picker .picker__stack { display:flex; flex-direction:column; gap:12px; }
4
+ .color-picker .current-card { display:flex; flex-direction: column; align-items:stretch; gap:12px; padding:14px 16px; border:1px solid var(--border-color); background: var(--surface-bg); border-radius: 12px; }
5
+ .color-picker .current-main { display:flex; align-items:center; gap:12px; min-width: 0; }
6
+ .color-picker .current-swatch { width: 32px; height: 32px; border-radius: 8px; border: 1px solid var(--border-color); }
7
+ .color-picker .current-text { display:flex; flex-direction: column; line-height: 1.2; min-width: 0; }
8
+ .color-picker .current-name { font-size: 14px; font-weight: 800; color: var(--text-color); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: clamp(140px, 28vw, 260px); }
9
+ .color-picker .current-hex, .color-picker .current-extra { font-size: 11px; color: var(--muted-color); letter-spacing: .02em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: clamp(140px, 28vw, 260px); }
10
+ /* theme preview styles removed */
11
+ .color-picker .picker__bar { display:flex; align-items:center; gap:12px; }
12
+ .color-picker .picker__label { font-weight:700; font-size: 13px; color: var(--text-color); }
13
+ .color-picker .hue-slider { position:relative; height:16px; border-radius:10px; border:1px solid var(--border-color); background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%); cursor: ew-resize; touch-action: none; flex: 1 1 320px; min-width: 200px; }
14
+ .color-picker .hue-knob { position:absolute; top:50%; left:93.6%; width:14px; height:14px; border-radius:50%; border:2px solid #fff; transform:translate(-50%, -50%); background: var(--surface-bg); z-index: 2; box-shadow: 0 0 0 1px rgba(0,0,0,.05); }
15
+ .color-picker .hue-slider:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
16
+ .color-picker .hue-value { font-variant-numeric: tabular-nums; color: var(--muted-color); min-width: 54px; text-align: right; }
17
+ @media (max-width: 560px) { .color-picker .picker__bar { gap:8px; } }
18
+ </style>
19
+ <div class="picker__stack">
20
+ <div class="current-card">
21
+ <div class="current-main">
22
+ <div class="current-swatch" aria-label="Current color" title="Current color"></div>
23
+ <div class="current-text">
24
+ <div class="current-name">—</div>
25
+ <div class="current-hex">—</div>
26
+ <div class="current-extra current-lch">—</div>
27
+ <div class="current-extra current-rgb">—</div>
28
+ </div>
29
+ </div>
30
+ <div class="picker__bar">
31
+ <div class="picker__label">Hue</div>
32
+ <div class="hue-slider" role="slider" aria-label="Hue" aria-valuemin="0" aria-valuemax="360" aria-valuenow="337" tabindex="0">
33
+ <div class="hue-knob"></div>
34
+ </div>
35
+ <div class="hue-value">337°</div>
36
+ </div>
37
+ </div>
38
+ </div>
39
+ </div>
40
+ <script>
41
+ (() => {
42
+ // Ensure chroma.js is loaded once
43
+ const ensureChroma = (next) => {
44
+ if (window.chroma) return next();
45
+ const loadScript = (id, src, onload, onerror) => {
46
+ let s = document.getElementById(id);
47
+ if (s) { return onload && onload(); }
48
+ s = document.createElement('script');
49
+ s.id = id; s.src = src; s.async = true;
50
+ if (onload) s.addEventListener('load', onload, { once: true });
51
+ if (onerror) s.addEventListener('error', onerror, { once: true });
52
+ document.head.appendChild(s);
53
+ };
54
+ loadScript('chroma-cdn', 'https://unpkg.com/chroma-js@2.4.2/dist/chroma.min.js', next, () => {
55
+ loadScript('chroma-cdn-fallback', 'https://cdnjs.cloudflare.com/ajax/libs/chroma-js/2.4.2/chroma.min.js', next);
56
+ });
57
+ };
58
+
59
+ // Minimal embedded color-name list (same as palettes)
60
+ const COLOR_NAMES = [{"name":"Candy Apple Red","hex":"#ff0800"},{"name":"Boiling Magma","hex":"#ff3300"},{"name":"Aerospace Orange","hex":"#ff4f00"},{"name":"Burtuqali Orange","hex":"#ff6700"},{"name":"American Orange","hex":"#ff8b00"},{"name":"Cheese","hex":"#ffa600"},{"name":"Amber","hex":"#ffbf00"},{"name":"Demonic Yellow","hex":"#ffe700"},{"name":"Bat-Signal","hex":"#feff00"},{"name":"Bitter Lime","hex":"#cfff00"},{"name":"Electric Lime","hex":"#ccff00"},{"name":"Bright Yellow Green","hex":"#9dff00"},{"name":"Lasting Lime","hex":"#88ff00"},{"name":"Bright Green","hex":"#66ff00"},{"name":"Chlorophyll Green","hex":"#4aff00"},{"name":"Green Screen","hex":"#22ff00"},{"name":"Electric Pickle","hex":"#00ff04"},{"name":"Acid","hex":"#00ff22"},{"name":"Lucent Lime","hex":"#00ff33"},{"name":"Cathode Green","hex":"#00ff55"},{"name":"Booger Buster","hex":"#00ff77"},{"name":"Green Gas","hex":"#00ff99"},{"name":"Enthusiasm","hex":"#00ffaa"},{"name":"Ice Ice Baby","hex":"#00ffdd"},{"name":"Master Sword Blue","hex":"#00ffee"},{"name":"Agressive Aqua","hex":"#00fbff"},{"name":"Vivid Sky Blue","hex":"#00ccff"},{"name":"Capri","hex":"#00bfff"},{"name":"Sky of Magritte","hex":"#0099ff"},{"name":"Azure","hex":"#007fff"},{"name":"Blue Ribbon","hex":"#0066ff"},{"name":"Blinking Blue","hex":"#0033ff"},{"name":"Icelandic Water","hex":"#0011ff"},{"name":"Blue","hex":"#0000ff"},{"name":"Blue Pencil","hex":"#2200ff"},{"name":"Electric Ultramarine","hex":"#3f00ff"},{"name":"Aladdin's Feather","hex":"#5500ff"},{"name":"Purple Climax","hex":"#8800ff"},{"name":"Amethyst Ganzstar","hex":"#8f00ff"},{"name":"Electric Purple","hex":"#bf00ff"},{"name":"Phlox","hex":"#df00ff"},{"name":"Brusque Pink","hex":"#ee00ff"},{"name":"Bright Magenta","hex":"#ff08e8"},{"name":"Brutal Pink","hex":"#ff00bb"},{"name":"Mean Girls Lipstick","hex":"#ff00ae"},{"name":"Big Bang Pink","hex":"#ff0099"},{"name":"Flaming Hot Flamingoes","hex":"#ff005d"},{"name":"Blazing Dragonfruit","hex":"#ff0054"},{"name":"Carmine Red","hex":"#ff0038"},{"name":"Bright Red","hex":"#ff000d"}];
61
+ if (!window.__colorNames) window.__colorNames = COLOR_NAMES;
62
+
63
+ // Shared event bus so multiple instances stay in sync
64
+ if (!window.__colorPickerBus) {
65
+ window.__colorPickerBus = (() => {
66
+ let hue = 337; // shared initial hue
67
+ let adjusting = false;
68
+ const listeners = new Set();
69
+ return {
70
+ get: () => ({ hue, adjusting }),
71
+ publish: (sourceId, nextHue, isAdjusting) => {
72
+ hue = ((nextHue % 360) + 360) % 360;
73
+ adjusting = !!isAdjusting;
74
+ listeners.forEach((fn) => { try { fn({ sourceId, hue, adjusting }); } catch {} });
75
+ },
76
+ subscribe: (fn) => { listeners.add(fn); return () => listeners.delete(fn); }
77
+ };
78
+ })();
79
+ }
80
+
81
+ const bootstrap = () => {
82
+ const mount = document.currentScript ? document.currentScript.previousElementSibling : null;
83
+ const root = mount && mount.closest('.color-picker') ? mount.closest('.color-picker') : document.querySelector('.color-picker');
84
+ if (!root || root.dataset.mounted) return; root.dataset.mounted = 'true';
85
+
86
+ const slider = root.querySelector('.hue-slider');
87
+ const knob = root.querySelector('.hue-knob');
88
+ const hueValue = root.querySelector('.hue-value');
89
+ const currentSwatch = root.querySelector('.current-swatch');
90
+ const currentName = root.querySelector('.current-name');
91
+ const currentHex = root.querySelector('.current-hex');
92
+ const currentLch = root.querySelector('.current-lch');
93
+ const currentRgb = root.querySelector('.current-rgb');
94
+
95
+ const bus = window.__colorPickerBus;
96
+ const instanceId = Math.random().toString(36).slice(2);
97
+
98
+ const getName = (hex) => {
99
+ const list = (window.__colorNames && window.__colorNames.length) ? window.__colorNames : COLOR_NAMES;
100
+ if (list && window.chroma) {
101
+ let bestName = null; let best = Infinity;
102
+ for (let i = 0; i < list.length; i++) {
103
+ const item = list[i];
104
+ const d = (chroma.deltaE ? chroma.deltaE(hex, item.hex) : chroma.distance(hex, item.hex, 'lab'));
105
+ if (d < best) { best = d; bestName = item.name; }
106
+ }
107
+ if (bestName) return bestName;
108
+ }
109
+ const hh = chroma(hex).get('hsl.h') || 0;
110
+ const labels = ['Red','Orange','Yellow','Lime','Green','Cyan','Blue','Indigo','Violet','Magenta'];
111
+ const idx = Math.round(((hh % 360) / 360) * (labels.length - 1));
112
+ return labels[idx];
113
+ };
114
+
115
+ const updateUI = (h, adjusting) => {
116
+ const pct = (h / 360) * 100;
117
+ if (knob) knob.style.left = pct + '%';
118
+ if (hueValue) hueValue.textContent = `${Math.round(h)}°`;
119
+ if (slider) slider.setAttribute('aria-valuenow', String(Math.round(h)));
120
+ // Use LCH for consistent chroma across hues
121
+ const L = 70; // lightness
122
+ const C = 60; // chroma kept within sRGB-friendly range
123
+ const base = chroma.lch(L, C, h);
124
+ const baseHex = base.hex();
125
+ if (currentSwatch) currentSwatch.style.background = baseHex;
126
+ if (currentName) currentName.textContent = getName(baseHex.toUpperCase());
127
+ if (currentHex) currentHex.textContent = baseHex.toUpperCase();
128
+ if (currentLch) {
129
+ const lc = base.lch();
130
+ const L = Math.round((lc[0] || 0));
131
+ const C = Math.round((lc[1] || 0));
132
+ const H = Math.round(((lc[2] || 0) % 360 + 360) % 360);
133
+ currentLch.textContent = `LCH ${L}, ${C}, ${H}°`;
134
+ }
135
+ if (currentRgb) {
136
+ const rgb = base.rgb().map(v => Math.round(v));
137
+ currentRgb.textContent = `RGB ${rgb[0]}, ${rgb[1]}, ${rgb[2]}`;
138
+ }
139
+ // Appliquer au thème (toujours, pour refléter la sélection)
140
+ const hoverL = Math.max(0, Math.min(100, L - 8));
141
+ const hoverHex = chroma.lch(hoverL, C, h).hex();
142
+ const rootEl = document.documentElement;
143
+ rootEl.style.setProperty('--primary', baseHex);
144
+ rootEl.style.setProperty('--primary-hover', hoverHex);
145
+ };
146
+
147
+ const getHueFromEvent = (ev) => {
148
+ const rect = slider.getBoundingClientRect();
149
+ const clientX = ev.touches ? ev.touches[0].clientX : ev.clientX;
150
+ const x = clientX - rect.left;
151
+ const t = Math.max(0, Math.min(1, x / rect.width));
152
+ return t * 360;
153
+ };
154
+
155
+ // Subscribe to bus to sync multiple instances
156
+ const unsubscribe = bus.subscribe(({ sourceId, hue, adjusting }) => {
157
+ if (sourceId === instanceId) return; // avoid feedback
158
+ updateUI(hue, adjusting);
159
+ });
160
+
161
+ // Init depuis la couleur de thème si disponible
162
+ try {
163
+ const cssPrimary = getComputedStyle(document.documentElement).getPropertyValue('--primary').trim();
164
+ if (cssPrimary) {
165
+ const initH = chroma(cssPrimary).get('hsl.h') || 0;
166
+ updateUI(initH, false);
167
+ bus.publish(instanceId, initH, false);
168
+ } else {
169
+ const { hue: sharedHue } = bus.get();
170
+ updateUI(sharedHue, false);
171
+ }
172
+ } catch {
173
+ const { hue: sharedHue } = bus.get();
174
+ updateUI(sharedHue, false);
175
+ }
176
+
177
+ const onDown = (ev) => {
178
+ ev.preventDefault();
179
+ const h = getHueFromEvent(ev);
180
+ updateUI(h, true);
181
+ bus.publish(instanceId, h, true);
182
+ const move = (e) => { e.preventDefault && e.preventDefault(); const hh = getHueFromEvent(e); updateUI(hh, true); bus.publish(instanceId, hh, true); };
183
+ const up = () => { bus.publish(instanceId, getHueFromEvent(ev), false); window.removeEventListener('mousemove', move); window.removeEventListener('touchmove', move); window.removeEventListener('mouseup', up); window.removeEventListener('touchend', up); };
184
+ window.addEventListener('mousemove', move, { passive: false });
185
+ window.addEventListener('touchmove', move, { passive: false });
186
+ window.addEventListener('mouseup', up, { once: true });
187
+ window.addEventListener('touchend', up, { once: true });
188
+ };
189
+
190
+ if (slider) {
191
+ slider.addEventListener('mousedown', onDown);
192
+ slider.addEventListener('touchstart', onDown, { passive: false });
193
+ // Minimal support clavier (←/→, ⇧ pour pas trop saccader)
194
+ slider.addEventListener('keydown', (e) => {
195
+ const step = e.shiftKey ? 10 : 2;
196
+ if (e.key === 'ArrowLeft') { e.preventDefault(); const { hue } = bus.get(); const h = hue - step; updateUI(h, true); bus.publish(instanceId, h, true); bus.publish(instanceId, h, false); }
197
+ if (e.key === 'ArrowRight') { e.preventDefault(); const { hue } = bus.get(); const h = hue + step; updateUI(h, true); bus.publish(instanceId, h, true); bus.publish(instanceId, h, false); }
198
+ });
199
+ }
200
+
201
+ // Clean up on detach (best-effort)
202
+ const ro = new MutationObserver(() => {
203
+ if (!document.body.contains(root)) { unsubscribe && unsubscribe(); ro.disconnect(); }
204
+ });
205
+ ro.observe(document.body, { childList: true, subtree: true });
206
+ };
207
+
208
+ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', () => ensureChroma(bootstrap), { once: true });
209
+ else ensureChroma(bootstrap);
210
+ })();
211
+ </script>
212
+
213
+
app/src/content/fragments/d3-bar.html ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div class="d3-bar" style="width:100%;margin:10px 0;"></div>
2
+ <style>
3
+ .d3-bar .controls { margin-top: 12px; display: flex; gap: 16px; align-items: center; flex-wrap: wrap; }
4
+ .d3-bar .controls label { font-size: 12px; color: var(--muted-color); display: flex; align-items: center; gap: 8px; white-space: nowrap; padding: 6px 10px; }
5
+ .d3-bar .controls select { font-size: 12px; padding: 8px 28px 8px 10px; border: 1px solid var(--border-color); border-radius: 8px; background-color: var(--surface-bg); color: var(--text-color); 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='%230f1115' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 8px center; background-size: 12px; -webkit-appearance: none; -moz-appearance: none; appearance: none; cursor: pointer; transition: border-color .15s ease, box-shadow .15s ease; }
6
+ [data-theme="dark"] .d3-bar .controls select { 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"); }
7
+ .d3-bar .controls select:hover { border-color: var(--primary); }
8
+ .d3-bar .controls select:focus { border-color: var(--primary); box-shadow: 0 0 0 3px rgba(232,137,171,.25); outline: none; }
9
+ .d3-bar .legend { font-size: 12px; line-height: 1.35; color: var(--text-color); }
10
+ </style>
11
+ <script>
12
+ (() => {
13
+ const ensureD3 = (cb) => {
14
+ if (window.d3 && typeof window.d3.select === 'function') return cb();
15
+ let s = document.getElementById('d3-cdn-script');
16
+ if (!s) { s = document.createElement('script'); s.id = 'd3-cdn-script'; s.src = 'https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js'; document.head.appendChild(s); }
17
+ const onReady = () => { if (window.d3 && typeof window.d3.select === 'function') cb(); };
18
+ s.addEventListener('load', onReady, { once: true });
19
+ if (window.d3) onReady();
20
+ };
21
+
22
+ const bootstrap = () => {
23
+ const mount = document.currentScript ? document.currentScript.previousElementSibling : null;
24
+ const container = (mount && mount.querySelector && mount.querySelector('.d3-bar')) || document.querySelector('.d3-bar');
25
+ if (!container) return;
26
+ if (container.dataset) { if (container.dataset.mounted === 'true') return; container.dataset.mounted = 'true'; }
27
+
28
+ // Data, matching bar.py
29
+ const seqLabels = ["1024","2048","4096","8192"];
30
+ const seqScale = [1,2,4,8];
31
+ const components = [
32
+ { key: 'parameters', color: 'rgb(78, 165, 183)' },
33
+ { key: 'gradients', color: 'rgb(227, 138, 66)' },
34
+ { key: 'optimizer', color: 'var(--primary)' },
35
+ { key: 'activations', color: 'rgb(206, 192, 250)' },
36
+ ];
37
+ const modelSizes = ["1B","3B","8B","70B","405B"];
38
+ const paramsMem = { "1B":4.0, "3B":13.3, "8B":26.0, "70B":244.0, "405B":1520.0 };
39
+ const actCoeff = { "1B":3.6, "3B":9.3, "8B":46.2, "70B":145.7, "405B":1519.9 };
40
+ const recomputeModes = ["none","selective","full"];
41
+
42
+ const activationsCurve = (sizeKey, mode) => {
43
+ const coeff = actCoeff[sizeKey];
44
+ let arr = seqScale.map((v) => coeff * (v * v));
45
+ if (mode === 'selective') arr = arr.map((v) => v * 0.25);
46
+ else if (mode === 'full') arr = arr.map((v) => v * (1 / 16));
47
+ return arr;
48
+ };
49
+ const stackFor = (sizeKey, mode) => {
50
+ const p = seqScale.map(() => paramsMem[sizeKey]);
51
+ const g = seqScale.map(() => paramsMem[sizeKey]);
52
+ const o = seqScale.map(() => 2*paramsMem[sizeKey]);
53
+ const a = activationsCurve(sizeKey, mode);
54
+ return { parameters: p, gradients: g, optimizer: o, activations: a };
55
+ };
56
+
57
+ const Y = {}; // Y[mode][size][component] => array
58
+ recomputeModes.forEach((m) => {
59
+ Y[m] = {}; modelSizes.forEach((s) => { Y[m][s] = stackFor(s, m); });
60
+ });
61
+
62
+ // Controls
63
+ const controls = document.createElement('div');
64
+ controls.className = 'controls';
65
+ const labelSize = document.createElement('label'); labelSize.textContent = 'Model Size';
66
+ const selSize = document.createElement('select'); modelSizes.forEach((s) => { const o = document.createElement('option'); o.value = s; o.textContent = s; selSize.appendChild(o); });
67
+ labelSize.appendChild(selSize);
68
+ const labelRecomp = document.createElement('label'); labelRecomp.textContent = 'Recomputation';
69
+ const selRecomp = document.createElement('select'); recomputeModes.forEach((m) => { const o = document.createElement('option'); o.value = m; o.textContent = m; selRecomp.appendChild(o); });
70
+ labelRecomp.appendChild(selRecomp);
71
+
72
+ // SVG scaffolding
73
+ const svg = d3.select(container).append('svg').attr('width','100%').style('display','block');
74
+ const gRoot = svg.append('g');
75
+ const gGrid = gRoot.append('g').attr('class','grid');
76
+ const gAxes = gRoot.append('g').attr('class','axes');
77
+ const gBars = gRoot.append('g').attr('class','bars');
78
+ const gLegend = gRoot.append('foreignObject').attr('class','legend');
79
+
80
+ // Tooltip
81
+ container.style.position = container.style.position || 'relative';
82
+ let tip = container.querySelector('.d3-tooltip'); let tipInner;
83
+ if (!tip) { tip = document.createElement('div'); tip.className = 'd3-tooltip'; Object.assign(tip.style,{ position:'absolute', top:'0px', left:'0px', transform:'translate(-9999px, -9999px)', pointerEvents:'none', padding:'8px 10px', borderRadius:'8px', fontSize:'12px', lineHeight:'1.35', border:'1px solid var(--border-color)', background:'var(--surface-bg)', color:'var(--text-color)', boxShadow:'0 4px 24px rgba(0,0,0,.18)', opacity:'0', transition:'opacity .12s ease' }); tipInner = document.createElement('div'); tipInner.className = 'd3-tooltip__inner'; tipInner.style.textAlign='left'; tip.appendChild(tipInner); container.appendChild(tip); } else { tipInner = tip.querySelector('.d3-tooltip__inner') || tip; }
84
+
85
+ // State
86
+ let currentSize = modelSizes[0];
87
+ let currentMode = 'none';
88
+
89
+ // Layout & scales
90
+ let width=800, height=360; const margin = { top: 16, right: 28, bottom: 56, left: 64 };
91
+ const x0 = d3.scaleBand().paddingInner(0.25).paddingOuter(0.1); // groups (seq)
92
+ const y = d3.scaleLinear();
93
+ const colorOf = (key) => components.find((c)=>c.key===key).color;
94
+
95
+ function yMax(sizeKey, mode){
96
+ const s = Y[mode][sizeKey];
97
+ let max = 0; for (let i=0;i<seqLabels.length;i++){ const sum = s.parameters[i]+s.gradients[i]+s.optimizer[i]+s.activations[i]; if (sum>max) max=sum; }
98
+ return max*1.05;
99
+ }
100
+
101
+ function renderLegend(innerWidth, innerHeight){
102
+ const legendWidth = 160, legendHeight = 84;
103
+ gLegend.attr('x', 15).attr('y', -3).attr('width', legendWidth).attr('height', legendHeight);
104
+ const root = gLegend.selectAll('div').data([0]).join('xhtml:div');
105
+ root.html(`
106
+ <div style="display:flex;flex-direction:column;gap:6px;">
107
+ ${components.map(c => `<div style="display:flex;align-items:center;gap:8px;"><span style="width:18px;height:10px;background:${c.color};border-radius:2px;display:inline-block"></span><span>${c.key}</span></div>`).join('')}
108
+ </div>
109
+ `);
110
+ }
111
+
112
+ function updateScales(){
113
+ const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
114
+ const axisColor = isDark ? 'rgba(255,255,255,0.25)' : 'rgba(0,0,0,0.25)';
115
+ const tickColor = isDark ? 'rgba(255,255,255,0.70)' : 'rgba(0,0,0,0.55)';
116
+ const gridColor = isDark ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.05)';
117
+
118
+ width = container.clientWidth || 800; height = Math.max(260, Math.round(width/3)); svg.attr('width', width).attr('height', height);
119
+ const innerWidth = width - margin.left - margin.right; const innerHeight = height - margin.top - margin.bottom; gRoot.attr('transform', `translate(${margin.left},${margin.top})`);
120
+
121
+ x0.domain(seqLabels).range([0, innerWidth]);
122
+ y.domain([0, yMax(currentSize, currentMode)]).range([innerHeight, 0]).nice();
123
+
124
+ // Grid
125
+ gGrid.selectAll('*').remove();
126
+ gGrid.selectAll('line').data(y.ticks(6)).join('line')
127
+ .attr('x1', 0).attr('x2', innerWidth).attr('y1', (d)=>y(d)).attr('y2', (d)=>y(d))
128
+ .attr('stroke', gridColor).attr('stroke-width', 1).attr('shape-rendering', 'crispEdges');
129
+
130
+ // Axes
131
+ gAxes.selectAll('*').remove();
132
+ gAxes.append('g').attr('transform', `translate(0,${innerHeight})`).call(d3.axisBottom(x0)).call((g)=>{ g.selectAll('path, line').attr('stroke', axisColor); g.selectAll('text').attr('fill', tickColor).style('font-size','12px'); });
133
+ gAxes.append('g').call(d3.axisLeft(y).ticks(6).tickFormat(d3.format('.2f'))).call((g)=>{ g.selectAll('path, line').attr('stroke', axisColor); g.selectAll('text').attr('fill', tickColor).style('font-size','12px'); });
134
+
135
+ // Axis labels
136
+ gAxes.append('text').attr('class','axis-label axis-label--x').attr('x', innerWidth/2).attr('y', innerHeight + 44).attr('text-anchor','middle').style('font-size','12px').style('fill', tickColor).text('Sequence Length');
137
+ gAxes.append('text').attr('class','axis-label axis-label--y').attr('text-anchor','middle').attr('transform', `translate(${-52},${innerHeight/2}) rotate(-90)`).style('font-size','12px').style('fill', tickColor).text('Memory (GB)');
138
+
139
+ renderLegend(innerWidth, innerHeight);
140
+
141
+ return { innerWidth, innerHeight };
142
+ }
143
+
144
+ function drawBars(){
145
+ const stacks = Y[currentMode][currentSize];
146
+ const series = components.map((c)=>({ key: c.key, color: c.color, values: stacks[c.key] }));
147
+ // Stack values
148
+ const stacked = seqLabels.map((label, i) => {
149
+ let acc = 0; const items = [];
150
+ series.forEach((s) => { const y0 = acc; const y1 = acc + s.values[i]; items.push({ key: s.key, color: s.color, i, y0, y1, xLabel: label, value: s.values[i] }); acc = y1; });
151
+ return { label, items };
152
+ });
153
+
154
+ const { innerWidth, innerHeight } = updateScales();
155
+
156
+ const bandWidth = x0.bandwidth();
157
+ const groups = gBars.selectAll('g.bar-group').data(stacked, d=>d.label);
158
+ const groupsEnter = groups.enter().append('g').attr('class','bar-group');
159
+ groupsEnter.merge(groups).attr('transform', (d)=>`translate(${x0(d.label)},0)`);
160
+ groups.exit().remove();
161
+
162
+ const rects = groupsEnter.merge(groups).selectAll('rect.bar').data(d=>d.items, d=>d.key);
163
+ rects.enter().append('rect').attr('class','bar').attr('x', 0).attr('width', bandWidth)
164
+ .attr('y', (d)=>y(d.y1)).attr('height', (d)=>Math.max(0.5, y(d.y0) - y(d.y1)))
165
+ .attr('fill', (d)=>d.color)
166
+ .on('mouseenter', function(ev, d){
167
+ d3.select(this).attr('stroke', 'rgba(0,0,0,0.85)').attr('stroke-width', 1);
168
+ tipInner.innerHTML = `<div><strong>${d.key}</strong></div><div><strong>Seq</strong> ${d.xLabel}</div><div><strong>Mem</strong> ${d.value.toFixed(1)} GB</div>`;
169
+ tip.style.opacity = '1';
170
+ })
171
+ .on('mousemove', function(ev, d){
172
+ const [mx, my] = d3.pointer(ev, container); const offsetX = 12, offsetY = 12; tip.style.transform = `translate(${Math.round(mx+offsetX)}px, ${Math.round(my+offsetY)}px)`;
173
+ })
174
+ .on('mouseleave', function(){ tip.style.opacity='0'; tip.style.transform='translate(-9999px, -9999px)'; d3.select(this).attr('stroke','none'); })
175
+ .merge(rects)
176
+ .transition().duration(200)
177
+ .attr('width', bandWidth)
178
+ .attr('y', (d)=>y(d.y1)).attr('height', (d)=>Math.max(0.5, y(d.y0) - y(d.y1)))
179
+ .attr('fill', (d)=>d.color);
180
+ rects.exit().remove();
181
+ }
182
+
183
+ function update(){ drawBars(); }
184
+
185
+ // Boot
186
+ update();
187
+ container.appendChild(controls);
188
+ controls.appendChild(labelSize); controls.appendChild(labelRecomp);
189
+ selSize.addEventListener('change', (e)=>{ currentSize = e.target.value; update(); });
190
+ selRecomp.addEventListener('change', (e)=>{ currentMode = e.target.value; update(); });
191
+
192
+ const rerender = () => { update(); };
193
+ if (window.ResizeObserver) { const ro = new ResizeObserver(()=>rerender()); ro.observe(container); } else { window.addEventListener('resize', rerender); }
194
+ };
195
+
196
+ if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => ensureD3(bootstrap), { once: true }); } else { ensureD3(bootstrap); }
197
+ })();
198
+ </script>
199
+
200
+
app/src/content/fragments/d3-line.html ADDED
@@ -0,0 +1,429 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div class="d3-line" style="width:100%;margin:10px 0;"></div>
2
+ <style>
3
+ .d3-line .d3-line__controls select {
4
+ font-size: 12px;
5
+ padding: 8px 28px 8px 10px;
6
+ border: 1px solid var(--border-color);
7
+ border-radius: 8px;
8
+ background-color: var(--surface-bg);
9
+ color: var(--text-color);
10
+ 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='%230f1115' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
11
+ background-repeat: no-repeat;
12
+ background-position: right 8px center;
13
+ background-size: 12px;
14
+ -webkit-appearance: none;
15
+ -moz-appearance: none;
16
+ appearance: none;
17
+ cursor: pointer;
18
+ transition: border-color .15s ease, box-shadow .15s ease;
19
+ }
20
+ [data-theme="dark"] .d3-line .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-line .d3-line__controls select:hover {
24
+ border-color: var(--primary);
25
+ }
26
+ .d3-line .d3-line__controls select:focus {
27
+ border-color: var(--primary);
28
+ box-shadow: 0 0 0 3px rgba(232,137,171,.25);
29
+ outline: none;
30
+ }
31
+ .d3-line .d3-line__controls label { gap: 8px; }
32
+
33
+ /* Range slider themed with --primary */
34
+ .d3-line .d3-line__controls input[type="range"] {
35
+ -webkit-appearance: none;
36
+ appearance: none;
37
+ width: 100%;
38
+ height: 6px;
39
+ border-radius: 999px;
40
+ background: var(--border-color);
41
+ outline: none;
42
+ }
43
+ .d3-line .d3-line__controls input[type="range"]::-webkit-slider-runnable-track {
44
+ height: 6px;
45
+ background: transparent;
46
+ border-radius: 999px;
47
+ }
48
+ .d3-line .d3-line__controls input[type="range"]::-webkit-slider-thumb {
49
+ -webkit-appearance: none;
50
+ appearance: none;
51
+ width: 16px;
52
+ height: 16px;
53
+ border-radius: 50%;
54
+ background: var(--primary);
55
+ border: 2px solid var(--on-primary);
56
+ margin-top: -5px;
57
+ cursor: pointer;
58
+ }
59
+ .d3-line .d3-line__controls input[type="range"]::-moz-range-track {
60
+ height: 6px;
61
+ background: transparent;
62
+ border-radius: 999px;
63
+ }
64
+ .d3-line .d3-line__controls input[type="range"]::-moz-range-thumb {
65
+ width: 16px;
66
+ height: 16px;
67
+ border-radius: 50%;
68
+ background: var(--primary);
69
+ border: 2px solid var(--on-primary);
70
+ cursor: pointer;
71
+ }
72
+ /* Improved line color via CSS */
73
+ .d3-line .lines path.improved { stroke: var(--primary); }
74
+ </style>
75
+ <script>
76
+ (() => {
77
+ const ensureD3 = (cb) => {
78
+ if (window.d3 && typeof window.d3.select === 'function') return cb();
79
+ let s = document.getElementById('d3-cdn-script');
80
+ if (!s) {
81
+ s = document.createElement('script');
82
+ s.id = 'd3-cdn-script';
83
+ s.src = 'https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js';
84
+ document.head.appendChild(s);
85
+ }
86
+ const onReady = () => { if (window.d3 && typeof window.d3.select === 'function') cb(); };
87
+ s.addEventListener('load', onReady, { once: true });
88
+ if (window.d3) onReady();
89
+ };
90
+
91
+ const bootstrap = () => {
92
+ const mount = document.currentScript ? document.currentScript.previousElementSibling : null;
93
+ const container = (mount && mount.querySelector && mount.querySelector('.d3-line')) || document.querySelector('.d3-line');
94
+ if (!container) return;
95
+ if (container.dataset) {
96
+ if (container.dataset.mounted === 'true') return;
97
+ container.dataset.mounted = 'true';
98
+ }
99
+
100
+ // Dataset params matching the Plotly version
101
+ const datasets = [
102
+ { name: 'CIFAR-10', base: { ymin:0.10, ymax:0.90, k:10.0, x0:0.55 }, aug: { ymin:0.15, ymax:0.96, k:12.0, x0:0.40 }, target: 0.97 },
103
+ { name: 'CIFAR-100', base: { ymin:0.05, ymax:0.70, k: 9.5, x0:0.60 }, aug: { ymin:0.08, ymax:0.80, k:11.0, x0:0.45 }, target: 0.85 },
104
+ { name: 'ImageNet-1K', base: { ymin:0.02, ymax:0.68, k: 8.5, x0:0.65 }, aug: { ymin:0.04, ymax:0.75, k: 9.5, x0:0.50 }, target: 0.82 },
105
+ ];
106
+
107
+ // Controls UI
108
+ const controls = document.createElement('div');
109
+ controls.className = 'd3-line__controls';
110
+ Object.assign(controls.style, {
111
+ marginTop: '12px',
112
+ display: 'flex',
113
+ gap: '16px',
114
+ alignItems: 'center'
115
+ });
116
+
117
+ const labelDs = document.createElement('label');
118
+ Object.assign(labelDs.style, {
119
+ fontSize: '12px', color: 'rgba(0,0,0,.65)', display: 'flex', alignItems: 'center', gap: '6px', whiteSpace: 'nowrap', padding: '6px 10px'
120
+ });
121
+ labelDs.textContent = 'Dataset';
122
+ const selectDs = document.createElement('select');
123
+ Object.assign(selectDs.style, { fontSize: '12px' });
124
+ datasets.forEach((d, i) => {
125
+ const o = document.createElement('option');
126
+ o.value = String(i);
127
+ o.textContent = d.name;
128
+ selectDs.appendChild(o);
129
+ });
130
+ labelDs.appendChild(selectDs);
131
+
132
+ const labelAlpha = document.createElement('label');
133
+ Object.assign(labelAlpha.style, {
134
+ fontSize: '12px', color: 'rgba(0,0,0,.65)', display: 'flex', alignItems: 'center', gap: '10px', flex: '1', padding: '6px 10px'
135
+ });
136
+ labelAlpha.appendChild(document.createTextNode('Augmentation α'));
137
+ const slider = document.createElement('input');
138
+ slider.type = 'range'; slider.min = '0'; slider.max = '1'; slider.step = '0.01'; slider.value = '0.70';
139
+ Object.assign(slider.style, { flex: '1' });
140
+ const alphaVal = document.createElement('span'); alphaVal.className = 'alpha-value'; alphaVal.textContent = slider.value;
141
+ labelAlpha.appendChild(slider);
142
+ labelAlpha.appendChild(alphaVal);
143
+
144
+ controls.appendChild(labelDs);
145
+ controls.appendChild(labelAlpha);
146
+
147
+ // Create SVG
148
+ const svg = d3.select(container).append('svg')
149
+ .attr('width', '100%')
150
+ .style('display', 'block');
151
+
152
+ // Groups
153
+ const gRoot = svg.append('g');
154
+ const gGrid = gRoot.append('g').attr('class', 'grid');
155
+ const gAxes = gRoot.append('g').attr('class', 'axes');
156
+ const gLines = gRoot.append('g').attr('class', 'lines');
157
+ const gHover = gRoot.append('g').attr('class', 'hover');
158
+ const gLegend = gRoot.append('foreignObject').attr('class', 'legend');
159
+
160
+ // Tooltip
161
+ container.style.position = container.style.position || 'relative';
162
+ let tip = container.querySelector('.d3-tooltip');
163
+ let tipInner;
164
+ if (!tip) {
165
+ tip = document.createElement('div');
166
+ tip.className = 'd3-tooltip';
167
+ Object.assign(tip.style, {
168
+ position: 'absolute', top: '0px', left: '0px', transform: 'translate(-9999px, -9999px)', pointerEvents: 'none',
169
+ padding: '8px 10px', borderRadius: '8px', fontSize: '12px', lineHeight: '1.35', border: '1px solid var(--border-color)',
170
+ background: 'var(--surface-bg)', color: 'var(--text-color)', boxShadow: '0 4px 24px rgba(0,0,0,.18)', opacity: '0',
171
+ transition: 'opacity .12s ease'
172
+ });
173
+ tipInner = document.createElement('div');
174
+ tipInner.className = 'd3-tooltip__inner';
175
+ tipInner.style.textAlign = 'left';
176
+ tip.appendChild(tipInner);
177
+ container.appendChild(tip);
178
+ } else {
179
+ tipInner = tip.querySelector('.d3-tooltip__inner') || tip;
180
+ }
181
+
182
+ // Colors
183
+ const colorBase = '#64748b'; // slate-500
184
+ const colorImproved = 'var(--primary)';
185
+ const colorTarget = '#4b5563'; // gray-600
186
+ const legendBgLight = 'rgba(255,255,255,0.85)';
187
+ const legendBgDark = 'rgba(17,17,23,0.85)';
188
+
189
+ // Data and helpers
190
+ const N = 240;
191
+ const xs = Array.from({ length: N }, (_, i) => i / (N - 1));
192
+ const logistic = (x, { ymin, ymax, k, x0 }) => ymin + (ymax - ymin) / (1 + Math.exp(-k * (x - x0)));
193
+ const blend = (l, e, a) => (1 - a) * l + a * e;
194
+
195
+ let datasetIndex = 0;
196
+ let alpha = parseFloat(slider.value) || 0.7;
197
+
198
+ let yBase = [];
199
+ let yAug = [];
200
+ let yImp = [];
201
+ let yTgt = [];
202
+
203
+ function computeCurves() {
204
+ const d = datasets[datasetIndex];
205
+ yBase = xs.map((x) => logistic(x, d.base));
206
+ yAug = xs.map((x) => logistic(x, d.aug));
207
+ yTgt = xs.map(() => d.target);
208
+ yImp = yBase.map((v, i) => blend(v, yAug[i], alpha));
209
+ }
210
+
211
+ // Scales and layout
212
+ let width = 800, height = 360;
213
+ let margin = { top: 16, right: 28, bottom: 56, left: 64 };
214
+ let xScale = d3.scaleLinear();
215
+ let yScale = d3.scaleLinear();
216
+
217
+ // Paths
218
+ const lineGen = d3.line()
219
+ .curve(d3.curveCatmullRom.alpha(0.6))
220
+ .x((d, i) => xScale(xs[i]))
221
+ .y((d) => yScale(d));
222
+
223
+ const pathBase = gLines.append('path').attr('fill', 'none').attr('stroke', colorBase).attr('stroke-width', 2);
224
+ const pathImp = gLines.append('path').attr('class', 'improved').attr('fill', 'none').style('stroke', 'var(--primary)').attr('stroke-width', 2);
225
+ const pathTgt = gLines.append('path').attr('fill', 'none').attr('stroke', colorTarget).attr('stroke-width', 2).attr('stroke-dasharray', '6,6');
226
+
227
+ // Hover elements
228
+ const hoverLine = gHover.append('line').attr('stroke-width', 1);
229
+ const hoverDotB = gHover.append('circle').attr('r', 3.5).attr('fill', colorBase).attr('stroke', '#fff').attr('stroke-width', 1);
230
+ const hoverDotI = gHover.append('circle').attr('class', 'improved').attr('r', 3.5).style('fill', 'var(--primary)').attr('stroke', '#fff').attr('stroke-width', 1);
231
+ const hoverDotT = gHover.append('circle').attr('r', 3.5).attr('fill', colorTarget).attr('stroke', '#fff').attr('stroke-width', 1);
232
+
233
+ const overlay = gHover.append('rect').attr('fill', 'transparent').style('cursor', 'crosshair');
234
+
235
+ function updateScales() {
236
+ const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
237
+ const axisColor = isDark ? 'rgba(255,255,255,0.25)' : 'rgba(0,0,0,0.25)';
238
+ const tickColor = isDark ? 'rgba(255,255,255,0.70)' : 'rgba(0,0,0,0.55)';
239
+ const gridColor = isDark ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.05)';
240
+
241
+ width = container.clientWidth || 800;
242
+ height = Math.max(260, Math.round(width / 3));
243
+ svg.attr('width', width).attr('height', height);
244
+
245
+ const innerWidth = width - margin.left - margin.right;
246
+ const innerHeight = height - margin.top - margin.bottom;
247
+ gRoot.attr('transform', `translate(${margin.left},${margin.top})`);
248
+
249
+ xScale.domain([0, 1]).range([0, innerWidth]);
250
+ yScale.domain([0, 1]).range([innerHeight, 0]);
251
+
252
+ // Grid (horizontal)
253
+ gGrid.selectAll('*').remove();
254
+ const yTicks = yScale.ticks(6);
255
+ gGrid.selectAll('line')
256
+ .data(yTicks)
257
+ .join('line')
258
+ .attr('x1', 0)
259
+ .attr('x2', innerWidth)
260
+ .attr('y1', (d) => yScale(d))
261
+ .attr('y2', (d) => yScale(d))
262
+ .attr('stroke', gridColor)
263
+ .attr('stroke-width', 1)
264
+ .attr('shape-rendering', 'crispEdges');
265
+
266
+ // Axes
267
+ gAxes.selectAll('*').remove();
268
+ const xAxis = d3.axisBottom(xScale).ticks(8).tickSizeOuter(0);
269
+ const yAxis = d3.axisLeft(yScale).ticks(6).tickSizeOuter(0).tickFormat(d3.format('.2f'));
270
+ gAxes.append('g')
271
+ .attr('transform', `translate(0,${innerHeight})`)
272
+ .call(xAxis)
273
+ .call((g) => {
274
+ g.selectAll('path, line').attr('stroke', axisColor);
275
+ g.selectAll('text').attr('fill', tickColor).style('font-size', '12px');
276
+ });
277
+ gAxes.append('g')
278
+ .call(yAxis)
279
+ .call((g) => {
280
+ g.selectAll('path, line').attr('stroke', axisColor);
281
+ g.selectAll('text').attr('fill', tickColor).style('font-size', '12px');
282
+ });
283
+
284
+ // Axis labels (X and Y)
285
+ gAxes.append('text')
286
+ .attr('class', 'axis-label axis-label--x')
287
+ .attr('x', innerWidth / 2)
288
+ .attr('y', innerHeight + 44)
289
+ .attr('text-anchor', 'middle')
290
+ .style('font-size', '12px')
291
+ .style('fill', tickColor)
292
+ .text('Epoch');
293
+ gAxes.append('text')
294
+ .attr('class', 'axis-label axis-label--y')
295
+ .attr('text-anchor', 'middle')
296
+ .attr('transform', `translate(${-52},${innerHeight/2}) rotate(-90)`)
297
+ .style('font-size', '12px')
298
+ .style('fill', tickColor)
299
+ .text('Accuracy');
300
+
301
+ overlay.attr('x', 0).attr('y', 0).attr('width', innerWidth).attr('height', innerHeight);
302
+ hoverLine.attr('y1', 0).attr('y2', innerHeight).attr('stroke', axisColor);
303
+
304
+ // Legend inside plot (bottom-right), no background/border/shadow
305
+ const legendWidth = Math.min(180, Math.max(120, Math.round(innerWidth * 0.22)));
306
+ const legendHeight = 64;
307
+ gLegend
308
+ .attr('x', innerWidth - legendWidth + 42)
309
+ .attr('y', innerHeight - legendHeight - 12)
310
+ .attr('width', legendWidth)
311
+ .attr('height', legendHeight);
312
+ const legendRoot = gLegend.selectAll('div').data([0]).join('xhtml:div');
313
+ Object.assign(legendRoot.node().style, {
314
+ background: 'transparent',
315
+ border: 'none',
316
+ borderRadius: '0',
317
+ padding: '0',
318
+ fontSize: '12px',
319
+ lineHeight: '1.35',
320
+ color: 'var(--text-color)'
321
+ });
322
+ legendRoot.html(`
323
+ <div style="display:flex;flex-direction:column;gap:6px;">
324
+ <div style="display:flex;align-items:center;gap:8px;">
325
+ <span style="width:18px;height:3px;background:${colorBase};border-radius:2px;display:inline-block"></span>
326
+ <span>Baseline</span>
327
+ </div>
328
+ <div style="display:flex;align-items:center;gap:8px;">
329
+ <span style="width:18px;height:3px;background:${colorImproved};border-radius:2px;display:inline-block"></span>
330
+ <span>Improved</span>
331
+ </div>
332
+ <div style="display:flex;align-items:center;gap:8px;">
333
+ <span style="width:18px;height:0;border-top:2px dashed ${colorTarget};display:inline-block"></span>
334
+ <span>Target</span>
335
+ </div>
336
+ </div>
337
+ `);
338
+ }
339
+
340
+ function updatePaths() {
341
+ pathBase.transition().duration(200).attr('d', lineGen(yBase));
342
+ pathImp.transition().duration(200).attr('d', lineGen(yImp));
343
+ pathTgt.transition().duration(200).attr('d', lineGen(yTgt));
344
+ }
345
+
346
+ function updateAlpha(a) {
347
+ alpha = a;
348
+ alphaVal.textContent = a.toFixed(2);
349
+ yImp = yBase.map((v, i) => blend(v, yAug[i], alpha));
350
+ pathImp.transition().duration(80).attr('d', lineGen(yImp));
351
+ }
352
+
353
+ function applyDataset() {
354
+ computeCurves();
355
+ updatePaths();
356
+ }
357
+
358
+ // Hover interactions
359
+ function onMove(event) {
360
+ const [mx, my] = d3.pointer(event, overlay.node());
361
+ const xi = Math.max(0, Math.min(N - 1, Math.round(xScale.invert(mx) * (N - 1))));
362
+ const xpx = xScale(xs[xi]);
363
+ const yb = yBase[xi], yi = yImp[xi], yt = yTgt[xi];
364
+ hoverLine.attr('x1', xpx).attr('x2', xpx).style('display', null);
365
+ hoverDotB.attr('cx', xpx).attr('cy', yScale(yb)).style('display', null);
366
+ hoverDotI.attr('cx', xpx).attr('cy', yScale(yi)).style('display', null);
367
+ hoverDotT.attr('cx', xpx).attr('cy', yScale(yt)).style('display', null);
368
+
369
+ // Tooltip content
370
+ const ds = datasets[datasetIndex].name;
371
+ tipInner.innerHTML = `<div><strong>${ds}</strong></div>` +
372
+ `<div><strong>x</strong> ${xs[xi].toFixed(2)}</div>` +
373
+ `<div><span style="display:inline-block;width:10px;height:10px;background:${colorBase};border-radius:50%;margin-right:6px;"></span><strong>Baseline</strong> ${yb.toFixed(3)}</div>` +
374
+ `<div><span style="display:inline-block;width:10px;height:10px;background:${colorImproved};border-radius:50%;margin-right:6px;"></span><strong>Improved</strong> ${yi.toFixed(3)}</div>` +
375
+ `<div><span style="display:inline-block;width:10px;height:10px;background:${colorTarget};border-radius:50%;margin-right:6px;"></span><strong>Target</strong> ${yt.toFixed(3)}</div>`;
376
+ const offsetX = 12, offsetY = 12;
377
+ tip.style.opacity = '1';
378
+ tip.style.transform = `translate(${Math.round(mx + offsetX + margin.left)}px, ${Math.round(my + offsetY + margin.top)}px)`;
379
+ }
380
+
381
+ function onLeave() {
382
+ tip.style.opacity = '0';
383
+ tip.style.transform = 'translate(-9999px, -9999px)';
384
+ hoverLine.style('display', 'none');
385
+ hoverDotB.style('display', 'none');
386
+ hoverDotI.style('display', 'none');
387
+ hoverDotT.style('display', 'none');
388
+ }
389
+
390
+ overlay.on('mousemove', onMove).on('mouseleave', onLeave);
391
+
392
+ // Init + controls wiring
393
+ computeCurves();
394
+ updateScales();
395
+ updatePaths();
396
+
397
+ // Attach controls after SVG for consistency with Plotly fragment
398
+ container.appendChild(controls);
399
+
400
+ selectDs.addEventListener('change', (e) => {
401
+ datasetIndex = parseInt(e.target.value) || 0;
402
+ applyDataset();
403
+ });
404
+ slider.addEventListener('input', (e) => {
405
+ const a = parseFloat(e.target.value) || 0;
406
+ updateAlpha(a);
407
+ });
408
+
409
+ // Resize handling
410
+ const render = () => {
411
+ updateScales();
412
+ updatePaths();
413
+ };
414
+ if (window.ResizeObserver) {
415
+ const ro = new ResizeObserver(() => render());
416
+ ro.observe(container);
417
+ } else {
418
+ window.addEventListener('resize', render);
419
+ }
420
+ render();
421
+ };
422
+
423
+ if (document.readyState === 'loading') {
424
+ document.addEventListener('DOMContentLoaded', () => ensureD3(bootstrap), { once: true });
425
+ } else { ensureD3(bootstrap); }
426
+ })();
427
+ </script>
428
+
429
+
app/src/content/fragments/heatmap.html CHANGED
@@ -1 +1,159 @@
1
- <div> <div id="47076730-9b36-4ef3-9cd9-80fa6f5baf65" class="plotly-graph-div" style="height:100%; width:100%;"></div> <script type="text/javascript"> window.PLOTLYENV=window.PLOTLYENV || {}; if (document.getElementById("47076730-9b36-4ef3-9cd9-80fa6f5baf65")) { Plotly.newPlot( "47076730-9b36-4ef3-9cd9-80fa6f5baf65", [{"colorscale":[[0.0,"#e5e7eb"],[0.4,"#64748b"],[0.75,"#2563eb"],[1.0,"#4b5563"]],"customdata":[["2024-09-02","2024-09-09","2024-09-16","2024-09-23","2024-09-30","2024-10-07","2024-10-14","2024-10-21","2024-10-28","2024-11-04","2024-11-11","2024-11-18","2024-11-25","2024-12-02","2024-12-09","2024-12-16","2024-12-23","2024-12-30","2025-01-06","2025-01-13","2025-01-20","2025-01-27","2025-02-03","2025-02-10","2025-02-17","2025-02-24","2025-03-03","2025-03-10","2025-03-17","2025-03-24","2025-03-31","2025-04-07","2025-04-14","2025-04-21","2025-04-28","2025-05-05","2025-05-12","2025-05-19","2025-05-26","2025-06-02","2025-06-09","2025-06-16","2025-06-23","2025-06-30","2025-07-07","2025-07-14","2025-07-21","2025-07-28","2025-08-04","2025-08-11","2025-08-18","2025-08-25"],["2024-09-03","2024-09-10","2024-09-17","2024-09-24","2024-10-01","2024-10-08","2024-10-15","2024-10-22","2024-10-29","2024-11-05","2024-11-12","2024-11-19","2024-11-26","2024-12-03","2024-12-10","2024-12-17","2024-12-24","2024-12-31","2025-01-07","2025-01-14","2025-01-21","2025-01-28","2025-02-04","2025-02-11","2025-02-18","2025-02-25","2025-03-04","2025-03-11","2025-03-18","2025-03-25","2025-04-01","2025-04-08","2025-04-15","2025-04-22","2025-04-29","2025-05-06","2025-05-13","2025-05-20","2025-05-27","2025-06-03","2025-06-10","2025-06-17","2025-06-24","2025-07-01","2025-07-08","2025-07-15","2025-07-22","2025-07-29","2025-08-05","2025-08-12","2025-08-19","2025-08-26"],["2024-09-04","2024-09-11","2024-09-18","2024-09-25","2024-10-02","2024-10-09","2024-10-16","2024-10-23","2024-10-30","2024-11-06","2024-11-13","2024-11-20","2024-11-27","2024-12-04","2024-12-11","2024-12-18","2024-12-25","2025-01-01","2025-01-08","2025-01-15","2025-01-22","2025-01-29","2025-02-05","2025-02-12","2025-02-19","2025-02-26","2025-03-05","2025-03-12","2025-03-19","2025-03-26","2025-04-02","2025-04-09","2025-04-16","2025-04-23","2025-04-30","2025-05-07","2025-05-14","2025-05-21","2025-05-28","2025-06-04","2025-06-11","2025-06-18","2025-06-25","2025-07-02","2025-07-09","2025-07-16","2025-07-23","2025-07-30","2025-08-06","2025-08-13","2025-08-20","2025-08-27"],["2024-09-05","2024-09-12","2024-09-19","2024-09-26","2024-10-03","2024-10-10","2024-10-17","2024-10-24","2024-10-31","2024-11-07","2024-11-14","2024-11-21","2024-11-28","2024-12-05","2024-12-12","2024-12-19","2024-12-26","2025-01-02","2025-01-09","2025-01-16","2025-01-23","2025-01-30","2025-02-06","2025-02-13","2025-02-20","2025-02-27","2025-03-06","2025-03-13","2025-03-20","2025-03-27","2025-04-03","2025-04-10","2025-04-17","2025-04-24","2025-05-01","2025-05-08","2025-05-15","2025-05-22","2025-05-29","2025-06-05","2025-06-12","2025-06-19","2025-06-26","2025-07-03","2025-07-10","2025-07-17","2025-07-24","2025-07-31","2025-08-07","2025-08-14","2025-08-21","2025-08-28"],["2024-09-06","2024-09-13","2024-09-20","2024-09-27","2024-10-04","2024-10-11","2024-10-18","2024-10-25","2024-11-01","2024-11-08","2024-11-15","2024-11-22","2024-11-29","2024-12-06","2024-12-13","2024-12-20","2024-12-27","2025-01-03","2025-01-10","2025-01-17","2025-01-24","2025-01-31","2025-02-07","2025-02-14","2025-02-21","2025-02-28","2025-03-07","2025-03-14","2025-03-21","2025-03-28","2025-04-04","2025-04-11","2025-04-18","2025-04-25","2025-05-02","2025-05-09","2025-05-16","2025-05-23","2025-05-30","2025-06-06","2025-06-13","2025-06-20","2025-06-27","2025-07-04","2025-07-11","2025-07-18","2025-07-25","2025-08-01","2025-08-08","2025-08-15","2025-08-22","2025-08-29"],["2024-09-07","2024-09-14","2024-09-21","2024-09-28","2024-10-05","2024-10-12","2024-10-19","2024-10-26","2024-11-02","2024-11-09","2024-11-16","2024-11-23","2024-11-30","2024-12-07","2024-12-14","2024-12-21","2024-12-28","2025-01-04","2025-01-11","2025-01-18","2025-01-25","2025-02-01","2025-02-08","2025-02-15","2025-02-22","2025-03-01","2025-03-08","2025-03-15","2025-03-22","2025-03-29","2025-04-05","2025-04-12","2025-04-19","2025-04-26","2025-05-03","2025-05-10","2025-05-17","2025-05-24","2025-05-31","2025-06-07","2025-06-14","2025-06-21","2025-06-28","2025-07-05","2025-07-12","2025-07-19","2025-07-26","2025-08-02","2025-08-09","2025-08-16","2025-08-23","2025-08-30"],["2024-09-08","2024-09-15","2024-09-22","2024-09-29","2024-10-06","2024-10-13","2024-10-20","2024-10-27","2024-11-03","2024-11-10","2024-11-17","2024-11-24","2024-12-01","2024-12-08","2024-12-15","2024-12-22","2024-12-29","2025-01-05","2025-01-12","2025-01-19","2025-01-26","2025-02-02","2025-02-09","2025-02-16","2025-02-23","2025-03-02","2025-03-09","2025-03-16","2025-03-23","2025-03-30","2025-04-06","2025-04-13","2025-04-20","2025-04-27","2025-05-04","2025-05-11","2025-05-18","2025-05-25","2025-06-01","2025-06-08","2025-06-15","2025-06-22","2025-06-29","2025-07-06","2025-07-13","2025-07-20","2025-07-27","2025-08-03","2025-08-10","2025-08-17","2025-08-24","2025-08-31"]],"hovertemplate":"Date: %{customdata}\u003cbr\u003eValue: %{z:.2f}\u003cextra\u003e\u003c\u002fextra\u003e","showscale":false,"x":["2024-09-02","2024-09-09","2024-09-16","2024-09-23","2024-09-30","2024-10-07","2024-10-14","2024-10-21","2024-10-28","2024-11-04","2024-11-11","2024-11-18","2024-11-25","2024-12-02","2024-12-09","2024-12-16","2024-12-23","2024-12-30","2025-01-06","2025-01-13","2025-01-20","2025-01-27","2025-02-03","2025-02-10","2025-02-17","2025-02-24","2025-03-03","2025-03-10","2025-03-17","2025-03-24","2025-03-31","2025-04-07","2025-04-14","2025-04-21","2025-04-28","2025-05-05","2025-05-12","2025-05-19","2025-05-26","2025-06-02","2025-06-09","2025-06-16","2025-06-23","2025-06-30","2025-07-07","2025-07-14","2025-07-21","2025-07-28","2025-08-04","2025-08-11","2025-08-18","2025-08-25"],"xgap":2,"y":["Mon","Tue","Wed","Thu","Fri","Sat","Sun"],"ygap":2,"z":[[0.13459901272105057,0.07144037267686924,0.0,0.02567169542484793,0.1943701957602955,0.0,0.10315424053432098,0.2223869321144732,0.0101747131633462,0.04624564471870697,0.1866248629409762,0.1619588952957143,0.10179656128853301,0.28127388190834635,0.24363532332682114,0.38314067714644795,0.3904396470156554,0.5871417623089922,0.4494512420063476,0.7075867892330924,0.5854536843986621,0.6749419514555224,0.7305655930672372,0.897409823215684,0.8918983512872566,0.9209354080279966,0.9308865657264072,0.8059584841797863,1.0,0.898048571887148,1.0,1.0,0.8590184058919965,1.0,1.0,0.7732387178889825,0.8912170907522784,0.8184163384586197,0.822475204602678,0.7642543483018442,0.5431644942315057,0.6355115517973967,0.7015969114652315,0.5460237405196436,0.4879670174082411,0.4675485058035454,0.42809456698479686,0.2911841791176736,0.1481675433317543,0.13923730763227324,0.1766461423778808,0.1699281866099175],[0.0,0.0,0.0017899698622748927,0.0,0.09677158638116767,0.12479477625677105,0.10895777790688227,0.15189044557185855,0.09654580653607348,0.1795492865957899,0.3052268504010917,0.32596436590345157,0.18411123726736778,0.35977527304759266,0.35252652681600993,0.5427417269194631,0.5651562981702217,0.47750647994960754,0.4561350741573436,0.7417137309609516,0.5102953771880667,0.8241126373750014,0.7797236509747064,0.9332037061153636,0.9824394294545304,0.8289995839250157,1.0,0.7913308880046568,0.8189055068633593,1.0,0.8223965509972967,0.8348974442606938,0.9564174304426082,0.8985185636493014,1.0,0.8604991326856607,0.9758735705615857,0.6558932159815902,0.7000147719646015,0.5799408830216487,0.7035151530934022,0.7478886919490498,0.6337798522620632,0.4941685362991634,0.4092062677590119,0.4232057215920778,0.2300237268973226,0.3526820715419722,0.3679596483947415,0.10802723932902636,0.25227992652493664,0.13091171870017465],[0.18654843761315884,0.02424509841700278,0.0,0.03388313524346659,0.0,0.12125378247595983,0.0,0.0,0.13088917701073183,0.16910681233669253,0.10310100475666693,0.20052186435225497,0.3816697335862501,0.22282049255664288,0.28520333869041986,0.40340313228078245,0.3690761124240207,0.6115883706700836,0.47033965930665544,0.6007036424693509,0.7598327462235945,0.5773116058876002,0.6237534811100747,0.8691407726978946,0.7375919944566651,0.941330162011735,0.8014934674635664,0.8458330455003304,0.9611035861419381,0.8463915521310343,1.0,1.0,1.0,1.0,1.0,0.7932695891537325,0.9596482462801176,0.915947343178816,0.6136511921161285,0.5624943289814737,0.5541252690852431,0.5344741327070746,0.5169619710495121,0.46374855539511284,0.47539824510653705,0.24291414204185113,0.2543250806739876,0.31026724130890454,0.16093385741803784,0.17923375751178566,0.28907858857606483,0.09118081773759709],[0.04274285618353736,0.16773213722549252,0.0,0.027348236792497493,0.19678388324323698,0.0,0.0,0.0,0.0,0.0067895255930027765,0.2289109950080088,0.12593408541693593,0.11541624142573209,0.17043612043585676,0.4096860328972915,0.5574474536390003,0.484527060997471,0.3920676804861751,0.5784662869674918,0.6995971109532144,0.6467757589136034,0.8109481639584317,0.7425226445007554,0.7478239359283666,0.9666926185706564,0.9587154359758231,0.7863943022510236,0.7986686327668135,1.0,1.0,1.0,1.0,0.8091532345633923,0.9558433252238909,0.8031303542172052,0.8725859202971921,0.7566835148011993,0.7778067221045616,0.8077718658444186,0.7050984538121973,0.6791435853965506,0.7315047589823398,0.6274606401446008,0.6090447486281656,0.47565459019472395,0.3626444151812243,0.3781788689164846,0.1428027187105927,0.13024918340438868,0.24538136440926647,0.25413551726238914,0.2638681222155129],[0.16631123049192714,0.21393461748259215,0.17443172320155878,0.16030598440664023,0.008372431838735095,0.048590697685584064,0.12304248758475622,0.05962196841028138,0.04021069416966182,0.19139690202159018,0.19091509263961848,0.33755767564372935,0.26222784209855,0.3836164558963834,0.4951442089456719,0.42861473393542787,0.6191816151803766,0.6337521044168415,0.5006396943655522,0.613622934507656,0.5840928534076,0.6852784052867706,0.7425115550483108,0.7805104874379335,0.8068752071639289,0.8268975901194805,0.8083965524738981,0.970997420540351,1.0,0.835199310773221,0.8451380333804511,0.8935781712143693,0.9260453704963016,1.0,0.8279150004605469,1.0,0.8523959398966388,0.7719099661835359,0.881081537171595,0.6749144766088301,0.6107016930483002,0.6206214151930902,0.41410447536673967,0.5191072714001534,0.5213551688304456,0.4247676354556676,0.4396179366988354,0.368866379134083,0.16339728503322348,0.1173613402559649,0.09602005004734745,0.07480784435331583],[0.046590987334549576,0.10123262009753725,0.02093213312475304,0.0,0.07125379358885636,0.11217969099637068,0.0,0.04223494562109133,0.0,0.2407179325890179,0.10427870014018772,0.16707365585204992,0.1793360141618333,0.44441581083681436,0.5015112790992493,0.324885132203605,0.4145937619787646,0.5945489220423629,0.49092001333284074,0.7608613987453299,0.62750715549575,0.8547015477375716,0.8346531778992586,0.9684788887645426,1.0,0.8136483326914014,0.807430828663213,0.9557997633645948,0.9341359236245382,0.8658706292175599,0.7999512579226341,0.8315899109479433,1.0,0.7834447149945696,0.821791956125248,0.8837193194656598,0.7717628566232109,0.800652279919202,0.6248520681660523,0.7382895308902923,0.6921482346155182,0.5893888952931976,0.4496816205443064,0.38499598068827606,0.28076008234368877,0.2359389188931954,0.21120429234963584,0.21097085986745998,0.3105027222666124,0.15148478367041826,0.1346761632737261,0.012262819186755189],[0.009666345383968863,0.11116647068498314,0.09294877334825495,0.0,0.0,0.2011845639304187,0.1746125923167111,0.16006896670795023,0.0,0.28572584519939537,0.2106947405688844,0.24717486234760797,0.28443580982016986,0.46446650959504143,0.331714655444355,0.43317910390302933,0.5322084048261323,0.6040016380656543,0.5272867409409122,0.6326146082611722,0.7481764440823108,0.6032879101358065,0.8983906824318192,0.8272461712741,0.7697692673827646,0.7968738421076039,0.8931913970024197,0.9329148302840636,0.904554090664641,0.8543001086485396,0.8838775248978465,0.8560252873023545,0.8122001360665569,1.0,0.7616937704042586,0.9029733179243924,0.8776848090469377,0.9202037297571337,0.6884370069119261,0.8140845758855468,0.7631365695720691,0.5410474299147227,0.6587768738699801,0.5594767226752763,0.39462779575958423,0.4973245647686208,0.23374565469040598,0.14285601938377673,0.0893905690513131,0.320572013689418,0.16675392505723788,0.039142897242472996]],"type":"heatmap"}], {"template":{"data":{"histogram2dcontour":[{"type":"histogram2dcontour","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"choropleth":[{"type":"choropleth","colorbar":{"outlinewidth":0,"ticks":""}}],"histogram2d":[{"type":"histogram2d","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"heatmap":[{"type":"heatmap","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"heatmapgl":[{"type":"heatmapgl","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"contourcarpet":[{"type":"contourcarpet","colorbar":{"outlinewidth":0,"ticks":""}}],"contour":[{"type":"contour","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"surface":[{"type":"surface","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"mesh3d":[{"type":"mesh3d","colorbar":{"outlinewidth":0,"ticks":""}}],"scatter":[{"fillpattern":{"fillmode":"overlay","size":10,"solidity":0.2},"type":"scatter"}],"parcoords":[{"type":"parcoords","line":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scatterpolargl":[{"type":"scatterpolargl","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"bar":[{"error_x":{"color":"#2a3f5f"},"error_y":{"color":"#2a3f5f"},"marker":{"line":{"color":"#E5ECF6","width":0.5},"pattern":{"fillmode":"overlay","size":10,"solidity":0.2}},"type":"bar"}],"scattergeo":[{"type":"scattergeo","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scatterpolar":[{"type":"scatterpolar","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"histogram":[{"marker":{"pattern":{"fillmode":"overlay","size":10,"solidity":0.2}},"type":"histogram"}],"scattergl":[{"type":"scattergl","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scatter3d":[{"type":"scatter3d","line":{"colorbar":{"outlinewidth":0,"ticks":""}},"marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scattermapbox":[{"type":"scattermapbox","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scatterternary":[{"type":"scatterternary","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scattercarpet":[{"type":"scattercarpet","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"carpet":[{"aaxis":{"endlinecolor":"#2a3f5f","gridcolor":"white","linecolor":"white","minorgridcolor":"white","startlinecolor":"#2a3f5f"},"baxis":{"endlinecolor":"#2a3f5f","gridcolor":"white","linecolor":"white","minorgridcolor":"white","startlinecolor":"#2a3f5f"},"type":"carpet"}],"table":[{"cells":{"fill":{"color":"#EBF0F8"},"line":{"color":"white"}},"header":{"fill":{"color":"#C8D4E3"},"line":{"color":"white"}},"type":"table"}],"barpolar":[{"marker":{"line":{"color":"#E5ECF6","width":0.5},"pattern":{"fillmode":"overlay","size":10,"solidity":0.2}},"type":"barpolar"}],"pie":[{"automargin":true,"type":"pie"}]},"layout":{"autotypenumbers":"strict","colorway":["#636efa","#EF553B","#00cc96","#ab63fa","#FFA15A","#19d3f3","#FF6692","#B6E880","#FF97FF","#FECB52"],"font":{"color":"#2a3f5f"},"hovermode":"closest","hoverlabel":{"align":"left"},"paper_bgcolor":"white","plot_bgcolor":"#E5ECF6","polar":{"bgcolor":"#E5ECF6","angularaxis":{"gridcolor":"white","linecolor":"white","ticks":""},"radialaxis":{"gridcolor":"white","linecolor":"white","ticks":""}},"ternary":{"bgcolor":"#E5ECF6","aaxis":{"gridcolor":"white","linecolor":"white","ticks":""},"baxis":{"gridcolor":"white","linecolor":"white","ticks":""},"caxis":{"gridcolor":"white","linecolor":"white","ticks":""}},"coloraxis":{"colorbar":{"outlinewidth":0,"ticks":""}},"colorscale":{"sequential":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"sequentialminus":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"diverging":[[0,"#8e0152"],[0.1,"#c51b7d"],[0.2,"#de77ae"],[0.3,"#f1b6da"],[0.4,"#fde0ef"],[0.5,"#f7f7f7"],[0.6,"#e6f5d0"],[0.7,"#b8e186"],[0.8,"#7fbc41"],[0.9,"#4d9221"],[1,"#276419"]]},"xaxis":{"gridcolor":"white","linecolor":"white","ticks":"","title":{"standoff":15},"zerolinecolor":"white","automargin":true,"zerolinewidth":2},"yaxis":{"gridcolor":"white","linecolor":"white","ticks":"","title":{"standoff":15},"zerolinecolor":"white","automargin":true,"zerolinewidth":2},"scene":{"xaxis":{"backgroundcolor":"#E5ECF6","gridcolor":"white","linecolor":"white","showbackground":true,"ticks":"","zerolinecolor":"white","gridwidth":2},"yaxis":{"backgroundcolor":"#E5ECF6","gridcolor":"white","linecolor":"white","showbackground":true,"ticks":"","zerolinecolor":"white","gridwidth":2},"zaxis":{"backgroundcolor":"#E5ECF6","gridcolor":"white","linecolor":"white","showbackground":true,"ticks":"","zerolinecolor":"white","gridwidth":2}},"shapedefaults":{"line":{"color":"#2a3f5f"}},"annotationdefaults":{"arrowcolor":"#2a3f5f","arrowhead":0,"arrowwidth":1},"geo":{"bgcolor":"white","landcolor":"#E5ECF6","subunitcolor":"white","showland":true,"showlakes":true,"lakecolor":"white"},"title":{"x":0.05},"mapbox":{"style":"light"}}},"margin":{"l":28,"r":12,"t":8,"b":28},"xaxis":{"showgrid":false,"zeroline":false,"showline":false,"ticks":"","showticklabels":false,"fixedrange":true},"yaxis":{"tickfont":{"size":12,"color":"rgba(0,0,0,0.65)"},"showgrid":false,"zeroline":false,"showline":false,"ticks":"","fixedrange":true},"autosize":true,"paper_bgcolor":"rgba(0,0,0,0)","plot_bgcolor":"rgba(0,0,0,0)"}, {"displayModeBar": false, "responsive": true, "scrollZoom": false} ) }; </script> </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div class="d3-train-diagram" style="width:100%;margin:10px 0;"></div>
2
+ <div class="caption">Survolez les blocs pour afficher une explication.</div>
3
+ <style>
4
+ .d3-train-diagram + .caption { margin-top: 8px; font-size: 14px; color: var(--muted-color); }
5
+ </style>
6
+ <script>
7
+ (() => {
8
+ const ensureD3 = (cb) => {
9
+ if (window.d3 && typeof window.d3.select === 'function') return cb();
10
+ let s = document.getElementById('d3-cdn-script');
11
+ if (!s) { s = document.createElement('script'); s.id = 'd3-cdn-script'; s.src = 'https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js'; document.head.appendChild(s); }
12
+ const onReady = () => { if (window.d3 && typeof window.d3.select === 'function') cb(); };
13
+ s.addEventListener('load', onReady, { once: true });
14
+ if (window.d3) onReady();
15
+ };
16
+
17
+ const bootstrap = () => {
18
+ const mount = document.currentScript ? document.currentScript.previousElementSibling : null;
19
+ const container = (mount && mount.querySelector && mount.querySelector('.d3-train-diagram')) || document.querySelector('.d3-train-diagram');
20
+ if (!container) return;
21
+ if (container.dataset) { if (container.dataset.mounted === 'true') return; container.dataset.mounted = 'true'; }
22
+
23
+ // Diagram spec
24
+ const numBlocks = 7;
25
+ const rows = [
26
+ { key: 'model', label: 'Model', color: '#a78bfa' },
27
+ { key: 'forward', label: 'Forward', color: '#14b8a6' },
28
+ { key: 'backward', label: 'Backward', color: '#f59e0b' },
29
+ { key: 'gradients', label: 'Gradients', color: 'var(--primary)' },
30
+ { key: 'optimization', label: 'Optimization', color: '#10b981' },
31
+ { key: 'updated', label: 'Updated', color: '#7c3aed' },
32
+ ];
33
+ const hoverText = {
34
+ model: 'Chaque bloc représente un sous-module du modèle.',
35
+ forward: 'Propagation avant: calcul des activations couche par couche.',
36
+ backward: 'Rétropropagation: calcul des gradients via la chaîne.',
37
+ gradients: 'Accumulateurs de gradients pour chaque couche.',
38
+ optimization: 'Étape d’optimisation: mise à jour des poids.',
39
+ updated: 'Paramètres mis à jour, prêts pour l’itération suivante.'
40
+ };
41
+
42
+ // SVG
43
+ const svg = d3.select(container).append('svg').attr('width', '100%').style('display','block');
44
+ const gRoot = svg.append('g');
45
+ const gLegend = gRoot.append('foreignObject').attr('class','legend');
46
+ const gArrows = gRoot.append('g').attr('class','arrows');
47
+ const gBlocks = gRoot.append('g').attr('class','blocks');
48
+ const gLabels = gRoot.append('g').attr('class','row-labels');
49
+
50
+ // Tooltip (reuse style from others)
51
+ container.style.position = container.style.position || 'relative';
52
+ let tip = container.querySelector('.d3-tooltip'); let tipInner;
53
+ if (!tip) { tip = document.createElement('div'); tip.className = 'd3-tooltip'; Object.assign(tip.style,{ position:'absolute', top:'0px', left:'0px', transform:'translate(-9999px, -9999px)', pointerEvents:'none', padding:'8px 10px', borderRadius:'8px', fontSize:'12px', lineHeight:'1.35', border:'1px solid var(--border-color)', background:'var(--surface-bg)', color:'var(--text-color)', boxShadow:'0 4px 24px rgba(0,0,0,.18)', opacity:'0', transition:'opacity .12s ease' }); tipInner = document.createElement('div'); tipInner.className = 'd3-tooltip__inner'; tipInner.style.textAlign='left'; tip.appendChild(tipInner); container.appendChild(tip); } else { tipInner = tip.querySelector('.d3-tooltip__inner') || tip; }
54
+
55
+ // Layout
56
+ let width=800, height=360; const margin = { top: 24, right: 180, bottom: 40, left: 32 };
57
+ const x = d3.scaleBand().domain(d3.range(numBlocks)).paddingInner(0.2).paddingOuter(0.05);
58
+ const y = d3.scaleBand().domain(d3.range(rows.length)).paddingInner(0.35);
59
+
60
+ function updateScales(){
61
+ width = container.clientWidth || 800;
62
+ const rowH = Math.max(54, Math.min(80, Math.round(width / 12)));
63
+ const innerHeight = rows.length * rowH;
64
+ height = innerHeight + margin.top + margin.bottom;
65
+ svg.attr('width', width).attr('height', height);
66
+ const innerWidth = width - margin.left - margin.right;
67
+ gRoot.attr('transform', `translate(${margin.left},${margin.top})`);
68
+
69
+ x.range([0, innerWidth]);
70
+ y.range([0, innerHeight]);
71
+
72
+ return { innerWidth, innerHeight };
73
+ }
74
+
75
+ function render(){
76
+ const { innerWidth, innerHeight } = updateScales();
77
+
78
+ // Legend right side
79
+ const legendWidth = 160, legendHeight = rows.length * 20;
80
+ gLegend.attr('x', innerWidth + 16).attr('y', 0).attr('width', legendWidth).attr('height', legendHeight);
81
+ const lroot = gLegend.selectAll('div').data([0]).join('xhtml:div');
82
+ lroot.html(`
83
+ <div style="display:flex;flex-direction:column;gap:8px;">
84
+ ${rows.map(r => `<div style=\"display:flex;align-items:center;gap:8px;\"><span style=\"width:14px;height:14px;background:${r.color};border-radius:4px;display:inline-block\"></span><span>${r.label}</span></div>`).join('')}
85
+ </div>
86
+ `);
87
+
88
+ // Row labels on the right side aligned to centers
89
+ gLabels.selectAll('*').remove();
90
+ gLabels.selectAll('text').data(rows).join('text')
91
+ .attr('x', innerWidth + 16)
92
+ .attr('y', (_,i)=> y(i) + y.bandwidth()/2)
93
+ .attr('dy','0.35em')
94
+ .style('font-size','14px')
95
+ .style('fill','var(--text-color)')
96
+ .text(d=>d.label);
97
+
98
+ // Blocks per row
99
+ const blockW = Math.min(84, x.bandwidth());
100
+ const blockH = Math.min(52, Math.round(y.bandwidth() * 0.8));
101
+ const blocks = [];
102
+ rows.forEach((row, ri) => {
103
+ for (let i=0;i<numBlocks;i++) blocks.push({ row, ri, i });
104
+ });
105
+ const sel = gBlocks.selectAll('rect.block').data(blocks, d=>`${d.row.key}-${d.i}`);
106
+ sel.join(
107
+ enter => enter.append('rect').attr('class','block')
108
+ .attr('x', d=>x(d.i))
109
+ .attr('y', d=>y(d.ri) + (y.bandwidth()-blockH)/2)
110
+ .attr('rx', 12).attr('ry', 12)
111
+ .attr('width', blockW)
112
+ .attr('height', blockH)
113
+ .attr('fill', d=>d.row.color)
114
+ .attr('opacity', 0.95)
115
+ .attr('stroke', 'rgba(0,0,0,0.18)')
116
+ .attr('filter', 'url(#shadow)')
117
+ .on('mouseenter', function(ev, d){
118
+ d3.select(this).attr('opacity', 1.0).attr('stroke-width', 1.2);
119
+ tipInner.innerHTML = `<div><strong>${d.row.label}</strong></div><div>${hoverText[d.row.key]}</div>`;
120
+ tip.style.opacity = '1';
121
+ })
122
+ .on('mousemove', function(ev){ const [mx,my] = d3.pointer(ev, container); tip.style.transform = `translate(${mx+12}px, ${my+12}px)`; })
123
+ .on('mouseleave', function(){ tip.style.opacity='0'; tip.style.transform='translate(-9999px,-9999px)'; d3.select(this).attr('opacity', 0.95).attr('stroke-width', 1); })
124
+ );
125
+
126
+ // Arrows forward/backward
127
+ gArrows.selectAll('*').remove();
128
+ const arrowY = (ri) => y(ri) + y.bandwidth()/2;
129
+ const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
130
+ const arrowColor = isDark ? 'rgba(255,255,255,0.55)' : 'rgba(0,0,0,0.55)';
131
+ const defs = svg.select('defs').empty() ? svg.append('defs') : svg.select('defs');
132
+ const marker = defs.append('marker').attr('id','arrow').attr('viewBox','0 0 10 10').attr('refX', 10).attr('refY', 5).attr('markerWidth', 6).attr('markerHeight', 6).attr('orient','auto-start-reverse');
133
+ marker.append('path').attr('d','M 0 0 L 10 5 L 0 10 z').attr('fill', arrowColor);
134
+ // drop shadow filter
135
+ const flt = defs.append('filter').attr('id','shadow').attr('x','-20%').attr('y','-20%').attr('width','140%').attr('height','140%');
136
+ flt.append('feDropShadow').attr('dx','0').attr('dy','1').attr('stdDeviation','1.5').attr('flood-color','rgba(0,0,0,0.18)');
137
+ // Forward arrow (top orientation)
138
+ gArrows.append('line').attr('x1', x(0)).attr('y1', arrowY(1)-28).attr('x2', x(numBlocks-1)+blockW).attr('y2', arrowY(1)-28)
139
+ .attr('stroke', rows[1].color).attr('stroke-width', 4).attr('marker-end','url(#arrow)');
140
+ // Backward arrow (orange, reversed)
141
+ gArrows.append('line').attr('x1', x(numBlocks-1)+blockW).attr('y1', arrowY(2)-20).attr('x2', x(0)).attr('y2', arrowY(2)-20)
142
+ .attr('stroke', rows[2].color).attr('stroke-width', 4).attr('marker-end','url(#arrow)');
143
+ // Vertical arrows (gradients down, updated up)
144
+ const midX = x(3) + blockW/2;
145
+ gArrows.append('line').attr('x1', midX).attr('y1', arrowY(2)+blockH/2+4).attr('x2', midX).attr('y2', arrowY(3)-blockH/2-6)
146
+ .attr('stroke', rows[3].color).attr('stroke-width', 3).attr('marker-end','url(#arrow)');
147
+ gArrows.append('line').attr('x1', midX).attr('y1', arrowY(4)+blockH/2+6).attr('x2', midX).attr('y2', arrowY(5)-blockH/2-6)
148
+ .attr('stroke', rows[5].color).attr('stroke-width', 3).attr('marker-end','url(#arrow)');
149
+ }
150
+
151
+ render();
152
+ if (window.ResizeObserver) { const ro = new ResizeObserver(()=>render()); ro.observe(container); } else { window.addEventListener('resize', render); }
153
+ };
154
+
155
+ if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => ensureD3(bootstrap), { once: true }); } else { ensureD3(bootstrap); }
156
+ })();
157
+ </script>
158
+
159
+
app/src/content/fragments/line.html CHANGED
@@ -1 +1,84 @@
1
- <div> <div id="9ede8c7f-7b5c-4431-8616-891d85efdf07" class="plotly-graph-div" style="height:100%; width:100%;"></div> <script type="text/javascript"> window.PLOTLYENV=window.PLOTLYENV || {}; if (document.getElementById("9ede8c7f-7b5c-4431-8616-891d85efdf07")) { Plotly.newPlot( "9ede8c7f-7b5c-4431-8616-891d85efdf07", [{"hovertemplate":"\u003cb\u003e%{fullData.name}\u003c\u002fb\u003e\u003cbr\u003ex=%{x:.2f}\u003cbr\u003ey=%{y:.3f}\u003cextra\u003e\u003c\u002fextra\u003e","line":{"color":"#64748b","shape":"spline","smoothing":0.6,"width":2},"mode":"lines","name":"Baseline","showlegend":true,"x":[0.0,0.0041841004184100415,0.008368200836820083,0.012552301255230124,0.016736401673640166,0.02092050209205021,0.025104602510460247,0.02928870292887029,0.03347280334728033,0.03765690376569037,0.04184100418410042,0.046025104602510455,0.050209205020920494,0.05439330543933054,0.05857740585774058,0.06276150627615062,0.06694560669456066,0.0711297071129707,0.07531380753138074,0.0794979079497908,0.08368200836820083,0.08786610878661087,0.09205020920502091,0.09623430962343095,0.10041841004184099,0.10460251046025104,0.10878661087866108,0.11297071129707112,0.11715481171548116,0.1213389121338912,0.12552301255230125,0.12970711297071127,0.13389121338912133,0.13807531380753138,0.1422594142259414,0.14644351464435146,0.15062761506276148,0.15481171548117154,0.1589958158995816,0.1631799163179916,0.16736401673640167,0.1715481171548117,0.17573221757322174,0.1799163179916318,0.18410041841004182,0.18828451882845187,0.1924686192468619,0.19665271966527195,0.20083682008368198,0.20502092050209203,0.20920502092050208,0.2133891213389121,0.21757322175732216,0.22175732217573219,0.22594142259414224,0.2301255230125523,0.23430962343096232,0.23849372384937237,0.2426778242677824,0.24686192468619245,0.2510460251046025,0.25523012552301255,0.25941422594142255,0.2635983263598326,0.26778242677824265,0.2719665271966527,0.27615062761506276,0.28033472803347276,0.2845188284518828,0.28870292887029286,0.2928870292887029,0.29707112970711297,0.30125523012552297,0.305439330543933,0.30962343096234307,0.3138075313807531,0.3179916317991632,0.3221757322175732,0.3263598326359832,0.3305439330543933,0.33472803347280333,0.3389121338912134,0.3430962343096234,0.34728033472803344,0.3514644351464435,0.35564853556485354,0.3598326359832636,0.3640167364016736,0.36820083682008364,0.3723849372384937,0.37656903765690375,0.3807531380753138,0.3849372384937238,0.38912133891213385,0.3933054393305439,0.39748953974895396,0.40167364016736395,0.405857740585774,0.41004184100418406,0.4142259414225941,0.41841004184100417,0.42259414225941416,0.4267782426778242,0.43096234309623427,0.4351464435146443,0.4393305439330544,0.44351464435146437,0.4476987447698744,0.4518828451882845,0.45606694560669453,0.4602510460251046,0.4644351464435146,0.46861924686192463,0.4728033472803347,0.47698744769874474,0.4811715481171548,0.4853556485355648,0.48953974895397484,0.4937238493723849,0.49790794979079495,0.502092050209205,0.506276150627615,0.5104602510460251,0.5146443514644351,0.5188284518828451,0.5230125523012552,0.5271966527196652,0.5313807531380753,0.5355648535564853,0.5397489539748953,0.5439330543933054,0.5481171548117154,0.5523012552301255,0.5564853556485355,0.5606694560669455,0.5648535564853556,0.5690376569037656,0.5732217573221757,0.5774058577405857,0.5815899581589957,0.5857740585774058,0.5899581589958158,0.5941422594142259,0.5983263598326359,0.6025104602510459,0.606694560669456,0.610878661087866,0.6150627615062761,0.6192468619246861,0.6234309623430961,0.6276150627615062,0.6317991631799162,0.6359832635983264,0.6401673640167364,0.6443514644351463,0.6485355648535565,0.6527196652719665,0.6569037656903766,0.6610878661087866,0.6652719665271966,0.6694560669456067,0.6736401673640167,0.6778242677824268,0.6820083682008368,0.6861924686192468,0.6903765690376569,0.6945606694560669,0.698744769874477,0.702928870292887,0.707112970711297,0.7112970711297071,0.7154811715481171,0.7196652719665272,0.7238493723849372,0.7280334728033472,0.7322175732217573,0.7364016736401673,0.7405857740585774,0.7447698744769874,0.7489539748953974,0.7531380753138075,0.7573221757322175,0.7615062761506276,0.7656903765690376,0.7698744769874476,0.7740585774058577,0.7782426778242677,0.7824267782426778,0.7866108786610878,0.7907949790794978,0.7949790794979079,0.7991631799163179,0.8033472803347279,0.807531380753138,0.811715481171548,0.8158995815899581,0.8200836820083681,0.8242677824267781,0.8284518828451882,0.8326359832635982,0.8368200836820083,0.8410041841004183,0.8451882845188283,0.8493723849372384,0.8535564853556484,0.8577405857740585,0.8619246861924685,0.8661087866108785,0.8702928870292886,0.8744769874476986,0.8786610878661087,0.8828451882845187,0.8870292887029287,0.8912133891213389,0.8953974895397488,0.899581589958159,0.903765690376569,0.907949790794979,0.9121338912133891,0.9163179916317991,0.9205020920502092,0.9246861924686192,0.9288702928870292,0.9330543933054393,0.9372384937238493,0.9414225941422594,0.9456066945606694,0.9497907949790794,0.9539748953974895,0.9581589958158995,0.9623430962343096,0.9665271966527196,0.9707112970711296,0.9748953974895397,0.9790794979079497,0.9832635983263598,0.9874476987447698,0.9916317991631798,0.9958158995815899,1.0],"y":[0.2,0.20251046025104605,0.20502092050209206,0.2075313807531381,0.2100418410041841,0.21255230125523014,0.21506276150627615,0.2175732217573222,0.2200836820083682,0.22259414225941423,0.22510460251046027,0.22761506276150628,0.2301255230125523,0.23263598326359833,0.23514644351464437,0.23765690376569037,0.2401673640167364,0.24267782426778242,0.24518828451882846,0.2476987447698745,0.25020920502092053,0.25271966527196654,0.25523012552301255,0.25774058577405856,0.2602510460251046,0.26276150627615064,0.26527196652719665,0.2677824267782427,0.2702928870292887,0.27280334728033473,0.27531380753138074,0.27782426778242675,0.2803347280334728,0.2828451882845188,0.28535564853556483,0.2878661087866109,0.2903765690376569,0.2928870292887029,0.295397489539749,0.297907949790795,0.300418410041841,0.302928870292887,0.3054393305439331,0.3079497907949791,0.3104602510460251,0.3129707112970711,0.31548117154811717,0.3179916317991632,0.3205020920502092,0.32301255230125525,0.32552301255230126,0.32803347280334727,0.33054393305439334,0.3330543933054393,0.33556485355648535,0.3380753138075314,0.3405857740585774,0.34309623430962344,0.34560669456066945,0.34811715481171546,0.3506276150627615,0.35313807531380753,0.35564853556485354,0.3581589958158996,0.36066945606694556,0.3631799163179916,0.3656903765690377,0.36820083682008364,0.3707112970711297,0.3732217573221757,0.3757322175732217,0.3782426778242678,0.38075313807531375,0.3832635983263598,0.3857740585774059,0.3882845188284519,0.3907949790794979,0.3933054393305439,0.3958158995815899,0.398326359832636,0.40083682008368204,0.403347280334728,0.40585774058577406,0.40836820083682007,0.4108786610878661,0.41338912133891215,0.41589958158995816,0.41841004184100417,0.42092050209205023,0.4234309623430962,0.42594142259414225,0.4284518828451883,0.43096234309623427,0.43347280334728033,0.43598326359832634,0.43849372384937235,0.44100418410041836,0.44351464435146437,0.44602510460251044,0.4485355648535565,0.4510460251046025,0.4535564853556485,0.45606694560669453,0.45857740585774054,0.4610878661087866,0.4635983263598326,0.4661087866108786,0.46861924686192463,0.4711297071129707,0.4736401673640167,0.47615062761506277,0.4786610878661087,0.4811715481171548,0.4836820083682008,0.48619246861924686,0.4887029288702929,0.4912133891213389,0.4937238493723849,0.49623430962343096,0.49874476987447697,0.501255230125523,0.503765690376569,0.506276150627615,0.5087866108786611,0.5112970711297071,0.5138075313807531,0.5163179916317991,0.5188284518828452,0.5213389121338912,0.5238493723849371,0.5263598326359833,0.5288702928870292,0.5313807531380753,0.5338912133891214,0.5364016736401673,0.5389121338912134,0.5414225941422594,0.5439330543933054,0.5464435146443514,0.5489539748953974,0.5514644351464435,0.5539748953974895,0.5564853556485356,0.5589958158995816,0.5615062761506275,0.5640167364016736,0.5665271966527197,0.5690376569037656,0.5715481171548117,0.5740585774058578,0.5765690376569037,0.5790794979079498,0.5815899581589958,0.5841004184100418,0.5866108786610877,0.5891213389121339,0.5916317991631799,0.5941422594142259,0.596652719665272,0.599163179916318,0.601673640167364,0.60418410041841,0.606694560669456,0.609205020920502,0.6117154811715481,0.6142259414225941,0.6167364016736401,0.6192468619246863,0.6217573221757322,0.6242677824267782,0.6267782426778242,0.6292887029288703,0.6317991631799162,0.6343096234309623,0.6368200836820084,0.6393305439330543,0.6418410041841004,0.6443514644351465,0.6468619246861924,0.6493723849372384,0.6518828451882845,0.6543933054393305,0.6569037656903766,0.6594142259414226,0.6619246861924686,0.6644351464435145,0.6669456066945606,0.6694560669456067,0.6719665271966526,0.6744769874476987,0.6769874476987447,0.6794979079497907,0.6820083682008367,0.6845188284518828,0.6870292887029288,0.6895397489539749,0.6920502092050209,0.6945606694560669,0.6970711297071129,0.699581589958159,0.702092050209205,0.7046025104602509,0.7071129707112971,0.709623430962343,0.712133891213389,0.7146443514644352,0.7171548117154811,0.7196652719665271,0.7221757322175733,0.7246861924686192,0.7271966527196652,0.7297071129707113,0.7322175732217573,0.7347280334728032,0.7372384937238492,0.7397489539748954,0.7422594142259413,0.7447698744769873,0.7472803347280335,0.7497907949790794,0.7523012552301256,0.7548117154811715,0.7573221757322175,0.7598326359832634,0.7623430962343096,0.7648535564853556,0.7673640167364015,0.7698744769874477,0.7723849372384937,0.7748953974895396,0.7774058577405858,0.7799163179916317,0.7824267782426777,0.7849372384937239,0.7874476987447698,0.7899581589958158,0.792468619246862,0.7949790794979079,0.7974895397489539,0.8],"type":"scatter"},{"hovertemplate":"\u003cb\u003e%{fullData.name}\u003c\u002fb\u003e\u003cbr\u003ex=%{x:.2f}\u003cbr\u003ey=%{y:.3f}\u003cextra\u003e\u003c\u002fextra\u003e","line":{"color":"#2563eb","shape":"spline","smoothing":0.6,"width":2},"mode":"lines","name":"Improved","showlegend":true,"x":[0.0,0.0041841004184100415,0.008368200836820083,0.012552301255230124,0.016736401673640166,0.02092050209205021,0.025104602510460247,0.02928870292887029,0.03347280334728033,0.03765690376569037,0.04184100418410042,0.046025104602510455,0.050209205020920494,0.05439330543933054,0.05857740585774058,0.06276150627615062,0.06694560669456066,0.0711297071129707,0.07531380753138074,0.0794979079497908,0.08368200836820083,0.08786610878661087,0.09205020920502091,0.09623430962343095,0.10041841004184099,0.10460251046025104,0.10878661087866108,0.11297071129707112,0.11715481171548116,0.1213389121338912,0.12552301255230125,0.12970711297071127,0.13389121338912133,0.13807531380753138,0.1422594142259414,0.14644351464435146,0.15062761506276148,0.15481171548117154,0.1589958158995816,0.1631799163179916,0.16736401673640167,0.1715481171548117,0.17573221757322174,0.1799163179916318,0.18410041841004182,0.18828451882845187,0.1924686192468619,0.19665271966527195,0.20083682008368198,0.20502092050209203,0.20920502092050208,0.2133891213389121,0.21757322175732216,0.22175732217573219,0.22594142259414224,0.2301255230125523,0.23430962343096232,0.23849372384937237,0.2426778242677824,0.24686192468619245,0.2510460251046025,0.25523012552301255,0.25941422594142255,0.2635983263598326,0.26778242677824265,0.2719665271966527,0.27615062761506276,0.28033472803347276,0.2845188284518828,0.28870292887029286,0.2928870292887029,0.29707112970711297,0.30125523012552297,0.305439330543933,0.30962343096234307,0.3138075313807531,0.3179916317991632,0.3221757322175732,0.3263598326359832,0.3305439330543933,0.33472803347280333,0.3389121338912134,0.3430962343096234,0.34728033472803344,0.3514644351464435,0.35564853556485354,0.3598326359832636,0.3640167364016736,0.36820083682008364,0.3723849372384937,0.37656903765690375,0.3807531380753138,0.3849372384937238,0.38912133891213385,0.3933054393305439,0.39748953974895396,0.40167364016736395,0.405857740585774,0.41004184100418406,0.4142259414225941,0.41841004184100417,0.42259414225941416,0.4267782426778242,0.43096234309623427,0.4351464435146443,0.4393305439330544,0.44351464435146437,0.4476987447698744,0.4518828451882845,0.45606694560669453,0.4602510460251046,0.4644351464435146,0.46861924686192463,0.4728033472803347,0.47698744769874474,0.4811715481171548,0.4853556485355648,0.48953974895397484,0.4937238493723849,0.49790794979079495,0.502092050209205,0.506276150627615,0.5104602510460251,0.5146443514644351,0.5188284518828451,0.5230125523012552,0.5271966527196652,0.5313807531380753,0.5355648535564853,0.5397489539748953,0.5439330543933054,0.5481171548117154,0.5523012552301255,0.5564853556485355,0.5606694560669455,0.5648535564853556,0.5690376569037656,0.5732217573221757,0.5774058577405857,0.5815899581589957,0.5857740585774058,0.5899581589958158,0.5941422594142259,0.5983263598326359,0.6025104602510459,0.606694560669456,0.610878661087866,0.6150627615062761,0.6192468619246861,0.6234309623430961,0.6276150627615062,0.6317991631799162,0.6359832635983264,0.6401673640167364,0.6443514644351463,0.6485355648535565,0.6527196652719665,0.6569037656903766,0.6610878661087866,0.6652719665271966,0.6694560669456067,0.6736401673640167,0.6778242677824268,0.6820083682008368,0.6861924686192468,0.6903765690376569,0.6945606694560669,0.698744769874477,0.702928870292887,0.707112970711297,0.7112970711297071,0.7154811715481171,0.7196652719665272,0.7238493723849372,0.7280334728033472,0.7322175732217573,0.7364016736401673,0.7405857740585774,0.7447698744769874,0.7489539748953974,0.7531380753138075,0.7573221757322175,0.7615062761506276,0.7656903765690376,0.7698744769874476,0.7740585774058577,0.7782426778242677,0.7824267782426778,0.7866108786610878,0.7907949790794978,0.7949790794979079,0.7991631799163179,0.8033472803347279,0.807531380753138,0.811715481171548,0.8158995815899581,0.8200836820083681,0.8242677824267781,0.8284518828451882,0.8326359832635982,0.8368200836820083,0.8410041841004183,0.8451882845188283,0.8493723849372384,0.8535564853556484,0.8577405857740585,0.8619246861924685,0.8661087866108785,0.8702928870292886,0.8744769874476986,0.8786610878661087,0.8828451882845187,0.8870292887029287,0.8912133891213389,0.8953974895397488,0.899581589958159,0.903765690376569,0.907949790794979,0.9121338912133891,0.9163179916317991,0.9205020920502092,0.9246861924686192,0.9288702928870292,0.9330543933054393,0.9372384937238493,0.9414225941422594,0.9456066945606694,0.9497907949790794,0.9539748953974895,0.9581589958158995,0.9623430962343096,0.9665271966527196,0.9707112970711296,0.9748953974895397,0.9790794979079497,0.9832635983263598,0.9874476987447698,0.9916317991631798,0.9958158995815899,1.0],"y":[0.15,0.15292887029288701,0.15585774058577406,0.15878661087866108,0.1617154811715481,0.16464435146443515,0.16757322175732217,0.1705020920502092,0.17343096234309624,0.17635983263598326,0.17928870292887028,0.1822175732217573,0.18514644351464432,0.18807531380753137,0.1910041841004184,0.19393305439330544,0.19686192468619246,0.19979079497907948,0.2027196652719665,0.20564853556485355,0.20857740585774057,0.21150627615062761,0.21443514644351463,0.21736401673640166,0.22029288702928868,0.22322175732217572,0.22615062761506274,0.2290794979079498,0.2320083682008368,0.23493723849372383,0.23786610878661085,0.24079497907949787,0.24372384937238492,0.24665271966527197,0.249581589958159,0.252510460251046,0.25543933054393303,0.25836820083682005,0.2612970711297071,0.26422594142259415,0.26715481171548117,0.2700836820083682,0.2730125523012552,0.2759414225941422,0.27887029288702925,0.28179916317991627,0.2847280334728033,0.28765690376569036,0.29058577405857733,0.2935146443514644,0.2964435146443515,0.2993723849372385,0.3023012552301255,0.30523012552301254,0.30815899581589956,0.3110878661087866,0.3140167364016736,0.3169456066945606,0.31987447698744764,0.32280334728033466,0.32573221757322174,0.32866108786610876,0.3315899581589958,0.3345188284518828,0.3374476987447699,0.3403765690376569,0.3433054393305439,0.34623430962343094,0.34916317991631796,0.352092050209205,0.355020920502092,0.3579497907949791,0.36087866108786604,0.36380753138075306,0.36673640167364013,0.36966527196652715,0.37259414225941423,0.3755230125523012,0.37845188284518827,0.3813807531380753,0.3843096234309623,0.38723849372384933,0.39016736401673635,0.39309623430962337,0.3960251046025104,0.39895397489539747,0.40188284518828454,0.40481171548117145,0.4077405857740586,0.4106694560669456,0.4135983263598326,0.41652719665271964,0.41945606694560666,0.4223849372384937,0.4253138075313807,0.4282426778242677,0.43117154811715475,0.43410041841004177,0.4370292887029288,0.4399581589958158,0.44288702928870294,0.44581589958158985,0.448744769874477,0.451673640167364,0.454602510460251,0.45753138075313804,0.46046025104602506,0.4633891213389121,0.4663179916317991,0.4692468619246861,0.47217573221757314,0.47510460251046016,0.4780334728033472,0.4809623430962342,0.48389121338912133,0.48682008368200835,0.4897489539748954,0.4926778242677824,0.4956066945606694,0.49853556485355643,0.5014644351464435,0.5043933054393305,0.5073221757322175,0.5102510460251045,0.5131799163179915,0.5161087866108787,0.5190376569037656,0.5219665271966527,0.5248953974895397,0.5278242677824266,0.5307531380753138,0.5336820083682008,0.5366108786610878,0.5395397489539748,0.5424686192468618,0.5453974895397489,0.5483263598326359,0.551255230125523,0.5541841004184099,0.557112970711297,0.5600418410041841,0.5629707112970711,0.5658995815899581,0.5688284518828451,0.5717573221757322,0.5746861924686192,0.5776150627615062,0.5805439330543932,0.5834728033472802,0.5864016736401673,0.5893305439330543,0.5922594142259413,0.5951882845188284,0.5981171548117155,0.6010460251046024,0.6039748953974895,0.6069037656903765,0.6098326359832635,0.6127615062761506,0.6156903765690376,0.6186192468619246,0.6215481171548116,0.6244769874476988,0.6274058577405857,0.6303347280334727,0.6332635983263598,0.6361924686192467,0.6391213389121339,0.6420502092050209,0.6449790794979079,0.6479079497907949,0.6508368200836819,0.6537656903765691,0.656694560669456,0.659623430962343,0.6625523012552301,0.6654811715481171,0.6684100418410042,0.6713389121338912,0.6742677824267782,0.6771966527196652,0.6801255230125522,0.6830543933054393,0.6859832635983263,0.6889121338912133,0.6918410041841003,0.6947698744769873,0.6976987447698745,0.7006276150627615,0.7035564853556484,0.7064853556485355,0.7094142259414226,0.7123430962343095,0.7152719665271966,0.7182008368200836,0.7211297071129706,0.7240585774058577,0.7269874476987447,0.7299163179916317,0.7328451882845187,0.7357740585774059,0.7387029288702928,0.7416317991631798,0.7445606694560669,0.7474895397489539,0.750418410041841,0.753347280334728,0.756276150627615,0.759205020920502,0.762133891213389,0.7650627615062761,0.7679916317991631,0.7709205020920501,0.7738493723849372,0.7767782426778241,0.7797071129707113,0.7826359832635983,0.7855648535564852,0.7884937238493723,0.7914225941422594,0.7943514644351464,0.7972803347280334,0.8002092050209204,0.8031380753138074,0.8060669456066945,0.8089958158995816,0.8119246861924685,0.8148535564853555,0.8177824267782426,0.8207112970711297,0.8236401673640167,0.8265690376569037,0.8294979079497907,0.8324267782426777,0.8353556485355648,0.8382845188284518,0.8412133891213388,0.8441422594142258,0.847071129707113,0.85],"type":"scatter"},{"hovertemplate":"\u003cb\u003e%{fullData.name}\u003c\u002fb\u003e\u003cbr\u003ex=%{x:.2f}\u003cbr\u003ey=%{y:.3f}\u003cextra\u003e\u003c\u002fextra\u003e","line":{"color":"#4b5563","dash":"dash","width":2},"mode":"lines","name":"Target","showlegend":true,"x":[0.0,0.0041841004184100415,0.008368200836820083,0.012552301255230124,0.016736401673640166,0.02092050209205021,0.025104602510460247,0.02928870292887029,0.03347280334728033,0.03765690376569037,0.04184100418410042,0.046025104602510455,0.050209205020920494,0.05439330543933054,0.05857740585774058,0.06276150627615062,0.06694560669456066,0.0711297071129707,0.07531380753138074,0.0794979079497908,0.08368200836820083,0.08786610878661087,0.09205020920502091,0.09623430962343095,0.10041841004184099,0.10460251046025104,0.10878661087866108,0.11297071129707112,0.11715481171548116,0.1213389121338912,0.12552301255230125,0.12970711297071127,0.13389121338912133,0.13807531380753138,0.1422594142259414,0.14644351464435146,0.15062761506276148,0.15481171548117154,0.1589958158995816,0.1631799163179916,0.16736401673640167,0.1715481171548117,0.17573221757322174,0.1799163179916318,0.18410041841004182,0.18828451882845187,0.1924686192468619,0.19665271966527195,0.20083682008368198,0.20502092050209203,0.20920502092050208,0.2133891213389121,0.21757322175732216,0.22175732217573219,0.22594142259414224,0.2301255230125523,0.23430962343096232,0.23849372384937237,0.2426778242677824,0.24686192468619245,0.2510460251046025,0.25523012552301255,0.25941422594142255,0.2635983263598326,0.26778242677824265,0.2719665271966527,0.27615062761506276,0.28033472803347276,0.2845188284518828,0.28870292887029286,0.2928870292887029,0.29707112970711297,0.30125523012552297,0.305439330543933,0.30962343096234307,0.3138075313807531,0.3179916317991632,0.3221757322175732,0.3263598326359832,0.3305439330543933,0.33472803347280333,0.3389121338912134,0.3430962343096234,0.34728033472803344,0.3514644351464435,0.35564853556485354,0.3598326359832636,0.3640167364016736,0.36820083682008364,0.3723849372384937,0.37656903765690375,0.3807531380753138,0.3849372384937238,0.38912133891213385,0.3933054393305439,0.39748953974895396,0.40167364016736395,0.405857740585774,0.41004184100418406,0.4142259414225941,0.41841004184100417,0.42259414225941416,0.4267782426778242,0.43096234309623427,0.4351464435146443,0.4393305439330544,0.44351464435146437,0.4476987447698744,0.4518828451882845,0.45606694560669453,0.4602510460251046,0.4644351464435146,0.46861924686192463,0.4728033472803347,0.47698744769874474,0.4811715481171548,0.4853556485355648,0.48953974895397484,0.4937238493723849,0.49790794979079495,0.502092050209205,0.506276150627615,0.5104602510460251,0.5146443514644351,0.5188284518828451,0.5230125523012552,0.5271966527196652,0.5313807531380753,0.5355648535564853,0.5397489539748953,0.5439330543933054,0.5481171548117154,0.5523012552301255,0.5564853556485355,0.5606694560669455,0.5648535564853556,0.5690376569037656,0.5732217573221757,0.5774058577405857,0.5815899581589957,0.5857740585774058,0.5899581589958158,0.5941422594142259,0.5983263598326359,0.6025104602510459,0.606694560669456,0.610878661087866,0.6150627615062761,0.6192468619246861,0.6234309623430961,0.6276150627615062,0.6317991631799162,0.6359832635983264,0.6401673640167364,0.6443514644351463,0.6485355648535565,0.6527196652719665,0.6569037656903766,0.6610878661087866,0.6652719665271966,0.6694560669456067,0.6736401673640167,0.6778242677824268,0.6820083682008368,0.6861924686192468,0.6903765690376569,0.6945606694560669,0.698744769874477,0.702928870292887,0.707112970711297,0.7112970711297071,0.7154811715481171,0.7196652719665272,0.7238493723849372,0.7280334728033472,0.7322175732217573,0.7364016736401673,0.7405857740585774,0.7447698744769874,0.7489539748953974,0.7531380753138075,0.7573221757322175,0.7615062761506276,0.7656903765690376,0.7698744769874476,0.7740585774058577,0.7782426778242677,0.7824267782426778,0.7866108786610878,0.7907949790794978,0.7949790794979079,0.7991631799163179,0.8033472803347279,0.807531380753138,0.811715481171548,0.8158995815899581,0.8200836820083681,0.8242677824267781,0.8284518828451882,0.8326359832635982,0.8368200836820083,0.8410041841004183,0.8451882845188283,0.8493723849372384,0.8535564853556484,0.8577405857740585,0.8619246861924685,0.8661087866108785,0.8702928870292886,0.8744769874476986,0.8786610878661087,0.8828451882845187,0.8870292887029287,0.8912133891213389,0.8953974895397488,0.899581589958159,0.903765690376569,0.907949790794979,0.9121338912133891,0.9163179916317991,0.9205020920502092,0.9246861924686192,0.9288702928870292,0.9330543933054393,0.9372384937238493,0.9414225941422594,0.9456066945606694,0.9497907949790794,0.9539748953974895,0.9581589958158995,0.9623430962343096,0.9665271966527196,0.9707112970711296,0.9748953974895397,0.9790794979079497,0.9832635983263598,0.9874476987447698,0.9916317991631798,0.9958158995815899,1.0],"y":[0.1,0.10334728033472804,0.10669456066945607,0.1100418410041841,0.11338912133891214,0.11673640167364017,0.1200836820083682,0.12343096234309624,0.12677824267782428,0.1301255230125523,0.13347280334728034,0.13682008368200838,0.1401673640167364,0.14351464435146444,0.14686192468619247,0.1502092050209205,0.15355648535564853,0.15690376569037656,0.1602510460251046,0.16359832635983262,0.16694560669456066,0.1702928870292887,0.17364016736401672,0.17698744769874478,0.1803347280334728,0.18368200836820084,0.18702928870292887,0.1903765690376569,0.19372384937238493,0.19707112970711296,0.20041841004184102,0.20376569037656903,0.20711297071129708,0.21046025104602512,0.21380753138075315,0.21715481171548118,0.2205020920502092,0.22384937238493724,0.22719665271966527,0.2305439330543933,0.23389121338912133,0.23723849372384936,0.2405857740585774,0.24393305439330545,0.24728033472803346,0.25062761506276154,0.2539748953974895,0.2573221757322176,0.2606694560669456,0.26401673640167367,0.26736401673640164,0.27071129707112973,0.2740585774058577,0.2774058577405858,0.28075313807531377,0.28410041841004186,0.28744769874476983,0.2907949790794979,0.2941422594142259,0.297489539748954,0.300836820083682,0.30418410041841004,0.307531380753138,0.3108786610878661,0.31422594142259413,0.31757322175732217,0.32092050209205025,0.32426778242677823,0.32761506276150626,0.3309623430962343,0.3343096234309624,0.33765690376569035,0.3410041841004184,0.3443514644351464,0.3476987447698745,0.3510460251046025,0.35439330543933056,0.35774058577405854,0.3610878661087866,0.3644351464435146,0.3677824267782427,0.3711297071129708,0.37447698744769875,0.3778242677824267,0.3811715481171548,0.3845188284518829,0.3878661087866109,0.39121338912133885,0.39456066945606694,0.397907949790795,0.401255230125523,0.4046025104602511,0.40794979079497906,0.41129707112970715,0.4146443514644351,0.4179916317991632,0.4213389121338912,0.4246861924686193,0.42803347280334725,0.43138075313807533,0.4347280334728033,0.4380753138075314,0.4414225941422594,0.44476987447698746,0.44811715481171543,0.4514644351464435,0.4548117154811715,0.4581589958158996,0.46150627615062756,0.46485355648535565,0.46820083682008373,0.4715481171548117,0.4748953974895397,0.47824267782426777,0.48158995815899586,0.48493723849372383,0.4882845188284518,0.4916317991631799,0.494979079497908,0.49832635983263596,0.501673640167364,0.505020920502092,0.5083682008368201,0.5117154811715481,0.5150627615062761,0.5184100418410041,0.5217573221757322,0.5251046025104603,0.5284518828451883,0.5317991631799163,0.5351464435146444,0.5384937238493723,0.5418410041841004,0.5451882845188284,0.5485355648535565,0.5518828451882846,0.5552301255230125,0.5585774058577406,0.5619246861924686,0.5652719665271966,0.5686192468619247,0.5719665271966526,0.5753138075313807,0.5786610878661088,0.5820083682008368,0.5853556485355649,0.5887029288702929,0.5920502092050209,0.5953974895397489,0.5987447698744769,0.602092050209205,0.605439330543933,0.608786610878661,0.6121338912133891,0.6154811715481171,0.6188284518828452,0.6221757322175732,0.6255230125523012,0.6288702928870292,0.6322175732217572,0.6355648535564853,0.6389121338912134,0.6422594142259415,0.6456066945606694,0.6489539748953974,0.6523012552301255,0.6556485355648535,0.6589958158995816,0.6623430962343095,0.6656903765690376,0.6690376569037657,0.6723849372384937,0.6757322175732218,0.6790794979079497,0.6824267782426777,0.6857740585774058,0.6891213389121338,0.6924686192468619,0.69581589958159,0.6991631799163179,0.702510460251046,0.705857740585774,0.7092050209205021,0.7125523012552301,0.7158995815899581,0.7192468619246862,0.7225941422594142,0.7259414225941423,0.7292887029288703,0.7326359832635982,0.7359832635983263,0.7393305439330543,0.7426778242677824,0.7460251046025105,0.7493723849372385,0.7527196652719665,0.7560669456066945,0.7594142259414225,0.7627615062761506,0.7661087866108786,0.7694560669456066,0.7728033472803347,0.7761506276150627,0.7794979079497908,0.7828451882845188,0.7861924686192469,0.7895397489539748,0.7928870292887028,0.7962343096234309,0.799581589958159,0.8029288702928871,0.806276150627615,0.809623430962343,0.8129707112970711,0.8163179916317991,0.8196652719665272,0.8230125523012551,0.8263598326359832,0.8297071129707113,0.8330543933054393,0.8364016736401674,0.8397489539748954,0.8430962343096233,0.8464435146443514,0.8497907949790794,0.8531380753138075,0.8564853556485356,0.8598326359832635,0.8631799163179916,0.8665271966527196,0.8698744769874477,0.8732217573221757,0.8765690376569036,0.8799163179916317,0.8832635983263598,0.8866108786610879,0.8899581589958159,0.8933054393305438,0.8966527196652719,0.9],"type":"scatter"}], {"template":{"data":{"histogram2dcontour":[{"type":"histogram2dcontour","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"choropleth":[{"type":"choropleth","colorbar":{"outlinewidth":0,"ticks":""}}],"histogram2d":[{"type":"histogram2d","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"heatmap":[{"type":"heatmap","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"heatmapgl":[{"type":"heatmapgl","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"contourcarpet":[{"type":"contourcarpet","colorbar":{"outlinewidth":0,"ticks":""}}],"contour":[{"type":"contour","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"surface":[{"type":"surface","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"mesh3d":[{"type":"mesh3d","colorbar":{"outlinewidth":0,"ticks":""}}],"scatter":[{"fillpattern":{"fillmode":"overlay","size":10,"solidity":0.2},"type":"scatter"}],"parcoords":[{"type":"parcoords","line":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scatterpolargl":[{"type":"scatterpolargl","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"bar":[{"error_x":{"color":"#2a3f5f"},"error_y":{"color":"#2a3f5f"},"marker":{"line":{"color":"#E5ECF6","width":0.5},"pattern":{"fillmode":"overlay","size":10,"solidity":0.2}},"type":"bar"}],"scattergeo":[{"type":"scattergeo","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scatterpolar":[{"type":"scatterpolar","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"histogram":[{"marker":{"pattern":{"fillmode":"overlay","size":10,"solidity":0.2}},"type":"histogram"}],"scattergl":[{"type":"scattergl","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scatter3d":[{"type":"scatter3d","line":{"colorbar":{"outlinewidth":0,"ticks":""}},"marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scattermapbox":[{"type":"scattermapbox","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scatterternary":[{"type":"scatterternary","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scattercarpet":[{"type":"scattercarpet","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"carpet":[{"aaxis":{"endlinecolor":"#2a3f5f","gridcolor":"white","linecolor":"white","minorgridcolor":"white","startlinecolor":"#2a3f5f"},"baxis":{"endlinecolor":"#2a3f5f","gridcolor":"white","linecolor":"white","minorgridcolor":"white","startlinecolor":"#2a3f5f"},"type":"carpet"}],"table":[{"cells":{"fill":{"color":"#EBF0F8"},"line":{"color":"white"}},"header":{"fill":{"color":"#C8D4E3"},"line":{"color":"white"}},"type":"table"}],"barpolar":[{"marker":{"line":{"color":"#E5ECF6","width":0.5},"pattern":{"fillmode":"overlay","size":10,"solidity":0.2}},"type":"barpolar"}],"pie":[{"automargin":true,"type":"pie"}]},"layout":{"autotypenumbers":"strict","colorway":["#636efa","#EF553B","#00cc96","#ab63fa","#FFA15A","#19d3f3","#FF6692","#B6E880","#FF97FF","#FECB52"],"font":{"color":"#2a3f5f"},"hovermode":"closest","hoverlabel":{"align":"left"},"paper_bgcolor":"white","plot_bgcolor":"#E5ECF6","polar":{"bgcolor":"#E5ECF6","angularaxis":{"gridcolor":"white","linecolor":"white","ticks":""},"radialaxis":{"gridcolor":"white","linecolor":"white","ticks":""}},"ternary":{"bgcolor":"#E5ECF6","aaxis":{"gridcolor":"white","linecolor":"white","ticks":""},"baxis":{"gridcolor":"white","linecolor":"white","ticks":""},"caxis":{"gridcolor":"white","linecolor":"white","ticks":""}},"coloraxis":{"colorbar":{"outlinewidth":0,"ticks":""}},"colorscale":{"sequential":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"sequentialminus":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"diverging":[[0,"#8e0152"],[0.1,"#c51b7d"],[0.2,"#de77ae"],[0.3,"#f1b6da"],[0.4,"#fde0ef"],[0.5,"#f7f7f7"],[0.6,"#e6f5d0"],[0.7,"#b8e186"],[0.8,"#7fbc41"],[0.9,"#4d9221"],[1,"#276419"]]},"xaxis":{"gridcolor":"white","linecolor":"white","ticks":"","title":{"standoff":15},"zerolinecolor":"white","automargin":true,"zerolinewidth":2},"yaxis":{"gridcolor":"white","linecolor":"white","ticks":"","title":{"standoff":15},"zerolinecolor":"white","automargin":true,"zerolinewidth":2},"scene":{"xaxis":{"backgroundcolor":"#E5ECF6","gridcolor":"white","linecolor":"white","showbackground":true,"ticks":"","zerolinecolor":"white","gridwidth":2},"yaxis":{"backgroundcolor":"#E5ECF6","gridcolor":"white","linecolor":"white","showbackground":true,"ticks":"","zerolinecolor":"white","gridwidth":2},"zaxis":{"backgroundcolor":"#E5ECF6","gridcolor":"white","linecolor":"white","showbackground":true,"ticks":"","zerolinecolor":"white","gridwidth":2}},"shapedefaults":{"line":{"color":"#2a3f5f"}},"annotationdefaults":{"arrowcolor":"#2a3f5f","arrowhead":0,"arrowwidth":1},"geo":{"bgcolor":"white","landcolor":"#E5ECF6","subunitcolor":"white","showland":true,"showlakes":true,"lakecolor":"white"},"title":{"x":0.05},"mapbox":{"style":"light"}}},"margin":{"l":28,"r":12,"t":8,"b":28},"hoverlabel":{"font":{"color":"#111827","size":12},"bgcolor":"white","bordercolor":"rgba(0,0,0,0.15)","align":"left","namelength":-1},"xaxis":{"tickfont":{"size":12,"color":"rgba(0,0,0,0.55)"},"showgrid":false,"zeroline":false,"showline":true,"linecolor":"rgba(0,0,0,0.25)","linewidth":1,"ticks":"outside","ticklen":6,"tickcolor":"rgba(0,0,0,0.25)","title":{},"automargin":true,"fixedrange":true},"yaxis":{"tickfont":{"size":12,"color":"rgba(0,0,0,0.55)"},"showgrid":false,"zeroline":false,"showline":true,"linecolor":"rgba(0,0,0,0.25)","linewidth":1,"ticks":"outside","ticklen":6,"tickcolor":"rgba(0,0,0,0.25)","title":{},"tickformat":".2f","rangemode":"tozero","automargin":true,"fixedrange":true},"autosize":true,"paper_bgcolor":"rgba(0,0,0,0)","plot_bgcolor":"rgba(0,0,0,0)","hovermode":"x unified"}, {"displayModeBar": false, "responsive": true, "scrollZoom": false} ) }; </script> </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <div id="line-ex-container-7f91adbe">
3
+ <div> <div id="92f51efc-b046-49aa-b59d-742de70d1d52" class="plotly-graph-div" style="height:100%; width:100%;"></div> <script type="text/javascript"> window.PLOTLYENV=window.PLOTLYENV || {}; if (document.getElementById("92f51efc-b046-49aa-b59d-742de70d1d52")) { Plotly.newPlot( "92f51efc-b046-49aa-b59d-742de70d1d52", [{"hovertemplate":"\u003cb\u003e%{fullData.name}\u003c\u002fb\u003e\u003cbr\u003ex=%{x:.2f}\u003cbr\u003ey=%{y:.3f}\u003cextra\u003e\u003c\u002fextra\u003e","line":{"color":"#64748b","shape":"spline","smoothing":0.6,"width":2},"mode":"lines","name":"Baseline","showlegend":true,"x":[0.0,0.0041841004184100415,0.008368200836820083,0.012552301255230124,0.016736401673640166,0.02092050209205021,0.025104602510460247,0.02928870292887029,0.03347280334728033,0.03765690376569037,0.04184100418410042,0.046025104602510455,0.050209205020920494,0.05439330543933054,0.05857740585774058,0.06276150627615062,0.06694560669456066,0.0711297071129707,0.07531380753138074,0.0794979079497908,0.08368200836820083,0.08786610878661087,0.09205020920502091,0.09623430962343095,0.10041841004184099,0.10460251046025104,0.10878661087866108,0.11297071129707112,0.11715481171548116,0.1213389121338912,0.12552301255230125,0.12970711297071127,0.13389121338912133,0.13807531380753138,0.1422594142259414,0.14644351464435146,0.15062761506276148,0.15481171548117154,0.1589958158995816,0.1631799163179916,0.16736401673640167,0.1715481171548117,0.17573221757322174,0.1799163179916318,0.18410041841004182,0.18828451882845187,0.1924686192468619,0.19665271966527195,0.20083682008368198,0.20502092050209203,0.20920502092050208,0.2133891213389121,0.21757322175732216,0.22175732217573219,0.22594142259414224,0.2301255230125523,0.23430962343096232,0.23849372384937237,0.2426778242677824,0.24686192468619245,0.2510460251046025,0.25523012552301255,0.25941422594142255,0.2635983263598326,0.26778242677824265,0.2719665271966527,0.27615062761506276,0.28033472803347276,0.2845188284518828,0.28870292887029286,0.2928870292887029,0.29707112970711297,0.30125523012552297,0.305439330543933,0.30962343096234307,0.3138075313807531,0.3179916317991632,0.3221757322175732,0.3263598326359832,0.3305439330543933,0.33472803347280333,0.3389121338912134,0.3430962343096234,0.34728033472803344,0.3514644351464435,0.35564853556485354,0.3598326359832636,0.3640167364016736,0.36820083682008364,0.3723849372384937,0.37656903765690375,0.3807531380753138,0.3849372384937238,0.38912133891213385,0.3933054393305439,0.39748953974895396,0.40167364016736395,0.405857740585774,0.41004184100418406,0.4142259414225941,0.41841004184100417,0.42259414225941416,0.4267782426778242,0.43096234309623427,0.4351464435146443,0.4393305439330544,0.44351464435146437,0.4476987447698744,0.4518828451882845,0.45606694560669453,0.4602510460251046,0.4644351464435146,0.46861924686192463,0.4728033472803347,0.47698744769874474,0.4811715481171548,0.4853556485355648,0.48953974895397484,0.4937238493723849,0.49790794979079495,0.502092050209205,0.506276150627615,0.5104602510460251,0.5146443514644351,0.5188284518828451,0.5230125523012552,0.5271966527196652,0.5313807531380753,0.5355648535564853,0.5397489539748953,0.5439330543933054,0.5481171548117154,0.5523012552301255,0.5564853556485355,0.5606694560669455,0.5648535564853556,0.5690376569037656,0.5732217573221757,0.5774058577405857,0.5815899581589957,0.5857740585774058,0.5899581589958158,0.5941422594142259,0.5983263598326359,0.6025104602510459,0.606694560669456,0.610878661087866,0.6150627615062761,0.6192468619246861,0.6234309623430961,0.6276150627615062,0.6317991631799162,0.6359832635983264,0.6401673640167364,0.6443514644351463,0.6485355648535565,0.6527196652719665,0.6569037656903766,0.6610878661087866,0.6652719665271966,0.6694560669456067,0.6736401673640167,0.6778242677824268,0.6820083682008368,0.6861924686192468,0.6903765690376569,0.6945606694560669,0.698744769874477,0.702928870292887,0.707112970711297,0.7112970711297071,0.7154811715481171,0.7196652719665272,0.7238493723849372,0.7280334728033472,0.7322175732217573,0.7364016736401673,0.7405857740585774,0.7447698744769874,0.7489539748953974,0.7531380753138075,0.7573221757322175,0.7615062761506276,0.7656903765690376,0.7698744769874476,0.7740585774058577,0.7782426778242677,0.7824267782426778,0.7866108786610878,0.7907949790794978,0.7949790794979079,0.7991631799163179,0.8033472803347279,0.807531380753138,0.811715481171548,0.8158995815899581,0.8200836820083681,0.8242677824267781,0.8284518828451882,0.8326359832635982,0.8368200836820083,0.8410041841004183,0.8451882845188283,0.8493723849372384,0.8535564853556484,0.8577405857740585,0.8619246861924685,0.8661087866108785,0.8702928870292886,0.8744769874476986,0.8786610878661087,0.8828451882845187,0.8870292887029287,0.8912133891213389,0.8953974895397488,0.899581589958159,0.903765690376569,0.907949790794979,0.9121338912133891,0.9163179916317991,0.9205020920502092,0.9246861924686192,0.9288702928870292,0.9330543933054393,0.9372384937238493,0.9414225941422594,0.9456066945606694,0.9497907949790794,0.9539748953974895,0.9581589958158995,0.9623430962343096,0.9665271966527196,0.9707112970711296,0.9748953974895397,0.9790794979079497,0.9832635983263598,0.9874476987447698,0.9916317991631798,0.9958158995815899,1.0],"y":[0.10325611017271691,0.10339464908070704,0.1035390562715586,0.10368957804184283,0.10384647082790888,0.10401000160614818,0.10418044830755235,0.10435810024693962,0.10454325856722096,0.1047362366990729,0.10493736083637589,0.10514697042776965,0.10536541868466405,0.10559307310603182,0.10583031602029194,0.10607754514457322,0.10633517416162493,0.10660363331461449,0.10688337002002223,0.10717484949880761,0.10747855542598257,0.10779499059868251,0.10812467762277514,0.1084681596179913,0.10882600094149918,0.10919878792977272,0.10958712965852876,0.10999165872042038,0.11041303202008067,0.11085193158600631,0.11130906539865677,0.11178516823401995,0.11228100252175863,0.1127973592169032,0.11333505868389485,0.11389495159160672,0.114477919817781,0.11508487736111359,0.11571677125899543,0.11637458250868019,0.11705932698939046,0.117772056382598,0.11851385908741732,0.1192858611277356,0.12008922704736441,0.12092516078913879,0.1217949065535085,0.12269974963176174,0.12364101720859388,0.1246200791282849,0.12563834861827425,0.12669728296342733,0.12779838412376887,0.12894319928791798,0.1301333213539018,0.13137038932844328,0.13265608863522507,0.13399215132202053,0.13538035615596092,0.13682252859557953,0.13832054062763832,0.1398763104561116,0.14149180203007444,0.1431690243966304,0.14491003086442047,0.14671691796269073,0.14859182418036831,0.15053692846911637,0.15255444849391867,0.15464663861439287,0.1568157875797699,0.15906421592030787,0.1613942730178593,0.16380833383839039,0.16630879530947795,0.1688980723262078,0.17157859336947684,0.17435279572149145,0.1772231202642655,0.18019200584818182,0.18326188321920467,0.18643516849514025,0.1897142561834541,0.1931015117355877,0.19659926363548103,0.2002097950231238,0.2039353348574255,0.207778048626525,0.21174002861785352,0.21582328376481186,0.22002972909182084,0.22436117478472664,0.2288193149190735,0.23340571588455722,0.23812180455000417,0.24296885621943393,0.24794798243609284,0.2530601186977304,0.25830601215274074,0.2636862093530299,0.26920104414548673,0.2748506257896357,0.28063482739431467,0.28655327477093173,0.2926053358048954,0.29879011045004616,0.30510642145323597,0.31155280591747114,0.31812750781214627,0.3248284715377472,0.3316533366498935,0.3385994338436497,0.3456637822936055,0.3528430884382674,0.36013374628881456,0.36753183933226063,0.37503314408758337,0.38263313536051513,0.3903269932285346,0.3981096117723113,0.405975609553604,0.4139193418225966,0.42193491442011344,0.43001619932232193,0.4381568517577029,0.446350328808488,0.454589909391767,0.4628687154993053,0.4711797345600831,0.47951584277594816,0.48786982926879496,0.4962344208675833,0.504602307355484,0.5129661669916019,0.5213186921182653,0.5296526146637606,0.5379607313517324,0.5462359284321939,0.5544712057551259,0.5626597000159065,0.5707947070120986,0.5788697027632768,0.586878363359353,0.5948145834180083,0.6026724930481002,0.6104464732329847,0.6181311695653043,0.6257215042826447,0.6332126865712656,0.6406002211226275,0.6478799149443696,0.6550478824435727,0.6621005488153135,0.6690346517835489,0.6758472417541074,0.6825356804508947,0.6890976381162865,0.6955310893650131,0.7018343077876497,0.7080058594051007,0.7140445950792645,0.7199496419874267,0.72572039426896,0.7313565029526626,0.7368578652717028,0.7422246134707049,0.7474571032062077,0.7525559016376057,0.7575217753009073,0.7623556778523443,0.7670587377631131,0.7716322460404988,0.7760776440443701,0.7803965114616772,0.7845905544952088,0.7886615943165318,0.7926115558268515,0.7964424567635111,0.8001563971840802,0.8037555493544745,0.8072421480623625,0.810618481372249,0.813886881834114,0.8170497181533275,0.8201093873257699,0.8230683072386488,0.8259289097344318,0.8286936341325762,0.8313649212013411,0.833945207569883,0.836436920569059,0.8388424734878631,0.8411642612311867,0.8434046563636076,0.8455660055231439,0.8476506261883461,0.8496608037817258,0.8515987890923044,0.8534667959999981,0.8552669994846239,0.8570015339024812,0.8586724915137435,0.8602819212442462,0.8618318276656909,0.8633241701787625,0.8647608623841947,0.8661437716273805,0.8674747187027282,0.8687554777045662,0.869987776012039,0.8711732943960615,0.8723136672370364,0.8734104828426641,0.874465283855802,0.8754795677429297,0.8764547873543801,0.8773923515480664,0.8782936258689954,0.8791599332773964,0.8799925549188086,0.8807927309299669,0.8815616612747921,0.8823005066052454,0.8830103891422267,0.8836923935721009,0.8843475679548162,0.8849769246399327,0.8855814411872175,0.8861620612887775,0.8867196956899914,0.8872552231067824,0.8877694911370229,0.8882633171641062,0.888737489250938,0.889192767022802,0.8896298825377497,0.8900495411433326,0.8904524223186584,0.8908391805009009,0.8912104458955256],"type":"scatter"},{"hovertemplate":"\u003cb\u003e%{fullData.name}\u003c\u002fb\u003e\u003cbr\u003ex=%{x:.2f}\u003cbr\u003ey=%{y:.3f}\u003cextra\u003e\u003c\u002fextra\u003e","line":{"color":"#2563eb","shape":"spline","smoothing":0.6,"width":2},"mode":"lines","name":"Improved","showlegend":true,"x":[0.0,0.0041841004184100415,0.008368200836820083,0.012552301255230124,0.016736401673640166,0.02092050209205021,0.025104602510460247,0.02928870292887029,0.03347280334728033,0.03765690376569037,0.04184100418410042,0.046025104602510455,0.050209205020920494,0.05439330543933054,0.05857740585774058,0.06276150627615062,0.06694560669456066,0.0711297071129707,0.07531380753138074,0.0794979079497908,0.08368200836820083,0.08786610878661087,0.09205020920502091,0.09623430962343095,0.10041841004184099,0.10460251046025104,0.10878661087866108,0.11297071129707112,0.11715481171548116,0.1213389121338912,0.12552301255230125,0.12970711297071127,0.13389121338912133,0.13807531380753138,0.1422594142259414,0.14644351464435146,0.15062761506276148,0.15481171548117154,0.1589958158995816,0.1631799163179916,0.16736401673640167,0.1715481171548117,0.17573221757322174,0.1799163179916318,0.18410041841004182,0.18828451882845187,0.1924686192468619,0.19665271966527195,0.20083682008368198,0.20502092050209203,0.20920502092050208,0.2133891213389121,0.21757322175732216,0.22175732217573219,0.22594142259414224,0.2301255230125523,0.23430962343096232,0.23849372384937237,0.2426778242677824,0.24686192468619245,0.2510460251046025,0.25523012552301255,0.25941422594142255,0.2635983263598326,0.26778242677824265,0.2719665271966527,0.27615062761506276,0.28033472803347276,0.2845188284518828,0.28870292887029286,0.2928870292887029,0.29707112970711297,0.30125523012552297,0.305439330543933,0.30962343096234307,0.3138075313807531,0.3179916317991632,0.3221757322175732,0.3263598326359832,0.3305439330543933,0.33472803347280333,0.3389121338912134,0.3430962343096234,0.34728033472803344,0.3514644351464435,0.35564853556485354,0.3598326359832636,0.3640167364016736,0.36820083682008364,0.3723849372384937,0.37656903765690375,0.3807531380753138,0.3849372384937238,0.38912133891213385,0.3933054393305439,0.39748953974895396,0.40167364016736395,0.405857740585774,0.41004184100418406,0.4142259414225941,0.41841004184100417,0.42259414225941416,0.4267782426778242,0.43096234309623427,0.4351464435146443,0.4393305439330544,0.44351464435146437,0.4476987447698744,0.4518828451882845,0.45606694560669453,0.4602510460251046,0.4644351464435146,0.46861924686192463,0.4728033472803347,0.47698744769874474,0.4811715481171548,0.4853556485355648,0.48953974895397484,0.4937238493723849,0.49790794979079495,0.502092050209205,0.506276150627615,0.5104602510460251,0.5146443514644351,0.5188284518828451,0.5230125523012552,0.5271966527196652,0.5313807531380753,0.5355648535564853,0.5397489539748953,0.5439330543933054,0.5481171548117154,0.5523012552301255,0.5564853556485355,0.5606694560669455,0.5648535564853556,0.5690376569037656,0.5732217573221757,0.5774058577405857,0.5815899581589957,0.5857740585774058,0.5899581589958158,0.5941422594142259,0.5983263598326359,0.6025104602510459,0.606694560669456,0.610878661087866,0.6150627615062761,0.6192468619246861,0.6234309623430961,0.6276150627615062,0.6317991631799162,0.6359832635983264,0.6401673640167364,0.6443514644351463,0.6485355648535565,0.6527196652719665,0.6569037656903766,0.6610878661087866,0.6652719665271966,0.6694560669456067,0.6736401673640167,0.6778242677824268,0.6820083682008368,0.6861924686192468,0.6903765690376569,0.6945606694560669,0.698744769874477,0.702928870292887,0.707112970711297,0.7112970711297071,0.7154811715481171,0.7196652719665272,0.7238493723849372,0.7280334728033472,0.7322175732217573,0.7364016736401673,0.7405857740585774,0.7447698744769874,0.7489539748953974,0.7531380753138075,0.7573221757322175,0.7615062761506276,0.7656903765690376,0.7698744769874476,0.7740585774058577,0.7782426778242677,0.7824267782426778,0.7866108786610878,0.7907949790794978,0.7949790794979079,0.7991631799163179,0.8033472803347279,0.807531380753138,0.811715481171548,0.8158995815899581,0.8200836820083681,0.8242677824267781,0.8284518828451882,0.8326359832635982,0.8368200836820083,0.8410041841004183,0.8451882845188283,0.8493723849372384,0.8535564853556484,0.8577405857740585,0.8619246861924685,0.8661087866108785,0.8702928870292886,0.8744769874476986,0.8786610878661087,0.8828451882845187,0.8870292887029287,0.8912133891213389,0.8953974895397488,0.899581589958159,0.903765690376569,0.907949790794979,0.9121338912133891,0.9163179916317991,0.9205020920502092,0.9246861924686192,0.9288702928870292,0.9330543933054393,0.9372384937238493,0.9414225941422594,0.9456066945606694,0.9497907949790794,0.9539748953974895,0.9581589958158995,0.9623430962343096,0.9665271966527196,0.9707112970711296,0.9748953974895397,0.9790794979079497,0.9832635983263598,0.9874476987447698,0.9916317991631798,0.9958158995815899,1.0],"y":[0.14060501089565672,0.14088283778515465,0.14117437668125757,0.14148029457499495,0.14180128973406453,0.14213809304132727,0.1424914693767342,0.14286221904252114,0.14325117923128,0.14365922553626193,0.14408727350297595,0.14453628022082632,0.14500724595316689,0.14550121580375022,0.1460192814170989,0.14656258270983058,0.14713230962941984,0.14772970393627682,0.14835606100435697,0.1490127316347919,0.14970112387623702,0.15042270484476727,0.151179002535215,0.15197160761482742,0.15280217518902647,0.15367242652787275,0.15458415074057302,0.15553920638401897,0.1565395229899072,0.15758710249346727,0.15868402054521485,0.15983242768545844,0.1610345503595203,0.16229269174979818,0.16360923239889855,0.16498663059612784,0.1664274224976533,0.16793422194865357,0.16950971997379546,0.17115668390042355,0.17287795607696416,0.17467645214725785,0.17655515883988954,0.17851713123012414,0.1805654894308331,0.18270341466786982,0.18493414469478012,0.18726096850159143,0.18968722027278023,0.1922162725504525,0.1948515285603704,0.19759641366080336,0.20045436587736357,0.20342882549109012,0.20652322365216083,0.20974096999781483,0.21308543926043932,0.21655995686037258,0.2201677834878503,0.22391209868971296,0.22779598348899335,0.23182240207931556,0.23599418265109512,0.24031399742276002,0.2447843419674832,0.24940751394406385,0.25418559135939783,0.25912041050917184,0.264213543762687,0.2694662773766969,0.2748795895414215,0.2804541288790112,0.2861901936301986,0.2920877117781588,0.29814622236917493,0.30436485829703797,0.31074233082165825,0.317276916091671,0.32396644393541485,0.3308082891742017,0.3377993656960141,0.34493612350649583,0.35221454894733234,0.3596301682399747,0.36717805447542784,0.37485283812895587,0.38264872113267095,0.39055949448984884,0.39857855936337727,0.40669895151805197,0.4149133689436421,0.4232142024339775,0.43159356884802497,0.4400433467332665,0.4485552139508604,0.4571206869071671,0.46573116096821093,0.47437795161332363,0.48305233587216456,0.49174559358589653,0.5004490480386277,0.5091541055191895,0.5178522933955252,0.5265352963138058,0.535194990171079,0.5438234735527685,0.5524130963735796,0.5609564855110812,0.5694465672741473,0.5778765866022512,0.586240122945051,0.5945311028235718,0.6027438091235013,0.6108728872166885,0.6189133480480735,0.6268605683613184,0.6347102882669134,0.6424586063811748,0.6501019727832414,0.657637180049939,0.665061352635402,0.6723719348639443,0.679566677801265,0.6866436252611693,0.6936010991931518,0.7004376846810229,0.7071522147649059,0.7137437552790105,0.720211589876207,0.7265552053881889,0.7327742776474424,0.7388686578748471,0.7448383597149452,0.7506835469801073,0.7564045221453197,0.7620017156173483,0.7674756757858036,0.7728270598492599,0.778056625397137,0.7831652227175776,0.7881537877929914,0.7930233359382668,0.7977749560317459,0.8024098052858115,0.8069291045022122,0.8113341337568697,0.8156262284597393,0.8198067757371317,0.8238772110865894,0.8278390152577807,0.8316937113167614,0.8354428618552057,0.8390880663106919,0.842630958368713,0.8460732034216532,0.8494164960644374,0.852662557610846,0.8558131336185033,0.858869991414269,0.8618349176151412,0.8647097156427792,0.8674962032323732,0.870196209938834,0.872811574645113,0.8753441430789634,0.8777957653455672,0.8801682934842785,0.8824635790582229,0.8846834707857396,0.8868298122226409,0.8889044395040612,0.8909091791542693,0.892845845972296,0.8947162410005652,0.8965221495829927,0.8982653395181989,0.8999475593126457,0.901570536537641,0.9031359762932827,0.904645559781565,0.9061009429900344,0.9075037554865942,0.9088555993253137,0.9101580480623906,0.9114126458807956,0.9126209068215325,0.913784314118947,0.9149043196370531,0.9159823434034584,0.9170197732371421,0.918017964466056,0.918978239730313,0.9199018888665516,0.9207901688689508,0.9216443039222985,0.9224654855024788,0.9232548725397511,0.9240135916402323,0.9247427373610504,0.9254433725347414,0.9261165286385616,0.9267632062045259,0.9273843752661248,0.9279809758378323,0.9285539184236826,0.9291040845513728,0.9296323273285203,0.9301394720178946,0.9306263166286217,0.9310936325205441,0.9315421650191003,0.9319726340382659,0.9323857347092799,0.9327821380130337,0.9331624914141858,0.9335274194952079,0.9338775245887276,0.9342133874066783,0.9345355676649034,0.9348446047019983,0.9351410180912925,0.9354253082449973,0.9356979570096523,0.935959428252111,0.9362101684353965,0.9364506071838582,0.9366811578371357,0.9369022179925198,0.9371141700353723,0.9373173816573281,0.9375122063620713,0.9376989839585275,0.9378780410413667,0.9380496914587627,0.9382142367673885,0.9383719666746729,0.9385231594683747,0.9386680824335623,0.9388069922571125,0.9389401354198723],"type":"scatter"},{"hovertemplate":"\u003cb\u003e%{fullData.name}\u003c\u002fb\u003e\u003cbr\u003ex=%{x:.2f}\u003cbr\u003ey=%{y:.3f}\u003cextra\u003e\u003c\u002fextra\u003e","line":{"color":"#4b5563","dash":"dash","width":2},"mode":"lines","name":"Target","showlegend":true,"x":[0.0,0.0041841004184100415,0.008368200836820083,0.012552301255230124,0.016736401673640166,0.02092050209205021,0.025104602510460247,0.02928870292887029,0.03347280334728033,0.03765690376569037,0.04184100418410042,0.046025104602510455,0.050209205020920494,0.05439330543933054,0.05857740585774058,0.06276150627615062,0.06694560669456066,0.0711297071129707,0.07531380753138074,0.0794979079497908,0.08368200836820083,0.08786610878661087,0.09205020920502091,0.09623430962343095,0.10041841004184099,0.10460251046025104,0.10878661087866108,0.11297071129707112,0.11715481171548116,0.1213389121338912,0.12552301255230125,0.12970711297071127,0.13389121338912133,0.13807531380753138,0.1422594142259414,0.14644351464435146,0.15062761506276148,0.15481171548117154,0.1589958158995816,0.1631799163179916,0.16736401673640167,0.1715481171548117,0.17573221757322174,0.1799163179916318,0.18410041841004182,0.18828451882845187,0.1924686192468619,0.19665271966527195,0.20083682008368198,0.20502092050209203,0.20920502092050208,0.2133891213389121,0.21757322175732216,0.22175732217573219,0.22594142259414224,0.2301255230125523,0.23430962343096232,0.23849372384937237,0.2426778242677824,0.24686192468619245,0.2510460251046025,0.25523012552301255,0.25941422594142255,0.2635983263598326,0.26778242677824265,0.2719665271966527,0.27615062761506276,0.28033472803347276,0.2845188284518828,0.28870292887029286,0.2928870292887029,0.29707112970711297,0.30125523012552297,0.305439330543933,0.30962343096234307,0.3138075313807531,0.3179916317991632,0.3221757322175732,0.3263598326359832,0.3305439330543933,0.33472803347280333,0.3389121338912134,0.3430962343096234,0.34728033472803344,0.3514644351464435,0.35564853556485354,0.3598326359832636,0.3640167364016736,0.36820083682008364,0.3723849372384937,0.37656903765690375,0.3807531380753138,0.3849372384937238,0.38912133891213385,0.3933054393305439,0.39748953974895396,0.40167364016736395,0.405857740585774,0.41004184100418406,0.4142259414225941,0.41841004184100417,0.42259414225941416,0.4267782426778242,0.43096234309623427,0.4351464435146443,0.4393305439330544,0.44351464435146437,0.4476987447698744,0.4518828451882845,0.45606694560669453,0.4602510460251046,0.4644351464435146,0.46861924686192463,0.4728033472803347,0.47698744769874474,0.4811715481171548,0.4853556485355648,0.48953974895397484,0.4937238493723849,0.49790794979079495,0.502092050209205,0.506276150627615,0.5104602510460251,0.5146443514644351,0.5188284518828451,0.5230125523012552,0.5271966527196652,0.5313807531380753,0.5355648535564853,0.5397489539748953,0.5439330543933054,0.5481171548117154,0.5523012552301255,0.5564853556485355,0.5606694560669455,0.5648535564853556,0.5690376569037656,0.5732217573221757,0.5774058577405857,0.5815899581589957,0.5857740585774058,0.5899581589958158,0.5941422594142259,0.5983263598326359,0.6025104602510459,0.606694560669456,0.610878661087866,0.6150627615062761,0.6192468619246861,0.6234309623430961,0.6276150627615062,0.6317991631799162,0.6359832635983264,0.6401673640167364,0.6443514644351463,0.6485355648535565,0.6527196652719665,0.6569037656903766,0.6610878661087866,0.6652719665271966,0.6694560669456067,0.6736401673640167,0.6778242677824268,0.6820083682008368,0.6861924686192468,0.6903765690376569,0.6945606694560669,0.698744769874477,0.702928870292887,0.707112970711297,0.7112970711297071,0.7154811715481171,0.7196652719665272,0.7238493723849372,0.7280334728033472,0.7322175732217573,0.7364016736401673,0.7405857740585774,0.7447698744769874,0.7489539748953974,0.7531380753138075,0.7573221757322175,0.7615062761506276,0.7656903765690376,0.7698744769874476,0.7740585774058577,0.7782426778242677,0.7824267782426778,0.7866108786610878,0.7907949790794978,0.7949790794979079,0.7991631799163179,0.8033472803347279,0.807531380753138,0.811715481171548,0.8158995815899581,0.8200836820083681,0.8242677824267781,0.8284518828451882,0.8326359832635982,0.8368200836820083,0.8410041841004183,0.8451882845188283,0.8493723849372384,0.8535564853556484,0.8577405857740585,0.8619246861924685,0.8661087866108785,0.8702928870292886,0.8744769874476986,0.8786610878661087,0.8828451882845187,0.8870292887029287,0.8912133891213389,0.8953974895397488,0.899581589958159,0.903765690376569,0.907949790794979,0.9121338912133891,0.9163179916317991,0.9205020920502092,0.9246861924686192,0.9288702928870292,0.9330543933054393,0.9372384937238493,0.9414225941422594,0.9456066945606694,0.9497907949790794,0.9539748953974895,0.9581589958158995,0.9623430962343096,0.9665271966527196,0.9707112970711296,0.9748953974895397,0.9790794979079497,0.9832635983263598,0.9874476987447698,0.9916317991631798,0.9958158995815899,1.0],"y":[0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97,0.97],"type":"scatter"}], {"template":{"data":{"histogram2dcontour":[{"type":"histogram2dcontour","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"choropleth":[{"type":"choropleth","colorbar":{"outlinewidth":0,"ticks":""}}],"histogram2d":[{"type":"histogram2d","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"heatmap":[{"type":"heatmap","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"heatmapgl":[{"type":"heatmapgl","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"contourcarpet":[{"type":"contourcarpet","colorbar":{"outlinewidth":0,"ticks":""}}],"contour":[{"type":"contour","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"surface":[{"type":"surface","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"mesh3d":[{"type":"mesh3d","colorbar":{"outlinewidth":0,"ticks":""}}],"scatter":[{"fillpattern":{"fillmode":"overlay","size":10,"solidity":0.2},"type":"scatter"}],"parcoords":[{"type":"parcoords","line":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scatterpolargl":[{"type":"scatterpolargl","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"bar":[{"error_x":{"color":"#2a3f5f"},"error_y":{"color":"#2a3f5f"},"marker":{"line":{"color":"#E5ECF6","width":0.5},"pattern":{"fillmode":"overlay","size":10,"solidity":0.2}},"type":"bar"}],"scattergeo":[{"type":"scattergeo","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scatterpolar":[{"type":"scatterpolar","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"histogram":[{"marker":{"pattern":{"fillmode":"overlay","size":10,"solidity":0.2}},"type":"histogram"}],"scattergl":[{"type":"scattergl","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scatter3d":[{"type":"scatter3d","line":{"colorbar":{"outlinewidth":0,"ticks":""}},"marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scattermapbox":[{"type":"scattermapbox","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scatterternary":[{"type":"scatterternary","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scattercarpet":[{"type":"scattercarpet","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"carpet":[{"aaxis":{"endlinecolor":"#2a3f5f","gridcolor":"white","linecolor":"white","minorgridcolor":"white","startlinecolor":"#2a3f5f"},"baxis":{"endlinecolor":"#2a3f5f","gridcolor":"white","linecolor":"white","minorgridcolor":"white","startlinecolor":"#2a3f5f"},"type":"carpet"}],"table":[{"cells":{"fill":{"color":"#EBF0F8"},"line":{"color":"white"}},"header":{"fill":{"color":"#C8D4E3"},"line":{"color":"white"}},"type":"table"}],"barpolar":[{"marker":{"line":{"color":"#E5ECF6","width":0.5},"pattern":{"fillmode":"overlay","size":10,"solidity":0.2}},"type":"barpolar"}],"pie":[{"automargin":true,"type":"pie"}]},"layout":{"autotypenumbers":"strict","colorway":["#636efa","#EF553B","#00cc96","#ab63fa","#FFA15A","#19d3f3","#FF6692","#B6E880","#FF97FF","#FECB52"],"font":{"color":"#2a3f5f"},"hovermode":"closest","hoverlabel":{"align":"left"},"paper_bgcolor":"white","plot_bgcolor":"#E5ECF6","polar":{"bgcolor":"#E5ECF6","angularaxis":{"gridcolor":"white","linecolor":"white","ticks":""},"radialaxis":{"gridcolor":"white","linecolor":"white","ticks":""}},"ternary":{"bgcolor":"#E5ECF6","aaxis":{"gridcolor":"white","linecolor":"white","ticks":""},"baxis":{"gridcolor":"white","linecolor":"white","ticks":""},"caxis":{"gridcolor":"white","linecolor":"white","ticks":""}},"coloraxis":{"colorbar":{"outlinewidth":0,"ticks":""}},"colorscale":{"sequential":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"sequentialminus":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"diverging":[[0,"#8e0152"],[0.1,"#c51b7d"],[0.2,"#de77ae"],[0.3,"#f1b6da"],[0.4,"#fde0ef"],[0.5,"#f7f7f7"],[0.6,"#e6f5d0"],[0.7,"#b8e186"],[0.8,"#7fbc41"],[0.9,"#4d9221"],[1,"#276419"]]},"xaxis":{"gridcolor":"white","linecolor":"white","ticks":"","title":{"standoff":15},"zerolinecolor":"white","automargin":true,"zerolinewidth":2},"yaxis":{"gridcolor":"white","linecolor":"white","ticks":"","title":{"standoff":15},"zerolinecolor":"white","automargin":true,"zerolinewidth":2},"scene":{"xaxis":{"backgroundcolor":"#E5ECF6","gridcolor":"white","linecolor":"white","showbackground":true,"ticks":"","zerolinecolor":"white","gridwidth":2},"yaxis":{"backgroundcolor":"#E5ECF6","gridcolor":"white","linecolor":"white","showbackground":true,"ticks":"","zerolinecolor":"white","gridwidth":2},"zaxis":{"backgroundcolor":"#E5ECF6","gridcolor":"white","linecolor":"white","showbackground":true,"ticks":"","zerolinecolor":"white","gridwidth":2}},"shapedefaults":{"line":{"color":"#2a3f5f"}},"annotationdefaults":{"arrowcolor":"#2a3f5f","arrowhead":0,"arrowwidth":1},"geo":{"bgcolor":"white","landcolor":"#E5ECF6","subunitcolor":"white","showland":true,"showlakes":true,"lakecolor":"white"},"title":{"x":0.05},"mapbox":{"style":"light"}}},"margin":{"l":40,"r":28,"t":20,"b":40},"legend":{"orientation":"v","x":1,"y":0,"xanchor":"right","yanchor":"bottom","bgcolor":"rgba(255,255,255,0)","borderwidth":0},"hoverlabel":{"font":{"color":"#111827","size":12},"bgcolor":"white","bordercolor":"rgba(0,0,0,0.15)","align":"left","namelength":-1},"xaxis":{"tickfont":{"size":12,"color":"rgba(0,0,0,0.55)"},"showgrid":false,"zeroline":false,"showline":true,"linecolor":"rgba(0,0,0,0.25)","linewidth":1,"ticks":"outside","ticklen":6,"tickcolor":"rgba(0,0,0,0.25)","title":{},"automargin":true,"fixedrange":true},"yaxis":{"tickfont":{"size":12,"color":"rgba(0,0,0,0.55)"},"showgrid":false,"zeroline":false,"showline":true,"linecolor":"rgba(0,0,0,0.25)","linewidth":1,"ticks":"outside","ticklen":6,"tickcolor":"rgba(0,0,0,0.25)","title":{},"tickformat":".2f","rangemode":"tozero","automargin":true,"fixedrange":true},"autosize":true,"paper_bgcolor":"rgba(0,0,0,0)","plot_bgcolor":"rgba(0,0,0,0)","hovermode":"x unified"}, {"displayModeBar": false, "responsive": true, "scrollZoom": false, "doubleClick": false, "modeBarButtonsToRemove": ["zoom2d", "pan2d", "select2d", "lasso2d", "zoomIn2d", "zoomOut2d", "autoScale2d", "resetScale2d", "toggleSpikelines"]} ).then(function(){
4
+
5
+ (function(){
6
+ function attach(gd){
7
+ function round(){
8
+ try {
9
+ var root = gd && gd.parentNode ? gd.parentNode : document;
10
+ var rects = root.querySelectorAll('.hoverlayer .hovertext rect');
11
+ rects.forEach(function(r){ r.setAttribute('rx', 8); r.setAttribute('ry', 8); });
12
+ } catch(e) {}
13
+ }
14
+ if (gd && gd.on) {
15
+ gd.on('plotly_hover', round);
16
+ gd.on('plotly_unhover', round);
17
+ gd.on('plotly_relayout', round);
18
+ }
19
+ setTimeout(round, 0);
20
+ }
21
+ var plots = document.querySelectorAll('.js-plotly-plot');
22
+ plots.forEach(attach);
23
+ })();
24
+
25
+ }) }; </script> </div>
26
+ <div class="plotly_controls" style="margin-top:12px; display:flex; gap:16px; align-items:center;">
27
+ <label style="font-size:12px;color:rgba(0,0,0,.65); display:flex; align-items:center; gap:6px; white-space:nowrap; padding:6px 10px;">
28
+ Dataset
29
+ <select id="__DSID__" style="font-size:12px; padding:2px 6px;">
30
+ <option value="0">CIFAR-10</option>
31
+ <option value="1">CIFAR-100</option>
32
+ <option value="2">ImageNet-1K</option>
33
+ </select>
34
+ </label>
35
+ <label style="font-size:12px;color:rgba(0,0,0,.65);display:flex;align-items:center;gap:10px; flex:1; padding:6px 10px;">
36
+ Augmentation α
37
+ <input id="line-ex-alpha-7f91adbe" type="range" min="0" max="1" step="0.01" value="0.70" style="flex:1;">
38
+ <span class="alpha-value">0.70</span>
39
+ </label>
40
+ </div>
41
+ </div>
42
+ <script>
43
+ (function(){
44
+ var container = document.getElementById('line-ex-container-7f91adbe');
45
+ if(!container) return;
46
+ var gd = container.querySelector('.js-plotly-plot');
47
+ var slider = document.getElementById('line-ex-alpha-7f91adbe');
48
+ var dsSelect = document.getElementById('__DSID__');
49
+ var valueEl = container.querySelector('.alpha-value');
50
+ var N = 240;
51
+ var xs = Array.from({length: N}, function(_,i){ return i/(N-1); });
52
+ function logistic(x, ymin, ymax, k, x0){ return ymin + (ymax - ymin) / (1 + Math.exp(-k*(x - x0))); }
53
+ function blend(l,e,a){ return (1-a)*l + a*e; }
54
+ var datasets = [
55
+ { name:'CIFAR-10', base:{ymin:0.10,ymax:0.90,k:10.0,x0:0.55}, aug:{ymin:0.15,ymax:0.96,k:12.0,x0:0.40}, target:0.97 },
56
+ { name:'CIFAR-100', base:{ymin:0.05,ymax:0.70,k:9.5,x0:0.60}, aug:{ymin:0.08,ymax:0.80,k:11.0,x0:0.45}, target:0.85 },
57
+ { name:'ImageNet-1K', base:{ymin:0.02,ymax:0.68,k:8.5,x0:0.65}, aug:{ymin:0.04,ymax:0.75,k:9.5,x0:0.50}, target:0.82 }
58
+ ];
59
+ var dsi = 0;
60
+ var yb = xs.map(function(x){ return logistic(x, datasets[dsi].base.ymin, datasets[dsi].base.ymax, datasets[dsi].base.k, datasets[dsi].base.x0); });
61
+ var ya = xs.map(function(x){ return logistic(x, datasets[dsi].aug.ymin, datasets[dsi].aug.ymax, datasets[dsi].aug.k, datasets[dsi].aug.x0); });
62
+ var yt = xs.map(function(){ return datasets[dsi].target; });
63
+ function applyAlpha(a){
64
+ var yi = yb.map(function(v,i){ return blend(v, ya[i], a); });
65
+ Plotly.restyle(gd, {y:[yi]}, [1]); // only Improved changes with α
66
+ if(valueEl) valueEl.textContent = a.toFixed(2);
67
+ }
68
+ function applyDataset(){
69
+ var d = datasets[dsi];
70
+ yb = xs.map(function(x){ return logistic(x, d.base.ymin, d.base.ymax, d.base.k, d.base.x0); });
71
+ ya = xs.map(function(x){ return logistic(x, d.aug.ymin, d.aug.ymax, d.aug.k, d.aug.x0); });
72
+ yt = xs.map(function(){ return d.target; });
73
+ var a = parseFloat(slider.value)||0;
74
+ var yi = yb.map(function(v,i){ return blend(v, ya[i], a); });
75
+ Plotly.restyle(gd, {y:[yb]}, [0]); // Baseline
76
+ Plotly.restyle(gd, {y:[yi]}, [1]); // Improved (blended)
77
+ Plotly.restyle(gd, {y:[yt]}, [2]); // Target
78
+ }
79
+ var initA = parseFloat(slider.value)||0;
80
+ slider.addEventListener('input', function(e){ applyAlpha(parseFloat(e.target.value)||0); });
81
+ dsSelect.addEventListener('change', function(e){ dsi = parseInt(e.target.value)||0; applyDataset(); });
82
+ setTimeout(function(){ applyDataset(); applyAlpha(initA); }, 0);
83
+ })();
84
+ </script>
app/src/content/fragments/palettes.html CHANGED
@@ -12,7 +12,7 @@
12
  .palettes .palette-card__swatches .sw:first-child { border-top-left-radius: 6px; border-bottom-left-radius: 6px; }
13
  .palettes .palette-card__swatches .sw:last-child { border-top-right-radius: 6px; border-bottom-right-radius: 6px; }
14
  .palettes .palette-card__footer { margin-top: auto; display: flex; flex-direction: column; gap: 8px; }
15
- .palettes .copy-btn { width: 100%; margin: 0; padding: 8px 10px; border-radius: 6px; border: 1px solid var(--border-color); background: var(--surface-bg); color: var(--text-color); font-size: 12px; cursor: pointer; }
16
  .palettes .palettes__meta { display: flex; align-items: center; gap: 10px; justify-content: space-between; }
17
  .palettes .current-color { display: flex; align-items: center; gap: 10px; }
18
  .palettes .current-swatch { width: 20px; height: 20px; border-radius: 50%; border: 1px solid var(--border-color); }
@@ -24,10 +24,10 @@
24
  <div class="palettes__controls" style="display:flex; flex-direction:column; gap:10px; margin-bottom:24px;">
25
  <div style="display:flex; align-items:center; justify-content:space-between; gap:12px;">
26
  <div style="font-weight:700;">Pick a hue</div>
27
- <div class="hue-value" style="font-variant-numeric: tabular-nums; color: var(--muted-color);">H 220°</div>
28
  </div>
29
  <div class="hue-slider" style="position:relative; height:18px; border-radius:10px; border:1px solid var(--border-color); background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%); cursor: ew-resize; touch-action: none;">
30
- <div class="hue-knob" style="position:absolute; top:50%; left:61.1%; width:14px; height:14px; border-radius:50%; border:2px solid #fff; box-shadow:0 0 0 1px rgba(0,0,0,.2), 0 2px 8px rgba(0,0,0,.25); transform:translate(-50%, -50%); background: var(--surface-bg); z-index: 2;"></div>
31
  </div>
32
  <div class="palettes__meta">
33
  <div class="current-color">
@@ -101,6 +101,20 @@
101
  const simSelect = root.querySelector('.cvd-select');
102
  console.log('[palettes] elements', { root: !!root, grid: !!grid, slider: !!slider, knob: !!knob, hueValue: !!hueValue });
103
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  // Cards data with full descriptions
105
  const cards = [
106
  { key: 'categorical', title: 'Categorical', desc: 'Categorical colors help users map non-numeric meaning to objects in a visualization. These are designed to be visually distinct from one another. Maximum of six.', generator: (base) => {
@@ -168,7 +182,19 @@
168
  // Rendering
169
  const renderPalettes = (h) => {
170
  console.log('[palettes] renderPalettes', h);
171
- const base = chroma.hsl(h, 0.75, 0.55);
 
 
 
 
 
 
 
 
 
 
 
 
172
  const uniformText = (bg) => (chroma(bg).luminance() > 0.45 ? '#111' : '#fff');
173
 
174
  // Update current swatch + name
@@ -260,12 +286,16 @@
260
  };
261
 
262
  // Hue slider behavior
263
- let hue = 220; // initial
 
264
  const setHue = (h) => { hue = (h + 360) % 360; const pct = hue / 360 * 100; if (knob) knob.style.left = pct + '%'; if (hueValue) hueValue.textContent = `H ${Math.round(hue)}°`; console.log('[palettes] setHue', hue, pct); renderPalettes(hue); };
265
  const getHueFromEvent = (ev) => { const rect = slider.getBoundingClientRect(); const clientX = ev.touches ? ev.touches[0].clientX : ev.clientX; const x = clientX - rect.left; const t = Math.max(0, Math.min(1, x / rect.width)); const h = t * 360; console.log('[palettes] getHueFromEvent', { clientX, left: rect.left, width: rect.width, t, h }); return h; };
266
- const onDown = (ev) => { console.log('[palettes] onDown', ev.type); ev.preventDefault(); setHue(getHueFromEvent(ev)); const move = (e) => { e.preventDefault && e.preventDefault(); setHue(getHueFromEvent(e)); }; const up = () => { console.log('[palettes] onUp'); window.removeEventListener('mousemove', move); window.removeEventListener('touchmove', move); window.removeEventListener('mouseup', up); window.removeEventListener('touchend', up); }; window.addEventListener('mousemove', move, { passive: false }); window.addEventListener('touchmove', move, { passive: false }); window.addEventListener('mouseup', up, { once: true }); window.addEventListener('touchend', up, { once: true }); };
267
  if (slider) { slider.addEventListener('mousedown', onDown); slider.addEventListener('touchstart', onDown, { passive: false }); console.log('[palettes] listeners attached'); } else { console.warn('[palettes] slider not found'); }
268
 
 
 
 
269
  // Color-vision simulation filters (SVG)
270
  const injectFilters = () => {
271
  if (document.getElementById('cvd-filters')) return;
@@ -290,7 +320,10 @@
290
  if (simSelect) simSelect.addEventListener('change', () => applySimulation(simSelect.value));
291
 
292
  console.log('[palettes] initial render');
293
- setHue(hue);
 
 
 
294
  applySimulation('none');
295
 
296
  // Fixed 3 columns layout
 
12
  .palettes .palette-card__swatches .sw:first-child { border-top-left-radius: 6px; border-bottom-left-radius: 6px; }
13
  .palettes .palette-card__swatches .sw:last-child { border-top-right-radius: 6px; border-bottom-right-radius: 6px; }
14
  .palettes .palette-card__footer { margin-top: auto; display: flex; flex-direction: column; gap: 8px; }
15
+ .palettes .copy-btn { width: 100%; margin: 0; padding: 8px 10px; border-radius: 6px; border: 1px solid var(--border-color); background: var(--surface-bg); color: var(--text-color)!important; font-size: 12px; cursor: pointer; }
16
  .palettes .palettes__meta { display: flex; align-items: center; gap: 10px; justify-content: space-between; }
17
  .palettes .current-color { display: flex; align-items: center; gap: 10px; }
18
  .palettes .current-swatch { width: 20px; height: 20px; border-radius: 50%; border: 1px solid var(--border-color); }
 
24
  <div class="palettes__controls" style="display:flex; flex-direction:column; gap:10px; margin-bottom:24px;">
25
  <div style="display:flex; align-items:center; justify-content:space-between; gap:12px;">
26
  <div style="font-weight:700;">Pick a hue</div>
27
+ <div class="hue-value" style="font-variant-numeric: tabular-nums; color: var(--muted-color);">H 337°</div>
28
  </div>
29
  <div class="hue-slider" style="position:relative; height:18px; border-radius:10px; border:1px solid var(--border-color); background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%); cursor: ew-resize; touch-action: none;">
30
+ <div class="hue-knob" style="position:absolute; top:50%; left:93.6%; width:14px; height:14px; border-radius:50%; border:2px solid #fff; transform:translate(-50%, -50%); background: var(--surface-bg); z-index: 2;"></div>
31
  </div>
32
  <div class="palettes__meta">
33
  <div class="current-color">
 
101
  const simSelect = root.querySelector('.cvd-select');
102
  console.log('[palettes] elements', { root: !!root, grid: !!grid, slider: !!slider, knob: !!knob, hueValue: !!hueValue });
103
 
104
+ // Shared bus to sync with color-picker instances
105
+ if (!window.__colorPickerBus) {
106
+ window.__colorPickerBus = (() => {
107
+ let hue = 337; let adjusting = false; const listeners = new Set();
108
+ return {
109
+ get: () => ({ hue, adjusting }),
110
+ publish: (sourceId, nextHue, isAdjusting) => { hue = ((nextHue % 360) + 360) % 360; adjusting = !!isAdjusting; listeners.forEach(fn => { try { fn({ sourceId, hue, adjusting }); } catch {} }); },
111
+ subscribe: (fn) => { listeners.add(fn); return () => listeners.delete(fn); }
112
+ };
113
+ })();
114
+ }
115
+ const bus = window.__colorPickerBus;
116
+ const instanceId = Math.random().toString(36).slice(2);
117
+
118
  // Cards data with full descriptions
119
  const cards = [
120
  { key: 'categorical', title: 'Categorical', desc: 'Categorical colors help users map non-numeric meaning to objects in a visualization. These are designed to be visually distinct from one another. Maximum of six.', generator: (base) => {
 
182
  // Rendering
183
  const renderPalettes = (h) => {
184
  console.log('[palettes] renderPalettes', h);
185
+ const base = chroma.hsl(h, 0.67, 0.72);
186
+ // Update CSS variables for live theming only during interaction
187
+ try {
188
+ if (isAdjusting) {
189
+ const baseHex = base.hex();
190
+ const hoverHex = chroma(base).darken(0.6).hex();
191
+ const rootEl = document.documentElement;
192
+ rootEl.style.setProperty('--primary', baseHex);
193
+ rootEl.style.setProperty('--primary-hover', hoverHex);
194
+ }
195
+ } catch (e) {
196
+ console.warn('[palettes] failed updating CSS vars', e);
197
+ }
198
  const uniformText = (bg) => (chroma(bg).luminance() > 0.45 ? '#111' : '#fff');
199
 
200
  // Update current swatch + name
 
286
  };
287
 
288
  // Hue slider behavior
289
+ let hue = 337; // initial
290
+ let isAdjusting = false; // only update theme while interacting
291
  const setHue = (h) => { hue = (h + 360) % 360; const pct = hue / 360 * 100; if (knob) knob.style.left = pct + '%'; if (hueValue) hueValue.textContent = `H ${Math.round(hue)}°`; console.log('[palettes] setHue', hue, pct); renderPalettes(hue); };
292
  const getHueFromEvent = (ev) => { const rect = slider.getBoundingClientRect(); const clientX = ev.touches ? ev.touches[0].clientX : ev.clientX; const x = clientX - rect.left; const t = Math.max(0, Math.min(1, x / rect.width)); const h = t * 360; console.log('[palettes] getHueFromEvent', { clientX, left: rect.left, width: rect.width, t, h }); return h; };
293
+ const onDown = (ev) => { console.log('[palettes] onDown', ev.type); ev.preventDefault(); isAdjusting = true; const h0 = getHueFromEvent(ev); setHue(h0); bus.publish(instanceId, h0, true); const move = (e) => { e.preventDefault && e.preventDefault(); const hh = getHueFromEvent(e); setHue(hh); bus.publish(instanceId, hh, true); }; const up = () => { console.log('[palettes] onUp'); isAdjusting = false; bus.publish(instanceId, hue, false); window.removeEventListener('mousemove', move); window.removeEventListener('touchmove', move); window.removeEventListener('mouseup', up); window.removeEventListener('touchend', up); }; window.addEventListener('mousemove', move, { passive: false }); window.addEventListener('touchmove', move, { passive: false }); window.addEventListener('mouseup', up, { once: true }); window.addEventListener('touchend', up, { once: true }); };
294
  if (slider) { slider.addEventListener('mousedown', onDown); slider.addEventListener('touchstart', onDown, { passive: false }); console.log('[palettes] listeners attached'); } else { console.warn('[palettes] slider not found'); }
295
 
296
+ // Subscribe to bus to receive updates from other pickers
297
+ const unsubscribe = bus.subscribe(({ sourceId, hue: H, adjusting }) => { if (sourceId === instanceId) return; isAdjusting = adjusting; setHue(H); });
298
+
299
  // Color-vision simulation filters (SVG)
300
  const injectFilters = () => {
301
  if (document.getElementById('cvd-filters')) return;
 
320
  if (simSelect) simSelect.addEventListener('change', () => applySimulation(simSelect.value));
321
 
322
  console.log('[palettes] initial render');
323
+ // Initialize from shared hue without updating CSS variables
324
+ const shared = bus.get();
325
+ isAdjusting = false;
326
+ setHue(shared && typeof shared.hue === 'number' ? shared.hue : hue);
327
  applySimulation('none');
328
 
329
  // Fixed 3 columns layout
app/src/pages/index.astro CHANGED
@@ -7,7 +7,7 @@ import ThemeToggle from '../components/ThemeToggle.astro';
7
  import SeoHead from '../components/SeoHead.astro';
8
  import ogDefault from '../assets/images/visual-vocabulary-poster.png';
9
  import 'katex/dist/katex.min.css';
10
- import '../styles/global.scss';
11
  const docTitle = articleFM?.title ?? 'Untitled article';
12
  // Allow explicit line breaks in the title via "\n" or YAML newlines
13
  const docTitleHtml = (articleFM?.title ?? 'Untitled article')
 
7
  import SeoHead from '../components/SeoHead.astro';
8
  import ogDefault from '../assets/images/visual-vocabulary-poster.png';
9
  import 'katex/dist/katex.min.css';
10
+ import '../styles/global.css';
11
  const docTitle = articleFM?.title ?? 'Untitled article';
12
  // Allow explicit line breaks in the title via "\n" or YAML newlines
13
  const docTitleHtml = (articleFM?.title ?? 'Untitled article')
app/src/styles/{_base.scss → _base.css} RENAMED
@@ -1,16 +1,16 @@
1
- // ============================================================================
2
- // Base / Reset
3
- // ============================================================================
4
  html { box-sizing: border-box; }
5
  *, *::before, *::after { box-sizing: inherit; }
6
  body { margin: 0; font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, Helvetica Neue, Arial, Apple Color Emoji, Segoe UI Emoji; color: var(--text-color); }
7
- audio { display: block; }
8
  /* Avoid constraining <main> inside grid; scope container sizing elsewhere if needed */
9
  /* main { max-width: 980px; margin: 24px auto; padding: 16px; } */
10
 
11
- // ============================================================================
12
- // Typography (inspired by Distill)
13
- // ============================================================================
14
  html { font-size: 14px; line-height: 1.6; }
15
  @media (min-width: 768px) { html { font-size: 16px; } }
16
  @media (min-width: 1024px) { html { font-size: 17px; } }
@@ -42,16 +42,16 @@ html { font-size: 14px; line-height: 1.6; }
42
  margin: var(--spacing-8) 0 var(--spacing-4);
43
  }
44
 
45
- .content-grid main a { color: inherit; text-decoration: none; border-bottom: 1px solid var(--link-underline); }
46
- .content-grid main a:hover { border-bottom: 1px solid var(--link-underline-hover); }
47
 
48
  /* Do not underline heading links inside the article (not the TOC) */
49
  .content-grid main h2 a,
50
  .content-grid main h3 a,
51
- .content-grid main h4 a { border-bottom: none; text-decoration: none; }
52
  .content-grid main h2 a:hover,
53
  .content-grid main h3 a:hover,
54
- .content-grid main h4 a:hover { border-bottom: none; text-decoration: none; }
55
 
56
  .content-grid main ul,
57
  .content-grid main ol { padding-left: 24px; margin: 0 0 var(--spacing-3); }
@@ -119,48 +119,35 @@ html { font-size: 14px; line-height: 1.6; }
119
  z-index: 1;
120
  }
121
 
122
- /* JS fallback chip */
123
- .content-grid main pre.has-lang-chip { position: relative; padding-top: 22px; }
124
- .content-grid main pre .code-lang-chip {
125
- position: absolute;
126
- top: 0px; right: 0px;
127
- font-size: 10px; line-height: 1;
128
- color: rgba(255,255,255,.5);
129
- background: rgba(255,255,255,.1);
130
- border: none;
131
- border-radius: 0px; padding: 6px 6px 4px 4px; pointer-events: none; z-index: 1;
132
- }
133
-
134
  .content-grid main table { border-collapse: collapse; width: 100%; margin: 0 0 var(--spacing-4); }
135
  .content-grid main th, .content-grid main td { border-bottom: 1px solid var(--border-color); padding: 6px 8px; text-align: left; font-size: 15px; }
136
  .content-grid main thead th { border-bottom: 1px solid var(--border-color); }
137
 
138
  .content-grid main hr { border: none; border-bottom: 1px solid var(--border-color); margin: var(--spacing-5) 0; }
139
 
 
 
 
 
 
 
 
 
 
 
140
 
141
- // .code-block {
142
- // background: rgba(120, 120, 120, 0.5);
143
- // border: 1px solid var(--border-color);
144
- // border-radius: 6px;
145
- // padding: var(--spacing-3);
146
- // font-size: 14px;
147
- // overflow: auto;
148
- // }
149
-
150
- // ============================================================================
151
- // Media / Figures
152
- // ============================================================================
153
- :where(picture, img) {
154
- max-width: 100%;
155
  width: 100%;
 
156
  height: auto;
157
  display: block;
158
  }
159
 
160
- figure { margin: 16px 0; }
161
- figcaption { color: var(--muted-color); font-size: 12px; }
162
-
163
- // Inline feature tags
164
  .tag-list { display: flex; flex-wrap: wrap; gap: 8px; margin: 8px 0 16px; }
165
  .tag {
166
  display: inline-flex;
@@ -177,47 +164,52 @@ figcaption { color: var(--muted-color); font-size: 12px; }
177
  [data-theme="dark"] .tag { background: #1a1f27; border-color: rgba(255,255,255,.15); }
178
 
179
 
180
- // ============================================================================
181
- // Figures, captions & image credits
182
- // ============================================================================
 
183
  figure { margin: 12px 0; }
184
- figcaption { text-align: center; font-size: 0.9rem; color: var(--muted-color); margin-top: 6px; }
185
  .image-credit { display: block; margin-top: 4px; font-size: 12px; color: var(--muted-color); }
186
  .image-credit a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
187
 
188
- // ============================================================================
189
- // Buttons (minimal, clean)
190
- // ============================================================================
191
- .meta .meta-container-cell button {
192
  appearance: none;
193
- background: var(--surface-bg);
194
- color: var(--text-color);
195
- border: 1px solid var(--border-color);
196
  border-radius: 6px;
197
  padding: 8px 12px;
198
  font-size: 14px;
199
  line-height: 1;
200
  cursor: pointer;
 
201
  transition: background-color .15s ease, border-color .15s ease, box-shadow .15s ease, transform .02s ease;
202
  }
203
- .meta .meta-container-cell button:hover {
204
- background: var(--code-bg);
205
  }
206
- .meta .meta-container-cell button:active {
207
  transform: translateY(1px);
208
  }
209
- .meta .meta-container-cell button:focus-visible {
210
  outline: none;
211
- box-shadow: 0 0 0 2px var(--link-underline);
212
  }
213
- .meta .meta-container-cell button:disabled {
214
  opacity: .6;
215
  cursor: not-allowed;
216
  }
217
 
218
- // ============================================================================
219
- // Print styles
220
- // =========================================================================
 
 
 
 
221
  @media print {
222
  html, body { background: #fff; }
223
  /* Margins handled by Playwright; avoid extra global margins */
@@ -248,3 +240,9 @@ figcaption { text-align: center; font-size: 0.9rem; color: var(--muted-color); m
248
  --link-underline-hover: rgba(0,0,0,.4);
249
  }
250
  }
 
 
 
 
 
 
 
1
+ /* ============================================================================ */
2
+ /* Base / Reset */
3
+ /* ============================================================================ */
4
  html { box-sizing: border-box; }
5
  *, *::before, *::after { box-sizing: inherit; }
6
  body { margin: 0; font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, Helvetica Neue, Arial, Apple Color Emoji, Segoe UI Emoji; color: var(--text-color); }
7
+ audio { display: block; width: 100%; }
8
  /* Avoid constraining <main> inside grid; scope container sizing elsewhere if needed */
9
  /* main { max-width: 980px; margin: 24px auto; padding: 16px; } */
10
 
11
+ /* ============================================================================ */
12
+ /* Typography (inspired by Distill) */
13
+ /* ============================================================================ */
14
  html { font-size: 14px; line-height: 1.6; }
15
  @media (min-width: 768px) { html { font-size: 16px; } }
16
  @media (min-width: 1024px) { html { font-size: 17px; } }
 
42
  margin: var(--spacing-8) 0 var(--spacing-4);
43
  }
44
 
45
+ .content-grid main a { color: var(--primary); text-decoration: none; border-bottom: 1px solid var(--link-underline); }
46
+ .content-grid main a:hover { color: var(--primary-hover); border-bottom: 1px solid var(--link-underline-hover); }
47
 
48
  /* Do not underline heading links inside the article (not the TOC) */
49
  .content-grid main h2 a,
50
  .content-grid main h3 a,
51
+ .content-grid main h4 a { color: inherit; border-bottom: none; text-decoration: none; }
52
  .content-grid main h2 a:hover,
53
  .content-grid main h3 a:hover,
54
+ .content-grid main h4 a:hover { color: inherit; border-bottom: none; text-decoration: none; }
55
 
56
  .content-grid main ul,
57
  .content-grid main ol { padding-left: 24px; margin: 0 0 var(--spacing-3); }
 
119
  z-index: 1;
120
  }
121
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  .content-grid main table { border-collapse: collapse; width: 100%; margin: 0 0 var(--spacing-4); }
123
  .content-grid main th, .content-grid main td { border-bottom: 1px solid var(--border-color); padding: 6px 8px; text-align: left; font-size: 15px; }
124
  .content-grid main thead th { border-bottom: 1px solid var(--border-color); }
125
 
126
  .content-grid main hr { border: none; border-bottom: 1px solid var(--border-color); margin: var(--spacing-5) 0; }
127
 
128
+ /*
129
+ .code-block {
130
+ background: rgba(120, 120, 120, 0.5);
131
+ border: 1px solid var(--border-color);
132
+ border-radius: 6px;
133
+ padding: var(--spacing-3);
134
+ font-size: 14px;
135
+ overflow: auto;
136
+ }
137
+ */
138
 
139
+ /* ============================================================================ */
140
+ /* Media / Figures */
141
+ /* ============================================================================ */
142
+ img,
143
+ picture {
 
 
 
 
 
 
 
 
 
144
  width: 100%;
145
+ max-width: 100%;
146
  height: auto;
147
  display: block;
148
  }
149
 
150
+ /* Inline feature tags */
 
 
 
151
  .tag-list { display: flex; flex-wrap: wrap; gap: 8px; margin: 8px 0 16px; }
152
  .tag {
153
  display: inline-flex;
 
164
  [data-theme="dark"] .tag { background: #1a1f27; border-color: rgba(255,255,255,.15); }
165
 
166
 
167
+
168
+ /* ============================================================================ */
169
+ /* Figures, captions & image credits */
170
+ /* ============================================================================ */
171
  figure { margin: 12px 0; }
172
+ figcaption { text-align: left; font-size: 0.9rem; color: var(--muted-color); margin-top: 6px; }
173
  .image-credit { display: block; margin-top: 4px; font-size: 12px; color: var(--muted-color); }
174
  .image-credit a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
175
 
176
+ /* ============================================================================ */
177
+ /* Buttons (minimal, clean) */
178
+ /* ============================================================================ */
179
+ button, .button {
180
  appearance: none;
181
+ background: linear-gradient(15deg, var(--primary) 0%, var(--primary-hover) 35%);
182
+ color: white!important;
183
+ border: 1px solid transparent;
184
  border-radius: 6px;
185
  padding: 8px 12px;
186
  font-size: 14px;
187
  line-height: 1;
188
  cursor: pointer;
189
+ display: inline-block;
190
  transition: background-color .15s ease, border-color .15s ease, box-shadow .15s ease, transform .02s ease;
191
  }
192
+ button:hover, .button:hover {
193
+ filter: brightness(96%);
194
  }
195
+ button:active, .button:active {
196
  transform: translateY(1px);
197
  }
198
+ button:focus-visible, .button:focus-visible {
199
  outline: none;
 
200
  }
201
+ button:disabled, .button:disabled {
202
  opacity: .6;
203
  cursor: not-allowed;
204
  }
205
 
206
+ .button-group .button {
207
+ margin: 5px;
208
+ }
209
+
210
+ /* ============================================================================ */
211
+ /* Print styles */
212
+ /* ========================================================================= */
213
  @media print {
214
  html, body { background: #fff; }
215
  /* Margins handled by Playwright; avoid extra global margins */
 
240
  --link-underline-hover: rgba(0,0,0,.4);
241
  }
242
  }
243
+
244
+ .muted {
245
+ color: var(--muted-color);
246
+ }
247
+
248
+
app/src/styles/{_layout.scss → _layout.css} RENAMED
@@ -1,18 +1,18 @@
1
- // ============================================================================
2
- // Layout – 3-column grid (TOC / Article / Aside)
3
- // ============================================================================
4
  .content-grid { max-width: 1280px; margin: 0 auto; padding: 0 16px; margin-top: 40px; display: grid; grid-template-columns: 220px minmax(0, 680px) 260px; gap: 32px; align-items: start; }
5
  .content-grid > main { max-width: 100%; margin: 0; padding: 0; }
6
 
7
- // TOC (left column)
8
  .toc { position: sticky; top: 24px; }
9
  .toc nav { border-left: 1px solid var(--border-color); padding-left: 16px; font-size: 13px; }
10
  .toc .title { font-weight: 600; font-size: 14px; margin-bottom: 8px; }
11
 
12
- // Hide in-article TOC (duplicated by sticky aside)
13
  main > nav:first-of-type { display: none; }
14
 
15
- // TOC look & feel
16
  .toc nav ul { margin: 0 0 6px; padding-left: 1em; }
17
  .toc nav li { list-style: none; margin: .25em 0; }
18
  .toc nav a { color: var(--text-color); text-decoration: none; border-bottom: none; }
@@ -20,7 +20,7 @@ main > nav:first-of-type { display: none; }
20
  .toc nav a:hover { text-decoration: underline solid var(--muted-color); }
21
  .toc nav a.active { text-decoration: underline; }
22
 
23
- // Mobile TOC accordion
24
  .toc-mobile { display: none; margin: 8px 0 16px; }
25
  .toc-mobile > summary { cursor: pointer; list-style: none; padding: 8px 12px; border: 1px solid var(--border-color); border-radius: 8px; color: var(--text-color); font-weight: 600; }
26
  .toc-mobile[open] > summary { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
@@ -32,11 +32,11 @@ main > nav:first-of-type { display: none; }
32
  .toc-mobile nav a:hover { text-decoration: underline solid var(--muted-color); }
33
  .toc-mobile nav a.active { text-decoration: underline; }
34
 
35
- // Right aside (notes)
36
  .right-aside { position: sticky; top: 24px; }
37
  .right-aside .aside-card { background: var(--surface-bg); border: 1px solid var(--border-color); border-radius: 8px; padding: 10px; margin-bottom: 10px; font-size: 0.9rem; color: var(--text-color); }
38
 
39
- // Responsive – collapse to single column
40
  @media (max-width: 1100px) {
41
  .content-grid { grid-template-columns: 1fr; }
42
  .toc { position: static; display: none; }
@@ -55,7 +55,7 @@ main > nav:first-of-type { display: none; }
55
  right: -260px; /* push into the right grid column (width 260 + gap 32) */
56
  width: 260px;
57
  border-radius: 8px;
58
- padding: 0 10px;
59
  font-size: 0.9rem;
60
  color: var(--muted-color);
61
  }
@@ -68,18 +68,18 @@ main > nav:first-of-type { display: none; }
68
  }
69
 
70
 
71
- // ============================================================================
72
- // Width helpers – slightly wider than main column, and full-bleed to viewport
73
- // ----------------------------------------------------------------------------
74
- // Usage in MDX:
75
- // <div className="wide"> ... </div>
76
- // <div className="full-bleed"> ... </div>
77
- // These center the content relative to the viewport while keeping it responsive.
78
- //
79
- // Notes:
80
- // - These helpers work inside the main article column; they break out visually
81
- // to be wider or fully span the viewport. On small screens, they fall back to 100%.
82
- // - Adjust the target width in .wide if desired.
83
  .wide,
84
  .full-bleed { box-sizing: border-box; }
85
 
@@ -106,3 +106,5 @@ main > nav:first-of-type { display: none; }
106
  }
107
  }
108
 
 
 
 
1
+ /* ============================================================================ */
2
+ /* Layout – 3-column grid (TOC / Article / Aside) */
3
+ /* ============================================================================ */
4
  .content-grid { max-width: 1280px; margin: 0 auto; padding: 0 16px; margin-top: 40px; display: grid; grid-template-columns: 220px minmax(0, 680px) 260px; gap: 32px; align-items: start; }
5
  .content-grid > main { max-width: 100%; margin: 0; padding: 0; }
6
 
7
+ /* TOC (left column) */
8
  .toc { position: sticky; top: 24px; }
9
  .toc nav { border-left: 1px solid var(--border-color); padding-left: 16px; font-size: 13px; }
10
  .toc .title { font-weight: 600; font-size: 14px; margin-bottom: 8px; }
11
 
12
+ /* Hide in-article TOC (duplicated by sticky aside) */
13
  main > nav:first-of-type { display: none; }
14
 
15
+ /* TOC look & feel */
16
  .toc nav ul { margin: 0 0 6px; padding-left: 1em; }
17
  .toc nav li { list-style: none; margin: .25em 0; }
18
  .toc nav a { color: var(--text-color); text-decoration: none; border-bottom: none; }
 
20
  .toc nav a:hover { text-decoration: underline solid var(--muted-color); }
21
  .toc nav a.active { text-decoration: underline; }
22
 
23
+ /* Mobile TOC accordion */
24
  .toc-mobile { display: none; margin: 8px 0 16px; }
25
  .toc-mobile > summary { cursor: pointer; list-style: none; padding: 8px 12px; border: 1px solid var(--border-color); border-radius: 8px; color: var(--text-color); font-weight: 600; }
26
  .toc-mobile[open] > summary { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
 
32
  .toc-mobile nav a:hover { text-decoration: underline solid var(--muted-color); }
33
  .toc-mobile nav a.active { text-decoration: underline; }
34
 
35
+ /* Right aside (notes) */
36
  .right-aside { position: sticky; top: 24px; }
37
  .right-aside .aside-card { background: var(--surface-bg); border: 1px solid var(--border-color); border-radius: 8px; padding: 10px; margin-bottom: 10px; font-size: 0.9rem; color: var(--text-color); }
38
 
39
+ /* Responsive – collapse to single column */
40
  @media (max-width: 1100px) {
41
  .content-grid { grid-template-columns: 1fr; }
42
  .toc { position: static; display: none; }
 
55
  right: -260px; /* push into the right grid column (width 260 + gap 32) */
56
  width: 260px;
57
  border-radius: 8px;
58
+ padding: 0 30px;
59
  font-size: 0.9rem;
60
  color: var(--muted-color);
61
  }
 
68
  }
69
 
70
 
71
+ /* ============================================================================ */
72
+ /* Width helpers – slightly wider than main column, and full-bleed to viewport */
73
+ /* ---------------------------------------------------------------------------- */
74
+ /* Usage in MDX: */
75
+ /* <div className="wide"> ... </div> */
76
+ /* <div className="full-bleed"> ... </div> */
77
+ /* These center the content relative to the viewport while keeping it responsive. */
78
+ /* */
79
+ /* Notes: */
80
+ /* - These helpers work inside the main article column; they break out visually */
81
+ /* to be wider or fully span the viewport. On small screens, they fall back to 100%. */
82
+ /* - Adjust the target width in .wide if desired. */
83
  .wide,
84
  .full-bleed { box-sizing: border-box; }
85
 
 
106
  }
107
  }
108
 
109
+
110
+
app/src/styles/{_variables.scss → _variables.css} RENAMED
@@ -1,12 +1,17 @@
1
- // ============================================================================
2
- // Theme Variables (inspired by Distill)
3
- // ============================================================================
4
  :root {
5
- --distill-gray: rgb(107, 114, 128);
6
- --distill-gray-light: rgb(185, 185, 185);
7
- --distill-gray-lighter: rgb(228, 228, 228);
8
- --distill-gray-lightest: rgb(245, 245, 245);
9
- --distill-blue: #007BFF;
 
 
 
 
 
10
 
11
  --text-color: rgba(0,0,0,.85);
12
  --muted-color: rgba(0,0,0,.6);
@@ -15,8 +20,8 @@
15
  /* Light surfaces & links */
16
  --surface-bg: #fafafa;
17
  --code-bg: #f6f8fa;
18
- --link-underline: rgba(0,0,0,.4);
19
- --link-underline-hover: rgba(0,0,0,.8);
20
 
21
  --spacing-1: 8px;
22
  --spacing-2: 12px;
@@ -29,17 +34,21 @@
29
  --spacing-9: 64px;
30
  --spacing-10: 72px;
31
  }
32
- // Theme tokens for dark mode
33
  [data-theme="dark"] {
34
  --text-color: rgba(255,255,255,.9);
35
  --muted-color: rgba(255,255,255,.7);
36
  --border-color: rgba(255,255,255,.15);
37
  --surface-bg: #12151b;
38
  --code-bg: #12151b;
39
- --link-underline: rgba(255,255,255,.5);
40
- --link-underline-hover: rgba(255,255,255,.9);
 
 
 
41
  color-scheme: dark;
42
  background: #0f1115;
43
  }
44
 
45
 
 
 
1
+ /* ============================================================================ */
2
+ /* Theme Variables (inspired by Distill) */
3
+ /* ============================================================================ */
4
  :root {
5
+ /* Neutral palette */
6
+ --neutral-600: rgb(107, 114, 128);
7
+ --neutral-400: rgb(185, 185, 185);
8
+ --neutral-300: rgb(228, 228, 228);
9
+ --neutral-200: rgb(245, 245, 245);
10
+
11
+ /* Primary brand color */
12
+ --primary: rgb(232, 137, 171);
13
+ --primary-hover: rgb(212, 126, 156);
14
+ --on-primary: #ffffff;
15
 
16
  --text-color: rgba(0,0,0,.85);
17
  --muted-color: rgba(0,0,0,.6);
 
20
  /* Light surfaces & links */
21
  --surface-bg: #fafafa;
22
  --code-bg: #f6f8fa;
23
+ --link-underline: var(--primary); /* based on --primary */
24
+ --link-underline-hover: var(--primary-hover);
25
 
26
  --spacing-1: 8px;
27
  --spacing-2: 12px;
 
34
  --spacing-9: 64px;
35
  --spacing-10: 72px;
36
  }
37
+ /* Theme tokens for dark mode */
38
  [data-theme="dark"] {
39
  --text-color: rgba(255,255,255,.9);
40
  --muted-color: rgba(255,255,255,.7);
41
  --border-color: rgba(255,255,255,.15);
42
  --surface-bg: #12151b;
43
  --code-bg: #12151b;
44
+ /* Primary in dark mode */
45
+ --primary: rgb(232, 137, 171);
46
+ --primary-hover: rgb(212, 126, 156);
47
+ --on-primary: #0f1115;
48
+
49
  color-scheme: dark;
50
  background: #0f1115;
51
  }
52
 
53
 
54
+
app/src/styles/components/{_code.scss → _code.css} RENAMED
@@ -1,17 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
1
 
2
  /* Sync Shiki variables with current theme */
3
  /* Standard wrapper look for code blocks */
4
- .astro-code { border: 1px solid var(--border-color); border-radius: 6px; padding: var(--spacing-3); padding-left: calc(var(--spacing-3) + 6px); font-size: 14px; }
5
 
6
  /* Prevent code blocks from breaking layout on small screens */
7
  .astro-code { overflow-x: auto; width: 100%; max-width: 100%; box-sizing: border-box; -webkit-overflow-scrolling: touch; }
8
- section.content-grid pre { overflow-x: auto; width: 100%; max-width: 100%; box-sizing: border-box; -webkit-overflow-scrolling: touch; }
9
  section.content-grid pre code { display: inline-block; min-width: 100%; }
10
 
11
  /* Wrap long lines on mobile to avoid overflow (URLs, etc.) */
12
- .astro-code,
13
- section.content-grid pre { white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-word; }
14
- section.content-grid pre code { white-space: pre-wrap; display: block; min-width: 0; }
 
 
 
15
 
16
  html[data-theme='light'] .astro-code { background-color: var(--code-bg); }
17
 
@@ -27,3 +41,25 @@ html[data-theme='light'] .astro-code {
27
  --shiki-foreground: #24292f;
28
  --shiki-background: #ffffff;
29
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ code {
2
+ font-size: 14px;
3
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
4
+ background-color: var(--code-bg);
5
+ padding: 0.2em 0.4em;
6
+ border-radius: 0.3em;
7
+ border: 1px solid var(--border-color);
8
+ color: var(--text-color);
9
+ font-weight: 400;
10
+ line-height: 1.5;
11
+ }
12
 
13
  /* Sync Shiki variables with current theme */
14
  /* Standard wrapper look for code blocks */
15
+ .astro-code { border: 1px solid var(--border-color); border-radius: 6px; padding: var(--spacing-3); font-size: 14px; --code-gutter-width: 2.5em; }
16
 
17
  /* Prevent code blocks from breaking layout on small screens */
18
  .astro-code { overflow-x: auto; width: 100%; max-width: 100%; box-sizing: border-box; -webkit-overflow-scrolling: touch; }
19
+ section.content-grid pre { overflow-x: auto; width: 100%; max-width: 100%; box-sizing: border-box; -webkit-overflow-scrolling: touch; padding: var(--spacing-3); }
20
  section.content-grid pre code { display: inline-block; min-width: 100%; }
21
 
22
  /* Wrap long lines on mobile to avoid overflow (URLs, etc.) */
23
+ /* Wrap long lines only on small screens to prevent layout overflow */
24
+ @media (max-width: 1100px) {
25
+ .astro-code,
26
+ section.content-grid pre { white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-word; }
27
+ section.content-grid pre code { white-space: pre-wrap; display: block; min-width: 0; }
28
+ }
29
 
30
  html[data-theme='light'] .astro-code { background-color: var(--code-bg); }
31
 
 
41
  --shiki-foreground: #24292f;
42
  --shiki-background: #ffffff;
43
  }
44
+
45
+ /* Line numbers for Shiki-rendered code blocks */
46
+ .astro-code code { counter-reset: astro-code-line; display: block; background: none; border: none; }
47
+ .astro-code .line { display: inline-block; position: relative; padding-left: calc(var(--code-gutter-width) + var(--spacing-3)); min-height: 1.25em; }
48
+ .astro-code .line::before { counter-increment: astro-code-line; content: counter(astro-code-line); position: absolute; left: 0; top: 0; bottom: 0; width: calc(var(--code-gutter-width)); text-align: right; color: var(--muted-color); opacity: .75; user-select: none; padding-right: var(--spacing-3); border-right: 1px solid var(--border-color); }
49
+ .astro-code .line:empty::after { content: "\00a0"; }
50
+ /* Hide trailing empty line added by parsers */
51
+ .astro-code code > .line:last-child:empty { display: none; }
52
+
53
+ /* JS fallback chip */
54
+ .astro-code.has-lang-chip { position: relative; padding-top: 22px; }
55
+ .astro-code .code-lang-chip {
56
+ position: absolute;
57
+ top: 0px; right: 0px;
58
+ font-size: 10px; line-height: 1;
59
+ color: rgba(255,255,255,.5);
60
+ background: rgba(255,255,255,.1);
61
+ border: none;
62
+ border-radius: 0px; padding: 6px 6px 4px 4px; pointer-events: none; z-index: 1;
63
+ }
64
+
65
+
app/src/styles/components/{_footer.scss → _footer.css} RENAMED
@@ -1,7 +1,7 @@
1
  .distill-footer { contain: layout style; font-size: 0.8em; line-height: 1.7em; margin-top: 60px; margin-bottom: 0; border-top: 1px solid rgba(0, 0, 0, 0.1); color: rgba(0, 0, 0, 0.5); }
2
  .footer-inner { max-width: 1280px; margin: 0 auto; padding: 60px 16px 48px; display: grid; grid-template-columns: 220px minmax(0, 680px) 260px; gap: 32px; align-items: start; }
3
 
4
- // Use the parent grid (3 columns like .content-grid)
5
  .citation-block,
6
  .references-block { display: contents; }
7
  .citation-block > h3,
@@ -19,7 +19,7 @@
19
  margin-top:0;
20
  }
21
 
22
- // Distill-like appendix citation styling
23
  .citation {
24
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
25
  font-size: 11px;
@@ -43,7 +43,7 @@
43
 
44
  .references-block h3 { margin: 0; }
45
 
46
- // Distill-like list styling for references/footnotes
47
  .references-block ol { padding: 0 0 0 15px; }
48
  @media (min-width: 768px) { .references-block ol { padding: 0 0 0 30px; margin-left: -30px; } }
49
  .references-block li { margin-bottom: 1em; }
@@ -52,3 +52,5 @@
52
  @media (max-width: 1100px) {
53
  .footer-inner { display: block; padding: 40px 16px; }
54
  }
 
 
 
1
  .distill-footer { contain: layout style; font-size: 0.8em; line-height: 1.7em; margin-top: 60px; margin-bottom: 0; border-top: 1px solid rgba(0, 0, 0, 0.1); color: rgba(0, 0, 0, 0.5); }
2
  .footer-inner { max-width: 1280px; margin: 0 auto; padding: 60px 16px 48px; display: grid; grid-template-columns: 220px minmax(0, 680px) 260px; gap: 32px; align-items: start; }
3
 
4
+ /* Use the parent grid (3 columns like .content-grid) */
5
  .citation-block,
6
  .references-block { display: contents; }
7
  .citation-block > h3,
 
19
  margin-top:0;
20
  }
21
 
22
+ /* Distill-like appendix citation styling */
23
  .citation {
24
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
25
  font-size: 11px;
 
43
 
44
  .references-block h3 { margin: 0; }
45
 
46
+ /* Distill-like list styling for references/footnotes */
47
  .references-block ol { padding: 0 0 0 15px; }
48
  @media (min-width: 768px) { .references-block ol { padding: 0 0 0 30px; margin-left: -30px; } }
49
  .references-block li { margin-bottom: 1em; }
 
52
  @media (max-width: 1100px) {
53
  .footer-inner { display: block; padding: 40px 16px; }
54
  }
55
+
56
+
app/src/styles/components/_poltly.css ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ============================================================================ */
2
+ /* Plotly – fragments & controls */
3
+ /* ============================================================================ */
4
+ .plot-card { background: var(--code-bg); border: 1px solid var(--border-color); border-radius: 10px; padding: 8px; margin: 8px 0; }
5
+ .plot-card svg text { fill: var(--text-color) !important; }
6
+ .plot-card label { color: var(--text-color) !important; }
7
+ .plotly-graph-div { width: 100% !important; min-height: 320px; }
8
+ @media (max-width: 768px) { .plotly-graph-div { min-height: 260px; } }
9
+ [id^="plot-"] { display: flex; flex-direction: column; align-items: center; gap: 15px; }
10
+ .plotly_caption { font-style: italic; margin-top: 10px; }
11
+ .plotly_controls { display: flex; flex-wrap: wrap; justify-content: center; gap: 30px; }
12
+ .plotly_input_container { display: flex; align-items: center; flex-direction: column; gap: 10px; }
13
+ .plotly_input_container > select { padding: 2px 4px; line-height: 1.5em; text-align: center; border-radius: 4px; font-size: 12px; background-color: var(--neutral-200); outline: none; border: 1px solid var(--neutral-300); }
14
+ .plotly_slider { display: flex; align-items: center; gap: 10px; }
15
+ .plotly_slider > input[type="range"] { -webkit-appearance: none; appearance: none; height: 2px; background: var(--neutral-400); border-radius: 5px; outline: none; }
16
+ .plotly_slider > input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; width: 18px; height: 18px; border-radius: 50%; background: var(--primary); cursor: pointer; }
17
+ .plotly_slider > input[type="range"]::-moz-range-thumb { width: 18px; height: 18px; border-radius: 50%; background: var(--primary); cursor: pointer; }
18
+ .plotly_slider > span { font-size: 14px; line-height: 1.6em; min-width: 16px; }
19
+
20
+ /* ---------------------------------------------------------------------------- */
21
+ /* Dark mode overrides for Plotly readability */
22
+ /* ---------------------------------------------------------------------------- */
23
+ [data-theme="dark"] .plot-card .xaxislayer-above text,
24
+ [data-theme="dark"] .plot-card .yaxislayer-above text,
25
+ [data-theme="dark"] .plot-card .infolayer text,
26
+ [data-theme="dark"] .plot-card .legend text,
27
+ [data-theme="dark"] .plot-card .annotation text,
28
+ [data-theme="dark"] .plot-card .colorbar text,
29
+ [data-theme="dark"] .plot-card .hoverlayer text { fill: #fff !important; }
30
+
31
+ [data-theme="dark"] .plot-card .xaxislayer-above path,
32
+ [data-theme="dark"] .plot-card .yaxislayer-above path,
33
+ [data-theme="dark"] .plot-card .xlines-above,
34
+ [data-theme="dark"] .plot-card .ylines-above { stroke: rgba(255,255,255,.35) !important; }
35
+
36
+ [data-theme="dark"] .plot-card .gridlayer path { stroke: rgba(255,255,255,.15) !important; }
37
+
38
+ /* Legend and hover backgrounds */
39
+ [data-theme="dark"] .plot-card .legend rect.bg { fill: rgba(0,0,0,.25) !important; stroke: rgba(255,255,255,.2) !important; }
40
+ [data-theme="dark"] .plot-card .hoverlayer .bg { fill: rgba(0,0,0,.8) !important; stroke: rgba(255,255,255,.2) !important; }
41
+
42
+ /* Colorbar background (keep gradient intact) */
43
+ [data-theme="dark"] .plot-card .colorbar .cbbg { fill: rgba(0,0,0,.25) !important; stroke: rgba(255,255,255,.2) !important; }
44
+
45
+
app/src/styles/{global.scss → global.css} RENAMED
@@ -1,8 +1,9 @@
1
- @use "./variables" as *;
2
- @use "./base" as *;
3
- @use "./layout" as *;
4
- @use "./components/footer" as *;
5
- @use "./components/code" as *;
 
6
 
7
  /* Dark-mode form tweak */
8
  [data-theme="dark"] .plotly_input_container > select { background-color: #1a1f27; border-color: var(--border-color); color: var(--text-color); }
@@ -19,10 +20,9 @@
19
  img[data-zoomable] { cursor: zoom-in; }
20
  .medium-zoom--opened img[data-zoomable] { cursor: zoom-out; }
21
 
22
-
23
- // ============================================================================
24
- // Hero (full-bleed)
25
- // ============================================================================
26
  .hero { width: 100%; padding: 48px 16px 16px; text-align: center; }
27
  .hero-title { font-size: clamp(28px, 4vw, 48px); font-weight: 800; line-height: 1.1; margin: 0 0 8px;
28
 
@@ -31,9 +31,9 @@ img[data-zoomable] { cursor: zoom-in; }
31
  .hero-banner { max-width: 980px; margin: 0 auto; }
32
  .hero-desc { color: var(--muted-color); font-style: italic; margin: 0 0 16px 0; }
33
 
34
- // ============================================================================
35
- // Meta (byline-like header)
36
- // ============================================================================
37
 
38
  .meta {
39
  border-top: 1px solid var(--border-color);
@@ -65,30 +65,13 @@ img[data-zoomable] { cursor: zoom-in; }
65
  }
66
  .meta-container-cell p { margin: 0; }
67
 
68
- // ============================================================================
69
- // Plotly fragments & controls
70
- // ============================================================================
71
- .plot-card { background: var(--code-bg); border: 1px solid var(--border-color); border-radius: 10px; padding: 8px; margin: 8px 0; }
72
- .plot-card svg text { fill: var(--text-color) !important; }
73
- .plotly-graph-div { width: 100% !important; min-height: 320px; }
74
- @media (max-width: 768px) { .plotly-graph-div { min-height: 260px; } }
75
- [id^="plot-"] { display: flex; flex-direction: column; align-items: center; gap: 15px; }
76
- .plotly_caption { font-style: italic; margin-top: 10px; }
77
- .plotly_controls { display: flex; flex-wrap: wrap; justify-content: center; gap: 30px; }
78
- .plotly_input_container { display: flex; align-items: center; flex-direction: column; gap: 10px; }
79
- .plotly_input_container > select { padding: 2px 4px; line-height: 1.5em; text-align: center; border-radius: 4px; font-size: 12px; background-color: var(--distill-gray-lightest); outline: none; border: 1px solid var(--distill-gray-lighter); }
80
- .plotly_slider { display: flex; align-items: center; gap: 10px; }
81
- .plotly_slider > input[type="range"] { -webkit-appearance: none; appearance: none; height: 2px; background: var(--distill-gray-light); border-radius: 5px; outline: none; }
82
- .plotly_slider > input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; width: 18px; height: 18px; border-radius: 50%; background: var(--distill-blue); cursor: pointer; }
83
- .plotly_slider > input[type="range"]::-moz-range-thumb { width: 18px; height: 18px; border-radius: 50%; background: var(--distill-blue); cursor: pointer; }
84
- .plotly_slider > span { font-size: 14px; line-height: 1.6em; min-width: 16px; }
85
-
86
- // ============================================================================
87
- // Theme Toggle button (moved from component)
88
- // ============================================================================
89
  #theme-toggle { display: inline-flex; align-items: center; gap: 8px; border: none; background: transparent; padding: 6px 10px; border-radius: 8px; cursor: pointer; margin: 12px 16px; }
90
  #theme-toggle .icon.dark { display: none; }
91
  [data-theme="dark"] #theme-toggle .icon.light { display: none; }
92
  [data-theme="dark"] #theme-toggle .icon.dark { display: inline; }
93
  [data-theme="dark"] #theme-toggle .icon { filter: invert(1) brightness(1.2); }
94
 
 
 
1
+ @import './_variables.css';
2
+ @import './_base.css';
3
+ @import './_layout.css';
4
+ @import './components/_footer.css';
5
+ @import './components/_code.css';
6
+ @import './components/_poltly.css';
7
 
8
  /* Dark-mode form tweak */
9
  [data-theme="dark"] .plotly_input_container > select { background-color: #1a1f27; border-color: var(--border-color); color: var(--text-color); }
 
20
  img[data-zoomable] { cursor: zoom-in; }
21
  .medium-zoom--opened img[data-zoomable] { cursor: zoom-out; }
22
 
23
+ /* ============================================================================ */
24
+ /* Hero (full-bleed) */
25
+ /* ============================================================================ */
 
26
  .hero { width: 100%; padding: 48px 16px 16px; text-align: center; }
27
  .hero-title { font-size: clamp(28px, 4vw, 48px); font-weight: 800; line-height: 1.1; margin: 0 0 8px;
28
 
 
31
  .hero-banner { max-width: 980px; margin: 0 auto; }
32
  .hero-desc { color: var(--muted-color); font-style: italic; margin: 0 0 16px 0; }
33
 
34
+ /* ============================================================================ */
35
+ /* Meta (byline-like header) */
36
+ /* ============================================================================ */
37
 
38
  .meta {
39
  border-top: 1px solid var(--border-color);
 
65
  }
66
  .meta-container-cell p { margin: 0; }
67
 
68
+ /* ============================================================================ */
69
+ /* Theme Toggle button (moved from component) */
70
+ /* ============================================================================ */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  #theme-toggle { display: inline-flex; align-items: center; gap: 8px; border: none; background: transparent; padding: 6px 10px; border-radius: 8px; cursor: pointer; margin: 12px 16px; }
72
  #theme-toggle .icon.dark { display: none; }
73
  [data-theme="dark"] #theme-toggle .icon.light { display: none; }
74
  [data-theme="dark"] #theme-toggle .icon.dark { display: inline; }
75
  [data-theme="dark"] #theme-toggle .icon { filter: invert(1) brightness(1.2); }
76
 
77
+
fragments/d3js/banner.html CHANGED
@@ -93,17 +93,6 @@
93
  .attr('width', '100%')
94
  .style('display', 'block');
95
 
96
- // Subtle background gradient
97
- const defs = svg.append('defs');
98
- const grad = defs.append('radialGradient')
99
- .attr('id', 'spaceBg')
100
- .attr('cx', '50%')
101
- .attr('cy', '50%')
102
- .attr('r', '65%');
103
- grad.append('stop').attr('offset', '0%').attr('stop-color', 'rgba(255,255,255,0.06)');
104
- grad.append('stop').attr('offset', '60%').attr('stop-color', 'rgba(160,120,200,0.05)');
105
- grad.append('stop').attr('offset', '100%').attr('stop-color', 'rgba(0,0,0,0)');
106
-
107
  const render = () => {
108
  const width = container.clientWidth || 800;
109
  const height = Math.max(260, Math.round(width / 3)); // keep ~3:1, min height
@@ -132,6 +121,7 @@
132
  // Ensure container can host an absolute tooltip
133
  container.style.position = container.style.position || 'relative';
134
  let tip = container.querySelector('.d3-tooltip');
 
135
  if (!tip) {
136
  tip = document.createElement('div');
137
  tip.className = 'd3-tooltip';
@@ -141,22 +131,28 @@
141
  left: '0px',
142
  transform: 'translate(-9999px, -9999px)',
143
  pointerEvents: 'none',
144
- padding: '6px 8px',
145
- borderRadius: '6px',
146
  fontSize: '12px',
147
- lineHeight: '1.3',
148
  border: '1px solid var(--border-color)',
149
  background: 'var(--surface-bg)',
150
  color: 'var(--text-color)',
151
- boxShadow: '0 2px 10px rgba(0,0,0,.15)',
152
  opacity: '0',
153
  transition: 'opacity .12s ease'
154
  });
 
 
 
 
155
  container.appendChild(tip);
 
 
156
  }
157
 
158
  // Final filter: remove small dots very close to the galaxy center (after placement)
159
- const centerHoleRadius = 0.08; // elliptical radius threshold
160
  const smallSizeThreshold = 7.5; // same notion as Python size cut
161
  const rTotal = idx.map((i) => Math.sqrt(((X[i] - cx) / a) ** 2 + ((Y[i] - cy) / b) ** 2));
162
  const idxFiltered = idx.filter((i, k) => !(rTotal[k] <= centerHoleRadius && sizesPx[i] < smallSizeThreshold));
@@ -171,15 +167,18 @@
171
  .attr('fill-opacity', 0.9)
172
  .attr('stroke', strokeColor)
173
  .attr('stroke-width', 0.4)
174
- .on('mouseenter', (ev, i) => {
 
 
 
175
  const r = Math.sqrt(((X[i] - cx) / a) ** 2 + ((Y[i] - cy) / b) ** 2);
176
  const type = i < lenSpiral ? 'spiral' : 'bulge';
177
  const arm = i < lenSpiral ? (armIndices[i] + 1) : null;
178
- tip.innerHTML = `<div><strong>${labelOf(i)}</strong></div>` +
179
- `<div>Type: ${type}${arm ? ` (arm ${arm})` : ''}</div>` +
180
- `<div>Size: ${sizesPx[i].toFixed(1)} px</div>` +
181
- `<div>X: ${X[i].toFixed(2)} · Y: ${Y[i].toFixed(2)}</div>` +
182
- `<div>r: ${r.toFixed(3)} · z: ${Zraw[i].toFixed(3)}</div>`;
183
  tip.style.opacity = '1';
184
  })
185
  .on('mousemove', (ev, i) => {
@@ -187,9 +186,10 @@
187
  const offsetX = 10, offsetY = 12;
188
  tip.style.transform = `translate(${Math.round(mx + offsetX)}px, ${Math.round(my + offsetY)}px)`;
189
  })
190
- .on('mouseleave', () => {
191
  tip.style.opacity = '0';
192
  tip.style.transform = 'translate(-9999px, -9999px)';
 
193
  }),
194
  (update) => update
195
  .attr('cx', (i) => xScale(X[i]))
@@ -199,15 +199,18 @@
199
  .attr('fill-opacity', 0.9)
200
  .attr('stroke', strokeColor)
201
  .attr('stroke-width', 0.4)
202
- .on('mouseenter', (ev, i) => {
 
 
 
203
  const r = Math.sqrt(((X[i] - cx) / a) ** 2 + ((Y[i] - cy) / b) ** 2);
204
  const type = i < lenSpiral ? 'spiral' : 'bulge';
205
  const arm = i < lenSpiral ? (armIndices[i] + 1) : null;
206
- tip.innerHTML = `<div><strong>${labelOf(i)}</strong></div>` +
207
- `<div>Type: ${type}${arm ? ` (arm ${arm})` : ''}</div>` +
208
- `<div>Size: ${sizesPx[i].toFixed(1)} px</div>` +
209
- `<div>X: ${X[i].toFixed(2)} · Y: ${Y[i].toFixed(2)}</div>` +
210
- `<div>r: ${r.toFixed(3)} · z: ${Zraw[i].toFixed(3)}</div>`;
211
  tip.style.opacity = '1';
212
  })
213
  .on('mousemove', (ev, i) => {
@@ -215,9 +218,10 @@
215
  const offsetX = 10, offsetY = 12;
216
  tip.style.transform = `translate(${Math.round(mx + offsetX)}px, ${Math.round(my + offsetY)}px)`;
217
  })
218
- .on('mouseleave', () => {
219
  tip.style.opacity = '0';
220
  tip.style.transform = 'translate(-9999px, -9999px)';
 
221
  })
222
  );
223
  };
 
93
  .attr('width', '100%')
94
  .style('display', 'block');
95
 
 
 
 
 
 
 
 
 
 
 
 
96
  const render = () => {
97
  const width = container.clientWidth || 800;
98
  const height = Math.max(260, Math.round(width / 3)); // keep ~3:1, min height
 
121
  // Ensure container can host an absolute tooltip
122
  container.style.position = container.style.position || 'relative';
123
  let tip = container.querySelector('.d3-tooltip');
124
+ let tipInner;
125
  if (!tip) {
126
  tip = document.createElement('div');
127
  tip.className = 'd3-tooltip';
 
131
  left: '0px',
132
  transform: 'translate(-9999px, -9999px)',
133
  pointerEvents: 'none',
134
+ padding: '8px 10px',
135
+ borderRadius: '8px',
136
  fontSize: '12px',
137
+ lineHeight: '1.35',
138
  border: '1px solid var(--border-color)',
139
  background: 'var(--surface-bg)',
140
  color: 'var(--text-color)',
141
+ boxShadow: '0 4px 24px rgba(0,0,0,.18)',
142
  opacity: '0',
143
  transition: 'opacity .12s ease'
144
  });
145
+ tipInner = document.createElement('div');
146
+ tipInner.className = 'd3-tooltip__inner';
147
+ tipInner.style.textAlign = 'left';
148
+ tip.appendChild(tipInner);
149
  container.appendChild(tip);
150
+ } else {
151
+ tipInner = tip.querySelector('.d3-tooltip__inner') || tip;
152
  }
153
 
154
  // Final filter: remove small dots very close to the galaxy center (after placement)
155
+ const centerHoleRadius = 0.48; // elliptical radius threshold
156
  const smallSizeThreshold = 7.5; // same notion as Python size cut
157
  const rTotal = idx.map((i) => Math.sqrt(((X[i] - cx) / a) ** 2 + ((Y[i] - cy) / b) ** 2));
158
  const idxFiltered = idx.filter((i, k) => !(rTotal[k] <= centerHoleRadius && sizesPx[i] < smallSizeThreshold));
 
167
  .attr('fill-opacity', 0.9)
168
  .attr('stroke', strokeColor)
169
  .attr('stroke-width', 0.4)
170
+ .on('mouseenter', function(ev, i) {
171
+ d3.select(this).raise()
172
+ .attr('stroke', isDark ? 'rgba(255,255,255,0.85)' : 'rgba(0,0,0,0.85)')
173
+ .attr('stroke-width', 1.2);
174
  const r = Math.sqrt(((X[i] - cx) / a) ** 2 + ((Y[i] - cy) / b) ** 2);
175
  const type = i < lenSpiral ? 'spiral' : 'bulge';
176
  const arm = i < lenSpiral ? (armIndices[i] + 1) : null;
177
+ tipInner.innerHTML = `<div><strong>${labelOf(i)}</strong></div>` +
178
+ `<div><strong>Type</strong> ${type}${arm ? ` (arm ${arm})` : ''}</div>` +
179
+ `<div><strong>Size</strong> ${sizesPx[i].toFixed(1)} px</div>` +
180
+ `<div><strong>X</strong> ${X[i].toFixed(2)} · <strong>Y</strong> ${Y[i].toFixed(2)}</div>` +
181
+ `<div><strong>r</strong> ${r.toFixed(3)} · <strong>z</strong> ${Zraw[i].toFixed(3)}</div>`;
182
  tip.style.opacity = '1';
183
  })
184
  .on('mousemove', (ev, i) => {
 
186
  const offsetX = 10, offsetY = 12;
187
  tip.style.transform = `translate(${Math.round(mx + offsetX)}px, ${Math.round(my + offsetY)}px)`;
188
  })
189
+ .on('mouseleave', function() {
190
  tip.style.opacity = '0';
191
  tip.style.transform = 'translate(-9999px, -9999px)';
192
+ d3.select(this).attr('stroke', strokeColor).attr('stroke-width', 0.4);
193
  }),
194
  (update) => update
195
  .attr('cx', (i) => xScale(X[i]))
 
199
  .attr('fill-opacity', 0.9)
200
  .attr('stroke', strokeColor)
201
  .attr('stroke-width', 0.4)
202
+ .on('mouseenter', function(ev, i) {
203
+ d3.select(this).raise()
204
+ .attr('stroke', isDark ? 'rgba(255,255,255,0.85)' : 'rgba(0,0,0,0.85)')
205
+ .attr('stroke-width', 1.2);
206
  const r = Math.sqrt(((X[i] - cx) / a) ** 2 + ((Y[i] - cy) / b) ** 2);
207
  const type = i < lenSpiral ? 'spiral' : 'bulge';
208
  const arm = i < lenSpiral ? (armIndices[i] + 1) : null;
209
+ tipInner.innerHTML = `<div><strong>${labelOf(i)}</strong></div>` +
210
+ `<div><strong>Type</strong> ${type}${arm ? ` (arm ${arm})` : ''}</div>` +
211
+ `<div><strong>Size</strong> ${sizesPx[i].toFixed(1)} px</div>` +
212
+ `<div><strong>X</strong> ${X[i].toFixed(2)} · <strong>Y</strong> ${Y[i].toFixed(2)}</div>` +
213
+ `<div><strong>r</strong> ${r.toFixed(3)} · <strong>z</strong> ${Zraw[i].toFixed(3)}</div>`;
214
  tip.style.opacity = '1';
215
  })
216
  .on('mousemove', (ev, i) => {
 
218
  const offsetX = 10, offsetY = 12;
219
  tip.style.transform = `translate(${Math.round(mx + offsetX)}px, ${Math.round(my + offsetY)}px)`;
220
  })
221
+ .on('mouseleave', function() {
222
  tip.style.opacity = '0';
223
  tip.style.transform = 'translate(-9999px, -9999px)';
224
+ d3.select(this).attr('stroke', strokeColor).attr('stroke-width', 0.4);
225
  })
226
  );
227
  };
fragments/d3js/line.html ADDED
@@ -0,0 +1,356 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div class="d3-line" style="width:100%;margin:10px 0;"></div>
2
+ <script>
3
+ (() => {
4
+ const ensureD3 = (cb) => {
5
+ if (window.d3 && typeof window.d3.select === 'function') return cb();
6
+ let s = document.getElementById('d3-cdn-script');
7
+ if (!s) {
8
+ s = document.createElement('script');
9
+ s.id = 'd3-cdn-script';
10
+ s.src = 'https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js';
11
+ document.head.appendChild(s);
12
+ }
13
+ const onReady = () => { if (window.d3 && typeof window.d3.select === 'function') cb(); };
14
+ s.addEventListener('load', onReady, { once: true });
15
+ if (window.d3) onReady();
16
+ };
17
+
18
+ const bootstrap = () => {
19
+ const mount = document.currentScript ? document.currentScript.previousElementSibling : null;
20
+ const container = (mount && mount.querySelector && mount.querySelector('.d3-line')) || document.querySelector('.d3-line');
21
+ if (!container) return;
22
+ if (container.dataset) {
23
+ if (container.dataset.mounted === 'true') return;
24
+ container.dataset.mounted = 'true';
25
+ }
26
+
27
+ // Dataset params matching the Plotly version
28
+ const datasets = [
29
+ { name: 'CIFAR-10', base: { ymin:0.10, ymax:0.90, k:10.0, x0:0.55 }, aug: { ymin:0.15, ymax:0.96, k:12.0, x0:0.40 }, target: 0.97 },
30
+ { name: 'CIFAR-100', base: { ymin:0.05, ymax:0.70, k: 9.5, x0:0.60 }, aug: { ymin:0.08, ymax:0.80, k:11.0, x0:0.45 }, target: 0.85 },
31
+ { name: 'ImageNet-1K', base: { ymin:0.02, ymax:0.68, k: 8.5, x0:0.65 }, aug: { ymin:0.04, ymax:0.75, k: 9.5, x0:0.50 }, target: 0.82 },
32
+ ];
33
+
34
+ // Controls UI
35
+ const controls = document.createElement('div');
36
+ controls.className = 'd3-line__controls';
37
+ Object.assign(controls.style, {
38
+ marginTop: '12px',
39
+ display: 'flex',
40
+ gap: '16px',
41
+ alignItems: 'center'
42
+ });
43
+
44
+ const labelDs = document.createElement('label');
45
+ Object.assign(labelDs.style, {
46
+ fontSize: '12px', color: 'rgba(0,0,0,.65)', display: 'flex', alignItems: 'center', gap: '6px', whiteSpace: 'nowrap', padding: '6px 10px'
47
+ });
48
+ labelDs.textContent = 'Dataset';
49
+ const selectDs = document.createElement('select');
50
+ Object.assign(selectDs.style, { fontSize: '12px', padding: '2px 6px' });
51
+ datasets.forEach((d, i) => {
52
+ const o = document.createElement('option');
53
+ o.value = String(i);
54
+ o.textContent = d.name;
55
+ selectDs.appendChild(o);
56
+ });
57
+ labelDs.appendChild(selectDs);
58
+
59
+ const labelAlpha = document.createElement('label');
60
+ Object.assign(labelAlpha.style, {
61
+ fontSize: '12px', color: 'rgba(0,0,0,.65)', display: 'flex', alignItems: 'center', gap: '10px', flex: '1', padding: '6px 10px'
62
+ });
63
+ labelAlpha.appendChild(document.createTextNode('Augmentation α'));
64
+ const slider = document.createElement('input');
65
+ slider.type = 'range'; slider.min = '0'; slider.max = '1'; slider.step = '0.01'; slider.value = '0.70';
66
+ Object.assign(slider.style, { flex: '1' });
67
+ const alphaVal = document.createElement('span'); alphaVal.className = 'alpha-value'; alphaVal.textContent = slider.value;
68
+ labelAlpha.appendChild(slider);
69
+ labelAlpha.appendChild(alphaVal);
70
+
71
+ controls.appendChild(labelDs);
72
+ controls.appendChild(labelAlpha);
73
+
74
+ // Create SVG
75
+ const svg = d3.select(container).append('svg')
76
+ .attr('width', '100%')
77
+ .style('display', 'block');
78
+
79
+ // Groups
80
+ const gRoot = svg.append('g');
81
+ const gGrid = gRoot.append('g').attr('class', 'grid');
82
+ const gAxes = gRoot.append('g').attr('class', 'axes');
83
+ const gLines = gRoot.append('g').attr('class', 'lines');
84
+ const gHover = gRoot.append('g').attr('class', 'hover');
85
+ const gLegend = gRoot.append('foreignObject').attr('class', 'legend');
86
+
87
+ // Tooltip
88
+ container.style.position = container.style.position || 'relative';
89
+ let tip = container.querySelector('.d3-tooltip');
90
+ let tipInner;
91
+ if (!tip) {
92
+ tip = document.createElement('div');
93
+ tip.className = 'd3-tooltip';
94
+ Object.assign(tip.style, {
95
+ position: 'absolute', top: '0px', left: '0px', transform: 'translate(-9999px, -9999px)', pointerEvents: 'none',
96
+ padding: '8px 10px', borderRadius: '8px', fontSize: '12px', lineHeight: '1.35', border: '1px solid var(--border-color)',
97
+ background: 'var(--surface-bg)', color: 'var(--text-color)', boxShadow: '0 4px 24px rgba(0,0,0,.18)', opacity: '0',
98
+ transition: 'opacity .12s ease'
99
+ });
100
+ tipInner = document.createElement('div');
101
+ tipInner.className = 'd3-tooltip__inner';
102
+ tipInner.style.textAlign = 'left';
103
+ tip.appendChild(tipInner);
104
+ container.appendChild(tip);
105
+ } else {
106
+ tipInner = tip.querySelector('.d3-tooltip__inner') || tip;
107
+ }
108
+
109
+ // Colors
110
+ const colorBase = '#64748b'; // slate-500
111
+ const colorImproved = '#2563eb'; // blue-600
112
+ const colorTarget = '#4b5563'; // gray-600
113
+ const legendBgLight = 'rgba(255,255,255,0.85)';
114
+ const legendBgDark = 'rgba(17,17,23,0.85)';
115
+
116
+ // Data and helpers
117
+ const N = 240;
118
+ const xs = Array.from({ length: N }, (_, i) => i / (N - 1));
119
+ const logistic = (x, { ymin, ymax, k, x0 }) => ymin + (ymax - ymin) / (1 + Math.exp(-k * (x - x0)));
120
+ const blend = (l, e, a) => (1 - a) * l + a * e;
121
+
122
+ let datasetIndex = 0;
123
+ let alpha = parseFloat(slider.value) || 0.7;
124
+
125
+ let yBase = [];
126
+ let yAug = [];
127
+ let yImp = [];
128
+ let yTgt = [];
129
+
130
+ function computeCurves() {
131
+ const d = datasets[datasetIndex];
132
+ yBase = xs.map((x) => logistic(x, d.base));
133
+ yAug = xs.map((x) => logistic(x, d.aug));
134
+ yTgt = xs.map(() => d.target);
135
+ yImp = yBase.map((v, i) => blend(v, yAug[i], alpha));
136
+ }
137
+
138
+ // Scales and layout
139
+ let width = 800, height = 360;
140
+ let margin = { top: 16, right: 28, bottom: 40, left: 44 };
141
+ let xScale = d3.scaleLinear();
142
+ let yScale = d3.scaleLinear();
143
+
144
+ // Paths
145
+ const lineGen = d3.line()
146
+ .curve(d3.curveCatmullRom.alpha(0.6))
147
+ .x((d, i) => xScale(xs[i]))
148
+ .y((d) => yScale(d));
149
+
150
+ const pathBase = gLines.append('path').attr('fill', 'none').attr('stroke', colorBase).attr('stroke-width', 2);
151
+ const pathImp = gLines.append('path').attr('fill', 'none').attr('stroke', colorImproved).attr('stroke-width', 2);
152
+ const pathTgt = gLines.append('path').attr('fill', 'none').attr('stroke', colorTarget).attr('stroke-width', 2).attr('stroke-dasharray', '6,6');
153
+
154
+ // Hover elements
155
+ const hoverLine = gHover.append('line').attr('stroke-width', 1);
156
+ const hoverDotB = gHover.append('circle').attr('r', 3.5).attr('fill', colorBase).attr('stroke', '#fff').attr('stroke-width', 1);
157
+ const hoverDotI = gHover.append('circle').attr('r', 3.5).attr('fill', colorImproved).attr('stroke', '#fff').attr('stroke-width', 1);
158
+ const hoverDotT = gHover.append('circle').attr('r', 3.5).attr('fill', colorTarget).attr('stroke', '#fff').attr('stroke-width', 1);
159
+
160
+ const overlay = gHover.append('rect').attr('fill', 'transparent').style('cursor', 'crosshair');
161
+
162
+ function updateScales() {
163
+ const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
164
+ const axisColor = isDark ? 'rgba(255,255,255,0.25)' : 'rgba(0,0,0,0.25)';
165
+ const tickColor = isDark ? 'rgba(255,255,255,0.70)' : 'rgba(0,0,0,0.55)';
166
+ const gridColor = isDark ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.05)';
167
+
168
+ width = container.clientWidth || 800;
169
+ height = Math.max(260, Math.round(width / 3));
170
+ svg.attr('width', width).attr('height', height);
171
+
172
+ const innerWidth = width - margin.left - margin.right;
173
+ const innerHeight = height - margin.top - margin.bottom;
174
+ gRoot.attr('transform', `translate(${margin.left},${margin.top})`);
175
+
176
+ xScale.domain([0, 1]).range([0, innerWidth]);
177
+ yScale.domain([0, 1]).range([innerHeight, 0]);
178
+
179
+ // Grid (horizontal)
180
+ gGrid.selectAll('*').remove();
181
+ const yTicks = yScale.ticks(6);
182
+ gGrid.selectAll('line')
183
+ .data(yTicks)
184
+ .join('line')
185
+ .attr('x1', 0)
186
+ .attr('x2', innerWidth)
187
+ .attr('y1', (d) => yScale(d))
188
+ .attr('y2', (d) => yScale(d))
189
+ .attr('stroke', gridColor)
190
+ .attr('stroke-width', 1)
191
+ .attr('shape-rendering', 'crispEdges');
192
+
193
+ // Axes
194
+ gAxes.selectAll('*').remove();
195
+ const xAxis = d3.axisBottom(xScale).ticks(8).tickSizeOuter(0);
196
+ const yAxis = d3.axisLeft(yScale).ticks(6).tickSizeOuter(0).tickFormat(d3.format('.2f'));
197
+ gAxes.append('g')
198
+ .attr('transform', `translate(0,${innerHeight})`)
199
+ .call(xAxis)
200
+ .call((g) => {
201
+ g.selectAll('path, line').attr('stroke', axisColor);
202
+ g.selectAll('text').attr('fill', tickColor).style('font-size', '12px');
203
+ });
204
+ gAxes.append('g')
205
+ .call(yAxis)
206
+ .call((g) => {
207
+ g.selectAll('path, line').attr('stroke', axisColor);
208
+ g.selectAll('text').attr('fill', tickColor).style('font-size', '12px');
209
+ });
210
+
211
+ // Axis labels (X and Y)
212
+ gAxes.append('text')
213
+ .attr('class', 'axis-label axis-label--x')
214
+ .attr('x', innerWidth)
215
+ .attr('y', innerHeight + 32)
216
+ .attr('text-anchor', 'end')
217
+ .style('font-size', '12px')
218
+ .style('fill', tickColor)
219
+ .text('x');
220
+ gAxes.append('text')
221
+ .attr('class', 'axis-label axis-label--y')
222
+ .attr('text-anchor', 'middle')
223
+ .attr('transform', `translate(${-36},${innerHeight/2}) rotate(-90)`)
224
+ .style('font-size', '12px')
225
+ .style('fill', tickColor)
226
+ .text('y');
227
+
228
+ overlay.attr('x', 0).attr('y', 0).attr('width', innerWidth).attr('height', innerHeight);
229
+ hoverLine.attr('y1', 0).attr('y2', innerHeight).attr('stroke', axisColor);
230
+
231
+ // Legend inside plot (bottom-right), no background/border/shadow
232
+ const legendWidth = Math.min(180, Math.max(120, Math.round(innerWidth * 0.22)));
233
+ const legendHeight = 64;
234
+ gLegend
235
+ .attr('x', innerWidth - legendWidth)
236
+ .attr('y', innerHeight - legendHeight)
237
+ .attr('width', legendWidth)
238
+ .attr('height', legendHeight);
239
+ const legendRoot = gLegend.selectAll('div').data([0]).join('xhtml:div');
240
+ Object.assign(legendRoot.node().style, {
241
+ background: 'transparent',
242
+ border: 'none',
243
+ borderRadius: '0',
244
+ padding: '0',
245
+ fontSize: '12px',
246
+ lineHeight: '1.35',
247
+ color: 'var(--text-color)'
248
+ });
249
+ legendRoot.html(`
250
+ <div style="display:flex;flex-direction:column;gap:6px;">
251
+ <div style="display:flex;align-items:center;gap:8px;">
252
+ <span style="width:18px;height:3px;background:${colorBase};border-radius:2px;display:inline-block"></span>
253
+ <span>Baseline</span>
254
+ </div>
255
+ <div style="display:flex;align-items:center;gap:8px;">
256
+ <span style="width:18px;height:3px;background:${colorImproved};border-radius:2px;display:inline-block"></span>
257
+ <span>Improved</span>
258
+ </div>
259
+ <div style="display:flex;align-items:center;gap:8px;">
260
+ <span style="width:18px;height:0;border-top:2px dashed ${colorTarget};display:inline-block"></span>
261
+ <span>Target</span>
262
+ </div>
263
+ </div>
264
+ `);
265
+ }
266
+
267
+ function updatePaths() {
268
+ pathBase.transition().duration(200).attr('d', lineGen(yBase));
269
+ pathImp.transition().duration(200).attr('d', lineGen(yImp));
270
+ pathTgt.transition().duration(200).attr('d', lineGen(yTgt));
271
+ }
272
+
273
+ function updateAlpha(a) {
274
+ alpha = a;
275
+ alphaVal.textContent = a.toFixed(2);
276
+ yImp = yBase.map((v, i) => blend(v, yAug[i], alpha));
277
+ pathImp.transition().duration(80).attr('d', lineGen(yImp));
278
+ }
279
+
280
+ function applyDataset() {
281
+ computeCurves();
282
+ updatePaths();
283
+ }
284
+
285
+ // Hover interactions
286
+ function onMove(event) {
287
+ const [mx, my] = d3.pointer(event, overlay.node());
288
+ const xi = Math.max(0, Math.min(N - 1, Math.round(xScale.invert(mx) * (N - 1))));
289
+ const xpx = xScale(xs[xi]);
290
+ const yb = yBase[xi], yi = yImp[xi], yt = yTgt[xi];
291
+ hoverLine.attr('x1', xpx).attr('x2', xpx).style('display', null);
292
+ hoverDotB.attr('cx', xpx).attr('cy', yScale(yb)).style('display', null);
293
+ hoverDotI.attr('cx', xpx).attr('cy', yScale(yi)).style('display', null);
294
+ hoverDotT.attr('cx', xpx).attr('cy', yScale(yt)).style('display', null);
295
+
296
+ // Tooltip content
297
+ const ds = datasets[datasetIndex].name;
298
+ tipInner.innerHTML = `<div><strong>${ds}</strong></div>` +
299
+ `<div><strong>x</strong> ${xs[xi].toFixed(2)}</div>` +
300
+ `<div><span style="display:inline-block;width:10px;height:10px;background:${colorBase};border-radius:50%;margin-right:6px;"></span><strong>Baseline</strong> ${yb.toFixed(3)}</div>` +
301
+ `<div><span style="display:inline-block;width:10px;height:10px;background:${colorImproved};border-radius:50%;margin-right:6px;"></span><strong>Improved</strong> ${yi.toFixed(3)}</div>` +
302
+ `<div><span style="display:inline-block;width:10px;height:10px;background:${colorTarget};border-radius:50%;margin-right:6px;"></span><strong>Target</strong> ${yt.toFixed(3)}</div>`;
303
+ const offsetX = 12, offsetY = 12;
304
+ tip.style.opacity = '1';
305
+ tip.style.transform = `translate(${Math.round(mx + offsetX + margin.left)}px, ${Math.round(my + offsetY + margin.top)}px)`;
306
+ }
307
+
308
+ function onLeave() {
309
+ tip.style.opacity = '0';
310
+ tip.style.transform = 'translate(-9999px, -9999px)';
311
+ hoverLine.style('display', 'none');
312
+ hoverDotB.style('display', 'none');
313
+ hoverDotI.style('display', 'none');
314
+ hoverDotT.style('display', 'none');
315
+ }
316
+
317
+ overlay.on('mousemove', onMove).on('mouseleave', onLeave);
318
+
319
+ // Init + controls wiring
320
+ computeCurves();
321
+ updateScales();
322
+ updatePaths();
323
+
324
+ // Attach controls after SVG for consistency with Plotly fragment
325
+ container.appendChild(controls);
326
+
327
+ selectDs.addEventListener('change', (e) => {
328
+ datasetIndex = parseInt(e.target.value) || 0;
329
+ applyDataset();
330
+ });
331
+ slider.addEventListener('input', (e) => {
332
+ const a = parseFloat(e.target.value) || 0;
333
+ updateAlpha(a);
334
+ });
335
+
336
+ // Resize handling
337
+ const render = () => {
338
+ updateScales();
339
+ updatePaths();
340
+ };
341
+ if (window.ResizeObserver) {
342
+ const ro = new ResizeObserver(() => render());
343
+ ro.observe(container);
344
+ } else {
345
+ window.addEventListener('resize', render);
346
+ }
347
+ render();
348
+ };
349
+
350
+ if (document.readyState === 'loading') {
351
+ document.addEventListener('DOMContentLoaded', () => ensureD3(bootstrap), { once: true });
352
+ } else { ensureD3(bootstrap); }
353
+ })();
354
+ </script>
355
+
356
+
fragments/plotly/banner.py CHANGED
@@ -123,7 +123,7 @@ fig.update_layout(
123
  # fig.show()
124
 
125
  fig.write_html(
126
- "../app/src/fragments/banner.html",
127
  include_plotlyjs=False,
128
  full_html=False,
129
  config={
 
123
  # fig.show()
124
 
125
  fig.write_html(
126
+ "../app/src/content/fragments/banner.html",
127
  include_plotlyjs=False,
128
  full_html=False,
129
  config={
fragments/plotly/bar.py CHANGED
@@ -1,116 +1,173 @@
1
  import plotly.graph_objects as go
2
  import plotly.io as pio
3
- import os
4
 
5
  """
6
- Simple grouped bar chart (Baseline / Improved / Target), minimal Distill-like style.
7
- Responsive, no zoom/pan, clean hover (rounded tooltip corners via post_script).
8
  """
9
 
10
- # Data (five categories)
11
- categories = ["A", "B", "C", "D", "E"]
12
- baseline = [0.52, 0.61, 0.67, 0.73, 0.78]
13
- improved = [0.58, 0.66, 0.72, 0.79, 0.86]
14
- target = [0.60, 0.68, 0.75, 0.82, 0.90]
15
 
16
- color_base = "#64748b" # slate-500
17
- color_improved = "#2563eb" # blue-600
18
- color_target = "#4b5563" # gray-600
 
 
 
 
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  fig = go.Figure()
21
- fig.add_bar(
22
- x=categories,
23
- y=baseline,
24
- name="Baseline",
25
- marker=dict(color=color_base),
26
- offsetgroup="grp",
27
- hovertemplate="<b>%{x}</b><br>%{fullData.name}: %{y:.3f}<extra></extra>",
28
- )
 
 
 
29
 
30
- fig.add_bar(
31
- x=categories,
32
- y=improved,
33
- name="Improved",
34
- marker=dict(color=color_improved),
35
- offsetgroup="grp",
36
- hovertemplate="<b>%{x}</b><br>%{fullData.name}: %{y:.3f}<extra></extra>",
37
- )
38
 
39
- fig.add_bar(
40
- x=categories,
41
- y=target,
42
- name="Target",
43
- marker=dict(color=color_target, opacity=0.65, line=dict(color=color_target, width=1)),
44
- offsetgroup="grp",
45
- hovertemplate="<b>%{x}</b><br>%{fullData.name}: %{y:.3f}<extra></extra>",
46
- )
47
 
 
48
  fig.update_layout(
49
- barmode="group",
50
  autosize=True,
51
  paper_bgcolor="rgba(0,0,0,0)",
52
  plot_bgcolor="rgba(0,0,0,0)",
53
- margin=dict(l=28, r=12, t=8, b=28),
54
  hovermode="x unified",
55
  legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="left", x=0),
56
- xaxis=dict(
57
- showgrid=False,
58
- zeroline=False,
59
- showline=True,
60
- linecolor="rgba(0,0,0,0.25)",
61
- linewidth=1,
62
- ticks="outside",
63
- ticklen=6,
64
- tickcolor="rgba(0,0,0,0.25)",
65
- tickfont=dict(size=12, color="rgba(0,0,0,0.65)"),
66
- title=None,
67
- automargin=True,
68
- fixedrange=True,
69
- ),
70
- yaxis=dict(
71
- showgrid=False,
72
- zeroline=False,
73
- showline=True,
74
- linecolor="rgba(0,0,0,0.25)",
75
- linewidth=1,
76
- ticks="outside",
77
- ticklen=6,
78
- tickcolor="rgba(0,0,0,0.25)",
79
- tickfont=dict(size=12, color="rgba(0,0,0,0.65)"),
80
- title=None,
81
- tickformat=".2f",
82
- automargin=True,
83
- fixedrange=True,
84
- ),
85
  )
86
 
87
- post_script = """
88
- (function(){
89
- var plots = document.querySelectorAll('.js-plotly-plot');
90
- plots.forEach(function(gd){
91
- function round(){
92
- try {
93
- var root = gd && gd.parentNode ? gd.parentNode : document;
94
- var rects = root.querySelectorAll('.hoverlayer .hovertext rect');
95
- rects.forEach(function(r){ r.setAttribute('rx', 8); r.setAttribute('ry', 8); });
96
- } catch(e) {}
97
- }
98
- if (gd && gd.on){
99
- gd.on('plotly_hover', round);
100
- gd.on('plotly_unhover', round);
101
- gd.on('plotly_relayout', round);
102
- }
103
- setTimeout(round, 0);
104
- });
105
- })();
106
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
 
108
- fig.write_html("../app/src/fragments/bar.html",
109
- include_plotlyjs=False,
110
- full_html=False,
 
111
  config={
112
  'displayModeBar': False,
113
- 'responsive': True,
114
  'scrollZoom': False,
115
  })
116
 
 
1
  import plotly.graph_objects as go
2
  import plotly.io as pio
3
+ import numpy as np
4
 
5
  """
6
+ Stacked bar chart: GPU memory breakdown vs sequence length, with menus for Model Size and Recomputation.
7
+ Responsive, no zoom/pan, clean hover; styled to match the minimal theme.
8
  """
9
 
10
+ # Axes
11
+ seq_labels = ["1024", "2048", "4096", "8192"]
12
+ seq_scale = np.array([1, 2, 4, 8], dtype=float)
 
 
13
 
14
+ # Components and colors (aligned with the provided example)
15
+ components = [
16
+ ("parameters", "rgb(78, 165, 183)"),
17
+ ("gradients", "rgb(227, 138, 66)"),
18
+ ("optimizer", "rgb(232, 137, 171)"),
19
+ ("activations", "rgb(206, 192, 250)"),
20
+ ]
21
 
22
+ # Model sizes and base memory (GB) for params/grad/opt (constant vs seq), by size
23
+ model_sizes = ["1B", "3B", "8B", "70B", "405B"]
24
+ params_mem = {
25
+ "1B": 4.0,
26
+ "3B": 13.3,
27
+ "8B": 26.0,
28
+ "70B": 244.0,
29
+ "405B": 1520.0,
30
+ }
31
+ # Optimizer ~= 2x params; gradients ~= params (illustrative)
32
+
33
+ # Activations base coefficient per size (growth ~ coeff * (seq/1024)^2)
34
+ act_coeff = {
35
+ "1B": 3.6,
36
+ "3B": 9.3,
37
+ "8B": 46.2,
38
+ "70B": 145.7,
39
+ "405B": 1519.9,
40
+ }
41
+
42
+ def activations_curve(size_key: str, recompute: str) -> np.ndarray:
43
+ base = act_coeff[size_key] * (seq_scale ** 2)
44
+ if recompute == "selective":
45
+ return base * 0.25
46
+ if recompute == "full":
47
+ return base * (1.0/16.0)
48
+ return base
49
+
50
+ def stack_for(size_key: str, recompute: str):
51
+ p = np.full_like(seq_scale, params_mem[size_key], dtype=float)
52
+ g = np.full_like(seq_scale, params_mem[size_key], dtype=float)
53
+ o = np.full_like(seq_scale, 2.0 * params_mem[size_key], dtype=float)
54
+ a = activations_curve(size_key, recompute)
55
+ return {
56
+ "parameters": p,
57
+ "gradients": g,
58
+ "optimizer": o,
59
+ "activations": a,
60
+ }
61
+
62
+ # Precompute all combinations
63
+ recomp_modes = ["none", "selective", "full"]
64
+ Y = {mode: {size: stack_for(size, mode) for size in model_sizes} for mode in recomp_modes}
65
+
66
+ # Build traces: 4 traces per size (20 total). Start with size index 0 visible
67
  fig = go.Figure()
68
+ for size in model_sizes:
69
+ for comp_name, color in components:
70
+ fig.add_bar(
71
+ x=seq_labels,
72
+ y=Y["none"][size][comp_name],
73
+ name=comp_name,
74
+ marker=dict(color=color),
75
+ hovertemplate="Seq len=%{x}<br>Mem=%{y:.1f}GB<br>%{data.name}<extra></extra>",
76
+ showlegend=True,
77
+ visible=(size == model_sizes[0]),
78
+ )
79
 
80
+ # Compute y-axis ranges per size and recomputation
81
+ def max_total(size: str, mode: str) -> float:
82
+ stacks = Y[mode][size]
83
+ totals = stacks["parameters"] + stacks["gradients"] + stacks["optimizer"] + stacks["activations"]
84
+ return float(np.max(totals))
 
 
 
85
 
86
+ layout_y_ranges = {mode: {size: 1.05 * max_total(size, mode) for size in model_sizes} for mode in recomp_modes}
 
 
 
 
 
 
 
87
 
88
+ # Layout
89
  fig.update_layout(
90
+ barmode="stack",
91
  autosize=True,
92
  paper_bgcolor="rgba(0,0,0,0)",
93
  plot_bgcolor="rgba(0,0,0,0)",
94
+ margin=dict(l=40, r=28, t=20, b=40),
95
  hovermode="x unified",
96
  legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="left", x=0),
97
+ xaxis=dict(title=dict(text="Sequence Length"), fixedrange=True),
98
+ yaxis=dict(title=dict(text="Memory (GB)"), fixedrange=True),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  )
100
 
101
+ # Updatemenus: Model Size (toggle visibility)
102
+ buttons_sizes = []
103
+ for i, size in enumerate(model_sizes):
104
+ visible = [False] * (len(model_sizes) * len(components))
105
+ start = i * len(components)
106
+ for j in range(len(components)):
107
+ visible[start + j] = True
108
+ buttons_sizes.append(dict(
109
+ label=size,
110
+ method="update",
111
+ args=[
112
+ {"visible": visible},
113
+ {"yaxis": {"range": [0, layout_y_ranges["none"][size]]}},
114
+ ],
115
+ ))
116
+
117
+ # Updatemenus: Recomputation (restyle y across all traces)
118
+ def y_for_mode(mode: str):
119
+ ys = []
120
+ for size in model_sizes:
121
+ stacks = Y[mode][size]
122
+ for comp_name, _ in components:
123
+ ys.append(stacks[comp_name])
124
+ return ys
125
+
126
+ buttons_recomp = []
127
+ for mode, label in [("none", "None"), ("selective", "selective"), ("full", "full")]:
128
+ ys = y_for_mode(mode)
129
+ # Flatten into the format expected by Plotly for multiple traces
130
+ buttons_recomp.append(dict(
131
+ label=label,
132
+ method="update",
133
+ args=[
134
+ {"y": ys},
135
+ {"yaxis": {"range": [0, max(layout_y_ranges[mode].values())]}},
136
+ ],
137
+ ))
138
+
139
+ fig.update_layout(
140
+ updatemenus=[
141
+ dict(
142
+ type="dropdown",
143
+ x=1.03, xanchor="left",
144
+ y=0.60, yanchor="top",
145
+ showactive=True,
146
+ active=0,
147
+ buttons=buttons_sizes,
148
+ ),
149
+ dict(
150
+ type="dropdown",
151
+ x=1.03, xanchor="left",
152
+ y=0.40, yanchor="top",
153
+ showactive=True,
154
+ active=0,
155
+ buttons=buttons_recomp,
156
+ ),
157
+ ],
158
+ annotations=[
159
+ dict(text="Model Size:", x=1.03, xanchor="left", xref="paper", y=0.60, yanchor="bottom", yref="paper", showarrow=False),
160
+ dict(text="Recomputation:", x=1.03, xanchor="left", xref="paper", y=0.40, yanchor="bottom", yref="paper", showarrow=False),
161
+ ],
162
+ )
163
 
164
+ # Write fragment
165
+ fig.write_html("../../app/src/content/fragments/bar.html",
166
+ include_plotlyjs=False,
167
+ full_html=False,
168
  config={
169
  'displayModeBar': False,
170
+ 'responsive': True,
171
  'scrollZoom': False,
172
  })
173
 
fragments/plotly/heatmap.py CHANGED
@@ -114,7 +114,7 @@ html = pio.to_html(
114
  },
115
  )
116
 
117
- fig.write_html("../app/src/fragments/heatmap.html",
118
  include_plotlyjs=False,
119
  full_html=False,
120
  config={
 
114
  },
115
  )
116
 
117
+ fig.write_html("../app/src/content/fragments/heatmap.html",
118
  include_plotlyjs=False,
119
  full_html=False,
120
  config={
fragments/plotly/line.py CHANGED
@@ -5,36 +5,56 @@ import os
5
  import uuid
6
 
7
  """
8
- Interactive line chart example (3 curves + live slider)
9
 
10
- The slider blends each curve from linear to exponential in real time (no mouseup required).
11
- This fragment is safe to insert multiple times on the page (unique IDs per instance).
 
 
12
  """
13
 
14
  # Grid (x) and parameterization
15
  N = 240
16
  x = np.linspace(0, 1, N)
17
 
18
- # Linear baselines (increasing)
19
- lin1 = 0.20 + 0.60 * x
20
- lin2 = 0.15 + 0.70 * x
21
- lin3 = 0.10 + 0.80 * x
22
 
23
- # Helper: normalized exponential on [0,1]
24
- def exp_norm(xv: np.ndarray, k: float) -> np.ndarray:
25
- return (np.exp(k * xv) - 1.0) / (np.exp(k) - 1.0)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
- # Exponential counterparts (similar ranges)
28
- exp1 = 0.20 + 0.60 * exp_norm(x, 3.0)
29
- exp2 = 0.15 + 0.70 * exp_norm(x, 3.5)
30
- exp3 = 0.10 + 0.80 * exp_norm(x, 2.8)
 
 
31
 
32
- # Initial blend (alpha=0 pure linear)
33
- alpha0 = 0.0
34
  blend = lambda l, e, a: (1 - a) * l + a * e
35
- y1 = blend(lin1, exp1, alpha0)
36
- y2 = blend(lin2, exp2, alpha0)
37
- y3 = blend(lin3, exp3, alpha0)
38
 
39
  color_base = "#64748b" # slate-500
40
  color_improved = "#2563eb" # blue-600
@@ -79,8 +99,17 @@ fig.update_layout(
79
  autosize=True,
80
  paper_bgcolor="rgba(0,0,0,0)",
81
  plot_bgcolor="rgba(0,0,0,0)",
82
- margin=dict(l=28, r=12, t=8, b=28),
83
  hovermode="x unified",
 
 
 
 
 
 
 
 
 
84
  hoverlabel=dict(
85
  bgcolor="white",
86
  font=dict(color="#111827", size=12),
@@ -173,17 +202,17 @@ container_id = f"line-ex-container-{uid}"
173
  slider_tpl = '''
174
  <div id="__CID__">
175
  __PLOT__
176
- <div class="plotly_controls" style="margin-top:10px; display:flex; gap:14px; align-items:center;">
177
- <label style="font-size:12px;color:rgba(0,0,0,.65); display:flex; align-items:center; gap:6px; white-space:nowrap;">
178
  Dataset
179
  <select id="__DSID__" style="font-size:12px; padding:2px 6px;">
180
- <option value="0">Dataset A</option>
181
- <option value="1">Dataset B</option>
182
- <option value="2">Dataset C</option>
183
  </select>
184
  </label>
185
- <label style="font-size:12px;color:rgba(0,0,0,.65);display:flex;align-items:center;gap:8px; flex:1;">
186
- Nonlinearity
187
  <input id="__SID__" type="range" min="0" max="1" step="0.01" value="__A0__" style="flex:1;">
188
  <span class="alpha-value">__A0__</span>
189
  </label>
@@ -199,32 +228,37 @@ slider_tpl = '''
199
  var valueEl = container.querySelector('.alpha-value');
200
  var N = __N__;
201
  var xs = Array.from({length: N}, function(_,i){ return i/(N-1); });
202
- function expNorm(x,k){ return (Math.exp(k*x)-1)/(Math.exp(k)-1); }
203
  function blend(l,e,a){ return (1-a)*l + a*e; }
204
  var datasets = [
205
- { curves: [ {o:0.20,s:0.60,k:3.0}, {o:0.15,s:0.70,k:3.5}, {o:0.10,s:0.80,k:2.8} ] },
206
- { curves: [ {o:0.30,s:0.55,k:2.2}, {o:0.18,s:0.65,k:2.8}, {o:0.12,s:0.70,k:2.0} ] },
207
- { curves: [ {o:0.10,s:0.85,k:3.8}, {o:0.12,s:0.80,k:3.2}, {o:0.08,s:0.90,k:3.0} ] }
208
  ];
209
  var dsi = 0;
210
- function makeY(a){
211
- var cs = datasets[dsi].curves;
212
- var y1 = xs.map(function(x){ return blend(cs[0].o + cs[0].s*x, cs[0].o + cs[0].s*expNorm(x,cs[0].k), a); });
213
- var y2 = xs.map(function(x){ return blend(cs[1].o + cs[1].s*x, cs[1].o + cs[1].s*expNorm(x,cs[1].k), a); });
214
- var y3 = xs.map(function(x){ return blend(cs[2].o + cs[2].s*x, cs[2].o + cs[2].s*expNorm(x,cs[2].k), a); });
215
- return [y1,y2,y3];
216
- }
217
- function apply(a){
218
- var ys = makeY(a);
219
- Plotly.restyle(gd, {y:[ys[0]]}, [0]);
220
- Plotly.restyle(gd, {y:[ys[1]]}, [1]);
221
- Plotly.restyle(gd, {y:[ys[2]]}, [2]);
222
  if(valueEl) valueEl.textContent = a.toFixed(2);
223
  }
 
 
 
 
 
 
 
 
 
 
 
224
  var initA = parseFloat(slider.value)||0;
225
- slider.addEventListener('input', function(e){ apply(parseFloat(e.target.value)||0); });
226
- dsSelect.addEventListener('change', function(e){ dsi = parseInt(e.target.value)||0; apply(parseFloat(slider.value)||0); });
227
- setTimeout(function(){ apply(initA); }, 0);
228
  })();
229
  </script>
230
  '''
@@ -237,12 +271,6 @@ slider_html = (slider_tpl
237
  .replace('__PLOT__', html_plot)
238
  )
239
 
240
- fig.write_html("../app/src/fragments/line.html",
241
- include_plotlyjs=False,
242
- full_html=False,
243
- config={
244
- 'displayModeBar': False,
245
- 'responsive': True,
246
- 'scrollZoom': False,
247
- })
248
 
 
5
  import uuid
6
 
7
  """
8
+ Interactive line chart example (Baseline / Improved / Target) with a live slider.
9
 
10
+ Context: research-style training curves for multiple datasets (CIFAR-10, CIFAR-100, ImageNet-1K).
11
+ The slider "Augmentation α" blends the Improved curve between the Baseline (α=0)
12
+ and an augmented counterpart (α=1) via a simple mixing equation.
13
+ Export remains responsive, with no zoom and no mode bar.
14
  """
15
 
16
  # Grid (x) and parameterization
17
  N = 240
18
  x = np.linspace(0, 1, N)
19
 
20
+ # Logistic helper for smooth learning curves
21
+ def logistic(xv: np.ndarray, ymin: float, ymax: float, k: float, x0: float) -> np.ndarray:
22
+ return ymin + (ymax - ymin) / (1.0 + np.exp(-k * (xv - x0)))
 
23
 
24
+ # Plausible dataset params (baseline vs augmented) + a constant target line
25
+ datasets_params = [
26
+ {
27
+ "name": "CIFAR-10",
28
+ "base": {"ymin": 0.10, "ymax": 0.90, "k": 10.0, "x0": 0.55},
29
+ "aug": {"ymin": 0.15, "ymax": 0.96, "k": 12.0, "x0": 0.40},
30
+ "target": 0.97,
31
+ },
32
+ {
33
+ "name": "CIFAR-100",
34
+ "base": {"ymin": 0.05, "ymax": 0.70, "k": 9.5, "x0": 0.60},
35
+ "aug": {"ymin": 0.08, "ymax": 0.80, "k": 11.0, "x0": 0.45},
36
+ "target": 0.85,
37
+ },
38
+ {
39
+ "name": "ImageNet-1K",
40
+ "base": {"ymin": 0.02, "ymax": 0.68, "k": 8.5, "x0": 0.65},
41
+ "aug": {"ymin": 0.04, "ymax": 0.75, "k": 9.5, "x0": 0.50},
42
+ "target": 0.82,
43
+ },
44
+ ]
45
 
46
+ # Initial dataset index and alpha
47
+ alpha0 = 0.7
48
+ ds0 = datasets_params[0]
49
+ base0 = logistic(x, **ds0["base"])
50
+ aug0 = logistic(x, **ds0["aug"])
51
+ target0 = np.full_like(x, ds0["target"], dtype=float)
52
 
53
+ # Traces: Baseline (fixed), Improved (blended by α), Target (constant goal)
 
54
  blend = lambda l, e, a: (1 - a) * l + a * e
55
+ y1 = base0
56
+ y2 = blend(base0, aug0, alpha0)
57
+ y3 = target0
58
 
59
  color_base = "#64748b" # slate-500
60
  color_improved = "#2563eb" # blue-600
 
99
  autosize=True,
100
  paper_bgcolor="rgba(0,0,0,0)",
101
  plot_bgcolor="rgba(0,0,0,0)",
102
+ margin=dict(l=40, r=28, t=20, b=40),
103
  hovermode="x unified",
104
+ legend=dict(
105
+ orientation="v",
106
+ x=1,
107
+ y=0,
108
+ xanchor="right",
109
+ yanchor="bottom",
110
+ bgcolor="rgba(255,255,255,0)",
111
+ borderwidth=0,
112
+ ),
113
  hoverlabel=dict(
114
  bgcolor="white",
115
  font=dict(color="#111827", size=12),
 
202
  slider_tpl = '''
203
  <div id="__CID__">
204
  __PLOT__
205
+ <div class="plotly_controls" style="margin-top:12px; display:flex; gap:16px; align-items:center;">
206
+ <label style="font-size:12px;color:rgba(0,0,0,.65); display:flex; align-items:center; gap:6px; white-space:nowrap; padding:6px 10px;">
207
  Dataset
208
  <select id="__DSID__" style="font-size:12px; padding:2px 6px;">
209
+ <option value="0">CIFAR-10</option>
210
+ <option value="1">CIFAR-100</option>
211
+ <option value="2">ImageNet-1K</option>
212
  </select>
213
  </label>
214
+ <label style="font-size:12px;color:rgba(0,0,0,.65);display:flex;align-items:center;gap:10px; flex:1; padding:6px 10px;">
215
+ Augmentation α
216
  <input id="__SID__" type="range" min="0" max="1" step="0.01" value="__A0__" style="flex:1;">
217
  <span class="alpha-value">__A0__</span>
218
  </label>
 
228
  var valueEl = container.querySelector('.alpha-value');
229
  var N = __N__;
230
  var xs = Array.from({length: N}, function(_,i){ return i/(N-1); });
231
+ function logistic(x, ymin, ymax, k, x0){ return ymin + (ymax - ymin) / (1 + Math.exp(-k*(x - x0))); }
232
  function blend(l,e,a){ return (1-a)*l + a*e; }
233
  var datasets = [
234
+ { name:'CIFAR-10', base:{ymin:0.10,ymax:0.90,k:10.0,x0:0.55}, aug:{ymin:0.15,ymax:0.96,k:12.0,x0:0.40}, target:0.97 },
235
+ { name:'CIFAR-100', base:{ymin:0.05,ymax:0.70,k:9.5,x0:0.60}, aug:{ymin:0.08,ymax:0.80,k:11.0,x0:0.45}, target:0.85 },
236
+ { name:'ImageNet-1K', base:{ymin:0.02,ymax:0.68,k:8.5,x0:0.65}, aug:{ymin:0.04,ymax:0.75,k:9.5,x0:0.50}, target:0.82 }
237
  ];
238
  var dsi = 0;
239
+ var yb = xs.map(function(x){ return logistic(x, datasets[dsi].base.ymin, datasets[dsi].base.ymax, datasets[dsi].base.k, datasets[dsi].base.x0); });
240
+ var ya = xs.map(function(x){ return logistic(x, datasets[dsi].aug.ymin, datasets[dsi].aug.ymax, datasets[dsi].aug.k, datasets[dsi].aug.x0); });
241
+ var yt = xs.map(function(){ return datasets[dsi].target; });
242
+ function applyAlpha(a){
243
+ var yi = yb.map(function(v,i){ return blend(v, ya[i], a); });
244
+ Plotly.restyle(gd, {y:[yi]}, [1]); // only Improved changes with α
 
 
 
 
 
 
245
  if(valueEl) valueEl.textContent = a.toFixed(2);
246
  }
247
+ function applyDataset(){
248
+ var d = datasets[dsi];
249
+ yb = xs.map(function(x){ return logistic(x, d.base.ymin, d.base.ymax, d.base.k, d.base.x0); });
250
+ ya = xs.map(function(x){ return logistic(x, d.aug.ymin, d.aug.ymax, d.aug.k, d.aug.x0); });
251
+ yt = xs.map(function(){ return d.target; });
252
+ var a = parseFloat(slider.value)||0;
253
+ var yi = yb.map(function(v,i){ return blend(v, ya[i], a); });
254
+ Plotly.restyle(gd, {y:[yb]}, [0]); // Baseline
255
+ Plotly.restyle(gd, {y:[yi]}, [1]); // Improved (blended)
256
+ Plotly.restyle(gd, {y:[yt]}, [2]); // Target
257
+ }
258
  var initA = parseFloat(slider.value)||0;
259
+ slider.addEventListener('input', function(e){ applyAlpha(parseFloat(e.target.value)||0); });
260
+ dsSelect.addEventListener('change', function(e){ dsi = parseInt(e.target.value)||0; applyDataset(); });
261
+ setTimeout(function(){ applyDataset(); applyAlpha(initA); }, 0);
262
  })();
263
  </script>
264
  '''
 
271
  .replace('__PLOT__', html_plot)
272
  )
273
 
274
+ with open("../../app/src/content/fragments/line.html", "w", encoding="utf-8") as f:
275
+ f.write(slider_html)
 
 
 
 
 
 
276