tinymoon 0.3.0__tar.gz → 0.4.0__tar.gz

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.
Files changed (164) hide show
  1. {tinymoon-0.3.0 → tinymoon-0.4.0}/.gitignore +3 -0
  2. tinymoon-0.4.0/PKG-INFO +209 -0
  3. tinymoon-0.4.0/README.md +188 -0
  4. tinymoon-0.4.0/assets/css/base.css +280 -0
  5. tinymoon-0.4.0/assets/css/primitives.css +1075 -0
  6. tinymoon-0.4.0/assets/css/shell.css +224 -0
  7. tinymoon-0.4.0/assets/css/tokens.css +154 -0
  8. tinymoon-0.4.0/assets/js/auditor.js +176 -0
  9. tinymoon-0.4.0/assets/js/controls.js +361 -0
  10. tinymoon-0.4.0/assets/js/ctxmenu.js +214 -0
  11. tinymoon-0.4.0/assets/js/datepicker.js +612 -0
  12. tinymoon-0.4.0/assets/js/extras.js +7 -0
  13. tinymoon-0.4.0/assets/js/hovercard.js +158 -0
  14. {tinymoon-0.3.0 → tinymoon-0.4.0}/assets/js/icons.js +6 -1
  15. tinymoon-0.4.0/assets/js/index.d.ts +290 -0
  16. tinymoon-0.4.0/assets/js/index.js +18 -0
  17. tinymoon-0.4.0/assets/js/kernel.js +159 -0
  18. {tinymoon-0.3.0 → tinymoon-0.4.0}/assets/js/markdown.js +16 -14
  19. tinymoon-0.4.0/assets/js/modal.js +91 -0
  20. {tinymoon-0.3.0 → tinymoon-0.4.0}/assets/js/net.js +8 -4
  21. tinymoon-0.4.0/assets/js/popover.js +42 -0
  22. tinymoon-0.4.0/assets/js/select.js +268 -0
  23. {tinymoon-0.3.0 → tinymoon-0.4.0}/assets/js/settings.js +0 -6
  24. {tinymoon-0.3.0 → tinymoon-0.4.0}/assets/js/shell.js +104 -23
  25. tinymoon-0.4.0/assets/js/toast.js +155 -0
  26. tinymoon-0.4.0/assets/js/tooltip.js +122 -0
  27. {tinymoon-0.3.0 → tinymoon-0.4.0}/assets/js/wiki.js +17 -8
  28. {tinymoon-0.3.0 → tinymoon-0.4.0}/pyproject.toml +17 -5
  29. {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/fixtures/clean/clean.html +13 -0
  30. tinymoon-0.4.0/tests/fixtures/clean/clean.js +42 -0
  31. tinymoon-0.4.0/tests/fixtures/violations/external-url-attrs.html +10 -0
  32. tinymoon-0.4.0/tests/fixtures/violations/external-url-importmap.html +14 -0
  33. tinymoon-0.4.0/tests/fixtures/violations/external-url-template.js +7 -0
  34. tinymoon-0.4.0/tests/fixtures/violations/external-url-ws.js +3 -0
  35. tinymoon-0.4.0/tests/fixtures/violations/raw-color-extra-fns.css +5 -0
  36. tinymoon-0.4.0/tests/js/e2e/conformance-csp.spec.js +87 -0
  37. tinymoon-0.4.0/tests/js/e2e/conformance-runtime.spec.js +69 -0
  38. tinymoon-0.4.0/tests/js/e2e/content-first.spec.js +127 -0
  39. tinymoon-0.4.0/tests/js/e2e/copy.spec.js +173 -0
  40. tinymoon-0.4.0/tests/js/e2e/ctxmenu.spec.js +168 -0
  41. tinymoon-0.4.0/tests/js/e2e/datepicker.spec.js +179 -0
  42. tinymoon-0.4.0/tests/js/e2e/fixtures/auditor-gallery.html +18 -0
  43. tinymoon-0.4.0/tests/js/e2e/fixtures/content-first.html +136 -0
  44. tinymoon-0.4.0/tests/js/e2e/fixtures/escape-stack.html +33 -0
  45. tinymoon-0.4.0/tests/js/e2e/fixtures/rtl.html +97 -0
  46. tinymoon-0.4.0/tests/js/e2e/fixtures/selector-hygiene.html +77 -0
  47. tinymoon-0.4.0/tests/js/e2e/forms.spec.js +145 -0
  48. tinymoon-0.4.0/tests/js/e2e/gallery-characterization.spec.js +61 -0
  49. tinymoon-0.4.0/tests/js/e2e/gallery.spec.js +9 -0
  50. tinymoon-0.4.0/tests/js/e2e/modal.spec.js +100 -0
  51. tinymoon-0.4.0/tests/js/e2e/red-bugs.spec.js +51 -0
  52. tinymoon-0.4.0/tests/js/e2e/reduced-motion.spec.js +55 -0
  53. tinymoon-0.4.0/tests/js/e2e/responsive.spec.js +187 -0
  54. tinymoon-0.4.0/tests/js/e2e/selector-hygiene.spec.js +112 -0
  55. tinymoon-0.4.0/tests/js/e2e/shell-semantics.spec.js +98 -0
  56. tinymoon-0.4.0/tests/js/e2e/toast.spec.js +81 -0
  57. tinymoon-0.4.0/tests/js/e2e/tooltip-hovercard.spec.js +156 -0
  58. tinymoon-0.4.0/tests/js/unit/api-convention.test.js +256 -0
  59. tinymoon-0.4.0/tests/js/unit/controls.test.js +425 -0
  60. tinymoon-0.4.0/tests/js/unit/copyable.test.js +140 -0
  61. tinymoon-0.4.0/tests/js/unit/datepicker.test.js +134 -0
  62. tinymoon-0.4.0/tests/js/unit/dom.test.js +13 -0
  63. tinymoon-0.4.0/tests/js/unit/hovercard.test.js +96 -0
  64. tinymoon-0.4.0/tests/js/unit/icons.test.js +26 -0
  65. tinymoon-0.4.0/tests/js/unit/import-graph.test.js +88 -0
  66. tinymoon-0.4.0/tests/js/unit/kernel.test.js +197 -0
  67. tinymoon-0.4.0/tests/js/unit/markdown.test.js +54 -0
  68. tinymoon-0.4.0/tests/js/unit/red-bugs.test.js +102 -0
  69. tinymoon-0.4.0/tests/js/unit/select.test.js +231 -0
  70. tinymoon-0.4.0/tests/js/unit/settings.test.js +36 -0
  71. tinymoon-0.4.0/tests/js/unit/toast.test.js +165 -0
  72. tinymoon-0.4.0/tests/js/unit/tooltip.test.js +115 -0
  73. tinymoon-0.4.0/tests/test_budgets.py +137 -0
  74. tinymoon-0.4.0/tests/test_charter.py +197 -0
  75. {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/test_checker.py +48 -2
  76. tinymoon-0.4.0/tests/test_contrast.py +182 -0
  77. {tinymoon-0.3.0 → tinymoon-0.4.0}/tinymoon/__init__.py +1 -1
  78. {tinymoon-0.3.0 → tinymoon-0.4.0}/tinymoon/checker.py +156 -32
  79. {tinymoon-0.3.0 → tinymoon-0.4.0}/tinymoon/cli.py +1 -1
  80. tinymoon-0.3.0/.github/workflows/ci-go.yml +0 -26
  81. tinymoon-0.3.0/.github/workflows/ci-pypi.yml +0 -33
  82. tinymoon-0.3.0/.github/workflows/publish.yml +0 -202
  83. tinymoon-0.3.0/.npmignore +0 -34
  84. tinymoon-0.3.0/.rlsbl/bases/.github/workflows/ci-go.yml +0 -26
  85. tinymoon-0.3.0/.rlsbl/bases/.github/workflows/ci-pypi.yml +0 -33
  86. tinymoon-0.3.0/.rlsbl/bases/.github/workflows/publish.yml +0 -192
  87. tinymoon-0.3.0/.rlsbl/bases/.gitignore +0 -18
  88. tinymoon-0.3.0/.rlsbl/bases/.npmignore +0 -34
  89. tinymoon-0.3.0/.rlsbl/bases/.rlsbl/lint/go.toml +0 -17
  90. tinymoon-0.3.0/.rlsbl/bases/.rlsbl/lint/npm.toml +0 -19
  91. tinymoon-0.3.0/.rlsbl/bases/.rlsbl/lint/python.toml +0 -25
  92. tinymoon-0.3.0/.rlsbl/bases/CHANGELOG.md +0 -5
  93. tinymoon-0.3.0/.rlsbl/bases/VERSION +0 -1
  94. tinymoon-0.3.0/.rlsbl/changes/0.0.1.jsonl +0 -1
  95. tinymoon-0.3.0/.rlsbl/changes/0.0.1.md +0 -7
  96. tinymoon-0.3.0/.rlsbl/changes/0.1.0.jsonl +0 -16
  97. tinymoon-0.3.0/.rlsbl/changes/0.1.0.md +0 -15
  98. tinymoon-0.3.0/.rlsbl/changes/0.2.0.jsonl +0 -18
  99. tinymoon-0.3.0/.rlsbl/changes/0.2.0.md +0 -24
  100. tinymoon-0.3.0/.rlsbl/changes/0.3.0.jsonl +0 -2
  101. tinymoon-0.3.0/.rlsbl/changes/0.3.0.md +0 -7
  102. tinymoon-0.3.0/.rlsbl/changes/unreleased.jsonl +0 -0
  103. tinymoon-0.3.0/.rlsbl/config.json +0 -23
  104. tinymoon-0.3.0/.rlsbl/lint/go.toml +0 -17
  105. tinymoon-0.3.0/.rlsbl/lint/npm.toml +0 -19
  106. tinymoon-0.3.0/.rlsbl/lint/python.toml +0 -25
  107. tinymoon-0.3.0/.rlsbl/managed-files.json +0 -16
  108. tinymoon-0.3.0/.rlsbl/releases/v0.0.1.toml +0 -12
  109. tinymoon-0.3.0/.rlsbl/releases/v0.2.0.toml +0 -14
  110. tinymoon-0.3.0/.rlsbl/releases/v0.3.0.toml +0 -12
  111. tinymoon-0.3.0/.rlsbl/version +0 -1
  112. tinymoon-0.3.0/.strictcli/schema.json +0 -66
  113. tinymoon-0.3.0/CHANGELOG.md +0 -60
  114. tinymoon-0.3.0/CLAUDE.md +0 -21
  115. tinymoon-0.3.0/PKG-INFO +0 -131
  116. tinymoon-0.3.0/README.md +0 -110
  117. tinymoon-0.3.0/VERSION +0 -1
  118. tinymoon-0.3.0/assets/css/base.css +0 -113
  119. tinymoon-0.3.0/assets/css/primitives.css +0 -627
  120. tinymoon-0.3.0/assets/css/shell.css +0 -135
  121. tinymoon-0.3.0/assets/css/tokens.css +0 -99
  122. tinymoon-0.3.0/assets/js/controls.js +0 -82
  123. tinymoon-0.3.0/assets/js/ctxmenu.js +0 -97
  124. tinymoon-0.3.0/assets/js/format.js +0 -9
  125. tinymoon-0.3.0/assets/js/index.js +0 -18
  126. tinymoon-0.3.0/assets/js/modal.js +0 -63
  127. tinymoon-0.3.0/assets/js/popover.js +0 -46
  128. tinymoon-0.3.0/assets/js/select.js +0 -138
  129. tinymoon-0.3.0/assets/js/toast.js +0 -47
  130. tinymoon-0.3.0/assets/js/tooltip.js +0 -106
  131. tinymoon-0.3.0/gallery/gallery.css +0 -81
  132. tinymoon-0.3.0/gallery/gallery.js +0 -654
  133. tinymoon-0.3.0/gallery/index.html +0 -19
  134. tinymoon-0.3.0/go.mod +0 -3
  135. tinymoon-0.3.0/index.js +0 -2
  136. tinymoon-0.3.0/package.json +0 -29
  137. tinymoon-0.3.0/tests/fixtures/clean/clean.js +0 -16
  138. tinymoon-0.3.0/tests/test_budgets.py +0 -72
  139. tinymoon-0.3.0/tinymoon.go +0 -36
  140. tinymoon-0.3.0/uv.lock +0 -92
  141. {tinymoon-0.3.0 → tinymoon-0.4.0}/LICENSE +0 -0
  142. {tinymoon-0.3.0 → tinymoon-0.4.0}/assets/fonts/ibm-plex-mono-latin-400.woff2 +0 -0
  143. {tinymoon-0.3.0 → tinymoon-0.4.0}/assets/fonts/ibm-plex-mono-latin-500.woff2 +0 -0
  144. {tinymoon-0.3.0 → tinymoon-0.4.0}/assets/fonts/ibm-plex-sans-latin.woff2 +0 -0
  145. {tinymoon-0.3.0 → tinymoon-0.4.0}/assets/fonts/space-grotesk-latin.woff2 +0 -0
  146. {tinymoon-0.3.0 → tinymoon-0.4.0}/assets/js/dom.js +0 -0
  147. {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/fixtures/clean/clean.css +0 -0
  148. {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/fixtures/clean/tinymoon-allowlist.txt +0 -0
  149. {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/fixtures/violations/border-radius.css +0 -0
  150. {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/fixtures/violations/border-radius.html +0 -0
  151. {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/fixtures/violations/border-radius.js +0 -0
  152. {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/fixtures/violations/external-url.css +0 -0
  153. {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/fixtures/violations/external-url.html +0 -0
  154. {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/fixtures/violations/external-url.js +0 -0
  155. {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/fixtures/violations/native-control.html +0 -0
  156. {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/fixtures/violations/native-control.js +0 -0
  157. {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/fixtures/violations/raw-color.css +0 -0
  158. {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/fixtures/violations/raw-color.html +0 -0
  159. {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/fixtures/violations/raw-color.js +0 -0
  160. {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/fixtures/violations/title-attr.html +0 -0
  161. {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/fixtures/violations/title-attr.js +0 -0
  162. {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/test_assets.py +0 -0
  163. {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/test_version.py +0 -0
  164. /tinymoon-0.3.0/.rlsbl/bases/.rlsbl/changes/unreleased.jsonl → /tinymoon-0.4.0/tinymoon/py.typed +0 -0
@@ -11,6 +11,9 @@ __pycache__/
11
11
  .DS_Store
12
12
  coverage/
13
13
  target/
14
+ test-results/
15
+ playwright-report/
16
+ .playwright/
14
17
  .rlsbl-notes-*.tmp
15
18
  .rlsbl/lock
16
19
  .rlsbl-monorepo/lock
@@ -0,0 +1,209 @@
1
+ Metadata-Version: 2.4
2
+ Name: tinymoon
3
+ Version: 0.4.0
4
+ Summary: Zero-dependency vanilla JS/CSS UI framework with a conformance checker CLI
5
+ Project-URL: Homepage, https://github.com/smm-h/tinymoon
6
+ Project-URL: Repository, https://github.com/smm-h/tinymoon
7
+ Author-email: smm-h <smmh72@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: css,framework,rlsbl,ui,vanilla-js,zero-dependency
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: Programming Language :: Python :: Implementation :: CPython
18
+ Requires-Python: >=3.12
19
+ Requires-Dist: strictcli
20
+ Description-Content-Type: text/markdown
21
+
22
+ # tinymoon
23
+
24
+ A content-first web framework: you bring plain, semantic content and small view objects; tinymoon brings the app -- shell, typography, widgets, motion. Everything ships as native ES modules and plain CSS with zero dependencies, zero build steps, and zero network loads.
25
+
26
+ tinymoon's palette is its identity. Consumer CSS must not redefine framework tokens.
27
+
28
+ ## Install
29
+
30
+ npm (core + extras):
31
+
32
+ ```
33
+ npm install tinymoon
34
+ ```
35
+
36
+ The npm package exports two barrels: `"tinymoon"` (core primitives) and `"tinymoon/extras"` (wiki, networking, settings). Assets are available at `"tinymoon/assets/*"`.
37
+
38
+ PyPI (assets + conformance checker CLI):
39
+
40
+ ```
41
+ pip install tinymoon
42
+ ```
43
+
44
+ `tinymoon.assets_path()` returns the directory containing `css/`, `js/`, and `fonts/`.
45
+
46
+ Go (embedded filesystem):
47
+
48
+ ```go
49
+ import "github.com/smm-h/tinymoon"
50
+
51
+ // tinymoon.Assets — embed.FS rooted at the repo root
52
+ // tinymoon.FS() — fs.FS rooted at the assets directory
53
+ // tinymoon.Handler() — http.Handler serving the assets
54
+ ```
55
+
56
+ ## Quick start
57
+
58
+ Link the four CSS layers (tokens first) and import the ES modules -- no build step, no bundler:
59
+
60
+ ```html
61
+ <link rel="stylesheet" href="assets/css/tokens.css">
62
+ <link rel="stylesheet" href="assets/css/base.css">
63
+ <link rel="stylesheet" href="assets/css/shell.css">
64
+ <link rel="stylesheet" href="assets/css/primitives.css">
65
+
66
+ <script type="module">
67
+ import { mountShell, toast } from "./assets/js/index.js";
68
+ import { createSettings } from "./assets/js/extras.js";
69
+
70
+ const settings = createSettings({
71
+ storageKey: "my-app",
72
+ defaults: { theme: "dark" },
73
+ });
74
+ settings.load();
75
+ settings.applyTheme();
76
+
77
+ const shell = mountShell({
78
+ root: document.body,
79
+ brand: {
80
+ name: "myapp",
81
+ logoHTML: '<div class="wordmark">my<b>app</b></div>',
82
+ },
83
+ routes: {
84
+ home: {
85
+ title: "Home",
86
+ icon: "library",
87
+ view: () => HomeView,
88
+ },
89
+ },
90
+ defaultRoute: "home",
91
+ });
92
+ </script>
93
+ ```
94
+
95
+ With npm, use bare specifiers by adding an import map:
96
+
97
+ ```html
98
+ <script type="importmap">
99
+ { "imports": { "tinymoon": "./node_modules/tinymoon/assets/js/index.js",
100
+ "tinymoon/extras": "./node_modules/tinymoon/assets/js/extras.js" } }
101
+ </script>
102
+ ```
103
+
104
+ ## Primitives
105
+
106
+ ### Core (`tinymoon`)
107
+
108
+ **Shell and DOM:**
109
+
110
+ - `mountShell(opts)` -- mount the app shell with sidebar, topbar, router, and footer slot
111
+ - `el(tag, cls?, text?)` -- element factory
112
+ - `$(sel, root?)` -- querySelector shorthand
113
+ - `$$(sel, root?)` -- querySelectorAll (returns array)
114
+
115
+ **Controls:**
116
+
117
+ - `createSwitch(opts)` -- role="switch" toggle button (not form-participating)
118
+ - `createCheckbox(opts)` -- hidden-native checkbox facade (form-participating)
119
+ - `createRadio(opts)` -- hidden-native radio facade (form-participating)
120
+ - `createFileInput(opts)` -- hidden-native file input facade (form-participating)
121
+ - `createSegmented(opts)` -- segmented control with hidden radios (form-participating)
122
+ - `createTabs(opts)` -- tab bar (not form-participating)
123
+ - `createSelect(opts)` -- custom dropdown select
124
+ - `createDatePicker(opts)` -- calendar date picker
125
+ - `copyButton(getText, tip?)` -- one-click clipboard copy button
126
+ - `kebabButton(itemsFn, tip?)` -- three-dot menu button
127
+
128
+ **Overlays:**
129
+
130
+ - `toast(msg, level?, opts?)` -- toast notification ("ok", "err", or plain)
131
+ - `setToastErrorHook(fn)` -- mirror error toasts into a custom hook
132
+ - `openModal(opts)` -- modal dialog (returns close function)
133
+ - `openPopover(anchor, builder)` / `closePopover()` -- positioned popover
134
+ - `registerCtx(key, provider)` / `registerCtxFooter(fn)` -- context menu regions
135
+ - `showCtxMenu(x, y, items, anchor?)` / `hideCtxMenu()` -- programmatic context menu
136
+ - `ensureTooltip(el, text)` / `hideTip()` -- tooltip lifecycle
137
+ - `ensureHovercard(el, md)` / `hideHovercard()` -- rich hovercard with markdown
138
+
139
+ **Data and utilities:**
140
+
141
+ - `ICONS` -- built-in icon set (26 icons)
142
+ - `icon(name)` -- render an icon as an SVG string
143
+ - `registerIcons(map)` -- merge consumer icons (collisions are hard errors)
144
+ - `renderMiniMd(text)` -- inline markdown to DOM fragment (bold, code, links)
145
+ - `cssVar(name)` -- read a computed CSS custom property value
146
+ - `ensureRoot()` -- ensure the root element exists
147
+ - `placeBelow(anchor, el)` -- position an element below an anchor
148
+ - `registerCopyable(el, fn)` / `unregisterCopyable(el)` -- register elements for the copy system
149
+ - `getCopyData(el)` -- retrieve copy data from a registered element
150
+
151
+ ### Extras (`tinymoon/extras`)
152
+
153
+ - `api(path)` -- GET JSON from a same-origin path
154
+ - `post(path, body, onError?)` -- POST JSON to a same-origin path
155
+ - `createSettings(opts)` -- localStorage-backed settings store with schema validation
156
+ - `createWikiView(opts)` -- wiki view factory with table of contents and deep-linkable sections
157
+ - `renderDocMd(md)` -- block-level markdown to DOM (paragraphs, subheadings, lists)
158
+
159
+ ## Identity
160
+
161
+ The visual identity is enforced by constraint, not offered as options:
162
+
163
+ - **Sharp corners everywhere** -- `border-radius` is 0; no exceptions.
164
+ - **Three-font system** -- brand headings (Space Grotesk), UI body (IBM Plex Sans), monospace for data (IBM Plex Mono). All vendored, no network loads.
165
+ - **Glow language** -- accent glows on active cards, focused inputs, and modals. Restrained and consistent.
166
+ - **Grain** -- a subtle SVG noise overlay on the background.
167
+ - **Motion timing** -- all transitions are 100--180ms one-shot eases. The spinner is the only continuous animation.
168
+ - **No native browser controls** -- checkbox, radio, select, file input, and date picker are all custom-drawn with hidden native elements for form participation and accessibility.
169
+ - **AA contrast** -- every text-on-background token pair passes WCAG AA 4.5:1. Enforced by CI.
170
+ - **Reduced motion** -- `prefers-reduced-motion: reduce` suppresses all animation and transition durations to near-zero. Enforced by E2E tests.
171
+
172
+ Design tokens let you re-theme and re-accent; they do not let you opt out of the identity.
173
+
174
+ ## Conformance checker
175
+
176
+ `tinymoon check` scans `.html`, `.css`, and `.js` files and enforces the framework's non-negotiables as hard errors:
177
+
178
+ - **external-url** -- no network loads (no `http://`, `https://`, or `//host` URLs in HTML, CSS, or JS)
179
+ - **native-control** -- no native `<select>`, `<dialog>`, or `<input type=checkbox|radio|file>`
180
+ - **title-attr** -- no `title=` attributes (use the tooltip primitive)
181
+ - **border-radius** -- no `border-radius` other than `0`/`0px`
182
+ - **raw-color** -- no color literals outside `:root`/`html[data-theme]` token definitions
183
+
184
+ ```
185
+ uvx tinymoon check --dir ./web
186
+ ```
187
+
188
+ One line per violation, exit non-zero on any finding. No `--skip`, `--ignore`, or warning mode. Exempt specific URLs by adding them to `tinymoon-allowlist.txt` at the scanned directory root.
189
+
190
+ ## App model
191
+
192
+ **Copy system.** `registerCopyable(el, fn)` marks any element as copyable -- clicking it copies the value returned by `fn()` to the clipboard, with a toast confirmation. The `copyButton` helper wires a standalone copy button. `getCopyData(el)` retrieves registered copy data programmatically.
193
+
194
+ **Selection model.** Elements declare tooltips via `data-tooltip` (plain text) and hovercards via `data-hovercard` (markdown with bold, code, links). The framework manages hover intent, positioning, and the hover bridge automatically.
195
+
196
+ **View contract.** Routes map to view objects `{root, built, build(), refresh(), setSub?}`. The shell's router owns the lifecycle: `build()` constructs the DOM once (idempotent), `refresh()` runs on every visit, `setSub(sub)` receives deep-link tails. A string value instead of a view factory activates the content-first path -- plain HTML styled automatically with zero framework classes.
197
+
198
+ ## Gallery
199
+
200
+ The gallery is a complete tinymoon app that documents every design token and primitive. Serve the repo root with a static server and open `/gallery/`:
201
+
202
+ ```
203
+ python3 -m http.server
204
+ # open http://localhost:8000/gallery/
205
+ ```
206
+
207
+ ## License
208
+
209
+ MIT
@@ -0,0 +1,188 @@
1
+ # tinymoon
2
+
3
+ A content-first web framework: you bring plain, semantic content and small view objects; tinymoon brings the app -- shell, typography, widgets, motion. Everything ships as native ES modules and plain CSS with zero dependencies, zero build steps, and zero network loads.
4
+
5
+ tinymoon's palette is its identity. Consumer CSS must not redefine framework tokens.
6
+
7
+ ## Install
8
+
9
+ npm (core + extras):
10
+
11
+ ```
12
+ npm install tinymoon
13
+ ```
14
+
15
+ The npm package exports two barrels: `"tinymoon"` (core primitives) and `"tinymoon/extras"` (wiki, networking, settings). Assets are available at `"tinymoon/assets/*"`.
16
+
17
+ PyPI (assets + conformance checker CLI):
18
+
19
+ ```
20
+ pip install tinymoon
21
+ ```
22
+
23
+ `tinymoon.assets_path()` returns the directory containing `css/`, `js/`, and `fonts/`.
24
+
25
+ Go (embedded filesystem):
26
+
27
+ ```go
28
+ import "github.com/smm-h/tinymoon"
29
+
30
+ // tinymoon.Assets — embed.FS rooted at the repo root
31
+ // tinymoon.FS() — fs.FS rooted at the assets directory
32
+ // tinymoon.Handler() — http.Handler serving the assets
33
+ ```
34
+
35
+ ## Quick start
36
+
37
+ Link the four CSS layers (tokens first) and import the ES modules -- no build step, no bundler:
38
+
39
+ ```html
40
+ <link rel="stylesheet" href="assets/css/tokens.css">
41
+ <link rel="stylesheet" href="assets/css/base.css">
42
+ <link rel="stylesheet" href="assets/css/shell.css">
43
+ <link rel="stylesheet" href="assets/css/primitives.css">
44
+
45
+ <script type="module">
46
+ import { mountShell, toast } from "./assets/js/index.js";
47
+ import { createSettings } from "./assets/js/extras.js";
48
+
49
+ const settings = createSettings({
50
+ storageKey: "my-app",
51
+ defaults: { theme: "dark" },
52
+ });
53
+ settings.load();
54
+ settings.applyTheme();
55
+
56
+ const shell = mountShell({
57
+ root: document.body,
58
+ brand: {
59
+ name: "myapp",
60
+ logoHTML: '<div class="wordmark">my<b>app</b></div>',
61
+ },
62
+ routes: {
63
+ home: {
64
+ title: "Home",
65
+ icon: "library",
66
+ view: () => HomeView,
67
+ },
68
+ },
69
+ defaultRoute: "home",
70
+ });
71
+ </script>
72
+ ```
73
+
74
+ With npm, use bare specifiers by adding an import map:
75
+
76
+ ```html
77
+ <script type="importmap">
78
+ { "imports": { "tinymoon": "./node_modules/tinymoon/assets/js/index.js",
79
+ "tinymoon/extras": "./node_modules/tinymoon/assets/js/extras.js" } }
80
+ </script>
81
+ ```
82
+
83
+ ## Primitives
84
+
85
+ ### Core (`tinymoon`)
86
+
87
+ **Shell and DOM:**
88
+
89
+ - `mountShell(opts)` -- mount the app shell with sidebar, topbar, router, and footer slot
90
+ - `el(tag, cls?, text?)` -- element factory
91
+ - `$(sel, root?)` -- querySelector shorthand
92
+ - `$$(sel, root?)` -- querySelectorAll (returns array)
93
+
94
+ **Controls:**
95
+
96
+ - `createSwitch(opts)` -- role="switch" toggle button (not form-participating)
97
+ - `createCheckbox(opts)` -- hidden-native checkbox facade (form-participating)
98
+ - `createRadio(opts)` -- hidden-native radio facade (form-participating)
99
+ - `createFileInput(opts)` -- hidden-native file input facade (form-participating)
100
+ - `createSegmented(opts)` -- segmented control with hidden radios (form-participating)
101
+ - `createTabs(opts)` -- tab bar (not form-participating)
102
+ - `createSelect(opts)` -- custom dropdown select
103
+ - `createDatePicker(opts)` -- calendar date picker
104
+ - `copyButton(getText, tip?)` -- one-click clipboard copy button
105
+ - `kebabButton(itemsFn, tip?)` -- three-dot menu button
106
+
107
+ **Overlays:**
108
+
109
+ - `toast(msg, level?, opts?)` -- toast notification ("ok", "err", or plain)
110
+ - `setToastErrorHook(fn)` -- mirror error toasts into a custom hook
111
+ - `openModal(opts)` -- modal dialog (returns close function)
112
+ - `openPopover(anchor, builder)` / `closePopover()` -- positioned popover
113
+ - `registerCtx(key, provider)` / `registerCtxFooter(fn)` -- context menu regions
114
+ - `showCtxMenu(x, y, items, anchor?)` / `hideCtxMenu()` -- programmatic context menu
115
+ - `ensureTooltip(el, text)` / `hideTip()` -- tooltip lifecycle
116
+ - `ensureHovercard(el, md)` / `hideHovercard()` -- rich hovercard with markdown
117
+
118
+ **Data and utilities:**
119
+
120
+ - `ICONS` -- built-in icon set (26 icons)
121
+ - `icon(name)` -- render an icon as an SVG string
122
+ - `registerIcons(map)` -- merge consumer icons (collisions are hard errors)
123
+ - `renderMiniMd(text)` -- inline markdown to DOM fragment (bold, code, links)
124
+ - `cssVar(name)` -- read a computed CSS custom property value
125
+ - `ensureRoot()` -- ensure the root element exists
126
+ - `placeBelow(anchor, el)` -- position an element below an anchor
127
+ - `registerCopyable(el, fn)` / `unregisterCopyable(el)` -- register elements for the copy system
128
+ - `getCopyData(el)` -- retrieve copy data from a registered element
129
+
130
+ ### Extras (`tinymoon/extras`)
131
+
132
+ - `api(path)` -- GET JSON from a same-origin path
133
+ - `post(path, body, onError?)` -- POST JSON to a same-origin path
134
+ - `createSettings(opts)` -- localStorage-backed settings store with schema validation
135
+ - `createWikiView(opts)` -- wiki view factory with table of contents and deep-linkable sections
136
+ - `renderDocMd(md)` -- block-level markdown to DOM (paragraphs, subheadings, lists)
137
+
138
+ ## Identity
139
+
140
+ The visual identity is enforced by constraint, not offered as options:
141
+
142
+ - **Sharp corners everywhere** -- `border-radius` is 0; no exceptions.
143
+ - **Three-font system** -- brand headings (Space Grotesk), UI body (IBM Plex Sans), monospace for data (IBM Plex Mono). All vendored, no network loads.
144
+ - **Glow language** -- accent glows on active cards, focused inputs, and modals. Restrained and consistent.
145
+ - **Grain** -- a subtle SVG noise overlay on the background.
146
+ - **Motion timing** -- all transitions are 100--180ms one-shot eases. The spinner is the only continuous animation.
147
+ - **No native browser controls** -- checkbox, radio, select, file input, and date picker are all custom-drawn with hidden native elements for form participation and accessibility.
148
+ - **AA contrast** -- every text-on-background token pair passes WCAG AA 4.5:1. Enforced by CI.
149
+ - **Reduced motion** -- `prefers-reduced-motion: reduce` suppresses all animation and transition durations to near-zero. Enforced by E2E tests.
150
+
151
+ Design tokens let you re-theme and re-accent; they do not let you opt out of the identity.
152
+
153
+ ## Conformance checker
154
+
155
+ `tinymoon check` scans `.html`, `.css`, and `.js` files and enforces the framework's non-negotiables as hard errors:
156
+
157
+ - **external-url** -- no network loads (no `http://`, `https://`, or `//host` URLs in HTML, CSS, or JS)
158
+ - **native-control** -- no native `<select>`, `<dialog>`, or `<input type=checkbox|radio|file>`
159
+ - **title-attr** -- no `title=` attributes (use the tooltip primitive)
160
+ - **border-radius** -- no `border-radius` other than `0`/`0px`
161
+ - **raw-color** -- no color literals outside `:root`/`html[data-theme]` token definitions
162
+
163
+ ```
164
+ uvx tinymoon check --dir ./web
165
+ ```
166
+
167
+ One line per violation, exit non-zero on any finding. No `--skip`, `--ignore`, or warning mode. Exempt specific URLs by adding them to `tinymoon-allowlist.txt` at the scanned directory root.
168
+
169
+ ## App model
170
+
171
+ **Copy system.** `registerCopyable(el, fn)` marks any element as copyable -- clicking it copies the value returned by `fn()` to the clipboard, with a toast confirmation. The `copyButton` helper wires a standalone copy button. `getCopyData(el)` retrieves registered copy data programmatically.
172
+
173
+ **Selection model.** Elements declare tooltips via `data-tooltip` (plain text) and hovercards via `data-hovercard` (markdown with bold, code, links). The framework manages hover intent, positioning, and the hover bridge automatically.
174
+
175
+ **View contract.** Routes map to view objects `{root, built, build(), refresh(), setSub?}`. The shell's router owns the lifecycle: `build()` constructs the DOM once (idempotent), `refresh()` runs on every visit, `setSub(sub)` receives deep-link tails. A string value instead of a view factory activates the content-first path -- plain HTML styled automatically with zero framework classes.
176
+
177
+ ## Gallery
178
+
179
+ The gallery is a complete tinymoon app that documents every design token and primitive. Serve the repo root with a static server and open `/gallery/`:
180
+
181
+ ```
182
+ python3 -m http.server
183
+ # open http://localhost:8000/gallery/
184
+ ```
185
+
186
+ ## License
187
+
188
+ MIT
@@ -0,0 +1,280 @@
1
+ /* tinymoon — fonts and base element styles: the sharp-corner reset,
2
+ native-control elimination, grain overlay, custom scrollbars, and focus
3
+ rings. All fonts are vendored: IBM Plex Sans (UI), IBM Plex Mono (data),
4
+ Space Grotesk (brand/headings). Requires tokens.css. */
5
+
6
+ /* ---------- fonts ---------- */
7
+
8
+ @font-face {
9
+ font-family: "Plex";
10
+ font-style: normal;
11
+ font-weight: 100 900;
12
+ font-display: swap;
13
+ src: url("../fonts/ibm-plex-sans-latin.woff2") format("woff2");
14
+ }
15
+ @font-face {
16
+ font-family: "Plex Mono";
17
+ font-style: normal;
18
+ font-weight: 400;
19
+ font-display: swap;
20
+ src: url("../fonts/ibm-plex-mono-latin-400.woff2") format("woff2");
21
+ }
22
+ @font-face {
23
+ font-family: "Plex Mono";
24
+ font-style: normal;
25
+ font-weight: 500;
26
+ font-display: swap;
27
+ src: url("../fonts/ibm-plex-mono-latin-500.woff2") format("woff2");
28
+ }
29
+ @font-face {
30
+ font-family: "Grotesk";
31
+ font-style: normal;
32
+ font-weight: 300 700;
33
+ font-display: swap;
34
+ src: url("../fonts/space-grotesk-latin.woff2") format("woff2");
35
+ }
36
+
37
+ /* ---------- base ---------- */
38
+
39
+ * { box-sizing: border-box; border-radius: 0 !important; }
40
+
41
+ /* Structural native-UI elimination: every form control drops its OS/browser
42
+ appearance and is rebuilt by the design system. Native checkboxes, radios,
43
+ selects, file inputs, and dialogs are banned outright — custom widgets
44
+ (toggleWidget, Select, segmented) replace them. */
45
+ button, input, textarea, select {
46
+ appearance: none;
47
+ -webkit-appearance: none;
48
+ border-radius: 0;
49
+ font-family: inherit;
50
+ }
51
+ input::-webkit-outer-spin-button,
52
+ input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
53
+ input::-webkit-search-decoration,
54
+ input::-webkit-search-cancel-button { -webkit-appearance: none; }
55
+ input::-webkit-calendar-picker-indicator { display: none; }
56
+ input:-webkit-autofill {
57
+ -webkit-box-shadow: 0 0 0 100px var(--input-bg) inset;
58
+ -webkit-text-fill-color: var(--text);
59
+ }
60
+ textarea {
61
+ background: var(--input-bg);
62
+ border: 1px solid var(--border-2);
63
+ color: var(--text);
64
+ font-weight: var(--weight-normal);
65
+ font-size: var(--text-xs);
66
+ font-family: var(--font-ui);
67
+ padding: var(--space-10);
68
+ resize: vertical;
69
+ user-select: text;
70
+ }
71
+ textarea:hover { border-color: var(--hover-border); }
72
+ textarea:focus { border-color: var(--accent); box-shadow: 0 0 10px var(--accent-a18); }
73
+ textarea::placeholder { color: var(--text-faint); }
74
+ html, body { height: 100%; }
75
+ body {
76
+ margin: 0;
77
+ background: var(--bg);
78
+ color: var(--text);
79
+ font-weight: var(--weight-normal);
80
+ font-size: var(--text-base);
81
+ line-height: var(--leading-normal);
82
+ font-family: var(--font-ui);
83
+ overflow: hidden;
84
+ -webkit-font-smoothing: antialiased;
85
+ user-select: none;
86
+ }
87
+ /* Wiki/doc prose is the one content carve-out: selectable text so users can
88
+ highlight and copy passages. Everything else (chrome, nav, controls) stays
89
+ unselectable via body's user-select: none. */
90
+ .doc-body { user-select: text; }
91
+ ::selection { background: var(--accent-soft); color: var(--text); }
92
+ .mono { font-family: var(--font-mono); }
93
+ .hidden { display: none !important; }
94
+ h1, h2, h3 { font-family: var(--font-brand); font-weight: var(--weight-medium); margin: 0; }
95
+ h4 { font-family: var(--font-brand); font-weight: var(--weight-medium); font-size: var(--text-base); margin: 0; }
96
+ h5 { font-family: var(--font-brand); font-weight: var(--weight-medium); font-size: var(--text-sm); margin: 0; }
97
+ h6 { font-family: var(--font-brand); font-weight: var(--weight-medium); font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.04em; margin: 0; }
98
+
99
+ a { color: var(--accent-hi); text-decoration: none; transition: color var(--dur-base) ease; }
100
+ a:hover { color: var(--accent); }
101
+
102
+ /* ---------- semantic prose elements ---------- */
103
+
104
+ ul, ol {
105
+ margin: var(--space-10) 0;
106
+ padding-inline-start: var(--space-24);
107
+ color: var(--text-dim);
108
+ font-size: var(--text-xs);
109
+ line-height: var(--leading-relaxed);
110
+ }
111
+ ul ul, ul ol, ol ul, ol ol { margin: var(--space-4) 0; }
112
+ li { margin-bottom: var(--space-4); }
113
+
114
+ blockquote {
115
+ margin: var(--space-16) 0;
116
+ padding: var(--space-12) var(--space-20);
117
+ border-inline-start: 2px solid var(--accent);
118
+ background: var(--surface);
119
+ color: var(--text-dim);
120
+ font-size: var(--text-xs);
121
+ line-height: var(--leading-relaxed);
122
+ }
123
+
124
+ pre {
125
+ margin: var(--space-12) 0;
126
+ padding: var(--space-14);
127
+ background: var(--surface);
128
+ border: 1px solid var(--border);
129
+ overflow-x: auto;
130
+ font-family: var(--font-mono);
131
+ font-size: var(--text-2xs);
132
+ line-height: var(--leading-relaxed);
133
+ color: var(--text);
134
+ }
135
+ pre code {
136
+ background: none;
137
+ border: none;
138
+ padding: 0;
139
+ font-size: inherit;
140
+ }
141
+ code {
142
+ font-family: var(--font-mono);
143
+ font-size: var(--text-3xs);
144
+ background: var(--surface-2);
145
+ border: 1px solid var(--border);
146
+ padding: var(--space-1) var(--space-4);
147
+ color: var(--text);
148
+ }
149
+
150
+ table {
151
+ border-collapse: collapse;
152
+ width: 100%;
153
+ font-size: var(--text-xs);
154
+ line-height: var(--leading-normal);
155
+ margin: var(--space-12) 0;
156
+ }
157
+ table th {
158
+ text-align: start;
159
+ font-weight: var(--weight-medium);
160
+ font-size: var(--text-2xs);
161
+ color: var(--text);
162
+ padding: var(--space-8) var(--space-10);
163
+ border-bottom: 1px solid var(--border-2);
164
+ }
165
+ table td {
166
+ padding: var(--space-6) var(--space-10);
167
+ border-bottom: 1px solid var(--border);
168
+ color: var(--text-dim);
169
+ }
170
+
171
+ dl { margin: var(--space-12) 0; }
172
+ dt {
173
+ font-weight: var(--weight-medium);
174
+ font-size: var(--text-xs);
175
+ color: var(--text);
176
+ margin-top: var(--space-10);
177
+ }
178
+ dd {
179
+ margin: var(--space-4) 0 0 var(--space-24);
180
+ font-size: var(--text-xs);
181
+ color: var(--text-dim);
182
+ line-height: var(--leading-relaxed);
183
+ }
184
+
185
+ hr {
186
+ border: none;
187
+ border-top: 1px solid var(--border-2);
188
+ margin: var(--space-24) 0;
189
+ }
190
+
191
+ figure {
192
+ margin: var(--space-16) 0;
193
+ padding: 0;
194
+ }
195
+ figcaption {
196
+ margin-top: var(--space-8);
197
+ font-family: var(--font-mono);
198
+ font-size: var(--text-2xs);
199
+ color: var(--text-faint);
200
+ }
201
+
202
+ /* grain overlay */
203
+ body::after {
204
+ content: "";
205
+ position: fixed;
206
+ inset: 0;
207
+ z-index: var(--z-grain);
208
+ pointer-events: none;
209
+ opacity: var(--grain-opacity);
210
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='0.55'/%3E%3C/svg%3E");
211
+ }
212
+
213
+ /* custom scrollbars */
214
+ * { scrollbar-width: thin; scrollbar-color: var(--border-2) transparent; }
215
+ *::-webkit-scrollbar { width: 8px; height: 8px; }
216
+ *::-webkit-scrollbar-track { background: transparent; }
217
+ *::-webkit-scrollbar-thumb { background: var(--scroll-thumb); border: 2px solid var(--bg); }
218
+ *::-webkit-scrollbar-thumb:hover { background: var(--accent); }
219
+
220
+ /* focus rings */
221
+ :focus { outline: none; }
222
+ :focus-visible {
223
+ outline: 1px solid var(--accent);
224
+ outline-offset: 2px;
225
+ box-shadow: 0 0 12px var(--accent-glow);
226
+ }
227
+
228
+ /* ---------- reduced motion ---------- */
229
+ /* Blanket motion suppression: near-instant durations disable visible
230
+ animation/transition without breaking layout-dependent transforms.
231
+ The spinner (.spin) keeps rotating but completes instantly per cycle. */
232
+ @media (prefers-reduced-motion: reduce) {
233
+ *, *::before, *::after {
234
+ animation-duration: 0.01ms !important;
235
+ animation-iteration-count: 1 !important;
236
+ transition-duration: 0.01ms !important;
237
+ }
238
+ /* The spinner must still rotate continuously (infinite), but with no
239
+ visible motion: near-instant duration + restored iteration. */
240
+ .spin {
241
+ animation-iteration-count: infinite !important;
242
+ }
243
+ #tm-content {
244
+ scroll-behavior: auto !important;
245
+ }
246
+ }
247
+
248
+ /* ---------- print ---------- */
249
+ /* Counters the Firefox first-page-only bug with overflow:hidden shells:
250
+ everything becomes static and visible so the full content area prints.
251
+ Sidebar, topbar, and footer are hidden — only the main content prints. */
252
+ @media print {
253
+ body {
254
+ overflow: visible !important;
255
+ user-select: text !important;
256
+ }
257
+ #tm-content {
258
+ overflow: visible !important;
259
+ height: auto !important;
260
+ max-height: none !important;
261
+ position: static !important;
262
+ }
263
+ #tm-sidebar,
264
+ #tm-topbar,
265
+ .gallery-footer,
266
+ #tm-toast-root,
267
+ #tm-ctx-root {
268
+ display: none !important;
269
+ }
270
+ /* Remove fixed/absolute positioning from shell chrome so content flows */
271
+ #tm-app {
272
+ display: block !important;
273
+ height: auto !important;
274
+ overflow: visible !important;
275
+ }
276
+ /* Grain overlay must not print */
277
+ body::after {
278
+ display: none !important;
279
+ }
280
+ }