hyperbook 0.99.0 → 0.100.0

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.
@@ -50,6 +50,132 @@
50
50
  font-family: hyperbook-monospace, monospace;
51
51
  }
52
52
 
53
+ /* Prompt bar, shown while the script waits for input. Styled as its own
54
+ console-like strip below the output/canvas panel so it reads as "the program
55
+ is waiting for you" rather than as part of the page chrome. */
56
+ .directive-pyide .stdin {
57
+ display: flex;
58
+ flex-wrap: wrap;
59
+ align-items: center;
60
+ gap: 8px 10px;
61
+ flex: 0 0 auto;
62
+ margin-top: 8px;
63
+ padding: 10px 12px;
64
+ border: 1px solid var(--color-brand);
65
+ border-radius: 8px;
66
+ background-color: color-mix(in srgb, var(--color-brand) 7%, transparent);
67
+ }
68
+
69
+ .directive-pyide .stdin.hidden {
70
+ display: none;
71
+ }
72
+
73
+ /* Blinking caret marks the waiting state without needing extra markup. */
74
+ .directive-pyide .stdin::before {
75
+ content: "❯";
76
+ flex: 0 0 auto;
77
+ font-family: hyperbook-monospace, monospace;
78
+ font-weight: 700;
79
+ line-height: 1;
80
+ color: var(--color-brand);
81
+ animation: pyide-stdin-caret 1.4s ease-in-out infinite;
82
+ }
83
+
84
+ @keyframes pyide-stdin-caret {
85
+ 0%,
86
+ 100% {
87
+ opacity: 1;
88
+ }
89
+ 50% {
90
+ opacity: 0.3;
91
+ }
92
+ }
93
+
94
+ @media (prefers-reduced-motion: reduce) {
95
+ .directive-pyide .stdin::before {
96
+ animation: none;
97
+ }
98
+ }
99
+
100
+ .directive-pyide .stdin .stdin-prompt {
101
+ flex: 0 1 auto;
102
+ min-width: 0;
103
+ font-family: hyperbook-monospace, monospace;
104
+ color: var(--color-text);
105
+ white-space: pre-wrap;
106
+ overflow-wrap: anywhere;
107
+ }
108
+
109
+ .directive-pyide .stdin .stdin-prompt:empty {
110
+ display: none;
111
+ }
112
+
113
+ /* Grows to fill the row, and wraps to its own line when the prompt is long. */
114
+ .directive-pyide .stdin .stdin-field {
115
+ flex: 1 1 160px;
116
+ min-width: 0;
117
+ padding: 8px 12px;
118
+ border: 1px solid var(--color-spacer);
119
+ border-radius: 6px;
120
+ font-family: hyperbook-monospace, monospace;
121
+ font-size: 1em;
122
+ line-height: 1.4;
123
+ background-color: var(--color-background);
124
+ color: var(--color-text);
125
+ }
126
+
127
+ .directive-pyide .stdin .stdin-field:focus {
128
+ outline: none;
129
+ border-color: var(--color-brand);
130
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-brand) 25%, transparent);
131
+ }
132
+
133
+ /* Overrides the shared .directive-pyide button reset, which is borderless and
134
+ full-width for the tab/toolbar groups.
135
+
136
+ The fill is a tint of the brand mixed into --color-background rather than
137
+ flat --color-brand, and the label is always --color-text. That keeps the
138
+ contrast guaranteed by the theme's own background/text pairing in both
139
+ colour schemes: --color-brand-text is only defined for light mode, and
140
+ --color-brand-dark falls back to --color-brand, so neither can be relied on
141
+ to stay legible against a brand fill. */
142
+ .directive-pyide .stdin .stdin-submit {
143
+ flex: 0 0 auto;
144
+ padding: 8px 18px;
145
+ border: 1px solid var(--color-brand);
146
+ border-radius: 6px;
147
+ background-color: color-mix(
148
+ in srgb,
149
+ var(--color-brand) 18%,
150
+ var(--color-background)
151
+ );
152
+ color: var(--color-text);
153
+ font-weight: 600;
154
+ line-height: 1.4;
155
+ cursor: pointer;
156
+ }
157
+
158
+ .directive-pyide .stdin .stdin-submit:hover {
159
+ background-color: color-mix(
160
+ in srgb,
161
+ var(--color-brand) 32%,
162
+ var(--color-background)
163
+ );
164
+ }
165
+
166
+ .directive-pyide .stdin .stdin-submit:active {
167
+ background-color: color-mix(
168
+ in srgb,
169
+ var(--color-brand) 45%,
170
+ var(--color-background)
171
+ );
172
+ }
173
+
174
+ .directive-pyide .stdin .stdin-submit:focus-visible {
175
+ outline: 2px solid var(--color-text);
176
+ outline-offset: 2px;
177
+ }
178
+
53
179
  .directive-pyide .output .error-line {
54
180
  color: #b42318;
55
181
  }
