diff --git a/packages/preview/zap/0.2.1/.gitignore b/packages/preview/zap/0.2.1/.gitignore
new file mode 100644
index 0000000000..a66b35a006
--- /dev/null
+++ b/packages/preview/zap/0.2.1/.gitignore
@@ -0,0 +1,5 @@
+/.idea/
+/main.typ
+*.pdf
+/doc/**/*.svg
+/main.svg
diff --git a/packages/preview/zap/0.2.1/LICENSE b/packages/preview/zap/0.2.1/LICENSE
new file mode 100644
index 0000000000..65c5ca88a6
--- /dev/null
+++ b/packages/preview/zap/0.2.1/LICENSE
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/packages/preview/zap/0.2.1/README.md b/packages/preview/zap/0.2.1/README.md
new file mode 100644
index 0000000000..1feb8eefa2
--- /dev/null
+++ b/packages/preview/zap/0.2.1/README.md
@@ -0,0 +1,52 @@
+
+
+
+
+
+# âĄī¸ Zap for Typst
+
+**Zap** is a lightweight đĒļ Typst package that makes drawing electronic circuits simple and intuitive. It's the first Typst library designed to align with widely recognized standards like **IEC** and **IEEE/ANSI** đ.
+
+[Documentation](https://l0uisgrange.github.io/zap/) â [Examples](https://l0uisgrange.github.io/zap/examples) â [Forum](https://github.com/l0uisgrange/zap/discussions/categories/q-a)
+
+## Simple examples
+
+You can find the full list of examples [here](https://l0uisgrange.github.io/zap/examples).
+
+
+
+
+
+ |
+
+
+ |
+
+
+ | Simple example |
+ Wheatstone bridge |
+
+
+
+
+## Quick usage
+
+```typst
+#import "@preview/zap:0.2.1"
+
+#zap.canvas({
+ import zap: *
+
+ isource("i1", (0,0), (5,0))
+ resistor("r1", (5,5), (0,5))
+ wire("r1.out", "i1.out")
+})
+```
+
+## Online documentation
+
+You can find the full documentation đ [available online](https://l0uisgrange.github.io/zap/). It provides comprehensive guides, a detailed list of components, full API references, and example codes to get you started easily.
+
+## Contributing
+
+I highly welcome contributions đą! Creating and maintaining Zap takes time and love. If you'd like to help, check out the [contribution procedure](https://github.com/l0uisgrange/zap/blob/main/CONTRIBUTING.md) and join the journey đ¤Š!
diff --git a/packages/preview/zap/0.2.1/src/component.typ b/packages/preview/zap/0.2.1/src/component.typ
new file mode 100644
index 0000000000..f0147e6cbe
--- /dev/null
+++ b/packages/preview/zap/0.2.1/src/component.typ
@@ -0,0 +1,140 @@
+#import "dependencies.typ": cetz
+#import "styles.typ": default-style
+#import "decorations.typ": current, flow, voltage
+#import "components/nodes.typ": node
+#import "components/nodes.typ": node
+#import "utils.typ": get-label-anchor
+
+#let component(
+ draw: none,
+ label: none,
+ i: none,
+ f: none,
+ u: none,
+ n: none,
+ position: 50%,
+ scale: 1.0,
+ rotate: 0deg,
+ debug: false,
+ style: none,
+ ..params,
+) = {
+ let p-position = position
+ let (uid, name, ..position) = params.pos()
+ if position.at(1, default: none) == none {
+ position = (position.first(),)
+ }
+ assert(position.len() in (1, 2), message: "accepts only 2 or 3 (for 2 nodes components only) positional arguments")
+ assert(position.at(1, default: none) == none or rotate == 0deg, message: "cannot use rotate argument with 2 nodes")
+ assert(type(name) == str, message: "component name must be a string")
+ assert(type(scale) == float, message: "scale must be a float")
+ assert(type(rotate) == angle, message: "rotate must an angle")
+ assert(label == none or type(label) in (content, str, dictionary), message: "label must content, dictionary or string")
+ assert(params.at("variant", default: default-style.variant) in ("ieee", "iec", "pretty"), message: "variant must be 'iec', 'ieee' or 'pretty'")
+ assert(n in (none, "*-", "*-*", "-*", "o-*", "*-o", "o-", "-o", "o-o"))
+
+ let p-rotate = rotate
+ let p-scale = scale
+ let p-draw = draw
+ let p-style = style
+ import cetz.draw: *
+
+ group(name: name, ctx => {
+ let pre-style = cetz.styles.resolve(ctx.style, root: "zap", base: default-style)
+ let base-style = p-style + pre-style + pre-style.at(uid, default: (something: none))
+ let style = cetz.styles.resolve(base-style, merge: params.named())
+ let p-rotate = p-rotate
+ let (ctx, ..position) = cetz.coordinate.resolve(ctx, ..position)
+ let p-origin = position.first()
+ if position.len() == 2 {
+ anchor("in", position.first())
+ anchor("out", position.last())
+ p-rotate = cetz.vector.angle2(..position)
+ p-origin = (position.first(), p-position, position.last())
+ }
+ set-origin(p-origin)
+ rotate(p-rotate)
+
+ // Component
+ on-layer(1, {
+ group(name: "component", {
+ scale(p-scale * style.at("scale", default: 1))
+ draw(ctx, position, style)
+ copy-anchors("bounds")
+ })
+ })
+
+ copy-anchors("component")
+
+ // Label
+ on-layer(0, {
+ if label != none {
+ if type(label) == dictionary and label.at("content", default: none) == none { panic("Label dictionary needs at least content key") }
+ let (label, distance, width, height, anchor) = if type(label) == dictionary {
+ (label.at("content", default: none), label.at("distance", default: 7pt), ..cetz.util.measure(ctx, label.at("content")), label.at("anchor", default: "north"))
+ } else {
+ (label, 7pt, ..cetz.util.measure(ctx, label), "north")
+ }
+ let reverse = "south" in anchor
+ let new-position = (0.5 * width * calc.abs(calc.sin(p-rotate)) + 0.5 * height * calc.abs(calc.cos(p-rotate)))
+ content("component."+anchor, anchor: get-label-anchor(p-rotate).at(if reverse { 1 } else { 0 }), label, padding: distance)
+ }
+ })
+
+ // Decorations
+ if position.len() == 2 {
+ line("in", "component.west", ..pre-style.at("wires"))
+ line("component.east", "out", ..pre-style.at("wires"))
+
+ if i != none {
+ current(ctx, i)
+ }
+ if f != none {
+ flow(ctx, f)
+ }
+ if u != none {
+ voltage(ctx, u, p-rotate)
+ }
+ if n != none {
+ if "*-" in n {
+ node("", "in")
+ }
+ if "-*" in n {
+ node("", "out")
+ }
+ if "o-" in n {
+ node("", "in", fill: false)
+ }
+ if "-o" in n {
+ node("", "out", fill: false)
+ }
+ }
+ }
+ })
+
+ if (debug) {
+ on-layer(1, {
+ for-each-anchor(name, exclude: ("start", "end", "mid", "component", "line", "bounds", "gl", "0", "1"), name => {
+ circle((), radius: .7pt, stroke: red + .2pt)
+ content((rel: (0, 3pt)), box(inset: 1pt, text(3pt, name, fill: red)), angle: -30deg)
+ })
+ })
+ }
+}
+
+#let interface(node1, node2, ..params, io: false) = {
+ import cetz.draw: *
+
+ hide(rect(node1, node2, name: "bounds"))
+ if io {
+ let (node3, node4) = (0, 0)
+ if params.pos().len() == 2 {
+ (node3, node4) = params.pos()
+ } else {
+ (node3, node4) = ("bounds.west", "bounds.east")
+ }
+
+ anchor("in", node3)
+ anchor("out", node4)
+ }
+}
diff --git a/packages/preview/zap/0.2.1/src/components/capacitors.typ b/packages/preview/zap/0.2.1/src/components/capacitors.typ
new file mode 100644
index 0000000000..027a1384fd
--- /dev/null
+++ b/packages/preview/zap/0.2.1/src/components/capacitors.typ
@@ -0,0 +1,28 @@
+#import "/src/component.typ": component, interface
+#import "/src/dependencies.typ": cetz
+#import cetz.draw: anchor, line
+#import "/src/mini.typ": variable-arrow
+
+#let capacitor(name, node, variable: false, ..params) = {
+ assert(type(variable) == bool, message: "variable must be of type bool")
+
+ // Capacitor style
+ let style = (
+ width: .8,
+ distance: .25,
+ )
+
+ // Drawing function
+ let draw(ctx, position, style) = {
+ interface((-style.distance / 2, -style.width / 2), (style.distance / 2, style.width / 2), io: position.len() < 2)
+
+ line((-style.distance / 2, -style.width / 2), (-style.distance / 2, style.width / 2), ..style)
+ line((style.distance / 2, -style.width / 2), (style.distance / 2, style.width / 2), ..style)
+ if (variable) {
+ variable-arrow()
+ }
+ }
+
+ // Componant call
+ component("capacitor", name, node, draw: draw, style: style, ..params)
+}
diff --git a/packages/preview/zap/0.2.1/src/components/diodes.typ b/packages/preview/zap/0.2.1/src/components/diodes.typ
new file mode 100644
index 0000000000..868556a21f
--- /dev/null
+++ b/packages/preview/zap/0.2.1/src/components/diodes.typ
@@ -0,0 +1,34 @@
+#import "/src/component.typ": component, interface
+#import "/src/dependencies.typ": cetz
+#import cetz.draw: anchor, circle, line, polygon, scope, translate
+#import "/src/mini.typ": radiation-arrows
+
+#let diode(name, node, emitting: false, receiving: false, ..params) = {
+ assert(type(emitting) == bool, message: "emitting must be of type bool")
+ assert(type(receiving) == bool, message: "receiving must be of type bool")
+
+ // Diode style
+ let style = (
+ radius: .3,
+ line: .25,
+ )
+
+ // Drawing function
+ let draw(ctx, position, style) = {
+ translate((-style.radius / 4, 0))
+ interface((-style.radius / 2, -style.radius), (style.radius, style.radius), io: position.len() < 2)
+
+ polygon((0, 0), 3, radius: style.radius, fill: white, ..style)
+ line((0deg, style.radius), (180deg, style.radius / 2), ..style.at("wires"))
+ line((style.radius, -style.line), (style.radius, style.line), ..style)
+ if (emitting or receiving) {
+ radiation-arrows((to: (0, 0), rel: (0.25, 0.65)), reversed: receiving)
+ }
+ }
+
+ // Componant call
+ component("diode", name, node, draw: draw, style: style, ..params)
+}
+
+#let led(name, node, ..params) = diode(name, node, emitting: true, ..params)
+#let photodiode(name, node, ..params) = diode(name, node, receiving: true, ..params)
diff --git a/packages/preview/zap/0.2.1/src/components/fuses.typ b/packages/preview/zap/0.2.1/src/components/fuses.typ
new file mode 100644
index 0000000000..9a882cc2a0
--- /dev/null
+++ b/packages/preview/zap/0.2.1/src/components/fuses.typ
@@ -0,0 +1,30 @@
+#import "/src/component.typ": component, interface
+#import "/src/dependencies.typ": cetz
+#import cetz.draw: anchor, circle, floating, line, rect
+
+#let fuse(name, node, asymmetric: false, ..params) = {
+ assert(type(asymmetric) == bool, message: "asymmetric must be of type bool")
+
+ // Fuses style
+ let style = (
+ width: .88,
+ height: .88 / 2.4,
+ asymmetry: 25%,
+ )
+
+ // Drawing function
+ let draw(ctx, position, style) = {
+ interface((-style.width / 2, -style.height / 2), (style.width / 2, style.height / 2), io: position.len() < 2)
+
+ rect((-style.width / 2, -style.height / 2), (style.width / 2, style.height / 2), fill: white, ..style)
+ line((-style.width / 2, 0), (style.width / 2, 0), ..style.at("wires"))
+ if (asymmetric) {
+ rect((-style.width / 2, -style.height / 2), (-style.width / 2 + float(style.asymmetry * style.width), style.height / 2), fill: black)
+ }
+ }
+
+ // Componant call
+ component("fuse", name, node, draw: draw, style: style, ..params)
+}
+
+#let afuse(name, node, ..params) = fuse(name, node, asymmetric: true, ..params)
diff --git a/packages/preview/zap/0.2.1/src/components/grounds.typ b/packages/preview/zap/0.2.1/src/components/grounds.typ
new file mode 100644
index 0000000000..dec6464ed8
--- /dev/null
+++ b/packages/preview/zap/0.2.1/src/components/grounds.typ
@@ -0,0 +1,102 @@
+#import "/src/component.typ": component, interface
+#import "/src/dependencies.typ": cetz
+#import cetz.draw: anchor, line, polygon
+
+#let ground(name, node, ..params) = {
+ assert(params.pos().len() == 0, message: "ground supports only one node")
+
+ // Ground style
+ let style = (
+ radius: 0.22,
+ distance: 0.28,
+ )
+
+ // Drawing function
+ let draw(ctx, position, style) = {
+ line((0, 0), (0, -style.distance), ..style.at("wires"))
+ polygon((0, -style.distance), 3, anchor: "north", radius: style.radius, angle: -90deg, name: "polygon", ..style)
+
+ let (width, height) = cetz.util.measure(ctx, "polygon")
+ interface((-width / 2, -height / 2), (width / 2, height / 2))
+ }
+
+ // Componant call
+ component("ground", name, node, draw: draw, style: style, ..params)
+}
+
+#let frame(name, node, ..params) = {
+ assert(params.pos().len() == 0, message: "earth supports only one node")
+
+ // Earth style
+ let style = (
+ width: 0.46,
+ angle: 20deg,
+ depth: 0.25,
+ distance: 0.28,
+ )
+
+ // Drawing function
+ let draw(ctx, position, style) = {
+ line((0, 0), (0, -style.distance), ..style.at("wires"))
+ let delta = style.width / 2
+ line((-style.width / 2, -style.distance), (style.width / 2, -style.distance), ..style)
+ for i in (0, 1, 2) {
+ line((-style.width / 2 + (1 - i) * .01 + i * delta, -style.distance), (rel: (angle: -style.angle - 90deg, radius: style.depth)), ..style)
+ }
+ interface((-style.width / 2, style.distance), (style.width / 2, -style.distance))
+ }
+
+ // Componant call
+ component("frame", name, node, draw: draw, style: style, ..params)
+}
+
+#let earth(name, node, ..params) = {
+ assert(params.pos().len() == 0, message: "earth supports only one node")
+
+ // Earth style
+ let style = (
+ width: .53,
+ delta: .09,
+ spacing: .11,
+ distance: .28,
+ )
+
+ // Drawing function
+ let draw(ctx, position, style) = {
+ line((0, 0), (0, -style.distance), ..style.at("wires"))
+ for i in (0, 1, 2) {
+ line(
+ (-style.width / 2 + i * style.delta, -style.distance - i * style.spacing),
+ (style.width / 2 - i * style.delta, -style.distance - i * style.spacing),
+ ..style,
+ )
+ }
+ interface((-style.width / 2, -style.distance - style.spacing * 2), (style.width / 2, -style.distance))
+ }
+
+ // Componant call
+ component("earth", name, node, draw: draw, style: style, ..params)
+}
+
+#let vcc(name, node, ..params) = {
+ // VCC style
+ let style = (
+ angle: 35deg,
+ radius: .4,
+ distance: .6,
+ )
+
+ // Drawing function
+ let draw(ctx, position, style) = {
+ line((0, 0), (0, style.distance), ..style.at("wires"))
+ line((rel: (radius: style.radius, angle: -90deg - style.angle), to: (0, style.distance)), (0, style.distance), (
+ rel: (radius: style.radius, angle: -90deg + style.angle),
+ ))
+
+ let (width, height) = (calc.sin(style.angle) * style.radius, calc.cos(style.angle) * style.radius)
+ interface((-width / 2, -height / 2), (width / 2, height / 2))
+ }
+
+ // Componant call
+ component("vcc", name, node, draw: draw, style: style, ..params)
+}
diff --git a/packages/preview/zap/0.2.1/src/components/inductors.typ b/packages/preview/zap/0.2.1/src/components/inductors.typ
new file mode 100644
index 0000000000..3f11e91399
--- /dev/null
+++ b/packages/preview/zap/0.2.1/src/components/inductors.typ
@@ -0,0 +1,35 @@
+#import "/src/component.typ": component, interface
+#import "/src/dependencies.typ": cetz
+#import cetz.draw: anchor, arc, line, rect
+
+#let inductor(name, node, ..params) = {
+ // Inductor style
+ let style = (
+ width: 1.41,
+ height: 1.41 / 3,
+ bumps: 3,
+ )
+
+ // Drawing function
+ let draw(ctx, position, style) = {
+ interface((-style.width / 2, -style.height / 2), (style.width / 2, style.height / 2), io: position.len() < 2)
+
+ let bump-radius = style.width / style.bumps / 2
+ if (style.variant == "iec") {
+ rect((-style.width / 2, -style.height / 2), (style.width / 2, style.height / 2), fill: black, ..style)
+ } else {
+ let sgn = if position.last().at(0) < position.first().at(0) { -1 } else { 1 }
+ let start = (-style.width / 2 - bump-radius, 0)
+ for i in range(style.bumps) {
+ let arc-center-x = (
+ start.at(0) + bump-radius + i * 2 * bump-radius
+ )
+ let arc-center = (arc-center-x, 0)
+ arc(arc-center, radius: bump-radius, start: sgn * 180deg, stop: 0deg, ..style)
+ }
+ }
+ }
+
+ // Componant call
+ component("inductor", name, node, draw: draw, style: style, ..params)
+}
diff --git a/packages/preview/zap/0.2.1/src/components/motors.typ b/packages/preview/zap/0.2.1/src/components/motors.typ
new file mode 100644
index 0000000000..0dcbe8585f
--- /dev/null
+++ b/packages/preview/zap/0.2.1/src/components/motors.typ
@@ -0,0 +1,36 @@
+#import "/src/component.typ": component, interface
+#import "/src/dependencies.typ": cetz
+#import cetz.draw: anchor, arc, circle, content, rect
+#import "/src/mini.typ": ac-sign, dc-sign
+
+#let motor(uid, name, node, current: "dc", magnet: false, ..params) = {
+ assert(current in ("dc", "ac"), message: "current must be ac or dc")
+ assert(type(magnet) == bool, message: "magnet must be bool")
+ assert(not (magnet and current == "ac"), message: "magnet only with dcmotor")
+
+ // DCmotor style
+ let style = (
+ radius: .49,
+ magnet-width: 1.23,
+ magnet-height: 1.23 / 4,
+ )
+
+ // Drawing function
+ let draw(ctx, position, style) = {
+ interface((-style.radius, -style.radius), (style.radius, style.radius), io: position.len() < 2)
+
+ if (magnet) {
+ rect((-style.magnet-width / 2, -style.magnet-height / 2), (style.magnet-width / 2, style.magnet-height / 2), fill: black)
+ }
+ circle((0, 0), radius: style.radius, fill: white, ..style)
+ content((0, 0), anchor: "south", "M", padding: .03)
+ let symbol = if current == "dc" { dc-sign } else { ac-sign }
+ content((0, 0), [#cetz.canvas({ symbol() })], anchor: "north", padding: .13)
+ }
+
+ // Componant call
+ component(uid, name, node, draw: draw, style: style, ..params)
+}
+
+#let dcmotor(name, node, ..params) = motor("dcmotor", name, node, current: "dc", ..params)
+#let acmotor(name, node, ..params) = motor("acmotor", name, node, current: "ac", ..params)
diff --git a/packages/preview/zap/0.2.1/src/components/nodes.typ b/packages/preview/zap/0.2.1/src/components/nodes.typ
new file mode 100644
index 0000000000..bbcf2ea297
--- /dev/null
+++ b/packages/preview/zap/0.2.1/src/components/nodes.typ
@@ -0,0 +1,10 @@
+#import "../dependencies.typ": cetz
+#import cetz.draw: circle, on-layer
+
+#let node(name, position, fill: true, ..params) = {
+ assert(type(name) == str, message: "node name must be a string")
+
+ on-layer(1, {
+ circle(position, radius: .05, fill: if fill { black } else { white }, name: name, stroke: .4pt, ..params)
+ })
+}
\ No newline at end of file
diff --git a/packages/preview/zap/0.2.1/src/components/opamp.typ b/packages/preview/zap/0.2.1/src/components/opamp.typ
new file mode 100644
index 0000000000..183564c395
--- /dev/null
+++ b/packages/preview/zap/0.2.1/src/components/opamp.typ
@@ -0,0 +1,47 @@
+#import "/src/component.typ": component, interface
+#import "/src/dependencies.typ": cetz
+#import cetz.draw: anchor, content, line, polygon, rect, scope, translate
+
+#let opamp(name, node, invert: false, label: none, ..params) = {
+ assert(params.pos().len() == 0, message: "opamp supports only one node")
+
+ // Capacitor style
+ let style = (
+ width: 1.8,
+ height: 1.75,
+ padding: .28,
+ sign-stroke: .55pt,
+ sign-size: .14,
+ sign-delta: .45,
+ )
+
+
+ // Drawing function
+ let draw(ctx, position, style) = {
+ interface((-style.width / 2, -style.height / 2), (style.width / 2, style.height / 2), io: true)
+
+ let sgn = if invert { -1 } else { 1 }
+ anchor("minus", (-style.width / 2, sgn * style.sign-delta))
+ anchor("plus", (-style.width / 2, -sgn * style.sign-delta))
+
+ if style.variant == "iec" {
+ rect((-style.width / 2, -style.height / 2), (style.width / 2, style.height / 2), fill: white, ..style)
+ } else {
+ scope({
+ if style.variant == "ieee" { translate((-style.width / 6, 0)) }
+ polygon((0, 0), 3, radius: style.width * 2 / 3, ..style)
+ })
+ }
+
+ line((rel: (style.padding - style.sign-size, 0), to: "minus"), (rel: (2 * style.sign-size, 0)), stroke: style.sign-stroke)
+ line((rel: (style.padding - style.sign-size, 0), to: "plus"), (rel: (2 * style.sign-size, 0)), stroke: style.sign-stroke)
+ line((rel: (style.padding, -style.sign-size), to: "plus"), (rel: (0, 2 * style.sign-size)), stroke: style.sign-stroke)
+
+ if label != none {
+ content((style.width / 2, 0), label, anchor: "east", padding: if style.variant == "ieee" { .45 } else { .15 })
+ }
+ }
+
+ // Componant call
+ component("opamp", name, node, draw: draw, style: style, ..params, label: none)
+}
diff --git a/packages/preview/zap/0.2.1/src/components/resistors.typ b/packages/preview/zap/0.2.1/src/components/resistors.typ
new file mode 100644
index 0000000000..a9537e7d3b
--- /dev/null
+++ b/packages/preview/zap/0.2.1/src/components/resistors.typ
@@ -0,0 +1,61 @@
+#import "/src/component.typ": component, interface
+#import "/src/dependencies.typ": cetz
+#import cetz.draw: anchor, line, rect
+#import "/src/mini.typ": variable-arrow
+
+#let resistor(name, node, variable: false, adjustable: false, ..params) = {
+ assert(type(variable) == bool, message: "variable must be of type bool")
+ assert(type(adjustable) == bool, message: "adjustable must be of type bool")
+
+ // Resistor style
+ let style = (
+ width: 1.41,
+ height: .47,
+ zigs: 3,
+ )
+
+ // Drawing function
+ let draw(ctx, position, style) = {
+ interface((-style.width / 2, -style.height / 2), (style.width / 2, style.height / 2), io: position.len() < 2)
+
+ if style.variant == "iec" {
+ rect(
+ (-style.width / 2, -style.height / 2),
+ (
+ style.width / 2,
+ style.height / 2,
+ ),
+ fill: white,
+ ..style,
+ )
+ } else {
+ let step = style.width / (style.zigs * 2)
+ let sign = -1
+ let x = style.width / 2
+ line(
+ (-x, 0),
+ (rel: (step / 2, style.height / 2)),
+ ..for _ in range(style.zigs * 2 - 1) {
+ ((rel: (step, style.height * sign)),)
+ sign *= -1
+ },
+ (x, 0),
+ ..style,
+ fill: none,
+ )
+ }
+ if variable {
+ variable-arrow()
+ } else if adjustable {
+ let arrow-length = .8
+ anchor("a", (0, style.height / 2 + arrow-length))
+ line("a", (0, style.height / 2), mark: (end: ">", fill: black), fill: none)
+ }
+ }
+
+ // Componant call
+ component("resistor", name, node, draw: draw, style: style, ..params)
+}
+
+#let rheostat(name, node, ..params) = resistor(name, node, variable: true, ..params)
+#let potentiometer(name, node, ..params) = resistor(name, node, adjustable: true, ..params)
diff --git a/packages/preview/zap/0.2.1/src/components/sources.typ b/packages/preview/zap/0.2.1/src/components/sources.typ
new file mode 100644
index 0000000000..1859e0ce81
--- /dev/null
+++ b/packages/preview/zap/0.2.1/src/components/sources.typ
@@ -0,0 +1,70 @@
+#import "/src/component.typ": component, interface
+#import "/src/dependencies.typ": cetz
+#import cetz.draw: anchor, circle, line, mark, rect
+
+#let isource(name, node, current: "dc", ..params) = {
+ assert(current in ("dc", "ac"), message: "current must be ac or dc")
+
+ // Isource style
+ let style = (
+ radius: .53,
+ padding: .25,
+ arrow-scale: 3,
+ )
+
+ // Drawing function
+ let draw(ctx, position, style) = {
+ interface((-style.radius, -style.radius), (style.radius, style.radius), io: position.len() < 2)
+
+ circle((0, 0), radius: style.radius, fill: white, ..style)
+ if (style.variant == "iec") {
+ line((0, -style.radius), (rel: (0, 2 * style.radius)), ..style, fill: none)
+ } else {
+ line((-style.radius + style.padding, 0), (rel: (2 * style.radius - 1.85 * style.padding, 0)), mark: (end: ">"), fill: black)
+ }
+ }
+
+ // Componant call
+ component("isource", name, node, draw: draw, style: style, ..params)
+}
+
+#let acisource(name, node, ..params) = isource(name, node, current: "ac", ..params)
+
+#let vsource(name, node, current: "dc", ..params) = {
+ assert(current in ("dc", "ac"), message: "current must be ac or dc")
+
+ // Vsource style
+ let style = (
+ radius: .53,
+ padding: .25,
+ sign-stroke: .55pt,
+ sign-size: .14,
+ sign-delta: .07,
+ )
+
+ // Drawing function
+ let draw(ctx, position, style) = {
+ interface((-style.radius, -style.radius), (style.radius, style.radius), io: position.len() < 2)
+
+ circle((0, 0), radius: style.radius, fill: white, ..style)
+ if (style.variant == "iec") {
+ line((-style.radius, 0), (rel: (2 * style.radius, 0)), ..style)
+ } else {
+ line((rel: (-style.radius + style.padding, -style.sign-size)), (rel: (0, 2 * style.sign-size)), stroke: style.sign-stroke)
+ line(
+ (
+ style.radius - style.padding - style.sign-delta,
+ -style.sign-size,
+ ),
+ (rel: (0, 2 * style.sign-size)),
+ stroke: style.sign-stroke,
+ )
+ line((rel: (style.sign-size, -style.sign-size)), (rel: (-2 * style.sign-size, 0)), stroke: style.sign-stroke)
+ }
+ }
+
+ // Componant call
+ component("vsource", name, node, draw: draw, style: style, ..params)
+}
+
+#let acvsource(name, node, ..params) = vsource(name, node, current: "ac", ..params)
diff --git a/packages/preview/zap/0.2.1/src/components/transistors/bjts.typ b/packages/preview/zap/0.2.1/src/components/transistors/bjts.typ
new file mode 100644
index 0000000000..ee1198368c
--- /dev/null
+++ b/packages/preview/zap/0.2.1/src/components/transistors/bjts.typ
@@ -0,0 +1,48 @@
+#import "/src/component.typ": component, interface
+#import "/src/dependencies.typ": cetz
+#import cetz.draw: anchor, circle, hide, line, mark, translate
+#import "/src/mini.typ": center-mark
+
+#let bjt(name, node, polarisation: "npn", envelope: false, ..params) = {
+ assert(polarisation in ("npn", "pnp"), message: "polarisation must `npn` or `pnp`")
+ assert(type(envelope) == bool, message: "envelope must be of type bool")
+ assert(params.pos().len() == 0, message: "ground supports only one node")
+
+ // BJT style
+ let style = (
+ radius: .65,
+ base-height: .6,
+ base-distance: .12,
+ aperture: 50deg,
+ )
+
+ // Drawing function
+ let draw(ctx, position, style) = {
+ interface((-style.radius, -style.radius), (style.radius, style.radius))
+
+ translate((-calc.cos(style.aperture) * style.radius, 0))
+
+ let sgn = if polarisation == "npn" { -1 } else { 1 }
+ anchor("base", ((-style.radius, 0), 30%, (style.radius, 0)))
+ anchor("e", (-style.aperture * sgn, style.radius))
+ anchor("c", (style.aperture * sgn, style.radius))
+ anchor("b", if envelope { (-style.radius, 0) } else { "base" })
+
+ if envelope {
+ circle((0, 0), radius: style.radius, ..style, name: "circle")
+ line("base", (-style.radius, 0), ..style.at("wires"))
+ } else {
+ hide(circle((0, 0), radius: style.radius, ..style, name: "circle"))
+ }
+
+ line((to: "base", rel: (0, -style.base-height / 2)), (to: "base", rel: (0, style.base-height / 2)), ..style)
+ line((to: "base", rel: (0, -style.base-distance * sgn)), "e", ..style.at("wires"), mark: center-mark(symbol: if sgn == -1 { "<" } else { ">" }))
+ line((to: "base", rel: (0, style.base-distance * sgn)), "c", ..style.at("wires"))
+ }
+
+ // Componant call
+ component("bjt", name, node, draw: draw, style: style, ..params, label: none)
+}
+
+#let pnp(name, node, ..params) = bjt(name, node, polarisation: "pnp", ..params)
+#let npn(name, node, ..params) = bjt(name, node, polarisation: "npn", ..params)
diff --git a/packages/preview/zap/0.2.1/src/components/transistors/mosfets.typ b/packages/preview/zap/0.2.1/src/components/transistors/mosfets.typ
new file mode 100644
index 0000000000..94f9afb3f9
--- /dev/null
+++ b/packages/preview/zap/0.2.1/src/components/transistors/mosfets.typ
@@ -0,0 +1,94 @@
+#import "/src/component.typ": component, interface
+#import "/src/dependencies.typ": cetz
+#import cetz.draw: anchor, circle, content, floating, hide, line, mark, scale, set-origin, translate
+
+#let mosfet(
+ name,
+ node,
+ channel: "n",
+ envelope: false,
+ mode: "enhancement",
+ bulk: "internal",
+ ..params,
+) = {
+ assert(type(envelope) == bool, message: "envelope must be of type bool")
+ assert(mode in ("enhancement", "depletion"), message: "mode must be `enhancement` or `depletion`")
+ assert(channel in ("p", "n"), message: "channel must be `p` or `n`")
+ assert(bulk in ("internal", "external", none), message: "substrate must be `internal`, `external` or none")
+
+ // Mosfet style
+ let style = (
+ height: 0.795,
+ width: 1.065,
+ base-width: 1.35,
+ base-spacing: 0.165,
+ base-distance: 0.165,
+ radius: 1.05,
+ )
+
+ // Drawing function
+ let draw(ctx, position, style) = {
+ let (height, width, base-width, base-spacing, radius) = style
+ interface((-height, -width / 2), (0, width / 2))
+
+ let center = (-height / 2, 0)
+
+ anchor("d", (0, width / 2))
+ anchor("s", (0, -width / 2))
+ if bulk == "external" {
+ anchor("bulk", (0, 0))
+ }
+
+ if mode == "enhancement" {
+ let bar-length = (base-width - 2 * base-spacing) / 3
+ for i in range(3) {
+ line((-height, -base-width / 2 + i * (bar-length + base-spacing)), (rel: (0, bar-length)), ..style)
+ }
+ } else {
+ line((-height, -base-width / 2), (rel: (0, base-width)), ..style)
+ }
+ if bulk == "internal" {
+ line((0, 0), (0, -width / 2), ..style.at("wires"))
+ }
+ line("d", (rel: (0, 0)), (rel: (-height, 0)), ..style.at("wires"))
+ line("s", (rel: (0, 0)), (rel: (-height, 0)), ..style.at("wires"))
+
+ if envelope {
+ circle(center, radius: radius, ..style, name: "c")
+ } else {
+ hide(circle((0, 0), radius: radius, ..style, name: "c"))
+ }
+
+ anchor("gl", (rel: (-3 * height / 4, width / 2), to: center))
+
+ if bulk != none {
+ line((-height, 0), (rel: (height, 0)), name: "line", ..style.at("wires"))
+ mark("line.centroid", (-height, 0), symbol: if (channel == "n") { ">" } else { "<" }, fill: black, anchor: "center")
+ line("gl", (rel: (0, -width)), (rel: (-height / 4, 0)), ..style.at("wires"))
+ anchor("g", ())
+ } else {
+ line("gl", (rel: (0, -width / 2)), (rel: (0, -width / 2)), ..style.at("wires"))
+ line((rel: (0, width / 2)), (rel: (-height / 2, 0)), ..style.at("wires"))
+ anchor("g", ())
+
+ mark(
+ (
+ -height / 2,
+ if (channel == "n") { -width / 2 } else { width / 2 },
+ ),
+ (rel: (height, 0)),
+ symbol: if (channel == "n") { ">" } else { "<" },
+ fill: black,
+ anchor: "center",
+ )
+ }
+ }
+
+ // Componant call
+ component("mosfet", name, node, draw: draw, style: style, ..params)
+}
+
+#let pmos(name, node, ..params) = mosfet(name, node, channel: "p", ..params)
+#let nmos(name, node, ..params) = mosfet(name, node, channel: "n", ..params)
+#let pmosd(name, node, ..params) = mosfet(name, node, channel: "p", mode: "depletion", ..params)
+#let nmosd(name, node, ..params) = mosfet(name, node, channel: "n", mode: "depletion", ..params)
diff --git a/packages/preview/zap/0.2.1/src/components/wires.typ b/packages/preview/zap/0.2.1/src/components/wires.typ
new file mode 100644
index 0000000000..a3a7a47e0a
--- /dev/null
+++ b/packages/preview/zap/0.2.1/src/components/wires.typ
@@ -0,0 +1,11 @@
+#import "../dependencies.typ": cetz
+#import cetz.draw: line
+
+#let wire(multi: 1, ..params) = {
+ assert(type(multi) == int, message: "multi must be an int")
+
+ line(stroke: 0.4pt, ..params)
+ /*if multi > 10 {
+ line(, stroke: 0.6pt)
+ }*/
+}
diff --git a/packages/preview/zap/0.2.1/src/decorations.typ b/packages/preview/zap/0.2.1/src/decorations.typ
new file mode 100644
index 0000000000..e10dabee53
--- /dev/null
+++ b/packages/preview/zap/0.2.1/src/decorations.typ
@@ -0,0 +1,77 @@
+#import "/src/dependencies.typ": cetz
+#import cetz.draw: bezier-through, catmull, circle, content, hobby, line, mark
+
+#let get-label(label) = {
+ let p-label = if type(label) == dictionary and "label" in label {
+ label.label
+ } else {
+ label
+ }
+ let p-invert = label.at("invert", default: false)
+ let p-position = if type(label) == dictionary and "position" in label and type(label.position) == alignment {
+ label.position
+ } else {
+ end + top
+ }
+ (p-label, p-position, p-invert, label.at("distance", default: 50%))
+}
+
+#let current(ctx, label) = {
+ let (p-label, p-position, p-invert, p-distance) = get-label(label)
+
+ let (width, height) = cetz.util.measure(ctx, p-label)
+ let side = if p-position.y == top { (1, ">", "<") } else { (-1, ">", "<") }
+
+ if p-position.x == start {
+ mark(("in", p-distance, "component.west"), "in", symbol: if p-invert { "<" } else { ">" }, anchor: "center", fill: black, scale: 0.8)
+ content((rel: (0, (.2 + height) * side.first()), to: ("in", p-distance, "component.west")), p-label)
+ } else {
+ mark(("component.east", p-distance, "out"), "out", symbol: if p-invert { "<" } else { ">" }, anchor: "center", fill: black, scale: 0.8)
+ content((rel: (0, (.2 + height) * side.first()), to: ("component.east", p-distance, "out")), p-label)
+ }
+}
+
+#let flow(ctx, label) = {
+ let (p-label, p-position, p-invert, p-distance) = get-label(label)
+
+ let (width, height) = cetz.util.measure(ctx, p-label)
+
+ let bottom = p-position.y == bottom
+
+ let (a-start, a-end) = if p-position.x == start {
+ let first = ("component.west", p-distance, "in")
+ (first, (rel: (-.7, 0), to: first))
+ } else {
+ let first = ("component.east", p-distance, "out")
+ (first, (rel: (.7, 0), to: first))
+ }
+
+ line(
+ (rel: (0, if bottom { -.2 } else { .2 }), to: if p-invert { a-end } else { a-start }),
+ (rel: (0, if bottom { -.2 } else { .2 }), to: if p-invert { a-start } else { a-end }),
+ mark: (end: ">"),
+ fill: black,
+ stroke: 0.55pt,
+ scale: 0.8,
+ )
+ content((rel: (0, height * if bottom { -2 } else { 2 }), to: (a-start, 50%, a-end)), p-label)
+}
+
+#let voltage(ctx, label, p-rotate) = {
+ let (p-label, p-position, ..params) = get-label(label)
+
+ let (width, height) = cetz.util.measure(ctx, p-label)
+ let side = if p-position.y == top { (1, "north") } else { (-1, "south") }
+
+ let a-start = (rel: (-.4, .1 * side.first()), to: "component." + side.last() + "-west")
+ let a-end = (rel: (.4, .1 * side.first()), to: "component." + side.last() + "-east")
+ let a-center = (rel: (0, .3 * side.first()), to: "component." + side.last())
+ let a-label = (width / 2 * calc.abs(calc.sin(p-rotate)) + height / 2 * calc.abs(calc.cos(p-rotate)))
+
+ content((rel: (0, a-label), to: (rel: (0, 5pt * side.first()), to: a-center)), p-label)
+ if p-position.x == start {
+ hobby(a-end, a-center, a-start, mark: (end: ">", fill: black), scale: 0.8, stroke: 0.55pt)
+ } else {
+ hobby(a-start, a-center, a-end, mark: (end: ">", fill: black), scale: 0.8, stroke: 0.55pt)
+ }
+}
diff --git a/packages/preview/zap/0.2.1/src/dependencies.typ b/packages/preview/zap/0.2.1/src/dependencies.typ
new file mode 100644
index 0000000000..e5b088c07e
--- /dev/null
+++ b/packages/preview/zap/0.2.1/src/dependencies.typ
@@ -0,0 +1 @@
+#import "@preview/cetz:0.4.0"
diff --git a/packages/preview/zap/0.2.1/src/lib.typ b/packages/preview/zap/0.2.1/src/lib.typ
new file mode 100644
index 0000000000..51281da5fc
--- /dev/null
+++ b/packages/preview/zap/0.2.1/src/lib.typ
@@ -0,0 +1,25 @@
+// Export dependencies
+#import "dependencies.typ": cetz
+#import cetz: canvas
+#import cetz.draw as draw
+#import cetz.draw: set-style
+
+// Export components
+#import "component.typ": component, interface
+
+// Export components
+#import "components/wires.typ": wire
+#import "components/nodes.typ": node
+#import "components/capacitors.typ": capacitor
+#import "components/diodes.typ": diode, led, photodiode
+#import "components/opamp.typ": opamp
+#import "components/fuses.typ": afuse, fuse
+#import "components/grounds.typ": earth, frame, ground, vcc
+#import "components/inductors.typ": inductor
+#import "components/resistors.typ": potentiometer, resistor, rheostat
+#import "components/sources.typ": isource, vsource
+#import "components/motors.typ": acmotor, dcmotor
+
+// Export transistors
+#import "components/transistors/bjts.typ": bjt, npn, pnp
+#import "components/transistors/mosfets.typ": mosfet, nmos, nmosd, pmos, pmosd
diff --git a/packages/preview/zap/0.2.1/src/mini.typ b/packages/preview/zap/0.2.1/src/mini.typ
new file mode 100644
index 0000000000..a05cc9b160
--- /dev/null
+++ b/packages/preview/zap/0.2.1/src/mini.typ
@@ -0,0 +1,82 @@
+#import "dependencies.typ": cetz
+#import cetz.draw: anchor, hobby, line, rotate, scope, set-origin, set-style
+
+#let center-mark(symbol: ">") = {
+ (end: ((pos: 50%, symbol: symbol, fill: black, anchor: "center"), (pos: 0%, symbol: ">", scale: 0)))
+}
+
+#let variable-arrow() = {
+ scope({
+ let arrow-length = 40pt
+ let arrow-angle = 55deg
+ let arrow-origin = (
+ -0.5 * calc.cos(arrow-angle) * arrow-length,
+ -0.5 * calc.sin(arrow-angle) * arrow-length,
+ )
+ anchor("adjust", arrow-origin)
+
+ set-origin(arrow-origin)
+ rotate(arrow-angle)
+ line((0, 0), (arrow-length, 0), mark: (end: ">", fill: black))
+ })
+}
+
+#let radiation-arrows(origin, angle: -120deg, reversed: false, length: 12pt) = {
+ scope({
+ let arrows-distance = 3pt
+ let arrows-length = length
+ let arrows-scale = 0.8
+
+ set-origin(origin)
+ set-style(stroke: 0.55pt)
+ rotate(angle)
+ if (reversed) {
+ line((arrows-length, -arrows-distance), (0, -arrows-distance), mark: (
+ start: ">",
+ scale: arrows-scale,
+ fill: black,
+ ))
+ line((arrows-length, arrows-distance), (0, arrows-distance), mark: (
+ start: ">",
+ scale: arrows-scale,
+ fill: black,
+ ))
+ } else {
+ line((arrows-length, -arrows-distance), (0, -arrows-distance), mark: (
+ end: ">",
+ scale: arrows-scale,
+ fill: black,
+ ))
+ line((arrows-length, arrows-distance), (0, arrows-distance), mark: (
+ end: ">",
+ scale: arrows-scale,
+ fill: black,
+ ))
+ }
+ })
+}
+
+#let dc-sign() = {
+ let width = 10pt
+ let spacing = 1.5pt
+ let vspace = 3pt
+ let symbol-stroke = 0.55pt
+ let tick-width = (width - 2 * spacing) / 3
+
+ set-style(stroke: symbol-stroke)
+
+ line((-width / 2, 0), (width / 2, 0))
+ line((-width / 2, -vspace), (-width / 2 + tick-width, -vspace))
+ line((-tick-width / 2, -vspace), (tick-width / 2, -vspace))
+ line((width / 2, -vspace), (width / 2 - tick-width, -vspace))
+}
+
+#let ac-sign() = {
+ let width = 10pt
+ let height = 4pt
+ let symbol-stroke = 0.55pt
+
+ set-style(stroke: symbol-stroke)
+
+ hobby((-width / 2, 0), (-width / 4, height / 2), (width / 4, -height / 2), (width / 2, 0))
+}
diff --git a/packages/preview/zap/0.2.1/src/styles.typ b/packages/preview/zap/0.2.1/src/styles.typ
new file mode 100644
index 0000000000..3d870a00b1
--- /dev/null
+++ b/packages/preview/zap/0.2.1/src/styles.typ
@@ -0,0 +1,5 @@
+#let default-style = (
+ variant: "iec",
+ wires: (stroke: 0.4pt),
+ stroke: .8pt,
+)
diff --git a/packages/preview/zap/0.2.1/src/utils.typ b/packages/preview/zap/0.2.1/src/utils.typ
new file mode 100644
index 0000000000..8682cc874f
--- /dev/null
+++ b/packages/preview/zap/0.2.1/src/utils.typ
@@ -0,0 +1,28 @@
+#let get-label-anchor(angle-deg, ) = {
+ let angle = angle-deg.deg()
+ let normalized-angle = calc.rem(if angle < 0 { angle + 360 } else { angle }, 360)
+
+ let tolerance = 15
+
+ //panic(repr(normalized-angle))
+
+ if calc.abs(normalized-angle) < tolerance {
+ return ("south", "north")
+ } else if calc.abs(normalized-angle - 90) < tolerance {
+ return ("east", "west")
+ } else if calc.abs(normalized-angle - 180) < tolerance {
+ return ("north", "south")
+ } else if calc.abs(normalized-angle - 270) < tolerance {
+ return ("west", "east")
+ } else {
+ if normalized-angle > 0 and normalized-angle < 90 {
+ return ("south-east", "north-west")
+ } else if normalized-angle > 90 and normalized-angle < 180 {
+ return ("north-east", "south-west")
+ } else if normalized-angle > 180 and normalized-angle < 270 {
+ return ("north-west", "south-east")
+ } else {
+ return ("south-west", "north-east")
+ }
+ }
+}
diff --git a/packages/preview/zap/0.2.1/typst.toml b/packages/preview/zap/0.2.1/typst.toml
new file mode 100644
index 0000000000..013746a4fb
--- /dev/null
+++ b/packages/preview/zap/0.2.1/typst.toml
@@ -0,0 +1,14 @@
+[package]
+name = "zap"
+version = "0.2.1"
+compiler = "0.13.0"
+entrypoint = "src/lib.typ"
+authors = ["Louis Grange "]
+license = "LGPL-3.0-or-later"
+homepage = "https://l0uisgrange.github.io/zap/"
+description = "A package to draw amazing electronic circuits using CeTZ superpowers"
+repository = "https://github.com/l0uisgrange/zap"
+categories = ["visualization", "components"]
+disciplines = ["engineering"]
+keywords = ["zap", "electronic", "circuit", "cetz", "voltage", "draw", "schema", "engineering"]
+exclude = [".github", "docs", "examples"]
\ No newline at end of file