@hitslop/cli 0.1.4 → 0.3.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.
@@ -0,0 +1,25 @@
1
+ // @bun
2
+ // src/module-loader.ts
3
+ import { pathToFileURL } from "url";
4
+ import { assertJSON } from "@hitslop/schema/json";
5
+ if (!process.send)
6
+ throw new Error("Module loader requires an IPC parent");
7
+ var result;
8
+ try {
9
+ const path = process.argv[2];
10
+ if (!path)
11
+ throw new Error("Missing module path");
12
+ const kind = process.argv[3];
13
+ if (kind !== "schema" && kind !== "theme")
14
+ throw new Error("Unknown module kind");
15
+ const { default: definition } = await import(pathToFileURL(path).href);
16
+ const value = kind === "theme" ? definition?.css : definition;
17
+ if (kind === "theme" && typeof value !== "string")
18
+ throw new Error("theme.ts must default-export defineTheme(...)");
19
+ assertJSON(value);
20
+ result = { ok: true, value };
21
+ } catch (error) {
22
+ result = { ok: false, error: error instanceof Error ? error.message : String(error) };
23
+ }
24
+ setInterval(() => {}, 60000);
25
+ process.send(result);
package/package.json CHANGED
@@ -1,35 +1,44 @@
1
1
  {
2
2
  "name": "@hitslop/cli",
3
- "version": "0.1.4",
3
+ "version": "0.3.0",
4
4
  "description": "Create, develop, build, register, and publish hitSlop apps.",
5
5
  "license": "MIT",
6
- "repository": { "type": "git", "url": "git+https://github.com/hitslop/hitslop.git", "directory": "packages/cli" },
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/hitslop/hitslop.git",
9
+ "directory": "packages/cli"
10
+ },
7
11
  "homepage": "https://hitslop.com",
8
- "bugs": { "url": "https://github.com/hitslop/hitslop/issues" },
12
+ "bugs": {
13
+ "url": "https://github.com/hitslop/hitslop/issues"
14
+ },
9
15
  "keywords": ["hitslop", "cli", "local-first", "mini-apps"],
10
- "publishConfig": { "access": "public" },
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
11
19
  "type": "module",
12
20
  "bin": {
13
21
  "slop": "dist/cli.js"
14
22
  },
15
23
  "files": ["dist", "templates"],
16
24
  "scripts": {
17
- "build": "bun ../../scripts/clean-dist.ts && bun build src/cli.ts --target bun --format esm --packages external --outfile dist/cli.js && /bin/chmod +x dist/cli.js",
25
+ "build": "bun ../../scripts/clean-dist.ts && bun build src/cli.ts src/module-loader.ts --target bun --format esm --packages external --outdir dist && /bin/chmod +x dist/cli.js",
18
26
  "check": "tsc -p tsconfig.json",
19
27
  "test": "bun run --cwd ../schema build && bun test"
20
28
  },
21
29
  "dependencies": {
22
30
  "@crustjs/core": "^0.0.19",
23
- "@hitslop/schema": "^0.1.3",
31
+ "@hitslop/schema": "^0.3.0",
24
32
  "@noble/ed25519": "^3.2.0",
25
33
  "fast-png": "^8.0.0",
26
34
  "fflate": "^0.8.3",
35
+ "parse5": "^8.0.1",
27
36
  "postcss": "^8.5.6",
28
- "vite": "^8.2.2",
29
- "zod": "^4.5.2"
37
+ "vite": "^8.2.2"
30
38
  },
31
39
  "devDependencies": {
32
40
  "@types/bun": "latest",
41
+ "typebox": "^1.3.26",
33
42
  "typescript": "^7.0.2"
34
43
  }
35
44
  }
@@ -30,14 +30,27 @@ adding persistence or changing the runtime boundary.
30
30
  - Runtime packages contain `manifest.json`, generated `app.html`, optional
31
31
  `data.schema.json`, the canonical document Agent Skill, optional immutable `assets/`, optional host-owned
32
32
  `stores/` in writable documents, and optional `QuickLook/` images.
