Fix: Link Error on Page 17

#117
README.md CHANGED
@@ -18,9 +18,6 @@ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-
18
  Instruction to install and run locally
19
 
20
  ```bash
21
- # fetch LFS-tracked files (large assets)
22
- git lfs pull
23
-
24
  npm install
25
  npm run build
26
  npm run dev
 
18
  Instruction to install and run locally
19
 
20
  ```bash
 
 
 
21
  npm install
22
  npm run build
23
  npm run dev
assets/images/moon.svg DELETED
assets/images/sun.svg DELETED
convert_to_md.py DELETED
@@ -1,110 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- HTML to Markdown Converter
4
-
5
- This script converts HTML files to Markdown format.
6
- Usage: python html_to_md.py input.html [output.md]
7
- If no output file is specified, it will use the input filename with .md extension.
8
- """
9
-
10
- import sys
11
- import os
12
- import argparse
13
- import html2text
14
- import requests
15
- from urllib.parse import urlparse
16
-
17
- def is_url(path):
18
- """Check if the given path is a URL."""
19
- parsed = urlparse(path)
20
- return parsed.scheme != '' and parsed.netloc != ''
21
-
22
- def convert_html_to_markdown(html_content, **options):
23
- """Convert HTML content to Markdown."""
24
- converter = html2text.HTML2Text()
25
-
26
- # Configure converter options
27
- converter.ignore_links = options.get('ignore_links', False)
28
- converter.ignore_images = options.get('ignore_images', False)
29
- converter.ignore_tables = options.get('ignore_tables', False)
30
- converter.body_width = options.get('body_width', 0) # 0 means no wrapping
31
- converter.unicode_snob = options.get('unicode_snob', True) # Use Unicode instead of ASCII
32
- converter.wrap_links = options.get('wrap_links', False)
33
- converter.inline_links = options.get('inline_links', True)
34
-
35
- # Convert HTML to Markdown
36
- return converter.handle(html_content)
37
-
38
- def main():
39
- parser = argparse.ArgumentParser(description='Convert HTML to Markdown')
40
- parser.add_argument('input', help='Input HTML file or URL')
41
- parser.add_argument('output', nargs='?', help='Output Markdown file (optional)')
42
- parser.add_argument('--ignore-links', action='store_true', help='Ignore links in the HTML')
43
- parser.add_argument('--ignore-images', action='store_true', help='Ignore images in the HTML')
44
- parser.add_argument('--ignore-tables', action='store_true', help='Ignore tables in the HTML')
45
- parser.add_argument('--body-width', type=int, default=0, help='Wrap text at this width (0 for no wrapping)')
46
- parser.add_argument('--unicode', action='store_true', help='Use Unicode characters instead of ASCII approximations')
47
- parser.add_argument('--wrap-links', action='store_true', help='Wrap links in angle brackets')
48
- parser.add_argument('--reference-links', action='store_true', help='Use reference style links instead of inline links')
49
-
50
- args = parser.parse_args()
51
-
52
- # Determine input
53
- if is_url(args.input):
54
- try:
55
- response = requests.get(args.input)
56
- response.raise_for_status()
57
- html_content = response.text
58
- except requests.exceptions.RequestException as e:
59
- print(f"Error fetching URL: {e}", file=sys.stderr)
60
- return 1
61
- else:
62
- try:
63
- with open(args.input, 'r', encoding='utf-8') as f:
64
- html_content = f.read()
65
- except IOError as e:
66
- print(f"Error reading file: {e}", file=sys.stderr)
67
- return 1
68
-
69
- # Configure conversion options
70
- options = {
71
- 'ignore_links': args.ignore_links,
72
- 'ignore_images': args.ignore_images,
73
- 'ignore_tables': args.ignore_tables,
74
- 'body_width': args.body_width,
75
- 'unicode_snob': args.unicode,
76
- 'wrap_links': args.wrap_links,
77
- 'inline_links': not args.reference_links,
78
- }
79
-
80
- # Convert HTML to Markdown
81
- markdown_content = convert_html_to_markdown(html_content, **options)
82
-
83
- # Determine output
84
- if args.output:
85
- output_file = args.output
86
- else:
87
- if is_url(args.input):
88
- # Generate a filename from the URL
89
- url_parts = urlparse(args.input)
90
- base_name = os.path.basename(url_parts.path) or 'index'
91
- if not base_name.endswith('.html'):
92
- base_name += '.html'
93
- output_file = os.path.splitext(base_name)[0] + '.md'
94
- else:
95
- # Generate a filename from the input file
96
- output_file = os.path.splitext(args.input)[0] + '.md'
97
-
98
- # Write output
99
- try:
100
- with open(output_file, 'w', encoding='utf-8') as f:
101
- f.write(markdown_content)
102
- print(f"Conversion successful! Output saved to: {output_file}")
103
- except IOError as e:
104
- print(f"Error writing file: {e}", file=sys.stderr)
105
- return 1
106
-
107
- return 0
108
-
109
- if __name__ == "__main__":
110
- sys.exit(main())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dist/assets/images/memorycoalescing.png CHANGED

Git LFS Details

  • SHA256: 1094fe9aeb953c743791445ee6d7e73a5a89fa85fe60f4312266d1265e7c591a
  • Pointer size: 130 Bytes
  • Size of remote file: 94.1 kB

Git LFS Details

  • SHA256: 088cd848100ab26abbffdcc7c0e8f18a83facd0a8637c460e3ac88d483b04b46
  • Pointer size: 130 Bytes
  • Size of remote file: 94.1 kB
dist/assets/images/moon.svg DELETED
dist/assets/images/sun.svg DELETED
dist/distill.bundle.js CHANGED
@@ -2146,7 +2146,7 @@ function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
2146
  function bylineTemplate(frontMatter) {
2147
  return "\n <div class=\"byline grid\">\n <div>\n <h3>Authors</h3>\n <div>\n ".concat(frontMatter.authors.map(function (author, i) {
2148
  return "\n <span class=\"author\">\n ".concat(author.personalURL ? "\n <a class=\"name\" href=\"".concat(author.personalURL, "\">").concat(author.name) + (i + 1 < frontMatter.authors.length ? "," : "") + "</a>" : "\n <span class=\"name\">".concat(author.name) + (i + 1 < frontMatter.authors.length ? "," : "") + "</span>", "\n </span>\n ");
2149
- }).join(''), "\n </div>\n </div>\n <div >\n <h3>Affiliation</h3>\n <div><a href=\"https://huggingface.co/\">Hugging Face</a>\n </div>\n </div>\n <div >\n <h3>Published</h3>\n <div>Feb 19, 2025</div>\n </div>\n </div>\n\n");
2150
  }
2151
  var Byline = /*#__PURE__*/function (_HTMLElement4) {
2152
  function Byline() {
 
2146
  function bylineTemplate(frontMatter) {
2147
  return "\n <div class=\"byline grid\">\n <div>\n <h3>Authors</h3>\n <div>\n ".concat(frontMatter.authors.map(function (author, i) {
2148
  return "\n <span class=\"author\">\n ".concat(author.personalURL ? "\n <a class=\"name\" href=\"".concat(author.personalURL, "\">").concat(author.name) + (i + 1 < frontMatter.authors.length ? "," : "") + "</a>" : "\n <span class=\"name\">".concat(author.name) + (i + 1 < frontMatter.authors.length ? "," : "") + "</span>", "\n </span>\n ");
2149
+ }).join(''), "\n </div>\n </div>\n <div >\n <h3>Affiliation</h3>\n <div><a href=\"https://huggingface.co/\">Hugging Face</a>\n </div>\n </div>\n <div >\n <h3>Published</h3>\n <div>Feb 19, 2025</div>\n </div>\n </div>\n <div class=\"side pdf-download\">\n <a href=\"https://huggingface.co/spaces/nanotron/ultrascale-playbook/resolve/main/The_Ultra-Scale_Playbook_Training_LLMs_on_GPU_Clusters.pdf\">Download PDF\n <br>\n <img style=\"width: 32px;\" src=\"../assets/images/256px-PDF.png\" alt=\"PDF\"></a>\n \n </div>\n");
2150
  }
2151
  var Byline = /*#__PURE__*/function (_HTMLElement4) {
2152
  function Byline() {
dist/distill.bundle.js.map CHANGED
The diff for this file is too large to render. See raw diff
 
dist/fragments/banner.html CHANGED
The diff for this file is too large to render. See raw diff
 
dist/index.html CHANGED
The diff for this file is too large to render. See raw diff
 
dist/main.bundle.js CHANGED
@@ -5662,199 +5662,12 @@ function postMessageToHFSpaces(elementId) {
5662
  }
5663
 
5664
  ;// ./src/index.js
5665
- function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = src_unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
5666
- function src_unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return src_arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? src_arrayLikeToArray(r, a) : void 0; } }
5667
- function src_arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
5668
  // import { plotClusters } from './clusters'
5669
 
5670
 
5671
 
5672
- // Dark mode is now handled manually via a CSS class on <html> and injected styles
5673
-
5674
  document.addEventListener("DOMContentLoaded", function () {
5675
  console.log("DOMContentLoaded");
5676
-
5677
- // Inject minimal styles for the theme toggle button
5678
- var styleEl = document.createElement('style');
5679
- styleEl.textContent = "\n .theme-toggle-btn{position:absolute;top:16px;left:16px;z-index:10000;display:inline-flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:999px;background:rgba(255,255,255,0.9);backdrop-filter:saturate(150%) blur(6px);cursor:pointer;border:1px solid transparent;outline:none;box-shadow:none;-webkit-appearance:none;appearance:none;-webkit-tap-highlight-color:transparent}\n .theme-toggle-btn:hover{border-color:transparent;box-shadow:none}\n .theme-toggle-btn:focus,.theme-toggle-btn:focus-visible{outline:none;border-color:transparent;box-shadow:none}\n .theme-toggle-btn img{width:22px;height:22px;transition:filter .15s ease}\n .theme-toggle-btn.dark img{filter: brightness(0) invert(1)}\n @media (prefers-color-scheme: dark){.theme-toggle-btn{background:rgba(30,30,30,0.85);border-color:transparent;box-shadow:none}}\n ";
5680
- document.head.appendChild(styleEl);
5681
-
5682
- // Inject dark mode CSS (scoped via html.dark)
5683
- var darkCSS = "\n html.dark{color-scheme:dark}\n html.dark body{background:#242525;color:#e5e7eb}\n html.dark a{color:#93c5fd}\n html.dark .figure-legend{color:#9ca3af}\n html.dark d-article, html.dark d-article p, html.dark d-article aside{color:white !important;}\n html.dark d-contents{background:#242525}\n html.dark d-contents nav a{color:#cbd5e1}\n html.dark d-contents nav a:hover{text-decoration:underline solid rgba(255,255,255,0.6)}\n html.dark .note-box{background:#111;border-left-color:#888}\n html.dark .note-box-title{color:#d1d5db}\n html.dark .note-box-content{color:#e5e7eb}\n html.dark .large-image-background{background:#242525}\n html.dark .boxed-image{background:#111;border-color:#262626;box-shadow:0 4px 6px rgba(0,0,0,.6)}\n html.dark #graph-all,html.dark #controls,html.dark .memory-block,html.dark .activation-memory,html.dark .gradient-memory{background:#111;border-color:#262626;box-shadow:0 4px 6px rgba(0,0,0,.6);color:#e5e7eb}\n html.dark label,html.dark .memory-title{color:#e5e7eb}\n html.dark .memory-value{color:#93c5fd}\n html.dark input,html.dark select,html.dark textarea{background:#0f0f0f;color:#e5e7eb;border:1px solid #333}\n html.dark input:hover,html.dark select:hover,html.dark textarea:hover{border-color:#60a5fa}\n html.dark input:focus,html.dark select:focus,html.dark textarea:focus{border-color:#3b82f6;box-shadow:0 0 0 2px rgba(59,130,246,0.35)}\n html.dark input[type=range]{background:#333}\n html.dark input[type=range]::-webkit-slider-thumb{background:#3b82f6}\n html.dark .plotly_caption{color:#9ca3af}\n html.dark .theme-toggle-btn{background:rgba(30,30,30,0.85);border-color:transparent}\n html.dark d-article img{background:white}\n html.dark summary {color:black !important;}\n html.dark .katex-container {color:white !important;}\n html.dark d-code {background: white!important;}\n html.dark .code-block div { background: white!important;}\n html.dark .code-block div p { color: black!important;}\n /* Table borders in dark mode */\n html.dark table{border-color:rgba(255,255,255,0.3)}\n html.dark th,html.dark td{border-color:rgba(255,255,255,0.3)}\n html.dark thead tr,html.dark tbody tr{border-color:rgba(255,255,255,0.3)}\n html.dark d-byline, html.dark d-article{border-top: 1px solid rgba(255, 255, 255, 0.5);}\n html.dark d-byline h3{color:white;}\n html.dark d-math *, html.dark span.katex{color:white !important;}\n html.dark d-appendix { color: white}\n html.dark h2 { border-bottom: 1px solid rgba(255, 255, 255, 0.25);}\n html.dark h1, html.dark h2, html.dark h3, html.dark h4, html.dark h5, html.dark h6 { color: white}\n html.dark .code-area { color: black;}\n html.dark .code-area a { color: black!important;}\n \n ";
5684
- var darkStyleEl = document.createElement('style');
5685
- darkStyleEl.id = 'darkmode-css';
5686
- darkStyleEl.textContent = darkCSS;
5687
- document.head.appendChild(darkStyleEl);
5688
-
5689
- // Inject equivalent dark CSS into all ShadowRoots using :host-context(.dark)
5690
- // This ensures styles also apply inside web components with Shadow DOM
5691
- var shadowDarkCSS = darkCSS.replace(/html\.dark/g, ':host-context(.dark)');
5692
- var injectDarkStylesIntoRoot = function injectDarkStylesIntoRoot(root) {
5693
- // Only target ShadowRoots here
5694
- if (!root || !(root instanceof ShadowRoot)) return;
5695
- if (root.querySelector('style#darkmode-css-shadow')) return;
5696
- var style = document.createElement('style');
5697
- style.id = 'darkmode-css-shadow';
5698
- style.textContent = shadowDarkCSS;
5699
- root.appendChild(style);
5700
- };
5701
-
5702
- // Normalize inline SVGs: ensure viewBox and preserveAspectRatio for responsiveness
5703
- var normalizeSvgElement = function normalizeSvgElement(svgEl) {
5704
- try {
5705
- if (!svgEl || svgEl.hasAttribute('viewBox')) return;
5706
- var widthAttr = svgEl.getAttribute('width');
5707
- var heightAttr = svgEl.getAttribute('height');
5708
- if (!widthAttr || !heightAttr) return;
5709
- var width = parseFloat(widthAttr);
5710
- var height = parseFloat(heightAttr);
5711
- if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return;
5712
- svgEl.setAttribute('viewBox', "0 0 ".concat(width, " ").concat(height));
5713
- if (!svgEl.hasAttribute('preserveAspectRatio')) {
5714
- svgEl.setAttribute('preserveAspectRatio', 'xMidYMid meet');
5715
- }
5716
- } catch (_) {
5717
- // no-op
5718
- }
5719
- };
5720
- var processRootForSVGs = function processRootForSVGs(root) {
5721
- if (!root || typeof root.querySelectorAll !== 'function') return;
5722
- var svgs = root.querySelectorAll('svg:not([viewBox])');
5723
- svgs.forEach(function (svg) {
5724
- return normalizeSvgElement(svg);
5725
- });
5726
- };
5727
- var _scanNodeForShadowRoots = function scanNodeForShadowRoots(node) {
5728
- if (!node) return;
5729
- if (node.shadowRoot) {
5730
- injectDarkStylesIntoRoot(node.shadowRoot);
5731
- processRootForSVGs(node.shadowRoot);
5732
- }
5733
- // Traverse children
5734
- if (node.childNodes && node.childNodes.length) {
5735
- node.childNodes.forEach(function (child) {
5736
- // Process SVGs in this subtree as well
5737
- processRootForSVGs(child);
5738
- _scanNodeForShadowRoots(child);
5739
- });
5740
- }
5741
- };
5742
-
5743
- // Intercept future shadow roots
5744
- var originalAttachShadow = Element.prototype.attachShadow;
5745
- Element.prototype.attachShadow = function (init) {
5746
- var shadow = originalAttachShadow.call(this, init);
5747
- try {
5748
- injectDarkStylesIntoRoot(shadow);
5749
- processRootForSVGs(shadow);
5750
- } catch (e) {}
5751
- return shadow;
5752
- };
5753
-
5754
- // Initial sweep for any existing shadow roots
5755
- _scanNodeForShadowRoots(document.documentElement);
5756
- // Initial pass for regular DOM SVGs
5757
- processRootForSVGs(document);
5758
-
5759
- // Observe DOM mutations to catch dynamically added components
5760
- var mo = new MutationObserver(function (mutations) {
5761
- var _iterator = _createForOfIteratorHelper(mutations),
5762
- _step;
5763
- try {
5764
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
5765
- var m = _step.value;
5766
- m.addedNodes && m.addedNodes.forEach(function (n) {
5767
- _scanNodeForShadowRoots(n);
5768
- processRootForSVGs(n);
5769
- });
5770
- }
5771
- } catch (err) {
5772
- _iterator.e(err);
5773
- } finally {
5774
- _iterator.f();
5775
- }
5776
- });
5777
- mo.observe(document.documentElement, {
5778
- childList: true,
5779
- subtree: true
5780
- });
5781
-
5782
- // Create the toggle button
5783
- var btn = document.createElement('button');
5784
- btn.className = 'theme-toggle-btn';
5785
- btn.setAttribute('type', 'button');
5786
- btn.setAttribute('aria-label', 'Basculer le mode sombre');
5787
- // Reuse icons declared in HTML and move them into the button
5788
- var sunIcon = document.getElementById('sunIcon');
5789
- var moonIcon = document.getElementById('moonIcon');
5790
- if (sunIcon && moonIcon) {
5791
- // Make sure they adopt button sizing
5792
- sunIcon.style.display = 'none';
5793
- sunIcon.style.width = '22px';
5794
- sunIcon.style.height = '22px';
5795
- moonIcon.style.display = 'none';
5796
- moonIcon.style.width = '22px';
5797
- moonIcon.style.height = '22px';
5798
- btn.appendChild(sunIcon);
5799
- btn.appendChild(moonIcon);
5800
- }
5801
- document.body.appendChild(btn);
5802
- var setIcon = function setIcon(enabled) {
5803
- // enabled = dark mode enabled -> show sun (to indicate turning off), hide moon
5804
- sunIcon.style.display = enabled ? '' : 'none';
5805
- moonIcon.style.display = enabled ? 'none' : '';
5806
- btn.setAttribute('title', enabled ? 'Désactiver le mode sombre' : 'Activer le mode sombre');
5807
- btn.setAttribute('aria-pressed', String(enabled));
5808
- btn.classList.toggle('dark', enabled);
5809
- };
5810
- var setDark = function setDark(enabled) {
5811
- document.documentElement.classList.toggle('dark', enabled);
5812
- setIcon(enabled);
5813
- };
5814
- var THEME_KEY = 'theme';
5815
- var savedTheme = null;
5816
- try {
5817
- savedTheme = localStorage.getItem(THEME_KEY);
5818
- } catch (e) {}
5819
- var media = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)');
5820
- var prefersDark = media ? media.matches : false;
5821
- // Initialisation: priorité à la préférence sauvegardée, sinon préférence système
5822
- if (savedTheme === 'dark') {
5823
- setDark(true);
5824
- } else if (savedTheme === 'light') {
5825
- setDark(false);
5826
- } else {
5827
- setDark(prefersDark);
5828
- }
5829
-
5830
- // Si l'utilisateur a déjà choisi manuellement, on ne suit plus la préférence système
5831
- var manualOverride = savedTheme === 'dark' || savedTheme === 'light';
5832
-
5833
- // React to system preference changes dynamically (no persistence)
5834
- if (media && typeof media.addEventListener === 'function') {
5835
- media.addEventListener('change', function (e) {
5836
- if (!manualOverride) {
5837
- setDark(e.matches);
5838
- }
5839
- });
5840
- } else if (media && typeof media.addListener === 'function') {
5841
- // Fallback for older browsers
5842
- media.addListener(function (e) {
5843
- if (!manualOverride) {
5844
- setDark(e.matches);
5845
- }
5846
- });
5847
- }
5848
-
5849
- // Toggle handler — for réduire les glitches, attendre le next frame avant d'ajuster l'icône
5850
- btn.addEventListener('click', function () {
5851
- manualOverride = true;
5852
- var next = !document.documentElement.classList.contains('dark');
5853
- setDark(next);
5854
- try {
5855
- localStorage.setItem(THEME_KEY, next ? 'dark' : 'light');
5856
- } catch (e) {}
5857
- });
5858
  loadFragments();
5859
  init_memory_plot();
5860
  syncHFSpacesURLHash();
 
5662
  }
5663
 
5664
  ;// ./src/index.js
 
 
 
5665
  // import { plotClusters } from './clusters'
5666
 
5667
 
5668
 
 
 
5669
  document.addEventListener("DOMContentLoaded", function () {
5670
  console.log("DOMContentLoaded");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5671
  loadFragments();
5672
  init_memory_plot();
5673
  syncHFSpacesURLHash();
dist/main.bundle.js.map CHANGED
The diff for this file is too large to render. See raw diff
 
dist/style.css CHANGED
@@ -304,6 +304,7 @@ d-contents nav > ul > li > a:hover {
304
  border-radius: 6px;
305
  /* Add this to ensure the box only takes up needed space */
306
  display: inline-block;
 
307
  }
308
 
309
  .note-box-title {
@@ -596,141 +597,3 @@ select[name="presets"] {
596
  border-radius: 8px;
597
  }
598
 
599
- .order-button-second {
600
- background: linear-gradient(135deg, #6DB4C4, #D4A5B8);
601
- color: white;
602
- font-size: 18px;
603
- font-weight: 600;
604
- padding: 20px 20px;
605
- border: none;
606
- border-radius: 12px;
607
- cursor: pointer;
608
- text-transform: uppercase;
609
- letter-spacing: 1px;
610
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
611
- transition: all 0.3s ease;
612
- position: relative;
613
- overflow: hidden;
614
- }
615
- .order-button-second:hover {
616
- transform: translateY(-2px);
617
- box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25);
618
- }
619
-
620
- .order-button:active {
621
- transform: translateY(0);
622
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
623
- }
624
-
625
- .order-button-second::before {
626
- content: '';
627
- position: absolute;
628
- top: 0;
629
- left: -100%;
630
- width: 100%;
631
- height: 100%;
632
- background: linear-gradient(135deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0));
633
- transition: left 0.5s ease;
634
- }
635
-
636
- .order-button-second:hover::before {
637
- left: 100%;
638
- }
639
-
640
- .order-button {
641
- background: linear-gradient(135deg, #6DB4C4, #D4A5B8);
642
- color: white;
643
- font-size: 18px;
644
- font-weight: 600;
645
- padding: 16px 32px;
646
- border: none;
647
- border-radius: 12px;
648
- cursor: pointer;
649
- text-transform: uppercase;
650
- letter-spacing: 1px;
651
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
652
- transition: all 0.3s ease;
653
- position: relative;
654
- overflow: hidden;
655
- }
656
-
657
- .order-button:hover {
658
- transform: translateY(-2px);
659
- box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25);
660
- }
661
-
662
- .order-button:active {
663
- transform: translateY(0);
664
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
665
- }
666
-
667
- .order-button::before {
668
- content: '';
669
- position: absolute;
670
- top: 0;
671
- left: -100%;
672
- width: 100%;
673
- height: 100%;
674
- background: linear-gradient(135deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0));
675
- transition: left 0.5s ease;
676
- }
677
-
678
- .order-button:hover::before {
679
- left: 100%;
680
- }
681
- .order-button-container-second {
682
- /* display: flex; */
683
- justify-content: center;
684
- margin: 0px 0;
685
- }
686
-
687
- .order-button-container {
688
- display: flex;
689
- justify-content: center;
690
- margin: 0px 0 40px 0;
691
- }
692
-
693
- d-article img {
694
- width: 100%!important;
695
- }
696
-
697
-
698
- iframe, .js-plotly-plot {
699
- width: 100%!important;
700
- margin-bottom: 20px;
701
- }
702
-
703
- .modebar-container {
704
- display: none;
705
- }
706
-
707
- #graph-container {
708
- display: grid; grid-template-columns: 1fr 1fr; align-items: center;
709
- }
710
-
711
- @media (max-width: 768px) {
712
- #graph-container {
713
- grid-template-columns: 1fr;
714
- }
715
- }
716
-
717
- @media (max-width: 1024px) {
718
- #graph-container {
719
- grid-template-columns: 1fr;
720
- }
721
- #graph-all {
722
- margin-right: 0px;
723
- }
724
- #controls {
725
- margin-left: 0px;
726
- }
727
- }
728
-
729
- .main-plot-container svg {
730
- background: transparent !important;
731
- }
732
-
733
- .large-image-background-transparent {
734
- margin-left: 0px;
735
- margin-right: 0px;
736
- }
 
304
  border-radius: 6px;
305
  /* Add this to ensure the box only takes up needed space */
306
  display: inline-block;
307
+ width: 100%;
308
  }
309
 
310
  .note-box-title {
 
597
  border-radius: 8px;
598
  }
599
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/distill.js CHANGED
@@ -2105,7 +2105,12 @@ d-appendix > distill-appendix {
2105
  <div>Feb 19, 2025</div>
2106
  </div>
2107
  </div>
2108
-
 
 
 
 
 
2109
  `;
2110
  }
2111
 
 
2105
  <div>Feb 19, 2025</div>
2106
  </div>
2107
  </div>
2108
+ <div class="side pdf-download">
2109
+ <a href="https://huggingface.co/spaces/nanotron/ultrascale-playbook/resolve/main/The_Ultra-Scale_Playbook_Training_LLMs_on_GPU_Clusters.pdf">Download PDF
2110
+ <br>
2111
+ <img style="width: 32px;" src="../assets/images/256px-PDF.png" alt="PDF"></a>
2112
+
2113
+ </div>
2114
  `;
2115
  }
2116
 
src/fragments/banner.html CHANGED
The diff for this file is too large to render. See raw diff
 
src/index.html CHANGED
@@ -58,9 +58,6 @@
58
  </script>
59
  </d-front-matter>
60
  <d-title>
61
- <!-- Theme toggle icons (declared once in DOM for clarity; visibility controlled via JS) -->
62
- <img id="sunIcon" src="assets/images/sun.svg" alt="Sun icon" style="display:none;width:22px;height:22px;color:inherit"/>
63
- <img id="moonIcon" src="assets/images/moon.svg" alt="Moon icon" style="display:none;width:22px;height:22px;color:inherit"/>
64
  <h1 class="l-page" style="text-align: center;">The Ultra-Scale Playbook:<br>Training LLMs on GPU Clusters</h1>
65
  <div id="title-plot" class="main-plot-container l-screen" style="overflow-x: hidden; width: 100%; text-align: center;">
66
  <div style="display: flex; justify-content: center; position: relative;">
@@ -68,14 +65,8 @@
68
  </div>
69
  <p style="text-align: cekter; font-style: italic; margin-top: 10px; max-width: 900px; margin-left: auto; margin-right: auto;">We ran over 4,000 scaling experiments on up to 512 GPUs and measured throughput (size of markers) and GPU utilization (color of markers). Note that both are normalized per model size in this visualization.</p>
70
 
71
-
72
  </div>
73
-
74
  </d-title>
75
- <div class="order-button-container">
76
- <button class="order-button" style="margin: 0 8px;" onclick="window.open('https://www.lulu.com/shop/nouamane-tazi-and-ferdinand-mom-and-haojun-zhao-and-phuc-nguyen/the-ultra-scale-playbook/paperback/product-45yk9dj.html?page=1&pageSize=4', '_blank')">Order Book</button>
77
- <button class="order-button" style="margin: 0 8px;" onclick="window.open('https://huggingface.co/nanotron', '_blank')">Get PDF</button>
78
- </div>
79
  <d-byline></d-byline>
80
  <d-article>
81
  <d-contents>
@@ -85,12 +76,10 @@
85
  Thousands of GPUs humming in perfect harmony. That's what it takes to train today's most powerful AI models – a symphony of computing power that until recently was the exclusive domain of elite research labs. Open source has transformed this landscape, but not completely. Yes, you can download the latest <a href="https://huggingface.co/meta-llama">Llama</a> or <a href="https://huggingface.co/deepseek-ai">DeepSeek</a> models. Yes, you can read their <a href="https://ai.meta.com/research/publications/the-llama-3-herd-of-models/">technical</a> and <a href="https://github.com/deepseek-ai/DeepSeek-R1/blob/main/DeepSeek_R1.pdf">experiment</a> reports. But the most challenging part – the training code, the knowledge and techniques necessary to coordinate GPUs to train these massive systems – remains shrouded in complexity and spread around in a series of disconnected papers and often private codebases.
86
  </p>
87
  <aside>Reading time: 2-4 days. <br>For the best reading experience, we recommend not using a mobile phone.</aside>
88
-
89
- <p>
90
  This open source book is here to change that. Starting from the basics, we'll walk you through the knowledge necessary to scale the training of large language models (LLMs) from one GPU to tens, hundreds, and even thousands of GPUs, illustrating theory with practical code examples and reproducible benchmarks.
91
  </p>
92
 
93
-
94
  <p>As the size of the clusters used to train these models has grown, various techniques, such as data parallelism, tensor parallelism, pipeline parallelism, and context parallelism as well as ZeRO and kernel fusion, have been invented to make sure that GPUs are highly utilized at all times. This significantly reduces training time and makes the most efficient use of this expensive hardware. These distributed training techniques are not only important for building initial models but have also become essential for fine-tuning large models on specialized data, which often produces the best results. In this book, we'll progressively go over all of these techniques – from the simplest to the most refined ones – while maintaining a single story line to help you understand where each method comes from.</p>
95
 
96
  <aside>If you have questions or remarks, open a discussion on the <a href="https://huggingface.co/spaces/nanotron/ultrascale-playbook/discussions?status=open&type=discussion">Community tab</a>!</aside>
@@ -106,7 +95,7 @@
106
  <aside>Note that we're still missing pipeline parallelism in this widget. To be added as an exercise for the reader.</aside>
107
 
108
  <div class="large-image-background-transparent">
109
- <div id="graph-container">
110
  <div id="graph-all">
111
  <div class="figure-legend">Memory usage breakdown</div>
112
  <div id="graph"></div>
@@ -2122,7 +2111,7 @@
2122
 
2123
  <p>To run the kernel you will also need some <strong><em>host code</em></strong>, which is executed on the CPU/host and takes care of preparing data allocations and loading data and code:</p>
2124
 
2125
- <div class="l-body code-block" style="display: grid; grid-template-columns: 1fr 1fr; align-items: center;">
2126
  <div>
2127
  <d-code block language="python">
2128
  // Host code
 
58
  </script>
59
  </d-front-matter>
60
  <d-title>
 
 
 
61
  <h1 class="l-page" style="text-align: center;">The Ultra-Scale Playbook:<br>Training LLMs on GPU Clusters</h1>
62
  <div id="title-plot" class="main-plot-container l-screen" style="overflow-x: hidden; width: 100%; text-align: center;">
63
  <div style="display: flex; justify-content: center; position: relative;">
 
65
  </div>
66
  <p style="text-align: cekter; font-style: italic; margin-top: 10px; max-width: 900px; margin-left: auto; margin-right: auto;">We ran over 4,000 scaling experiments on up to 512 GPUs and measured throughput (size of markers) and GPU utilization (color of markers). Note that both are normalized per model size in this visualization.</p>
67
 
 
68
  </div>
 
69
  </d-title>
 
 
 
 
70
  <d-byline></d-byline>
71
  <d-article>
72
  <d-contents>
 
76
  Thousands of GPUs humming in perfect harmony. That's what it takes to train today's most powerful AI models – a symphony of computing power that until recently was the exclusive domain of elite research labs. Open source has transformed this landscape, but not completely. Yes, you can download the latest <a href="https://huggingface.co/meta-llama">Llama</a> or <a href="https://huggingface.co/deepseek-ai">DeepSeek</a> models. Yes, you can read their <a href="https://ai.meta.com/research/publications/the-llama-3-herd-of-models/">technical</a> and <a href="https://github.com/deepseek-ai/DeepSeek-R1/blob/main/DeepSeek_R1.pdf">experiment</a> reports. But the most challenging part – the training code, the knowledge and techniques necessary to coordinate GPUs to train these massive systems – remains shrouded in complexity and spread around in a series of disconnected papers and often private codebases.
77
  </p>
78
  <aside>Reading time: 2-4 days. <br>For the best reading experience, we recommend not using a mobile phone.</aside>
79
+ <p>
 
80
  This open source book is here to change that. Starting from the basics, we'll walk you through the knowledge necessary to scale the training of large language models (LLMs) from one GPU to tens, hundreds, and even thousands of GPUs, illustrating theory with practical code examples and reproducible benchmarks.
81
  </p>
82
 
 
83
  <p>As the size of the clusters used to train these models has grown, various techniques, such as data parallelism, tensor parallelism, pipeline parallelism, and context parallelism as well as ZeRO and kernel fusion, have been invented to make sure that GPUs are highly utilized at all times. This significantly reduces training time and makes the most efficient use of this expensive hardware. These distributed training techniques are not only important for building initial models but have also become essential for fine-tuning large models on specialized data, which often produces the best results. In this book, we'll progressively go over all of these techniques – from the simplest to the most refined ones – while maintaining a single story line to help you understand where each method comes from.</p>
84
 
85
  <aside>If you have questions or remarks, open a discussion on the <a href="https://huggingface.co/spaces/nanotron/ultrascale-playbook/discussions?status=open&type=discussion">Community tab</a>!</aside>
 
95
  <aside>Note that we're still missing pipeline parallelism in this widget. To be added as an exercise for the reader.</aside>
96
 
97
  <div class="large-image-background-transparent">
98
+ <div style="display: grid; grid-template-columns: 1fr 1fr; align-items: center;">
99
  <div id="graph-all">
100
  <div class="figure-legend">Memory usage breakdown</div>
101
  <div id="graph"></div>
 
2111
 
2112
  <p>To run the kernel you will also need some <strong><em>host code</em></strong>, which is executed on the CPU/host and takes care of preparing data allocations and loading data and code:</p>
2113
 
2114
+ <div class="l-body" style="display: grid; grid-template-columns: 1fr 1fr; align-items: center;">
2115
  <div>
2116
  <d-code block language="python">
2117
  // Host code
src/index.js CHANGED
@@ -2,237 +2,9 @@
2
  import { init_memory_plot } from './memory'
3
  import { loadFragments } from './fragmentLoader'
4
  import { syncHFSpacesURLHash } from './syncHFSpacesURLHash'
5
- // Dark mode is now handled manually via a CSS class on <html> and injected styles
6
 
7
  document.addEventListener("DOMContentLoaded", () => {
8
  console.log("DOMContentLoaded");
9
-
10
- // Inject minimal styles for the theme toggle button
11
- const styleEl = document.createElement('style');
12
- styleEl.textContent = `
13
- .theme-toggle-btn{position:absolute;top:16px;left:16px;z-index:10000;display:inline-flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:999px;background:rgba(255,255,255,0.9);backdrop-filter:saturate(150%) blur(6px);cursor:pointer;border:1px solid transparent;outline:none;box-shadow:none;-webkit-appearance:none;appearance:none;-webkit-tap-highlight-color:transparent}
14
- .theme-toggle-btn:hover{border-color:transparent;box-shadow:none}
15
- .theme-toggle-btn:focus,.theme-toggle-btn:focus-visible{outline:none;border-color:transparent;box-shadow:none}
16
- .theme-toggle-btn img{width:22px;height:22px;transition:filter .15s ease}
17
- .theme-toggle-btn.dark img{filter: brightness(0) invert(1)}
18
- @media (prefers-color-scheme: dark){.theme-toggle-btn{background:rgba(30,30,30,0.85);border-color:transparent;box-shadow:none}}
19
- `;
20
- document.head.appendChild(styleEl);
21
-
22
- // Inject dark mode CSS (scoped via html.dark)
23
- const darkCSS = `
24
- html.dark{color-scheme:dark}
25
- html.dark body{background:#242525;color:#e5e7eb}
26
- html.dark a{color:#93c5fd}
27
- html.dark .figure-legend{color:#9ca3af}
28
- html.dark d-article, html.dark d-article p, html.dark d-article aside{color:white !important;}
29
- html.dark d-contents{background:#242525}
30
- html.dark d-contents nav a{color:#cbd5e1}
31
- html.dark d-contents nav a:hover{text-decoration:underline solid rgba(255,255,255,0.6)}
32
- html.dark .note-box{background:#111;border-left-color:#888}
33
- html.dark .note-box-title{color:#d1d5db}
34
- html.dark .note-box-content{color:#e5e7eb}
35
- html.dark .large-image-background{background:#242525}
36
- html.dark .boxed-image{background:#111;border-color:#262626;box-shadow:0 4px 6px rgba(0,0,0,.6)}
37
- html.dark #graph-all,html.dark #controls,html.dark .memory-block,html.dark .activation-memory,html.dark .gradient-memory{background:#111;border-color:#262626;box-shadow:0 4px 6px rgba(0,0,0,.6);color:#e5e7eb}
38
- html.dark label,html.dark .memory-title{color:#e5e7eb}
39
- html.dark .memory-value{color:#93c5fd}
40
- html.dark input,html.dark select,html.dark textarea{background:#0f0f0f;color:#e5e7eb;border:1px solid #333}
41
- html.dark input:hover,html.dark select:hover,html.dark textarea:hover{border-color:#60a5fa}
42
- html.dark input:focus,html.dark select:focus,html.dark textarea:focus{border-color:#3b82f6;box-shadow:0 0 0 2px rgba(59,130,246,0.35)}
43
- html.dark input[type=range]{background:#333}
44
- html.dark input[type=range]::-webkit-slider-thumb{background:#3b82f6}
45
- html.dark .plotly_caption{color:#9ca3af}
46
- html.dark .theme-toggle-btn{background:rgba(30,30,30,0.85);border-color:transparent}
47
- html.dark d-article img{background:white}
48
- html.dark summary {color:black !important;}
49
- html.dark .katex-container {color:white !important;}
50
- html.dark d-code {background: white!important;}
51
- html.dark .code-block div { background: white!important;}
52
- html.dark .code-block div p { color: black!important;}
53
- /* Table borders in dark mode */
54
- html.dark table{border-color:rgba(255,255,255,0.3)}
55
- html.dark th,html.dark td{border-color:rgba(255,255,255,0.3)}
56
- html.dark thead tr,html.dark tbody tr{border-color:rgba(255,255,255,0.3)}
57
- html.dark d-byline, html.dark d-article{border-top: 1px solid rgba(255, 255, 255, 0.5);}
58
- html.dark d-byline h3{color:white;}
59
- html.dark d-math *, html.dark span.katex{color:white !important;}
60
- html.dark d-appendix { color: white}
61
- html.dark h2 { border-bottom: 1px solid rgba(255, 255, 255, 0.25);}
62
- html.dark h1, html.dark h2, html.dark h3, html.dark h4, html.dark h5, html.dark h6 { color: white}
63
- html.dark .code-area { color: black;}
64
- html.dark .code-area a { color: black!important;}
65
-
66
- `;
67
- const darkStyleEl = document.createElement('style');
68
- darkStyleEl.id = 'darkmode-css';
69
- darkStyleEl.textContent = darkCSS;
70
- document.head.appendChild(darkStyleEl);
71
-
72
- // Inject equivalent dark CSS into all ShadowRoots using :host-context(.dark)
73
- // This ensures styles also apply inside web components with Shadow DOM
74
- const shadowDarkCSS = darkCSS.replace(/html\.dark/g, ':host-context(.dark)');
75
-
76
- const injectDarkStylesIntoRoot = (root) => {
77
- // Only target ShadowRoots here
78
- if (!root || !(root instanceof ShadowRoot)) return;
79
- if (root.querySelector('style#darkmode-css-shadow')) return;
80
- const style = document.createElement('style');
81
- style.id = 'darkmode-css-shadow';
82
- style.textContent = shadowDarkCSS;
83
- root.appendChild(style);
84
- };
85
-
86
- // Normalize inline SVGs: ensure viewBox and preserveAspectRatio for responsiveness
87
- const normalizeSvgElement = (svgEl) => {
88
- try {
89
- if (!svgEl || svgEl.hasAttribute('viewBox')) return;
90
- const widthAttr = svgEl.getAttribute('width');
91
- const heightAttr = svgEl.getAttribute('height');
92
- if (!widthAttr || !heightAttr) return;
93
- const width = parseFloat(widthAttr);
94
- const height = parseFloat(heightAttr);
95
- if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return;
96
- svgEl.setAttribute('viewBox', `0 0 ${width} ${height}`);
97
- if (!svgEl.hasAttribute('preserveAspectRatio')) {
98
- svgEl.setAttribute('preserveAspectRatio', 'xMidYMid meet');
99
- }
100
- } catch (_) {
101
- // no-op
102
- }
103
- };
104
-
105
- const processRootForSVGs = (root) => {
106
- if (!root || typeof root.querySelectorAll !== 'function') return;
107
- const svgs = root.querySelectorAll('svg:not([viewBox])');
108
- svgs.forEach((svg) => normalizeSvgElement(svg));
109
- };
110
-
111
- const scanNodeForShadowRoots = (node) => {
112
- if (!node) return;
113
- if (node.shadowRoot) {
114
- injectDarkStylesIntoRoot(node.shadowRoot);
115
- processRootForSVGs(node.shadowRoot);
116
- }
117
- // Traverse children
118
- if (node.childNodes && node.childNodes.length) {
119
- node.childNodes.forEach((child) => {
120
- // Process SVGs in this subtree as well
121
- processRootForSVGs(child);
122
- scanNodeForShadowRoots(child);
123
- });
124
- }
125
- };
126
-
127
- // Intercept future shadow roots
128
- const originalAttachShadow = Element.prototype.attachShadow;
129
- Element.prototype.attachShadow = function(init) {
130
- const shadow = originalAttachShadow.call(this, init);
131
- try {
132
- injectDarkStylesIntoRoot(shadow);
133
- processRootForSVGs(shadow);
134
- } catch (e) {}
135
- return shadow;
136
- };
137
-
138
- // Initial sweep for any existing shadow roots
139
- scanNodeForShadowRoots(document.documentElement);
140
- // Initial pass for regular DOM SVGs
141
- processRootForSVGs(document);
142
-
143
- // Observe DOM mutations to catch dynamically added components
144
- const mo = new MutationObserver((mutations) => {
145
- for (const m of mutations) {
146
- m.addedNodes && m.addedNodes.forEach((n) => {
147
- scanNodeForShadowRoots(n);
148
- processRootForSVGs(n);
149
- });
150
- }
151
- });
152
- mo.observe(document.documentElement, { childList: true, subtree: true });
153
-
154
- // Create the toggle button
155
- const btn = document.createElement('button');
156
- btn.className = 'theme-toggle-btn';
157
- btn.setAttribute('type', 'button');
158
- btn.setAttribute('aria-label', 'Basculer le mode sombre');
159
- // Reuse icons declared in HTML and move them into the button
160
- const sunIcon = document.getElementById('sunIcon');
161
- const moonIcon = document.getElementById('moonIcon');
162
-
163
- if (sunIcon && moonIcon) {
164
- // Make sure they adopt button sizing
165
- sunIcon.style.display = 'none';
166
- sunIcon.style.width = '22px';
167
- sunIcon.style.height = '22px';
168
- moonIcon.style.display = 'none';
169
- moonIcon.style.width = '22px';
170
- moonIcon.style.height = '22px';
171
- btn.appendChild(sunIcon);
172
- btn.appendChild(moonIcon);
173
- }
174
- document.body.appendChild(btn);
175
-
176
- const setIcon = (enabled) => {
177
- // enabled = dark mode enabled -> show sun (to indicate turning off), hide moon
178
- sunIcon.style.display = enabled ? '' : 'none';
179
- moonIcon.style.display = enabled ? 'none' : '';
180
- btn.setAttribute('title', enabled ? 'Désactiver le mode sombre' : 'Activer le mode sombre');
181
- btn.setAttribute('aria-pressed', String(enabled));
182
- btn.classList.toggle('dark', enabled);
183
- };
184
-
185
- const setDark = (enabled) => {
186
- document.documentElement.classList.toggle('dark', enabled);
187
- setIcon(enabled);
188
- };
189
-
190
- const THEME_KEY = 'theme';
191
- let savedTheme = null;
192
- try {
193
- savedTheme = localStorage.getItem(THEME_KEY);
194
- } catch (e) {}
195
-
196
- const media = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)');
197
- const prefersDark = media ? media.matches : false;
198
- // Initialisation: priorité à la préférence sauvegardée, sinon préférence système
199
- if (savedTheme === 'dark') {
200
- setDark(true);
201
- } else if (savedTheme === 'light') {
202
- setDark(false);
203
- } else {
204
- setDark(prefersDark);
205
- }
206
-
207
- // Si l'utilisateur a déjà choisi manuellement, on ne suit plus la préférence système
208
- let manualOverride = savedTheme === 'dark' || savedTheme === 'light';
209
-
210
- // React to system preference changes dynamically (no persistence)
211
- if (media && typeof media.addEventListener === 'function') {
212
- media.addEventListener('change', (e) => {
213
- if (!manualOverride) {
214
- setDark(e.matches);
215
- }
216
- });
217
- } else if (media && typeof media.addListener === 'function') {
218
- // Fallback for older browsers
219
- media.addListener((e) => {
220
- if (!manualOverride) {
221
- setDark(e.matches);
222
- }
223
- });
224
- }
225
-
226
- // Toggle handler — for réduire les glitches, attendre le next frame avant d'ajuster l'icône
227
- btn.addEventListener('click', () => {
228
- manualOverride = true;
229
- const next = !document.documentElement.classList.contains('dark');
230
- setDark(next);
231
- try {
232
- localStorage.setItem(THEME_KEY, next ? 'dark' : 'light');
233
- } catch (e) {}
234
- });
235
-
236
  loadFragments();
237
  init_memory_plot();
238
  syncHFSpacesURLHash();
 
2
  import { init_memory_plot } from './memory'
3
  import { loadFragments } from './fragmentLoader'
4
  import { syncHFSpacesURLHash } from './syncHFSpacesURLHash'
 
5
 
6
  document.addEventListener("DOMContentLoaded", () => {
7
  console.log("DOMContentLoaded");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  loadFragments();
9
  init_memory_plot();
10
  syncHFSpacesURLHash();
src/style.css CHANGED
@@ -304,6 +304,7 @@ d-contents nav > ul > li > a:hover {
304
  border-radius: 6px;
305
  /* Add this to ensure the box only takes up needed space */
306
  display: inline-block;
 
307
  }
308
 
309
  .note-box-title {
@@ -596,141 +597,3 @@ select[name="presets"] {
596
  border-radius: 8px;
597
  }
598
 
599
- .order-button-second {
600
- background: linear-gradient(135deg, #6DB4C4, #D4A5B8);
601
- color: white;
602
- font-size: 18px;
603
- font-weight: 600;
604
- padding: 20px 20px;
605
- border: none;
606
- border-radius: 12px;
607
- cursor: pointer;
608
- text-transform: uppercase;
609
- letter-spacing: 1px;
610
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
611
- transition: all 0.3s ease;
612
- position: relative;
613
- overflow: hidden;
614
- }
615
- .order-button-second:hover {
616
- transform: translateY(-2px);
617
- box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25);
618
- }
619
-
620
- .order-button:active {
621
- transform: translateY(0);
622
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
623
- }
624
-
625
- .order-button-second::before {
626
- content: '';
627
- position: absolute;
628
- top: 0;
629
- left: -100%;
630
- width: 100%;
631
- height: 100%;
632
- background: linear-gradient(135deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0));
633
- transition: left 0.5s ease;
634
- }
635
-
636
- .order-button-second:hover::before {
637
- left: 100%;
638
- }
639
-
640
- .order-button {
641
- background: linear-gradient(135deg, #6DB4C4, #D4A5B8);
642
- color: white;
643
- font-size: 18px;
644
- font-weight: 600;
645
- padding: 16px 32px;
646
- border: none;
647
- border-radius: 12px;
648
- cursor: pointer;
649
- text-transform: uppercase;
650
- letter-spacing: 1px;
651
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
652
- transition: all 0.3s ease;
653
- position: relative;
654
- overflow: hidden;
655
- }
656
-
657
- .order-button:hover {
658
- transform: translateY(-2px);
659
- box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25);
660
- }
661
-
662
- .order-button:active {
663
- transform: translateY(0);
664
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
665
- }
666
-
667
- .order-button::before {
668
- content: '';
669
- position: absolute;
670
- top: 0;
671
- left: -100%;
672
- width: 100%;
673
- height: 100%;
674
- background: linear-gradient(135deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0));
675
- transition: left 0.5s ease;
676
- }
677
-
678
- .order-button:hover::before {
679
- left: 100%;
680
- }
681
- .order-button-container-second {
682
- /* display: flex; */
683
- justify-content: center;
684
- margin: 0px 0;
685
- }
686
-
687
- .order-button-container {
688
- display: flex;
689
- justify-content: center;
690
- margin: 0px 0 40px 0;
691
- }
692
-
693
- d-article img {
694
- width: 100%!important;
695
- }
696
-
697
-
698
- iframe, .js-plotly-plot {
699
- width: 100%!important;
700
- margin-bottom: 20px;
701
- }
702
-
703
- .modebar-container {
704
- display: none;
705
- }
706
-
707
- #graph-container {
708
- display: grid; grid-template-columns: 1fr 1fr; align-items: center;
709
- }
710
-
711
- @media (max-width: 768px) {
712
- #graph-container {
713
- grid-template-columns: 1fr;
714
- }
715
- }
716
-
717
- @media (max-width: 1024px) {
718
- #graph-container {
719
- grid-template-columns: 1fr;
720
- }
721
- #graph-all {
722
- margin-right: 0px;
723
- }
724
- #controls {
725
- margin-left: 0px;
726
- }
727
- }
728
-
729
- .main-plot-container svg {
730
- background: transparent !important;
731
- }
732
-
733
- .large-image-background-transparent {
734
- margin-left: 0px;
735
- margin-right: 0px;
736
- }
 
304
  border-radius: 6px;
305
  /* Add this to ensure the box only takes up needed space */
306
  display: inline-block;
307
+ width: 100%;
308
  }
309
 
310
  .note-box-title {
 
597
  border-radius: 8px;
598
  }
599
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ultra_blog.md DELETED
The diff for this file is too large to render. See raw diff