munchboka-edutools 0.1.14__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 +101 -38
- 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.14.dist-info → munchboka_edutools-0.1.19.dist-info}/METADATA +1 -1
- {munchboka_edutools-0.1.14.dist-info → munchboka_edutools-0.1.19.dist-info}/RECORD +13 -10
- {munchboka_edutools-0.1.14.dist-info → munchboka_edutools-0.1.19.dist-info}/WHEEL +1 -1
- {munchboka_edutools-0.1.14.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
|
}
|
|
@@ -941,8 +960,10 @@ class PlotDirective(SphinxDirective):
|
|
|
941
960
|
except Exception:
|
|
942
961
|
pass
|
|
943
962
|
|
|
944
|
-
# Tangents:
|
|
945
|
-
# "(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.
|
|
946
967
|
tangent_vals: List[Tuple[float, float, float, str | None, str | None]] = (
|
|
947
968
|
[]
|
|
948
969
|
) # (a, b, x0, style, color)
|
|
@@ -972,7 +993,52 @@ class PlotDirective(SphinxDirective):
|
|
|
972
993
|
if not parts_t:
|
|
973
994
|
continue
|
|
974
995
|
|
|
975
|
-
#
|
|
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))
|
|
976
1042
|
m_pair = re.match(r"^\(\s*([^,]+?)\s*,\s*([^,]+?)\s*\)$", parts_t[0])
|
|
977
1043
|
if not m_pair:
|
|
978
1044
|
continue
|
|
@@ -991,20 +1057,10 @@ class PlotDirective(SphinxDirective):
|
|
|
991
1057
|
continue
|
|
992
1058
|
try:
|
|
993
1059
|
x0 = _eval_expr(x_raw)
|
|
994
|
-
|
|
1060
|
+
_ = _eval_expr(arg_expr) # just ensure it's valid
|
|
995
1061
|
except Exception:
|
|
996
1062
|
continue
|
|
997
|
-
|
|
998
|
-
style_t: str | None = None
|
|
999
|
-
color_t: str | None = None
|
|
1000
|
-
_allowed_styles_t = {"solid", "dotted", "dashed", "dashdot"}
|
|
1001
|
-
for extra in parts_t[1:]:
|
|
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
|
|
1063
|
+
style_t, color_t = _parse_tangent_style(parts_t[1:])
|
|
1008
1064
|
try:
|
|
1009
1065
|
idx = fn_labels_list.index(lbl)
|
|
1010
1066
|
f = functions[idx]
|
|
@@ -2441,10 +2497,17 @@ class PlotDirective(SphinxDirective):
|
|
|
2441
2497
|
for a_l, b_l, st_l, col_l in line_vals:
|
|
2442
2498
|
_draw_line(a_l, b_l, st_l, col_l)
|
|
2443
2499
|
|
|
2444
|
-
# Tangents: allow optional style/color, with dashed
|
|
2500
|
+
# Tangents: allow optional style/color, with dashed red default
|
|
2445
2501
|
for a_t, b_t, _x0, st_t, col_t in tangent_vals:
|
|
2446
2502
|
style_use = st_t or "dashed"
|
|
2447
|
-
|
|
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"
|
|
2448
2511
|
_draw_line(a_t, b_t, style_use, color_use)
|
|
2449
2512
|
|
|
2450
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.14.dist-info → munchboka_edutools-0.1.19.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|