@@ -128,7 +254,11 @@
128
254
  linear-gradient(45deg, transparent 75%, #cccccc 75%),
129
255
  linear-gradient(-45deg, transparent 75%, #cccccc 75%);
130
256
  background-size: 20px 20px;
131
- background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
257
+ background-position:
258
+ 0 0,
259
+ 0 10px,
260
+ 10px -10px,
261
+ -10px 0px;
132
262
  }
133
263
 
134
264
  .directive-pyide .canvas {
@@ -247,6 +377,31 @@
247
377
  border-top-right-radius: 0;
248
378
  }
249
379
 
380
+ /* Icon-only toolbar: the written labels (Reset/Copy/Download) made this row
381
+ wrap on narrow screens, so the wording moved into title/aria-label. */
382
+ .directive-pyide .buttons.bottom button {
383
+ flex: 0 0 auto;
384
+ min-width: 42px;
385
+ width: 42px;
386
+ padding: 8px 0;
387
+ display: flex;
388
+ align-items: center;
389
+ justify-content: center;
390
+ }
391
+
392
+ .directive-pyide .buttons.bottom button .icon {
393
+ width: 18px;
394
+ height: 18px;
395
+ display: block;
396
+ }
397
+
398
+ /* Fullscreen sits apart, at the trailing edge of the toolbar. */
399
+ .directive-pyide .buttons.bottom button.fullscreen {
400
+ margin-inline-start: auto;
401
+ border-right: none;
402
+ border-left: 1px solid var(--color-spacer);
403
+ }
404
+
250
405
  .directive-pyide .test-heading {
251
406
  font-size: 1.1em;
252
407
  font-weight: bold;
@@ -375,7 +530,8 @@
375
530
  border-top-right-radius: 0;
376
531
  }
377
532
 
378
- .directive-pyide[data-canvas="true"].canvas-split-mode .canvas-output-splitter {
533
+ .directive-pyide[data-canvas="true"].canvas-split-mode
534
+ .canvas-output-splitter {
379
535
  display: block;
380
536
  }
381
537
  }
@@ -343,8 +343,14 @@ code-input {
343
343
 
344
344
  .directive-typst .binary-files-section summary .summary-indicator {
345
345
  transition: transform 0.2s;
346
- display: inline-block;
347
- font-size: 0.75rem;
346
+ display: inline-flex;
347
+ align-items: center;
348
+ }
349
+
350
+ .directive-typst .binary-files-section summary .summary-indicator .icon {
351
+ width: 14px;
352
+ height: 14px;
353
+ display: block;
348
354
  }
349
355
 
350
356
  .directive-typst .binary-files-section[open] summary .summary-indicator {
@@ -484,6 +490,19 @@ code-input {
484
490
  padding: 8px 0;
485
491
  }
486
492
 
493
+ .directive-typst button.fullscreen .icon {
494
+ width: 18px;
495
+ height: 18px;
496
+ display: block;
497
+ margin: 0 auto;
498
+ }
499
+
500
+ .directive-typst .add-source-file .icon {
501
+ width: 16px;
502
+ height: 16px;
503
+ display: block;
504
+ }
505
+
487
506
  .directive-typst button:hover {
488
507
  background-color: var(--color-spacer);
489
508
  }
@@ -531,3 +550,26 @@ code-input {
531
550
  }
532
551
  }
533
552
  }
553
+
554
+ /* Icon-only toolbar: the written labels made this row wrap on narrow screens,
555
+ so the wording moved into title/aria-label. */
556
+ .directive-typst .buttons.bottom button {
557
+ flex: 0 0 auto;
558
+ min-width: 42px;
559
+ width: 42px;
560
+ padding: 8px 0;
561
+ display: flex;
562
+ align-items: center;
563
+ justify-content: center;
564
+ }
565
+
566
+ .directive-typst .buttons.bottom button .icon {
567
+ width: 18px;
568
+ height: 18px;
569
+ display: block;
570
+ }
571
+
572
+ /* Fullscreen sits apart, at the trailing edge of the toolbar. */
573
+ .directive-typst .buttons.bottom button.fullscreen {
574
+ margin-inline-start: auto;
575
+ }
@@ -18,8 +18,13 @@ hyperbook.webide = (function () {
18
18
 
19
19
  const applySplitSize = (rawSize, isHorizontal) => {
20
20
  const total = isHorizontal ? elem.clientWidth : elem.clientHeight;
21
- const splitterSize = isHorizontal ? splitter.offsetWidth : splitter.offsetHeight;
22
- const maxSize = Math.max(minPanelSize, total - splitterSize - minPanelSize);
21
+ const splitterSize = isHorizontal
22
+ ? splitter.offsetWidth
23
+ : splitter.offsetHeight;
24
+ const maxSize = Math.max(
25
+ minPanelSize,
26
+ total - splitterSize - minPanelSize,
27
+ );
23
28
  const clamped = Math.max(minPanelSize, Math.min(rawSize, maxSize));
24
29
  container.style.flex = `0 0 ${clamped}px`;
25
30
  return clamped;
@@ -79,7 +84,6 @@ hyperbook.webide = (function () {
79
84
  if (!elem || !button) return;
80
85
  const isFullscreen = document.fullscreenElement === elem;
81
86
  const label = hyperbook.i18n.get("ide-fullscreen-enter");
82
- button.textContent = "⛶";
83
87
  button.title = label;
84
88
  button.setAttribute("aria-label", label);
85
89
  button.classList.toggle("active", isFullscreen);
@@ -121,21 +125,39 @@ hyperbook.webide = (function () {
121
125
  const btnJS = elem.querySelector("button.js");
122
126
 
123
127
  // Initialize CodeMirror instances
124
- const cmHTML = editorHTMLDiv ? (() => {
125
- const src = editorHTMLDiv.textContent;
126
- editorHTMLDiv.textContent = "";
127
- return HyperbookCM.create(editorHTMLDiv, { lang: "html", value: src, onChange: () => update() });
128
- })() : null;
129
- const cmCSS = editorCSSDiv ? (() => {
130
- const src = editorCSSDiv.textContent;
131
- editorCSSDiv.textContent = "";
132
- return HyperbookCM.create(editorCSSDiv, { lang: "css", value: src, onChange: () => update() });
133
- })() : null;
134
- const cmJS = editorJSDiv ? (() => {
135
- const src = editorJSDiv.textContent;
136
- editorJSDiv.textContent = "";
137
- return HyperbookCM.create(editorJSDiv, { lang: "javascript", value: src, onChange: () => update() });
138
- })() : null;
128
+ const cmHTML = editorHTMLDiv
129
+ ? (() => {
130
+ const src = editorHTMLDiv.textContent;
131
+ editorHTMLDiv.textContent = "";
132
+ return HyperbookCM.create(editorHTMLDiv, {
133
+ lang: "html",
134
+ value: src,
135
+ onChange: () => update(),
136
+ });
137
+ })()
138
+ : null;
139
+ const cmCSS = editorCSSDiv
140
+ ? (() => {
141
+ const src = editorCSSDiv.textContent;
142
+ editorCSSDiv.textContent = "";
143
+ return HyperbookCM.create(editorCSSDiv, {
144
+ lang: "css",
145
+ value: src,
146
+ onChange: () => update(),
147
+ });
148
+ })()
149
+ : null;
150
+ const cmJS = editorJSDiv
151
+ ? (() => {
152
+ const src = editorJSDiv.textContent;
153
+ editorJSDiv.textContent = "";
154
+ return HyperbookCM.create(editorJSDiv, {
155
+ lang: "javascript",
156
+ value: src,
157
+ onChange: () => update(),
158
+ });
159
+ })()
160
+ : null;
139
161
 
140
162
  const frame = elem.getElementsByTagName("iframe")[0];
141
163
  const template = elem.getAttribute("data-template");
@@ -39,7 +39,7 @@ code-input {
39
39
  padding: 8px 16px;
40
40
  padding-left: 0px;
41
41
  overflow: hidden;
42
- white-space:nowrap;
42
+ white-space: nowrap;
43
43
  text-overflow: ellipsis;
44
44
  background-color: var(--color-background);
45
45
  color: var(--color-text);
@@ -148,6 +148,13 @@ code-input {
148
148
  padding: 8px 0;
149
149
  }
150
150
 
151
+ .directive-webide button.fullscreen .icon {
152
+ width: 18px;
153
+ height: 18px;
154
+ display: block;
155
+ margin: 0 auto;
156
+ }
157
+
151
158
  .directive-webide button:hover {
152
159
  background-color: var(--color-spacer);
153
160
  }
@@ -186,3 +193,26 @@ code-input {
186
193
  }
187
194
  }
188
195
  }
196
+
197
+ /* Icon-only toolbar: the written labels made this row wrap on narrow screens,
198
+ so the wording moved into title/aria-label. */
199
+ .directive-webide .buttons.bottom button {
200
+ flex: 0 0 auto;
201
+ min-width: 42px;
202
+ width: 42px;
203
+ padding: 8px 0;
204
+ display: flex;
205
+ align-items: center;
206
+ justify-content: center;
207
+ }
208
+
209
+ .directive-webide .buttons.bottom button .icon {
210
+ width: 18px;
211
+ height: 18px;
212
+ display: block;
213
+ }
214
+
215
+ /* Fullscreen sits apart, at the trailing edge of the toolbar. */
216
+ .directive-webide .buttons.bottom button.fullscreen {
217
+ margin-inline-start: auto;
218
+ }
@@ -140,25 +140,16 @@ header.inverted>.branding:hover {
140
140
  color: var(--color-text);
141
141
  }
142
142
 
143
- .section>.name.active>.label {
143
+ /* `active` and `empty` sit on the <details>, not on the <summary>. */
144
+ .section.active>.name>.label {
144
145
  color: var(--color-brand);
145
146
  }
146
147
 
147
- .section .name.empty {
148
- color: var(--color-text-deactivated);
149
- cursor: pointer;
150
- font-style: italic;
151
- }
152
-
153
148
  .section>.name:hover {
154
149
  background: var(--color-spacer);
155
150
  }
156
151
 
157
- .section>.name.empty:hover {
158
- background: none;
159
- }
160
-
161
- .section>.name.active {
152
+ .section.active>.name {
162
153
  background: var(--color-background);
163
154
  color: var(--color-brand);
164
155
  }
@@ -698,14 +689,23 @@ nav li+li {
698
689
  text-decoration: none;
699
690
  }
700
691
 
701
- .section.empty summary.name {
702
- cursor: default;
703
- }
692
+ /* The row always toggles the section, so it always looks clickable. Only the
693
+ title tells the two kinds of section apart: a section without its own page
694
+ is set in italics and is not a link.
704
695
 
705
- .section.empty summary.name .label {
696
+ Both rules are scoped with child combinators. Sections nest, so a descendant
697
+ selector would style a subsection according to its parent — a subsection with
698
+ content sitting inside an empty section came out italic. */
699
+ .section.empty>summary.name>.label {
706
700
  font-style: italic;
707
701
  }
708
702
 
703
+ /* A section that has its own page: the title is a real link and behaves like
704
+ one, while clicking elsewhere in the row just expands the section. */
705
+ .section:not(.empty)>summary.name>.label:hover {
706
+ text-decoration: underline;
707
+ }
708
+
709
709
  .section summary.name:after {
710
710
  content: "\002B";
711
711
  width: 1.2rem;