driftlet 0.1.1 → 0.2.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.
- package/README.md +8 -2
- package/docs/alignment.md +146 -0
- package/docs/conventions.md +194 -0
- package/docs/data.md +120 -0
- package/docs/device.md +529 -0
- package/docs/kit.md +299 -0
- package/docs/logo-dark.svg +7 -0
- package/docs/logo.svg +7 -0
- package/docs/numerics.md +629 -0
- package/docs/statistics.md +226 -0
- package/docs/visualization.md +259 -0
- package/llms.txt +62 -9
- package/package.json +2 -1
- package/src/constants.js +11 -0
- package/src/index.js +5 -2
- package/src/kit.js +1 -0
- package/src/solver.js +66 -7
- package/src/sources.js +103 -0
- package/src/types.js +3 -2
- package/types/constants.d.ts +10 -0
- package/types/index.d.ts +11 -2
- package/types/kit.d.ts +1 -0
- package/types/solver.d.ts +6 -0
- package/types/sources.d.ts +102 -0
- package/types/types.d.ts +4 -2
package/docs/kit.md
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
# The kit
|
|
2
|
+
|
|
3
|
+
`driftlet/kit` is the porcelain: helpers that write the plain device definition for you, so a
|
|
4
|
+
device can be set up the way it's drawn and from familiar quantities. It never makes a physical
|
|
5
|
+
choice. Alignments, offsets, rate constants and every other physical input stay yours to give,
|
|
6
|
+
and what the kit produces is an ordinary definition you can print, edit and pass to
|
|
7
|
+
`new Device` (see the [device reference](device.md)).
|
|
8
|
+
|
|
9
|
+
Its data library, with sources, is described in [data](data.md).
|
|
10
|
+
|
|
11
|
+
## A device as a stack
|
|
12
|
+
|
|
13
|
+
`build()` takes the device left to right: a contact, then layers and the faces between them,
|
|
14
|
+
then a contact.
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
import { Device, units } from 'driftlet';
|
|
18
|
+
import { build, layer, ohmic } from 'driftlet/kit';
|
|
19
|
+
|
|
20
|
+
const Si = {
|
|
21
|
+
species: [
|
|
22
|
+
{ name: 'e-', z: -1 },
|
|
23
|
+
{ name: 'h+', z: 1 },
|
|
24
|
+
],
|
|
25
|
+
materials: {
|
|
26
|
+
Si: {
|
|
27
|
+
epsr: 11.7,
|
|
28
|
+
species: {
|
|
29
|
+
'e-': { D: 36e-4, mu0: 0, cRef: units.perCm3(2.8e19) },
|
|
30
|
+
'h+': { D: 12e-4, mu0: units.eV(1.12), cRef: units.perCm3(1.04e19) },
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const def = build({
|
|
37
|
+
library: [Si],
|
|
38
|
+
stack: [
|
|
39
|
+
ohmic(0),
|
|
40
|
+
layer('Si', units.um(1), { donors: units.perCm3(1e17) }),
|
|
41
|
+
layer('Si', units.um(1), { acceptors: units.perCm3(1e16) }),
|
|
42
|
+
ohmic(0.5),
|
|
43
|
+
],
|
|
44
|
+
bulkReactions: [{ equation: 'e- + h+ = 0', kf: { Si: 1e8 } }],
|
|
45
|
+
grid: { hmin: 1e-9, hmax: 20e-9 },
|
|
46
|
+
});
|
|
47
|
+
console.log(JSON.stringify(def.regions)); // plain data: fixedCharge, in C/m³
|
|
48
|
+
const sol = new Device(def).solve();
|
|
49
|
+
console.log(`I = ${sol.current.toFixed(1)} A/m²`);
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
- **Layers** are regions: anything with a `material`. `layer(material, length, more)` writes
|
|
53
|
+
one. Doping can be given as `donors` and `acceptors` (mol/m³) in place of `fixedCharge`.
|
|
54
|
+
- **Faces** are anything else between layers: an interface's fields, exactly as in
|
|
55
|
+
`interfaces[f]`. Two adjacent layers with no face between them get the default face, which
|
|
56
|
+
between different materials is an error asking for the alignment, as always.
|
|
57
|
+
- **Contacts** are the first and last items. `ohmic(drive, carriers)` holds each carrier in
|
|
58
|
+
equilibrium with a metal at the terminal voltage (default `['e-', 'h+']`, an
|
|
59
|
+
infinite-recombination contact; `['e-']` alone is selective, and is the current collector on
|
|
60
|
+
a metal region). `bath(c, reference, drive, { offset })` is a bath (`offset` places the reference species
|
|
61
|
+
relative to the terminal voltage, e.g. to read a solution's potentials against SHE). A drive is a voltage, a waveform, or
|
|
62
|
+
`{ V }`, `{ I }`, `{ V, R }`.
|
|
63
|
+
- **`library`** takes pieces with `species` and `materials`, merged with any given directly
|
|
64
|
+
(`combine()` does the merging on its own). A species or material given twice must be given
|
|
65
|
+
identically.
|
|
66
|
+
- Nested lists in the stack are flattened, so a helper can return several items.
|
|
67
|
+
- Reactions pass through as written (see below).
|
|
68
|
+
|
|
69
|
+
## Reactions and half-reactions
|
|
70
|
+
|
|
71
|
+
Reactions are written as equations, `'Ag+ + e- = Ag(s)'`, in the definition itself (see the
|
|
72
|
+
[device reference](device.md#interfaces)), so `build()`, `new Device` and `set()` all take them.
|
|
73
|
+
A coefficient is an integer and a space (`2 e-`); names may start with digits (`3He`).
|
|
74
|
+
|
|
75
|
+
`half(equation, fixed)` writes a half-reaction, with electrons `e-` on one side, as plain
|
|
76
|
+
`{ equation, fixed }`: spread it into a face reaction with its kinetics. `level(sol, half)` is the
|
|
77
|
+
electronic level it implies at each node, as an electron voltage: where an electrode exchanging
|
|
78
|
+
electrons by that couple would sit in equilibrium with the local composition: the couple's redox
|
|
79
|
+
level (`NaN` where a participant is absent; see [reading level diagrams](visualization.md)). With `{ standard: true }` every species
|
|
80
|
+
is at its reference concentration instead. `SHE` is 2 H⁺ + 2 e⁻ ⇌ H₂ with
|
|
81
|
+
$`\mu_{\mathrm{H_2}} = 0`$, so its standard level is $`\phi + \mu^\circ_{\mathrm{H}^+}/F`$.
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
import { Device } from 'driftlet';
|
|
85
|
+
import { build, layer, ohmic, half, level } from 'driftlet/kit';
|
|
86
|
+
|
|
87
|
+
const silver = half('Ag+ + e- = Ag(s)', { 'Ag(s)': 0 });
|
|
88
|
+
const stern = { phi: { type: 'capacitive', C: 0.2 }, zeroCharge: 0.1 }; // an assumed pzc
|
|
89
|
+
const def = build({
|
|
90
|
+
species: [
|
|
91
|
+
{ name: 'Ag+', z: 1, cRef: 1000 },
|
|
92
|
+
{ name: 'NO3-', z: -1, cRef: 1000 },
|
|
93
|
+
{ name: 'e-', z: -1 },
|
|
94
|
+
],
|
|
95
|
+
materials: {
|
|
96
|
+
water: { epsr: 78.5, species: { 'Ag+': { D: 1.65e-9, mu0: 77.1e3 }, 'NO3-': { D: 1.9e-9, mu0: -111.3e3 } } },
|
|
97
|
+
Ag: { conductor: { species: 'e-', conductivity: 6.3e7 } },
|
|
98
|
+
},
|
|
99
|
+
stack: [
|
|
100
|
+
ohmic(0, ['e-']),
|
|
101
|
+
layer('Ag', 1e-6),
|
|
102
|
+
{ ...stern, reactions: [{ ...silver, k0: 1e-3, alpha: 0.5 }] },
|
|
103
|
+
layer('water', 10e-6, { c0: { 'Ag+': 10, 'NO3-': 10 } }),
|
|
104
|
+
{ ...stern, reactions: [{ ...silver, k0: 1e-3, alpha: 0.5 }] },
|
|
105
|
+
layer('Ag', 1e-6),
|
|
106
|
+
ohmic(0.05, ['e-']),
|
|
107
|
+
],
|
|
108
|
+
grid: { hmin: 0.1e-9, hmax: 100e-9, ratio: 1.15 },
|
|
109
|
+
});
|
|
110
|
+
console.log(JSON.stringify(def.interfaces[0].reactions[0])); // { left: { e-: -1, Ag(s): 1 }, right: { Ag+: -1 }, … }
|
|
111
|
+
const sol = new Device(def).solve();
|
|
112
|
+
const redox = level(sol, silver); // V_Ag⁺(x) in the solution
|
|
113
|
+
console.log(`redox level at mid-cell: ${redox[sol.x.length >> 1].toFixed(4)} V`);
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Sources
|
|
117
|
+
|
|
118
|
+
Helpers that write sources as plain data:
|
|
119
|
+
|
|
120
|
+
- **Waveforms** for a terminal's `V` or `I`: `pulse({ start, width, amplitude, rise, base })`,
|
|
121
|
+
`square({ high, low, period, duty })`, `triangle({ from, to, period })` (a voltammogram's sweep)
|
|
122
|
+
and `ramp({ from, to, duration, start })`. Each returns `{ t, values }` (with `repeat` for the
|
|
123
|
+
periodic ones), times from the device's time 0. A pulse keeps its area,
|
|
124
|
+
`amplitude · width`, whatever its `rise`; with no rise its edges are jumps, which the time
|
|
125
|
+
stepping handles exactly, so it must start after t = 0 (where a device starts, and `solve()`
|
|
126
|
+
reads it).
|
|
127
|
+
- **`injector({ name, region, from, to, species, I, headroom })`**: a port that injects a species
|
|
128
|
+
into a window at a driven current (a number or a waveform), spread evenly. Its conductance is
|
|
129
|
+
set small enough that at the peak current the port sits `headroom` volts (default 2.5) above
|
|
130
|
+
the window, so every node takes the same share, like a current source.
|
|
131
|
+
- **`recombination({ material, tau, majority })`**: band-to-band recombination e⁻ + h⁺ ⇌ 0 as
|
|
132
|
+
a bulk reaction, from the low-injection minority lifetime it gives at that majority density,
|
|
133
|
+
$`k_f = 1/(\tau \cdot n_{\mathrm{maj}})`$.
|
|
134
|
+
|
|
135
|
+
Read a transient at points inside the device with `advance(t, { probes })` (see the
|
|
136
|
+
[device reference](device.md#using-a-device)). And `units` converts back out of SI for display:
|
|
137
|
+
`toPerCm3`, `toMolar`, `toCm2PerS`, `toUm`, `toNm`, `toEV`.
|
|
138
|
+
|
|
139
|
+
```js
|
|
140
|
+
import { Device, FARADAY, units } from 'driftlet';
|
|
141
|
+
import { build, layer, ohmic, semiconductor, pulse, injector, recombination } from 'driftlet/kit';
|
|
142
|
+
|
|
143
|
+
// Holes injected for 0.5 µs into n-Ge under 10 V/cm (Haynes–Shockley), read 1 mm downstream.
|
|
144
|
+
const ND = units.perCm3(1e15), I = (0.01 * ND * FARADAY * 50e-6) / 0.5e-6; // 1% of n₀ over 50 µm
|
|
145
|
+
const dev = new Device(
|
|
146
|
+
build({
|
|
147
|
+
T: 300,
|
|
148
|
+
library: [semiconductor('Ge')],
|
|
149
|
+
stack: [ohmic(3), layer('Ge', 3e-3, { name: 'bar', donors: ND }), ohmic(0)],
|
|
150
|
+
bulkReactions: [recombination({ material: 'Ge', tau: 20e-6, majority: ND })],
|
|
151
|
+
ports: [injector({ region: 'bar', from: 0.475e-3, to: 0.525e-3, species: 'h+', I: pulse({ width: 0.5e-6, amplitude: I, rise: 1e-9 }) })],
|
|
152
|
+
grid: { hmin: 10e-6, hmax: 10e-6 },
|
|
153
|
+
}),
|
|
154
|
+
);
|
|
155
|
+
dev.solve();
|
|
156
|
+
const run = dev.advance(8e-6, { dtMax: 20e-9, probes: [{ x: 1.5e-3, species: 'h+' }] });
|
|
157
|
+
const seen = run.trace.probes[0], k = seen.indexOf(Math.max(...seen));
|
|
158
|
+
console.log(`the pulse passes at ${(run.trace.t[k] * 1e6).toFixed(2)} µs, Δp ≈ ${units.toPerCm3(seen[k]).toExponential(1)} cm⁻³`);
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Plotting
|
|
162
|
+
|
|
163
|
+
Solutions are plain arrays, ready for any plotting toolkit. Two helpers save the bookkeeping of
|
|
164
|
+
a level diagram:
|
|
165
|
+
|
|
166
|
+
- `traces(sol, { species, standard, phi, levels, labels, shifts })` (in `driftlet/kit`) returns
|
|
167
|
+
its lines as data: per charged species its voltage $`V_i`$ (`kind: 'level'`) and standard level
|
|
168
|
+
$`V^\circ_i`$ (`'standard'`, the band edges for e⁻ and h⁺), optionally $`\phi`$ (`phi: true`, or a
|
|
169
|
+
list of regions to draw it in only, by name or material, such as an insulator's:
|
|
170
|
+
`phi: ['SiO2']`; see [what's left out](visualization.md#whats-left-out-and-why)) and
|
|
171
|
+
half-reaction levels (`levels: [{ half, label, standard }]`, kinds `'redox'` and
|
|
172
|
+
`'redox-standard'`). Labels typeset the species as a subscript (`V_{SO₄²⁻}`, the species as
|
|
173
|
+
`typeset(name, z)` writes it); `labels` renames lines by id (`{ 'V:e-': 'Fermi level' }`).
|
|
174
|
+
`shifts` gives species display offsets in volts (`{ 'K+': 3.5 }`), moving all of a species'
|
|
175
|
+
lines together so that widely separated species share one readable plot; the label says so
|
|
176
|
+
(⌇). Each line has a colour `role` (`'electron'` for e⁻, `'cation'` (holes too), `'anion'`,
|
|
177
|
+
`'redox'`, `'phi'`) and a `slot` within it, so a colour follows its species whichever lines are
|
|
178
|
+
shown (`speciesRole(sol, name)` gives a species' own). Undefined values are `NaN` (break the line
|
|
179
|
+
there), and doubled interface nodes share an x, so steps draw as vertical lines. It also gives
|
|
180
|
+
the regions (`{ name, material, x0, x1 }`), the faces' positions and a suggested range.
|
|
181
|
+
- `bandDiagram(sol, opts)` in `driftlet/plot` draws them as an SVG string, with no DOM needed, in
|
|
182
|
+
the style [reading level diagrams](visualization.md) explains: species voltages thick and solid, standard levels thin and solid, redox
|
|
183
|
+
levels thick and dashed, standard redox levels thin and dashed, $`\phi`$ thin, dotted and grey;
|
|
184
|
+
electrons steel blue, cations warm colours, anions cool ones, redox levels blue-violets (a
|
|
185
|
+
palette checked for colour-vision deficiency, light and dark); a ⌇ across a shifted species,
|
|
186
|
+
with a note. The colours are CSS custom properties (`--driftlet-electron`,
|
|
187
|
+
`--driftlet-cation-1` …, `--driftlet-anion-1` …, `--driftlet-redox-1` …, `--driftlet-ink`, …)
|
|
188
|
+
with defaults of no specificity, so a rule such as `.figure svg { --driftlet-electron: … }`
|
|
189
|
+
restyles them; `THEME` and `themeColor(role, slot, { dark })` give them to other charts.
|
|
190
|
+
`xlim` (m) zooms into a window, with ticks and range fitted to it; `ytick(v)` writes the y
|
|
191
|
+
axis's tick labels. `levelChart(traces, opts)` draws traces you've edited, or any profiles
|
|
192
|
+
against x ($`\log_{10}`$ concentrations, say, with `ytick` writing powers of ten). Labels, titles and
|
|
193
|
+
the y label mark subscripts TeX-style, `V_{e⁻}` or `C_ox` (a run of letters, digits and
|
|
194
|
+
charge signs), drawn lowered and smaller; tooltips and the accessible name read `V_e⁻`.
|
|
195
|
+
`labelParts(label)` splits a label into its plain and subscript runs, for other renderers.
|
|
196
|
+
|
|
197
|
+
```js
|
|
198
|
+
import { Device } from 'driftlet';
|
|
199
|
+
import { build, layer, bath } from 'driftlet/kit';
|
|
200
|
+
import { bandDiagram } from 'driftlet/plot';
|
|
201
|
+
|
|
202
|
+
const def = build({
|
|
203
|
+
species: [
|
|
204
|
+
{ name: 'Na+', z: 1, cRef: 1000 },
|
|
205
|
+
{ name: 'Cl-', z: -1, cRef: 1000 },
|
|
206
|
+
],
|
|
207
|
+
materials: { water: { epsr: 78.5, species: { 'Na+': { D: 1.33e-9, mu0: -261.9e3 }, 'Cl-': { D: 2.03e-9, mu0: -131.2e3 } } } },
|
|
208
|
+
stack: [bath({ 'Na+': 100, 'Cl-': 100 }, 'Cl-'), layer('water', 10e-6), bath({ 'Na+': 10, 'Cl-': 10 }, 'Cl-', { I: 0 })],
|
|
209
|
+
grid: { hmin: 0.2e-9, hmax: 50e-9, ratio: 1.15 },
|
|
210
|
+
});
|
|
211
|
+
const svg = bandDiagram(new Device(def).solve(), { title: 'liquid junction', phi: true, xlim: [0, 1e-6] });
|
|
212
|
+
console.log(svg.length); // in a page: element.innerHTML = svg
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Checking a definition
|
|
216
|
+
|
|
217
|
+
`describe(def)` validates a definition and returns a readable summary: each region with its
|
|
218
|
+
doping, Debye length and the grid at its ends, each face's law, alignment, blocked species and
|
|
219
|
+
reactions, the contacts, bulk reactions, and characteristic times (dielectric relaxation,
|
|
220
|
+
diffusion across each region). It ends with warnings:
|
|
221
|
+
|
|
222
|
+
- numbers that look like unit slips (`unitWarnings(def)` gives these alone): a D beyond 1 m²/s
|
|
223
|
+
(cm²/s?), a nonzero $`\mu^\circ`$ under 100 J/mol (eV or volts?), a capacitance over 10 F/m²
|
|
224
|
+
(µF/cm²?), a concentration over 1000 M, lengths over a metre or under an atom, a T under
|
|
225
|
+
200 K (°C?), and so on;
|
|
226
|
+
- double layers the grid won't resolve: end cells coarser than the Debye length (the solution's
|
|
227
|
+
`warnings` check this again against the solved concentrations).
|
|
228
|
+
|
|
229
|
+
They're heuristics, prompts to check, never errors.
|
|
230
|
+
|
|
231
|
+
```js
|
|
232
|
+
import { units } from 'driftlet';
|
|
233
|
+
import { build, layer, ohmic, describe } from 'driftlet/kit';
|
|
234
|
+
|
|
235
|
+
const def = build({
|
|
236
|
+
species: [
|
|
237
|
+
{ name: 'e-', z: -1 },
|
|
238
|
+
{ name: 'h+', z: 1 },
|
|
239
|
+
],
|
|
240
|
+
materials: {
|
|
241
|
+
Si: { epsr: 11.7, species: { 'e-': { D: 36, mu0: 0, cRef: units.perCm3(2.8e19) }, 'h+': { D: 12e-4, mu0: units.eV(1.12), cRef: units.perCm3(1.04e19) } } },
|
|
242
|
+
},
|
|
243
|
+
stack: [ohmic(0), layer('Si', 1e-6, { name: 'n', donors: units.perCm3(1e17) }), layer('Si', 1e-6, { name: 'p', acceptors: units.perCm3(1e16) }), ohmic(0)],
|
|
244
|
+
grid: { hmin: 1e-9, hmax: 20e-9 },
|
|
245
|
+
});
|
|
246
|
+
console.log(describe(def)); // … warnings: materials.Si.species.e-.D: 36 m²/s is beyond any real diffusivity …
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Live demos
|
|
250
|
+
|
|
251
|
+
`live(def, opts)` wraps a device for sliders. Call `set(patch)` as often as a control moves
|
|
252
|
+
(the patch merges into the definition, as `Device.set()` does), and draw what comes back:
|
|
253
|
+
|
|
254
|
+
```js nocheck
|
|
255
|
+
const dev = live(def, { worker: true, onsolution: (sol, info) => (figure.innerHTML = bandDiagram(sol)) });
|
|
256
|
+
slider.oninput = () => dev.set({ contacts: { right: { V: +slider.value } } });
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
- Solves run one at a time, each warm-started from the last. Changes made while one runs are
|
|
260
|
+
merged and solved together, so a fast slider never queues up stale work. On the page's own
|
|
261
|
+
thread, each solve is its own task, so the page paints and takes input in between.
|
|
262
|
+
- A change that fails from the warm start is ramped to from the last good state, in more and
|
|
263
|
+
more steps (every number that differs is interpolated), then tried from scratch. If all of
|
|
264
|
+
that fails, the last good solution stays, with `info.failed` and the solver's `warnings`.
|
|
265
|
+
An invalid change does the same, with `info.error`. `info.ramp` says how many ramp steps were
|
|
266
|
+
needed, `info.ms` how long it took.
|
|
267
|
+
- `set()` returns a promise of `{ solution, info }`, and `ready` is the first solve's.
|
|
268
|
+
`solution` is always the last good one.
|
|
269
|
+
- `worker: true` solves in a Web Worker (a module worker), keeping the page responsive however
|
|
270
|
+
long a solve takes. It works with the library loaded from a CDN too.
|
|
271
|
+
|
|
272
|
+
```js
|
|
273
|
+
import { units } from 'driftlet';
|
|
274
|
+
import { build, layer, ohmic, live } from 'driftlet/kit';
|
|
275
|
+
|
|
276
|
+
const dev = live(
|
|
277
|
+
build({
|
|
278
|
+
species: [
|
|
279
|
+
{ name: 'e-', z: -1 },
|
|
280
|
+
{ name: 'h+', z: 1 },
|
|
281
|
+
],
|
|
282
|
+
materials: {
|
|
283
|
+
Si: { epsr: 11.7, species: { 'e-': { D: 36e-4, mu0: 0, cRef: units.perCm3(2.8e19) }, 'h+': { D: 12e-4, mu0: units.eV(1.12), cRef: units.perCm3(1.04e19) } } },
|
|
284
|
+
},
|
|
285
|
+
stack: [ohmic(0), layer('Si', 1e-6, { donors: units.perCm3(1e17) }), layer('Si', 1e-6, { acceptors: units.perCm3(1e16) }), ohmic(0)],
|
|
286
|
+
bulkReactions: [{ equation: 'e- + h+ = 0', kf: { Si: 1e8 } }],
|
|
287
|
+
grid: { hmin: 1e-9, hmax: 20e-9 },
|
|
288
|
+
}),
|
|
289
|
+
);
|
|
290
|
+
for (const V of [0.1, 0.2, 0.3, 0.4]) dev.set({ contacts: { right: { V } } }); // as a slider would
|
|
291
|
+
const { solution, info } = await dev.set({ contacts: { right: { V: 0.5 } } });
|
|
292
|
+
console.log(`I(0.5 V) = ${solution.current.toFixed(1)} A/m², in ${info.ms.toFixed(1)} ms`);
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Alignment from vacuum levels
|
|
296
|
+
|
|
297
|
+
`vacuumLevel`, `vacuumDipole` and `vacuumZeroCharge` turn vacuum-level estimates (electron
|
|
298
|
+
affinities, work functions, Trasatti's absolute SHE) into an interface's alignment. See the
|
|
299
|
+
[alignment guide](alignment.md).
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 74" width="300" height="74" role="img" aria-label="driftlet">
|
|
2
|
+
<path d="M4 18 C 34 18, 46 20, 58 34 S 72 54, 80 54" fill="none" stroke="#e8584f" stroke-width="5" stroke-linecap="round"/>
|
|
3
|
+
<path d="M4 34 C 34 34, 46 36, 58 50 S 72 70, 80 70" fill="none" stroke="#e8584f" stroke-width="1.8" stroke-linecap="round" opacity="0.75"/>
|
|
4
|
+
<path d="M44.9 9.7 L38.9 6.1 M41.0 13.4 L32.8 8.5" stroke="#ffb3ab" stroke-width="1.8" stroke-linecap="round" opacity="0.6"/>
|
|
5
|
+
<circle cx="52.2" cy="17.1" r="6.2" fill="#ffb3ab"/>
|
|
6
|
+
<text x="96" y="52" fill="#ffffff" font-family="system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="46" font-weight="600" letter-spacing="-0.5">driftlet</text>
|
|
7
|
+
</svg>
|
package/docs/logo.svg
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 74" width="300" height="74" role="img" aria-label="driftlet">
|
|
2
|
+
<path d="M4 18 C 34 18, 46 20, 58 34 S 72 54, 80 54" fill="none" stroke="#d1362f" stroke-width="5" stroke-linecap="round"/>
|
|
3
|
+
<path d="M4 34 C 34 34, 46 36, 58 50 S 72 70, 80 70" fill="none" stroke="#d1362f" stroke-width="1.8" stroke-linecap="round" opacity="0.75"/>
|
|
4
|
+
<path d="M44.9 9.7 L38.9 6.1 M41.0 13.4 L32.8 8.5" stroke="#8a1d18" stroke-width="1.8" stroke-linecap="round" opacity="0.6"/>
|
|
5
|
+
<circle cx="52.2" cy="17.1" r="6.2" fill="#8a1d18"/>
|
|
6
|
+
<text x="96" y="52" fill="#0b0b0b" font-family="system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="46" font-weight="600" letter-spacing="-0.5">driftlet</text>
|
|
7
|
+
</svg>
|