@ascentsparksoftware/angular-image-editor 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ascentspark Software Private Limited
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,406 @@
1
+ <div align="center">
2
+
3
+ <a href="https://github.com/ascentspark/angular-image-editor"><img src="image-editor-logo.png" alt="Angular Image Editor" height="120"></a>
4
+
5
+ # @ascentsparksoftware/angular-image-editor
6
+
7
+ A standalone, themeable **Angular 22** image editor β€” crop, filter, draw, redact, layers and
8
+ **in-browser AI background removal** β€” built from scratch on **Fabric.js v7**.
9
+
10
+ by&nbsp;<a href="https://ascentspark.com" target="_blank" rel="noopener"><img src="https://cdn.ascentspark.com/assets/images/asc-logo-full.svg" alt="Ascentspark" height="22" valign="middle"></a>
11
+
12
+ [![npm version](https://img.shields.io/npm/v/@ascentsparksoftware/angular-image-editor.svg?color=dd0031)](https://www.npmjs.com/package/@ascentsparksoftware/angular-image-editor)
13
+ [![downloads](https://img.shields.io/npm/dm/@ascentsparksoftware/angular-image-editor.svg)](https://www.npmjs.com/package/@ascentsparksoftware/angular-image-editor)
14
+ [![Angular 22](https://img.shields.io/badge/Angular-22-dd0031.svg)](https://angular.dev)
15
+ [![license MIT](https://img.shields.io/github/license/ascentspark/angular-image-editor?color=3b82f6)](https://github.com/ascentspark/angular-image-editor/blob/main/LICENSE)
16
+
17
+ ### **[πŸš€ Live demo &amp; docs β†’](https://angular-image-editor.ascentspark.com)**
18
+
19
+ **[✨ Features](#features)** &nbsp;·&nbsp;
20
+ **[🎨 Theming](#theming)** &nbsp;·&nbsp;
21
+ **[🧩 API](#api)** &nbsp;·&nbsp;
22
+ **[πŸ”’ Security](#security)** &nbsp;Β·&nbsp;
23
+ **[πŸ—ΊοΈ Roadmap](#-roadmap)**
24
+
25
+ </div>
26
+
27
+ ---
28
+
29
+ A full image editor you drop into an Angular app as a standalone component: crop, rotate, filters,
30
+ draw, text, shapes, freehand redaction, layers, and export to PNG / JPEG / WEBP / SVG / PDF / JSON.
31
+ Put `<asp-image-editor>` on the page, bind your image and options as signal inputs, and read results
32
+ back as a `Blob`. It targets **Angular 22**, is signal-driven, standalone, `OnPush` and
33
+ zoneless-compatible.
34
+
35
+ It is built from scratch on **[Fabric.js v7](https://fabricjs.com/) (MIT)** β€” a free, open-source
36
+ alternative to commercial editors such as Syncfusion, Pintura and Filerobot. No license keys, no
37
+ telemetry, no per-seat pricing. Fabric is the only runtime dependency and it is lazy-loaded, so it
38
+ never weighs down your initial bundle.
39
+
40
+ > ## πŸ†“ In-browser AI background removal, free
41
+ >
42
+ > One-click background removal is a paid feature in most commercial editors. Here it runs **on-device
43
+ > for free** (MIT): the **Remove background** and **Cut out subject** tools lazy-load an ONNX model
44
+ > and run it entirely in the browser β€” no image ever leaves the page, no API key, no per-call cost.
45
+ > The model packages are `optionalDependencies`, so if you don't install them the rest of the editor
46
+ > is unaffected and the AI tools simply don't appear. There's also a dependency-free **Magic wand**
47
+ > (flood-fill erase) for clearing flat color regions by click. See [Smart & AI tools](#smart--ai-tools).
48
+
49
+ ## Features
50
+
51
+ - **Four modes** β€” `viewer` Β· `basic` Β· `advanced` Β· `full`, each with sensible default chrome and
52
+ tools.
53
+ - **Editing** β€” crop (preset + custom CMS aspect ratios), rotate / straighten, flip, zoom / **pan**,
54
+ fine-tune adjustments (brightness / contrast / saturation / vibrance / hue / blur / …), filter
55
+ looks (B&W / sepia / invert / sharpen / tint), draw / pen, highlighter, **eraser**, shapes, arrows,
56
+ **text with web fonts**, freehand **redaction** (solid / blur / pixelate), frames, **background
57
+ color / gradient**, and undo / redo history.
58
+ - **Smart & AI tools** β€” **Magic wand** flood-fill erase (no dependencies), plus in-browser AI
59
+ **Remove background** and **Cut out subject**.
60
+ - **Layers panel** (persistent companion) β€” drag-to-reorder z-order, per-layer **lock**, show / hide,
61
+ **opacity**, inline **rename**, plus the object-ops on the selection: **group / ungroup**,
62
+ **align**, **duplicate**, delete, and multi-select (shift / ⌘ / ctrl). Available regardless of the
63
+ active tool.
64
+ - **Multiple images** β€” replace the canvas image or **add an image as its own layer** to composite.
65
+ - **Precision** β€” optional **rulers** with draggable, snapping **guides**, **snapping + alignment
66
+ guides** (magnet toggle), and an **artboard / output size** (presets + custom WΓ—H) for exact-pixel
67
+ raster and PDF export.
68
+ - **Export** β€” PNG / JPEG / WEBP / SVG / **PDF** (lazy `jspdf`) / JSON, plus engine
69
+ `exportScene` / `loadScene` for save-and-reload templates.
70
+ - **Usage-based UI** β€” a grouped, Photoshop-style **flyout toolbar** (tools double as modes), a tool
71
+ **Options** panel that adapts to the selection, and top-bar **actions** (undo / redo, zoom, fit,
72
+ history, image, export).
73
+ - **Color anywhere** β€” preset swatches **plus a custom color picker** for fill, draw, text, shapes,
74
+ frame and background.
75
+ - **Web fonts** β€” a curated Google-font list plus an "add any Google font" search.
76
+ - **Correct compositing** β€” the highlighter is genuinely translucent; **redact** bakes the
77
+ *composited* pixels under the region, concealing everything beneath, not just the base image.
78
+ - **Responsive** β€” the editor adapts to **its own width** via CSS container queries (single column on
79
+ narrow hosts), so it works in a sidebar, a modal or full-page without horizontal overflow.
80
+ - **3-input runtime theming** β€” `baseColor` + `accentColor` + `themeMode` derive the whole palette as
81
+ scoped `--asp-*` CSS variables, with guaranteed WCAG **AA** contrast.
82
+ - **Robust & accessible** β€” oversized imports auto-downscale; recoverable failures surface via an
83
+ `errorOccurred` event instead of throwing; keyboard-operable, `:focus-visible` rings,
84
+ `prefers-reduced-motion`, Trusted-Types-safe. No `any` anywhere.
85
+
86
+ ## Install
87
+
88
+ ```bash
89
+ npm install @ascentsparksoftware/angular-image-editor fabric
90
+ ```
91
+
92
+ - **Peer dependencies:** `@angular/core` and `@angular/common` `^22`.
93
+ - **Runtime dependency:** `fabric` `^7.4.0` (declared by the package; install it alongside). Fabric is
94
+ lazy-loaded on demand, so it never weighs down your initial bundle.
95
+ - **Optional** (only if you want the AI tools): `@imgly/background-removal` and `onnxruntime-web`.
96
+ Without them, **Remove background** and **Cut out subject** are simply not shown.
97
+
98
+ ## Quick start
99
+
100
+ `AspImageEditor` is a standalone component β€” import it directly:
101
+
102
+ ```ts
103
+ import { Component } from '@angular/core';
104
+ import { AspImageEditor } from '@ascentsparksoftware/angular-image-editor';
105
+
106
+ @Component({
107
+ selector: 'app-photo',
108
+ imports: [AspImageEditor],
109
+ template: `
110
+ <asp-image-editor
111
+ [src]="imageUrl"
112
+ mode="advanced"
113
+ [baseColor]="'#f4f6f9'"
114
+ [accentColor]="'#02375e'"
115
+ [themeMode]="isDark ? 'dark' : 'light'"
116
+ (saved)="onSaved($event)">
117
+ </asp-image-editor>
118
+ `,
119
+ })
120
+ export class PhotoComponent {
121
+ imageUrl = 'https://example.com/photo.jpg';
122
+ isDark = false;
123
+ onSaved(blob: Blob): void {
124
+ /* upload or preview the edited image */
125
+ }
126
+ }
127
+ ```
128
+
129
+ Give the host a size β€” the editor fills its container. Either set the `width` / `height` inputs, or
130
+ size the host in CSS:
131
+
132
+ ```css
133
+ asp-image-editor { display: block; height: 640px; }
134
+ ```
135
+
136
+ ```html
137
+ <!-- inputs accept px, %, vh, or any CSS length / calc() -->
138
+ <asp-image-editor [src]="url" width="100%" height="70vh"></asp-image-editor>
139
+ ```
140
+
141
+ ## Modes
142
+
143
+ `mode` sets the chrome and a sensible default tool set:
144
+
145
+ | mode | chrome | default tools |
146
+ | --- | --- | --- |
147
+ | `viewer` | minimal: zoom + export | none (read-only) |
148
+ | `basic` | a compact card (also openable as a modal) | crop, rotate, flip, zoom |
149
+ | `advanced` *(default)* | full 3-column workspace | curated everyday set |
150
+ | `full` | full workspace | every tool + every filter |
151
+
152
+ ```html
153
+ <asp-image-editor mode="viewer" [src]="url"></asp-image-editor>
154
+ <asp-image-editor mode="full" [src]="url"></asp-image-editor>
155
+ ```
156
+
157
+ ## Tool configuration
158
+
159
+ Resolution order, from easiest to most precise:
160
+
161
+ 1. **`mode`** sets the default tool set.
162
+ 2. **`tools`** (if provided) is an explicit allowlist that *replaces* the mode default, in the order
163
+ you give.
164
+ 3. **`disabledTools`** is then subtracted from the result.
165
+
166
+ ```html
167
+ <!-- advanced, but without filters or stickers -->
168
+ <asp-image-editor mode="advanced" [disabledTools]="['filters', 'sticker']" />
169
+
170
+ <!-- a bespoke rail (order preserved) -->
171
+ <asp-image-editor [tools]="['crop', 'rotate', 'text']" />
172
+ ```
173
+
174
+ Filters follow the same idea via `filters`: an explicit `AspFilter[]`, the literal `'all'` (every
175
+ Fabric filter), or `null` for the mode default.
176
+
177
+ ```html
178
+ <asp-image-editor mode="advanced" [filters]="['brightness', 'contrast', 'grayscale']" />
179
+ <asp-image-editor mode="advanced" [filters]="'all'" />
180
+ ```
181
+
182
+ ## Smart & AI tools
183
+
184
+ | Tool | What it does | Dependency |
185
+ | --- | --- | --- |
186
+ | **Magic wand** (`magicwand`) | Click a flat color region to erase it to transparency; tolerance slider. | none |
187
+ | **Remove background** (`removebg`) | In-browser AI that replaces the base image's background with transparency. | optional |
188
+ | **Cut out subject** (`selectsubject`) | In-browser AI that extracts the subject onto its own layer. | optional |
189
+
190
+ The AI tools lazy-load `@imgly/background-removal`, which fetches and caches an ONNX model at runtime
191
+ and runs it on-device β€” no image data leaves the browser. A progress bar and busy cursor show while
192
+ the model loads. Install the optional dependencies to enable them; omit them to ship a smaller bundle
193
+ without these two tools. The magic wand has no dependencies and works everywhere.
194
+
195
+ ## Theming
196
+
197
+ Three inputs derive the **entire** UI palette at runtime β€” set them and the editor blends into your
198
+ brand in light or dark, with WCAG **AA** text contrast guaranteed (AAA for primary text), no extra
199
+ config:
200
+
201
+ - **`baseColor`** β€” neutral anchor; surfaces, ink and borders are tinted toward its hue.
202
+ - **`accentColor`** β€” interactive accent; buttons, active tool, focus ring, selection.
203
+ - **`themeMode`** β€” `'light'` or `'dark'`.
204
+
205
+ The derived values are applied as scoped CSS custom properties on the editor root, so they never
206
+ clash with your page. For fine control you can override any individual variable:
207
+
208
+ ```css
209
+ asp-image-editor {
210
+ --asp-radius-md: 12px;
211
+ --asp-accent: #ff5a5f;
212
+ }
213
+ ```
214
+
215
+ <details><summary>All <code>--asp-*</code> tokens</summary>
216
+
217
+ | token | role |
218
+ | --- | --- |
219
+ | `--asp-bg` | app/backdrop base |
220
+ | `--asp-surface` | card / panel surface |
221
+ | `--asp-surface-2` | inset surface (inputs) |
222
+ | `--asp-surface-sunk` | sunk wells / hover, checkerboard cell |
223
+ | `--asp-ink` | primary text |
224
+ | `--asp-ink-700` | secondary text / icons |
225
+ | `--asp-ink-muted` | muted labels |
226
+ | `--asp-ink-faint` | disabled / faint |
227
+ | `--asp-line` | hairline borders |
228
+ | `--asp-line-strong` | control borders |
229
+ | `--asp-accent` | accent fill |
230
+ | `--asp-accent-ink` | on-accent text (AA) |
231
+ | `--asp-accent-hover` | accent hover |
232
+ | `--asp-accent-soft` | active-tool background |
233
+ | `--asp-accent-soft-ink` | text on soft accent (AA) |
234
+ | `--asp-ring` | focus ring |
235
+ | `--asp-scrim` | modal scrim |
236
+ | `--asp-success` / `--asp-warning` / `--asp-error` | status colors |
237
+ | `--asp-radius-sm` / `-md` / `-lg` / `-pill` | radii |
238
+ | `--asp-ctl-h` / `--asp-ctl-h-sm` | control heights |
239
+ | `--asp-font-mono` | numeric readouts |
240
+
241
+ You can also call the pure helper directly: `deriveTheme(baseColor, accentColor, mode)` returns the
242
+ full token map, and `applyTheme(element, tokens)` sets them.
243
+ </details>
244
+
245
+ ## Modal dialog
246
+
247
+ For avatar / quick-edit flows, open the `basic` editor as a modal and await the result:
248
+
249
+ ```ts
250
+ import { Component, inject } from '@angular/core';
251
+ import { AspImageEditorDialog } from '@ascentsparksoftware/angular-image-editor';
252
+
253
+ @Component({ /* ... */ })
254
+ export class AvatarComponent {
255
+ private readonly editor = inject(AspImageEditorDialog);
256
+
257
+ async edit(src: string): Promise<void> {
258
+ const blob = await this.editor.open({
259
+ src,
260
+ heading: 'Update profile photo',
261
+ aspectPresets: ['1:1', '4:3', 'free'],
262
+ });
263
+ if (blob) {
264
+ /* user saved β€” upload `blob` */
265
+ } // else: user cancelled (close, scrim click, or Escape)
266
+ }
267
+ }
268
+ ```
269
+
270
+ ## API
271
+
272
+ ```ts
273
+ @Component({ selector: 'asp-image-editor', standalone: true })
274
+ class AspImageEditor {
275
+ src = input<string | Blob | null>(null);
276
+ mode = input<AspMode>('advanced'); // 'viewer'|'basic'|'advanced'|'full'
277
+ width = input<AspSize | null>(null); // px | % | vh | any CSS length / calc()
278
+ height = input<AspSize | null>(null);
279
+ tools = input<AspTool[] | null>(null); // explicit allowlist
280
+ disabledTools = input<AspTool[]>([]); // subtracted from the resolved set
281
+ filters = input<AspFilter[] | 'all' | null>(null);
282
+ aspectPresets = input<AspAspectPreset[]>(['free', '1:1', '4:3', '16:9']);
283
+ aspectRatios = input<AspAspectOption[]>([]); // custom CMS targets, e.g. aspectOption(1200,630)
284
+ exportFormats = input<AspExportFormat[]>(['png', 'jpeg', 'webp']); // + 'svg' | 'pdf' | 'json'
285
+ exportQuality = input<number>(90); // 10–100
286
+ heading = input<string>('Edit image'); // basic-mode title
287
+ showHistory = input<boolean>(true); // show the history panel
288
+ keyboardEnabled = input<boolean>(true); // editor keyboard shortcuts
289
+ fonts = input<FontOption[]>(DEFAULT_FONTS); // text font choices
290
+
291
+ baseColor = input<string>('#f4f6f9');
292
+ accentColor = input<string>('#1f6feb');
293
+ themeMode = input<'light' | 'dark'>('light');
294
+
295
+ saved = output<Blob>(); // export / Save
296
+ canceled = output<void>(); // basic Cancel
297
+ imageLoaded = output<void>(); // an image finished loading
298
+ exported = output<Blob>(); // Export download produced a Blob
299
+ errorOccurred = output<AspEditorError>(); // recoverable load/export/init error
300
+ }
301
+ ```
302
+
303
+ ### Keyboard shortcuts
304
+
305
+ While the pointer is over the editor (and not typing in a field): **Ctrl/Cmd+Z** undo,
306
+ **Ctrl/Cmd+Shift+Z / Ctrl+Y** redo, **Delete/Backspace** remove selection, **Ctrl/Cmd+C / V / D**
307
+ copy / paste / duplicate, **Ctrl/Cmd+A** select all, **Esc** deselect (or cancel the basic modal),
308
+ **Space** (hold) to pan. Disable with `[keyboardEnabled]="false"`.
309
+
310
+ ### Headless engine
311
+
312
+ For headless or advanced use, `EditorEngine` (the Fabric wrapper) and the pure helpers
313
+ (`resolveTools`, `resolveFilters`, `deriveTheme`, `applyTheme`, `EditHistory`, `DeltaHistory`) are all
314
+ exported. `EditorEngine` exposes scene save/load (`exportScene` / `loadScene`), layers, guides,
315
+ artboard sizing and the AI/magic operations directly.
316
+
317
+ ## Accessibility
318
+
319
+ WCAG AA color contrast (derivation-guaranteed), keyboard-operable controls, `:focus-visible` rings,
320
+ `prefers-reduced-motion` honored, and Trusted-Types-safe icon rendering. Run `axe` against the demo
321
+ to verify in your own setup.
322
+
323
+ ## Security
324
+
325
+ This library renders consumer-supplied images and SVGs onto an HTML canvas; SVGs are rasterized
326
+ through a sandboxed `<img>` element, so scripts inside an SVG do not execute. Still treat
327
+ user-uploaded files as untrusted: validate type and size at your upload boundary, and sanitize any
328
+ text you overlay from untrusted sources. Exported blobs and PDFs reflect exactly what was drawn. The
329
+ optional AI tools download a model at runtime but never send your image data anywhere. Full policy and
330
+ private reporting: [SECURITY.md](https://github.com/ascentspark/angular-image-editor/blob/main/SECURITY.md).
331
+
332
+ ## πŸ—ΊοΈ Roadmap
333
+
334
+ > 🚧 **Work in progress for upcoming releases.** Everything below is built on **free** Fabric.js v7 β€”
335
+ > no paid SDKs, no commercial extensions β€” there are no firm dates yet, and pull requests are very
336
+ > welcome. The north star is the essential Photoshop / Photopea toolset, delivered the modern-Angular
337
+ > way (signals, standalone, zoneless) and free.
338
+
339
+ **Selection & masking**
340
+
341
+ | | Tool | What's coming |
342
+ | :-: | --- | --- |
343
+ | 🟦 | **Marquee & lasso selection** | Rectangular, elliptical and freehand selections that become a reusable mask β€” fill it, delete it, or confine a filter / adjustment to just the selected pixels. Complements the existing flood-fill magic wand. |
344
+ | 🎭 | **Layer masks** | Non-destructive per-layer masking (Fabric `clipPath`), painted with a brush or a gradient, so you can hide and reveal without touching the pixels underneath. |
345
+
346
+ **Retouch & paint**
347
+
348
+ | | Tool | What's coming |
349
+ | :-: | --- | --- |
350
+ | 🩹 | **Clone stamp & healing brush** | Sample pixels from one area and paint them over another to remove blemishes, objects or backgrounds; spot-heal blends the surrounding texture automatically. |
351
+ | πŸͺ£ | **Paint bucket & eyedropper** | Flood-fill a region with a solid color or pattern, and sample any on-canvas color into the active swatch. |
352
+ | πŸŒ— | **Dodge, burn & smudge** | Brush-based local retouching β€” lighten, darken, saturate, or push pixels around for quick corrections. |
353
+
354
+ **Color & tone**
355
+
356
+ | | Tool | What's coming |
357
+ | :-: | --- | --- |
358
+ | πŸ“ˆ | **Curves & levels** | Histogram-driven tonal control with per-channel curves and black / white / gamma points, alongside today's slider adjustments. |
359
+ | πŸ§ͺ | **Blend modes** | Per-layer compositing (multiply, screen, overlay, soft-light, …) via canvas composite operations, for the standard layer-stack looks. |
360
+ | 🧱 | **Adjustment layers** | Stackable, re-editable color and tone adjustments that affect the layers beneath them, instead of baking changes into a single image. |
361
+
362
+ **Vector, type & transform**
363
+
364
+ | | Tool | What's coming |
365
+ | :-: | --- | --- |
366
+ | βœ’οΈ | **BΓ©zier pen & boolean shapes** | A true vector pen with editable nodes for precise paths, plus union / subtract / intersect on shapes to build compound vectors. |
367
+ | πŸ”€ | **Text on a path & rich type** | Curve text along a path, with letter-spacing, line-height and OpenType controls on top of the current web-font picker. |
368
+ | πŸ”² | **Free transform** | Skew, distort, perspective and warp, on top of the existing move / scale / rotate. |
369
+ | πŸ™‚ | **Sticker & emoji library** | Finish the declared `sticker` tool with a searchable emoji / shape / badge library you can drop onto the canvas. |
370
+
371
+ Following the same approach as the AI tools and magic wand already in the box: modern Angular, and
372
+ free alternatives to capabilities that are otherwise paid in commercial editors.
373
+
374
+ ## Documentation
375
+
376
+ A live playground and full docs for every tool, theming, the complete API and more:
377
+ **[angular-image-editor.ascentspark.com](https://angular-image-editor.ascentspark.com)**.
378
+
379
+ ## Local development
380
+
381
+ Angular CLI multi-project workspace β€” library in `projects/angular-image-editor`, demo in
382
+ `projects/demo`:
383
+
384
+ ```bash
385
+ npm install
386
+ npm start # serve the demo (http://localhost:4200)
387
+ npm run build:lib # build the publishable library to dist/
388
+ npm test # unit tests (Vitest)
389
+ npm run lint
390
+ npm run pack:lib # build + npm pack a tarball
391
+ ```
392
+
393
+ Contributions are welcome β€” see [CONTRIBUTING.md](CONTRIBUTING.md).
394
+
395
+ ## Help keep it healthy
396
+
397
+ We genuinely try to keep this library current, bug-free and secure, and the best way to get there is
398
+ together. If something breaks, please
399
+ [open an issue](https://github.com/ascentspark/angular-image-editor/issues) with a minimal
400
+ reproduction; if you can fix a bug or add something useful, pull requests are very welcome, big or
401
+ small. An open-source library stays dependable only when people use it, tell us what's broken, and
402
+ pitch in now and then β€” so thank you in advance for anything you send our way. πŸ’›
403
+
404
+ ## License
405
+
406
+ [MIT](./LICENSE), by [Ascentspark](https://ascentspark.com).