# Initialization code that runs before all other cells
import marimo as mo
import typst as typst_lib
I 💖 typst
header = """
#set page(width: auto, height: auto, margin: 10pt)
#set text(size: 20pt)
"""
def typst_math(input):
source = (header + input).encode("utf-8")
svg = typst_lib.compile(source, format="svg")
return mo.Html(svg.decode("utf-8"))
typst_math("$(oo -> oo) / (1 eq 1)$")
def sandwiches():
source = """
#import "@preview/cetz:0.4.2": canvas, draw
#set page(height: auto, margin: 1cm)
#let colors = (blue, red, green, purple, orange, yellow, teal, maroon)
#let up = (0, 0, 1)
#let down = (0, 0, -1)
#let east = (1, 0, 0)
#let west = (-1, 0, 0)
#let north = (0, 1, 0)
#let south = (0, -1, 0)
#let vec-scale = 0.8
#let rot-z = 20deg
#let tilt = 80deg
#let transform(x, y, z) = {
let x1 = x * calc.cos(rot-z) - y * calc.sin(rot-z)
let y1 = x * calc.sin(rot-z) + y * calc.cos(rot-z)
let z1 = z
let y2 = y1 * calc.cos(tilt) - z1 * calc.sin(tilt)
let z2 = y1 * calc.sin(tilt) + z1 * calc.cos(tilt)
(x1, -y2)
}
#let draw-vector(anchor, vec, color) = {
let o = transform(..anchor)
let end-pt = (anchor.at(0) + vec.at(0), anchor.at(1) + vec.at(1), anchor.at(2) + vec.at(2))
let e = transform(..end-pt)
draw.set-style(mark: (end: (symbol: ">")))
draw.line(o, e, stroke: (paint: color, thickness: 2pt))
}
#let draw-tangents(vecs) = {
for (i, t) in vecs.enumerate() {
let scaled-vec = (t.vector.at(0) * vec-scale, t.vector.at(1) * vec-scale, t.vector.at(2) * vec-scale)
draw-vector(t.anchor, scaled-vec, colors.at(i))
draw.circle(transform(..t.anchor), radius: 0.08, fill: black)
}
}
#let draw-on-sphere(vecs) = {
let r = 2
let n = 30
// Latitude lines
for i in range(0, 9) {
let lat = (i - 4) * 20deg
let r-lat = r * calc.cos(lat)
let z = r * calc.sin(lat)
let pts = ()
for j in range(0, n + 1) {
let lng = j * 360deg / n
pts.push(transform(r-lat * calc.cos(lng), r-lat * calc.sin(lng), z))
}
draw.line(..pts, stroke: (paint: gray.darken(20%), thickness: 0.5pt))
}
// Longitude lines
for i in range(0, 8) {
let lng = i * 360deg / 8
let pts = ()
for j in range(0, n + 1) {
let lat = (j - n/2) * 180deg / n
pts.push(transform(
r * calc.cos(lat) * calc.cos(lng),
r * calc.cos(lat) * calc.sin(lng),
r * calc.sin(lat)
))
}
draw.line(..pts, stroke: (paint: gray.darken(20%), thickness: 0.5pt))
}
// Draw tangent points on sphere
for (i, t) in vecs.enumerate() {
let vec = t.vector
let norm = calc.sqrt(vec.at(0)*vec.at(0) + vec.at(1)*vec.at(1) + vec.at(2)*vec.at(2))
let pole = transform(r * vec.at(0)/norm, r * vec.at(1)/norm, r * vec.at(2)/norm)
draw.circle(pole, radius: 0.15, fill: colors.at(i), stroke: colors.at(i))
}
}
#let draw-plane(offset: (0, 0, 0), fill-color: rgb("#e8e8e8")) = {
let base-corners = ((-1.5, -1, 0), (1.5, -1, 0), (1.5, 1, 0), (-1.5, 1, 0))
let corners = base-corners.map(c => transform(
c.at(0) + offset.at(0),
c.at(1) + offset.at(1),
c.at(2) + offset.at(2)
))
draw.line(..corners, close: true, fill: fill-color, stroke: black)
}
#let draw-cube() = {
let cube-pts = (
(-1, -1, -1), (1, -1, -1), (1, 1, -1), (-1, 1, -1),
(-1, -1, 1), (1, -1, 1), (1, 1, 1), (-1, 1, 1)
)
let edges = ((0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(6,7),(7,4),(0,4),(1,5),(2,6),(3,7))
for (i, j) in edges {
draw.line(transform(..cube-pts.at(i)), transform(..cube-pts.at(j)), stroke: black)
}
}
#let draw-cylinder(axis: 2, height: 2, radius: 1) = {
let n = 30
// Generate circle points based on axis
let circle-pts(z-val) = {
let pts = ()
for i in range(0, n + 1) {
let angle = i * 360deg / n
let x = radius * calc.cos(angle)
let y = radius * calc.sin(angle)
if axis == 0 {
pts.push((z-val, x, y))
} else if axis == 1 {
pts.push((x, z-val, y))
} else {
pts.push((x, y, z-val))
}
}
pts
}
let bottom-z = -height / 2
let top-z = height / 2
// Draw top and bottom circles
draw.line(..circle-pts(bottom-z).map(p => transform(..p)), close: true, stroke: black)
draw.line(..circle-pts(top-z).map(p => transform(..p)), close: true, stroke: black)
// Draw connecting lines (every few steps for clarity)
for i in range(0, n, step: 4) {
let angle = i * 360deg / n
let x = radius * calc.cos(angle)
let y = radius * calc.sin(angle)
let bottom = none
let top = none
if axis == 0 {
bottom = (bottom-z, x, y)
top = (top-z, x, y)
} else if axis == 1 {
bottom = (x, bottom-z, y)
top = (x, top-z, y)
} else {
bottom = (x, y, bottom-z)
top = (x, y, top-z)
}
draw.line(transform(..bottom), transform(..top), stroke: black)
}
}
#let mid = [#set text(size: 40pt); #sym.arrow.double]
// Row 1: Single upward vector
#{
let vecs = ((anchor: (0, 0, 0), vector: up),)
grid(columns: (1fr, 20pt, 1fr), align: center+horizon,
canvas(length: 1.3cm, {
draw-plane()
draw-tangents(vecs)
}),
mid,
canvas(length: 1.3cm, { draw-on-sphere(vecs) })
)
}
#pagebreak()
// Row 2: Opposing vectors (parallel planes)
#{
let vecs = (
(anchor: (0, 0, -1), vector: up),
(anchor: (0, 0, 1), vector: down)
)
grid(columns: (1fr, 20pt, 1fr), align: center+horizon,
canvas(length: 1.3cm, {
draw-plane(offset: (0, 0, -1))
draw-plane(offset: (0, 0, 1), fill-color: rgb("#d8d8d8"))
draw-tangents(vecs)
}),
mid,
canvas(length: 1.3cm, { draw-on-sphere(vecs) })
)
}
#pagebreak()
// Row 3: Cylinder
#{
let vecs = (
(anchor: (0, 0, 1), vector: up),
)
grid(columns: (1fr, 20pt, 1fr), align: center+horizon,
canvas(length: 1.3cm, {
draw-cylinder(axis: 2, height: 2, radius: 1)
draw-tangents(vecs)
}),
mid,
canvas(length: 1.3cm, { draw-on-sphere(vecs) })
)
}
#pagebreak()
// Row 4: Cube with inverted vectors
#{
let vecs = (
(anchor: up, vector: down),
(anchor: down, vector: up),
(anchor: east, vector: west),
(anchor: west, vector: east)
)
grid(columns: (1fr, 20pt, 1fr), align: center+horizon,
canvas(length: 1.3cm, {
draw-cube()
draw-tangents(vecs)
}),
mid,
canvas(length: 1.3cm, { draw-on-sphere(vecs) })
)
}
"""
out = typst_lib.compile(source.encode("utf-8"), format="svg")
return [mo.Html(p.decode("utf-8")) for p in out]
sandwiches()
[Html(), Html(), Html(), Html()]