33
- - Svelte JSON stores require `{ schema, initial }`; default-export the Zod 4
34
- schema from root `schema.ts` and attach that same export to the store.
33
+ - Author root `schema.ts` with `import * as Type from "typebox"`.
34
+ Import that schema directly into `jsonStore({ schema, initial })`.
35
+ Type inference requires no generated files or running dev server. The store
36
+ uses TypeBox runtime validation; builds emit `data.schema.json` for the host.
37
+ Keep schema definitions deterministic: app and builder evaluate separately.
38
+ Never manually supply a validator or rewrite ordinary schema imports. Preserve
39
+ unknown fields with `additionalProperties: true`; never coerce or insert defaults.
40
+ Quick Checklist is the only active example. Paused source is preserved in
41
+ `examples/slops/_backlog/`, excluded from active checks, tests, and builds.
42
+ Promote and migrate one example at a time. The CLI counter starter follows the same APIs.
35
43
  - Authored templates and published artifacts contain no stores, source,
36
44
  dependencies, build caches, editable stylesheets, SQLite sidecars, or
37
45
  Finder-managed `Icon\r`.
38
46
  - Storage is implicit and ID-free. Never add storage declarations or release
39
47
  versions to the manifest.
40
48
  - Treat `dist/<slug>.slop` as generated output.
49
+ - Quick Checklist and the CLI counter starter use root `theme.ts` uses `defineTheme`
50
+ from `@hitslop/runtime/theme`, supplying typed variables and generated
51
+ immutable `assets/theme.css`. Owners still edit `stores/theme.css`.
52
+ - Builds embed document guidance; missing or changed guidance never prevents
53
+ opening. Do not compare its text with the host's current copy.
41
54
  - Publishing captures `QuickLook/Preview.png`, produces an exact 512×512
42
55
  `QuickLook/Icon.png`, and signs one immutable ZIP.
43
56
  - Publisher ownership comes from the local Ed25519 identity; back it up with
@@ -5,18 +5,24 @@ graph, SQLite for queryable collections and transactional changes, and named
5
5
  media for a small set of known file roles.
6
6
 
7
7
  JSON writes replace the value atomically and may use an expected revision.
8
- For Svelte, define a Zod 4 schema in root `schema.ts` and pass it with the
9
- explicit initial value to `jsonStore`. Keep new fields backward-readable. Group
10
- related SQLite statements in one host transaction and parameterize values. Never copy live WAL
8
+ Root `schema.ts` authors the TypeBox data shape. Svelte stores import its
9
+ default export directly and infer data types through
10
+ `jsonStore({ schema, initial })`. Validation checks values without
11
+ coercion, defaults, or field removal. Quick Checklist and the CLI counter starter use this workflow;
12
+ backlog examples remain deferred. Group related SQLite statements in one host transaction and parameterize values. Never copy live WAL
11
13
  or SHM files. Replace/remove named media through the host rather than treating
12
14
  it as an arbitrary filesystem.
13
15
 
14
16
  A template contains immutable `manifest.json`, generated `app.html`, optional
15
- `data.schema.json`, the canonical `.agents/skills/hitslop-document` skill,
17
+ `data.schema.json`, optional immutable `.agents/skills/hitslop-document` guidance,
16
18
  optional `assets/`, and capture images when registered/published. A writable document may lazily add `stores/data.json`,
17
19
  `stores/data.sqlite`, `stores/media/`, and `stores/theme.css`.
18
20
  The macOS host may add Finder `Icon\r` metadata locally.
19
21
 
22
+ The builder supplies current guidance, but hosts must not require its presence
23
+ or compare it with their own copy. Quick Checklist and the CLI starter use single-source
24
+ `theme.ts`; backlog migration remains a separate task.
25
+
20
26
  Never ship source, `node_modules`, `.hitslop`, `dist` nesting, authoring skills,
21
27
  `style.css`, `document.json`, seed stores, SQLite sidecars, env files, keys,
22
28
  or Finder metadata in a template or published artifact.
@@ -2,8 +2,16 @@
2
2
 
3
3
  ## Commands
4
4
 
