diff --git a/.gitattributes b/.gitattributes
index 515987de99bb25c6e2b35d4bee8ce79604bc9086..f01e1072e923fe3946461b03f9e6142e210e6b1c 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,3 +1,4 @@
app/assets/images/*.png filter=lfs diff=lfs merge=lfs -text
app/src/assets/images/*.png filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
+*.wav filter=lfs diff=lfs merge=lfs -text
diff --git a/app/.astro/astro/content.d.ts b/app/.astro/astro/content.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/app/.astro/settings.json b/app/.astro/settings.json
new file mode 100644
index 0000000000000000000000000000000000000000..e5770ce5192fa67124229aa002a7c75b28f18c51
--- /dev/null
+++ b/app/.astro/settings.json
@@ -0,0 +1,5 @@
+{
+ "_variables": {
+ "lastUpdateCheck": 1756115195405
+ }
+}
\ No newline at end of file
diff --git a/app/.astro/types.d.ts b/app/.astro/types.d.ts
index f964fe0cffd88ccb9fcc20bd3c22e848c879d78a..9a2a78c18c6c506ffa571b6f2ece2be80caecfd6 100644
--- a/app/.astro/types.d.ts
+++ b/app/.astro/types.d.ts
@@ -1 +1,2 @@
///
+///
\ No newline at end of file
diff --git a/app/astro.config.mjs b/app/astro.config.mjs
index 48e8ecc82b566f9014cecac959343d0c8dfe0615..e55fe506396e981ff8b95bb8a32f371d5d392593 100644
--- a/app/astro.config.mjs
+++ b/app/astro.config.mjs
@@ -1,8 +1,33 @@
import { defineConfig } from 'astro/config';
+import mdx from '@astrojs/mdx';
+import remarkMath from 'remark-math';
+import rehypeKatex from 'rehype-katex';
+import remarkToc from 'remark-toc';
+import remarkFootnotes from 'remark-footnotes';
+import rehypeSlug from 'rehype-slug';
+import rehypeAutolinkHeadings from 'rehype-autolink-headings';
+import rehypeCitation from 'rehype-citation';
export default defineConfig({
output: 'static',
- integrations: []
+ integrations: [mdx()]
+ ,
+ markdown: {
+ remarkPlugins: [
+ [remarkToc, { heading: 'Table of Contents', maxDepth: 3 }],
+ remarkMath,
+ [remarkFootnotes, { inlineNotes: true }]
+ ],
+ rehypePlugins: [
+ rehypeSlug,
+ [rehypeAutolinkHeadings, { behavior: 'wrap' }],
+ rehypeKatex,
+ [rehypeCitation, {
+ bibliography: 'src/content/bibliography.bib',
+ linkCitations: true
+ }]
+ ]
+ }
});
diff --git a/app/package-lock.json b/app/package-lock.json
index 2bcc135d85d938370cfe95a87eceb791a30100d5..d3c136d5db05c39caf17cc28a3b3f9698a4e9cb5 100644
Binary files a/app/package-lock.json and b/app/package-lock.json differ
diff --git a/app/package.json b/app/package.json
index 2d77f25177e2a3be7c6569e92cc04e85547d3ca6..3f0c365609e8cfb39bb7180ef6a4aef0b7a5072a 100644
Binary files a/app/package.json and b/app/package.json differ
diff --git a/app/src/assets/audio/audio-example.wav b/app/src/assets/audio/audio-example.wav
new file mode 100644
index 0000000000000000000000000000000000000000..d0a5d0390c67bae54736cfcafb9067898e419c99
--- /dev/null
+++ b/app/src/assets/audio/audio-example.wav
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:552f71aef82738f9b5c9f1d6be495e0f83cec0eabf485066628badb3283cb4b8
+size 48830444
diff --git a/app/src/assets/images/banner.png b/app/src/assets/images/banner.png
deleted file mode 100644
index 91748a94b747610d662d8455f7cbc4e04893e693..0000000000000000000000000000000000000000
--- a/app/src/assets/images/banner.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:d53634b257ea2d99635eae2dbcd796abab5083e2bc250400f6f999e206c4f343
-size 189491
diff --git a/app/src/assets/images/moon.svg b/app/src/assets/images/moon.svg
new file mode 100644
index 0000000000000000000000000000000000000000..75e9e94fda037ea1ee819e73921c08d184ee70cf
--- /dev/null
+++ b/app/src/assets/images/moon.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/app/src/assets/images/placeholder.png b/app/src/assets/images/placeholder.png
new file mode 100644
index 0000000000000000000000000000000000000000..7e8dd23f5ef93e175c18d0b041aa4e10fa58d9fd
--- /dev/null
+++ b/app/src/assets/images/placeholder.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c121166b2de694f4bb71dca004c8f413899016751eed5daa1570ba0d5ad9faec
+size 53050
diff --git a/app/src/assets/images/sun.svg b/app/src/assets/images/sun.svg
new file mode 100644
index 0000000000000000000000000000000000000000..4aee836b4d5e69eb95920ab393e8c7e23ed0b50a
--- /dev/null
+++ b/app/src/assets/images/sun.svg
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/assets/images/visual-vocabulary-poster.png b/app/src/assets/images/visual-vocabulary-poster.png
new file mode 100644
index 0000000000000000000000000000000000000000..418527bd50ea464437b59626ad2f8e86dd8ce78a
--- /dev/null
+++ b/app/src/assets/images/visual-vocabulary-poster.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:62f72a7eeabc611d4b312c882589bae9369d49e39dd40e2d17e68c77399efc11
+size 915038
diff --git a/app/src/components/Aside.astro b/app/src/components/Aside.astro
new file mode 100644
index 0000000000000000000000000000000000000000..387b835c8610072e1e041c2d0393e0fe0c664d21
--- /dev/null
+++ b/app/src/components/Aside.astro
@@ -0,0 +1,18 @@
+---
+---
+
+
+
+
+
diff --git a/app/src/components/Footer.astro b/app/src/components/Footer.astro
new file mode 100644
index 0000000000000000000000000000000000000000..7ad8aa30386e51c565370b0c537b53e56822c6c6
--- /dev/null
+++ b/app/src/components/Footer.astro
@@ -0,0 +1,32 @@
+---
+interface Props {
+ citationText: string;
+ bibtex: string;
+}
+const { citationText, bibtex } = Astro.props as Props;
+---
+
+
+
+
+
diff --git a/app/src/components/Header.astro b/app/src/components/Header.astro
new file mode 100644
index 0000000000000000000000000000000000000000..2aad75413e5bc11a7cdd1c0d65b90ffd64da1d12
--- /dev/null
+++ b/app/src/components/Header.astro
@@ -0,0 +1,16 @@
+---
+interface Props {
+ title: string;
+ description?: string;
+}
+const { title, description } = Astro.props as Props;
+---
+
+ {title}
+
+
+ {description &&
{description}
}
+
+
+
+
diff --git a/app/src/components/HtmlFragment.astro b/app/src/components/HtmlFragment.astro
index 79ff0091e6fb95edb16cea600740def60037f409..f4670cb750d0d51a1d82274ae5918581bd6378b0 100644
--- a/app/src/components/HtmlFragment.astro
+++ b/app/src/components/HtmlFragment.astro
@@ -3,7 +3,7 @@ interface Props { src: string }
const { src } = Astro.props as Props;
// Charge tous les fragments .html sous src/fragments/** en tant que string (dev & build)
-const fragments = import.meta.glob('../fragments/**/*.html', { as: 'raw', eager: true }) as Record;
+const fragments = import.meta.glob('../fragments/**/*.html', { query: '?raw', import: 'default', eager: true }) as Record;
function resolveFragment(requested: string): string | null {
// Autorise "banner.html" ou "fragments/banner.html"
@@ -17,11 +17,43 @@ function resolveFragment(requested: string): string | null {
}
const html = resolveFragment(src);
+const mountId = `frag-${Math.random().toString(36).slice(2)}`;
---
{ html ? (
-
+
) : (
) }
+
+
diff --git a/app/src/components/Meta.astro b/app/src/components/Meta.astro
new file mode 100644
index 0000000000000000000000000000000000000000..502eb0a6fcee7a3e5d274d1f54cf21d041d711ab
--- /dev/null
+++ b/app/src/components/Meta.astro
@@ -0,0 +1,32 @@
+---
+interface Props {
+ title: string;
+ authors?: string[];
+ affiliation?: string;
+ published?: string;
+}
+const { title, authors = [], affiliation, published } = Astro.props as Props;
+---
+
+
diff --git a/app/src/components/SeoHead.astro b/app/src/components/SeoHead.astro
new file mode 100644
index 0000000000000000000000000000000000000000..d5558c0acdb2d0959edb3245001f682479f6617e
--- /dev/null
+++ b/app/src/components/SeoHead.astro
@@ -0,0 +1,50 @@
+---
+interface Props {
+ title: string;
+ description?: string;
+ authors?: string[];
+ published?: string;
+ tags?: string[];
+ image?: string; // URL absolue préférée
+}
+
+const { title, description = '', authors = [], published, tags = [], image } = Astro.props as Props;
+
+const url = Astro.url?.toString?.() ?? '';
+const site = (Astro.site ? String(Astro.site) : '') as string;
+
+const ogImage = image && image.length > 0
+ ? (image.startsWith('http') ? image : (site ? new URL(image, site).toString() : image))
+ : undefined;
+
+const jsonLd = {
+ '@context': 'https://schema.org',
+ '@type': 'Article',
+ headline: title,
+ description: description || undefined,
+ datePublished: published || undefined,
+ author: authors.map((name) => ({ '@type': 'Person', name })),
+ keywords: tags.length ? tags.join(', ') : undefined,
+ mainEntityOfPage: url || undefined,
+ image: ogImage ? [ogImage] : undefined,
+};
+---
+
+
+
+
+
+{description && }
+
+{ogImage && }
+{published && }
+{authors.map(a => )}
+
+
+
+{description && }
+{ogImage && }
+
+
+
+
diff --git a/app/src/components/ThemeToggle.astro b/app/src/components/ThemeToggle.astro
new file mode 100644
index 0000000000000000000000000000000000000000..b89d93543c72b6e5cf70fd2539aa37f152ecc717
--- /dev/null
+++ b/app/src/components/ThemeToggle.astro
@@ -0,0 +1,36 @@
+---
+import sunIconUrl from "../assets/images/sun.svg?url";
+import moonIconUrl from "../assets/images/moon.svg?url";
+---
+
+
+
+
+
+
+
diff --git a/app/src/content/bibliography.bib b/app/src/content/bibliography.bib
new file mode 100644
index 0000000000000000000000000000000000000000..09b496cdadfce6ef717f6a86955df7a91ba61a79
--- /dev/null
+++ b/app/src/content/bibliography.bib
@@ -0,0 +1,20 @@
+@inproceedings{vaswani2017attention,
+ title={Attention Is All You Need},
+ author={Vaswani, Ashish and Shazeer, Noam and Parmar, Niki and Uszkoreit, Jakob and Jones, Llion and Gomez, Aidan N and Kaiser, {
+ }Lukasz and Polosukhin, Illia},
+ booktitle={Advances in Neural Information Processing Systems},
+ year={2017}
+}
+
+@article{example2023,
+ title={Example Paper Title},
+ author={Example, A. and Another, A.},
+ journal={Journal of Examples},
+ volume={1},
+ number={1},
+ pages={1--10},
+ year={2023},
+ publisher={Example Publisher}
+}
+
+
diff --git a/app/src/fragments/banner.html b/app/src/fragments/banner.html
index cc2174e1edff8e1c566a0037ca60f5c643036474..8b922c0184ab26c581b1260d6f049e7f5a094be7 100644
--- a/app/src/fragments/banner.html
+++ b/app/src/fragments/banner.html
@@ -1,13 +1 @@
-
-
-
+
\ No newline at end of file
diff --git a/app/src/fragments/bar.html b/app/src/fragments/bar.html
new file mode 100644
index 0000000000000000000000000000000000000000..381be0b01b507e5dcce85ef50986e477f47531fa
--- /dev/null
+++ b/app/src/fragments/bar.html
@@ -0,0 +1,22 @@
+
\ No newline at end of file
diff --git a/app/src/fragments/heatmap.html b/app/src/fragments/heatmap.html
new file mode 100644
index 0000000000000000000000000000000000000000..55f9331941ac9b975e0caf054f1e6fdc00ae04be
--- /dev/null
+++ b/app/src/fragments/heatmap.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/src/fragments/line.html b/app/src/fragments/line.html
new file mode 100644
index 0000000000000000000000000000000000000000..83f354cdbd97d0adedbd380f910bbb2d45728001
--- /dev/null
+++ b/app/src/fragments/line.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/src/layouts/ArticleLayout.astro b/app/src/layouts/ArticleLayout.astro
new file mode 100644
index 0000000000000000000000000000000000000000..84f1b1731c361a37c939da42a41b422f520b83e9
--- /dev/null
+++ b/app/src/layouts/ArticleLayout.astro
@@ -0,0 +1,70 @@
+---
+import type { APIContext } from 'astro';
+
+interface Props {
+ title: string;
+ description?: string;
+ authors?: string[];
+ published?: string; // ISO ou lisible
+ tags?: string[];
+ image?: string; // URL absolue recommandée
+}
+
+const {
+ title,
+ description = '',
+ authors = [],
+ published,
+ tags = [],
+ image,
+} = Astro.props as Props;
+
+const url = Astro.url?.toString?.() ?? '';
+const site = (Astro.site ? String(Astro.site) : '') as string;
+const ogImage = image && image.length > 0
+ ? (image.startsWith('http') ? image : (site ? new URL(image, site).toString() : image))
+ : undefined;
+
+const jsonLd = {
+ '@context': 'https://schema.org',
+ '@type': 'Article',
+ headline: title,
+ description: description || undefined,
+ datePublished: published || undefined,
+ author: authors.map((name) => ({ '@type': 'Person', name })),
+ keywords: tags.length ? tags.join(', ') : undefined,
+ mainEntityOfPage: url || undefined,
+ image: ogImage ? [ogImage] : undefined,
+};
+---
+
+
+
+
+
+ {title}
+ {description && }
+
+
+
+
+
+ {description && }
+
+ {ogImage && }
+ {published && }
+ {authors.map(a => )}
+
+
+
+ {description && }
+ {ogImage && }
+
+
+
+
+
+
+
+
+
diff --git a/app/src/pages/article.mdx b/app/src/pages/article.mdx
new file mode 100644
index 0000000000000000000000000000000000000000..8c7ba4ced5f117f33530c7fa778e98666fe04101
--- /dev/null
+++ b/app/src/pages/article.mdx
@@ -0,0 +1,195 @@
+---
+title: "The science Template:\nCraft Beautiful Blogs"
+description: "A modern, MDX-first research article template with math, citations, and interactive figures."
+authors:
+ - "Hynek Kydlíček"
+ - "Guilherme Penedo"
+ - "Clémentine Fourier"
+affiliation: "Hugging Face"
+published: "Feb 19, 2025"
+tags:
+ - research
+ - template
+ogImage: "https://example.com/your-og-image.png"
+---
+
+import HtmlFragment from "../components/HtmlFragment.astro";
+import { Image } from 'astro:assets';
+import placeholder from "../assets/images/placeholder.png";
+import audioDemo from "../assets/audio/audio-example.wav";
+import Aside from "../components/Aside.astro";
+import visualPoster from "../assets/images/visual-vocabulary-poster.png";
+
+
+Welcome to this single-page research article template built with Astro and MDX. It’s designed to help you write clear, modern, and interactive technical articles with minimal setup. Whether you cover machine learning, data science, physics, or software topics, this template keeps the authoring flow simple while offering robust features out of the box.
+
+Reading time: 10–15 minutes.
+
+In this guide, you’ll learn how to install the template, write content (math, citations, images, code, asides, interactive fragments), customize styles and behavior, and follow a few best practices for publishing.
+
+## Features
+
+- Math via KaTeX (remark-math/rehype-katex)
+- Citations and footnotes
+- Responsive images with `astro:assets`
+- Anchored headings and a generated table of contents
+- Dark/light theme toggle
+- HTML fragments for self-contained visualizations (e.g., Plotly)
+
+## Getting Started
+
+Installation:
+
+```bash
+npm install
+npm run dev
+```
+
+Build for production:
+
+```bash
+npm run build
+```
+
+Deployment: serve the `dist/` directory on any static host. A Docker/Nginx setup is included and works well on Spaces or similar environments.
+
+Large assets: track binaries (e.g., `.png`, `.wav`) with Git LFS to keep the repository lean. This project is preconfigured to store such files via LFS.
+
+## Writing Your Content
+
+Author content in MDX. Below are minimal examples for the core elements.
+
+### Math
+
+Inline example: $x^2 + y^2 = z^2$.
+
+Block (scaled dot-product attention):
+
+$$
+\mathrm{Attention}(Q, K, V) = \mathrm{softmax}\!\left(\frac{QK^{\top}}{\sqrt{d_k}}\right) V
+$$
+
+```mdx
+Inline: $x^2 + y^2 = z^2$
+
+Block:
+$$
+\mathrm{Attention}(Q,K,V)=\mathrm{softmax}\!\left(\frac{QK^\top}{\sqrt{d_k}}\right) V
+$$
+```
+
+### Images
+
+Responsive image using `astro:assets`:
+
+
+
+```mdx
+import { Image } from 'astro:assets'
+import myImage from '../assets/images/placeholder.png'
+
+
+```
+
+### Citations and notes
+
+A short citation [@example2023]. A footnote[^note].
+
+[^note]: Example footnote.
+
+```mdx
+As shown in [@example2023]. A footnote[^note].
+
+[^note]: Example footnote.
+```
+
+### Interactive fragments
+
+Plotly example (line):
+
+
+
+
+
+```mdx
+import HtmlFragment from '../components/HtmlFragment.astro'
+
+
+```
+
+### Asides
+
+
+ This paragraph presents a key idea concisely.
+
+ Side note for brief context or a definition.
+
+
+
+```mdx
+import Aside from '../components/Aside.astro'
+
+
+ Main paragraph with the core idea.
+ Short side note.
+
+```
+
+### Minimal table
+
+| Method | Score |
+|---|---|
+| A | 0.78 |
+| B | 0.86 |
+
+```mdx
+| Method | Score |
+| --- | --- |
+| A | 0.78 |
+| B | 0.86 |
+```
+
+### Audio
+
+
+ Your browser does not support the audio element.
+
+
+```mdx
+import audioDemo from '../assets/audio/audio-example.wav'
+
+
+ Your browser does not support the audio element.
+
+```
+
+## Advanced Customization
+
+- Styling: customize `src/styles/global.scss` for layout, typography, color scheme, and dark mode variables.
+- Headings/TOC: tweak the TOC builder in `src/pages/index.astro` to match your preferred levels and behavior.
+- Components: extend `components/` with bespoke MDX-friendly blocks (callouts, figures, widgets).
+- Fragments: generate self-contained HTML fragments (e.g., Plotly) and include them via ` `.
+
+## Best Practices
+
+- Keep sections short and focused.
+- Prefer vector or optimized images; use `astro:assets` for responsive delivery.
+- Annotate figures and code minimally but clearly.
+- Use math sparingly; explain notation on first use.
+
+### Choosing the right chart
+
+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.
+
+
+
+
+ Visual Vocabulary: a handy reference to select chart types by purpose (comparison, distribution, part-to-whole, correlation, and more).
+
+
+
+
+## Conclusions
+
+This template provides a practical baseline for writing and sharing technical articles with math, citations, and interactive figures. Start simple, iterate on structure and style, and keep content maintainable for future readers and collaborators.
+
diff --git a/app/src/pages/index.astro b/app/src/pages/index.astro
index 630c04ecb4e8af91a765ba6639bdeb28e23bea96..9d97f89511c86eb70b45bca9393bb6101b4e9b41 100644
--- a/app/src/pages/index.astro
+++ b/app/src/pages/index.astro
@@ -1,157 +1,194 @@
---
-import { Image } from 'astro:assets';
-import banner from "../assets/images/banner.png";
-import HtmlFragment from "../components/HtmlFragment.astro";
-const title = 'The Distill Blog Template (Astro)';
+import Article, { frontmatter as articleFM } from './article.mdx';
+import Meta from '../components/Meta.astro';
+import HtmlFragment from '../components/HtmlFragment.astro';
+import Footer from '../components/Footer.astro';
+import ThemeToggle from '../components/ThemeToggle.astro';
+import SeoHead from '../components/SeoHead.astro';
+import ogDefault from '../assets/images/visual-vocabulary-poster.png';
+import '../styles/global.scss';
+const docTitle = articleFM?.title ?? 'Untitled article';
+// Autoriser un retour à la ligne dans le titre via "\n" ou sauts de ligne YAML
+const docTitleHtml = (articleFM?.title ?? 'Untitled article')
+ .replace(/\\n/g, ' ')
+ .replace(/\n/g, ' ');
+const description = articleFM?.description ?? '';
+const authors = articleFM?.authors ?? [];
+const published = articleFM?.published ?? undefined;
+const tags = articleFM?.tags ?? [];
+// Prefer ogImage from frontmatter if provided
+const fmOg = articleFM?.ogImage as string | undefined;
+const imageAbs = fmOg && fmOg.startsWith('http')
+ ? fmOg
+ : (Astro.site ? new URL((fmOg ?? ogDefault.src), Astro.site).toString() : (fmOg ?? ogDefault.src));
---
-
- {title}
+ {docTitle}
+
+
-
-
-
-
-
-
-
- The Distill Template: Craft Beautiful Blogs
-
-
-
-
-
-
-
-
-
-
It's nice to have a cute interactive banner!
-
-
-
-
-
-
-
-
- Welcome to the Distill Blog Template! This framework is based on the distill.pub design and has been adapted to make it easy for you to create beautiful, interactive technical blogs.
- Reading time: 10-15 minutes.
- This template includes support for mathematics, interactive visualizations, citations, footnotes, and more.
-
- Getting Started with the Template
- Installation and Setup
- npm install\nnpm run dev
-
- Mathematical Equations
- You can include inline math using x^2 + y^2 = z^2.
-
- Figures and Images
-
-
- Caption for your figure
-
-
- Tables
-
-
- Model Accuracy Speed
-
-
- Model A 95% Fast
- Model B 98% Medium
- Model C 99% Slow
-
-
-
- Interactive Components
-
-
-
-
-
-
-
- Citation
- "The Distill Blog Template", 2025.
-
-
-
-
-
+
+
+
+
-
+ }
+ };
+ window.addEventListener('scroll', onScroll);
+ onScroll();
+ };
+
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', buildTOC, { once: true });
+ } else { buildTOC(); }
+
diff --git a/app/src/styles/global.scss b/app/src/styles/global.scss
new file mode 100644
index 0000000000000000000000000000000000000000..004fb51d39e40bdf1c1aef6dcbafa32a035f6d01
--- /dev/null
+++ b/app/src/styles/global.scss
@@ -0,0 +1,243 @@
+// ============================================================================
+// Theme Variables (inspired by Distill)
+// ============================================================================
+:root {
+ --distill-gray: rgb(107, 114, 128);
+ --distill-gray-light: rgb(185, 185, 185);
+ --distill-gray-lighter: rgb(228, 228, 228);
+ --distill-gray-lightest: rgb(245, 245, 245);
+ --distill-blue: #007BFF;
+
+ --text-color: rgba(0,0,0,.85);
+ --muted-color: rgba(0,0,0,.6);
+ --border-color: rgba(0,0,0,.1);
+
+ /* Light surfaces & links */
+ --surface-bg: #fafafa;
+ --code-bg: #f6f8fa;
+ --link-underline: rgba(0,0,0,.4);
+ --link-underline-hover: rgba(0,0,0,.8);
+
+ --spacing-1: 8px;
+ --spacing-2: 12px;
+ --spacing-3: 16px;
+ --spacing-4: 24px;
+ --spacing-5: 32px;
+}
+// Theme tokens for dark mode
+[data-theme="dark"] {
+ --text-color: rgba(255,255,255,.9);
+ --muted-color: rgba(255,255,255,.7);
+ --border-color: rgba(255,255,255,.15);
+ --surface-bg: #12151b;
+ --code-bg: #12151b;
+ --link-underline: rgba(255,255,255,.5);
+ --link-underline-hover: rgba(255,255,255,.9);
+ color-scheme: dark;
+ background: #0f1115;
+}
+/* Dark-mode form tweak */
+[data-theme="dark"] .plotly_input_container > select { background-color: #1a1f27; border-color: var(--border-color); color: var(--text-color); }
+
+[data-theme="dark"] .plot-card { background: #12151b; border-color: rgba(255,255,255,.15); }
+[data-theme="dark"] .right-aside .aside-card { background: #12151b; border-color: rgba(255,255,255,.15); }
+[data-theme="dark"] .content-grid main pre { background: #12151b; border-color: rgba(255,255,255,.15); }
+[data-theme="dark"] .toc nav { border-left-color: rgba(255,255,255,.15); }
+[data-theme="dark"] .distill-footer { border-top-color: rgba(255,255,255,.15); }
+[data-theme="dark"] .citation-text,
+[data-theme="dark"] .citation-bibtex { background: #12151b; border-color: rgba(255,255,255,.15); color: var(--text-color); }
+
+// ============================================================================
+// Base / Reset
+// ============================================================================
+html { box-sizing: border-box; }
+*, *::before, *::after { box-sizing: inherit; }
+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); }
+
+/* Avoid constraining inside grid; scope container sizing elsewhere if needed */
+/* main { max-width: 980px; margin: 24px auto; padding: 16px; } */
+
+// ============================================================================
+// Typography (inspired by Distill)
+// ============================================================================
+html { font-size: 14px; line-height: 1.6; }
+@media (min-width: 768px) { html { font-size: 16px; } }
+@media (min-width: 1024px) { html { font-size: 17px; } }
+
+.content-grid main { color: var(--text-color); }
+.content-grid main p { margin: 0 0 var(--spacing-3); }
+
+.content-grid main h2 {
+ font-weight: 600;
+ font-size: clamp(22px, 2.6vw, 32px);
+ line-height: 1.2;
+ margin: var(--spacing-5) 0 var(--spacing-3);
+ padding-bottom: var(--spacing-2);
+ border-bottom: 1px solid var(--border-color);
+}
+
+.content-grid main h3 {
+ font-weight: 700;
+ font-size: clamp(18px, 2.1vw, 22px);
+ line-height: 1.25;
+ margin: var(--spacing-4) 0 var(--spacing-2);
+}
+
+.content-grid main h4 {
+ font-weight: 600;
+ text-transform: uppercase;
+ font-size: 14px;
+ line-height: 1.2;
+ margin: var(--spacing-3) 0 var(--spacing-2);
+}
+
+.content-grid main a { color: inherit; text-decoration: none; border-bottom: 1px solid var(--link-underline); }
+.content-grid main a:hover { border-bottom: 1px solid var(--link-underline-hover); }
+
+/* Ne pas souligner les liens de titres dans l'article (pas le TOC) */
+.content-grid main h2 a,
+.content-grid main h3 a,
+.content-grid main h4 a { border-bottom: none; text-decoration: none; }
+.content-grid main h2 a:hover,
+.content-grid main h3 a:hover,
+.content-grid main h4 a:hover { border-bottom: none; text-decoration: none; }
+
+.content-grid main ul,
+.content-grid main ol { padding-left: 24px; margin: 0 0 var(--spacing-3); }
+.content-grid main li { margin-bottom: var(--spacing-2); }
+.content-grid main li:last-child { margin-bottom: 0; }
+
+.content-grid main blockquote {
+ border-left: 2px solid var(--border-color);
+ padding-left: var(--spacing-4);
+ font-style: italic;
+ color: var(--muted-color);
+ margin: var(--spacing-4) 0;
+}
+
+.content-grid main pre { background: var(--code-bg); border: 1px solid var(--border-color); border-radius: 6px; padding: var(--spacing-3); font-size: 14px; overflow: auto; }
+.content-grid main code { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
+
+.content-grid main table { border-collapse: collapse; width: 100%; margin: 0 0 var(--spacing-4); }
+.content-grid main th, .content-grid main td { border-bottom: 1px solid var(--border-color); padding: 6px 8px; text-align: left; font-size: 15px; }
+.content-grid main thead th { border-bottom: 1px solid var(--border-color); }
+
+.content-grid main hr { border: none; border-bottom: 1px solid var(--border-color); margin: var(--spacing-5) 0; }
+
+// ============================================================================
+// Media / Figures
+// ============================================================================
+:where(picture, img) {
+ max-width: 100%;
+ width: 100%;
+ height: auto;
+ display: block;
+}
+
+figure { margin: 16px 0; }
+figcaption { color: var(--muted-color); font-size: 12px; }
+
+// ============================================================================
+// Hero (full-bleed)
+// ============================================================================
+.hero { width: 100%; padding: 48px 16px 16px; text-align: center; }
+.hero-title { font-size: clamp(28px, 4vw, 48px); font-weight: 800; line-height: 1.1; margin: 0 0 8px;
+
+ max-width: 60%;
+ margin: auto;}
+.hero-banner { max-width: 980px; margin: 0 auto; }
+.hero-desc { color: var(--muted-color); font-style: italic; margin: 0 0 16px 0; }
+
+// ============================================================================
+// Meta (byline-like header)
+// ============================================================================
+
+.meta {
+ border-top: 1px solid var(--border-color);
+ border-bottom: 1px solid var(--border-color);
+ padding: 1rem 0;
+ font-size: 0.9rem;
+ line-height: 1.8em;
+ }
+ .meta-container {
+ max-width: 720px;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ margin: 0 auto;
+ gap: 8px;
+ }
+ .meta-container-cell {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ }
+ .meta-container-cell h3 {
+ margin: 0;
+ font-size: 12px;
+ font-weight: 400;
+ color: var(--muted-color);
+ text-transform: uppercase;
+ letter-spacing: .02em;
+ }
+ .meta-container-cell p { margin: 0; }
+
+// ============================================================================
+// Layout – 3-column grid (TOC / Article / Aside)
+// ============================================================================
+.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; }
+.content-grid > main { max-width: 100%; margin: 0; padding: 0; }
+
+// TOC (left column)
+.toc { position: sticky; top: 24px; }
+.toc nav { border-left: 1px solid var(--border-color); padding-left: 16px; font-size: 13px; }
+.toc .title { font-weight: 600; font-size: 14px; margin-bottom: 8px; }
+
+// Hide in-article TOC (duplicated by sticky aside)
+main > nav:first-of-type { display: none; }
+
+// TOC look & feel
+.toc nav ul { margin: 0 0 6px; padding-left: 1em; }
+.toc nav li { list-style: none; margin: .25em 0; }
+.toc nav a { color: var(--text-color); text-decoration: none; border-bottom: none; }
+.toc nav > ul > li > a { font-weight: 700; }
+.toc nav a:hover { text-decoration: underline solid var(--muted-color); }
+.toc nav a.active { text-decoration: underline; }
+
+// Right aside (notes)
+.right-aside { position: sticky; top: 24px; }
+.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); }
+
+// Responsive – collapse to single column
+@media (max-width: 1100px) {
+ .content-grid { grid-template-columns: 1fr; }
+ .toc { position: static; }
+ .right-aside { display: none; }
+ main > nav:first-of-type { display: block; }
+}
+
+// ============================================================================
+// Plotly – fragments & controls
+// ============================================================================
+.plot-card { background: var(--code-bg); border: 1px solid var(--border-color); border-radius: 10px; padding: 8px; margin: 8px 0; }
+.plotly-graph-div { width: 100% !important; min-height: 320px; }
+@media (max-width: 768px) { .plotly-graph-div { min-height: 260px; } }
+[id^="plot-"] { display: flex; flex-direction: column; align-items: center; gap: 15px; }
+.plotly_caption { font-style: italic; margin-top: 10px; }
+.plotly_controls { display: flex; flex-wrap: wrap; justify-content: center; gap: 30px; }
+.plotly_input_container { display: flex; align-items: center; flex-direction: column; gap: 10px; }
+.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); }
+.plotly_slider { display: flex; align-items: center; gap: 10px; }
+.plotly_slider > input[type="range"] { -webkit-appearance: none; appearance: none; height: 2px; background: var(--distill-gray-light); border-radius: 5px; outline: none; }
+.plotly_slider > input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; width: 18px; height: 18px; border-radius: 50%; background: var(--distill-blue); cursor: pointer; }
+.plotly_slider > input[type="range"]::-moz-range-thumb { width: 18px; height: 18px; border-radius: 50%; background: var(--distill-blue); cursor: pointer; }
+.plotly_slider > span { font-size: 14px; line-height: 1.6em; min-width: 16px; }
+
+// ============================================================================
+// Theme Toggle button (moved from component)
+// ============================================================================
+#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; }
+#theme-toggle .icon.dark { display: none; }
+[data-theme="dark"] #theme-toggle .icon.light { display: none; }
+[data-theme="dark"] #theme-toggle .icon.dark { display: inline; }
+[data-theme="dark"] #theme-toggle .icon { filter: invert(1) brightness(1.2); }
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000000000000000000000000000000000000..de9a02e1ffe9c2468dbc6da2d3ae446484b54a36
Binary files /dev/null and b/package-lock.json differ
diff --git a/package.json b/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..13c5d6ffab58b848d4ae5fd237dc9801a51bdafc
Binary files /dev/null and b/package.json differ
diff --git a/python/fragments/banner.py b/python/fragments/banner.py
index 39b7a3ff31f852afafbbd505a025fb10d1fbfee9..3c1f01aac82bfc998ee432e21fc2432777b926fe 100644
--- a/python/fragments/banner.py
+++ b/python/fragments/banner.py
@@ -2,50 +2,92 @@ import plotly.graph_objects as go
import numpy as np
import pandas as pd
-# Paramètres de l'ellipse (galaxie) et échantillonnage
-num_points = 512
-cx, cy = 1.5, 0.5 # centre (au milieu des ranges actuels)
-a, b = 1.3, 0.45 # demi‑axes (ellipse horizontale)
+# Paramètres de la scène (mêmes ranges que l'intégration Astro)
+cx, cy = 1.5, 0.5 # centre
+a, b = 1.3, 0.45 # étendue max en x/y (ellipse pour l'anisotropie)
-# Échantillonnage en coordonnées polaires puis transformation elliptique
-# r concentré vers le centre (alpha>1) pour densité centrale façon galaxie
-theta = 2*np.pi*np.random.rand(num_points)
-r_base = np.random.rand(num_points)**2
+# Paramètres de la galaxie en spirale
+num_points = 3000 # plus de dots
+num_arms = 3 # nombre de bras spiraux
+num_turns = 2.1 # nombre de tours par bras
+angle_jitter = 0.12 # écart angulaire pour évaser les bras
+pos_noise = 0.015 # bruit de position global
-# Légère irrégularité pour un aspect plus naturel
-noise_x = 0.015*np.random.randn(num_points)
-noise_y = 0.015*np.random.randn(num_points)
+# Génération des points sur des bras spiraux (spirale d'Archimède)
+t = np.random.rand(num_points) * (2 * np.pi * num_turns) # progression le long du bras
+arm_indices = np.random.randint(0, num_arms, size=num_points)
+arm_offsets = arm_indices * (2 * np.pi / num_arms)
-x = cx + a * r_base * np.cos(theta) + noise_x
-y = cy + b * r_base * np.sin(theta) + noise_y
+theta = t + arm_offsets + np.random.randn(num_points) * angle_jitter
-# Taille plus grande au centre, plus petite en périphérie
-# On conserve la même échelle finale qu'avant: (valeur in [0,1]) -> (val+1)*5
-z_raw = 1 - r_base # 1 au centre, 0 au bord
-sizes = (z_raw + 1) * 5 # 5..10, comme précédemment
+# Rayon normalisé (0->centre, 1->bord). Puissance <1 pour densifier le centre
+r_norm = (t / (2 * np.pi * num_turns)) ** 0.9
+
+# Bruit radial/lateral qui augmente légèrement avec le rayon
+noise_x = pos_noise * (0.8 + 0.6 * r_norm) * np.random.randn(num_points)
+noise_y = pos_noise * (0.8 + 0.6 * r_norm) * np.random.randn(num_points)
+
+# Projection elliptique
+x_spiral = cx + a * r_norm * np.cos(theta) + noise_x
+y_spiral = cy + b * r_norm * np.sin(theta) + noise_y
+
+# Bulbe central (points supplémentaires très proches du centre)
+bulge_points = int(0.18 * num_points)
+phi_b = 2 * np.pi * np.random.rand(bulge_points)
+r_b = (np.random.rand(bulge_points) ** 2.2) * 0.22 # bulbe compact
+noise_x_b = (pos_noise * 0.6) * np.random.randn(bulge_points)
+noise_y_b = (pos_noise * 0.6) * np.random.randn(bulge_points)
+x_bulge = cx + a * r_b * np.cos(phi_b) + noise_x_b
+y_bulge = cy + b * r_b * np.sin(phi_b) + noise_y_b
+
+# Concaténation
+x = np.concatenate([x_spiral, x_bulge])
+y = np.concatenate([y_spiral, y_bulge])
+
+# Intensité centrale (pour tailles/couleurs). 1 au centre, ~0 au bord
+z_spiral = 1 - r_norm
+z_bulge = 1 - (r_b / max(r_b.max(), 1e-6)) # bulbe très lumineux
+z_raw = np.concatenate([z_spiral, z_bulge])
+
+# Tailles: conserver l'échelle 5..10 pour cohérence
+sizes = (z_raw + 1) * 5
+
+# Filtrer les petits points proches du centre (esthétique du bulbe)
+# - on calcule le rayon elliptique normalisé
+# - on retire les points de petite taille situés trop près du centre
+central_radius_cut = 0.18
+min_size_center = 7.5
+r_total = np.sqrt(((x - cx) / a) ** 2 + ((y - cy) / b) ** 2)
+mask = ~((r_total <= central_radius_cut) & (sizes < min_size_center))
+
+# Appliquer le masque
+x = x[mask]
+y = y[mask]
+z_raw = z_raw[mask]
+sizes = sizes[mask]
df = pd.DataFrame({
"x": x,
"y": y,
- "z": sizes, # réutilisé pour size+color comme avant
+ "z": sizes, # réutilisé pour size+color comme avant
})
def get_label(z):
- if z<0.25:
+ if z < 0.25:
return "smol dot"
- if z<0.5:
+ if z < 0.5:
return "ok-ish dot"
- if z<0.75:
+ if z < 0.75:
return "a dot"
else:
return "biiig dot"
-# Les labels sont fondés sur l'intensité centrale (z_raw en [0,1])
+# Labels basés sur l'intensité centrale
df["label"] = pd.Series(z_raw).apply(get_label)
fig = go.Figure()
-fig.add_trace(go.Scatter(
+fig.add_trace(go.Scattergl(
x=df['x'],
y=df['y'],
mode='markers',
@@ -53,19 +95,18 @@ fig.add_trace(go.Scatter(
size=df['z'],
color=df['z'],
colorscale=[
- [0, 'rgb(78, 165, 183)'], # Light blue
- [0.5, 'rgb(206, 192, 250)'], # Purple
- [1, 'rgb(232, 137, 171)'] # Pink
+ [0, 'rgb(78, 165, 183)'],
+ [0.5, 'rgb(206, 192, 250)'],
+ [1, 'rgb(232, 137, 171)']
],
opacity=0.9,
),
- customdata=df[["label"]],
+ customdata=df[["label"]],
hovertemplate="Dot category: %{customdata[0]}",
hoverlabel=dict(namelength=0),
showlegend=False
))
-
fig.update_layout(
autosize=True,
paper_bgcolor='rgba(0,0,0,0)',
@@ -88,13 +129,15 @@ fig.update_layout(
)
)
-fig.show()
+# fig.show()
-fig.write_html("../../src/fragments/banner.html",
- include_plotlyjs=False,
- full_html=False,
- config={
- 'displayModeBar': False,
- 'responsive': True,
- 'scrollZoom': False,
- })
\ No newline at end of file
+fig.write_html(
+ "../app/src/fragments/banner.html",
+ include_plotlyjs=False,
+ full_html=False,
+ config={
+ 'displayModeBar': False,
+ 'responsive': True,
+ 'scrollZoom': False,
+ }
+)
\ No newline at end of file
diff --git a/python/fragments/bar.py b/python/fragments/bar.py
index 852752bc75e9d06434787da0e3c98890cf7a5a3a..441b7ba9cda2ee444fa4abd74932207d59aead20 100644
--- a/python/fragments/bar.py
+++ b/python/fragments/bar.py
@@ -105,27 +105,12 @@ post_script = """
})();
"""
-html = pio.to_html(
- fig,
- include_plotlyjs=False,
- full_html=False,
- post_script=post_script,
- config={
- "displayModeBar": False,
- "responsive": True,
- "scrollZoom": False,
- "doubleClick": False,
- "modeBarButtonsToRemove": [
- "zoom2d", "pan2d", "select2d", "lasso2d",
- "zoomIn2d", "zoomOut2d", "autoScale2d", "resetScale2d",
- "toggleSpikelines"
- ],
- },
-)
-
-output_path = os.path.join(os.path.dirname(__file__), "fragments", "bar.html")
-os.makedirs(os.path.dirname(output_path), exist_ok=True)
-with open(output_path, "w", encoding="utf-8") as f:
- f.write(html)
-
+fig.write_html("../app/src/fragments/bar.html",
+ include_plotlyjs=False,
+ full_html=False,
+ config={
+ 'displayModeBar': False,
+ 'responsive': True,
+ 'scrollZoom': False,
+ })
diff --git a/python/fragments/heatmap.py b/python/fragments/heatmap.py
index 9745b4cc796a1dae50a595e9e8ea9e94e94c7726..4d9cb1e995da5e155821aa0ce79bc910d4aa0980 100644
--- a/python/fragments/heatmap.py
+++ b/python/fragments/heatmap.py
@@ -114,9 +114,12 @@ html = pio.to_html(
},
)
-output_path = os.path.join(os.path.dirname(__file__), "fragments", "heatmap.html")
-os.makedirs(os.path.dirname(output_path), exist_ok=True)
-with open(output_path, "w", encoding="utf-8") as f:
- f.write(html)
-
+fig.write_html("../app/src/fragments/heatmap.html",
+ include_plotlyjs=False,
+ full_html=False,
+ config={
+ 'displayModeBar': False,
+ 'responsive': True,
+ 'scrollZoom': False,
+ })
diff --git a/python/fragments/line.py b/python/fragments/line.py
index 91d5ba8c5119ea71f89726b246be315f07581baa..d657a03f53ca757bc54e827cd28f25d1b51b8873 100644
--- a/python/fragments/line.py
+++ b/python/fragments/line.py
@@ -237,7 +237,12 @@ slider_html = (slider_tpl
.replace('__PLOT__', html_plot)
)
-with open(output_path, 'w', encoding='utf-8') as f:
- f.write(slider_html)
-
+fig.write_html("../app/src/fragments/line.html",
+ include_plotlyjs=False,
+ full_html=False,
+ config={
+ 'displayModeBar': False,
+ 'responsive': True,
+ 'scrollZoom': False,
+ })
diff --git a/python/poetry.lock b/python/poetry.lock
new file mode 100644
index 0000000000000000000000000000000000000000..55dc5b270a044b51d1def9be28f25d940a66265f
--- /dev/null
+++ b/python/poetry.lock
@@ -0,0 +1,511 @@
+# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand.
+
+[[package]]
+name = "certifi"
+version = "2025.8.3"
+description = "Python package for providing Mozilla's CA Bundle."
+optional = false
+python-versions = ">=3.7"
+groups = ["main"]
+files = [
+ {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"},
+ {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"},
+]
+
+[[package]]
+name = "charset-normalizer"
+version = "3.4.3"
+description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
+optional = false
+python-versions = ">=3.7"
+groups = ["main"]
+files = [
+ {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"},
+ {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"},
+ {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601"},
+ {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c"},
+ {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2"},
+ {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0"},
+ {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0"},
+ {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0"},
+ {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a"},
+ {file = "charset_normalizer-3.4.3-cp310-cp310-win32.whl", hash = "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f"},
+ {file = "charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669"},
+ {file = "charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b"},
+ {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64"},
+ {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91"},
+ {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f"},
+ {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07"},
+ {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30"},
+ {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14"},
+ {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c"},
+ {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae"},
+ {file = "charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849"},
+ {file = "charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c"},
+ {file = "charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1"},
+ {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884"},
+ {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018"},
+ {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392"},
+ {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f"},
+ {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154"},
+ {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491"},
+ {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93"},
+ {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f"},
+ {file = "charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37"},
+ {file = "charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc"},
+ {file = "charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe"},
+ {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8"},
+ {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9"},
+ {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31"},
+ {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f"},
+ {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927"},
+ {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9"},
+ {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5"},
+ {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc"},
+ {file = "charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce"},
+ {file = "charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef"},
+ {file = "charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15"},
+ {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db"},
+ {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d"},
+ {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096"},
+ {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa"},
+ {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049"},
+ {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0"},
+ {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92"},
+ {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16"},
+ {file = "charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce"},
+ {file = "charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c"},
+ {file = "charset_normalizer-3.4.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c"},
+ {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b"},
+ {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4"},
+ {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b"},
+ {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9"},
+ {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb"},
+ {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a"},
+ {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942"},
+ {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b"},
+ {file = "charset_normalizer-3.4.3-cp38-cp38-win32.whl", hash = "sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557"},
+ {file = "charset_normalizer-3.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40"},
+ {file = "charset_normalizer-3.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05"},
+ {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e"},
+ {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99"},
+ {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7"},
+ {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7"},
+ {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19"},
+ {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312"},
+ {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc"},
+ {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34"},
+ {file = "charset_normalizer-3.4.3-cp39-cp39-win32.whl", hash = "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432"},
+ {file = "charset_normalizer-3.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca"},
+ {file = "charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a"},
+ {file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"},
+]
+
+[[package]]
+name = "idna"
+version = "3.10"
+description = "Internationalized Domain Names in Applications (IDNA)"
+optional = false
+python-versions = ">=3.6"
+groups = ["main"]
+files = [
+ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"},
+ {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
+]
+
+[package.extras]
+all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
+
+[[package]]
+name = "markdown"
+version = "3.8.2"
+description = "Python implementation of John Gruber's Markdown."
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "markdown-3.8.2-py3-none-any.whl", hash = "sha256:5c83764dbd4e00bdd94d85a19b8d55ccca20fe35b2e678a1422b380324dd5f24"},
+ {file = "markdown-3.8.2.tar.gz", hash = "sha256:247b9a70dd12e27f67431ce62523e675b866d254f900c4fe75ce3dda62237c45"},
+]
+
+[package.extras]
+docs = ["mdx_gh_links (>=0.2)", "mkdocs (>=1.6)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"]
+testing = ["coverage", "pyyaml"]
+
+[[package]]
+name = "numpy"
+version = "2.2.6"
+description = "Fundamental package for array computing in Python"
+optional = false
+python-versions = ">=3.10"
+groups = ["main"]
+markers = "python_version == \"3.10\""
+files = [
+ {file = "numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb"},
+ {file = "numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90"},
+ {file = "numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163"},
+ {file = "numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf"},
+ {file = "numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83"},
+ {file = "numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915"},
+ {file = "numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680"},
+ {file = "numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289"},
+ {file = "numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d"},
+ {file = "numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3"},
+ {file = "numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae"},
+ {file = "numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a"},
+ {file = "numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42"},
+ {file = "numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491"},
+ {file = "numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a"},
+ {file = "numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf"},
+ {file = "numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1"},
+ {file = "numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab"},
+ {file = "numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47"},
+ {file = "numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303"},
+ {file = "numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff"},
+ {file = "numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c"},
+ {file = "numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3"},
+ {file = "numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282"},
+ {file = "numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87"},
+ {file = "numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249"},
+ {file = "numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49"},
+ {file = "numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de"},
+ {file = "numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4"},
+ {file = "numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2"},
+ {file = "numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84"},
+ {file = "numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b"},
+ {file = "numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d"},
+ {file = "numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566"},
+ {file = "numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f"},
+ {file = "numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f"},
+ {file = "numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868"},
+ {file = "numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d"},
+ {file = "numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd"},
+ {file = "numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c"},
+ {file = "numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6"},
+ {file = "numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda"},
+ {file = "numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40"},
+ {file = "numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8"},
+ {file = "numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f"},
+ {file = "numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa"},
+ {file = "numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571"},
+ {file = "numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1"},
+ {file = "numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff"},
+ {file = "numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06"},
+ {file = "numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d"},
+ {file = "numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db"},
+ {file = "numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543"},
+ {file = "numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00"},
+ {file = "numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd"},
+]
+
+[[package]]
+name = "numpy"
+version = "2.3.2"
+description = "Fundamental package for array computing in Python"
+optional = false
+python-versions = ">=3.11"
+groups = ["main"]
+markers = "python_version >= \"3.11\""
+files = [
+ {file = "numpy-2.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:852ae5bed3478b92f093e30f785c98e0cb62fa0a939ed057c31716e18a7a22b9"},
+ {file = "numpy-2.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a0e27186e781a69959d0230dd9909b5e26024f8da10683bd6344baea1885168"},
+ {file = "numpy-2.3.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:f0a1a8476ad77a228e41619af2fa9505cf69df928e9aaa165746584ea17fed2b"},
+ {file = "numpy-2.3.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cbc95b3813920145032412f7e33d12080f11dc776262df1712e1638207dde9e8"},
+ {file = "numpy-2.3.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75018be4980a7324edc5930fe39aa391d5734531b1926968605416ff58c332d"},
+ {file = "numpy-2.3.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20b8200721840f5621b7bd03f8dcd78de33ec522fc40dc2641aa09537df010c3"},
+ {file = "numpy-2.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f91e5c028504660d606340a084db4b216567ded1056ea2b4be4f9d10b67197f"},
+ {file = "numpy-2.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:fb1752a3bb9a3ad2d6b090b88a9a0ae1cd6f004ef95f75825e2f382c183b2097"},
+ {file = "numpy-2.3.2-cp311-cp311-win32.whl", hash = "sha256:4ae6863868aaee2f57503c7a5052b3a2807cf7a3914475e637a0ecd366ced220"},
+ {file = "numpy-2.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:240259d6564f1c65424bcd10f435145a7644a65a6811cfc3201c4a429ba79170"},
+ {file = "numpy-2.3.2-cp311-cp311-win_arm64.whl", hash = "sha256:4209f874d45f921bde2cff1ffcd8a3695f545ad2ffbef6d3d3c6768162efab89"},
+ {file = "numpy-2.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bc3186bea41fae9d8e90c2b4fb5f0a1f5a690682da79b92574d63f56b529080b"},
+ {file = "numpy-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f4f0215edb189048a3c03bd5b19345bdfa7b45a7a6f72ae5945d2a28272727f"},
+ {file = "numpy-2.3.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b1224a734cd509f70816455c3cffe13a4f599b1bf7130f913ba0e2c0b2006c0"},
+ {file = "numpy-2.3.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3dcf02866b977a38ba3ec10215220609ab9667378a9e2150615673f3ffd6c73b"},
+ {file = "numpy-2.3.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:572d5512df5470f50ada8d1972c5f1082d9a0b7aa5944db8084077570cf98370"},
+ {file = "numpy-2.3.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8145dd6d10df13c559d1e4314df29695613575183fa2e2d11fac4c208c8a1f73"},
+ {file = "numpy-2.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:103ea7063fa624af04a791c39f97070bf93b96d7af7eb23530cd087dc8dbe9dc"},
+ {file = "numpy-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc927d7f289d14f5e037be917539620603294454130b6de200091e23d27dc9be"},
+ {file = "numpy-2.3.2-cp312-cp312-win32.whl", hash = "sha256:d95f59afe7f808c103be692175008bab926b59309ade3e6d25009e9a171f7036"},
+ {file = "numpy-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:9e196ade2400c0c737d93465327d1ae7c06c7cb8a1756121ebf54b06ca183c7f"},
+ {file = "numpy-2.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:ee807923782faaf60d0d7331f5e86da7d5e3079e28b291973c545476c2b00d07"},
+ {file = "numpy-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8d9727f5316a256425892b043736d63e89ed15bbfe6556c5ff4d9d4448ff3b3"},
+ {file = "numpy-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:efc81393f25f14d11c9d161e46e6ee348637c0a1e8a54bf9dedc472a3fae993b"},
+ {file = "numpy-2.3.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dd937f088a2df683cbb79dda9a772b62a3e5a8a7e76690612c2737f38c6ef1b6"},
+ {file = "numpy-2.3.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:11e58218c0c46c80509186e460d79fbdc9ca1eb8d8aee39d8f2dc768eb781089"},
+ {file = "numpy-2.3.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5ad4ebcb683a1f99f4f392cc522ee20a18b2bb12a2c1c42c3d48d5a1adc9d3d2"},
+ {file = "numpy-2.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:938065908d1d869c7d75d8ec45f735a034771c6ea07088867f713d1cd3bbbe4f"},
+ {file = "numpy-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:66459dccc65d8ec98cc7df61307b64bf9e08101f9598755d42d8ae65d9a7a6ee"},
+ {file = "numpy-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a7af9ed2aa9ec5950daf05bb11abc4076a108bd3c7db9aa7251d5f107079b6a6"},
+ {file = "numpy-2.3.2-cp313-cp313-win32.whl", hash = "sha256:906a30249315f9c8e17b085cc5f87d3f369b35fedd0051d4a84686967bdbbd0b"},
+ {file = "numpy-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:c63d95dc9d67b676e9108fe0d2182987ccb0f11933c1e8959f42fa0da8d4fa56"},
+ {file = "numpy-2.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:b05a89f2fb84d21235f93de47129dd4f11c16f64c87c33f5e284e6a3a54e43f2"},
+ {file = "numpy-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4e6ecfeddfa83b02318f4d84acf15fbdbf9ded18e46989a15a8b6995dfbf85ab"},
+ {file = "numpy-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:508b0eada3eded10a3b55725b40806a4b855961040180028f52580c4729916a2"},
+ {file = "numpy-2.3.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:754d6755d9a7588bdc6ac47dc4ee97867271b17cee39cb87aef079574366db0a"},
+ {file = "numpy-2.3.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f66e7d2b2d7712410d3bc5684149040ef5f19856f20277cd17ea83e5006286"},
+ {file = "numpy-2.3.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de6ea4e5a65d5a90c7d286ddff2b87f3f4ad61faa3db8dabe936b34c2275b6f8"},
+ {file = "numpy-2.3.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3ef07ec8cbc8fc9e369c8dcd52019510c12da4de81367d8b20bc692aa07573a"},
+ {file = "numpy-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:27c9f90e7481275c7800dc9c24b7cc40ace3fdb970ae4d21eaff983a32f70c91"},
+ {file = "numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:07b62978075b67eee4065b166d000d457c82a1efe726cce608b9db9dd66a73a5"},
+ {file = "numpy-2.3.2-cp313-cp313t-win32.whl", hash = "sha256:c771cfac34a4f2c0de8e8c97312d07d64fd8f8ed45bc9f5726a7e947270152b5"},
+ {file = "numpy-2.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:72dbebb2dcc8305c431b2836bcc66af967df91be793d63a24e3d9b741374c450"},
+ {file = "numpy-2.3.2-cp313-cp313t-win_arm64.whl", hash = "sha256:72c6df2267e926a6d5286b0a6d556ebe49eae261062059317837fda12ddf0c1a"},
+ {file = "numpy-2.3.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:448a66d052d0cf14ce9865d159bfc403282c9bc7bb2a31b03cc18b651eca8b1a"},
+ {file = "numpy-2.3.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:546aaf78e81b4081b2eba1d105c3b34064783027a06b3ab20b6eba21fb64132b"},
+ {file = "numpy-2.3.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:87c930d52f45df092f7578889711a0768094debf73cfcde105e2d66954358125"},
+ {file = "numpy-2.3.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:8dc082ea901a62edb8f59713c6a7e28a85daddcb67454c839de57656478f5b19"},
+ {file = "numpy-2.3.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af58de8745f7fa9ca1c0c7c943616c6fe28e75d0c81f5c295810e3c83b5be92f"},
+ {file = "numpy-2.3.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed5527c4cf10f16c6d0b6bee1f89958bccb0ad2522c8cadc2efd318bcd545f5"},
+ {file = "numpy-2.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:095737ed986e00393ec18ec0b21b47c22889ae4b0cd2d5e88342e08b01141f58"},
+ {file = "numpy-2.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5e40e80299607f597e1a8a247ff8d71d79c5b52baa11cc1cce30aa92d2da6e0"},
+ {file = "numpy-2.3.2-cp314-cp314-win32.whl", hash = "sha256:7d6e390423cc1f76e1b8108c9b6889d20a7a1f59d9a60cac4a050fa734d6c1e2"},
+ {file = "numpy-2.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:b9d0878b21e3918d76d2209c924ebb272340da1fb51abc00f986c258cd5e957b"},
+ {file = "numpy-2.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:2738534837c6a1d0c39340a190177d7d66fdf432894f469728da901f8f6dc910"},
+ {file = "numpy-2.3.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:4d002ecf7c9b53240be3bb69d80f86ddbd34078bae04d87be81c1f58466f264e"},
+ {file = "numpy-2.3.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:293b2192c6bcce487dbc6326de5853787f870aeb6c43f8f9c6496db5b1781e45"},
+ {file = "numpy-2.3.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0a4f2021a6da53a0d580d6ef5db29947025ae8b35b3250141805ea9a32bbe86b"},
+ {file = "numpy-2.3.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9c144440db4bf3bb6372d2c3e49834cc0ff7bb4c24975ab33e01199e645416f2"},
+ {file = "numpy-2.3.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f92d6c2a8535dc4fe4419562294ff957f83a16ebdec66df0805e473ffaad8bd0"},
+ {file = "numpy-2.3.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cefc2219baa48e468e3db7e706305fcd0c095534a192a08f31e98d83a7d45fb0"},
+ {file = "numpy-2.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:76c3e9501ceb50b2ff3824c3589d5d1ab4ac857b0ee3f8f49629d0de55ecf7c2"},
+ {file = "numpy-2.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:122bf5ed9a0221b3419672493878ba4967121514b1d7d4656a7580cd11dddcbf"},
+ {file = "numpy-2.3.2-cp314-cp314t-win32.whl", hash = "sha256:6f1ae3dcb840edccc45af496f312528c15b1f79ac318169d094e85e4bb35fdf1"},
+ {file = "numpy-2.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:087ffc25890d89a43536f75c5fe8770922008758e8eeeef61733957041ed2f9b"},
+ {file = "numpy-2.3.2-cp314-cp314t-win_arm64.whl", hash = "sha256:092aeb3449833ea9c0bf0089d70c29ae480685dd2377ec9cdbbb620257f84631"},
+ {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:14a91ebac98813a49bc6aa1a0dfc09513dcec1d97eaf31ca21a87221a1cdcb15"},
+ {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:71669b5daae692189540cffc4c439468d35a3f84f0c88b078ecd94337f6cb0ec"},
+ {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:69779198d9caee6e547adb933941ed7520f896fd9656834c300bdf4dd8642712"},
+ {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2c3271cc4097beb5a60f010bcc1cc204b300bb3eafb4399376418a83a1c6373c"},
+ {file = "numpy-2.3.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8446acd11fe3dc1830568c941d44449fd5cb83068e5c70bd5a470d323d448296"},
+ {file = "numpy-2.3.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa098a5ab53fa407fded5870865c6275a5cd4101cfdef8d6fafc48286a96e981"},
+ {file = "numpy-2.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6936aff90dda378c09bea075af0d9c675fe3a977a9d2402f95a87f440f59f619"},
+ {file = "numpy-2.3.2.tar.gz", hash = "sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48"},
+]
+
+[[package]]
+name = "packaging"
+version = "25.0"
+description = "Core utilities for Python packages"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+files = [
+ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"},
+ {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"},
+]
+
+[[package]]
+name = "pandas"
+version = "2.3.2"
+description = "Powerful data structures for data analysis, time series, and statistics"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "pandas-2.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52bc29a946304c360561974c6542d1dd628ddafa69134a7131fdfd6a5d7a1a35"},
+ {file = "pandas-2.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:220cc5c35ffaa764dd5bb17cf42df283b5cb7fdf49e10a7b053a06c9cb48ee2b"},
+ {file = "pandas-2.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42c05e15111221384019897df20c6fe893b2f697d03c811ee67ec9e0bb5a3424"},
+ {file = "pandas-2.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc03acc273c5515ab69f898df99d9d4f12c4d70dbfc24c3acc6203751d0804cf"},
+ {file = "pandas-2.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d25c20a03e8870f6339bcf67281b946bd20b86f1a544ebbebb87e66a8d642cba"},
+ {file = "pandas-2.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21bb612d148bb5860b7eb2c10faacf1a810799245afd342cf297d7551513fbb6"},
+ {file = "pandas-2.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:b62d586eb25cb8cb70a5746a378fc3194cb7f11ea77170d59f889f5dfe3cec7a"},
+ {file = "pandas-2.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1333e9c299adcbb68ee89a9bb568fc3f20f9cbb419f1dd5225071e6cddb2a743"},
+ {file = "pandas-2.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:76972bcbd7de8e91ad5f0ca884a9f2c477a2125354af624e022c49e5bd0dfff4"},
+ {file = "pandas-2.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b98bdd7c456a05eef7cd21fd6b29e3ca243591fe531c62be94a2cc987efb5ac2"},
+ {file = "pandas-2.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d81573b3f7db40d020983f78721e9bfc425f411e616ef019a10ebf597aedb2e"},
+ {file = "pandas-2.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e190b738675a73b581736cc8ec71ae113d6c3768d0bd18bffa5b9a0927b0b6ea"},
+ {file = "pandas-2.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c253828cb08f47488d60f43c5fc95114c771bbfff085da54bfc79cb4f9e3a372"},
+ {file = "pandas-2.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:9467697b8083f9667b212633ad6aa4ab32436dcbaf4cd57325debb0ddef2012f"},
+ {file = "pandas-2.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fbb977f802156e7a3f829e9d1d5398f6192375a3e2d1a9ee0803e35fe70a2b9"},
+ {file = "pandas-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b9b52693123dd234b7c985c68b709b0b009f4521000d0525f2b95c22f15944b"},
+ {file = "pandas-2.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bd281310d4f412733f319a5bc552f86d62cddc5f51d2e392c8787335c994175"},
+ {file = "pandas-2.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96d31a6b4354e3b9b8a2c848af75d31da390657e3ac6f30c05c82068b9ed79b9"},
+ {file = "pandas-2.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:df4df0b9d02bb873a106971bb85d448378ef14b86ba96f035f50bbd3688456b4"},
+ {file = "pandas-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:213a5adf93d020b74327cb2c1b842884dbdd37f895f42dcc2f09d451d949f811"},
+ {file = "pandas-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c13b81a9347eb8c7548f53fd9a4f08d4dfe996836543f805c987bafa03317ae"},
+ {file = "pandas-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0c6ecbac99a354a051ef21c5307601093cb9e0f4b1855984a084bfec9302699e"},
+ {file = "pandas-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c6f048aa0fd080d6a06cc7e7537c09b53be6642d330ac6f54a600c3ace857ee9"},
+ {file = "pandas-2.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0064187b80a5be6f2f9c9d6bdde29372468751dfa89f4211a3c5871854cfbf7a"},
+ {file = "pandas-2.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ac8c320bded4718b298281339c1a50fb00a6ba78cb2a63521c39bec95b0209b"},
+ {file = "pandas-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:114c2fe4f4328cf98ce5716d1532f3ab79c5919f95a9cfee81d9140064a2e4d6"},
+ {file = "pandas-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:48fa91c4dfb3b2b9bfdb5c24cd3567575f4e13f9636810462ffed8925352be5a"},
+ {file = "pandas-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:12d039facec710f7ba305786837d0225a3444af7bbd9c15c32ca2d40d157ed8b"},
+ {file = "pandas-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c624b615ce97864eb588779ed4046186f967374185c047070545253a52ab2d57"},
+ {file = "pandas-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0cee69d583b9b128823d9514171cabb6861e09409af805b54459bd0c821a35c2"},
+ {file = "pandas-2.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2319656ed81124982900b4c37f0e0c58c015af9a7bbc62342ba5ad07ace82ba9"},
+ {file = "pandas-2.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b37205ad6f00d52f16b6d09f406434ba928c1a1966e2771006a9033c736d30d2"},
+ {file = "pandas-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:837248b4fc3a9b83b9c6214699a13f069dc13510a6a6d7f9ba33145d2841a012"},
+ {file = "pandas-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d2c3554bd31b731cd6490d94a28f3abb8dd770634a9e06eb6d2911b9827db370"},
+ {file = "pandas-2.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:88080a0ff8a55eac9c84e3ff3c7665b3b5476c6fbc484775ca1910ce1c3e0b87"},
+ {file = "pandas-2.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d4a558c7620340a0931828d8065688b3cc5b4c8eb674bcaf33d18ff4a6870b4a"},
+ {file = "pandas-2.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45178cf09d1858a1509dc73ec261bf5b25a625a389b65be2e47b559905f0ab6a"},
+ {file = "pandas-2.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77cefe00e1b210f9c76c697fedd8fdb8d3dd86563e9c8adc9fa72b90f5e9e4c2"},
+ {file = "pandas-2.3.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:13bd629c653856f00c53dc495191baa59bcafbbf54860a46ecc50d3a88421a96"},
+ {file = "pandas-2.3.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:36d627906fd44b5fd63c943264e11e96e923f8de77d6016dc2f667b9ad193438"},
+ {file = "pandas-2.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:a9d7ec92d71a420185dec44909c32e9a362248c4ae2238234b76d5be37f208cc"},
+ {file = "pandas-2.3.2.tar.gz", hash = "sha256:ab7b58f8f82706890924ccdfb5f48002b83d2b5a3845976a9fb705d36c34dcdb"},
+]
+
+[package.dependencies]
+numpy = [
+ {version = ">=1.22.4", markers = "python_version < \"3.11\""},
+ {version = ">=1.23.2", markers = "python_version == \"3.11\""},
+ {version = ">=1.26.0", markers = "python_version >= \"3.12\""},
+]
+python-dateutil = ">=2.8.2"
+pytz = ">=2020.1"
+tzdata = ">=2022.7"
+
+[package.extras]
+all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"]
+aws = ["s3fs (>=2022.11.0)"]
+clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"]
+compression = ["zstandard (>=0.19.0)"]
+computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"]
+consortium-standard = ["dataframe-api-compat (>=0.1.7)"]
+excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"]
+feather = ["pyarrow (>=10.0.1)"]
+fss = ["fsspec (>=2022.11.0)"]
+gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"]
+hdf5 = ["tables (>=3.8.0)"]
+html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"]
+mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"]
+output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"]
+parquet = ["pyarrow (>=10.0.1)"]
+performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"]
+plot = ["matplotlib (>=3.6.3)"]
+postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"]
+pyarrow = ["pyarrow (>=10.0.1)"]
+spss = ["pyreadstat (>=1.2.0)"]
+sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"]
+test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"]
+xml = ["lxml (>=4.9.2)"]
+
+[[package]]
+name = "plotly"
+version = "5.24.1"
+description = "An open-source, interactive data visualization library for Python"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+files = [
+ {file = "plotly-5.24.1-py3-none-any.whl", hash = "sha256:f67073a1e637eb0dc3e46324d9d51e2fe76e9727c892dde64ddf1e1b51f29089"},
+ {file = "plotly-5.24.1.tar.gz", hash = "sha256:dbc8ac8339d248a4bcc36e08a5659bacfe1b079390b8953533f4eb22169b4bae"},
+]
+
+[package.dependencies]
+packaging = "*"
+tenacity = ">=6.2.0"
+
+[[package]]
+name = "python-dateutil"
+version = "2.9.0.post0"
+description = "Extensions to the standard Python datetime module"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+groups = ["main"]
+files = [
+ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"},
+ {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
+]
+
+[package.dependencies]
+six = ">=1.5"
+
+[[package]]
+name = "pytz"
+version = "2025.2"
+description = "World timezone definitions, modern and historical"
+optional = false
+python-versions = "*"
+groups = ["main"]
+files = [
+ {file = "pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00"},
+ {file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"},
+]
+
+[[package]]
+name = "requests"
+version = "2.32.5"
+description = "Python HTTP for Humans."
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"},
+ {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"},
+]
+
+[package.dependencies]
+certifi = ">=2017.4.17"
+charset_normalizer = ">=2,<4"
+idna = ">=2.5,<4"
+urllib3 = ">=1.21.1,<3"
+
+[package.extras]
+socks = ["PySocks (>=1.5.6,!=1.5.7)"]
+use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
+
+[[package]]
+name = "six"
+version = "1.17.0"
+description = "Python 2 and 3 compatibility utilities"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+groups = ["main"]
+files = [
+ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"},
+ {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"},
+]
+
+[[package]]
+name = "tenacity"
+version = "9.1.2"
+description = "Retry code until it succeeds"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138"},
+ {file = "tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb"},
+]
+
+[package.extras]
+doc = ["reno", "sphinx"]
+test = ["pytest", "tornado (>=4.5)", "typeguard"]
+
+[[package]]
+name = "tzdata"
+version = "2025.2"
+description = "Provider of IANA time zone data"
+optional = false
+python-versions = ">=2"
+groups = ["main"]
+files = [
+ {file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"},
+ {file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"},
+]
+
+[[package]]
+name = "urllib3"
+version = "2.5.0"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"},
+ {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"},
+]
+
+[package.extras]
+brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""]
+h2 = ["h2 (>=4,<5)"]
+socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
+zstd = ["zstandard (>=0.18.0)"]
+
+[metadata]
+lock-version = "2.1"
+python-versions = ">=3.10,<3.13"
+content-hash = "a7f64c43efcba78952701498a72a8fe503e995841717b2d5de4c9aa20c9a996a"
diff --git a/python/pyproject.toml b/python/pyproject.toml
new file mode 100644
index 0000000000000000000000000000000000000000..daf80e2aa23ffa47eb1c704e4ceb47b20deaa8f7
--- /dev/null
+++ b/python/pyproject.toml
@@ -0,0 +1,20 @@
+[tool.poetry]
+name = "blogpost-fine-tasks-python"
+version = "0.1.0"
+description = "Scripts de génération de fragments Plotly et conversions HTML/Markdown pour le blogpost."
+package-mode = false
+
+[tool.poetry.dependencies]
+python = ">=3.10,<3.13"
+Markdown = "^3.6"
+requests = "^2.32.3"
+numpy = "^2.0.0"
+pandas = "^2.2.2"
+plotly = "^5.24.0"
+
+[tool.poetry.scripts]
+html-to-md = "convert_to_md:main"
+
+[build-system]
+requires = ["poetry-core>=1.5.0"]
+build-backend = "poetry.core.masonry.api"