This commit is contained in:
Felix Roos
2022-02-18 21:58:15 +01:00
parent 57bddf9189
commit d26467d0de
12 changed files with 110588 additions and 125 deletions
+2 -1
View File
@@ -8,6 +8,7 @@ import './common/index-d01087d6.js';
var useCallback = react.useCallback;
var useEffect = react.useEffect;
var useLayoutEffect = react.useLayoutEffect;
var useMemo = react.useMemo;
var useRef = react.useRef;
var useState = react.useState;
export { useCallback, useEffect, useLayoutEffect, useRef, useState };
export { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState };
+21 -121
View File
@@ -1,14 +1,12 @@
import React, {useCallback, useEffect, useLayoutEffect, useRef, useState} from "../_snowpack/pkg/react.js";
import logo from "./logo.svg.proxy.js";
import cx from "./cx.js";
import React, {useCallback, useLayoutEffect, useRef} from "../_snowpack/pkg/react.js";
import * as Tone from "../_snowpack/pkg/tone.js";
import useCycle from "./useCycle.js";
import * as tunes from "./tunes.js";
import {evaluate} from "./evaluate.js";
import CodeMirror from "./CodeMirror.js";
import hot from "../hot.js";
import {isNote} from "../_snowpack/pkg/tone.js";
import cx from "./cx.js";
import {evaluate} from "./evaluate.js";
import logo from "./logo.svg.proxy.js";
import {useWebMidi} from "./midi.js";
import * as tunes from "./tunes.js";
import useRepl from "./useRepl.js";
const [_, codeParam] = window.location.href.split("#");
let decoded;
try {
@@ -16,11 +14,6 @@ try {
} catch (err) {
console.warn("failed to decode", err);
}
const getHotCode = async () => {
return fetch("/hot.js").then((res) => res.text()).then((src) => {
return src.split("export default").slice(-1)[0].trim();
});
};
const defaultSynth = new Tone.PolySynth().chain(new Tone.Gain(0.5), Tone.Destination);
defaultSynth.set({
oscillator: {type: "triangle"},
@@ -35,76 +28,14 @@ function getRandomTune() {
}
const randomTune = getRandomTune();
function App() {
const [code, setCode] = useState(decoded || randomTune);
const [activeCode, setActiveCode] = useState();
const [log, setLog] = useState("");
const logBox = useRef();
const [error, setError] = useState();
const [pattern, setPattern] = useState();
const [activePattern, setActivePattern] = useState();
const dirty = code !== activeCode;
const activateCode = (_code = code) => {
!cycle.started && cycle.start();
if (activeCode && !dirty) {
setError(void 0);
return;
}
try {
const parsed = evaluate(_code);
setPattern(() => parsed.pattern);
activatePattern(parsed.pattern);
setError(void 0);
setActiveCode(_code);
} catch (err) {
setError(err);
}
};
const activatePattern = (_pattern) => {
try {
setActivePattern(() => _pattern);
window.location.hash = "#" + encodeURIComponent(btoa(code));
} catch (err) {
setError(err);
}
};
const [isHot, setIsHot] = useState(false);
const pushLog = (message) => setLog((log2) => log2 + `${log2 ? "\n\n" : ""}${message}`);
const logCycle = (_events, cycle2) => {
if (_events.length) {
pushLog(`# cycle ${cycle2}
` + _events.map((e) => e.show()).join("\n"));
}
};
const cycle = useCycle({
onEvent: useCallback((time, event) => {
try {
if (!event.value?.onTrigger) {
const note = event.value?.value || event.value;
if (!isNote(note)) {
throw new Error("not a note: " + note);
}
defaultSynth.triggerAttackRelease(note, event.duration, time);
} else {
const {onTrigger} = event.value;
onTrigger(time, event);
}
} catch (err) {
console.warn(err);
err.message = "unplayable event: " + err?.message;
pushLog(err.message);
}
}, []),
onQuery: useCallback((span) => {
try {
return activePattern?.query(span) || [];
} catch (err) {
setError(err);
return [];
}
}, [activePattern]),
onSchedule: useCallback((_events, cycle2) => logCycle(_events, cycle2), [activePattern]),
ready: !!activePattern
const {setCode, setPattern, error, code, cycle, dirty, log, togglePlay, activateCode, pattern, pushLog} = useRepl({
tune: decoded || randomTune,
defaultSynth
});
const logBox = useRef();
useLayoutEffect(() => {
logBox.current.scrollTop = logBox.current?.scrollHeight;
}, [log]);
useLayoutEffect(() => {
const handleKeyPress = (e) => {
if (e.ctrlKey || e.altKey) {
@@ -121,22 +52,6 @@ function App() {
document.addEventListener("keypress", handleKeyPress);
return () => document.removeEventListener("keypress", handleKeyPress);
}, [pattern, code]);
useEffect(() => {
if (isHot) {
if (typeof hot !== "string") {
getHotCode().then((_code) => {
});
activatePattern(hot);
return;
} else {
setCode(hot);
activateCode(hot);
}
}
}, [code, isHot]);
useLayoutEffect(() => {
logBox.current.scrollTop = logBox.current?.scrollHeight;
}, [log]);
useWebMidi({
ready: useCallback(({outputs}) => {
pushLog(`WebMidi ready! Just add .midi(${outputs.map((o) => `"${o.name}"`).join(" | ")}) to the pattern. `);
@@ -168,15 +83,11 @@ function App() {
console.log("tune", _code);
setCode(_code);
const parsed = evaluate(_code);
setActivePattern(parsed.pattern);
setPattern(parsed.pattern);
}
}, "🎲 random tune"), window.location.href.includes("http://localhost:8080") && /* @__PURE__ */ React.createElement("button", {
onClick: () => {
if (isHot || confirm("Really switch? You might loose your current pattern..")) {
setIsHot((h) => !h);
}
}
}, "🔥 toggle hot mode"))), /* @__PURE__ */ React.createElement("section", {
}, "🎲 random tune"), /* @__PURE__ */ React.createElement("button", null, /* @__PURE__ */ React.createElement("a", {
href: "./tutorial"
}, "📚 tutorial")))), /* @__PURE__ */ React.createElement("section", {
className: "grow flex flex-col text-gray-100"
}, /* @__PURE__ */ React.createElement("div", {
className: "grow relative"
@@ -184,32 +95,21 @@ function App() {
className: cx("h-full bg-[#2A3236]", error ? "focus:ring-red-500" : "focus:ring-slate-800")
}, /* @__PURE__ */ React.createElement(CodeMirror, {
value: code,
readOnly: isHot,
options: {
mode: "javascript",
theme: "material",
lineNumbers: true
},
onChange: (_2, __, value) => {
if (!isHot) {
setCode(value);
}
}
onChange: (_2, __, value) => setCode(value)
}), /* @__PURE__ */ React.createElement("span", {
className: "p-4 absolute top-0 right-0 text-xs whitespace-pre text-right"
}, !cycle.started ? `press ctrl+enter to play
` : !isHot && code !== activeCode ? `ctrl+enter to update
` : "no changes\n", isHot && "🔥 hot mode: go to hot.js to edit pattern, then save")), error && /* @__PURE__ */ React.createElement("div", {
` : dirty ? `ctrl+enter to update
` : "no changes\n")), error && /* @__PURE__ */ React.createElement("div", {
className: cx("absolute right-2 bottom-2", "text-red-500")
}, error?.message || "unknown error")), /* @__PURE__ */ React.createElement("button", {
className: "flex-none w-full border border-gray-700 p-2 bg-slate-700 hover:bg-slate-500",
onClick: () => {
if (!cycle.started) {
activateCode();
} else {
cycle.stop();
}
}
onClick: () => togglePlay()
}, cycle.started ? "pause" : "play"), /* @__PURE__ */ React.createElement("textarea", {
className: "grow bg-[#283237] border-0 text-xs min-h-[200px]",
value: log,
+2 -3
View File
@@ -34,9 +34,8 @@ function useCycle(props) {
};
useEffect(() => {
ready && query();
}, [onEvent, onSchedule, onQuery]);
}, [onEvent, onSchedule, onQuery, ready]);
const start = async () => {
console.log("start");
setStarted(true);
await Tone.start();
Tone.Transport.start("+0.1");
@@ -47,6 +46,6 @@ function useCycle(props) {
Tone.Transport.pause();
};
const toggle = () => started ? stop() : start();
return {start, stop, onEvent, started, toggle, query, activeCycle};
return {start, stop, setStarted, onEvent, started, toggle, query, activeCycle};
}
export default useCycle;
+9
View File
@@ -0,0 +1,9 @@
import {useEffect} from "../_snowpack/pkg/react.js";
function usePostMessage(listener) {
useEffect(() => {
window.addEventListener("message", listener);
return () => window.removeEventListener("message", listener);
}, [listener]);
return (data) => window.postMessage(data, "*");
}
export default usePostMessage;
+105
View File
@@ -0,0 +1,105 @@
import {useCallback, useState, useMemo} from "../_snowpack/pkg/react.js";
import {isNote} from "../_snowpack/pkg/tone.js";
import {evaluate} from "./evaluate.js";
import useCycle from "./useCycle.js";
import usePostMessage from "./usePostMessage.js";
let s4 = () => {
return Math.floor((1 + Math.random()) * 65536).toString(16).substring(1);
};
function useRepl({tune, defaultSynth, autolink = true}) {
const id = useMemo(() => s4(), []);
const [code, setCode] = useState(tune);
const [activeCode, setActiveCode] = useState();
const [log, setLog] = useState("");
const [error, setError] = useState();
const [pattern, setPattern] = useState();
const dirty = code !== activeCode;
const activateCode = (_code = code) => {
!cycle.started && cycle.start();
broadcast({type: "start", from: id});
if (activeCode && !dirty) {
setError(void 0);
return;
}
try {
const parsed = evaluate(_code);
setPattern(() => parsed.pattern);
if (autolink) {
window.location.hash = "#" + encodeURIComponent(btoa(code));
}
setError(void 0);
setActiveCode(_code);
} catch (err) {
setError(err);
}
};
const pushLog = (message) => setLog((log2) => log2 + `${log2 ? "\n\n" : ""}${message}`);
const logCycle = (_events, cycle2) => {
if (_events.length) {
pushLog(`# cycle ${cycle2}
` + _events.map((e) => e.show()).join("\n"));
}
};
const cycle = useCycle({
onEvent: useCallback((time, event) => {
try {
if (!event.value?.onTrigger) {
const note = event.value?.value || event.value;
if (!isNote(note)) {
throw new Error("not a note: " + note);
}
if (defaultSynth) {
defaultSynth.triggerAttackRelease(note, event.duration, time);
} else {
throw new Error("no defaultSynth passed to useRepl.");
}
} else {
const {onTrigger} = event.value;
onTrigger(time, event);
}
} catch (err) {
console.warn(err);
err.message = "unplayable event: " + err?.message;
pushLog(err.message);
}
}, []),
onQuery: useCallback((span) => {
try {
return pattern?.query(span) || [];
} catch (err) {
setError(err);
return [];
}
}, [pattern]),
onSchedule: useCallback((_events, cycle2) => logCycle(_events, cycle2), [pattern]),
ready: !!pattern
});
const broadcast = usePostMessage(({data: {from, type}}) => {
if (type === "start" && from !== id) {
cycle.setStarted(false);
setActiveCode(void 0);
}
});
const togglePlay = () => {
if (!cycle.started) {
activateCode();
} else {
cycle.stop();
}
};
return {
code,
setCode,
pattern,
error,
cycle,
setPattern,
dirty,
log,
togglePlay,
activateCode,
activeCode,
pushLog
};
}
export default useRepl;
+378
View File
@@ -590,6 +590,343 @@ select {
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
}
.prose {
color: var(--tw-prose-body);
max-width: 65ch;
}
.prose :where([class~="lead"]):not(:where([class~="not-prose"] *)) {
color: var(--tw-prose-lead);
font-size: 1.25em;
line-height: 1.6;
margin-top: 1.2em;
margin-bottom: 1.2em;
}
.prose :where(a):not(:where([class~="not-prose"] *)) {
color: var(--tw-prose-links);
text-decoration: underline;
font-weight: 500;
}
.prose :where(strong):not(:where([class~="not-prose"] *)) {
color: var(--tw-prose-bold);
font-weight: 600;
}
.prose :where(ol):not(:where([class~="not-prose"] *)) {
list-style-type: decimal;
padding-left: 1.625em;
}
.prose :where(ol[type="A"]):not(:where([class~="not-prose"] *)) {
list-style-type: upper-alpha;
}
.prose :where(ol[type="a"]):not(:where([class~="not-prose"] *)) {
list-style-type: lower-alpha;
}
.prose :where(ol[type="A" s]):not(:where([class~="not-prose"] *)) {
list-style-type: upper-alpha;
}
.prose :where(ol[type="a" s]):not(:where([class~="not-prose"] *)) {
list-style-type: lower-alpha;
}
.prose :where(ol[type="I"]):not(:where([class~="not-prose"] *)) {
list-style-type: upper-roman;
}
.prose :where(ol[type="i"]):not(:where([class~="not-prose"] *)) {
list-style-type: lower-roman;
}
.prose :where(ol[type="I" s]):not(:where([class~="not-prose"] *)) {
list-style-type: upper-roman;
}
.prose :where(ol[type="i" s]):not(:where([class~="not-prose"] *)) {
list-style-type: lower-roman;
}
.prose :where(ol[type="1"]):not(:where([class~="not-prose"] *)) {
list-style-type: decimal;
}
.prose :where(ul):not(:where([class~="not-prose"] *)) {
list-style-type: disc;
padding-left: 1.625em;
}
.prose :where(ol > li):not(:where([class~="not-prose"] *))::marker {
font-weight: 400;
color: var(--tw-prose-counters);
}
.prose :where(ul > li):not(:where([class~="not-prose"] *))::marker {
color: var(--tw-prose-bullets);
}
.prose :where(hr):not(:where([class~="not-prose"] *)) {
border-color: var(--tw-prose-hr);
border-top-width: 1px;
margin-top: 3em;
margin-bottom: 3em;
}
.prose :where(blockquote):not(:where([class~="not-prose"] *)) {
font-weight: 500;
font-style: italic;
color: var(--tw-prose-quotes);
border-left-width: 0.25rem;
border-left-color: var(--tw-prose-quote-borders);
quotes: "\201C""\201D""\2018""\2019";
margin-top: 1.6em;
margin-bottom: 1.6em;
padding-left: 1em;
}
.prose :where(blockquote p:first-of-type):not(:where([class~="not-prose"] *))::before {
content: open-quote;
}
.prose :where(blockquote p:last-of-type):not(:where([class~="not-prose"] *))::after {
content: close-quote;
}
.prose :where(h1):not(:where([class~="not-prose"] *)) {
color: var(--tw-prose-headings);
font-weight: 800;
font-size: 2.25em;
margin-top: 0;
margin-bottom: 0.8888889em;
line-height: 1.1111111;
}
.prose :where(h1 strong):not(:where([class~="not-prose"] *)) {
font-weight: 900;
}
.prose :where(h2):not(:where([class~="not-prose"] *)) {
color: var(--tw-prose-headings);
font-weight: 700;
font-size: 1.5em;
margin-top: 2em;
margin-bottom: 1em;
line-height: 1.3333333;
}
.prose :where(h2 strong):not(:where([class~="not-prose"] *)) {
font-weight: 800;
}
.prose :where(h3):not(:where([class~="not-prose"] *)) {
color: var(--tw-prose-headings);
font-weight: 600;
font-size: 1.25em;
margin-top: 1.6em;
margin-bottom: 0.6em;
line-height: 1.6;
}
.prose :where(h3 strong):not(:where([class~="not-prose"] *)) {
font-weight: 700;
}
.prose :where(h4):not(:where([class~="not-prose"] *)) {
color: var(--tw-prose-headings);
font-weight: 600;
margin-top: 1.5em;
margin-bottom: 0.5em;
line-height: 1.5;
}
.prose :where(h4 strong):not(:where([class~="not-prose"] *)) {
font-weight: 700;
}
.prose :where(figure > *):not(:where([class~="not-prose"] *)) {
margin-top: 0;
margin-bottom: 0;
}
.prose :where(figcaption):not(:where([class~="not-prose"] *)) {
color: var(--tw-prose-captions);
font-size: 0.875em;
line-height: 1.4285714;
margin-top: 0.8571429em;
}
.prose :where(code):not(:where([class~="not-prose"] *)) {
color: var(--tw-prose-code);
font-weight: 600;
font-size: 0.875em;
}
.prose :where(code):not(:where([class~="not-prose"] *))::before {
content: "`";
}
.prose :where(code):not(:where([class~="not-prose"] *))::after {
content: "`";
}
.prose :where(a code):not(:where([class~="not-prose"] *)) {
color: var(--tw-prose-links);
}
.prose :where(pre):not(:where([class~="not-prose"] *)) {
color: var(--tw-prose-pre-code);
background-color: var(--tw-prose-pre-bg);
overflow-x: auto;
font-weight: 400;
font-size: 0.875em;
line-height: 1.7142857;
margin-top: 1.7142857em;
margin-bottom: 1.7142857em;
border-radius: 0.375rem;
padding-top: 0.8571429em;
padding-right: 1.1428571em;
padding-bottom: 0.8571429em;
padding-left: 1.1428571em;
}
.prose :where(pre code):not(:where([class~="not-prose"] *)) {
background-color: transparent;
border-width: 0;
border-radius: 0;
padding: 0;
font-weight: inherit;
color: inherit;
font-size: inherit;
font-family: inherit;
line-height: inherit;
}
.prose :where(pre code):not(:where([class~="not-prose"] *))::before {
content: none;
}
.prose :where(pre code):not(:where([class~="not-prose"] *))::after {
content: none;
}
.prose :where(table):not(:where([class~="not-prose"] *)) {
width: 100%;
table-layout: auto;
text-align: left;
margin-top: 2em;
margin-bottom: 2em;
font-size: 0.875em;
line-height: 1.7142857;
}
.prose :where(thead):not(:where([class~="not-prose"] *)) {
border-bottom-width: 1px;
border-bottom-color: var(--tw-prose-th-borders);
}
.prose :where(thead th):not(:where([class~="not-prose"] *)) {
color: var(--tw-prose-headings);
font-weight: 600;
vertical-align: bottom;
padding-right: 0.5714286em;
padding-bottom: 0.5714286em;
padding-left: 0.5714286em;
}
.prose :where(tbody tr):not(:where([class~="not-prose"] *)) {
border-bottom-width: 1px;
border-bottom-color: var(--tw-prose-td-borders);
}
.prose :where(tbody tr:last-child):not(:where([class~="not-prose"] *)) {
border-bottom-width: 0;
}
.prose :where(tbody td):not(:where([class~="not-prose"] *)) {
vertical-align: baseline;
padding-top: 0.5714286em;
padding-right: 0.5714286em;
padding-bottom: 0.5714286em;
padding-left: 0.5714286em;
}
.prose {
--tw-prose-body: #374151;
--tw-prose-headings: #111827;
--tw-prose-lead: #4b5563;
--tw-prose-links: #111827;
--tw-prose-bold: #111827;
--tw-prose-counters: #6b7280;
--tw-prose-bullets: #d1d5db;
--tw-prose-hr: #e5e7eb;
--tw-prose-quotes: #111827;
--tw-prose-quote-borders: #e5e7eb;
--tw-prose-captions: #6b7280;
--tw-prose-code: #111827;
--tw-prose-pre-code: #e5e7eb;
--tw-prose-pre-bg: #1f2937;
--tw-prose-th-borders: #d1d5db;
--tw-prose-td-borders: #e5e7eb;
--tw-prose-invert-body: #d1d5db;
--tw-prose-invert-headings: #fff;
--tw-prose-invert-lead: #9ca3af;
--tw-prose-invert-links: #fff;
--tw-prose-invert-bold: #fff;
--tw-prose-invert-counters: #9ca3af;
--tw-prose-invert-bullets: #4b5563;
--tw-prose-invert-hr: #374151;
--tw-prose-invert-quotes: #f3f4f6;
--tw-prose-invert-quote-borders: #374151;
--tw-prose-invert-captions: #9ca3af;
--tw-prose-invert-code: #fff;
--tw-prose-invert-pre-code: #d1d5db;
--tw-prose-invert-pre-bg: rgb(0 0 0 / 50%);
--tw-prose-invert-th-borders: #4b5563;
--tw-prose-invert-td-borders: #374151;
font-size: 1rem;
line-height: 1.75;
}
.prose :where(p):not(:where([class~="not-prose"] *)) {
margin-top: 1.25em;
margin-bottom: 1.25em;
}
.prose :where(img):not(:where([class~="not-prose"] *)) {
margin-top: 2em;
margin-bottom: 2em;
}
.prose :where(video):not(:where([class~="not-prose"] *)) {
margin-top: 2em;
margin-bottom: 2em;
}
.prose :where(figure):not(:where([class~="not-prose"] *)) {
margin-top: 2em;
margin-bottom: 2em;
}
.prose :where(h2 code):not(:where([class~="not-prose"] *)) {
font-size: 0.875em;
}
.prose :where(h3 code):not(:where([class~="not-prose"] *)) {
font-size: 0.9em;
}
.prose :where(li):not(:where([class~="not-prose"] *)) {
margin-top: 0.5em;
margin-bottom: 0.5em;
}
.prose :where(ol > li):not(:where([class~="not-prose"] *)) {
padding-left: 0.375em;
}
.prose :where(ul > li):not(:where([class~="not-prose"] *)) {
padding-left: 0.375em;
}
.prose > :where(ul > li p):not(:where([class~="not-prose"] *)) {
margin-top: 0.75em;
margin-bottom: 0.75em;
}
.prose > :where(ul > li > *:first-child):not(:where([class~="not-prose"] *)) {
margin-top: 1.25em;
}
.prose > :where(ul > li > *:last-child):not(:where([class~="not-prose"] *)) {
margin-bottom: 1.25em;
}
.prose > :where(ol > li > *:first-child):not(:where([class~="not-prose"] *)) {
margin-top: 1.25em;
}
.prose > :where(ol > li > *:last-child):not(:where([class~="not-prose"] *)) {
margin-bottom: 1.25em;
}
.prose :where(ul ul, ul ol, ol ul, ol ol):not(:where([class~="not-prose"] *)) {
margin-top: 0.75em;
margin-bottom: 0.75em;
}
.prose :where(hr + *):not(:where([class~="not-prose"] *)) {
margin-top: 0;
}
.prose :where(h2 + *):not(:where([class~="not-prose"] *)) {
margin-top: 0;
}
.prose :where(h3 + *):not(:where([class~="not-prose"] *)) {
margin-top: 0;
}
.prose :where(h4 + *):not(:where([class~="not-prose"] *)) {
margin-top: 0;
}
.prose :where(thead th:first-child):not(:where([class~="not-prose"] *)) {
padding-left: 0;
}
.prose :where(thead th:last-child):not(:where([class~="not-prose"] *)) {
padding-right: 0;
}
.prose :where(tbody td:first-child):not(:where([class~="not-prose"] *)) {
padding-left: 0;
}
.prose :where(tbody td:last-child):not(:where([class~="not-prose"] *)) {
padding-right: 0;
}
.prose > :where(:first-child):not(:where([class~="not-prose"] *)) {
margin-top: 0;
}
.prose > :where(:last-child):not(:where([class~="not-prose"] *)) {
margin-bottom: 0;
}
.static {
position: static;
}
@@ -621,6 +958,9 @@ select {
.flex {
display: flex;
}
.contents {
display: contents;
}
.h-16 {
height: 4rem;
}
@@ -639,6 +979,9 @@ select {
.w-16 {
width: 4rem;
}
.max-w-3xl {
max-width: 48rem;
}
.flex-none {
flex: none;
}
@@ -648,12 +991,18 @@ select {
.transform {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.cursor-not-allowed {
cursor: not-allowed;
}
.flex-col {
flex-direction: column;
}
.items-center {
align-items: center;
}
.justify-center {
justify-content: center;
}
.justify-between {
justify-content: space-between;
}
@@ -667,6 +1016,14 @@ select {
margin-right: calc(1rem * var(--tw-space-x-reverse));
margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
}
.space-y-0 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(0px * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(0px * var(--tw-space-y-reverse));
}
.overflow-auto {
overflow: auto;
}
.whitespace-pre {
white-space: pre;
}
@@ -687,6 +1044,10 @@ select {
--tw-border-opacity: 1;
border-color: rgb(55 65 81 / var(--tw-border-opacity));
}
.border-slate-500 {
--tw-border-opacity: 1;
border-color: rgb(100 116 139 / var(--tw-border-opacity));
}
.bg-\[\#2A3236\] {
--tw-bg-opacity: 1;
background-color: rgb(42 50 54 / var(--tw-bg-opacity));
@@ -703,6 +1064,10 @@ select {
--tw-bg-opacity: 1;
background-color: rgb(40 50 55 / var(--tw-bg-opacity));
}
.bg-slate-600 {
--tw-bg-opacity: 1;
background-color: rgb(71 85 105 / var(--tw-bg-opacity));
}
.p-4 {
padding: 1rem;
}
@@ -732,6 +1097,14 @@ select {
--tw-text-opacity: 1;
color: rgb(239 68 68 / var(--tw-text-opacity));
}
.text-white {
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
}
.text-slate-400 {
--tw-text-opacity: 1;
color: rgb(148 163 184 / var(--tw-text-opacity));
}
.filter {
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
}
@@ -746,6 +1119,11 @@ select {
background-color: rgb(100 116 139 / var(--tw-bg-opacity));
}
.hover\:bg-slate-600:hover {
--tw-bg-opacity: 1;
background-color: rgb(71 85 105 / var(--tw-bg-opacity));
}
.focus\:ring-red-500:focus {
--tw-ring-opacity: 1;
--tw-ring-color: rgb(239 68 68 / var(--tw-ring-opacity));
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+16
View File
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="icon" href="/tutorial/favicon.e3ab9dd9.ico">
<link rel="stylesheet" type="text/css" href="/tutorial/index.1e09ac22.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Strudel REPL">
<title>Strudel Tutorial</title>
</head>
<body>
<div id="root"></div>
<noscript>You need to enable JavaScript to run this app.</noscript>
<script src="/tutorial/index.ea127b5f.js" defer=""></script>
</body>
</html>