5
+ Quick Checklist imports root `schema.ts` directly into `jsonStore`.
6
+ Editor types and checks work without preparation or a dev server. Builds emit
7
+ `data.schema.json` for the host; keep schema definitions deterministic.
8
+ The CLI counter starter follows the same workflow; run `bun run check` for editor/type diagnostics.
9
+
5
10
  ```sh
6
11
  bunx @hitslop/cli init my-slop
12
+ cd my-slop
13
+ bun install
14
+ bun run check
7
15
  bun run dev
8
16
  bun run validate
9
17
  bun run build
@@ -25,19 +33,25 @@ skill's single reference after validating its encoding and size.
25
33
 
26
34
  ## Capture
27
35
 
28
- Call `ready()` after initial durable data and critical assets are usable. Test
29
- the manifest viewport in live and `data-slop-capture="static"` states. Mark
30
- editing controls `data-slop-export="hide"`. Keep output in normal document
31
- flow so full-height PNG/PDF can see it.
36
+ Call `ready()` after initial data is usable. Prefer optional `IconTarget` and
37
+ `ExportTarget` from `@hitslop/svelte`, wrapping ordinary `Icon.svelte` and
38
+ `Export.svelte` presentation components. Pass the same data and selected view;
39
+ never open a second store. Helpers own mounting, geometry, and capture state.
40
+ Export content belongs in normal flow. Without an export target, use the existing
41
+ `data-slop-capture="static"` CSS and `data-slop-export="hide"` fallback.
32
42
 
33
- An optional renderer-only icon target is a square 512px DOM element. Use
34
- `capture.isRenderer()` so they never mount in the interactive app.
43
+ Preview `?capture=icon` and `?capture=export` in the disposable gallery. The
44
+ runtime waits for fonts, images, and stable layout. For charts/virtualization,
45
+ register `capture.onPrepare(async (mode, signal) => { ... })` and clean up the
46
+ returned registration. Background captures use temporary snapshots. Optional
47
+ icons refresh Finder metadata on close; `QuickLook/Icon.png` remains immutable.
35
48
 
36
49
  ## Identity and publish
37
50
 
38
- `slop identity show`, `set-name`, `export`, and `import` manage the local
39
- publisher key. Never place private identity material in a project. Publish signs
40
- one built/captured artifact; the registry assigns release numbers externally.
51
+ `manifest.json` owns the public author name and optional HTTP(S) author URL.
52
+ `slop identity show`, `export`, and `import` manage only the local publisher
53
+ key. Never place private identity material in a project. Publish signs one
54
+ built/captured artifact; the registry assigns release numbers externally.
41
55
 
42
56
  ## Definition of done
43
57
 
@@ -49,9 +49,9 @@ and PDF behavior.
49
49
  authentic physical personality (Paper, Instrument, Skin) while remaining effortless
50
50
  to restyle or re-theme at runtime.
51
51
  - In new Svelte projects, keep structural styles in Vanilla Extract `.css.ts`
52
- files and bind public `--slop-*` variables with `createGlobalThemeContract`.
53
- Keep the plain default values in `assets/theme.css`; do not compile the owner-editable
54
- theme surface into generated class names.
52
+ files and define public tokens in root `theme.ts` via `defineTheme` from
53
+ `@hitslop/runtime/theme`. Use its typed variable references in Vanilla Extract;
54
+ the builder generates `assets/theme.css`. Never maintain both defaults files.
55
55
  - Mark editing-only UI with `data-slop-export="hide"`; keep exportable content
56
56
  in normal flow.
57
57
  - Make each slop purpose-specific. Shared SDK patterns must not make unrelated
@@ -25,18 +25,21 @@ windows cannot resize. Avoid critical controls on antialiased/translucent edges.
25
25
 
26
26
  ## Static output
27
27
 
28
- During static capture the root has `data-slop-capture="static"`. Add
29
- `data-slop-export="hide"` to editing-only controls and use capture CSS for
30
- necessary flattening. Do not hide the content those controls manipulate.
28
+ Prefer an optional `Export.svelte` wrapped in `ExportTarget` from `@hitslop/svelte`.
29
+ Pass the current data and selected view; share presentation and theme components.
30
+ Use normal flow rather than viewport heights or scrolling panels. This view also
31
+ supplies the window-sized preview. Without it, use `data-slop-capture="static"`
32
+ styles and `data-slop-export="hide"` on editing controls.
31
33
 
32
- Preview preserves manifest dimensions. PNG/PDF export uses current width and
33
- full document height; PNG is deterministic and PDF keeps selectable text/
34
- vectors. Keep export content in normal flow, not nested scroll panels.
34
+ PNG exports use current width and full content height at 2×, within 16384 pixels
35
+ per side and 24 megapixels. PDF retains selectable text on one content-sized page.
36
+ Dedicated exports do not inherit native window masks. Fonts, visible images, and
37
+ stable geometry are awaited; asynchronous charts can use `capture.onPrepare`.
35
38
 
36
39
  ## Icon
37
40
 
38
- Mount at most one 512×512 `data-slop-render="icon"` target only when
39
- `capture.isRenderer()` is true. Reveal it only in icon capture mode. The icon
40
- should communicate the job with a strong silhouette, safe margins, and no
41
- essential small text. Catalog detail uses the full preview; Finder and compact
42
- catalog rows use the icon.
41
+ Use optional `IconTarget` around `Icon.svelte`. It owns renderer-only mounting and
42
+ a transparent 512×512 surface. Pass progress or other saved data if useful; keep
43
+ a strong silhouette, safe margins, and no essential small text. It refreshes
44
+ Finder metadata on close; the published `QuickLook/Icon.png` remains immutable.
45
+ Preview icon and export modes in the disposable gallery before native checks.
@@ -8,12 +8,16 @@ and size it for realistic default content. Use Bits UI (`bits-ui`) for interacti
8
8
  controls (dialogs, selects, sliders, tabs, checkboxes, calendars, tooltips) rather
9
9
  than home-making components. Style via data attributes and semantic CSS custom
10
10
  properties so your slop retains its bespoke aesthetic and can be re-themed cleanly.
11
- Use Vanilla Extract for structural `.css.ts` styles and a global theme contract;
12
- keep the editable token defaults in plain `assets/theme.css`.
11
+ Use Vanilla Extract for structural `.css.ts` styles and root `theme.ts` with
12
+ `defineTheme` from `@hitslop/runtime/theme`. The builder emits immutable default
13
+ CSS; owners override tokens in `stores/theme.css`.
13
14
  Preview both live and static states, then validate, build, install a writable test
14
15
  copy, and publish only when ready. Browser development is a disposable UI
15
- preview; test persistence in an installed writable copy. Attach the root Zod
16
- schema to every Svelte JSON store. Source, skills, dependencies, editable
16
+ preview; test persistence in an installed writable copy. Attach the root TypeBox
17
+ schema to every Svelte JSON store. Call `ready()` after loading and destroy stores on component teardown. Use
18
+ `IconTarget` and `ExportTarget` for optional capture views with the same store data.
19
+ Run `bun run check` without a dev server or generated files.
20
+ Source, authoring skills, dependencies, editable
17
21
  styles, secrets, and seed data never enter the runtime `.slop`. Build output
18
22
  does include the canonical `hitslop-document` skill; optional app-specific
19
23
  agent guidance belongs only in root `document-guide.md`.
@@ -1,5 +1,5 @@
1
- import * as z from "zod";
1
+ import * as Type from "typebox";
2
2
 
3
- export default z.object({
4
- count: z.number().int().describe("Current tally count value"),
5
- });
3
+ export default Type.Object({
4
+ count: Type.Integer({ description: "Current tally count value" }),
5
+ }, { additionalProperties: true });
@@ -1,32 +1,40 @@
1
1
  <script lang="ts">
2
- import { capture } from "@hitslop/runtime";
3
- import { jsonStore } from "@hitslop/svelte";
2
+ import { ready } from "@hitslop/runtime";
3
+ import { jsonStore, IconTarget, ExportTarget } from "@hitslop/svelte";
4
+ import { onDestroy } from "svelte";
4
5
  import { Button } from "bits-ui";
5
6
  import counterSchema from "../schema";
6
7
  import * as styles from "./styles.css.ts";
8
+ import Readout from "./Readout.svelte";
9
+ import Icon from "./Icon.svelte";
10
+ import Export from "./Export.svelte";
7
11
 
8
12
  const title = __SLOP_TITLE_LITERAL__;
9
13
  const state = jsonStore({ schema: counterSchema, initial: { count: 0 } });
14
+ $effect(() => { if (state.isReady) ready(); });
15
+ onDestroy(() => state.destroy());
10
16
  </script>
11
17
 
12
- <main class={styles.main} data-slop-selection="none">
13
- <section class={styles.counter} aria-labelledby="counter-title">
14
- <header>
15
- <span class={styles.eyebrow}>Quick counter</span>
16
- <h1 class={styles.heading} id="counter-title">{title}</h1>
17
- </header>
18
- <div class={styles.readout}><output class={styles.output} aria-live="polite">{state.current.count}</output><span class={styles.readoutLabel}>things counted</span></div>
19
- <div class={styles.controls} data-slop-export="hide">
18
+ <main class={styles.main} data-slop-selection="none" aria-busy={state.isLoading}>
19
+ <section class={styles.counter} aria-label={title}>
20
+ <Readout {title} count={state.current.count} />
21
+ <div class={styles.controls} data-slop-export="hide" inert={!state.isReady || state.isLoading}>
20
22
  <Button.Root class={styles.controlButton} onclick={() => state.current.count -= 1} aria-label="Decrease count">−</Button.Root>
21
23
  <Button.Root class={`${styles.controlButton} ${styles.primaryButton}`} onclick={() => state.current.count += 1} aria-label="Increase count">+</Button.Root>
22
24
  <Button.Root class={`${styles.controlButton} ${styles.resetButton}`} onclick={() => state.current.count = 0}>Reset</Button.Root>
23
25
  </div>
24
- {#if state.error}<p class={styles.error} data-slop-export="hide">Your latest change couldn’t be saved.</p>{/if}
26
+ {#if state.error}
27
+ <div class={styles.error} role="alert" data-slop-export="hide">
28
+ <p>{state.isReady ? "Your changes couldn’t be saved." : "Your counter couldn’t be loaded."} {state.error}</p>
29
+ {#if state.isReady}
30
+ <button onclick={() => { void state.flush().catch(() => {}); }}>Retry saving</button>
31
+ {:else}
32
+ <button onclick={() => state.reload()} disabled={state.isLoading}>Retry loading</button>
33
+ {/if}
34
+ </div>
35
+ {/if}
25
36
  </section>
26
37
  </main>
27
38
 
28
- {#if capture.isRenderer()}
29
- <section class={styles.renderTarget} data-slop-render="icon" aria-hidden="true">
30
- <span class={styles.iconTile}>#</span>
31
- </section>
32
- {/if}
39
+ <IconTarget><Icon /></IconTarget>
40
+ <ExportTarget><Export {title} count={state.current.count} /></ExportTarget>
@@ -0,0 +1,9 @@
1
+ <script lang="ts">
2
+ import Readout from "./Readout.svelte";
3
+ import * as styles from "./styles.css";
4
+ let { title, count }: { title: string; count: number } = $props();
5
+ </script>
6
+
7
+ <section class={styles.exportSurface} aria-label={title}>
8
+ <Readout {title} {count} />
9
+ </section>
@@ -0,0 +1,5 @@
1
+ <script lang="ts">
2
+ import * as styles from "./styles.css";
3
+ </script>
4
+
5
+ <div class={styles.iconSurface}><span class={styles.iconTile}>#</span></div>
@@ -0,0 +1,13 @@
1
+ <script lang="ts">
2
+ import * as styles from "./styles.css";
3
+ let { title, count }: { title: string; count: number } = $props();
4
+ </script>
5
+
6
+ <header>
7
+ <span class={styles.eyebrow}>Quick counter</span>
8
+ <h1 class={styles.heading}>{title}</h1>
9
+ </header>
10
+ <div class={styles.readout}>
11
+ <output class={styles.output} aria-live="polite">{count}</output>
12
+ <span class={styles.readoutLabel}>things counted</span>
13
+ </div>
@@ -1,4 +1,3 @@
1
- import { ready } from "@hitslop/runtime";
2
1
  import { mount } from "svelte";
3
2
  import App from "./App.svelte";
4
3
  import "./styles.css.ts";
@@ -6,4 +5,3 @@ import "./styles.css.ts";
6
5
  const target = document.getElementById("app");
7
6
  if (!target) throw new Error("Missing #app");
8
7
  mount(App, { target });
9
- ready();
@@ -1,5 +1,7 @@
1
1
  import { globalStyle, style } from "@vanilla-extract/css";
2
- import { theme } from "./theme-contract.css.ts";
2
+ import definition from "../theme";
3
+
4
+ const theme = definition.vars;
3
5
 
4
6
  globalStyle(":root", { colorScheme: "light", fontFamily: theme.font, fontSynthesis: "none" });
5
7
  globalStyle("*", { boxSizing: "border-box" });
@@ -7,8 +9,6 @@ globalStyle("html, body, #app", { width: "100%", minHeight: "100%", margin: 0 })
7
9
  globalStyle("body", { color: theme.ink, background: theme.surface });
8
10
  globalStyle("button", { font: "inherit" });
9
11
  globalStyle('html[data-slop-capture="static"] [data-slop-export="hide"]', { display: "none !important" });
10
- globalStyle('html[data-slop-renderer="true"][data-slop-capture="icon"] main', { display: "none" });
11
- globalStyle('html[data-slop-renderer="true"][data-slop-capture="icon"] [data-slop-render="icon"]', { display: "grid !important" });
12
12
  globalStyle("*, *::before, *::after", {
13
13
  "@media": { "(prefers-reduced-motion: reduce)": { scrollBehavior: "auto", transitionDuration: ".01ms" } },
14
14
  });
@@ -41,5 +41,6 @@ export const controlButton = style({
41
41
  export const primaryButton = style({ borderColor: "transparent", color: theme.surface, background: theme.accent });
42
42
  export const resetButton = style({ gridColumn: "1 / -1", minHeight: 30, border: 0, color: theme.muted, background: "transparent", fontSize: 10, fontWeight: 750, letterSpacing: ".08em", textTransform: "uppercase" });
43
43
  export const error = style({ margin: "10px 0 0", color: "#9a2e27", fontSize: 11 });
44
- export const renderTarget = style({ display: "none !important", width: 512, height: 512, placeItems: "center", overflow: "hidden", background: "transparent" });
44
+ export const exportSurface = style({ minHeight: "100vh", padding: "34px 38px", background: theme.surface, color: theme.ink });
45
+ export const iconSurface = style({ display: "grid", width: "100%", height: "100%", placeItems: "center" });
45
46
  export const iconTile = style({ display: "grid", placeItems: "center", width: 464, height: 464, border: `18px solid ${theme.ink}`, borderRadius: 72, color: theme.surface, background: theme.accent, fontSize: 250, fontWeight: 700 });
@@ -0,0 +1,12 @@
1
+ import { defineTheme } from "@hitslop/runtime/theme";
2
+
3
+ export default defineTheme({
4
+ surface: "#f7f4eb",
5
+ panel: "#e3e9df",
6
+ control: "#ede9df",
7
+ ink: "#182126",
8
+ muted: "#687276",
9
+ accent: "#db6648",
10
+ rule: "color-mix(in srgb, var(--slop-ink) 15%, transparent)",
11
+ font: '"Avenir Next", Avenir, sans-serif',
12
+ });
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "skipLibCheck": true,
8
+ "allowJs": true,
9
+ "checkJs": true,
10
+ "verbatimModuleSyntax": true,
11
+ "noEmit": true,
12
+ "allowImportingTsExtensions": true
13
+ },
14
+ "include": ["src/**/*", "schema.ts", "theme.ts", "vite.config.ts"]
15
+ }
@@ -1,10 +0,0 @@
1
- :root {
2
- --slop-surface: #f7f4eb;
3
- --slop-panel: #e3e9df;
4
- --slop-control: #ede9df;
5
- --slop-ink: #182126;
6
- --slop-muted: #687276;
7
- --slop-accent: #db6648;
8
- --slop-rule: color-mix(in srgb, var(--slop-ink) 15%, transparent);
9
- --slop-font: "Avenir Next", Avenir, sans-serif;
10
- }
@@ -1,12 +0,0 @@
1
- import { createGlobalThemeContract } from "@vanilla-extract/css";
2
-
3
- export const theme = createGlobalThemeContract({
4
- surface: "slop-surface",
5
- panel: "slop-panel",
6
- control: "slop-control",
7
- ink: "slop-ink",
8
- muted: "slop-muted",
9
- accent: "slop-accent",
10
- rule: "slop-rule",
11
- font: "slop-font",
12
- });