driftlet 0.13.0 → 0.16.2

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 CHANGED
@@ -56,7 +56,7 @@ import { bandDiagram } from 'driftlet/plot'; // a level diagram as an SVG str
56
56
  ```
57
57
 
58
58
  Or straight from a CDN in a page, pinned to a version:
59
- `https://cdn.jsdelivr.net/npm/driftlet@0.13.0/src/index.js` (and `src/kit.js`, `src/plot.js`).
59
+ `https://cdn.jsdelivr.net/npm/driftlet@0.16.2/src/index.js` (and `src/kit.js`, `src/plot.js`).
60
60
  TypeScript declarations ship in the package. Writing a demo with an LLM agent? Point it at
61
61
  [`llms.txt`](llms.txt).
62
62
 
package/docs/alignment.md CHANGED
@@ -83,7 +83,9 @@ def.interfaces = [{
83
83
 
84
84
  `vacuumDipole` gives $`\phi_R - \phi_L = (V_{\mathrm{vac}} - \phi)_L - (V_{\mathrm{vac}} - \phi)_R`$,
85
85
  each side's $`V_{\mathrm{vac}} - \phi`$ coming from its anchor and offset (`vacuumLevel` gives one
86
- side's).
86
+ side's). Its two sides are the face's, left then right as the stack runs: given the other way
87
+ round, the dipole changes sign, and nothing can tell (a photoanode's band edges came out 0.5 V
88
+ off that way).
87
89
 
88
90
  Where a conductor meets a material, at a conductor region's face or at a contact's capacitive
89
91
  or pinned law, the conductor's side is its work function W from its Fermi level, and the
@@ -120,8 +122,9 @@ $`\mathrm{pzc} \approx W/e - 4.44\,\mathrm{V}`$ on the SHE scale (on the usual c
120
122
 
121
123
  **Semiconductor | electrolyte.** Electron affinity against the absolute SHE places the band edges on
122
124
  the SHE scale, $`E_c \approx -(\chi - 4.44)\,\mathrm{eV}`$ relative to SHE: the usual
123
- photoelectrochemistry estimate:
124
- `vacuumDipole(def, { material: 'TiO2', anchor: 'e-', offset: χ }, { material: 'water', anchor: 'H+', offset: 4.44 })`.
125
+ photoelectrochemistry estimate, for TiO₂ on the left of the water:
126
+ `vacuumDipole(def, { material: 'TiO2', anchor: 'e-', offset: χ }, { material: 'water', anchor: 'H+', offset: 4.44 })`
127
+ (the anchor needs `H+` among the species).
125
128
 
126
129
  **Solvent | solvent.** Each solvent's own absolute-SHE-type value (its Trasatti-type offset)
127
130
  aligns them, exactly as Anderson's rule aligns two semiconductors.
package/docs/data.md CHANGED
@@ -17,7 +17,9 @@ for `build()`: each ion a species with $`c_{\mathrm{ref}}`$ = 1000 mol/m³ (1 M)
17
17
  `water` with its D and $`\mu^\circ`$. `epsr` defaults to water's 78.30 at 25 °C (Malmberg and
18
18
  Maryott 1956); `epsr: 0` makes the solution strictly neutral, for macroscopic models without
19
19
  double layers. The data are in `IONS`, and $`\mu^\circ`$ of liquid water is `H2O`
20
- (−237.129 kJ/mol), for reactions such as H₂O ⇌ H⁺ + OH⁻.
20
+ (−237.129 kJ/mol), for reactions such as H₂O ⇌ H⁺ + OH⁻. Together they give $`K_w`$ =
21
+ 1.011e-14 at 25 °C (pK_w 13.995): neutral water holds 1.0055e-4 mol/m³ of each, which a bath
22
+ with that reaction should hold too.
21
23
 
22
24
  | Ion | z | D (10⁻⁹ m²/s) | $`\mu^\circ`$ (kJ/mol) |
23
25
  |---|---|---|---|
package/docs/device.md CHANGED
@@ -114,7 +114,10 @@ regions: [
114
114
  injected hole packet); what the cold start leaves unbalanced settles within the first steps.
115
115
  That fill-in is right in a quasi-neutral bulk (it's what dielectric relaxation would do), not
116
116
  where the rest state carries charge: a packet laid into a junction starts with the junction
117
- itself unformed.
117
+ itself unformed. The grid isn't refined to fit a profile: where it's steeper than the cells
118
+ under it, the start is the profile sampled at the nodes, something else, and every solution
119
+ warns of it with the cells that would resolve it (grade toward it, or put a region boundary
120
+ there).
118
121
  For a spectator, the profile's integral is the amount it conserves. Where nothing else sets
119
122
  $`\phi`$ at the start, a region beside an electrode starts with the electrode's first reaction at
120
123
  equilibrium (the electrode at its open-circuit level); otherwise $`\phi`$ carries over from the
@@ -167,10 +170,14 @@ defaults to no dipole.
167
170
  The alignment drops out and must not be given. This is the default between two $`\varepsilon = 0`$
168
171
  materials, and next to an insertion host (where $`\phi`$ is undefined).
169
172
  - `{ type: 'capacitive', C }`: a Helmholtz layer, $`D = -C \cdot (\Delta\phi - \mathtt{dipole})`$, C
170
- in F/m².
173
+ in F/m². Beside a strictly neutral ($`\varepsilon = 0`$) material, the face's charge is held in
174
+ the cell next to it, which acts as a diffuse layer half that cell wide, $`F^2 \sum_i z_i^2 c_i \, h/2RT`$
175
+ in series with C. So keep that cell coarse rather than fine (a warning says when it costs C more
176
+ than 2%, and how coarse is enough).
171
177
 
172
178
  **Species laws** `species` (default: local equilibrium, $`\bar\mu`$ continuous, where the species is
173
- present on both sides; blocked otherwise): `'equilibrium'`, `'blocked'`, or
179
+ present on both sides and can move on one at least; blocked otherwise, so trap states with
180
+ $`D = 0`$ stay in their regions): `'equilibrium'`, `'blocked'`, or
174
181
  `{ type: 'conductance', G }` ($`J = G \cdot (V_L - V_R)`$, G in S/m², charged species), or
175
182
  `{ type: 'permeability', P }` (P in m/s; see [membranes](#membranes)). A species that
176
183
  takes part in a reaction at the face and exists on both sides has no default: give its link
@@ -367,7 +374,11 @@ The conductor's carrier can continue across as a species (e⁻ into a semiconduc
367
374
  `'equilibrium'` by default). An electrode is a conductor region with `reactions` at its face,
368
375
  the metal's electrons taking part on its side at its Fermi level, behind a contact that holds
369
376
  the electrons (`{ V, terminal: 'e-', species: { 'e-': 'equilibrium' }, phi: 'bulk' }`). A
370
- floating conductor region with reactions on both faces is a bipolar electrode.
377
+ floating conductor region with reactions on both faces is a bipolar electrode; between two
378
+ capacitive faces and nothing else, a floating gate, which keeps its charge
379
+ $`D_{\mathrm{right}} - D_{\mathrm{left}}`$ (its faces' `interfaces[f].D`) through any steady solve. A
380
+ port on it (a wire to the whole conductor, with a `conductance`) charges it, held at a voltage,
381
+ and leaves it charged, set to `I: 0`.
371
382
 
372
383
  ## Bulk reactions
373
384
 
@@ -463,25 +474,37 @@ contacts: {
463
474
  over a Schottky barrier (below).
464
475
  - `'blocked'`: no flux.
465
476
 
477
+ A contact holding several species in equilibrium fixes the composition at its end: with Ag⁺ and
478
+ e⁻ both held in a silver halide, its electron and hole densities follow from their levels and
479
+ neutrality, not from `c0` (so a different fixed charge changes them). Read them from the solution.
480
+
466
481
  **`bath`** (instead of `species` and `phi`): the outside phase is a neutral composition `c`
467
482
  held in place by a charged `reference` species (as for a reversible reference electrode, e.g.
468
483
  Cl⁻ for Ag/AgCl), which is the terminal. Every bath species is in equilibrium at the level its
469
484
  composition implies, and the $`\phi`$ law is `'bulk'`. An optional `offset` places the reference
470
485
  species relative to V. Without a `reference`, V is the bath's own $`\phi`$: an ideal salt bridge
471
486
  (no junction potential), the convention by which membrane potentials are $`\phi`$ differences.
472
- Between two such baths, a held $`V_{\mathrm{right}} - V_{\mathrm{left}}`$ is a voltage clamp.
487
+ Between two such baths, a held $`V_{\mathrm{right}} - V_{\mathrm{left}}`$ is a voltage clamp. The bath is the end
488
+ material at that composition (which then balances any fixed charge there, as an insertion host's
489
+ background carriers do). A solution against a charged end region (an ion exchanger, a gel) names
490
+ its own material, `bath: { c, material: 'water' }`: its composition is then neutral by itself, its
491
+ levels are that material's, and the end node takes them through a Donnan step.
473
492
 
474
493
  **`phi`**, required whenever any species or reaction connects at the contact (default
475
494
  `'neutral'`):
476
495
  - `'bulk'`: the end node is plain bulk, locally neutral, with no double layer at the contact
477
- (ohmic contacts, baths). The outside takes whatever surface charge that needs.
496
+ (ohmic contacts, baths). The outside takes whatever surface charge that needs. A double layer
497
+ or depletion region that reaches it is cut off there (σ 58% low for a crowded layer 5 Debye
498
+ lengths from the contact), which the solution's `warnings` say: lengthen the region.
478
499
  - `'neutral'`: no charge at the face ($`D = 0`$), as for an internal `'neutral'` face.
479
500
  - `{ type: 'capacitive', C }` with the contact's `zeroCharge`: a gate or Stern layer to a conductor
480
501
  at the terminal voltage V. The displacement into the device is
481
502
  $`C \cdot ((V - \mathtt{zeroCharge}) - \phi_{\mathrm{edge}})`$, so `zeroCharge` is the value of
482
503
  $`V - \phi_{\mathrm{edge}}`$ at which the interface carries no charge: the potential of zero charge
483
504
  (pzc) of an electrode, or for a gate the flat-band voltage less the semiconductor's bulk $`\phi`$
484
- ($`V_{\mathrm{FB}} = \mathtt{zeroCharge} + \phi_{\mathrm{bulk}}`$).
505
+ ($`V_{\mathrm{FB}} = \mathtt{zeroCharge} + \phi_{\mathrm{bulk}}`$). So to start uncharged at
506
+ V = 0, give `zeroCharge` = −φ_bulk, which the material's composition sets (`sol.phi` far from
507
+ the face, after a solve).
485
508
  - `'pinned'` with `zeroCharge`: the $`C \to \infty`$ limit,
486
509
  $`\phi_{\mathrm{edge}} = V - \mathtt{zeroCharge}`$. This is what a "fixed $`\phi`$" boundary honestly
487
510
  means. For example, a Schottky barrier $`\phi_B`$ on n-type material with
@@ -571,11 +594,15 @@ that, use a [bulk reaction](#bulk-reactions) with the outside phase as a fixed p
571
594
  $`k_f (1 - c/c_{sat})`$; a profile gives $`k_f`$ its spatial variation ($`D c_{sat}/h^2`$ under a film
572
595
  of thickness $`h(x)`$).
573
596
 
574
- A held level can also anchor what nothing else holds. In a 1D MOS capacitor without generation,
575
- inversion electrons can only arrive by minority-carrier diffusion from the back contact (weeks),
576
- so the inversion layer's Fermi level is undetermined to within round-off and no steady state can
577
- be computed. A port holding the electrons beside the oxide, as source and drain would, anchors it,
578
- and the device shows the low-frequency C–V.
597
+ A held level can also stand in for a supply. In a 1D MOS capacitor without a port, inversion
598
+ electrons arrive only by generation or by diffusion from the back contact. The steady state
599
+ still solves (to equilibrium), but they can't follow a signal faster than that supply: the
600
+ impedance gives the low-frequency C–V only below a knee at about 1/(2πτ_c), τ_c the time the
601
+ inversion charge takes to refill (10 s with a 1 µs lifetime; hours by diffusion alone through a
602
+ wafer), and the high-frequency curve above it. A port holding
603
+ the electrons beside the oxide, as source and drain would, supplies them at once, and the device
604
+ shows the low-frequency C–V at any frequency (a conductance link instead puts the knee where you
605
+ choose).
579
606
 
580
607
  ### Electrodes spread through a window
581
608
 
@@ -628,7 +655,10 @@ Langmuir statistics, $`\mu = \mu^\circ + RT\ln(\theta/\theta_0)`$ with $`\theta_
628
655
  bare fraction, and `capacity` $`\Gamma`$, mol of sites per m² of electrode. Reactions name them like
629
656
  species, and a reaction with `bare: true` runs only on bare metal: its rate is multiplied by
630
657
  $`\theta_0`$, both ways, so equilibrium stays exact. Written for adsorption itself, that's Langmuir
631
- kinetics (on at a rate ∝ $`\theta_0`$, off ∝ $`\theta`$).
658
+ kinetics (on at a rate ∝ $`\theta_0`$, off ∝ $`\theta`$). A species' `theta0` is its starting
659
+ coverage (not the bare fraction). Surface species carry no charge, so an adsorbed redox couple
660
+ balances its electrons through the solution: `'Oads + e- = Rads + Cl-'`, its coverages at
661
+ $`\theta_O/\theta_R = e^{(E - E^\circ)/V_T}`$ against the chloride's level.
632
662
 
633
663
  ```js nocheck
634
664
  ports: [{
@@ -663,8 +693,9 @@ Cautions:
663
693
 
664
694
  Validated in test/coverage.test.js: the Langmuir isotherm against the electrode's potential (to
665
695
  1e-9), filling against its exponential and its charge $`F\,\Gamma\,\Delta\theta`$ per area of electrode,
666
- the active–passive curve with blocking exactly $`(1-\theta)`$ times Butler–Volmer, and a steady state
667
- through a surface intermediate where the transient ends.
696
+ the active–passive curve with blocking exactly $`(1-\theta)`$ times Butler–Volmer, a steady state
697
+ through a surface intermediate where the transient ends, and an adsorbed couple at Nernst from a
698
+ cold start.
668
699
 
669
700
  ### A capacitance through a window
670
701
 
@@ -812,7 +843,10 @@ options at all, each region is graded from `hmin` = 1/1000 to `hmax` = 1/20 of i
812
843
  geometrically from `hmin` at both ends of each region up to `hmax`, scaled to fit exactly. Each
813
844
  region boundary becomes a pair of nodes at the same x, one per side. The grid is never refined
814
845
  automatically. If a double layer that the model resolves is coarser than the local Debye
815
- length, the solution's `warnings` say so.
846
+ length, the solution's `warnings` say so. That's a floor, not an accuracy: a strongly polarised
847
+ layer screens over its local length (counter-ions piled up at the wall: 0.5 nm at 1 V on
848
+ 0.1 mM, where λ_D is 30 nm), and σ to ~1% wants cells a few times finer than that (0.3 nm cells
849
+ there left σ 0.7% high, without a warning). Halve `hmin` and compare.
816
850
 
817
851
  Grade the grid toward wherever a profile is steep: double layers, and electrodes where a species
818
852
  is depleted. Near a limiting current the depleted species' profile is steep in a thin layer at
@@ -821,6 +855,21 @@ cells exceeds the limiting current by 1.4% at 0.5 V, while `{ hmin: 10e-9, hmax:
821
855
  within 0.1% with as many nodes. Where a species carrying the current is steep across a region's
822
856
  end cells in this way, the solution's `warnings` say so, with a rough estimate of the excess.
823
857
 
858
+ A homogeneous reaction feeding an electrode (a CE mechanism) works within its reaction layer,
859
+ $`\sqrt{D/k}`$, which wants cells of a third of it for ~0.5% and a tenth for ~0.1% (20% off at
860
+ ten times that, unwarned).
861
+
862
+ Past a limiting current, the depleted layer at a membrane or an electrode is nanometres thick,
863
+ and anything that happens in it (water splitting, say) needs cells a fraction of that: a bulk
864
+ water-splitting reaction at an ion-exchange membrane gave 2.7 times the limiting current on
865
+ 50 nm cells and 1.03 times on 0.2 nm ones. Only `check()` catches it.
866
+
867
+ In a transient, a diffusion layer grows from the electrode as $`\sqrt{Dt}`$, through cells up to
868
+ `hmax` wide, and nothing warns while they're coarse: give `hmax` a small fraction of
869
+ $`\sqrt{Dt}`$ at the earliest time that matters. In a chronopotentiometry run, cells half
870
+ $`\sqrt{D\tau}`$ wide put the surface concentration 0.5% off, a fifteenth 0.06% (the error goes
871
+ as $`h^2`$).
872
+
824
873
  ## Geometry
825
874
 
826
875
  A device is planar unless it says otherwise: a slab whose cross-section $`A`$ is 1 m², so every
@@ -868,22 +917,27 @@ const now = dev.solution(); // snapshot of the current state
868
917
 
869
918
  - `solve()` finds the steady state from the current state without advancing time. If every
870
919
  species is fed by a contact, it solves the steady equations directly. Otherwise conserved
871
- amounts (blocked species, reactive moieties) are kept exactly. If a direct solve fails at a
920
+ amounts are kept exactly: blocked species, reactive moieties, and whatever a terminal driven
921
+ at no current is the only way into (a closed battery at open circuit keeps its charge, the
922
+ state of charge it holds when the solve begins; a gate left floating keeps the charge on its
923
+ capacitance). Driven at any other current, such a terminal only stores what comes in (a host
924
+ filling, a capacitor charging), which has no steady state, and the solve says so at once. If a direct solve fails at a
872
925
  bias, it ramps the contacts' voltages from where they were last solved (the left one first,
873
926
  where it moved), or from level terminals on a cold start; with generation reactions (below),
874
927
  it ramps their rates up from nearly nothing. A terminal driven by a current (open circuit,
875
928
  say) is held at a voltage instead, the voltage marched until the current crosses its target,
876
929
  and floated from there. If 20 V either way brings no crossing (a current beyond the limiting
877
- one, or a device that only stores what comes in, which has no steady state at a current), the
878
- solve fails with a warning giving the range of currents the held voltages passed. A solve that
879
- fails reports the state it failed in, then leaves the device as it was before it.
930
+ one), the solve fails with a warning giving the range of currents the held voltages passed. A
931
+ solve that fails reports the state it failed in, then leaves the device as it was before it.
880
932
  - `step(dt, { method })` advances the transient by dt seconds, halving internally where Newton
881
933
  needs it. `method` is `'be'` (backward Euler, the default) or `'bdf2'`.
882
934
  - `advance(tEnd, opts)` integrates adaptively to `tEnd` with variable-step BDF2, controlling
883
935
  the local error per step to `tol` (default 1e-3) in thermal units of every potential ($`\phi`$ and
884
936
  each $`\bar\mu/RT`$): roughly 0.1% in concentrations per step. That's local: over a long decay
885
937
  the errors add up, and a small current at the end of one can be tens of percent off, so tighten
886
- `tol` (1e-5, say) before fitting a time constant to a tail. It lands exactly on `tEnd`. Options: `tol`,
938
+ `tol` (1e-5, say) before fitting a time constant to a tail. It lands exactly on `tEnd`, an
939
+ absolute time (a solution's `time` is where the device is); a NaN does nothing, and its
940
+ `warnings` say so. Options: `tol`,
887
941
  `dt0` (first step), `dtMax`, `budgetMs` (return after this much wall time, with
888
942
  `done: false`), `maxSteps`, `method`. The step size carries over between calls, so an
889
943
  animation can call `advance(tNext, { budgetMs })` once per frame. The solution adds `done`,
@@ -894,26 +948,34 @@ const now = dev.solution(); // snapshot of the current state
894
948
  `probes: [{ x, species, quantity, region }]` the trace also reads inside the device,
895
949
  as `trace.probes[k]` beside `trace.t`: a species' concentration (`quantity: 'c'`, the default,
896
950
  mol/m³) or species voltage (`'V'`), or $`\phi`$ itself (`'phi'`, no species), linearly between the
897
- nodes around `x` (at an interface, `region` picks the side). That's what a detector at `x` sees.
951
+ nodes around `x` (at a face, the side where the species is; where both sides have it, `region`
952
+ says which). That's what a detector at `x` sees.
898
953
  At a face, `{ interface: f, species }` reads a species' flux through it (mol/(m²·s), toward +x,
899
954
  as `interfaces[f].N`), and `{ interface: f, gate }` a gate's fraction open. Probes read at accepted steps, which grow
900
955
  as a transient slows, so give `dtMax` to resolve a signal in time.
901
956
  - `impedance(frequencies, { terminal, profiles })` solves the steady state, then linearises about
902
957
  it: $`Z(f) = \delta V/\delta I`$ in Ω·m² at one terminal (`'right'` by default), with I into the
903
958
  device. A held terminal's voltage is perturbed, or a driven one's current; the other terminals
904
- keep their drives (held ones at AC ground, driven ones open). A terminal behind a resistance can't
905
- be the one measured: the resistance belongs to the external circuit. With `profiles: true`, each
959
+ keep their drives (held ones at AC ground, driven ones open), so at least one other must be held
960
+ for the current to return (with every other one driven, the impedance is infinite, and an error
961
+ says to measure at a driven one). A terminal behind a resistance can't be the one measured: the
962
+ resistance belongs to the external circuit. The grid must resolve how far the signal reaches,
963
+ and nothing warns of it: in a porous electrode, $`\sqrt{\kappa/(\omega C a)}`$ (cells of 2 µm
964
+ against a 3.4 µm depth put Z 7% off, falling as the cells do). With `profiles: true`, each
906
965
  frequency also returns complex profiles of $`\delta\phi`$, $`\delta\bar\mu`$ and $`\delta c`$ per unit
907
966
  excitation.
908
967
  - `set(patch)` merges plain objects deeply (arrays are replaced), except that a contact given `V`
909
968
  or `I` drops the other: `{ contacts: { right: { I: 0 } } }` switches it to open circuit. A patch that changes only the
910
969
  terminals' drives (`V`, `I`, `R`) updates them in place, cheaply, keeping everything else.
911
- Otherwise the device is rebuilt: the current state carries over while the grid and species
912
- are unchanged, or restarts from the regions' `c0`. A carried state keeps what it holds: a
970
+ Otherwise the device is rebuilt: the current state carries over while the species, regions
971
+ and terminals are unchanged (on a new grid, interpolated onto its nodes, so a floating gate
972
+ keeps its charge), or restarts from the regions' `c0`. A carried state keeps what it holds: a
913
973
  stretch that was closed stays at its amount, and one that a change closes (or splits) keeps
914
974
  what it holds at that moment. So a new `c0` doesn't apply to a carried state; to start over
915
- from `c0`, make a new `Device`. Either way the time stepping restarts its
916
- order, as after any discontinuity.
975
+ from `c0`, make a new `Device`. (A bath's composition is a contact's, not a `c0`: changing it
976
+ mid-run, `set({ contacts: { right: { bath: { c } } } })`, is a step in the sample.) Either way the time stepping restarts its
977
+ order, as after any discontinuity, but a carried state goes on at the step size an adaptive
978
+ transient had reached, so a page may change a rate constant or a diffusivity every frame.
917
979
 
918
980
  For example, a silver nitrate cell between silver electrodes: its impedance spectrum, then the
919
981
  current transient after a voltage step.
@@ -950,17 +1012,19 @@ console.log(`${run.steps} steps; I(0.01 s) ≈ ${run.trace.current[run.trace.t.f
950
1012
  | `phi` | bookkeeping $`\phi`$, V (`NaN` where undefined) |
951
1013
  | `c[name]`, `mu[name]`, `muStd[name]` | concentration, $`\bar\mu`$, standard level $`\mu^\circ + zF\phi`$ (`NaN` where absent) |
952
1014
  | `V[name]`, `Vstd[name]` | species voltage $`\bar\mu/(zF)`$ and standard level as a voltage (charged species) |
1015
+ | `flux[name]` | flux toward +x across each segment, between nodes `g` and `g + 1` (a face's across its pair of nodes), mol/(m²·s) (mol/s through a cross-section): the fluxes the balances add up |
1016
+ | `D[name]` | diffusivity at each node, m²/s (`NaN` where absent, or in a metal) |
953
1017
  | `current`, `terminalVoltage` | current toward +x through the device (A/m², or A through a [cross-section](#geometry)) and $`V_{\mathrm{right}} - V_{\mathrm{left}}`$ |
954
1018
  | `terminals[name]` | `{ V, current }` for each terminal (contacts and ports), current into the device |
955
- | `contacts.left/right` | `{ V, flux: {name}, D, current }` at each contact |
1019
+ | `contacts.left/right` | `{ V, flux: {name}, D, current, links: {name} }` at each contact; `links` names how each species that crosses it does (`'equilibrium'`, `'velocity'`, …) |
956
1020
  | `gates.left/right` | charge on a gate or Stern plate, where the contact is capacitive |
957
1021
  | `ports[k]` | `{ name, V, flux: {name}, current }`: what each internal port brings into the device; an electrode's rates, coverages and charge [as well](#what-a-solution-reports) |
958
- | `interfaces[f]` | `{ left, right, dipole, sheetCharge, D, N: {name}, rates }`: the names of the regions the face joins, what crosses it by its links, and each face reaction's rate (mol/(m²·s)) |
959
- | `bulkReactions[k]` | `{ rate, regions, total }`: each bulk reaction's forward rate at every node (mol/(m³·s), `NaN` where it doesn't run), and integrated over each region and the device (mol/(m²·s)), as the balances count it: a charge-balance check is J = F(generation − recombination), which the kit's `check()` does for every species |
1022
+ | `interfaces[f]` | `{ left, right, dipole, sheetCharge, D, N: {name}, rates, links: {name} }`: the names of the regions the face joins, what crosses it by its links, each face reaction's rate (mol/(m²·s)), and how each species that crosses it does; for those crossing by permeability, `oneWay: { name: [toward +x, toward −x] }`, the one-way fluxes whose difference is `N` (Ussing's unidirectional fluxes) |
1023
+ | `bulkReactions[k]` | `{ rate, forward, nu, regions, total }`: each bulk reaction's net forward rate at every node (mol/(m³·s), `NaN` where it doesn't run), its one-way forward rate (the backward is `forward − rate`), what one forward reaction makes of each species (`nu`, negative for what it consumes), and integrated over each region and the device (mol/(m²·s)), as the balances count it: a charge-balance check is J = F(generation − recombination), which the kit's `check()` does for every species |
960
1024
  | `charge` | total charge in the device, C/m² (C through a cross-section) |
961
- | `conservation` | per species stretch: amount, reference, intake through contacts, drift |
1025
+ | `conservation` | per species stretch: amount, reference, intake through contacts, and drift, relative: (amount − reference − intake) over the largest of them |
962
1026
  | `warnings` | e.g. unresolved double layers, conventions a statistics model relies on, and for a failed solve, where the system is nearly singular |
963
- | `converged`, `iterations`, `steps`, `substeps`, `history`, `time` | solver bookkeeping |
1027
+ | `converged`, `iterations`, `steps`, `substeps`, `history`, `time`, `T` | solver bookkeeping, and the temperature |
964
1028
  | `steady` | whether this is a converged steady state (from `solve()`), not a transient's: what `check()` in the kit decides its checks by |
965
1029
  | `done`, `rejected`, `trace` | from `advance()`: whether `tEnd` was reached; rejected steps; `{ t, current, voltage }` per accepted step |
966
1030
 
package/docs/kit.md CHANGED
@@ -57,13 +57,14 @@ console.log(`I = ${sol.current.toFixed(1)} A/m²`);
57
57
  - **Contacts** are the first and last items. `ohmic(drive, carriers)` holds each carrier in
58
58
  equilibrium with a metal at the terminal voltage (default `['e-', 'h+']`, an
59
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 whose terminal voltage is
60
+ a metal region). `bath(c, reference, drive, { offset, material })` is a bath whose terminal voltage is
61
61
  the level of its `reference` species, as an electrode reversible to it would read (`offset`
62
62
  places that species relative to the terminal voltage, e.g. to read a solution's potentials
63
63
  against SHE). `bath(c, drive)`, without a reference, is a bath whose terminal voltage is its
64
64
  φ: an ideal salt bridge, the convention of membrane potentials, so that between two such baths
65
65
  V_right − V_left is the φ difference a voltage clamp sets. A drive is a voltage, a waveform, or
66
- `{ V }`, `{ I }`, `{ V, R }`.
66
+ `{ V }`, `{ I }`, `{ V, R }`. A bath against a charged layer (an ion exchanger) gives its own
67
+ `material`, as `bath(c, 0, { material: 'water' })`: a Donnan step then joins them at the contact.
67
68
  - **`library`** takes pieces with `species` and `materials`, merged with any given directly
68
69
  (`combine()` does the merging on its own). A species or material given twice must be given
69
70
  identically.
@@ -186,14 +187,17 @@ Helpers that write sources as plain data:
186
187
  - **`photogeneration({ material, flux, alpha, mu, from, to, makes })`**: light absorbed as it goes in
187
188
  (Beer–Lambert). A photon flux (mol/(m²·s); a power P at photon energy E is
188
189
  $`P/(E N_A)`$) enters the material at x = `from` heading toward +x and generates pairs at
189
- $`G(x) = \Phi\alpha e^{-\alpha(x - \mathrm{from})}`$ until `to`, the material's far end. It's
190
+ $`G(x) = \Phi\alpha e^{-\alpha(x - \mathrm{from})}`$ until `to`, and nothing outside (so `from` and
191
+ `to` can pick out one layer of a material that's also elsewhere). It's
190
192
  photon ⇌ e⁻ + h⁺ from a photon reservoir at $`\mu`$ = `mu` (J/mol, well above the gap, e.g.
191
193
  `units.eV(3)`), with `kf` a profile against x. `makes` replaces the right-hand side: in an
192
194
  organic semiconductor, light makes neutral excitons, `makes: 'X'`. The table generates exactly what the light
193
195
  loses, $`\Phi(1 - e^{-\alpha(\mathrm{to} - \mathrm{from})})`$, and the solver averages it
194
196
  over each node's box, so the total stays right on a grid coarser than $`1/\alpha`$ (where
195
197
  carriers start within a cell still needs the grid). Light through several materials is one
196
- call per material, each with the flux that reaches it.
198
+ call per material, each with the flux that reaches it. To switch the light during a run (a
199
+ pulse, a slider), `set()` the device's `bulkReactions` with a new flux: the state carries over,
200
+ and so does an adaptive transient's step size.
197
201
 
198
202
  Read a transient at points inside the device with `advance(t, { probes })` (see the
199
203
  [device reference](device.md#using-a-device)). And `units` converts back out of SI for display:
@@ -378,15 +382,20 @@ console.log(svg.length); // in a page: element.innerHTML = svg
378
382
 
379
383
  `describe(def)` validates a definition and returns a readable summary: each region with its
380
384
  doping, Debye length and the grid at its ends, each face's law, alignment, blocked species and
381
- reactions, the contacts, bulk reactions, and characteristic times (dielectric relaxation,
382
- diffusion across each region). It ends with warnings:
385
+ reactions, the contacts (a bath read through a reference ion says where its SHE level sits
386
+ against the terminal voltage: 1.37 V below for 0.5 M Cl⁻ on table $`\mu^\circ`$), bulk reactions, and
387
+ characteristic times (dielectric relaxation, diffusion across each region). It ends with
388
+ warnings:
383
389
 
384
390
  - numbers that look like unit slips (`unitWarnings(def)` gives these alone): a D beyond 1 m²/s
385
391
  (cm²/s?), a nonzero $`\mu^\circ`$ under 100 J/mol (eV or volts?), a capacitance over 10 F/m²
386
392
  (µF/cm²?), a concentration over 1000 M, lengths over a metre or under an atom, a T under
387
393
  200 K (°C?), and so on;
388
394
  - double layers the grid won't resolve: end cells coarser than the Debye length (the solution's
389
- `warnings` check this again against the solved concentrations).
395
+ `warnings` check this again against the solved concentrations);
396
+ - an outside (a bath, a contact's levels) out of equilibrium with a bulk reaction that runs at the
397
+ contact, which would run there for ever: H⁺ and OH⁻ in a bath a little off $`K_w`$ pass a current
398
+ at zero bias.
390
399
 
391
400
  They're heuristics, prompts to check, never errors.
392
401
 
@@ -432,7 +441,8 @@ page can show that its numbers hold up. Pass the solution `solve()` or `advance(
432
441
  region's charge must agree to `tol` (relative; default 1e-2, about what a plot shows; a
433
442
  benchmark wants 1e-3 or less). The discretisation is second order, so the change estimates
434
443
  this grid's own error (about ¾ of it). It costs one more solve, so run it once, not on every
435
- slider move.
444
+ slider move. In a transient it's `?`, not checked: the answer there depends on the whole run
445
+ (and on drives set along the way), so run it again yourself on a grid twice as fine.
436
446
 
437
447
  It returns `{ ok, items, text }`: `ok` when nothing failed, an item per check
438
448
  (`{ name, ok, summary, details }`, with `ok: null` for a prompt to look), and `text`, a line
@@ -508,6 +518,83 @@ const { solution, info } = await dev.set({ contacts: { right: { V: 0.5 } } });
508
518
  console.log(`I(0.5 V) = ${solution.current.toFixed(1)} A/m², in ${info.ms.toFixed(1)} ms`);
509
519
  ```
510
520
 
521
+ ## Particles
522
+
523
+ `particles(sol, opts)` draws a solution as dots, each a fixed amount of one species (`weight`,
524
+ mol/m²), hopping between the cells of a display lattice. The hops' one-way rates come from the
525
+ solution, so on average the dots fill each cell as its concentration does and cross each
526
+ boundary, net, at its flux: they're a sample of the solution, not a separate model. Dots are
527
+ made and unmade by the bulk reactions' one-way rates (so in equilibrium generation and
528
+ recombination both go on, in balance) and wherever else the flux diverges (ports, face
529
+ reactions), and enter and leave through the contacts and any metal.
530
+
531
+ - Across a boundary the one-way fluxes have the solution's flux as their difference and
532
+ Scharfetter–Gummel's exchange as their product, which gives a dot in a flat potential the
533
+ diffusivity D. A face crossed by permeability uses its own one-way fluxes (the solution's
534
+ `oneWay`), so the dots crossing each way are Ussing's unidirectional fluxes, as a tracer
535
+ experiment counts them.
536
+ - Concentrations span decades, so one weight can't draw them all. A cell that would hold more
537
+ than `cap` dots (for each width asked for that it spans: a density) is a **sea** instead: drawn as shading, holding no dots, exchanging them with
538
+ its neighbours as a contact's reservoir does. A junction then shows its majority carriers as
539
+ seas and its minority carriers as dots. Crossings between two seas through a membrane are
540
+ `events`.
541
+ - Options: `species` (which to draw; default every species with a concentration), `dots`
542
+ (about how many per species, for the default `weight`), or `weight` itself (mol/m² per dot,
543
+ one for all or `{ name: weight }`; fixed for the swarm's life), `cap` (default ∞: no seas),
544
+ `cells` (a target), `random`.
545
+ - `step(dt)` moves the dots on by dt of the device's time, and returns the swarm. A cell a dot
546
+ would hop out of many times in one step (more than `maxHops`/4, default 200) is a sea for
547
+ that step and is then drawn afresh, so a step far longer than the hop time costs little and
548
+ is still a sample (only its dots' paths are lost). A NaN or negative dt (a first frame, a clock
549
+ that stepped back) moves nothing.
550
+ - `update(sol)` takes a new solution. On a transient's frames, the dots move on at the new rates.
551
+ With `{ resample: true }`, for a jump between steady states only (never a transient's frames,
552
+ whose path it would lose), each cell's dots are thinned or added to by the change in what it
553
+ should hold, which keeps them a Poisson sample while moving as few as can be. A solution on
554
+ another grid (a resized device) redraws the lattice and places the dots afresh.
555
+ - The swarm has `dots` (`{ species, cell, x }`, placed within a cell by its concentration; each
556
+ keeps its object while it lives, so a page can give it fields of its own, a height say),
557
+ `cells` (`{ x0, x1, region }`: grid nodes in runs as near `L/cells` wide as the nodes allow,
558
+ never across a region boundary or into a metal, so the count and widths come out a little off
559
+ what was asked),
560
+ `weight[name]`, `sea[name]` (1 per sea cell), `expected(name)` (dots per cell on average),
561
+ `events` from the last step (`{ species, kind, x, dir }`: `'in'`, `'out'`, `'made'`,
562
+ `'unmade'`, `'cross'`) and `crossed[name]` (`{ up, down }`: dots across boundary k, at
563
+ `cells[k].x0`, toward +x and −x, k = 0 … cells.length, counted until you zero them). A sea
564
+ is a reservoir, not a sample: nothing is counted between two seas (except through a
565
+ permeable face) and nothing made or unmade in one is an event.
566
+ - It's a sample, so its averages are noisy in the usual ways. Successive steps are correlated
567
+ over a hop time, h²/D for a cell h wide, so a cell's count averages to its expectation only
568
+ over many of those. And where the two-way traffic dwarfs the net (a dense species beside a
569
+ sea, a majority carrier anywhere), the net crossing is the small difference of two large
570
+ Poisson counts: its noise is √(up + down), not √(net).
571
+
572
+ ```js
573
+ import { Device, GAS_CONSTANT } from 'driftlet';
574
+ import { particles } from 'driftlet/kit';
575
+
576
+ // A species diffusing through a gel slab between two baths: c falls from 1 to 0.1 mol/m³.
577
+ const RT = GAS_CONSTANT * 298.15;
578
+ const sol = new Device({
579
+ species: [{ name: 'X', z: 0 }],
580
+ materials: { gel: { epsr: 0, species: { X: { D: 1e-9, mu0: 0, cRef: 1 } } } },
581
+ regions: [{ material: 'gel', length: 1e-4 }],
582
+ contacts: {
583
+ left: { phi: 'neutral', species: { X: { type: 'equilibrium', mu: 0 } } },
584
+ right: { phi: 'neutral', species: { X: { type: 'equilibrium', mu: RT * Math.log(0.1) } } },
585
+ },
586
+ }).solve();
587
+ const swarm = particles(sol, { dots: 300, cells: 20 });
588
+ for (let i = 0; i < 100; i++) swarm.step(0.01); // 1 s of the slab's time
589
+ console.log(`${swarm.dots.length} dots, each ${swarm.weight.X.toExponential(1)} mol/m²`);
590
+ ```
591
+
592
+ The dots are a friendly first picture for teaching. Pages that explain driftlet itself lead with
593
+ the fields, so the [pn demo](https://markblundeberg.github.io/driftlet/demos/pn.html) draws its
594
+ carriers this way at the bottom, on the band diagram, and the
595
+ [axon demo](https://markblundeberg.github.io/driftlet/demos/axon.html) the ions crossing its
596
+ membrane in each direction, both sides seas (`cap: 0`).
597
+
511
598
  ## Alignment from vacuum levels
512
599
 
513
600
  `vacuumLevel`, `vacuumDipole` and `vacuumZeroCharge` turn vacuum-level estimates (electron
@@ -1,7 +1,8 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 74" width="300" height="74" role="img" aria-label="driftlet">
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 269 74" width="269" height="74" role="img" aria-label="driftlet">
2
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
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
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
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>
6
+ <!-- "driftlet" in Recursive (Sans Casual, weight 700), as outlines -->
7
+ <path fill="#ece6da" d="M107.79 26.72Q109.2 26.72 110.37 27.22Q111.55 27.73 112.45 28.54Q113.34 29.35 113.84 30.31H114.79V34.87Q113.81 33.75 112.66 33.23Q111.5 32.71 109.91 32.71Q108.09 32.71 106.88 33.66Q105.68 34.62 105.09 36.5Q104.5 38.38 104.5 41.12V41.61Q104.5 42.92 104.55 43.82Q104.6 44.73 104.8 45.46Q105.07 45.69 105.35 45.78Q105.63 45.88 105.94 45.88Q107.82 45.88 109.38 45.05Q110.94 44.22 112.24 42.27Q113.54 40.32 114.56 37.08V45.16H113.8Q113.02 47.17 111.67 48.85Q110.33 50.53 108.66 51.54Q106.99 52.54 105.23 52.54Q103.84 52.54 102.55 51.8Q101.25 51.05 100.25 49.73Q99.24 48.41 98.66 46.69Q98.08 44.96 98.08 42.99V42.01Q98.08 37.95 98.9 35.04Q99.72 32.14 101.13 30.3Q102.54 28.46 104.27 27.59Q105.99 26.72 107.79 26.72ZM115.1 17.21Q116.47 17.21 117.66 17.87Q118.85 18.52 119.58 20.14Q120.31 21.76 120.31 24.65Q120.31 27.51 120.31 30.29Q120.31 33.07 120.31 35.8Q120.31 38.53 120.31 41.25Q120.31 43.97 120.31 46.71Q120.31 48.12 120.46 49.37Q120.61 50.62 121.05 51.97Q120.76 52.01 120.47 52.08Q120.18 52.14 119.92 52.2Q119.65 52.26 119.38 52.28Q119.11 52.3 118.78 52.3Q117.09 52.3 116.04 51.33Q114.99 50.36 114.54 48.77Q114.08 47.19 114.08 45.45Q114.08 42.04 114.08 39.14Q114.08 36.24 114.08 33.54Q114.08 30.83 114.08 28.01Q114.08 25.19 114.08 21.94Q114.08 20.72 113.89 19.52Q113.7 18.31 113.22 17.47Q113.47 17.42 113.7 17.38Q113.93 17.34 114.16 17.29Q114.39 17.25 114.62 17.23Q114.85 17.21 115.1 17.21Z M133.34 34.89H133.91Q134.64 32.17 135.54 30.26Q136.45 28.35 137.58 27.36Q138.72 26.37 140.11 26.37Q140.74 26.37 141.2 26.52Q141.66 26.66 142.05 27.05Q142.59 27.59 142.87 28.56Q143.16 29.54 143.16 31.32Q143.16 31.78 143.13 32.21Q143.11 32.65 143.07 33.09Q143.03 33.53 142.92 33.93Q142.82 34.33 142.68 34.7H142.41Q141.81 34.31 141.23 34.11Q140.65 33.91 139.81 33.91Q138.4 33.91 137.28 34.88Q136.17 35.84 135.21 37.9Q134.25 39.95 133.34 43.23ZM133.63 52.03Q133.27 52.07 132.98 52.11Q132.69 52.15 132.43 52.17Q132.16 52.19 131.83 52.19Q130.56 52.19 129.71 51.93Q128.85 51.67 128.34 51.16Q127.84 50.65 127.62 49.95Q127.4 49.26 127.4 48.38Q127.4 46.53 127.4 44.79Q127.4 43.05 127.4 41.37Q127.4 39.69 127.4 38.09Q127.4 36.49 127.4 34.89Q127.4 33.29 127.4 31.71Q127.4 30.49 127.21 29.28Q127.02 28.08 126.54 27.23Q126.88 27.15 127.19 27.11Q127.5 27.06 127.81 27.02Q128.13 26.98 128.42 26.98Q129.91 26.98 131.1 27.64Q132.29 28.3 132.96 29.92Q133.63 31.54 133.63 34.42Q133.63 36.23 133.63 38.09Q133.63 39.95 133.63 41.78Q133.63 43.6 133.63 45.37Q133.63 47.13 133.63 48.82Q133.63 50.5 133.63 52.03Z M150.21 46.91Q150.21 45.77 150.21 44.47Q150.21 43.17 150.21 41.8Q150.21 40.42 150.21 39.01Q150.21 37.6 150.21 36.22Q150.21 34.84 150.21 33.54L149.94 33.52Q149.74 33.66 149.51 33.8Q149.27 33.94 149.03 34.01Q148.8 34.09 148.52 34.14Q148.23 34.19 147.9 34.19Q146.88 34.19 146.31 33.22Q145.74 32.26 145.74 30.36Q145.74 29.54 145.85 28.73Q145.95 27.91 146.11 27.25Q146.59 27.16 147.03 27.09Q147.47 27.02 147.93 26.97Q148.38 26.93 148.85 26.88Q149.32 26.84 149.8 26.82Q150.28 26.79 150.78 26.79Q152.4 26.79 153.69 27.22Q154.98 27.65 155.71 28.54Q156.43 29.44 156.43 30.88Q156.43 32.63 156.43 34.54Q156.43 36.44 156.43 38.37Q156.43 40.3 156.43 42.17Q156.43 44.03 156.43 45.71Q156.43 46.92 156.5 47.86Q156.56 48.81 156.76 49.76Q156.96 50.72 157.31 52Q156.7 52.12 156.14 52.19Q155.59 52.25 154.93 52.25Q152.3 52.25 151.25 51.12Q150.21 49.98 150.21 46.91ZM148.77 17.37Q149.14 17.2 149.64 17.08Q150.13 16.96 150.67 16.86Q151.2 16.76 151.78 16.72Q152.36 16.68 152.9 16.68Q154.91 16.68 156.19 17.35Q157.48 18.02 157.48 19.74V22.92Q157.1 23.09 156.67 23.21Q156.23 23.33 155.76 23.43Q155.28 23.53 154.8 23.57Q154.32 23.61 153.82 23.61Q151.62 23.61 150.2 22.6Q148.77 21.6 148.77 19.92Z M160.64 27.25H172.57Q174.2 27.25 175.1 27.61Q176 27.96 176.38 28.76Q176.77 29.57 176.77 30.94Q176.77 31.62 176.69 32.29Q176.61 32.96 176.49 33.48H166.57Q164.21 33.48 162.91 32.97Q161.62 32.47 161.13 31.29Q160.64 30.11 160.64 28.07Q160.64 27.86 160.64 27.64Q160.64 27.42 160.64 27.25ZM170.59 48.26Q170.59 50.54 169.43 51.48Q168.27 52.42 166.04 52.42Q165.1 52.42 164.51 52.29Q163.93 52.16 163.51 52Q163.86 50.94 164.11 49.73Q164.36 48.53 164.36 47.11Q164.36 44.63 164.36 42.06Q164.36 39.49 164.36 36.92Q164.36 34.35 164.36 31.79Q164.36 29.22 164.36 26.73Q164.36 24.57 164.97 22.74Q165.59 20.91 166.76 19.56Q167.94 18.2 169.65 17.43Q171.37 16.66 173.57 16.66Q174.52 16.66 175.15 16.81Q175.79 16.95 176.2 17.21Q176.62 17.46 176.93 17.77Q177.51 18.31 177.72 19.19Q177.94 20.07 177.94 21.1Q177.94 21.64 177.88 22.24Q177.82 22.85 177.65 23.38H177.38Q176.92 23.16 176.23 22.98Q175.55 22.8 174.68 22.69Q173.81 22.57 172.78 22.57Q172.1 22.57 171.43 22.7Q170.76 22.83 170.07 23.04Q169.99 23.91 170.19 24.93Q170.39 25.96 170.59 26.97Q170.59 28.26 170.59 30.36Q170.59 32.47 170.59 34.97Q170.59 37.48 170.59 40Q170.59 42.52 170.59 44.69Q170.59 46.85 170.59 48.26Z M178.97 27.25H195.3Q197.05 27.25 197.69 28.06Q198.33 28.86 198.33 30.94Q198.33 31.38 198.29 31.84Q198.25 32.31 198.19 32.71Q198.13 33.11 198.06 33.48H185.93Q183.85 33.48 182.48 33.19Q181.11 32.89 180.3 32.27Q179.5 31.64 179.17 30.69Q178.85 29.73 178.85 28.38Q178.85 28.17 178.87 27.97Q178.89 27.76 178.91 27.57Q178.93 27.38 178.97 27.25ZM198.09 44.91Q198.19 45.33 198.23 45.74Q198.27 46.14 198.27 46.54Q198.27 48.08 197.95 49.2Q197.64 50.31 197.06 51.04Q196.74 51.44 196.28 51.74Q195.82 52.04 195.17 52.23Q194.51 52.42 193.69 52.51Q192.87 52.61 191.83 52.61Q190.1 52.61 188.6 52.04Q187.1 51.48 185.99 50.42Q184.87 49.36 184.26 47.9Q183.64 46.43 183.64 44.63Q183.64 42.07 183.64 39.51Q183.64 36.95 183.64 34.4Q183.64 31.86 183.64 29.3Q183.64 26.73 183.64 24.21Q183.64 23.1 183.54 22Q183.44 20.9 183.13 19.8Q183.86 19.59 184.57 19.49Q185.28 19.39 185.98 19.39Q187.06 19.39 187.94 19.84Q188.82 20.28 189.34 21.2Q189.87 22.12 189.87 23.55Q189.87 26.11 189.87 28.67Q189.87 31.22 189.87 33.77Q189.87 36.33 189.87 38.88Q189.87 41.43 189.87 44Q189.87 44.55 189.94 45.04Q190.01 45.53 190.16 45.96Q190.7 46.18 191.2 46.28Q191.71 46.38 192.51 46.38Q193.44 46.38 194.35 46.22Q195.26 46.07 196.13 45.74Q197 45.41 197.82 44.91Z M201.2 17.5Q201.73 17.41 202.28 17.32Q202.84 17.24 203.38 17.19Q203.93 17.14 204.43 17.11Q204.94 17.09 205.4 17.06Q205.86 17.04 206.27 17.04Q207.71 17.04 208.68 17.51Q209.64 17.97 210.13 18.87Q210.63 19.77 210.63 21.08Q210.63 23.24 210.63 25.36Q210.63 27.48 210.63 29.59Q210.63 31.69 210.63 33.81Q210.63 35.94 210.63 38.06Q210.63 40.18 210.63 42.34Q210.63 43.45 210.69 44.29Q210.75 45.12 210.95 45.78Q211.21 46 211.54 46.12Q211.88 46.23 212.4 46.23Q213.09 46.23 213.69 45.98Q214.29 45.72 214.74 45.21H215.02Q215.11 45.63 215.15 46.08Q215.19 46.53 215.19 47.24Q215.19 48.23 215.04 49.07Q214.89 49.91 214.63 50.52Q214.37 51.13 214.02 51.49Q213.67 51.85 213.15 52.08Q212.63 52.31 212.02 52.38Q211.42 52.46 210.75 52.46Q208.94 52.46 207.73 51.81Q206.52 51.16 205.78 50.07Q205.04 48.98 204.72 47.65Q204.4 46.33 204.4 44.98Q204.4 42.9 204.4 40.78Q204.4 38.67 204.4 36.52Q204.4 34.36 204.4 32.23Q204.4 30.09 204.4 27.96Q204.4 25.82 204.4 23.75L204.18 23.65Q203.96 23.83 203.65 23.98Q203.34 24.13 203.02 24.18Q202.71 24.24 202.48 24.24Q201.77 24.24 201.32 23.33Q200.87 22.42 200.87 20.17Q200.87 19.46 200.97 18.73Q201.08 18 201.2 17.5Z M230.77 26.56Q233.98 26.56 236.24 27.88Q238.5 29.2 239.72 31.52Q240.94 33.84 240.94 36.85V37.08Q240.94 38.86 240.72 39.77Q240.5 40.68 240.14 40.99Q239.77 41.29 239.31 41.29H229.03Q227.68 41.29 226.58 40.93Q225.49 40.56 224.83 39.95L223.86 40.4L224.07 37.57H234.66Q234.76 37.24 234.79 36.84Q234.83 36.44 234.83 36.13Q234.83 35.04 234.6 34.05Q234.37 33.07 233.78 32.25Q233.21 32.03 232.7 31.95Q232.19 31.87 231.24 31.87Q228.24 31.87 226.4 34.18Q224.56 36.49 224.56 41.22V41.45Q224.56 42.38 224.65 43.17Q224.73 43.96 224.94 44.65Q225.14 45.34 225.39 45.92Q226.51 46.48 227.9 46.73Q229.29 46.98 231.33 46.98Q232.86 46.98 234.28 46.77Q235.7 46.57 237.06 46.11Q238.42 45.65 239.65 44.94H239.92Q240.01 45.35 240.05 45.76Q240.09 46.16 240.09 46.57Q240.09 48.01 239.71 48.97Q239.32 49.93 238.6 50.65Q237.87 51.39 236.71 51.84Q235.56 52.29 234.18 52.49Q232.79 52.7 231.27 52.7Q228.42 52.7 226.04 51.81Q223.67 50.93 221.95 49.3Q220.23 47.67 219.28 45.35Q218.33 43.03 218.33 40.15V39.91Q218.33 36.96 219.25 34.53Q220.17 32.1 221.84 30.31Q223.51 28.52 225.79 27.54Q228.08 26.56 230.77 26.56Z M243.71 27.25H260.04Q261.79 27.25 262.43 28.06Q263.07 28.86 263.07 30.94Q263.07 31.38 263.03 31.84Q262.99 32.31 262.93 32.71Q262.87 33.11 262.8 33.48H250.67Q248.59 33.48 247.22 33.19Q245.85 32.89 245.04 32.27Q244.24 31.64 243.91 30.69Q243.59 29.73 243.59 28.38Q243.59 28.17 243.61 27.97Q243.63 27.76 243.65 27.57Q243.67 27.38 243.71 27.25ZM262.83 44.91Q262.93 45.33 262.97 45.74Q263.01 46.14 263.01 46.54Q263.01 48.08 262.69 49.2Q262.38 50.31 261.8 51.04Q261.48 51.44 261.02 51.74Q260.56 52.04 259.91 52.23Q259.25 52.42 258.43 52.51Q257.61 52.61 256.57 52.61Q254.84 52.61 253.34 52.04Q251.84 51.48 250.73 50.42Q249.61 49.36 249 47.9Q248.38 46.43 248.38 44.63Q248.38 42.07 248.38 39.51Q248.38 36.95 248.38 34.4Q248.38 31.86 248.38 29.3Q248.38 26.73 248.38 24.21Q248.38 23.1 248.28 22Q248.18 20.9 247.87 19.8Q248.6 19.59 249.31 19.49Q250.02 19.39 250.72 19.39Q251.8 19.39 252.68 19.84Q253.56 20.28 254.08 21.2Q254.61 22.12 254.61 23.55Q254.61 26.11 254.61 28.67Q254.61 31.22 254.61 33.77Q254.61 36.33 254.61 38.88Q254.61 41.43 254.61 44Q254.61 44.55 254.68 45.04Q254.75 45.53 254.9 45.96Q255.44 46.18 255.94 46.28Q256.45 46.38 257.25 46.38Q258.18 46.38 259.09 46.22Q260 46.07 260.87 45.74Q261.74 45.41 262.56 44.91Z"/>
7
8
  </svg>
package/docs/logo.svg CHANGED
@@ -1,7 +1,8 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 74" width="300" height="74" role="img" aria-label="driftlet">
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 269 74" width="269" height="74" role="img" aria-label="driftlet">
2
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
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
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
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>
6
+ <!-- "driftlet" in Recursive (Sans Casual, weight 700), as outlines -->
7
+ <path fill="#1d2330" d="M107.79 26.72Q109.2 26.72 110.37 27.22Q111.55 27.73 112.45 28.54Q113.34 29.35 113.84 30.31H114.79V34.87Q113.81 33.75 112.66 33.23Q111.5 32.71 109.91 32.71Q108.09 32.71 106.88 33.66Q105.68 34.62 105.09 36.5Q104.5 38.38 104.5 41.12V41.61Q104.5 42.92 104.55 43.82Q104.6 44.73 104.8 45.46Q105.07 45.69 105.35 45.78Q105.63 45.88 105.94 45.88Q107.82 45.88 109.38 45.05Q110.94 44.22 112.24 42.27Q113.54 40.32 114.56 37.08V45.16H113.8Q113.02 47.17 111.67 48.85Q110.33 50.53 108.66 51.54Q106.99 52.54 105.23 52.54Q103.84 52.54 102.55 51.8Q101.25 51.05 100.25 49.73Q99.24 48.41 98.66 46.69Q98.08 44.96 98.08 42.99V42.01Q98.08 37.95 98.9 35.04Q99.72 32.14 101.13 30.3Q102.54 28.46 104.27 27.59Q105.99 26.72 107.79 26.72ZM115.1 17.21Q116.47 17.21 117.66 17.87Q118.85 18.52 119.58 20.14Q120.31 21.76 120.31 24.65Q120.31 27.51 120.31 30.29Q120.31 33.07 120.31 35.8Q120.31 38.53 120.31 41.25Q120.31 43.97 120.31 46.71Q120.31 48.12 120.46 49.37Q120.61 50.62 121.05 51.97Q120.76 52.01 120.47 52.08Q120.18 52.14 119.92 52.2Q119.65 52.26 119.38 52.28Q119.11 52.3 118.78 52.3Q117.09 52.3 116.04 51.33Q114.99 50.36 114.54 48.77Q114.08 47.19 114.08 45.45Q114.08 42.04 114.08 39.14Q114.08 36.24 114.08 33.54Q114.08 30.83 114.08 28.01Q114.08 25.19 114.08 21.94Q114.08 20.72 113.89 19.52Q113.7 18.31 113.22 17.47Q113.47 17.42 113.7 17.38Q113.93 17.34 114.16 17.29Q114.39 17.25 114.62 17.23Q114.85 17.21 115.1 17.21Z M133.34 34.89H133.91Q134.64 32.17 135.54 30.26Q136.45 28.35 137.58 27.36Q138.72 26.37 140.11 26.37Q140.74 26.37 141.2 26.52Q141.66 26.66 142.05 27.05Q142.59 27.59 142.87 28.56Q143.16 29.54 143.16 31.32Q143.16 31.78 143.13 32.21Q143.11 32.65 143.07 33.09Q143.03 33.53 142.92 33.93Q142.82 34.33 142.68 34.7H142.41Q141.81 34.31 141.23 34.11Q140.65 33.91 139.81 33.91Q138.4 33.91 137.28 34.88Q136.17 35.84 135.21 37.9Q134.25 39.95 133.34 43.23ZM133.63 52.03Q133.27 52.07 132.98 52.11Q132.69 52.15 132.43 52.17Q132.16 52.19 131.83 52.19Q130.56 52.19 129.71 51.93Q128.85 51.67 128.34 51.16Q127.84 50.65 127.62 49.95Q127.4 49.26 127.4 48.38Q127.4 46.53 127.4 44.79Q127.4 43.05 127.4 41.37Q127.4 39.69 127.4 38.09Q127.4 36.49 127.4 34.89Q127.4 33.29 127.4 31.71Q127.4 30.49 127.21 29.28Q127.02 28.08 126.54 27.23Q126.88 27.15 127.19 27.11Q127.5 27.06 127.81 27.02Q128.13 26.98 128.42 26.98Q129.91 26.98 131.1 27.64Q132.29 28.3 132.96 29.92Q133.63 31.54 133.63 34.42Q133.63 36.23 133.63 38.09Q133.63 39.95 133.63 41.78Q133.63 43.6 133.63 45.37Q133.63 47.13 133.63 48.82Q133.63 50.5 133.63 52.03Z M150.21 46.91Q150.21 45.77 150.21 44.47Q150.21 43.17 150.21 41.8Q150.21 40.42 150.21 39.01Q150.21 37.6 150.21 36.22Q150.21 34.84 150.21 33.54L149.94 33.52Q149.74 33.66 149.51 33.8Q149.27 33.94 149.03 34.01Q148.8 34.09 148.52 34.14Q148.23 34.19 147.9 34.19Q146.88 34.19 146.31 33.22Q145.74 32.26 145.74 30.36Q145.74 29.54 145.85 28.73Q145.95 27.91 146.11 27.25Q146.59 27.16 147.03 27.09Q147.47 27.02 147.93 26.97Q148.38 26.93 148.85 26.88Q149.32 26.84 149.8 26.82Q150.28 26.79 150.78 26.79Q152.4 26.79 153.69 27.22Q154.98 27.65 155.71 28.54Q156.43 29.44 156.43 30.88Q156.43 32.63 156.43 34.54Q156.43 36.44 156.43 38.37Q156.43 40.3 156.43 42.17Q156.43 44.03 156.43 45.71Q156.43 46.92 156.5 47.86Q156.56 48.81 156.76 49.76Q156.96 50.72 157.31 52Q156.7 52.12 156.14 52.19Q155.59 52.25 154.93 52.25Q152.3 52.25 151.25 51.12Q150.21 49.98 150.21 46.91ZM148.77 17.37Q149.14 17.2 149.64 17.08Q150.13 16.96 150.67 16.86Q151.2 16.76 151.78 16.72Q152.36 16.68 152.9 16.68Q154.91 16.68 156.19 17.35Q157.48 18.02 157.48 19.74V22.92Q157.1 23.09 156.67 23.21Q156.23 23.33 155.76 23.43Q155.28 23.53 154.8 23.57Q154.32 23.61 153.82 23.61Q151.62 23.61 150.2 22.6Q148.77 21.6 148.77 19.92Z M160.64 27.25H172.57Q174.2 27.25 175.1 27.61Q176 27.96 176.38 28.76Q176.77 29.57 176.77 30.94Q176.77 31.62 176.69 32.29Q176.61 32.96 176.49 33.48H166.57Q164.21 33.48 162.91 32.97Q161.62 32.47 161.13 31.29Q160.64 30.11 160.64 28.07Q160.64 27.86 160.64 27.64Q160.64 27.42 160.64 27.25ZM170.59 48.26Q170.59 50.54 169.43 51.48Q168.27 52.42 166.04 52.42Q165.1 52.42 164.51 52.29Q163.93 52.16 163.51 52Q163.86 50.94 164.11 49.73Q164.36 48.53 164.36 47.11Q164.36 44.63 164.36 42.06Q164.36 39.49 164.36 36.92Q164.36 34.35 164.36 31.79Q164.36 29.22 164.36 26.73Q164.36 24.57 164.97 22.74Q165.59 20.91 166.76 19.56Q167.94 18.2 169.65 17.43Q171.37 16.66 173.57 16.66Q174.52 16.66 175.15 16.81Q175.79 16.95 176.2 17.21Q176.62 17.46 176.93 17.77Q177.51 18.31 177.72 19.19Q177.94 20.07 177.94 21.1Q177.94 21.64 177.88 22.24Q177.82 22.85 177.65 23.38H177.38Q176.92 23.16 176.23 22.98Q175.55 22.8 174.68 22.69Q173.81 22.57 172.78 22.57Q172.1 22.57 171.43 22.7Q170.76 22.83 170.07 23.04Q169.99 23.91 170.19 24.93Q170.39 25.96 170.59 26.97Q170.59 28.26 170.59 30.36Q170.59 32.47 170.59 34.97Q170.59 37.48 170.59 40Q170.59 42.52 170.59 44.69Q170.59 46.85 170.59 48.26Z M178.97 27.25H195.3Q197.05 27.25 197.69 28.06Q198.33 28.86 198.33 30.94Q198.33 31.38 198.29 31.84Q198.25 32.31 198.19 32.71Q198.13 33.11 198.06 33.48H185.93Q183.85 33.48 182.48 33.19Q181.11 32.89 180.3 32.27Q179.5 31.64 179.17 30.69Q178.85 29.73 178.85 28.38Q178.85 28.17 178.87 27.97Q178.89 27.76 178.91 27.57Q178.93 27.38 178.97 27.25ZM198.09 44.91Q198.19 45.33 198.23 45.74Q198.27 46.14 198.27 46.54Q198.27 48.08 197.95 49.2Q197.64 50.31 197.06 51.04Q196.74 51.44 196.28 51.74Q195.82 52.04 195.17 52.23Q194.51 52.42 193.69 52.51Q192.87 52.61 191.83 52.61Q190.1 52.61 188.6 52.04Q187.1 51.48 185.99 50.42Q184.87 49.36 184.26 47.9Q183.64 46.43 183.64 44.63Q183.64 42.07 183.64 39.51Q183.64 36.95 183.64 34.4Q183.64 31.86 183.64 29.3Q183.64 26.73 183.64 24.21Q183.64 23.1 183.54 22Q183.44 20.9 183.13 19.8Q183.86 19.59 184.57 19.49Q185.28 19.39 185.98 19.39Q187.06 19.39 187.94 19.84Q188.82 20.28 189.34 21.2Q189.87 22.12 189.87 23.55Q189.87 26.11 189.87 28.67Q189.87 31.22 189.87 33.77Q189.87 36.33 189.87 38.88Q189.87 41.43 189.87 44Q189.87 44.55 189.94 45.04Q190.01 45.53 190.16 45.96Q190.7 46.18 191.2 46.28Q191.71 46.38 192.51 46.38Q193.44 46.38 194.35 46.22Q195.26 46.07 196.13 45.74Q197 45.41 197.82 44.91Z M201.2 17.5Q201.73 17.41 202.28 17.32Q202.84 17.24 203.38 17.19Q203.93 17.14 204.43 17.11Q204.94 17.09 205.4 17.06Q205.86 17.04 206.27 17.04Q207.71 17.04 208.68 17.51Q209.64 17.97 210.13 18.87Q210.63 19.77 210.63 21.08Q210.63 23.24 210.63 25.36Q210.63 27.48 210.63 29.59Q210.63 31.69 210.63 33.81Q210.63 35.94 210.63 38.06Q210.63 40.18 210.63 42.34Q210.63 43.45 210.69 44.29Q210.75 45.12 210.95 45.78Q211.21 46 211.54 46.12Q211.88 46.23 212.4 46.23Q213.09 46.23 213.69 45.98Q214.29 45.72 214.74 45.21H215.02Q215.11 45.63 215.15 46.08Q215.19 46.53 215.19 47.24Q215.19 48.23 215.04 49.07Q214.89 49.91 214.63 50.52Q214.37 51.13 214.02 51.49Q213.67 51.85 213.15 52.08Q212.63 52.31 212.02 52.38Q211.42 52.46 210.75 52.46Q208.94 52.46 207.73 51.81Q206.52 51.16 205.78 50.07Q205.04 48.98 204.72 47.65Q204.4 46.33 204.4 44.98Q204.4 42.9 204.4 40.78Q204.4 38.67 204.4 36.52Q204.4 34.36 204.4 32.23Q204.4 30.09 204.4 27.96Q204.4 25.82 204.4 23.75L204.18 23.65Q203.96 23.83 203.65 23.98Q203.34 24.13 203.02 24.18Q202.71 24.24 202.48 24.24Q201.77 24.24 201.32 23.33Q200.87 22.42 200.87 20.17Q200.87 19.46 200.97 18.73Q201.08 18 201.2 17.5Z M230.77 26.56Q233.98 26.56 236.24 27.88Q238.5 29.2 239.72 31.52Q240.94 33.84 240.94 36.85V37.08Q240.94 38.86 240.72 39.77Q240.5 40.68 240.14 40.99Q239.77 41.29 239.31 41.29H229.03Q227.68 41.29 226.58 40.93Q225.49 40.56 224.83 39.95L223.86 40.4L224.07 37.57H234.66Q234.76 37.24 234.79 36.84Q234.83 36.44 234.83 36.13Q234.83 35.04 234.6 34.05Q234.37 33.07 233.78 32.25Q233.21 32.03 232.7 31.95Q232.19 31.87 231.24 31.87Q228.24 31.87 226.4 34.18Q224.56 36.49 224.56 41.22V41.45Q224.56 42.38 224.65 43.17Q224.73 43.96 224.94 44.65Q225.14 45.34 225.39 45.92Q226.51 46.48 227.9 46.73Q229.29 46.98 231.33 46.98Q232.86 46.98 234.28 46.77Q235.7 46.57 237.06 46.11Q238.42 45.65 239.65 44.94H239.92Q240.01 45.35 240.05 45.76Q240.09 46.16 240.09 46.57Q240.09 48.01 239.71 48.97Q239.32 49.93 238.6 50.65Q237.87 51.39 236.71 51.84Q235.56 52.29 234.18 52.49Q232.79 52.7 231.27 52.7Q228.42 52.7 226.04 51.81Q223.67 50.93 221.95 49.3Q220.23 47.67 219.28 45.35Q218.33 43.03 218.33 40.15V39.91Q218.33 36.96 219.25 34.53Q220.17 32.1 221.84 30.31Q223.51 28.52 225.79 27.54Q228.08 26.56 230.77 26.56Z M243.71 27.25H260.04Q261.79 27.25 262.43 28.06Q263.07 28.86 263.07 30.94Q263.07 31.38 263.03 31.84Q262.99 32.31 262.93 32.71Q262.87 33.11 262.8 33.48H250.67Q248.59 33.48 247.22 33.19Q245.85 32.89 245.04 32.27Q244.24 31.64 243.91 30.69Q243.59 29.73 243.59 28.38Q243.59 28.17 243.61 27.97Q243.63 27.76 243.65 27.57Q243.67 27.38 243.71 27.25ZM262.83 44.91Q262.93 45.33 262.97 45.74Q263.01 46.14 263.01 46.54Q263.01 48.08 262.69 49.2Q262.38 50.31 261.8 51.04Q261.48 51.44 261.02 51.74Q260.56 52.04 259.91 52.23Q259.25 52.42 258.43 52.51Q257.61 52.61 256.57 52.61Q254.84 52.61 253.34 52.04Q251.84 51.48 250.73 50.42Q249.61 49.36 249 47.9Q248.38 46.43 248.38 44.63Q248.38 42.07 248.38 39.51Q248.38 36.95 248.38 34.4Q248.38 31.86 248.38 29.3Q248.38 26.73 248.38 24.21Q248.38 23.1 248.28 22Q248.18 20.9 247.87 19.8Q248.6 19.59 249.31 19.49Q250.02 19.39 250.72 19.39Q251.8 19.39 252.68 19.84Q253.56 20.28 254.08 21.2Q254.61 22.12 254.61 23.55Q254.61 26.11 254.61 28.67Q254.61 31.22 254.61 33.77Q254.61 36.33 254.61 38.88Q254.61 41.43 254.61 44Q254.61 44.55 254.68 45.04Q254.75 45.53 254.9 45.96Q255.44 46.18 255.94 46.28Q256.45 46.38 257.25 46.38Q258.18 46.38 259.09 46.22Q260 46.07 260.87 45.74Q261.74 45.41 262.56 44.91Z"/>
7
8
  </svg>