munchboka-edutools 0.1.13__py3-none-any.whl → 0.1.19__py3-none-any.whl
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.
Potentially problematic release.
This version of munchboka-edutools might be problematic. Click here for more details.
- munchboka_edutools/_version.py +1 -1
- munchboka_edutools/directives/flashcards.py +231 -0
- munchboka_edutools/directives/plot.py +110 -41
- munchboka_edutools/static/css/flashcards.css +219 -0
- munchboka_edutools/static/css/github-dark.css +76 -41
- munchboka_edutools/static/css/github-light.css +58 -23
- munchboka_edutools/static/css/jeopardy.css +26 -8
- munchboka_edutools/static/js/flashcards.js +199 -0
- munchboka_edutools/static/js/jeopardy.js +24 -11
- {munchboka_edutools-0.1.13.dist-info → munchboka_edutools-0.1.19.dist-info}/METADATA +1 -1
- {munchboka_edutools-0.1.13.dist-info → munchboka_edutools-0.1.19.dist-info}/RECORD +13 -10
- {munchboka_edutools-0.1.13.dist-info → munchboka_edutools-0.1.19.dist-info}/WHEEL +1 -1
- {munchboka_edutools-0.1.13.dist-info → munchboka_edutools-0.1.19.dist-info}/licenses/LICENSE +0 -0
munchboka_edutools/_version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
__all__ = ["__version__"]
|
|
2
|
-
__version__ = "0.1.
|
|
2
|
+
__version__ = "0.1.19"
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import html as _html
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import uuid
|
|
8
|
+
from typing import Any, Dict, List
|
|
9
|
+
|
|
10
|
+
from docutils import nodes
|
|
11
|
+
from docutils.parsers.rst import directives
|
|
12
|
+
from sphinx.util.docutils import SphinxDirective
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FlashcardsDirective(SphinxDirective):
|
|
16
|
+
has_content = True
|
|
17
|
+
required_arguments = 0
|
|
18
|
+
option_spec = {
|
|
19
|
+
"shuffle": directives.flag,
|
|
20
|
+
"show_progress": directives.flag,
|
|
21
|
+
"start_index": directives.nonnegative_int,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
def run(self):
|
|
25
|
+
deck_id = uuid.uuid4().hex
|
|
26
|
+
container_id = f"flashcards-{deck_id}"
|
|
27
|
+
|
|
28
|
+
data = self._parse_deck()
|
|
29
|
+
|
|
30
|
+
source_file = self.state.document["source"]
|
|
31
|
+
source_dir = os.path.dirname(source_file)
|
|
32
|
+
app_src_dir = self.env.srcdir
|
|
33
|
+
depth = os.path.relpath(source_dir, app_src_dir).count(os.sep)
|
|
34
|
+
rel_prefix = "../" * (depth + 1)
|
|
35
|
+
|
|
36
|
+
cfg: Dict[str, Any] = {
|
|
37
|
+
"cards": data["cards"],
|
|
38
|
+
"options": {
|
|
39
|
+
"shuffle": "shuffle" in self.options,
|
|
40
|
+
"show_progress": "show_progress" in self.options,
|
|
41
|
+
"start_index": int(self.options.get("start_index", 0) or 0),
|
|
42
|
+
"staticPrefix": rel_prefix,
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
cfg_str_attr = _html.escape(json.dumps(cfg, ensure_ascii=False), quote=True)
|
|
47
|
+
json_str = json.dumps(cfg, ensure_ascii=False)
|
|
48
|
+
|
|
49
|
+
html = f"""
|
|
50
|
+
<div id="{container_id}" class=\"flashcards-container\" data-config=\"{cfg_str_attr}\">
|
|
51
|
+
<script type=\"application/json\" class=\"flashcards-data\">{json_str}</script>
|
|
52
|
+
</div>
|
|
53
|
+
<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex/dist/katex.min.css\">
|
|
54
|
+
<script defer src=\"https://cdn.jsdelivr.net/npm/katex/dist/katex.min.js\"></script>
|
|
55
|
+
<script defer src=\"https://cdn.jsdelivr.net/npm/katex/dist/contrib/auto-render.min.js\"></script>
|
|
56
|
+
"""
|
|
57
|
+
return [nodes.raw("", html, format="html")]
|
|
58
|
+
|
|
59
|
+
def _parse_deck(self) -> Dict[str, Any]:
|
|
60
|
+
cards: List[Dict[str, Any]] = []
|
|
61
|
+
current: Dict[str, Any] | None = None
|
|
62
|
+
section: str | None = None
|
|
63
|
+
|
|
64
|
+
def flush():
|
|
65
|
+
nonlocal current
|
|
66
|
+
if current is not None:
|
|
67
|
+
for k in ("front", "back"):
|
|
68
|
+
if current.get(k) is None:
|
|
69
|
+
current[k] = ""
|
|
70
|
+
cards.append(current)
|
|
71
|
+
current = None
|
|
72
|
+
|
|
73
|
+
for raw in self.content:
|
|
74
|
+
line = self._process_figures(raw)
|
|
75
|
+
if line is None:
|
|
76
|
+
line = raw
|
|
77
|
+
s = line.rstrip("\n")
|
|
78
|
+
|
|
79
|
+
m = re.match(r"^\s*Q\s*:\s*(.*)$", s, flags=re.IGNORECASE)
|
|
80
|
+
if m:
|
|
81
|
+
flush()
|
|
82
|
+
current = {"front": m.group(1), "back": ""}
|
|
83
|
+
section = "front"
|
|
84
|
+
continue
|
|
85
|
+
|
|
86
|
+
m = re.match(r"^\s*A\s*:\s*(.*)$", s, flags=re.IGNORECASE)
|
|
87
|
+
if m and current is not None:
|
|
88
|
+
current["back"] = (current.get("back") or "") + m.group(1)
|
|
89
|
+
section = "back"
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
if section == "front" and current is not None:
|
|
93
|
+
current["front"] = (current.get("front") or "") + "\n" + s
|
|
94
|
+
continue
|
|
95
|
+
if section == "back" and current is not None:
|
|
96
|
+
current["back"] = (current.get("back") or "") + "\n" + s
|
|
97
|
+
continue
|
|
98
|
+
|
|
99
|
+
flush()
|
|
100
|
+
|
|
101
|
+
for c in cards:
|
|
102
|
+
for key in ("front", "back"):
|
|
103
|
+
val = c.get(key) or ""
|
|
104
|
+
c[key] = self._process_code_blocks(val)
|
|
105
|
+
|
|
106
|
+
return {"cards": cards}
|
|
107
|
+
|
|
108
|
+
def _process_code_blocks(self, text: str) -> str:
|
|
109
|
+
def repl(match):
|
|
110
|
+
code = match.group(2).replace("\\n", "\n")
|
|
111
|
+
lang = match.group(1)
|
|
112
|
+
return f'<pre><code class="{lang}">{code}</code></pre>'
|
|
113
|
+
|
|
114
|
+
pattern = r'<pre><code class="([\\w-]+)">(.*?)</code></pre>'
|
|
115
|
+
return re.sub(pattern, repl, text, flags=re.DOTALL)
|
|
116
|
+
|
|
117
|
+
def _process_figures(self, text: str):
|
|
118
|
+
import shutil
|
|
119
|
+
import json as _json
|
|
120
|
+
|
|
121
|
+
if not hasattr(self, "_image_counter"):
|
|
122
|
+
self._image_counter = 0
|
|
123
|
+
|
|
124
|
+
def _parse_figure_options(alt_text: str) -> Dict[str, Any]:
|
|
125
|
+
opts: Dict[str, Any] = {}
|
|
126
|
+
s = (alt_text or "").strip()
|
|
127
|
+
|
|
128
|
+
def parse_pairs(content: str):
|
|
129
|
+
for m in re.finditer(r'(\\w+)\\s*=\\s*(?:"([^"]*)"|\'([^\']*)\'|([^\\s}]+))', content):
|
|
130
|
+
val = m.group(2) or m.group(3) or m.group(4) or ""
|
|
131
|
+
opts[m.group(1)] = val
|
|
132
|
+
|
|
133
|
+
if s.startswith("{") and s.endswith("}"):
|
|
134
|
+
inner = s[1:-1].strip()
|
|
135
|
+
ok = False
|
|
136
|
+
try:
|
|
137
|
+
js = s
|
|
138
|
+
js = re.sub(r"(\\w+)\\s*:", r'"\\1":', js)
|
|
139
|
+
js = re.sub(r':\\s*([^",}]+)', r': "\\1"', js)
|
|
140
|
+
opts.update(_json.loads(js))
|
|
141
|
+
ok = True
|
|
142
|
+
except Exception:
|
|
143
|
+
ok = False
|
|
144
|
+
if not ok:
|
|
145
|
+
parse_pairs(inner)
|
|
146
|
+
else:
|
|
147
|
+
parse_pairs(s)
|
|
148
|
+
if not opts and s:
|
|
149
|
+
opts["alt"] = s
|
|
150
|
+
return opts
|
|
151
|
+
|
|
152
|
+
def _build_figure_html(html_img_path: str, options: Dict[str, Any]) -> str:
|
|
153
|
+
user_opts = dict(options or {})
|
|
154
|
+
user_class = user_opts.pop("class", "").strip()
|
|
155
|
+
classes = "flashcard-image adaptive-figure" + (f" {user_class}" if user_class else "")
|
|
156
|
+
|
|
157
|
+
alt_text = user_opts.pop("alt", "Figure")
|
|
158
|
+
title = user_opts.pop("title", None)
|
|
159
|
+
width = user_opts.pop("width", None)
|
|
160
|
+
height = user_opts.pop("height", None)
|
|
161
|
+
extra_style = user_opts.pop("style", None)
|
|
162
|
+
|
|
163
|
+
def _normalize_wh(val: Any) -> str:
|
|
164
|
+
s = str(val).strip()
|
|
165
|
+
if re.fullmatch(r"\\d+(?:\\.\\d+)?", s):
|
|
166
|
+
return f"{s}px"
|
|
167
|
+
s = re.sub(r"\\s+(?=(px|%|em|rem|vh|vw)$)", "", s)
|
|
168
|
+
return s
|
|
169
|
+
|
|
170
|
+
styles: List[str] = []
|
|
171
|
+
if width is not None:
|
|
172
|
+
styles.append(f"width: {_normalize_wh(width)};")
|
|
173
|
+
if height is not None:
|
|
174
|
+
styles.append(f"height: {_normalize_wh(height)};")
|
|
175
|
+
if extra_style:
|
|
176
|
+
styles.append(str(extra_style))
|
|
177
|
+
|
|
178
|
+
attrs = [f'src="{html_img_path}"', f'class="{classes}"', f'alt="{alt_text}"']
|
|
179
|
+
if title:
|
|
180
|
+
attrs.append(f'title="{title}"')
|
|
181
|
+
if styles:
|
|
182
|
+
style_str = " ".join(styles)
|
|
183
|
+
attrs.append(f'style="{style_str}"')
|
|
184
|
+
for k, v in user_opts.items():
|
|
185
|
+
if k not in {"src", "class", "alt", "title", "width", "height", "style"}:
|
|
186
|
+
attrs.append(f'{k}="{v}"')
|
|
187
|
+
img = f"<img {' '.join(attrs)} >"
|
|
188
|
+
return f'<div class="flashcard-image-container">{img}</div>'
|
|
189
|
+
|
|
190
|
+
def replace(m):
|
|
191
|
+
alt_or_opts = m.group(1).strip()
|
|
192
|
+
raw_src = m.group(2)
|
|
193
|
+
self._image_counter += 1
|
|
194
|
+
options = _parse_figure_options(alt_or_opts)
|
|
195
|
+
|
|
196
|
+
source_file = self.state.document["source"]
|
|
197
|
+
source_dir = os.path.dirname(source_file)
|
|
198
|
+
app_src_dir = self.env.srcdir
|
|
199
|
+
|
|
200
|
+
abs_fig_src = os.path.normpath(os.path.join(source_dir, raw_src))
|
|
201
|
+
if not os.path.exists(abs_fig_src):
|
|
202
|
+
return f'<img src="{raw_src}" class="flashcard-image adaptive-figure" alt="Figure (missing)">'
|
|
203
|
+
|
|
204
|
+
relative_doc_path = os.path.relpath(source_dir, app_src_dir)
|
|
205
|
+
figure_dest_dir = os.path.join(app_src_dir, "_static", "figurer", relative_doc_path)
|
|
206
|
+
os.makedirs(figure_dest_dir, exist_ok=True)
|
|
207
|
+
|
|
208
|
+
rel_path_from_source = os.path.relpath(abs_fig_src, source_dir)
|
|
209
|
+
safe_path = rel_path_from_source.replace(os.sep, "_").replace("/", "_")
|
|
210
|
+
base, ext = os.path.splitext(safe_path)
|
|
211
|
+
fig_filename = f"flash_{self._image_counter}_{base}{ext}"
|
|
212
|
+
fig_dest_path = os.path.join(figure_dest_dir, fig_filename)
|
|
213
|
+
shutil.copy2(abs_fig_src, fig_dest_path)
|
|
214
|
+
|
|
215
|
+
depth = os.path.relpath(source_dir, app_src_dir).count(os.sep)
|
|
216
|
+
rel_prefix = "../" * (depth + 1)
|
|
217
|
+
html_img_path = f"{rel_prefix}_static/figurer/{relative_doc_path}/{fig_filename}"
|
|
218
|
+
return _build_figure_html(html_img_path, options)
|
|
219
|
+
|
|
220
|
+
return re.sub(r"!\[([^\]]*)\]\(([^)]+)\)", replace, text)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def setup(app):
|
|
224
|
+
app.add_directive("flashcards", FlashcardsDirective)
|
|
225
|
+
try:
|
|
226
|
+
app.add_css_file("munchboka/css/flashcards.css")
|
|
227
|
+
app.add_js_file("munchboka/js/flashcards.js")
|
|
228
|
+
app.add_css_file("munchboka/css/general_style.css")
|
|
229
|
+
except Exception:
|
|
230
|
+
pass
|
|
231
|
+
return {"version": "0.1", "parallel_read_safe": True, "parallel_write_safe": True}
|
|
@@ -823,30 +823,48 @@ class PlotDirective(SphinxDirective):
|
|
|
823
823
|
if s0 in _num_cache:
|
|
824
824
|
return _num_cache[s0]
|
|
825
825
|
s = s0
|
|
826
|
-
# Replace user function label calls iteratively
|
|
827
|
-
#
|
|
828
|
-
pat = re.compile(r"([A-Za-z_][A-Za-z0-9_]*)\(")
|
|
829
|
-
# Simpler: fallback to previous non-nested approach but broaden attempt; for safety keep prior pattern.
|
|
830
|
-
pat_simple = re.compile(r"([A-Za-z_][A-Za-z0-9_]*)\(([^()]+)\)")
|
|
826
|
+
# Replace user function label calls iteratively, allowing
|
|
827
|
+
# general expressions as arguments, e.g. f(2 - sqrt(2)).
|
|
831
828
|
for _ in range(50):
|
|
832
|
-
m =
|
|
829
|
+
m = re.search(r"([A-Za-z_][A-Za-z0-9_]*)\(", s)
|
|
833
830
|
if not m:
|
|
834
831
|
break
|
|
835
|
-
lbl
|
|
836
|
-
if lbl in fn_labels_list:
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
832
|
+
lbl = m.group(1)
|
|
833
|
+
if lbl not in fn_labels_list:
|
|
834
|
+
# Skip this label, look for the next one
|
|
835
|
+
start_next = m.start() + 1
|
|
836
|
+
n = re.search(r"([A-Za-z_][A-Za-z0-9_]*)\(", s[start_next:])
|
|
837
|
+
if not n:
|
|
838
|
+
break
|
|
839
|
+
m = n
|
|
840
|
+
lbl = m.group(1)
|
|
841
|
+
if lbl not in fn_labels_list:
|
|
842
|
+
break
|
|
843
|
+
# Find matching closing parenthesis for this call
|
|
844
|
+
start = m.end() - 1 # position of '('
|
|
845
|
+
depth = 0
|
|
846
|
+
end = None
|
|
847
|
+
for i in range(start, len(s)):
|
|
848
|
+
if s[i] == "(":
|
|
849
|
+
depth += 1
|
|
850
|
+
elif s[i] == ")":
|
|
851
|
+
depth -= 1
|
|
852
|
+
if depth == 0:
|
|
853
|
+
end = i
|
|
854
|
+
break
|
|
855
|
+
if end is None:
|
|
856
|
+
break
|
|
857
|
+
arg_expr = s[start + 1 : end]
|
|
858
|
+
try:
|
|
859
|
+
arg_val = _eval_expr(arg_expr)
|
|
860
|
+
idx = fn_labels_list.index(lbl)
|
|
861
|
+
f = functions[idx]
|
|
862
|
+
yv = float(f([arg_val])[0])
|
|
863
|
+
s = s[: m.start()] + f"{yv}" + s[end + 1 :]
|
|
864
|
+
continue
|
|
865
|
+
except Exception:
|
|
866
|
+
# leave unresolved for sympy if evaluation fails
|
|
867
|
+
break
|
|
850
868
|
allowed = {
|
|
851
869
|
k: getattr(sympy, k)
|
|
852
870
|
for k in [
|
|
@@ -862,6 +880,7 @@ class PlotDirective(SphinxDirective):
|
|
|
862
880
|
"acos",
|
|
863
881
|
"atan",
|
|
864
882
|
"Rational",
|
|
883
|
+
"erf",
|
|
865
884
|
]
|
|
866
885
|
if hasattr(sympy, k)
|
|
867
886
|
}
|
|
@@ -885,17 +904,21 @@ class PlotDirective(SphinxDirective):
|
|
|
885
904
|
except Exception:
|
|
886
905
|
pass
|
|
887
906
|
else:
|
|
888
|
-
# Support dynamic evaluation referencing previously defined function labels, e.g.
|
|
907
|
+
# Support dynamic evaluation referencing previously defined function labels, e.g.
|
|
908
|
+
# (5, f(5)) or (2 - sqrt(2), f(2 - sqrt(2))).
|
|
889
909
|
# Simple pattern match for a parenthesized pair allowing arbitrary (non-comma) inner expressions.
|
|
890
910
|
ps = str(p).strip()
|
|
891
911
|
m_pair = re.match(r"^\(\s*([^,]+?)\s*,\s*([^,]+?)\s*\)$", ps)
|
|
892
912
|
if m_pair:
|
|
893
913
|
x_raw = m_pair.group(1).strip()
|
|
894
914
|
y_raw = m_pair.group(2).strip()
|
|
915
|
+
# Evaluate x and y as full expressions first; this already
|
|
916
|
+
# supports f(2 - sqrt(2)) via _eval_expr.
|
|
895
917
|
try:
|
|
896
918
|
x_val = _eval_expr(x_raw)
|
|
897
919
|
except Exception:
|
|
898
|
-
# If x itself references a function call label(arg)
|
|
920
|
+
# If x itself references a function call label(arg),
|
|
921
|
+
# fall back to a simple numeric-argument pattern.
|
|
899
922
|
m_fx = re.match(
|
|
900
923
|
r"^([A-Za-z_][A-Za-z0-9_]*)\(\s*([+-]?(?:\d+(?:\.\d+)?))\s*\)$",
|
|
901
924
|
x_raw,
|
|
@@ -910,7 +933,9 @@ class PlotDirective(SphinxDirective):
|
|
|
910
933
|
continue # give up on this point
|
|
911
934
|
else:
|
|
912
935
|
continue
|
|
913
|
-
|
|
936
|
+
|
|
937
|
+
# y may be a direct expression or a function label call
|
|
938
|
+
# like f(2 - sqrt(2)). Try full expression evaluation first.
|
|
914
939
|
try:
|
|
915
940
|
y_val = _eval_expr(y_raw)
|
|
916
941
|
except Exception:
|
|
@@ -935,8 +960,10 @@ class PlotDirective(SphinxDirective):
|
|
|
935
960
|
except Exception:
|
|
936
961
|
pass
|
|
937
962
|
|
|
938
|
-
# Tangents:
|
|
939
|
-
# "(x0, f(x0)), dashed, red"
|
|
963
|
+
# Tangents: can be specified either as
|
|
964
|
+
# - "(x0, f(x0))" or "(x0, f(x0)), dashed, red"
|
|
965
|
+
# - "x0, f [, style] [, color]" (preferred, simpler form)
|
|
966
|
+
# In all cases, we draw the tangent to the function with label f at x0.
|
|
940
967
|
tangent_vals: List[Tuple[float, float, float, str | None, str | None]] = (
|
|
941
968
|
[]
|
|
942
969
|
) # (a, b, x0, style, color)
|
|
@@ -966,7 +993,52 @@ class PlotDirective(SphinxDirective):
|
|
|
966
993
|
if not parts_t:
|
|
967
994
|
continue
|
|
968
995
|
|
|
969
|
-
#
|
|
996
|
+
# Helper: parse optional style/color tokens
|
|
997
|
+
def _parse_tangent_style(tokens: List[str]) -> Tuple[str | None, str | None]:
|
|
998
|
+
style_t: str | None = None
|
|
999
|
+
color_t: str | None = None
|
|
1000
|
+
_allowed_styles_t = {"solid", "dotted", "dashed", "dashdot"}
|
|
1001
|
+
for extra in tokens:
|
|
1002
|
+
tok = extra.strip().strip("'\"")
|
|
1003
|
+
low = tok.lower()
|
|
1004
|
+
if low in _allowed_styles_t and style_t is None:
|
|
1005
|
+
style_t = low
|
|
1006
|
+
elif color_t is None:
|
|
1007
|
+
color_t = tok
|
|
1008
|
+
return style_t, color_t
|
|
1009
|
+
|
|
1010
|
+
# First try the simple form: x0, f [, style] [, color]
|
|
1011
|
+
simple_ok = False
|
|
1012
|
+
if len(parts_t) >= 2:
|
|
1013
|
+
x0_raw = parts_t[0].strip()
|
|
1014
|
+
f_lbl_raw = parts_t[1].strip()
|
|
1015
|
+
# f_lbl_raw should look like a bare label, e.g. "f"
|
|
1016
|
+
if re.match(r"^[A-Za-z_][A-Za-z0-9_]*$", f_lbl_raw):
|
|
1017
|
+
lbl = f_lbl_raw
|
|
1018
|
+
if lbl in fn_labels_list:
|
|
1019
|
+
try:
|
|
1020
|
+
x0 = _eval_expr(x0_raw)
|
|
1021
|
+
style_t, color_t = _parse_tangent_style(parts_t[2:])
|
|
1022
|
+
idx = fn_labels_list.index(lbl)
|
|
1023
|
+
f = functions[idx]
|
|
1024
|
+
import numpy as _np_t
|
|
1025
|
+
|
|
1026
|
+
# Finite-difference derivative around x0
|
|
1027
|
+
h = max(1e-5, 1e-5 * (1.0 + abs(x0)))
|
|
1028
|
+
y_plus = float(f(_np_t.array([x0 + h], dtype=float))[0])
|
|
1029
|
+
y_minus = float(f(_np_t.array([x0 - h], dtype=float))[0])
|
|
1030
|
+
a_t = (y_plus - y_minus) / (2 * h)
|
|
1031
|
+
y0 = float(f(_np_t.array([x0], dtype=float))[0])
|
|
1032
|
+
b_t = y0 - a_t * x0
|
|
1033
|
+
tangent_vals.append((a_t, b_t, x0, style_t, color_t))
|
|
1034
|
+
simple_ok = True
|
|
1035
|
+
except Exception:
|
|
1036
|
+
pass
|
|
1037
|
+
|
|
1038
|
+
if simple_ok:
|
|
1039
|
+
continue
|
|
1040
|
+
|
|
1041
|
+
# Fallback: old form where first part is the point pair (x0, f(x0))
|
|
970
1042
|
m_pair = re.match(r"^\(\s*([^,]+?)\s*,\s*([^,]+?)\s*\)$", parts_t[0])
|
|
971
1043
|
if not m_pair:
|
|
972
1044
|
continue
|
|
@@ -985,20 +1057,10 @@ class PlotDirective(SphinxDirective):
|
|
|
985
1057
|
continue
|
|
986
1058
|
try:
|
|
987
1059
|
x0 = _eval_expr(x_raw)
|
|
988
|
-
|
|
1060
|
+
_ = _eval_expr(arg_expr) # just ensure it's valid
|
|
989
1061
|
except Exception:
|
|
990
1062
|
continue
|
|
991
|
-
|
|
992
|
-
style_t: str | None = None
|
|
993
|
-
color_t: str | None = None
|
|
994
|
-
_allowed_styles_t = {"solid", "dotted", "dashed", "dashdot"}
|
|
995
|
-
for extra in parts_t[1:]:
|
|
996
|
-
tok = extra.strip().strip("'\"")
|
|
997
|
-
low = tok.lower()
|
|
998
|
-
if low in _allowed_styles_t and style_t is None:
|
|
999
|
-
style_t = low
|
|
1000
|
-
elif color_t is None:
|
|
1001
|
-
color_t = tok
|
|
1063
|
+
style_t, color_t = _parse_tangent_style(parts_t[1:])
|
|
1002
1064
|
try:
|
|
1003
1065
|
idx = fn_labels_list.index(lbl)
|
|
1004
1066
|
f = functions[idx]
|
|
@@ -2435,10 +2497,17 @@ class PlotDirective(SphinxDirective):
|
|
|
2435
2497
|
for a_l, b_l, st_l, col_l in line_vals:
|
|
2436
2498
|
_draw_line(a_l, b_l, st_l, col_l)
|
|
2437
2499
|
|
|
2438
|
-
# Tangents: allow optional style/color, with dashed
|
|
2500
|
+
# Tangents: allow optional style/color, with dashed red default
|
|
2439
2501
|
for a_t, b_t, _x0, st_t, col_t in tangent_vals:
|
|
2440
2502
|
style_use = st_t or "dashed"
|
|
2441
|
-
|
|
2503
|
+
# Prefer the plotmath color palette for the default
|
|
2504
|
+
if col_t:
|
|
2505
|
+
color_use = col_t
|
|
2506
|
+
else:
|
|
2507
|
+
_mapped_red = (
|
|
2508
|
+
plotmath.COLORS.get("red") if hasattr(plotmath, "COLORS") else None
|
|
2509
|
+
)
|
|
2510
|
+
color_use = _mapped_red or "red"
|
|
2442
2511
|
_draw_line(a_t, b_t, style_use, color_use)
|
|
2443
2512
|
|
|
2444
2513
|
# Bars
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--flashcard-bg: #ffffff;
|
|
3
|
+
--flashcard-border: #d1d5db;
|
|
4
|
+
--flashcard-shadow: rgba(15, 23, 42, 0.12);
|
|
5
|
+
--flashcard-text: #111827;
|
|
6
|
+
--flashcard-muted: #6b7280;
|
|
7
|
+
--flashcard-accent: #0ea5e9;
|
|
8
|
+
--flashcard-accent-soft: rgba(14, 165, 233, 0.08);
|
|
9
|
+
--flashcard-control-bg: #f9fafb;
|
|
10
|
+
--flashcard-control-border: #d1d5db;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
[data-mode="dark"] {
|
|
14
|
+
--flashcard-bg: #020617;
|
|
15
|
+
--flashcard-border: #1f2937;
|
|
16
|
+
--flashcard-shadow: rgba(15, 23, 42, 0.7);
|
|
17
|
+
--flashcard-text: #e5e7eb;
|
|
18
|
+
--flashcard-muted: #9ca3af;
|
|
19
|
+
--flashcard-accent: #38bdf8;
|
|
20
|
+
--flashcard-accent-soft: rgba(56, 189, 248, 0.15);
|
|
21
|
+
--flashcard-control-bg: #020617;
|
|
22
|
+
--flashcard-control-border: #374151;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
[data-mode="auto"] {}
|
|
26
|
+
|
|
27
|
+
@media (prefers-color-scheme: dark) {
|
|
28
|
+
[data-mode="auto"] {
|
|
29
|
+
--flashcard-bg: #020617;
|
|
30
|
+
--flashcard-border: #1f2937;
|
|
31
|
+
--flashcard-shadow: rgba(15, 23, 42, 0.7);
|
|
32
|
+
--flashcard-text: #e5e7eb;
|
|
33
|
+
--flashcard-muted: #9ca3af;
|
|
34
|
+
--flashcard-accent: #38bdf8;
|
|
35
|
+
--flashcard-accent-soft: rgba(56, 189, 248, 0.15);
|
|
36
|
+
--flashcard-control-bg: #020617;
|
|
37
|
+
--flashcard-control-border: #374151;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.flashcards-root {
|
|
42
|
+
display: flex;
|
|
43
|
+
flex-direction: column;
|
|
44
|
+
gap: 0.75rem;
|
|
45
|
+
margin: 1.5rem 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.flashcards-card-shell {
|
|
49
|
+
display: flex;
|
|
50
|
+
justify-content: center;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.flashcards-card {
|
|
54
|
+
position: relative;
|
|
55
|
+
width: min(640px, 100%);
|
|
56
|
+
min-height: 160px;
|
|
57
|
+
padding: 1.5rem 1.75rem;
|
|
58
|
+
border-radius: 0.9rem;
|
|
59
|
+
border: 1px solid var(--flashcard-border);
|
|
60
|
+
background: radial-gradient(circle at top left, var(--flashcard-accent-soft), transparent 55%), var(--flashcard-bg);
|
|
61
|
+
box-shadow: 0 18px 35px -22px var(--flashcard-shadow);
|
|
62
|
+
color: var(--flashcard-text);
|
|
63
|
+
overflow: hidden;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.flashcards-root[data-side="back"] .flashcards-card {
|
|
67
|
+
background: radial-gradient(circle at top left, rgba(34, 197, 94, 0.12), transparent 55%), var(--flashcard-bg);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.flashcards-card-flip {
|
|
71
|
+
animation: flashcards-flip 0.22s ease-out;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@keyframes flashcards-flip {
|
|
75
|
+
0% {
|
|
76
|
+
transform: translateY(0) rotateY(0deg);
|
|
77
|
+
opacity: 1;
|
|
78
|
+
}
|
|
79
|
+
50% {
|
|
80
|
+
transform: translateY(-4px) rotateY(7deg);
|
|
81
|
+
opacity: 0.85;
|
|
82
|
+
}
|
|
83
|
+
100% {
|
|
84
|
+
transform: translateY(0) rotateY(0deg);
|
|
85
|
+
opacity: 1;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.flashcards-face {
|
|
90
|
+
transition: opacity 0.24s ease, transform 0.24s ease;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.flashcards-face.back {
|
|
94
|
+
display: none;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.flashcards-root[data-side="back"] .flashcards-face.front {
|
|
98
|
+
display: none;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.flashcards-root[data-side="back"] .flashcards-face.back {
|
|
102
|
+
display: block;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.flashcards-header {
|
|
106
|
+
display: flex;
|
|
107
|
+
justify-content: space-between;
|
|
108
|
+
align-items: center;
|
|
109
|
+
margin-bottom: 0.75rem;
|
|
110
|
+
font-size: 0.8rem;
|
|
111
|
+
text-transform: uppercase;
|
|
112
|
+
letter-spacing: 0.08em;
|
|
113
|
+
color: var(--flashcard-muted);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.flashcards-progress {
|
|
117
|
+
font-weight: 600;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.flashcards-pill {
|
|
121
|
+
padding: 0.15rem 0.6rem;
|
|
122
|
+
border-radius: 999px;
|
|
123
|
+
border: 1px solid var(--flashcard-border);
|
|
124
|
+
background-color: rgba(15, 23, 42, 0.02);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.flashcards-body {
|
|
128
|
+
font-size: 0.98rem;
|
|
129
|
+
line-height: 1.6;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.flashcards-body pre {
|
|
133
|
+
margin: 0.5rem 0 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.flashcards-body img.flashcard-image {
|
|
137
|
+
max-width: 100%;
|
|
138
|
+
height: auto;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.flashcards-footer {
|
|
142
|
+
display: flex;
|
|
143
|
+
justify-content: space-between;
|
|
144
|
+
align-items: center;
|
|
145
|
+
margin-top: 0.75rem;
|
|
146
|
+
font-size: 0.8rem;
|
|
147
|
+
color: var(--flashcard-muted);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.flashcards-controls {
|
|
151
|
+
display: flex;
|
|
152
|
+
gap: 0.5rem;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.flashcards-btn {
|
|
156
|
+
border-radius: 999px;
|
|
157
|
+
border: 1px solid var(--flashcard-control-border);
|
|
158
|
+
background-color: var(--flashcard-control-bg);
|
|
159
|
+
color: var(--flashcard-text);
|
|
160
|
+
font-size: 0.8rem;
|
|
161
|
+
padding: 0.4rem 0.75rem;
|
|
162
|
+
display: inline-flex;
|
|
163
|
+
align-items: center;
|
|
164
|
+
gap: 0.25rem;
|
|
165
|
+
cursor: pointer;
|
|
166
|
+
transition: background-color 0.17s ease, transform 0.1s ease, box-shadow 0.1s ease, border-color 0.17s ease;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.flashcards-btn-primary {
|
|
170
|
+
background-color: var(--flashcard-accent-soft);
|
|
171
|
+
border-color: rgba(56, 189, 248, 0.4);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.flashcards-btn:hover {
|
|
175
|
+
background-color: rgba(15, 23, 42, 0.03);
|
|
176
|
+
transform: translateY(-1px);
|
|
177
|
+
box-shadow: 0 8px 18px -12px var(--flashcard-shadow);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.flashcards-btn:active {
|
|
181
|
+
transform: translateY(0);
|
|
182
|
+
box-shadow: none;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.flashcards-btn:disabled {
|
|
186
|
+
opacity: 0.55;
|
|
187
|
+
cursor: default;
|
|
188
|
+
box-shadow: none;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.flashcards-hotkeys {
|
|
192
|
+
display: none;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
@media (min-width: 640px) {
|
|
196
|
+
.flashcards-hotkeys {
|
|
197
|
+
display: inline-flex;
|
|
198
|
+
gap: 0.4rem;
|
|
199
|
+
align-items: center;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.flashcards-hotkey-pill {
|
|
203
|
+
padding: 0.1rem 0.45rem;
|
|
204
|
+
border-radius: 999px;
|
|
205
|
+
border: 1px solid rgba(148, 163, 184, 0.8);
|
|
206
|
+
font-size: 0.7rem;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
@media (max-width: 640px) {
|
|
211
|
+
.flashcards-card {
|
|
212
|
+
padding: 1.15rem 1.25rem;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.flashcards-controls {
|
|
216
|
+
flex-wrap: wrap;
|
|
217
|
+
justify-content: flex-end;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
@@ -1,44 +1,79 @@
|
|
|
1
|
-
/*
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
1
|
+
/* GitHub-like DARK theme for rendered code blocks.
|
|
2
|
+
Scoped to dark-mode attributes so it can coexist with a light theme.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
html.dark-mode,
|
|
6
|
+
body.dark-mode,
|
|
7
|
+
html[data-theme='dark'],
|
|
8
|
+
body[data-theme='dark'],
|
|
9
|
+
[data-mode='dark'] {
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
html.dark-mode pre, html.dark-mode code,
|
|
13
|
+
body.dark-mode pre, body.dark-mode code,
|
|
14
|
+
html[data-theme='dark'] pre, html[data-theme='dark'] code,
|
|
15
|
+
body[data-theme='dark'] pre, body[data-theme='dark'] code,
|
|
16
|
+
[data-mode='dark'] pre, [data-mode='dark'] code {
|
|
17
|
+
background-color: #0d1117;
|
|
18
|
+
color: #e6edf3;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
html.dark-mode pre, body.dark-mode pre,
|
|
22
|
+
html[data-theme='dark'] pre, body[data-theme='dark'] pre,
|
|
23
|
+
[data-mode='dark'] pre {
|
|
24
|
+
border: 1px solid #30363d;
|
|
25
|
+
border-radius: 6px;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* Basic token colors assuming highlight.js / pygments style classes */
|
|
29
|
+
html.dark-mode .hljs-comment,
|
|
30
|
+
body.dark-mode .hljs-comment,
|
|
31
|
+
html[data-theme='dark'] .hljs-comment,
|
|
32
|
+
body[data-theme='dark'] .hljs-comment,
|
|
33
|
+
[data-mode='dark'] .hljs-comment {
|
|
34
|
+
color: #8b949e;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
html.dark-mode .hljs-keyword,
|
|
38
|
+
body.dark-mode .hljs-keyword,
|
|
39
|
+
html[data-theme='dark'] .hljs-keyword,
|
|
40
|
+
body[data-theme='dark'] .hljs-keyword,
|
|
41
|
+
[data-mode='dark'] .hljs-keyword {
|
|
42
|
+
color: #ff7b72;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
html.dark-mode .hljs-string,
|
|
46
|
+
body.dark-mode .hljs-string,
|
|
47
|
+
html[data-theme='dark'] .hljs-string,
|
|
48
|
+
body[data-theme='dark'] .hljs-string,
|
|
49
|
+
[data-mode='dark'] .hljs-string {
|
|
50
|
+
color: #7ee787;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
html.dark-mode .hljs-number,
|
|
54
|
+
body.dark-mode .hljs-number,
|
|
55
|
+
html[data-theme='dark'] .hljs-number,
|
|
56
|
+
body[data-theme='dark'] .hljs-number,
|
|
57
|
+
[data-mode='dark'] .hljs-number {
|
|
58
|
+
color: #79c0ff;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
html.dark-mode .hljs-title,
|
|
62
|
+
body.dark-mode .hljs-title,
|
|
63
|
+
html[data-theme='dark'] .hljs-title,
|
|
64
|
+
body[data-theme='dark'] .hljs-title,
|
|
65
|
+
[data-mode='dark'] .hljs-title {
|
|
66
|
+
color: #d2a8ff;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Fallback for generic code tokens used by sphinx/highlight.js */
|
|
70
|
+
html.dark-mode .nb-output pre,
|
|
71
|
+
body.dark-mode .nb-output pre,
|
|
72
|
+
html[data-theme='dark'] .nb-output pre,
|
|
73
|
+
body[data-theme='dark'] .nb-output pre,
|
|
74
|
+
[data-mode='dark'] .nb-output pre {
|
|
75
|
+
background-color: #161b22;
|
|
76
|
+
}
|
|
42
77
|
|
|
43
78
|
.cm-s-github-dark .cm-literal {
|
|
44
79
|
color: #a5d6ff;
|
|
@@ -1,43 +1,78 @@
|
|
|
1
|
-
/*
|
|
2
|
-
|
|
1
|
+
/* GitHub-like LIGHT theme for rendered code blocks.
|
|
2
|
+
Scoped to light-mode attributes so it can coexist with a dark theme.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
html.light-mode,
|
|
6
|
+
body.light-mode,
|
|
7
|
+
html[data-theme='light'],
|
|
8
|
+
body[data-theme='light'],
|
|
9
|
+
[data-mode='light'] {
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
html.light-mode pre, html.light-mode code,
|
|
13
|
+
body.light-mode pre, body.light-mode code,
|
|
14
|
+
html[data-theme='light'] pre, html[data-theme='light'] code,
|
|
15
|
+
body[data-theme='light'] pre, body[data-theme='light'] code,
|
|
16
|
+
[data-mode='light'] pre, [data-mode='light'] code {
|
|
3
17
|
background-color: #ffffff;
|
|
4
18
|
color: #24292f;
|
|
5
19
|
}
|
|
6
20
|
|
|
7
|
-
.
|
|
8
|
-
|
|
9
|
-
|
|
21
|
+
html.light-mode pre, body.light-mode pre,
|
|
22
|
+
html[data-theme='light'] pre, body[data-theme='light'] pre,
|
|
23
|
+
[data-mode='light'] pre {
|
|
24
|
+
border: 1px solid #d1d5da;
|
|
25
|
+
border-radius: 6px;
|
|
10
26
|
}
|
|
11
27
|
|
|
12
|
-
|
|
28
|
+
/* Basic token colors assuming highlight.js / pygments style classes */
|
|
29
|
+
html.light-mode .hljs-comment,
|
|
30
|
+
body.light-mode .hljs-comment,
|
|
31
|
+
html[data-theme='light'] .hljs-comment,
|
|
32
|
+
body[data-theme='light'] .hljs-comment,
|
|
33
|
+
[data-mode='light'] .hljs-comment {
|
|
13
34
|
color: #6e7781;
|
|
14
35
|
}
|
|
15
36
|
|
|
16
|
-
.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
37
|
+
html.light-mode .hljs-keyword,
|
|
38
|
+
body.light-mode .hljs-keyword,
|
|
39
|
+
html[data-theme='light'] .hljs-keyword,
|
|
40
|
+
body[data-theme='light'] .hljs-keyword,
|
|
41
|
+
[data-mode='light'] .hljs-keyword {
|
|
42
|
+
color: #cf222e;
|
|
22
43
|
}
|
|
23
44
|
|
|
24
|
-
|
|
25
|
-
.
|
|
26
|
-
|
|
27
|
-
|
|
45
|
+
html.light-mode .hljs-string,
|
|
46
|
+
body.light-mode .hljs-string,
|
|
47
|
+
html[data-theme='light'] .hljs-string,
|
|
48
|
+
body[data-theme='light'] .hljs-string,
|
|
49
|
+
[data-mode='light'] .hljs-string {
|
|
50
|
+
color: #116329;
|
|
28
51
|
}
|
|
29
52
|
|
|
30
|
-
.
|
|
31
|
-
|
|
53
|
+
html.light-mode .hljs-number,
|
|
54
|
+
body.light-mode .hljs-number,
|
|
55
|
+
html[data-theme='light'] .hljs-number,
|
|
56
|
+
body[data-theme='light'] .hljs-number,
|
|
57
|
+
[data-mode='light'] .hljs-number {
|
|
58
|
+
color: #0550ae;
|
|
32
59
|
}
|
|
33
60
|
|
|
34
|
-
.
|
|
35
|
-
|
|
61
|
+
html.light-mode .hljs-title,
|
|
62
|
+
body.light-mode .hljs-title,
|
|
63
|
+
html[data-theme='light'] .hljs-title,
|
|
64
|
+
body[data-theme='light'] .hljs-title,
|
|
65
|
+
[data-mode='light'] .hljs-title {
|
|
66
|
+
color: #0550ae;
|
|
36
67
|
}
|
|
37
68
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
69
|
+
/* Fallback for generic code tokens used by sphinx/highlight.js */
|
|
70
|
+
html.light-mode .nb-output pre,
|
|
71
|
+
body.light-mode .nb-output pre,
|
|
72
|
+
html[data-theme='light'] .nb-output pre,
|
|
73
|
+
body[data-theme='light'] .nb-output pre,
|
|
74
|
+
[data-mode='light'] .nb-output pre {
|
|
75
|
+
background-color: #f6f8fa;
|
|
41
76
|
}
|
|
42
77
|
|
|
43
78
|
.cm-s-github-light .cm-literal {
|
|
@@ -307,29 +307,31 @@ html[data-theme='dark'] .jeopardy-modal-body,
|
|
|
307
307
|
body[data-theme='dark'] .jeopardy-modal-body,
|
|
308
308
|
[data-mode='dark'] .jeopardy-modal-body { background: var(--jp-modal-bg) !important; color: var(--jp-modal-fg) !important; }
|
|
309
309
|
|
|
310
|
-
/* Code inside Jeopardy content should follow the
|
|
311
|
-
|
|
310
|
+
/* Code inside Jeopardy content should fully follow the site's
|
|
311
|
+
syntax-highlighting theme. Do not touch colors, only allow
|
|
312
|
+
small layout tweaks if needed. */
|
|
312
313
|
.jeopardy-modal-body pre,
|
|
313
314
|
.jeopardy-modal-body code {
|
|
314
|
-
|
|
315
|
+
/* no color overrides here; let global syntax theme win */
|
|
316
|
+
margin-top: 0.5rem;
|
|
315
317
|
}
|
|
316
318
|
|
|
319
|
+
/* Light theme: rely entirely on the site's light code theme. */
|
|
317
320
|
html.light-mode .jeopardy-modal-body pre,
|
|
318
321
|
body.light-mode .jeopardy-modal-body pre,
|
|
319
322
|
html[data-theme='light'] .jeopardy-modal-body pre,
|
|
320
323
|
body[data-theme='light'] .jeopardy-modal-body pre,
|
|
321
324
|
[data-mode='light'] .jeopardy-modal-body pre {
|
|
322
|
-
|
|
325
|
+
/* intentionally empty */
|
|
323
326
|
}
|
|
324
327
|
|
|
325
|
-
/*
|
|
326
|
-
global theme already supplies one; inherit instead. */
|
|
328
|
+
/* Dark theme: rely entirely on the site's dark code theme. */
|
|
327
329
|
html.dark-mode .jeopardy-modal-body pre,
|
|
328
330
|
body.dark-mode .jeopardy-modal-body pre,
|
|
329
331
|
html[data-theme='dark'] .jeopardy-modal-body pre,
|
|
330
332
|
body[data-theme='dark'] .jeopardy-modal-body pre,
|
|
331
333
|
[data-mode='dark'] .jeopardy-modal-body pre {
|
|
332
|
-
|
|
334
|
+
/* intentionally empty */
|
|
333
335
|
}
|
|
334
336
|
|
|
335
337
|
.jeopardy-q { font-size: 1.05rem; margin-bottom: 1rem; background: inherit !important; }
|
|
@@ -365,7 +367,23 @@ body[data-theme='dark'] .jeopardy-modal-body pre,
|
|
|
365
367
|
.j-btn.accent:hover { filter: brightness(1.05); }
|
|
366
368
|
.j-btn:focus { outline: 2px solid var(--jp-outline); outline-offset: 2px; }
|
|
367
369
|
|
|
368
|
-
.jeopardy-team-actions {
|
|
370
|
+
.jeopardy-team-actions {
|
|
371
|
+
display: flex;
|
|
372
|
+
flex-wrap: wrap;
|
|
373
|
+
gap: 0.75rem;
|
|
374
|
+
margin-top: 0.75rem;
|
|
375
|
+
}
|
|
376
|
+
.jeopardy-team-actions .jeopardy-team-column {
|
|
377
|
+
display: flex;
|
|
378
|
+
flex-direction: column;
|
|
379
|
+
align-items: stretch;
|
|
380
|
+
gap: 0.35rem;
|
|
381
|
+
min-width: 7rem;
|
|
382
|
+
}
|
|
383
|
+
.jeopardy-team-actions .jeopardy-team-label {
|
|
384
|
+
font-weight: 600;
|
|
385
|
+
font-size: 0.9rem;
|
|
386
|
+
}
|
|
369
387
|
.jeopardy-team-actions .j-btn { font-size: 0.9rem; }
|
|
370
388
|
|
|
371
389
|
/* Per-team scoring buttons in the modal: make choices visually distinct
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
function renderMathIn(element) {
|
|
3
|
+
if (typeof window !== 'undefined' && typeof window.renderMathInElement === 'function') {
|
|
4
|
+
window.renderMathInElement(element, {
|
|
5
|
+
delimiters: [
|
|
6
|
+
{ left: '$$', right: '$$', display: true },
|
|
7
|
+
{ left: '$', right: '$', display: false },
|
|
8
|
+
{ left: '\\[', right: '\\]', display: true },
|
|
9
|
+
{ left: '\\(', right: '\\)', display: false },
|
|
10
|
+
],
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function highlightCode(element) {
|
|
16
|
+
if (typeof window !== 'undefined' && window.hljs && typeof window.hljs.highlightElement === 'function') {
|
|
17
|
+
const blocks = element.querySelectorAll('code');
|
|
18
|
+
blocks.forEach((b) => window.hljs.highlightElement(b));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function createFlashcards(root, cfg) {
|
|
23
|
+
const cards = Array.isArray(cfg.cards) ? cfg.cards.slice() : [];
|
|
24
|
+
const opts = cfg.options || {};
|
|
25
|
+
const showProgress = !!opts.show_progress;
|
|
26
|
+
let index = Number.isFinite(opts.start_index) ? Math.max(0, Math.min(cards.length - 1, opts.start_index)) : 0;
|
|
27
|
+
let side = 'front';
|
|
28
|
+
|
|
29
|
+
if (opts.shuffle && cards.length > 1) {
|
|
30
|
+
for (let i = cards.length - 1; i > 0; i -= 1) {
|
|
31
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
32
|
+
[cards[i], cards[j]] = [cards[j], cards[i]];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
root.innerHTML = '';
|
|
37
|
+
root.classList.add('flashcards-root');
|
|
38
|
+
|
|
39
|
+
const cardShell = document.createElement('div');
|
|
40
|
+
cardShell.className = 'flashcards-card-shell';
|
|
41
|
+
|
|
42
|
+
const card = document.createElement('div');
|
|
43
|
+
card.className = 'flashcards-card';
|
|
44
|
+
|
|
45
|
+
const header = document.createElement('div');
|
|
46
|
+
header.className = 'flashcards-header';
|
|
47
|
+
|
|
48
|
+
const sideLabel = document.createElement('div');
|
|
49
|
+
sideLabel.className = 'flashcards-pill';
|
|
50
|
+
|
|
51
|
+
const progress = document.createElement('div');
|
|
52
|
+
progress.className = 'flashcards-progress';
|
|
53
|
+
|
|
54
|
+
header.appendChild(sideLabel);
|
|
55
|
+
header.appendChild(progress);
|
|
56
|
+
|
|
57
|
+
const body = document.createElement('div');
|
|
58
|
+
body.className = 'flashcards-body';
|
|
59
|
+
|
|
60
|
+
const frontDiv = document.createElement('div');
|
|
61
|
+
frontDiv.className = 'flashcards-face front';
|
|
62
|
+
|
|
63
|
+
const backDiv = document.createElement('div');
|
|
64
|
+
backDiv.className = 'flashcards-face back';
|
|
65
|
+
|
|
66
|
+
body.appendChild(frontDiv);
|
|
67
|
+
body.appendChild(backDiv);
|
|
68
|
+
|
|
69
|
+
const footer = document.createElement('div');
|
|
70
|
+
footer.className = 'flashcards-footer';
|
|
71
|
+
|
|
72
|
+
const hint = document.createElement('div');
|
|
73
|
+
hint.textContent = 'Tips: bruk Space for å snu kortet';
|
|
74
|
+
|
|
75
|
+
const controls = document.createElement('div');
|
|
76
|
+
controls.className = 'flashcards-controls';
|
|
77
|
+
|
|
78
|
+
const prevBtn = document.createElement('button');
|
|
79
|
+
prevBtn.type = 'button';
|
|
80
|
+
prevBtn.className = 'flashcards-btn';
|
|
81
|
+
prevBtn.textContent = '← Forrige';
|
|
82
|
+
|
|
83
|
+
const flipBtn = document.createElement('button');
|
|
84
|
+
flipBtn.type = 'button';
|
|
85
|
+
flipBtn.className = 'flashcards-btn flashcards-btn-primary';
|
|
86
|
+
flipBtn.textContent = 'Snu kortet';
|
|
87
|
+
|
|
88
|
+
const nextBtn = document.createElement('button');
|
|
89
|
+
nextBtn.type = 'button';
|
|
90
|
+
nextBtn.className = 'flashcards-btn';
|
|
91
|
+
nextBtn.textContent = 'Neste →';
|
|
92
|
+
|
|
93
|
+
controls.appendChild(prevBtn);
|
|
94
|
+
controls.appendChild(flipBtn);
|
|
95
|
+
controls.appendChild(nextBtn);
|
|
96
|
+
|
|
97
|
+
footer.appendChild(hint);
|
|
98
|
+
footer.appendChild(controls);
|
|
99
|
+
|
|
100
|
+
card.appendChild(header);
|
|
101
|
+
card.appendChild(body);
|
|
102
|
+
card.appendChild(footer);
|
|
103
|
+
cardShell.appendChild(card);
|
|
104
|
+
root.appendChild(cardShell);
|
|
105
|
+
|
|
106
|
+
function update() {
|
|
107
|
+
if (!cards.length) {
|
|
108
|
+
frontDiv.innerHTML = '<em>Ingen kort.</em>';
|
|
109
|
+
backDiv.innerHTML = '';
|
|
110
|
+
sideLabel.textContent = 'Kort';
|
|
111
|
+
progress.textContent = '';
|
|
112
|
+
prevBtn.disabled = true;
|
|
113
|
+
nextBtn.disabled = true;
|
|
114
|
+
flipBtn.disabled = true;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const current = cards[index] || { front: '', back: '' };
|
|
119
|
+
frontDiv.innerHTML = current.front || '';
|
|
120
|
+
backDiv.innerHTML = current.back || '';
|
|
121
|
+
root.dataset.side = side;
|
|
122
|
+
|
|
123
|
+
sideLabel.textContent = side === 'front' ? 'Forside' : 'Bakside';
|
|
124
|
+
progress.textContent = showProgress ? `Kort ${index + 1} / ${cards.length}` : '';
|
|
125
|
+
|
|
126
|
+
prevBtn.disabled = index === 0;
|
|
127
|
+
nextBtn.disabled = index === cards.length - 1;
|
|
128
|
+
|
|
129
|
+
[frontDiv, backDiv].forEach((el) => {
|
|
130
|
+
renderMathIn(el);
|
|
131
|
+
highlightCode(el);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function flip() {
|
|
136
|
+
side = side === 'front' ? 'back' : 'front';
|
|
137
|
+
card.classList.remove('flashcards-card-flip');
|
|
138
|
+
// Force reflow so the animation can restart
|
|
139
|
+
// eslint-disable-next-line no-unused-expressions
|
|
140
|
+
void card.offsetWidth;
|
|
141
|
+
card.classList.add('flashcards-card-flip');
|
|
142
|
+
update();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function prev() {
|
|
146
|
+
if (index > 0) {
|
|
147
|
+
index -= 1;
|
|
148
|
+
side = 'front';
|
|
149
|
+
update();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function next() {
|
|
154
|
+
if (index < cards.length - 1) {
|
|
155
|
+
index += 1;
|
|
156
|
+
side = 'front';
|
|
157
|
+
update();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
flipBtn.addEventListener('click', flip);
|
|
162
|
+
prevBtn.addEventListener('click', prev);
|
|
163
|
+
nextBtn.addEventListener('click', next);
|
|
164
|
+
|
|
165
|
+
root.tabIndex = 0;
|
|
166
|
+
root.addEventListener('keydown', (ev) => {
|
|
167
|
+
if (ev.key === ' ' || ev.code === 'Space') {
|
|
168
|
+
ev.preventDefault();
|
|
169
|
+
flip();
|
|
170
|
+
} else if (ev.key === 'ArrowLeft') {
|
|
171
|
+
prev();
|
|
172
|
+
} else if (ev.key === 'ArrowRight') {
|
|
173
|
+
next();
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
update();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function init() {
|
|
181
|
+
const containers = document.querySelectorAll('.flashcards-container');
|
|
182
|
+
containers.forEach((c) => {
|
|
183
|
+
try {
|
|
184
|
+
const script = c.querySelector('.flashcards-data');
|
|
185
|
+
const raw = script && script.textContent ? script.textContent : c.getAttribute('data-config') || '{}';
|
|
186
|
+
const cfg = JSON.parse(raw);
|
|
187
|
+
createFlashcards(c, cfg);
|
|
188
|
+
} catch (e) {
|
|
189
|
+
console.error('flashcards: init failed', e);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (document.readyState === 'loading') {
|
|
195
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
196
|
+
} else {
|
|
197
|
+
init();
|
|
198
|
+
}
|
|
199
|
+
})();
|
|
@@ -361,18 +361,22 @@
|
|
|
361
361
|
teams.forEach((t, i)=>{
|
|
362
362
|
// In turn-based mode, only the active team can be scored on
|
|
363
363
|
if (gameMode==='turn' && i!==currentTurn) return;
|
|
364
|
+
// Create a per-team column with label and buttons
|
|
365
|
+
const teamCol = document.createElement('div');
|
|
366
|
+
teamCol.className = 'jeopardy-team-column';
|
|
367
|
+
const teamLabel = document.createElement('div');
|
|
368
|
+
teamLabel.className = 'jeopardy-team-label';
|
|
369
|
+
teamLabel.textContent = t.name;
|
|
370
|
+
teamCol.appendChild(teamLabel);
|
|
364
371
|
|
|
365
372
|
const add = document.createElement('button');
|
|
366
373
|
add.className='j-btn primary';
|
|
367
|
-
add.textContent = `+${value}
|
|
374
|
+
add.textContent = `+${value} poeng`;
|
|
368
375
|
|
|
369
|
-
//
|
|
370
|
-
let zeroBtn =
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
zeroBtn.className = 'j-btn secondary';
|
|
374
|
-
zeroBtn.textContent = `0 ${t.name}`;
|
|
375
|
-
}
|
|
376
|
+
// Explicit 0-points option per team in both modes
|
|
377
|
+
let zeroBtn = document.createElement('button');
|
|
378
|
+
zeroBtn.className = 'j-btn secondary';
|
|
379
|
+
zeroBtn.textContent = '0 poeng';
|
|
376
380
|
|
|
377
381
|
// Register this team as pending in duel mode
|
|
378
382
|
if (gameMode === 'duel') {
|
|
@@ -414,6 +418,7 @@
|
|
|
414
418
|
registerScore(value);
|
|
415
419
|
scored = true;
|
|
416
420
|
if (add) { add.disabled = true; add.classList.add('used-choice'); }
|
|
421
|
+
if (zeroBtn) { zeroBtn.disabled = true; zeroBtn.classList.add('used-choice'); }
|
|
417
422
|
finalizeIfNeeded();
|
|
418
423
|
} else {
|
|
419
424
|
if (!duelPendingTeams.has(i)) return;
|
|
@@ -426,7 +431,14 @@
|
|
|
426
431
|
};
|
|
427
432
|
|
|
428
433
|
const handleZero = ()=>{
|
|
429
|
-
if (gameMode === '
|
|
434
|
+
if (gameMode === 'turn') {
|
|
435
|
+
if (scored) return;
|
|
436
|
+
// Explicitly choose 0 points: no score change, but consume the question
|
|
437
|
+
scored = true;
|
|
438
|
+
if (add) { add.disabled = true; add.classList.add('used-choice'); }
|
|
439
|
+
if (zeroBtn) { zeroBtn.disabled = true; zeroBtn.classList.add('used-choice'); }
|
|
440
|
+
finalizeIfNeeded();
|
|
441
|
+
} else if (gameMode === 'duel') {
|
|
430
442
|
if (!duelPendingTeams.has(i)) return;
|
|
431
443
|
// Zero points: just clear pending state for this team
|
|
432
444
|
duelPendingTeams.delete(i);
|
|
@@ -444,8 +456,9 @@
|
|
|
444
456
|
if (zeroBtn) zeroBtn.disabled = true;
|
|
445
457
|
}
|
|
446
458
|
|
|
447
|
-
|
|
448
|
-
if (zeroBtn)
|
|
459
|
+
teamCol.appendChild(add);
|
|
460
|
+
if (zeroBtn) teamCol.appendChild(zeroBtn);
|
|
461
|
+
teamActions.appendChild(teamCol);
|
|
449
462
|
});
|
|
450
463
|
const footerRight = document.createElement('div');
|
|
451
464
|
footerRight.className = 'jeopardy-footer-right';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: munchboka-edutools
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.19
|
|
4
4
|
Summary: Sphinx/Jupyter Book directives and assets for Munchboka and VGS books
|
|
5
5
|
Project-URL: Homepage, https://github.com/reneaas/munchboka-edutools
|
|
6
6
|
Project-URL: Issues, https://github.com/reneaas/munchboka-edutools/issues
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
munchboka_edutools/__init__.py,sha256=fgHZaxSDzU7Didm5UN2_H2-ZYZIUowU8MJaRYxNmKNA,7380
|
|
2
2
|
munchboka_edutools/_plotmath_shim.py,sha256=kWZV-SO4idGHCVdBcfQmrhFF9DEgxPY5Xg8d1h1Bb6c,3348
|
|
3
|
-
munchboka_edutools/_version.py,sha256=
|
|
3
|
+
munchboka_edutools/_version.py,sha256=bmv5BPFZ5wOAf5EHfelg_MGwWrQ0-YajL51koWqQQAQ,49
|
|
4
4
|
munchboka_edutools/directives/__init__.py,sha256=wxsmI7Wt_Rpft925y9BmU7urgKhGNlpqnzN2cFbCtoI,83
|
|
5
5
|
munchboka_edutools/directives/admonitions.py,sha256=yJB5F1Wlu_AZdiWQWFR7ArBO0OFE5bsx3DirLV_ujaw,10226
|
|
6
6
|
munchboka_edutools/directives/cas_popup.py,sha256=WED8V1DT55sqYRr_CpgbkanadXRkNmddeW9KSw11IKA,9113
|
|
@@ -8,6 +8,7 @@ munchboka_edutools/directives/clear.py,sha256=LouDw6yPUccbHCKOt-64vv34xyaBZwbL_4
|
|
|
8
8
|
munchboka_edutools/directives/dialogue.py,sha256=qgG8AW0vNvwpnGlGruqTm2KK6KG2gcHJoZ8Ibo8RGEQ,4203
|
|
9
9
|
munchboka_edutools/directives/escape_room.py,sha256=ZVnxcINvMGis1AOpHZLSOuGDN1zVRAyWJ3Mm7RUDBvk,10986
|
|
10
10
|
munchboka_edutools/directives/factor_tree.py,sha256=BW6-OQO_9x4H5ktfmkz0zOnNmp0N8azj17LQMgzm9Jo,18451
|
|
11
|
+
munchboka_edutools/directives/flashcards.py,sha256=yyzBGXFa2IpeephiRP9qWWOttFf4HZwa93XK3wruUKQ,8879
|
|
11
12
|
munchboka_edutools/directives/ggb.py,sha256=zHlO_zAFXNqAnvH8xUYP1b0khjpbcUwuPb0QMzkObGc,6232
|
|
12
13
|
munchboka_edutools/directives/ggb_icon.py,sha256=tgOLBQt7CzMpEz5WmAL9-VjPYCMd4zDP973zsanIw0c,3118
|
|
13
14
|
munchboka_edutools/directives/ggb_popup.py,sha256=G84ZgYExh9nLav8b_8ZBe8825nU8wcedOIvSGlDK2FM,5384
|
|
@@ -17,7 +18,7 @@ munchboka_edutools/directives/jeopardy.py,sha256=FoYvyuGbRXpF8KjjKJ-FUzonvPpqm3v
|
|
|
17
18
|
munchboka_edutools/directives/multi_plot.py,sha256=q2ocaAWzf_ZnDcUi3vKAEt_M_d7zNTEJwveyHrEK5mc,42739
|
|
18
19
|
munchboka_edutools/directives/pair_puzzle.py,sha256=8x6RXVT7I3EcEZEX2TJxPJxOIkwdhd-YPh_DnoKQarI,5277
|
|
19
20
|
munchboka_edutools/directives/parsons.py,sha256=fpKcbyiHcJQlYsiMBaHKAy5vJ0eCUMUVV8h1clb5s7A,3384
|
|
20
|
-
munchboka_edutools/directives/plot.py,sha256=
|
|
21
|
+
munchboka_edutools/directives/plot.py,sha256=s4TscvwfXXt2D8Jul1QkA1ITSn_JCixUJ819U_-K5Eg,135225
|
|
21
22
|
munchboka_edutools/directives/poly_icon.py,sha256=hT0NUrgkcYqcHk4_lKGiqam4RT6LXTarTim08j4BsJc,3558
|
|
22
23
|
munchboka_edutools/directives/polydiv.py,sha256=vd8rwNqpn6_Wu52P7_LAGRhL36hufFB1aRskuxEiko4,11735
|
|
23
24
|
munchboka_edutools/directives/popup.py,sha256=Nni7uxmXD4YguC7L-tgiowkDNsXZ8S_XlKGU-LRroCY,8072
|
|
@@ -29,12 +30,13 @@ munchboka_edutools/static/css/admonitions.css,sha256=qwA4Xvj6dJ5pd-vKi9CiMqvNJCl
|
|
|
29
30
|
munchboka_edutools/static/css/cas_popup.css,sha256=-tpz-YJHrmcZtRcVWzPLiKZMF7QYaAcVIzXZw6ER8W0,5731
|
|
30
31
|
munchboka_edutools/static/css/dialogue.css,sha256=9G8bswTTVjTaqWVStFR7PaS7uT-mpQrq2Sf07zEgS6g,1494
|
|
31
32
|
munchboka_edutools/static/css/figures.css,sha256=E-CeY02Ac-T1H6EGuCBxeoKvug307_oG2Iq9vVxSCiY,6782
|
|
33
|
+
munchboka_edutools/static/css/flashcards.css,sha256=oe8IDsT873dCbkevJ_8HYgYsDXk2VvRWs01ArYJ1tA0,4692
|
|
32
34
|
munchboka_edutools/static/css/general_style.css,sha256=6m07K84uQf7iaFMpnVmLUt1V4ZrV-673bo7n2sKS9Yo,2298
|
|
33
35
|
munchboka_edutools/static/css/github-dark-high-contrast.css,sha256=Dk7DDwe7x66NA3vwNIzB4mGD2GD6pODUCoSippd56Hk,2883
|
|
34
|
-
munchboka_edutools/static/css/github-dark.css,sha256=
|
|
35
|
-
munchboka_edutools/static/css/github-light.css,sha256=
|
|
36
|
+
munchboka_edutools/static/css/github-dark.css,sha256=XhVzY424aY3qvMwlwowsn_ncW9y2WHhUsXncvPsPk-U,3150
|
|
37
|
+
munchboka_edutools/static/css/github-light.css,sha256=G7yNZ0s7qjwigyr6gN4qhQdUo3_bhJIFtJOZ41cMKNw,3191
|
|
36
38
|
munchboka_edutools/static/css/interactive_code.css,sha256=ZD0NEw_VM2fczaxOkKREPB8GykDJVDoQ9rDbttw0m0Y,13808
|
|
37
|
-
munchboka_edutools/static/css/jeopardy.css,sha256=
|
|
39
|
+
munchboka_edutools/static/css/jeopardy.css,sha256=N375ADhb9ElKzdkdMHKbkwJsmsy2NusT9kfB8quIjYo,20726
|
|
38
40
|
munchboka_edutools/static/css/popup.css,sha256=0-wc2b6zXwZXecxSey5S5Y9036BBXjNFRQpf9ft5dUE,2514
|
|
39
41
|
munchboka_edutools/static/css/quiz.css,sha256=gLvmSuQalYgFXP3-fkLOwfE6LpX6oHkm4vHPDMhUFPE,8209
|
|
40
42
|
munchboka_edutools/static/css/timedQuiz.css,sha256=wb0u7CmbMJ-JkYkHQYksrir329sTAtKyL7q9K-eYY14,9912
|
|
@@ -123,9 +125,10 @@ munchboka_edutools/static/icons/solid/light_mode/stop.svg,sha256=MHKp5NcZ6dV_oN7
|
|
|
123
125
|
munchboka_edutools/static/icons/stickers/edit.svg,sha256=P1XbvIodj08IQkv7MJMKatCFOuu4EABhhWVXJLGx5og,317
|
|
124
126
|
munchboka_edutools/static/icons/stickers/pencil_square.svg,sha256=NQ0nT7vhRzrznFC6rt7Tiqq9EeJJwiDdphRs1-A5vvM,455
|
|
125
127
|
munchboka_edutools/static/js/casThemeManager.js,sha256=DCHqn7c2NhyGo8r4Q6KF5hs5eskHKUhhb-OHZJxGM38,3189
|
|
128
|
+
munchboka_edutools/static/js/flashcards.js,sha256=-PIxkxqcatd1sCNsOFg-QBsQwv9uxRw6_mZKCBo_9FA,5976
|
|
126
129
|
munchboka_edutools/static/js/geogebra-setup.js,sha256=iLMH-Ja_PJv2Yg8Ujy_LoiVQkd_x1urYoK5KCgIjPtU,279
|
|
127
130
|
munchboka_edutools/static/js/highlight-init.js,sha256=4sTpB5Z8uLp4-iczmsmRBt3zBjID6dIbeFSfad2Fz3E,188
|
|
128
|
-
munchboka_edutools/static/js/jeopardy.js,sha256=
|
|
131
|
+
munchboka_edutools/static/js/jeopardy.js,sha256=xEGMIN6gIvHyQprxj1Ur8GjHaUseMkHLu_oNwFvFa_4,26214
|
|
129
132
|
munchboka_edutools/static/js/popup.js,sha256=5zI8AmR1-LcUUqlFn210lUDdfFCY0UmzqoSsIZ899JQ,2740
|
|
130
133
|
munchboka_edutools/static/js/quiz.js,sha256=9dpH3B4KM3nPscjE41seQNneXJGj1hrMPjirh3DYunI,15468
|
|
131
134
|
munchboka_edutools/static/js/utils.js,sha256=p5qSGOB1orGhQLft6bZ3qpx1Mpog4y6yAvh4WyXDt6k,160
|
|
@@ -143,7 +146,7 @@ munchboka_edutools/static/js/skulpt/skulpt.js,sha256=xzS8AVz4mGi2v4lytAEeH0tm7Ip
|
|
|
143
146
|
munchboka_edutools/static/js/timedQuiz/multipleChoiceQuestion.js,sha256=OleHi24leM3Ejc2dvHYSwQGarIA_ZoqjtA3WrPvaX8s,6571
|
|
144
147
|
munchboka_edutools/static/js/timedQuiz/timedMultipleChoiceQuiz.js,sha256=TFKnF80ZsnYbzdSKuOsKcHfEg1ZiUok9vgraOTFE5gM,9030
|
|
145
148
|
munchboka_edutools/static/js/timedQuiz/utils.js,sha256=CvlHVW3Uq-DQapRRuMC61q785GN5NZX5VmZj0zeP2hE,195
|
|
146
|
-
munchboka_edutools-0.1.
|
|
147
|
-
munchboka_edutools-0.1.
|
|
148
|
-
munchboka_edutools-0.1.
|
|
149
|
-
munchboka_edutools-0.1.
|
|
149
|
+
munchboka_edutools-0.1.19.dist-info/METADATA,sha256=vscsVJhxMAYX-aHKUMYZbS5olMCTHDmBwDvECWaiMwA,3270
|
|
150
|
+
munchboka_edutools-0.1.19.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
151
|
+
munchboka_edutools-0.1.19.dist-info/licenses/LICENSE,sha256=7nP4mEfu9fXD-OFxavvJyiudI9LGiEvLk9a0ucEZ6NE,1079
|
|
152
|
+
munchboka_edutools-0.1.19.dist-info/RECORD,,
|
{munchboka_edutools-0.1.13.dist-info → munchboka_edutools-0.1.19.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|