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.
- {tinymoon-0.3.0 → tinymoon-0.4.0}/.gitignore +3 -0
- tinymoon-0.4.0/PKG-INFO +209 -0
- tinymoon-0.4.0/README.md +188 -0
- tinymoon-0.4.0/assets/css/base.css +280 -0
- tinymoon-0.4.0/assets/css/primitives.css +1075 -0
- tinymoon-0.4.0/assets/css/shell.css +224 -0
- tinymoon-0.4.0/assets/css/tokens.css +154 -0
- tinymoon-0.4.0/assets/js/auditor.js +176 -0
- tinymoon-0.4.0/assets/js/controls.js +361 -0
- tinymoon-0.4.0/assets/js/ctxmenu.js +214 -0
- tinymoon-0.4.0/assets/js/datepicker.js +612 -0
- tinymoon-0.4.0/assets/js/extras.js +7 -0
- tinymoon-0.4.0/assets/js/hovercard.js +158 -0
- {tinymoon-0.3.0 → tinymoon-0.4.0}/assets/js/icons.js +6 -1
- tinymoon-0.4.0/assets/js/index.d.ts +290 -0
- tinymoon-0.4.0/assets/js/index.js +18 -0
- tinymoon-0.4.0/assets/js/kernel.js +159 -0
- {tinymoon-0.3.0 → tinymoon-0.4.0}/assets/js/markdown.js +16 -14
- tinymoon-0.4.0/assets/js/modal.js +91 -0
- {tinymoon-0.3.0 → tinymoon-0.4.0}/assets/js/net.js +8 -4
- tinymoon-0.4.0/assets/js/popover.js +42 -0
- tinymoon-0.4.0/assets/js/select.js +268 -0
- {tinymoon-0.3.0 → tinymoon-0.4.0}/assets/js/settings.js +0 -6
- {tinymoon-0.3.0 → tinymoon-0.4.0}/assets/js/shell.js +104 -23
- tinymoon-0.4.0/assets/js/toast.js +155 -0
- tinymoon-0.4.0/assets/js/tooltip.js +122 -0
- {tinymoon-0.3.0 → tinymoon-0.4.0}/assets/js/wiki.js +17 -8
- {tinymoon-0.3.0 → tinymoon-0.4.0}/pyproject.toml +17 -5
- {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/fixtures/clean/clean.html +13 -0
- tinymoon-0.4.0/tests/fixtures/clean/clean.js +42 -0
- tinymoon-0.4.0/tests/fixtures/violations/external-url-attrs.html +10 -0
- tinymoon-0.4.0/tests/fixtures/violations/external-url-importmap.html +14 -0
- tinymoon-0.4.0/tests/fixtures/violations/external-url-template.js +7 -0
- tinymoon-0.4.0/tests/fixtures/violations/external-url-ws.js +3 -0
- tinymoon-0.4.0/tests/fixtures/violations/raw-color-extra-fns.css +5 -0
- tinymoon-0.4.0/tests/js/e2e/conformance-csp.spec.js +87 -0
- tinymoon-0.4.0/tests/js/e2e/conformance-runtime.spec.js +69 -0
- tinymoon-0.4.0/tests/js/e2e/content-first.spec.js +127 -0
- tinymoon-0.4.0/tests/js/e2e/copy.spec.js +173 -0
- tinymoon-0.4.0/tests/js/e2e/ctxmenu.spec.js +168 -0
- tinymoon-0.4.0/tests/js/e2e/datepicker.spec.js +179 -0
- tinymoon-0.4.0/tests/js/e2e/fixtures/auditor-gallery.html +18 -0
- tinymoon-0.4.0/tests/js/e2e/fixtures/content-first.html +136 -0
- tinymoon-0.4.0/tests/js/e2e/fixtures/escape-stack.html +33 -0
- tinymoon-0.4.0/tests/js/e2e/fixtures/rtl.html +97 -0
- tinymoon-0.4.0/tests/js/e2e/fixtures/selector-hygiene.html +77 -0
- tinymoon-0.4.0/tests/js/e2e/forms.spec.js +145 -0
- tinymoon-0.4.0/tests/js/e2e/gallery-characterization.spec.js +61 -0
- tinymoon-0.4.0/tests/js/e2e/gallery.spec.js +9 -0
- tinymoon-0.4.0/tests/js/e2e/modal.spec.js +100 -0
- tinymoon-0.4.0/tests/js/e2e/red-bugs.spec.js +51 -0
- tinymoon-0.4.0/tests/js/e2e/reduced-motion.spec.js +55 -0
- tinymoon-0.4.0/tests/js/e2e/responsive.spec.js +187 -0
- tinymoon-0.4.0/tests/js/e2e/selector-hygiene.spec.js +112 -0
- tinymoon-0.4.0/tests/js/e2e/shell-semantics.spec.js +98 -0
- tinymoon-0.4.0/tests/js/e2e/toast.spec.js +81 -0
- tinymoon-0.4.0/tests/js/e2e/tooltip-hovercard.spec.js +156 -0
- tinymoon-0.4.0/tests/js/unit/api-convention.test.js +256 -0
- tinymoon-0.4.0/tests/js/unit/controls.test.js +425 -0
- tinymoon-0.4.0/tests/js/unit/copyable.test.js +140 -0
- tinymoon-0.4.0/tests/js/unit/datepicker.test.js +134 -0
- tinymoon-0.4.0/tests/js/unit/dom.test.js +13 -0
- tinymoon-0.4.0/tests/js/unit/hovercard.test.js +96 -0
- tinymoon-0.4.0/tests/js/unit/icons.test.js +26 -0
- tinymoon-0.4.0/tests/js/unit/import-graph.test.js +88 -0
- tinymoon-0.4.0/tests/js/unit/kernel.test.js +197 -0
- tinymoon-0.4.0/tests/js/unit/markdown.test.js +54 -0
- tinymoon-0.4.0/tests/js/unit/red-bugs.test.js +102 -0
- tinymoon-0.4.0/tests/js/unit/select.test.js +231 -0
- tinymoon-0.4.0/tests/js/unit/settings.test.js +36 -0
- tinymoon-0.4.0/tests/js/unit/toast.test.js +165 -0
- tinymoon-0.4.0/tests/js/unit/tooltip.test.js +115 -0
- tinymoon-0.4.0/tests/test_budgets.py +137 -0
- tinymoon-0.4.0/tests/test_charter.py +197 -0
- {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/test_checker.py +48 -2
- tinymoon-0.4.0/tests/test_contrast.py +182 -0
- {tinymoon-0.3.0 → tinymoon-0.4.0}/tinymoon/__init__.py +1 -1
- {tinymoon-0.3.0 → tinymoon-0.4.0}/tinymoon/checker.py +156 -32
- {tinymoon-0.3.0 → tinymoon-0.4.0}/tinymoon/cli.py +1 -1
- tinymoon-0.3.0/.github/workflows/ci-go.yml +0 -26
- tinymoon-0.3.0/.github/workflows/ci-pypi.yml +0 -33
- tinymoon-0.3.0/.github/workflows/publish.yml +0 -202
- tinymoon-0.3.0/.npmignore +0 -34
- tinymoon-0.3.0/.rlsbl/bases/.github/workflows/ci-go.yml +0 -26
- tinymoon-0.3.0/.rlsbl/bases/.github/workflows/ci-pypi.yml +0 -33
- tinymoon-0.3.0/.rlsbl/bases/.github/workflows/publish.yml +0 -192
- tinymoon-0.3.0/.rlsbl/bases/.gitignore +0 -18
- tinymoon-0.3.0/.rlsbl/bases/.npmignore +0 -34
- tinymoon-0.3.0/.rlsbl/bases/.rlsbl/lint/go.toml +0 -17
- tinymoon-0.3.0/.rlsbl/bases/.rlsbl/lint/npm.toml +0 -19
- tinymoon-0.3.0/.rlsbl/bases/.rlsbl/lint/python.toml +0 -25
- tinymoon-0.3.0/.rlsbl/bases/CHANGELOG.md +0 -5
- tinymoon-0.3.0/.rlsbl/bases/VERSION +0 -1
- tinymoon-0.3.0/.rlsbl/changes/0.0.1.jsonl +0 -1
- tinymoon-0.3.0/.rlsbl/changes/0.0.1.md +0 -7
- tinymoon-0.3.0/.rlsbl/changes/0.1.0.jsonl +0 -16
- tinymoon-0.3.0/.rlsbl/changes/0.1.0.md +0 -15
- tinymoon-0.3.0/.rlsbl/changes/0.2.0.jsonl +0 -18
- tinymoon-0.3.0/.rlsbl/changes/0.2.0.md +0 -24
- tinymoon-0.3.0/.rlsbl/changes/0.3.0.jsonl +0 -2
- tinymoon-0.3.0/.rlsbl/changes/0.3.0.md +0 -7
- tinymoon-0.3.0/.rlsbl/changes/unreleased.jsonl +0 -0
- tinymoon-0.3.0/.rlsbl/config.json +0 -23
- tinymoon-0.3.0/.rlsbl/lint/go.toml +0 -17
- tinymoon-0.3.0/.rlsbl/lint/npm.toml +0 -19
- tinymoon-0.3.0/.rlsbl/lint/python.toml +0 -25
- tinymoon-0.3.0/.rlsbl/managed-files.json +0 -16
- tinymoon-0.3.0/.rlsbl/releases/v0.0.1.toml +0 -12
- tinymoon-0.3.0/.rlsbl/releases/v0.2.0.toml +0 -14
- tinymoon-0.3.0/.rlsbl/releases/v0.3.0.toml +0 -12
- tinymoon-0.3.0/.rlsbl/version +0 -1
- tinymoon-0.3.0/.strictcli/schema.json +0 -66
- tinymoon-0.3.0/CHANGELOG.md +0 -60
- tinymoon-0.3.0/CLAUDE.md +0 -21
- tinymoon-0.3.0/PKG-INFO +0 -131
- tinymoon-0.3.0/README.md +0 -110
- tinymoon-0.3.0/VERSION +0 -1
- tinymoon-0.3.0/assets/css/base.css +0 -113
- tinymoon-0.3.0/assets/css/primitives.css +0 -627
- tinymoon-0.3.0/assets/css/shell.css +0 -135
- tinymoon-0.3.0/assets/css/tokens.css +0 -99
- tinymoon-0.3.0/assets/js/controls.js +0 -82
- tinymoon-0.3.0/assets/js/ctxmenu.js +0 -97
- tinymoon-0.3.0/assets/js/format.js +0 -9
- tinymoon-0.3.0/assets/js/index.js +0 -18
- tinymoon-0.3.0/assets/js/modal.js +0 -63
- tinymoon-0.3.0/assets/js/popover.js +0 -46
- tinymoon-0.3.0/assets/js/select.js +0 -138
- tinymoon-0.3.0/assets/js/toast.js +0 -47
- tinymoon-0.3.0/assets/js/tooltip.js +0 -106
- tinymoon-0.3.0/gallery/gallery.css +0 -81
- tinymoon-0.3.0/gallery/gallery.js +0 -654
- tinymoon-0.3.0/gallery/index.html +0 -19
- tinymoon-0.3.0/go.mod +0 -3
- tinymoon-0.3.0/index.js +0 -2
- tinymoon-0.3.0/package.json +0 -29
- tinymoon-0.3.0/tests/fixtures/clean/clean.js +0 -16
- tinymoon-0.3.0/tests/test_budgets.py +0 -72
- tinymoon-0.3.0/tinymoon.go +0 -36
- tinymoon-0.3.0/uv.lock +0 -92
- {tinymoon-0.3.0 → tinymoon-0.4.0}/LICENSE +0 -0
- {tinymoon-0.3.0 → tinymoon-0.4.0}/assets/fonts/ibm-plex-mono-latin-400.woff2 +0 -0
- {tinymoon-0.3.0 → tinymoon-0.4.0}/assets/fonts/ibm-plex-mono-latin-500.woff2 +0 -0
- {tinymoon-0.3.0 → tinymoon-0.4.0}/assets/fonts/ibm-plex-sans-latin.woff2 +0 -0
- {tinymoon-0.3.0 → tinymoon-0.4.0}/assets/fonts/space-grotesk-latin.woff2 +0 -0
- {tinymoon-0.3.0 → tinymoon-0.4.0}/assets/js/dom.js +0 -0
- {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/fixtures/clean/clean.css +0 -0
- {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/fixtures/clean/tinymoon-allowlist.txt +0 -0
- {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/fixtures/violations/border-radius.css +0 -0
- {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/fixtures/violations/border-radius.html +0 -0
- {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/fixtures/violations/border-radius.js +0 -0
- {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/fixtures/violations/external-url.css +0 -0
- {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/fixtures/violations/external-url.html +0 -0
- {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/fixtures/violations/external-url.js +0 -0
- {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/fixtures/violations/native-control.html +0 -0
- {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/fixtures/violations/native-control.js +0 -0
- {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/fixtures/violations/raw-color.css +0 -0
- {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/fixtures/violations/raw-color.html +0 -0
- {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/fixtures/violations/raw-color.js +0 -0
- {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/fixtures/violations/title-attr.html +0 -0
- {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/fixtures/violations/title-attr.js +0 -0
- {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/test_assets.py +0 -0
- {tinymoon-0.3.0 → tinymoon-0.4.0}/tests/test_version.py +0 -0
- /tinymoon-0.3.0/.rlsbl/bases/.rlsbl/changes/unreleased.jsonl → /tinymoon-0.4.0/tinymoon/py.typed +0 -0
tinymoon-0.4.0/PKG-INFO
ADDED
|
@@ -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
|
tinymoon-0.4.0/README.md
ADDED
|
@@ -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
|
+
}
|