@neuromatch/course-kit 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # @neuromatch/course-kit
2
+
3
+ The [MyST](https://mystmd.org) plugin for Neuromatch courses: one bundled
4
+ file plus the browser assets it uses. It is one half of the
5
+ [Neuromatch course kit](https://github.com/neuromatch/course-kit); the other
6
+ half is the [`nma-kit`](https://pypi.org/project/neuromatch-course-kit/)
7
+ command line, which checks content and generates notebooks. Both ship under
8
+ the same version.
9
+
10
+ You normally don't install this package. A course's `myst.yml` loads it from
11
+ jsDelivr:
12
+
13
+ ```yaml
14
+ version: 1
15
+ extend: https://cdn.jsdelivr.net/npm/@neuromatch/course-kit@0.1/dist/nma.yml
16
+ project:
17
+ github: https://github.com/<org>/<course>
18
+ ```
19
+
20
+ `dist/nma.yml` adds the plugin and turns on `project.jupyter.lite`. In CI,
21
+ `nma-kit prepare-site` rewrites `@0.1` to the exact version of the CLI, so the
22
+ plugin, its assets and the CLI always match. See the
23
+ [kit README](https://github.com/neuromatch/course-kit#using-the-kit-in-a-course)
24
+ for the full course setup.
25
+
26
+ ## What it adds to MyST
27
+
28
+ | Directive / feature | What it does on the site |
29
+ |---|---|
30
+ | `{nma-video}`, `{nma-slides}` | Embedded YouTube, Bilibili or OSF videos and slide decks |
31
+ | `{nma-solution}`, `{nma-explanation}` | A "Click for solution" link; the solution text itself is never on the page |
32
+ | `{nma-feedback}` | A feedback widget that posts to datatops from the browser |
33
+ | `layout: scrolly`, `{step}`, `{stage}` | Scrollytelling pages with live Python (Pyodide) |
34
+ | Editable cells | Code cells become editable and runnable in the browser on pages with a `kernelspec`; `runtime: colab` pages get a Colab banner instead |
35
+
36
+ Examples:
37
+
38
+ ```markdown
39
+ :::{nma-video} Video 1: Linear dynamical systems
40
+ :youtube: vPYQPI4nKT8
41
+ :bilibili: BV1up4y1S7wj
42
+ :::
43
+
44
+ :::{nma-feedback} Linear_Dynamical_Systems_Video
45
+ :::
46
+ ```
47
+
48
+ Directives must be at the top level of a page. Their options are checked by
49
+ `nma-kit check`.
50
+
51
+ Settings live in the `nma:` block of `myst.yml` (all optional):
52
+
53
+ ```yaml
54
+ nma:
55
+ paths:
56
+ tutorials: tutorials
57
+ notebooks:
58
+ branch: notebooks-branch
59
+ feedback: { url: https://..., project: ..., user_key: ... }
60
+ scrolly:
61
+ pyodide_url: https://cdn.jsdelivr.net/pyodide/v314.0.7/full/
62
+ ```
63
+
64
+ A plugin can't add site-wide CSS, so the Neuromatch look (colours, Rubik font,
65
+ logos, favicon, and the rule that hides JupyterLite's toolbar) comes from the
66
+ `nma-kit sync-assets` command in the PyPI package instead. It writes `_nma/`,
67
+ which the course's `myst.yml` lists under `site.options` (`style`, `logo`,
68
+ `logo_dark`, `favicon`). See the
69
+ [kit README](https://github.com/neuromatch/course-kit#site-branding).
70
+
71
+ ## What's in the package
72
+
73
+ ```
74
+ dist/nma.mjs the plugin (all features, one file)
75
+ dist/nma.yml the config courses `extend:`
76
+ dist/assets/<name>/ browser widgets and CSS, one folder per feature
77
+ dist/build-info.json {mode, version}
78
+ ```
79
+
80
+ Asset URLs inside the bundle point at this exact version on jsDelivr. MyST
81
+ downloads them at build time and serves them from the course's own site, so
82
+ readers' browsers don't contact the CDN for them. The one exception is
83
+ the Python runtime: editable cells and scrolly pages load Pyodide from
84
+ `cdn.jsdelivr.net`, and micropip installs packages from PyPI.
85
+
86
+ ## Versions
87
+
88
+ The kit is on 0.x. A breaking change bumps the minor version, so `@0.1` only
89
+ receives compatible fixes. Don't use `@latest` in `myst.yml`: jsDelivr caches
90
+ alias URLs for up to 7 days and `@latest` crosses major versions.
91
+
92
+ ## Development
93
+
94
+ Source, tests and release process: <https://github.com/neuromatch/course-kit>.
95
+
96
+ BSD-3-Clause.
@@ -1,6 +1,7 @@
1
1
  /* Editable cell panel (in each panel's shadow root).
2
2
  Layout: header, editor, action bar, output. Colours follow the site theme.
3
- Public tokens, set on :root / html.dark in _static/custom.css:
3
+ Public tokens, set on :root / html.dark in the kit stylesheet (python/src/nma_kit/assets/site.css);
4
+ a course overrides them in its own CSS (nma.style.extra):
4
5
  --nma-cell-{bg,fg}-{light,dark}, --nma-cell-head-{bg,fg}-{light,dark},
5
6
  --nma-cell-bar-bg-{light,dark}, --nma-cell-out-bg-{light,dark},
6
7
  --nma-cell-gutter-{bg,fg}-{light,dark}, --nma-cell-active-line-{light,dark},
@@ -2,7 +2,7 @@
2
2
  .nma-cells-runtime { display: none; }
3
3
 
4
4
  /* Floating "Run all" control (runtime-widget.mjs). Brand-coloured via the theme
5
- variables set in _static/custom.css, with safe fallbacks. */
5
+ variables set in the kit stylesheet (site.css), with safe fallbacks. */
6
6
  .nma-run-all-bar {
7
7
  position: fixed;
8
8
  right: 1rem;
@@ -11,14 +11,14 @@
11
11
 
12
12
  const STYLE = `
13
13
  .hud { position: absolute; top: 0; left: 0; right: 0; display: flex; align-items: center; gap: 16px; padding: 14px 28px; pointer-events: none; font-family: var(--nm-font-head, system-ui, sans-serif); }
14
- .sec { font-weight: 600; font-size: 0.78rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--nm-mint, #5efbd7); min-width: 12ch; }
15
- .bar { flex: 1; height: 3px; background: rgba(255,255,255,0.14); border-radius: 3px; overflow: hidden; }
16
- .bar i { display: block; height: 100%; width: 0; background: linear-gradient(90deg, var(--nm-salmon, #fb7a7a), var(--nm-mint, #5efbd7)); transition: width 0.4s; }
14
+ .sec { font-weight: 600; font-size: 0.78rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--sc-accent, #008cbf); min-width: 12ch; }
15
+ .bar { flex: 1; height: 3px; background: var(--sc-track, rgba(26,26,26,0.12)); border-radius: 3px; overflow: hidden; }
16
+ .bar i { display: block; height: 100%; width: 0; background: linear-gradient(90deg, var(--sc-warm, #fa7a7a), var(--sc-accent, #008cbf)); transition: width 0.4s; }
17
17
  .py[hidden] { display: none; }
18
- .py { pointer-events: auto; flex: none; font: 600 0.74rem var(--nm-font-head, system-ui, sans-serif); letter-spacing: 0.04em; border: 1px solid var(--nm-mint, #5efbd7); border-radius: 999px; padding: 0.3rem 0.85rem; cursor: pointer; max-width: 34ch; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; background: var(--nm-mint, #5efbd7); color: var(--nm-indigo, #2a2952); }
19
- .py[data-state="loading"] { background: transparent; color: var(--nm-mint, #5efbd7); cursor: progress; }
20
- .py[data-state="ready"] { background: transparent; color: var(--nm-mint, #5efbd7); cursor: default; }
21
- .py[data-state="error"] { background: var(--nm-salmon, #fb7a7a); border-color: var(--nm-salmon, #fb7a7a); }
18
+ .py { pointer-events: auto; flex: none; font: 600 0.74rem var(--nm-font-head, system-ui, sans-serif); letter-spacing: 0.04em; border: 1px solid var(--sc-accent, #008cbf); border-radius: 999px; padding: 0.3rem 0.85rem; cursor: pointer; max-width: 34ch; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; background: var(--sc-accent, #008cbf); color: var(--sc-accent-fg, #fff); }
19
+ .py[data-state="loading"] { background: transparent; color: var(--sc-accent, #008cbf); cursor: progress; }
20
+ .py[data-state="ready"] { background: transparent; color: var(--sc-accent, #008cbf); cursor: default; }
21
+ .py[data-state="error"] { background: var(--sc-warm, #fa7a7a); border-color: var(--sc-warm, #fa7a7a); color: #1a1a1a; }
22
22
  `;
23
23
 
24
24
  // Section width (px) from which the stage sits beside the cards.
@@ -1,27 +1,28 @@
1
1
  /* Runnable Python panel (inside the widget's shadow DOM).
2
- Colours come from the --nm-* variables set on .scrolly in scrolly.css. */
2
+ Colours come from the --sc-* variables set on .scrolly in scrolly.css (they
3
+ inherit into the shadow DOM and switch with the site's light/dark mode). */
3
4
  :host { display: block; width: 100%; }
4
5
  * { box-sizing: border-box; }
5
6
  [hidden] { display: none !important; }
6
7
 
7
8
  .fl-codebox {
8
9
  width: 100%;
9
- background: #14131f;
10
- border: 1px solid rgba(185, 184, 255, 0.28);
10
+ background: var(--sc-code-bg);
11
+ border: 1px solid var(--sc-line);
11
12
  border-radius: 12px;
12
13
  overflow: hidden;
13
- box-shadow: 0 12px 34px rgba(8, 7, 30, 0.5);
14
- color: #ecebff;
14
+ box-shadow: var(--sc-shadow);
15
+ color: var(--sc-code-text);
15
16
  font-family: var(--nm-font-body, system-ui, sans-serif);
16
17
  text-align: left;
17
18
  }
18
- .fl-codehead { display: flex; justify-content: space-between; padding: 0.45rem 1rem; background: #22214a; font: 600 0.72rem var(--nm-font-head, system-ui, sans-serif); letter-spacing: 0.1em; text-transform: uppercase; color: var(--nm-lilac, #b9b8ff); }
19
+ .fl-codehead { display: flex; justify-content: space-between; padding: 0.45rem 1rem; background: var(--sc-code-bar); border-bottom: 1px solid var(--sc-line); font: 600 0.72rem var(--nm-font-head, system-ui, sans-serif); letter-spacing: 0.1em; text-transform: uppercase; color: var(--sc-code-head-fg); }
19
20
 
20
- .fl-params { display: flex; flex-wrap: wrap; gap: 0.4rem 1.4rem; padding: 0.55rem 1rem; background: #1b1a36; border-bottom: 1px solid rgba(185, 184, 255, 0.2); }
21
- .fl-params label { display: flex; align-items: center; gap: 0.5rem; font: 600 0.8rem ui-monospace, Consolas, monospace; color: var(--nm-lilac, #b9b8ff); }
22
- .fl-params input[type=range] { width: 9rem; accent-color: var(--nm-mint, #5efbd7); }
23
- .fl-params output { min-width: 4ch; color: var(--nm-mint, #5efbd7); text-align: right; }
24
- .fl-params select { font: inherit; background: #14131f; color: var(--nm-mint, #5efbd7); border: 1px solid rgba(185, 184, 255, 0.4); border-radius: 6px; padding: 0.15rem 0.4rem; }
21
+ .fl-params { display: flex; flex-wrap: wrap; gap: 0.4rem 1.4rem; padding: 0.55rem 1rem; background: var(--sc-code-bar); border-bottom: 1px solid var(--sc-line); }
22
+ .fl-params label { display: flex; align-items: center; gap: 0.5rem; font: 600 0.8rem ui-monospace, Consolas, monospace; color: var(--sc-code-head-fg); }
23
+ .fl-params input[type=range] { width: 9rem; accent-color: var(--sc-accent); }
24
+ .fl-params output { min-width: 4ch; color: var(--sc-accent); text-align: right; }
25
+ .fl-params select { font: inherit; background: var(--sc-code-bg); color: var(--sc-accent); border: 1px solid var(--sc-line); border-radius: 6px; padding: 0.15rem 0.4rem; }
25
26
 
26
27
  .fl-editor { position: relative; max-height: 46vh; overflow: auto; }
27
28
  .fl-editor .fl-hl, .fl-editor .fl-ta {
@@ -31,37 +32,37 @@
31
32
  white-space: pre;
32
33
  tab-size: 4;
33
34
  }
34
- .fl-editor .fl-hl { color: #ecebff; overflow: hidden; pointer-events: none; }
35
+ .fl-editor .fl-hl { color: var(--sc-code-text); overflow: hidden; pointer-events: none; }
35
36
  .fl-editor .fl-ta {
36
37
  position: absolute; inset: 0; width: 100%; height: 100%;
37
- color: transparent; caret-color: var(--nm-mint, #5efbd7); background: transparent; border: 0; outline: 0; resize: none; overflow: hidden;
38
+ color: transparent; caret-color: var(--sc-accent); background: transparent; border: 0; outline: 0; resize: none; overflow: hidden;
38
39
  }
39
- .fl-editor .fl-ta::selection { background: rgba(94, 251, 215, 0.28); }
40
- .fl-editor:focus-within { box-shadow: inset 0 0 0 2px rgba(94, 251, 215, 0.55); }
40
+ .fl-editor .fl-ta::selection { background: color-mix(in srgb, var(--sc-accent) 30%, transparent); }
41
+ .fl-editor:focus-within { box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--sc-accent) 55%, transparent); }
41
42
 
42
- .fl-actions { display: flex; align-items: center; gap: 0.6rem; padding: 0.45rem 1rem; background: #1b1a36; border-top: 1px solid rgba(185, 184, 255, 0.2); }
43
+ .fl-actions { display: flex; align-items: center; gap: 0.6rem; padding: 0.45rem 1rem; background: var(--sc-code-bar); border-top: 1px solid var(--sc-line); }
43
44
  .mode-output .fl-actions { border-top: 0; background: transparent; padding: 0.35rem 0.6rem; }
44
45
  .fl-actions button { font: 600 0.78rem var(--nm-font-head, system-ui, sans-serif); border: 0; border-radius: 999px; padding: 0.3rem 0.95rem; cursor: pointer; }
45
- .fl-run { background: var(--nm-mint, #5efbd7); color: var(--nm-indigo, #2a2952); }
46
+ .fl-run { background: var(--sc-accent); color: var(--sc-accent-fg); }
46
47
  .fl-run:disabled { opacity: 0.55; cursor: progress; }
47
- .fl-reset, .fl-toggle { background: transparent; color: var(--nm-lilac, #b9b8ff); border: 1px solid rgba(185, 184, 255, 0.5) !important; }
48
- .fl-status { font-size: 0.76rem; color: var(--nm-muted, #b4b3e6); }
48
+ .fl-reset, .fl-toggle { background: transparent; color: var(--sc-code-text); border: 1px solid var(--sc-line) !important; }
49
+ .fl-status { font-size: 0.76rem; color: var(--sc-muted); }
49
50
 
50
- .fl-out { border-top: 1px solid rgba(185, 184, 255, 0.2); background: #0e0d18; padding: 0.55rem 1.1rem 0.75rem; }
51
+ .fl-out { border-top: 1px solid var(--sc-line); background: var(--sc-code-out); padding: 0.55rem 1.1rem 0.75rem; }
51
52
  .mode-output .fl-out { border-top: 0; }
52
- .fl-out pre { margin: 0.25rem 0 0; font: 0.84rem/1.55 ui-monospace, "Cascadia Code", Consolas, monospace; color: #c9f5ea; white-space: pre-wrap; max-height: 30vh; overflow: auto; }
53
+ .fl-out pre { margin: 0.25rem 0 0; font: 0.84rem/1.55 ui-monospace, "Cascadia Code", Consolas, monospace; color: var(--sc-code-text); white-space: pre-wrap; max-height: 30vh; overflow: auto; }
53
54
  .fl-figs img { display: block; max-width: 100%; max-height: 48vh; margin: 0.5rem auto 0; border-radius: 8px; background: #fff; }
54
- .fl-err { color: var(--nm-salmon, #fb7a7a); font-size: 0.86rem; margin-top: 0.3rem; }
55
- .fl-err details { margin-top: 0.25rem; color: var(--nm-muted, #b4b3e6); }
56
- .fl-err pre { font: 0.74rem/1.5 ui-monospace, Consolas, monospace; color: #f5c1c1; }
57
- .fl-note { margin: 0 0 0.3rem; font-size: 0.78rem; color: var(--nm-salmon, #fb7a7a); }
58
- .fl-empty { margin: 0; font-size: 0.8rem; color: var(--nm-muted, #b4b3e6); font-style: italic; }
55
+ .fl-err { color: var(--sc-warm-text); font-size: 0.86rem; margin-top: 0.3rem; }
56
+ .fl-err details { margin-top: 0.25rem; color: var(--sc-muted); }
57
+ .fl-err pre { font: 0.74rem/1.5 ui-monospace, Consolas, monospace; color: var(--sc-warm-text); }
58
+ .fl-note { margin: 0 0 0.3rem; font-size: 0.78rem; color: var(--sc-warm-text); }
59
+ .fl-empty { margin: 0; font-size: 0.8rem; color: var(--sc-muted); font-style: italic; }
59
60
 
60
- .tk-c { color: #8b8ac7; font-style: italic; }
61
- .tk-s { color: var(--nm-mint, #5efbd7); font-style: normal; }
62
- .tk-k { color: var(--nm-salmon, #fb7a7a); font-style: normal; }
63
- .tk-n { color: var(--nm-lilac, #b9b8ff); font-style: normal; }
64
- .tk-f { color: #7cc4ff; font-style: normal; }
65
- .tk-b { font-style: normal; color: #fff; background: rgba(251, 122, 122, 0.35); border-radius: 4px; }
61
+ .tk-c { color: var(--sc-tok-comment); font-style: italic; }
62
+ .tk-s { color: var(--sc-tok-string); font-style: normal; }
63
+ .tk-k { color: var(--sc-tok-keyword); font-style: normal; }
64
+ .tk-n { color: var(--sc-tok-number); font-style: normal; }
65
+ .tk-f { color: var(--sc-tok-function); font-style: normal; }
66
+ .tk-b { font-style: normal; color: inherit; background: color-mix(in srgb, var(--sc-warm) 30%, transparent); border-radius: 4px; }
66
67
 
67
68
  .is-collapsed .fl-editor { display: none; }
@@ -1,4 +1,4 @@
1
- /* Scrollytelling page layout (plugins/scrolly/).
1
+ /* Scrollytelling page layout and colours (js/src/scrolly/).
2
2
  Attached to the engine widget so MyST bundles it; engine.mjs copies it
3
3
  into <head>. Without JavaScript the page is a plain linear document; the
4
4
  pinned layout switches on when the engine sets data-ready.
@@ -26,28 +26,95 @@
26
26
  body:has(.myst-primary-sidebar.lg\:hidden) .article-grid:not(:has(> .lg\:col-margin-right)) > .scrolly-col { grid-column: screen-inset; }
27
27
  }
28
28
 
29
+ /* ---- colours ----
30
+ The section follows the site: light and dark mode (the theme's `.dark` class
31
+ on <html>), the brand colours from the kit stylesheet (--nma-brand-*, which a
32
+ course changes with `nma.brand`) and the kit's syntax colours (--nma-tok-*,
33
+ shared with the editable cells). The hex values are the Neuromatch defaults,
34
+ used when the kit stylesheet is not loaded. runnable.css and the HUD in
35
+ engine.mjs read the same --sc-* tokens through the shadow DOM. */
29
36
  .scrolly {
30
37
  --scrolly-top: 60px;
31
- --nm-indigo: #2a2952;
32
- --nm-indigo-deep: #1c1b3e;
33
- --nm-purple: #45429c;
34
- --nm-blue: #008cbf;
35
- --nm-salmon: #fa7a7a;
36
- --nm-mint: #5efbd7;
37
- --nm-lilac: #b9b8ff;
38
- --nm-cream: #f3f0ec;
39
- --nm-ink: #161922;
40
- --nm-muted: #b4b3e6;
41
- --nm-text: #f3f0ec;
38
+ --sc-primary: var(--nma-brand-primary, #008cbf);
39
+ --sc-primary-light: var(--nma-brand-primary-light, #35b4dc);
40
+ --sc-secondary: var(--nma-brand-secondary, #45429c);
41
+ --sc-warm: var(--nma-brand-accent, #fa7a7a);
42
+
43
+ --sc-bg: #f7f8fa;
44
+ --sc-text: #1a1a1a;
45
+ --sc-muted: #7a7a7a;
46
+ --sc-line: #e6e6e6;
47
+ --sc-heading: var(--sc-secondary);
48
+ --sc-title: var(--sc-secondary);
49
+ --sc-link: var(--sc-primary);
50
+ --sc-accent: var(--sc-primary);
51
+ --sc-accent-fg: #ffffff;
52
+ --sc-warm-text: color-mix(in srgb, var(--sc-warm) 60%, #1a1a1a);
53
+ --sc-glow-1: color-mix(in srgb, var(--sc-secondary) 10%, transparent);
54
+ --sc-glow-2: color-mix(in srgb, var(--sc-primary) 12%, transparent);
55
+ --sc-track: rgba(26, 26, 26, 0.12);
56
+ --sc-chip-bg: color-mix(in srgb, var(--sc-primary) 6%, #ffffff);
57
+ --sc-chip-line: color-mix(in srgb, var(--sc-primary) 28%, transparent);
58
+
59
+ --sc-card-bg: #ffffff;
60
+ --sc-card-text: #1a1a1a;
61
+ --sc-card-code: color-mix(in srgb, var(--sc-primary) 10%, #ffffff);
62
+ --sc-shadow: 0 10px 30px rgba(26, 26, 26, 0.1);
63
+
64
+ --sc-code-bg: #ffffff;
65
+ --sc-code-bar: #f7f8fa;
66
+ --sc-code-out: #f7f8fa;
67
+ --sc-code-text: #1a1a1a;
68
+ --sc-code-head-fg: var(--sc-secondary);
69
+ --sc-tok-comment: var(--nma-tok-comment-light, #7a7a7a);
70
+ --sc-tok-string: var(--nma-tok-string-light, #0e7c86);
71
+ --sc-tok-keyword: var(--nma-tok-keyword-light, #45429c);
72
+ --sc-tok-number: var(--nma-tok-number-light, #b5484b);
73
+ --sc-tok-function: var(--nma-tok-function-light, #006b8f);
74
+
42
75
  --nm-font-body: inherit;
43
76
  --nm-font-head: inherit;
44
77
  position: relative;
45
78
  margin-block: 1.5rem;
46
- color: var(--nm-text);
47
- background: var(--nm-indigo-deep);
79
+ color: var(--sc-text);
80
+ background: var(--sc-bg);
81
+ border: 1px solid var(--sc-line);
48
82
  border-radius: 12px;
49
83
  padding: 1rem;
50
84
  }
85
+ .dark .scrolly {
86
+ --sc-bg: #232323;
87
+ --sc-text: #f7f8fa;
88
+ --sc-muted: #bdcfcf;
89
+ --sc-line: #3a3a3a;
90
+ --sc-heading: var(--nma-tok-keyword-dark, #b9b8ff);
91
+ --sc-title: #f7f8fa;
92
+ --sc-link: var(--sc-primary-light);
93
+ --sc-accent: var(--sc-primary-light);
94
+ --sc-accent-fg: #14131f;
95
+ --sc-warm-text: color-mix(in srgb, var(--sc-warm) 75%, #ffffff);
96
+ --sc-glow-1: color-mix(in srgb, var(--sc-secondary) 45%, transparent);
97
+ --sc-glow-2: color-mix(in srgb, var(--sc-primary) 18%, transparent);
98
+ --sc-track: rgba(255, 255, 255, 0.14);
99
+ --sc-chip-bg: rgba(255, 255, 255, 0.05);
100
+ --sc-chip-line: color-mix(in srgb, var(--sc-primary-light) 30%, transparent);
101
+
102
+ --sc-card-bg: #2e2e2e;
103
+ --sc-card-text: #f7f8fa;
104
+ --sc-card-code: rgba(255, 255, 255, 0.1);
105
+ --sc-shadow: 0 14px 40px rgba(0, 0, 0, 0.45);
106
+
107
+ --sc-code-bg: #1b1b1b;
108
+ --sc-code-bar: #262626;
109
+ --sc-code-out: #151515;
110
+ --sc-code-text: #e6e6e6;
111
+ --sc-code-head-fg: #bdcfcf;
112
+ --sc-tok-comment: var(--nma-tok-comment-dark, #9aa7a7);
113
+ --sc-tok-string: var(--nma-tok-string-dark, #5efbd7);
114
+ --sc-tok-keyword: var(--nma-tok-keyword-dark, #b9b8ff);
115
+ --sc-tok-number: var(--nma-tok-number-dark, #ff9e9e);
116
+ --sc-tok-function: var(--nma-tok-function-dark, #5ec8ec);
117
+ }
51
118
  .scrolly *, .scrolly *::before, .scrolly *::after { box-sizing: border-box; }
52
119
  .scrolly-engine { display: block; }
53
120
  .scrolly-stage { margin-bottom: 1.5rem; }
@@ -77,8 +144,8 @@ body:has(.scrolly) .myst-jp-nb-toolbar-power { display: none !important; }
77
144
  z-index: 0;
78
145
  border-radius: 12px;
79
146
  background:
80
- radial-gradient(ellipse 60% 50% at 78% 20%, rgba(68, 67, 156, 0.55), transparent 70%),
81
- radial-gradient(ellipse 40% 40% at 10% 95%, rgba(0, 137, 247, 0.16), transparent 70%);
147
+ radial-gradient(ellipse 60% 50% at 78% 20%, var(--sc-glow-1), transparent 70%),
148
+ radial-gradient(ellipse 40% 40% at 10% 95%, var(--sc-glow-2), transparent 70%);
82
149
  }
83
150
  .scrolly[data-ready="true"] .scrolly-view {
84
151
  position: absolute;
@@ -104,59 +171,60 @@ body:has(.scrolly) .myst-jp-nb-toolbar-power { display: none !important; }
104
171
  pointer-events: auto;
105
172
  max-width: 32rem;
106
173
  min-width: 0; /* flex item in .scrolly-step: don't grow to the widest code line */
107
- background: var(--nm-cream);
108
- color: var(--nm-ink);
174
+ background: var(--sc-card-bg);
175
+ color: var(--sc-card-text);
176
+ border: 1px solid var(--sc-line);
109
177
  border-radius: 14px;
110
- border-left: 6px solid var(--nm-salmon);
178
+ border-left: 6px solid var(--sc-warm);
111
179
  padding: 1.1rem 1.35rem 1.25rem;
112
- box-shadow: 0 14px 40px rgba(8, 7, 30, 0.45);
180
+ box-shadow: var(--sc-shadow);
113
181
  line-height: 1.6;
114
182
  }
115
183
  .scrolly[data-ready="true"] .scrolly-card { opacity: 0.32; transform: translateX(-10px) scale(0.985); transition: opacity 0.35s, transform 0.35s; }
116
184
  .scrolly[data-ready="true"] .scrolly-step.is-active .scrolly-card { opacity: 1; transform: none; }
117
185
  .scrolly-card > :first-child { margin-top: 0; }
118
186
  .scrolly-card > :last-child { margin-bottom: 0; }
119
- .scrolly-card :is(h1, h2, h3, h4, h5, h6) { display: block; border: 0; padding: 0; margin: 0.2rem 0 0.45rem; font-weight: 600; font-size: 1.35rem; line-height: 1.2; color: var(--nm-indigo); }
187
+ .scrolly-card :is(h1, h2, h3, h4, h5, h6) { display: block; border: 0; padding: 0; margin: 0.2rem 0 0.45rem; font-weight: 600; font-size: 1.35rem; line-height: 1.2; color: var(--sc-heading); }
120
188
  .scrolly-card p { margin: 0 0 0.6rem; font-size: 1rem; }
121
- .scrolly-card :is(p, li, strong, em, span, b, i, td, th) { color: var(--nm-ink); }
122
- .scrolly-card code { background: #e4e0f7; color: var(--nm-indigo); padding: 0.05em 0.4em; border-radius: 5px; font-size: 0.88em; }
123
- .scrolly-card a { color: var(--nm-purple); }
189
+ .scrolly-card :is(p, li, strong, em, span, b, i, td, th) { color: var(--sc-card-text); }
190
+ .scrolly-card code { background: var(--sc-card-code); color: var(--sc-heading); padding: 0.05em 0.4em; border-radius: 5px; font-size: 0.88em; }
191
+ .scrolly-card a { color: var(--sc-link); }
124
192
  .scrolly-card .katex-display { overflow-x: auto; overflow-y: hidden; }
125
193
  .scrolly-card details { margin: 0.5rem 0; }
126
194
  .scrolly-card pre { max-width: 100%; overflow-x: auto; }
127
195
  .scrolly-card .nma-solution-link p { margin: 0.4rem 0; }
128
196
  .scrolly-kicker { margin: 0 0 0.1rem; }
129
- .scrolly-kicker p { margin: 0; font-weight: 600; font-size: 0.72rem; letter-spacing: 0.12em; text-transform: uppercase; color: var(--nm-purple) !important; }
197
+ .scrolly-kicker p { margin: 0; font-weight: 600; font-size: 0.72rem; letter-spacing: 0.12em; text-transform: uppercase; color: var(--sc-heading) !important; }
130
198
 
131
199
  /* ---- stage content ---- */
132
- .scrolly-view { color: var(--nm-text); }
200
+ .scrolly-view { color: var(--sc-text); }
133
201
  .scrolly-view :is(p, li, strong, em, span) { color: inherit; }
134
- .scrolly-view :is(h1, h2, h3, h4) { display: block; border: 0; padding: 0; color: #fff; }
135
- .scrolly-view a { color: var(--nm-mint); }
136
- .scrolly-view img { max-width: 100%; max-height: 70vh; width: auto; height: auto; border-radius: 10px; background: #fff; margin: 0 auto; display: block; }
202
+ .scrolly-view :is(h1, h2, h3, h4) { display: block; border: 0; padding: 0; color: var(--sc-title); }
203
+ .scrolly-view a { color: var(--sc-link); }
204
+ .scrolly-view img { max-width: 100%; max-height: 70vh; width: auto; height: auto; border-radius: 10px; background: #fff; border: 1px solid var(--sc-line); margin: 0 auto; display: block; }
137
205
  .scrolly-view video { max-width: 100%; max-height: 70vh; border-radius: 10px; margin: 0 auto; display: block; }
138
206
  .scrolly-view figure { margin: 0; }
139
- .scrolly-view figcaption, .scrolly-view figcaption * { color: var(--nm-muted); }
207
+ .scrolly-view figcaption, .scrolly-view figcaption * { color: var(--sc-muted); }
140
208
  .scrolly-view iframe { width: 100%; aspect-ratio: 16 / 9; height: auto; border: 0; border-radius: 10px; background: #000; }
141
209
  .scrolly-view .nma-video p, .scrolly-view .nma-slides p { margin: 0.3rem 0; font-size: 0.85rem; }
142
- .scrolly-view .nma-video [role="tab"] { color: var(--nm-lilac); }
210
+ .scrolly-view .nma-video [role="tab"] { color: var(--sc-link); }
143
211
  .scrolly-view .scrolly-runnable { width: 100%; }
144
212
  .scrolly-view.is-list ol, .scrolly-view.is-list ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.75rem; counter-reset: chip; }
145
- .scrolly-view.is-list li { counter-increment: chip; display: grid; grid-template-columns: 2.6rem 1fr; align-items: center; gap: 0.9rem; font-size: 1.05rem; line-height: 1.45; background: rgba(255, 255, 255, 0.06); border: 1px solid rgba(185, 184, 255, 0.2); border-radius: 12px; padding: 0.75rem 1rem; margin: 0; }
146
- .scrolly-view.is-list li::before { content: counter(chip); display: grid; place-items: center; width: 2.6rem; height: 2.6rem; border-radius: 50%; background: var(--nm-mint); color: var(--nm-indigo); font-weight: 700; font-size: 1.1rem; }
213
+ .scrolly-view.is-list li { counter-increment: chip; display: grid; grid-template-columns: 2.6rem 1fr; align-items: center; gap: 0.9rem; font-size: 1.05rem; line-height: 1.45; background: var(--sc-chip-bg); border: 1px solid var(--sc-chip-line); border-radius: 12px; padding: 0.75rem 1rem; margin: 0; }
214
+ .scrolly-view.is-list li::before { content: counter(chip); display: grid; place-items: center; width: 2.6rem; height: 2.6rem; border-radius: 50%; background: var(--sc-accent); color: var(--sc-accent-fg); font-weight: 700; font-size: 1.1rem; }
147
215
  .scrolly-view.is-list li > p { margin: 0; }
148
216
  .scrolly-view.is-list :is(h2, h3, h4) { margin: 0 0 0.5rem; font-size: clamp(1.4rem, 4cqi, 2.2rem); font-weight: 700; }
149
217
 
150
218
  /* ---- hero (title and closing screens) ----
151
219
  MyST re-levels headings, so the title is styled by position, not by tag. */
152
220
  .scrolly-view.is-hero { max-width: 72rem; margin-inline: auto; padding: 0 clamp(0.5rem, 5cqi, 4rem); }
153
- .scrolly-view.is-hero .scrolly-kicker p { font-size: 0.85rem; letter-spacing: 0.32em; color: var(--nm-mint) !important; margin-bottom: 0.6rem; }
221
+ .scrolly-view.is-hero .scrolly-kicker p { font-size: 0.85rem; letter-spacing: 0.32em; color: var(--sc-accent) !important; margin-bottom: 0.6rem; }
154
222
  .scrolly-view.is-hero :is(h1, h2, h3, h4, h5, h6) { display: block; margin: 0; padding: 0; border: 0; }
155
- .scrolly-view.is-hero :is(h1, h2, h3, h4, h5, h6):not(:is(h1, h2, h3, h4, h5, h6) ~ *) { font-weight: 700; font-size: clamp(2rem, 8cqi, 4.6rem); line-height: 1.05; color: #fff; }
156
- .scrolly-view.is-hero :is(h1, h2, h3, h4, h5, h6) ~ :is(h1, h2, h3, h4, h5, h6) { font-weight: 500; font-size: 1.15rem; color: var(--nm-lilac); margin: 0.9rem 0 0.3rem; }
157
- .scrolly-view.is-hero :is(h1, h2, h3, h4, h5, h6) em { font-style: normal; background: linear-gradient(90deg, var(--nm-salmon), var(--nm-mint)); -webkit-background-clip: text; background-clip: text; color: transparent; }
223
+ .scrolly-view.is-hero :is(h1, h2, h3, h4, h5, h6):not(:is(h1, h2, h3, h4, h5, h6) ~ *) { font-weight: 700; font-size: clamp(2rem, 8cqi, 4.6rem); line-height: 1.05; color: var(--sc-title); }
224
+ .scrolly-view.is-hero :is(h1, h2, h3, h4, h5, h6) ~ :is(h1, h2, h3, h4, h5, h6) { font-weight: 500; font-size: 1.15rem; color: var(--sc-link); margin: 0.9rem 0 0.3rem; }
225
+ .scrolly-view.is-hero :is(h1, h2, h3, h4, h5, h6) em { font-style: normal; background: linear-gradient(90deg, var(--sc-warm), var(--sc-accent)); -webkit-background-clip: text; background-clip: text; color: transparent; }
158
226
  .scrolly-view.is-hero p { margin: 0.5rem 0; font-size: 0.98rem; line-height: 1.5; max-width: 52rem; }
159
- .scrolly-view.is-hero p strong { color: var(--nm-salmon); }
227
+ .scrolly-view.is-hero p strong { color: var(--sc-warm-text); }
160
228
 
161
229
  /* ---- narrow sections: stage on top, cards below it ----
162
230
  Keep the 760px breakpoint in sync with WIDE in engine.mjs. */
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "mode": "release",
3
- "version": "0.1.1"
3
+ "version": "0.1.3"
4
4
  }
package/dist/nma.mjs CHANGED
@@ -1,4 +1,4 @@
1
- // @neuromatch/course-kit 0.1.1 MyST plugin (release build). GENERATED, do not edit.
1
+ // @neuromatch/course-kit 0.1.3 MyST plugin (release build). GENERATED, do not edit.
2
2
  import { createRequire as __nmaCreateRequire } from 'node:module';
3
3
  const require = __nmaCreateRequire(import.meta.url);
4
4
  var __create = Object.create;
@@ -7645,7 +7645,7 @@ import { readFileSync as readFileSync2 } from "node:fs";
7645
7645
  // src/shared/assets.mjs
7646
7646
  import { dirname, join as join2, relative as relative2, sep as sep2 } from "node:path";
7647
7647
  import { fileURLToPath } from "node:url";
7648
- var BASE = true ? "https://cdn.jsdelivr.net/npm/@neuromatch/course-kit@0.1.1/dist/assets/" : null;
7648
+ var BASE = true ? "https://cdn.jsdelivr.net/npm/@neuromatch/course-kit@0.1.3/dist/assets/" : null;
7649
7649
  var BUNDLED = true;
7650
7650
  function assetsDir() {
7651
7651
  const here = dirname(fileURLToPath(import.meta.url));
package/dist/nma.yml CHANGED
@@ -1,4 +1,4 @@
1
- # Course kit 0.1.1. Courses add this file with
1
+ # Course kit 0.1.3. Courses add this file with
2
2
  # extend: https://cdn.jsdelivr.net/npm/@neuromatch/course-kit@0.1/dist/nma.yml
3
3
  # and `nma-kit prepare-site` pins that URL to the CLI's exact version on CI.
4
4
  version: 1
@@ -6,4 +6,4 @@ project:
6
6
  jupyter:
7
7
  lite: true
8
8
  plugins:
9
- - https://cdn.jsdelivr.net/npm/@neuromatch/course-kit@0.1.1/dist/nma.mjs
9
+ - https://cdn.jsdelivr.net/npm/@neuromatch/course-kit@0.1.3/dist/nma.mjs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neuromatch/course-kit",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Neuromatch course kit: MyST plugins for Neuromatch courses",
5
5
  "repository": {
6
6
  "type": "git",