@sethmakes/css 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.css ADDED
@@ -0,0 +1,34 @@
1
+ /*
2
+ * @sethmakes/css
3
+ *
4
+ * Class-based styles for native HTML. Zero JS. References semantic tokens
5
+ * only (see @sethmakes/tokens). All rules live in cascade layers so
6
+ * consumer styles win without specificity wars.
7
+ *
8
+ * One file per component under src/; this index is the public entry.
9
+ *
10
+ * CONSUMPTION CONTRACT: this package defines no tokens — import
11
+ * @sethmakes/tokens (and optionally its fonts.css) BEFORE this file, or
12
+ * every var(--mk-*) resolves to nothing. Kept separate on purpose so
13
+ * tokens-only consumption stays possible.
14
+ */
15
+
16
+ /* mk.utilities is declared-but-empty: reserved so adding utilities later
17
+ can't reorder the cascade. */
18
+ @layer mk.reset, mk.base, mk.components, mk.utilities;
19
+
20
+ @import "./src/reset.css";
21
+ @import "./src/base.css";
22
+ @import "./src/prose.css";
23
+ @import "./src/button.css";
24
+ @import "./src/badge.css";
25
+ @import "./src/forms.css";
26
+ @import "./src/card.css";
27
+ @import "./src/table.css";
28
+ @import "./src/divider.css";
29
+ @import "./src/disclosure.css";
30
+ @import "./src/alert.css";
31
+ @import "./src/spinner.css";
32
+ @import "./src/progress.css";
33
+ @import "./src/media.css";
34
+ @import "./src/empty.css";
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@sethmakes/css",
3
+ "version": "0.0.0",
4
+ "description": "Class-based styles for native HTML in the sethmakes design language.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": "./index.css",
9
+ "./index.css": "./index.css"
10
+ },
11
+ "files": [
12
+ "index.css",
13
+ "src"
14
+ ],
15
+ "dependencies": {
16
+ "@sethmakes/tokens": "0.0.0"
17
+ },
18
+ "keywords": [
19
+ "css",
20
+ "design-system"
21
+ ]
22
+ }
package/src/alert.css ADDED
@@ -0,0 +1,46 @@
1
+ @layer mk.components {
2
+ /*
3
+ * Alert — a subtle filled status block. No border (none exist anywhere);
4
+ * the tinted background carries the meaning. Info uses the moss accent in
5
+ * its subtle role; success/warning/danger use the functional status hues.
6
+ * On-color text comes from the matching -subtle pairing.
7
+ */
8
+ .mk-alert {
9
+ display: flex;
10
+ flex-direction: column;
11
+ gap: var(--mk-space-1);
12
+ border-radius: var(--mk-radius-surface);
13
+ padding: var(--mk-space-3) var(--mk-space-4);
14
+ background: var(--mk-color-accent-subtle);
15
+ color: var(--mk-color-on-accent-subtle);
16
+ font-size: var(--mk-text-sm);
17
+ line-height: var(--mk-leading-normal);
18
+ }
19
+
20
+ /* Bold lead line — terse, sits above the body copy */
21
+ .mk-alert__title {
22
+ font-weight: var(--mk-weight-bold);
23
+ line-height: var(--mk-leading-tight);
24
+ }
25
+
26
+ /* Info — the default; accent-subtle. Listed for explicit opt-in. */
27
+ .mk-alert--info {
28
+ background: var(--mk-color-accent-subtle);
29
+ color: var(--mk-color-on-accent-subtle);
30
+ }
31
+
32
+ .mk-alert--success {
33
+ background: var(--mk-color-success-subtle);
34
+ color: var(--mk-color-success);
35
+ }
36
+
37
+ .mk-alert--warning {
38
+ background: var(--mk-color-warning-subtle);
39
+ color: var(--mk-color-warning);
40
+ }
41
+
42
+ .mk-alert--danger {
43
+ background: var(--mk-color-danger-subtle);
44
+ color: var(--mk-color-danger);
45
+ }
46
+ }
package/src/badge.css ADDED
@@ -0,0 +1,44 @@
1
+ @layer mk.components {
2
+ /* Badge — tonal */
3
+ .mk-badge {
4
+ display: inline-flex;
5
+ align-items: center;
6
+ gap: var(--mk-space-2);
7
+ border-radius: var(--mk-radius-pill);
8
+ background: var(--mk-color-surface-3);
9
+ color: var(--mk-color-text-muted);
10
+ font-size: var(--mk-text-xs);
11
+ font-weight: var(--mk-weight-medium);
12
+ padding: 0.25em 0.9em;
13
+ }
14
+
15
+ .mk-badge--accent {
16
+ background: var(--mk-color-accent-subtle);
17
+ color: var(--mk-color-on-accent-subtle);
18
+ }
19
+
20
+ .mk-badge--success {
21
+ background: var(--mk-color-success-subtle);
22
+ color: var(--mk-color-success);
23
+ }
24
+
25
+ .mk-badge--warning {
26
+ background: var(--mk-color-warning-subtle);
27
+ color: var(--mk-color-warning);
28
+ }
29
+
30
+ .mk-badge--danger {
31
+ background: var(--mk-color-danger-subtle);
32
+ color: var(--mk-color-danger);
33
+ }
34
+
35
+ /* Status dot — leading indicator in currentColor */
36
+ .mk-badge--dot::before {
37
+ content: "";
38
+ width: 0.5em;
39
+ height: 0.5em;
40
+ border-radius: var(--mk-radius-pill);
41
+ background: currentColor;
42
+ flex: none;
43
+ }
44
+ }
package/src/base.css ADDED
@@ -0,0 +1,68 @@
1
+ @layer mk.base {
2
+ body {
3
+ background: var(--mk-color-bg);
4
+ color: var(--mk-color-text);
5
+ font-family: var(--mk-font-body);
6
+ font-size: var(--mk-text-md);
7
+ font-weight: var(--mk-weight-regular);
8
+ line-height: var(--mk-leading-normal);
9
+ -webkit-font-smoothing: antialiased;
10
+ }
11
+
12
+ h1,
13
+ h2,
14
+ h3,
15
+ h4 {
16
+ font-weight: var(--mk-weight-bold);
17
+ line-height: var(--mk-leading-tight);
18
+ margin-block: 0 var(--mk-space-3);
19
+ }
20
+
21
+ h1 {
22
+ font-size: var(--mk-text-3xl);
23
+ }
24
+
25
+ h2 {
26
+ font-size: var(--mk-text-2xl);
27
+ }
28
+
29
+ h3 {
30
+ font-size: var(--mk-text-xl);
31
+ }
32
+
33
+ h4 {
34
+ font-size: var(--mk-text-lg);
35
+ }
36
+
37
+ a {
38
+ color: var(--mk-color-accent);
39
+ text-decoration: none;
40
+ transition: color var(--mk-motion-quick) var(--mk-ease-out);
41
+ }
42
+
43
+ a:hover {
44
+ color: var(--mk-color-accent-hover);
45
+ text-decoration: underline;
46
+ }
47
+
48
+ :focus-visible {
49
+ outline: 2px solid var(--mk-color-focus);
50
+ outline-offset: 2px;
51
+ }
52
+
53
+ /*
54
+ * Global reduced-motion guard. Motion is "quietly alive" by default, but
55
+ * honor the user's OS-level request to cut it: collapse every transition
56
+ * and animation site-wide rather than relying on each component to remember.
57
+ */
58
+ @media (prefers-reduced-motion: reduce) {
59
+ *,
60
+ *::before,
61
+ *::after {
62
+ animation-duration: 0.01ms !important;
63
+ animation-iteration-count: 1 !important;
64
+ transition-duration: 0.01ms !important;
65
+ scroll-behavior: auto !important;
66
+ }
67
+ }
68
+ }
package/src/button.css ADDED
@@ -0,0 +1,122 @@
1
+ @layer mk.components {
2
+ /* Button — tonal by default, accent-filled for primary */
3
+ .mk-btn {
4
+ display: inline-flex;
5
+ align-items: center;
6
+ justify-content: center;
7
+ gap: var(--mk-space-2);
8
+ border: 0;
9
+ border-radius: var(--mk-radius-pill);
10
+ background: var(--mk-color-surface-3);
11
+ color: var(--mk-color-text);
12
+ font: inherit;
13
+ font-size: var(--mk-text-sm);
14
+ font-weight: var(--mk-weight-medium);
15
+ padding: 0.6em 1.3em;
16
+ min-height: 44px;
17
+ cursor: pointer;
18
+ transition:
19
+ background var(--mk-motion-quick) var(--mk-ease-out),
20
+ transform var(--mk-motion-quick) var(--mk-ease-spring);
21
+ }
22
+
23
+ .mk-btn:hover {
24
+ background: color-mix(in oklch, var(--mk-color-surface-3), var(--mk-color-text) 8%);
25
+ }
26
+
27
+ .mk-btn:active {
28
+ transform: scale(0.97);
29
+ }
30
+
31
+ .mk-btn--primary {
32
+ background: var(--mk-color-accent);
33
+ color: var(--mk-color-accent-contrast);
34
+ }
35
+
36
+ .mk-btn--primary:hover {
37
+ background: var(--mk-color-accent-hover);
38
+ }
39
+
40
+ .mk-btn--ghost {
41
+ background: transparent;
42
+ }
43
+
44
+ .mk-btn--ghost:hover {
45
+ background: var(--mk-color-surface-2);
46
+ }
47
+
48
+ /* Danger — destructive fill; contrast text reads on the saturated fill */
49
+ .mk-btn--danger {
50
+ background: var(--mk-color-danger);
51
+ color: var(--mk-color-accent-contrast);
52
+ }
53
+
54
+ .mk-btn--danger:hover {
55
+ background: color-mix(in oklch, var(--mk-color-danger), var(--mk-color-text) 12%);
56
+ }
57
+
58
+ /* Sizes */
59
+ .mk-btn--sm {
60
+ font-size: var(--mk-text-xs);
61
+ padding: 0.45em 1.1em;
62
+ min-height: 36px;
63
+ }
64
+
65
+ .mk-btn--lg {
66
+ font-size: var(--mk-text-md);
67
+ padding: 0.7em 1.6em;
68
+ min-height: 52px;
69
+ }
70
+
71
+ /* Full-width block */
72
+ .mk-btn--block {
73
+ display: flex;
74
+ width: 100%;
75
+ }
76
+
77
+ /* Icon-only — square, single glyph/svg */
78
+ .mk-btn--icon {
79
+ padding: 0;
80
+ width: 44px;
81
+ min-width: 44px;
82
+ height: 44px;
83
+ min-height: 44px;
84
+ }
85
+
86
+ .mk-btn--icon svg {
87
+ width: 1.25em;
88
+ height: 1.25em;
89
+ }
90
+
91
+ /* Disabled — dim, no hover lift, default cursor */
92
+ .mk-btn:disabled,
93
+ .mk-btn[aria-disabled="true"] {
94
+ opacity: 0.5;
95
+ cursor: default;
96
+ }
97
+
98
+ .mk-btn:disabled:hover,
99
+ .mk-btn[aria-disabled="true"]:hover {
100
+ background: var(--mk-color-surface-3);
101
+ }
102
+
103
+ .mk-btn--primary:disabled:hover,
104
+ .mk-btn--primary[aria-disabled="true"]:hover {
105
+ background: var(--mk-color-accent);
106
+ }
107
+
108
+ .mk-btn--ghost:disabled:hover,
109
+ .mk-btn--ghost[aria-disabled="true"]:hover {
110
+ background: transparent;
111
+ }
112
+
113
+ .mk-btn--danger:disabled:hover,
114
+ .mk-btn--danger[aria-disabled="true"]:hover {
115
+ background: var(--mk-color-danger);
116
+ }
117
+
118
+ .mk-btn:disabled:active,
119
+ .mk-btn[aria-disabled="true"]:active {
120
+ transform: none;
121
+ }
122
+ }
package/src/card.css ADDED
@@ -0,0 +1,19 @@
1
+ @layer mk.components {
2
+ /* Card — raised tonal surface */
3
+ .mk-card {
4
+ background: var(--mk-color-surface-1);
5
+ border-radius: var(--mk-radius-surface);
6
+ padding: var(--mk-space-5);
7
+ }
8
+
9
+ /* Sunken — recedes to surface-2 for nesting inside a card */
10
+ .mk-card--sunken {
11
+ background: var(--mk-color-surface-2);
12
+ }
13
+
14
+ /* Flush — no padding, for media that bleeds to the edge */
15
+ .mk-card--flush {
16
+ padding: 0;
17
+ overflow: hidden;
18
+ }
19
+ }
@@ -0,0 +1,64 @@
1
+ @layer mk.components {
2
+ /* Disclosure — native details/summary, styled tonal */
3
+ .mk-disclosure {
4
+ background: var(--mk-color-surface-1);
5
+ border-radius: var(--mk-radius-surface);
6
+ }
7
+
8
+ .mk-disclosure > summary {
9
+ display: flex;
10
+ align-items: center;
11
+ gap: var(--mk-space-3);
12
+ min-height: 44px;
13
+ padding: var(--mk-space-3) var(--mk-space-4);
14
+ color: var(--mk-color-text);
15
+ font-weight: var(--mk-weight-medium);
16
+ cursor: pointer;
17
+ list-style: none;
18
+ transition: background var(--mk-motion-quick) var(--mk-ease-out);
19
+ }
20
+
21
+ /* Strip the native marker in every engine */
22
+ .mk-disclosure > summary::-webkit-details-marker {
23
+ display: none;
24
+ }
25
+
26
+ .mk-disclosure > summary:hover {
27
+ background: var(--mk-color-surface-2);
28
+ }
29
+
30
+ /*
31
+ * Custom marker — a chevron that rotates open. Drawn with a currentColor
32
+ * background SVG (no border-*, matching the select caret technique) so the
33
+ * zero-border rule holds uniformly for decorative glyphs.
34
+ */
35
+ .mk-disclosure > summary::before {
36
+ content: "";
37
+ flex: none;
38
+ width: 0.7em;
39
+ height: 0.7em;
40
+ color: var(--mk-color-text-muted);
41
+ background: currentColor;
42
+ mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3' stroke-linecap='square'%3E%3Cpath d='M9 6l6 6-6 6'/%3E%3C/svg%3E")
43
+ center / contain no-repeat;
44
+ transform-origin: center;
45
+ }
46
+
47
+ @media (prefers-reduced-motion: no-preference) {
48
+ .mk-disclosure > summary::before {
49
+ transition: transform var(--mk-motion-standard) var(--mk-ease-out);
50
+ }
51
+ }
52
+
53
+ .mk-disclosure[open] > summary::before {
54
+ transform: rotate(90deg);
55
+ }
56
+
57
+ /* Content body — tonal recess under the summary */
58
+ .mk-disclosure__body {
59
+ padding: var(--mk-space-2) var(--mk-space-4) var(--mk-space-4);
60
+ color: var(--mk-color-text-muted);
61
+ font-size: var(--mk-text-sm);
62
+ line-height: var(--mk-leading-normal);
63
+ }
64
+ }
@@ -0,0 +1,29 @@
1
+ @layer mk.components {
2
+ /* Divider — a 1px tonal line drawn as a background shift, not a border */
3
+ .mk-divider {
4
+ height: 1px;
5
+ border: 0;
6
+ margin: var(--mk-space-5) 0;
7
+ background: var(--mk-color-surface-3);
8
+ }
9
+
10
+ /* Labelled variant — centered text flanked by tonal rules */
11
+ .mk-divider--label {
12
+ height: auto;
13
+ background: none;
14
+ display: flex;
15
+ align-items: center;
16
+ gap: var(--mk-space-3);
17
+ color: var(--mk-color-text-muted);
18
+ font-size: var(--mk-text-xs);
19
+ font-weight: var(--mk-weight-medium);
20
+ }
21
+
22
+ .mk-divider--label::before,
23
+ .mk-divider--label::after {
24
+ content: "";
25
+ flex: 1;
26
+ height: 1px;
27
+ background: var(--mk-color-surface-3);
28
+ }
29
+ }
package/src/empty.css ADDED
@@ -0,0 +1,37 @@
1
+ @layer mk.components {
2
+ /*
3
+ * Empty state — a centered tonal block for "nothing here yet". Surface-2
4
+ * sets it apart from the page without a border; muted text keeps it quiet,
5
+ * and the optional action sits below with comfortable spacing.
6
+ */
7
+ .mk-empty {
8
+ display: flex;
9
+ flex-direction: column;
10
+ align-items: center;
11
+ gap: var(--mk-space-3);
12
+ border-radius: var(--mk-radius-surface);
13
+ padding: var(--mk-space-7) var(--mk-space-5);
14
+ background: var(--mk-color-surface-2);
15
+ color: var(--mk-color-text-muted);
16
+ text-align: center;
17
+ }
18
+
19
+ /* Optional bold lead line above the muted message */
20
+ .mk-empty__title {
21
+ color: var(--mk-color-text);
22
+ font-weight: var(--mk-weight-bold);
23
+ font-size: var(--mk-text-lg);
24
+ line-height: var(--mk-leading-tight);
25
+ }
26
+
27
+ .mk-empty__message {
28
+ font-size: var(--mk-text-sm);
29
+ line-height: var(--mk-leading-normal);
30
+ max-width: 32ch;
31
+ }
32
+
33
+ /* Action slot — extra breathing room above the CTA */
34
+ .mk-empty__action {
35
+ margin-top: var(--mk-space-2);
36
+ }
37
+ }
package/src/forms.css ADDED
@@ -0,0 +1,279 @@
1
+ @layer mk.components {
2
+ /* Form controls — filled (zero-border consequence) */
3
+ .mk-input,
4
+ .mk-select,
5
+ .mk-textarea {
6
+ border: 0;
7
+ border-radius: var(--mk-radius-control);
8
+ background: var(--mk-color-field);
9
+ color: var(--mk-color-text);
10
+ font: inherit;
11
+ font-size: max(16px, 1em); /* prevent iOS zoom */
12
+ padding: 0.6em 0.9em;
13
+ min-height: 44px;
14
+ width: 100%;
15
+ transition: background var(--mk-motion-quick) var(--mk-ease-out);
16
+ }
17
+
18
+ .mk-input::placeholder,
19
+ .mk-textarea::placeholder {
20
+ color: var(--mk-color-text-faint);
21
+ }
22
+
23
+ .mk-input:hover,
24
+ .mk-select:hover,
25
+ .mk-textarea:hover {
26
+ background: color-mix(in oklch, var(--mk-color-field), var(--mk-color-text) 4%);
27
+ }
28
+
29
+ /*
30
+ * Select — drop the native chrome and paint a caret with a background
31
+ * data-URI. The glyph is stroked in currentColor so it tracks the text
32
+ * color across light/dark with no extra tokens. Extra inline-end padding
33
+ * keeps long option text clear of the caret.
34
+ */
35
+ .mk-select {
36
+ appearance: none;
37
+ padding-inline-end: var(--mk-space-7);
38
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='square'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
39
+ background-repeat: no-repeat;
40
+ background-position: right var(--mk-space-4) center;
41
+ background-size: 1.1em;
42
+ }
43
+
44
+ /* Textarea — taller default, vertical resize only */
45
+ .mk-textarea {
46
+ min-height: 7rem;
47
+ resize: vertical;
48
+ line-height: var(--mk-leading-normal);
49
+ }
50
+
51
+ /* Field scaffolding */
52
+ .mk-field {
53
+ display: flex;
54
+ flex-direction: column;
55
+ gap: var(--mk-space-1);
56
+ }
57
+
58
+ .mk-field > label {
59
+ font-size: var(--mk-text-sm);
60
+ font-weight: var(--mk-weight-medium);
61
+ }
62
+
63
+ .mk-field__help {
64
+ font-size: var(--mk-text-xs);
65
+ color: var(--mk-color-text-muted);
66
+ }
67
+
68
+ /* Error message — danger-tinted, sits below the control */
69
+ .mk-field__error {
70
+ font-size: var(--mk-text-xs);
71
+ color: var(--mk-color-danger);
72
+ }
73
+
74
+ /*
75
+ * Error state — no border (there are none anywhere); the control gets a
76
+ * danger-tinted fill instead, mixed off the field token so it stays a
77
+ * surface, not a loud alert.
78
+ */
79
+ .mk-field--error .mk-input,
80
+ .mk-field--error .mk-select,
81
+ .mk-field--error .mk-textarea {
82
+ background: color-mix(in oklch, var(--mk-color-field), var(--mk-color-danger) 18%);
83
+ }
84
+
85
+ .mk-field--error .mk-input:hover,
86
+ .mk-field--error .mk-select:hover,
87
+ .mk-field--error .mk-textarea:hover {
88
+ background: color-mix(in oklch, var(--mk-color-field), var(--mk-color-danger) 26%);
89
+ }
90
+
91
+ /*
92
+ * Fieldset — native chrome reset; the legend reads like a field label.
93
+ * Groups related choices (radio sets, switch stacks).
94
+ */
95
+ .mk-fieldset {
96
+ border: 0; /* native-chrome reset, not a design border */
97
+ margin: 0;
98
+ padding: 0;
99
+ display: flex;
100
+ flex-direction: column;
101
+ gap: var(--mk-space-1);
102
+ }
103
+
104
+ .mk-fieldset > legend {
105
+ padding: 0;
106
+ margin-bottom: var(--mk-space-2);
107
+ font-size: var(--mk-text-sm);
108
+ font-weight: var(--mk-weight-medium);
109
+ }
110
+
111
+ /*
112
+ * Choice row — a 44px touch target wrapping a 24px control + label.
113
+ * Used for checkboxes and radios; the whole row is comfortably tappable.
114
+ */
115
+ .mk-choice {
116
+ display: flex;
117
+ align-items: center;
118
+ gap: var(--mk-space-3);
119
+ min-height: 44px;
120
+ cursor: pointer;
121
+ }
122
+
123
+ .mk-choice > span {
124
+ font-size: var(--mk-text-md);
125
+ }
126
+
127
+ /*
128
+ * Checkbox & radio — strip native chrome, paint a 24px filled tonal box.
129
+ * Checkbox stays sharp (zero radius); radio is a true circle because a
130
+ * circle is geometry, not softness.
131
+ *
132
+ * Affordance ring: field-vs-surface-1 is only ~1.1:1, so an unchecked box
133
+ * is nearly invisible on a card. We add an inset ring (not a border — the
134
+ * zero-border rule forbids border-*) in text-muted, which clears 3:1
135
+ * against every surface (WCAG 1.4.11 non-text contrast). The checked state
136
+ * drops the ring since the accent fill is its own affordance.
137
+ */
138
+ .mk-checkbox,
139
+ .mk-radio {
140
+ appearance: none;
141
+ flex: none;
142
+ width: 24px;
143
+ height: 24px;
144
+ margin: 0;
145
+ background: var(--mk-color-field);
146
+ color: var(--mk-color-accent-contrast);
147
+ box-shadow: inset 0 0 0 2px var(--mk-color-text-muted);
148
+ display: grid;
149
+ place-content: center;
150
+ cursor: pointer;
151
+ transition:
152
+ background var(--mk-motion-quick) var(--mk-ease-out),
153
+ box-shadow var(--mk-motion-quick) var(--mk-ease-out);
154
+ }
155
+
156
+ .mk-checkbox {
157
+ border-radius: var(--mk-radius-control);
158
+ }
159
+
160
+ .mk-radio {
161
+ border-radius: 50%;
162
+ }
163
+
164
+ .mk-checkbox:hover,
165
+ .mk-radio:hover {
166
+ background: color-mix(in oklch, var(--mk-color-field), var(--mk-color-text) 8%);
167
+ }
168
+
169
+ /* Checked — accent fill with a glyph drawn via ::before; the fill is its
170
+ own affordance, so the inset ring is dropped. */
171
+ .mk-checkbox:checked,
172
+ .mk-radio:checked {
173
+ background: var(--mk-color-accent);
174
+ box-shadow: none;
175
+ }
176
+
177
+ .mk-checkbox:checked:hover,
178
+ .mk-radio:checked:hover {
179
+ background: var(--mk-color-accent-hover);
180
+ }
181
+
182
+ /*
183
+ * Checkmark — a square-cut tick. Drawn as a currentColor block clipped to
184
+ * a check polygon (no border-* — the zero-border rule applies to glyphs
185
+ * too; this mirrors the select caret using a non-border technique).
186
+ */
187
+ .mk-checkbox::before {
188
+ content: "";
189
+ width: 0.8em;
190
+ height: 0.8em;
191
+ background: currentColor;
192
+ clip-path: polygon(
193
+ 14% 44%, 0% 60%, 40% 100%, 100% 18%, 84% 4%, 38% 70%
194
+ );
195
+ transform: scale(0);
196
+ transition: transform var(--mk-motion-quick) var(--mk-ease-spring);
197
+ }
198
+
199
+ .mk-checkbox:checked::before {
200
+ transform: scale(1);
201
+ }
202
+
203
+ /* Radio dot — a solid inner disc */
204
+ .mk-radio::before {
205
+ content: "";
206
+ width: 0.55em;
207
+ height: 0.55em;
208
+ border-radius: 50%;
209
+ background: currentColor;
210
+ transform: scale(0);
211
+ transition: transform var(--mk-motion-quick) var(--mk-ease-spring);
212
+ }
213
+
214
+ .mk-radio:checked::before {
215
+ transform: scale(1);
216
+ }
217
+
218
+ @media (prefers-reduced-motion: reduce) {
219
+ .mk-checkbox::before,
220
+ .mk-radio::before {
221
+ transition: none;
222
+ }
223
+ }
224
+
225
+ /*
226
+ * Switch — a checkbox restyled as a track + sliding thumb. Square thumb
227
+ * (terminal bones: no soft pills), accent track when on, motion-tokened
228
+ * slide. Lives in a .mk-choice row like the other choices.
229
+ */
230
+ .mk-switch {
231
+ appearance: none;
232
+ flex: none;
233
+ width: 44px;
234
+ height: 24px;
235
+ margin: 0;
236
+ padding: 3px;
237
+ background: var(--mk-color-surface-3);
238
+ border-radius: var(--mk-radius-pill);
239
+ cursor: pointer;
240
+ display: flex;
241
+ align-items: center;
242
+ transition: background var(--mk-motion-standard) var(--mk-ease-out);
243
+ }
244
+
245
+ .mk-switch::before {
246
+ content: "";
247
+ width: 18px;
248
+ height: 18px;
249
+ background: var(--mk-color-text-muted);
250
+ border-radius: var(--mk-radius-sm);
251
+ transition:
252
+ transform var(--mk-motion-standard) var(--mk-ease-out),
253
+ background var(--mk-motion-standard) var(--mk-ease-out);
254
+ }
255
+
256
+ .mk-switch:hover {
257
+ background: color-mix(in oklch, var(--mk-color-surface-3), var(--mk-color-text) 8%);
258
+ }
259
+
260
+ .mk-switch:checked {
261
+ background: var(--mk-color-accent);
262
+ }
263
+
264
+ .mk-switch:checked:hover {
265
+ background: var(--mk-color-accent-hover);
266
+ }
267
+
268
+ .mk-switch:checked::before {
269
+ background: var(--mk-color-accent-contrast);
270
+ transform: translateX(20px);
271
+ }
272
+
273
+ @media (prefers-reduced-motion: reduce) {
274
+ .mk-switch,
275
+ .mk-switch::before {
276
+ transition: none;
277
+ }
278
+ }
279
+ }
package/src/media.css ADDED
@@ -0,0 +1,113 @@
1
+ @layer mk.components {
2
+ /*
3
+ * Media thumb — an aspect-ratio box that crops its image to fill. Zero
4
+ * radius (terminal bones). Default ratio is a poster (2/3); square and
5
+ * video variants swap the ratio. The box itself carries a tonal surface
6
+ * so it reads as a slot before any image (or fallback) lands.
7
+ */
8
+ .mk-thumb {
9
+ display: block;
10
+ position: relative;
11
+ aspect-ratio: 2 / 3;
12
+ overflow: hidden;
13
+ background: var(--mk-color-surface-2);
14
+ border-radius: var(--mk-radius-surface);
15
+ }
16
+
17
+ .mk-thumb--square {
18
+ aspect-ratio: 1 / 1;
19
+ }
20
+
21
+ .mk-thumb--video {
22
+ aspect-ratio: 16 / 9;
23
+ }
24
+
25
+ .mk-thumb > img {
26
+ display: block;
27
+ width: 100%;
28
+ height: 100%;
29
+ object-fit: cover;
30
+ }
31
+
32
+ /*
33
+ * Server-rendered no-image path. tv-tracker emits this on the server when
34
+ * a poster is missing: it fills the box with a deeper tonal surface and
35
+ * centers a single large mono initial in muted text. No border, no glyph
36
+ * font — just type weight doing the work.
37
+ */
38
+ .mk-thumb__fallback {
39
+ position: absolute;
40
+ inset: 0;
41
+ display: grid;
42
+ place-content: center;
43
+ background: var(--mk-color-surface-3);
44
+ color: var(--mk-color-text-muted);
45
+ font-size: var(--mk-text-3xl);
46
+ font-weight: var(--mk-weight-bold);
47
+ line-height: var(--mk-leading-tight);
48
+ text-transform: uppercase;
49
+ user-select: none;
50
+ }
51
+
52
+ /*
53
+ * Skeleton — a tonal shimmer placeholder. Animates a gradient sweep
54
+ * between surface-2 and surface-3; under reduced-motion (and as the
55
+ * no-animation fallback) it sits as a static surface-2 block. Fills a
56
+ * thumb when nested, or stands alone as a text-line shim.
57
+ */
58
+ .mk-skeleton {
59
+ background: var(--mk-color-surface-2);
60
+ }
61
+
62
+ .mk-thumb > .mk-skeleton {
63
+ position: absolute;
64
+ inset: 0;
65
+ }
66
+
67
+ /* Text-line skeleton — a single muted bar at line height. */
68
+ .mk-skeleton--text {
69
+ height: 1em;
70
+ line-height: var(--mk-leading-normal);
71
+ }
72
+
73
+ @media (prefers-reduced-motion: no-preference) {
74
+ .mk-skeleton {
75
+ background: linear-gradient(
76
+ 100deg,
77
+ var(--mk-color-surface-2) 30%,
78
+ var(--mk-color-surface-3) 50%,
79
+ var(--mk-color-surface-2) 70%
80
+ );
81
+ background-size: 200% 100%;
82
+ /* slow token x5 keeps the loop tokenized but readable as a sweep */
83
+ animation: mk-skeleton-shimmer calc(var(--mk-motion-slow) * 5) var(--mk-ease-out) infinite;
84
+ }
85
+ }
86
+
87
+ @keyframes mk-skeleton-shimmer {
88
+ from {
89
+ background-position: 200% 0;
90
+ }
91
+ to {
92
+ background-position: -200% 0;
93
+ }
94
+ }
95
+
96
+ /*
97
+ * Figure + caption — wraps a thumb (or any media) with a muted, small
98
+ * caption beneath. The figure carries no chrome; the caption is the only
99
+ * styled part.
100
+ */
101
+ .mk-figure {
102
+ margin: 0;
103
+ display: flex;
104
+ flex-direction: column;
105
+ gap: var(--mk-space-2);
106
+ }
107
+
108
+ .mk-figure > figcaption {
109
+ font-size: var(--mk-text-sm);
110
+ line-height: var(--mk-leading-normal);
111
+ color: var(--mk-color-text-muted);
112
+ }
113
+ }
@@ -0,0 +1,77 @@
1
+ @layer mk.components {
2
+ /* Progress — tonal track, accent fill */
3
+ .mk-progress {
4
+ appearance: none;
5
+ width: 100%;
6
+ height: 0.5rem;
7
+ border: 0;
8
+ border-radius: var(--mk-radius-pill);
9
+ background: var(--mk-color-surface-3);
10
+ overflow: hidden;
11
+ }
12
+
13
+ .mk-progress::-webkit-progress-bar {
14
+ background: var(--mk-color-surface-3);
15
+ border-radius: var(--mk-radius-pill);
16
+ }
17
+
18
+ .mk-progress::-webkit-progress-value {
19
+ background: var(--mk-color-accent);
20
+ border-radius: var(--mk-radius-pill);
21
+ transition: width var(--mk-motion-standard) var(--mk-ease-out);
22
+ }
23
+
24
+ .mk-progress::-moz-progress-bar {
25
+ background: var(--mk-color-accent);
26
+ border-radius: var(--mk-radius-pill);
27
+ }
28
+
29
+ /* Thinner variant */
30
+ .mk-progress--sm {
31
+ height: 0.25rem;
32
+ }
33
+
34
+ /*
35
+ * Indeterminate — a <progress> with no value attribute. Browsers zero out
36
+ * the value bar, so we paint a sliding accent segment on the track itself
37
+ * and let it travel. Reduced motion falls back to a static partial fill so
38
+ * the bar still signals "working" without movement.
39
+ */
40
+ .mk-progress:indeterminate {
41
+ background:
42
+ linear-gradient(
43
+ to right,
44
+ var(--mk-color-accent) 0%,
45
+ var(--mk-color-accent) 40%,
46
+ transparent 40%
47
+ )
48
+ var(--mk-color-surface-3);
49
+ background-repeat: no-repeat;
50
+ background-size: 40% 100%;
51
+ background-position: 0% 0%;
52
+ }
53
+
54
+ .mk-progress:indeterminate::-webkit-progress-bar {
55
+ background: transparent;
56
+ }
57
+
58
+ .mk-progress:indeterminate::-moz-progress-bar {
59
+ background: transparent;
60
+ }
61
+
62
+ @media (prefers-reduced-motion: no-preference) {
63
+ .mk-progress:indeterminate {
64
+ /* slow token x4 keeps the loop tokenized (~1280ms ≈ the original 1.4s). */
65
+ animation: mk-progress-slide calc(var(--mk-motion-slow) * 4) var(--mk-ease-out) infinite;
66
+ }
67
+ }
68
+
69
+ @keyframes mk-progress-slide {
70
+ from {
71
+ background-position: -40% 0%;
72
+ }
73
+ to {
74
+ background-position: 140% 0%;
75
+ }
76
+ }
77
+ }
package/src/prose.css ADDED
@@ -0,0 +1,122 @@
1
+ @layer mk.components {
2
+ /*
3
+ * Prose — article-style long-form content. Scoped to .mk-prose so it never
4
+ * leaks into app chrome. Hierarchy comes from rhythm, weight, and tonal
5
+ * fields — zero borders, zero radius (terminal bones).
6
+ */
7
+ .mk-prose {
8
+ color: var(--mk-color-text);
9
+ max-width: 70ch;
10
+ }
11
+
12
+ /* Vertical rhythm: every flow child gets top spacing, first child none. */
13
+ .mk-prose > * {
14
+ margin-block: var(--mk-space-4) 0;
15
+ }
16
+
17
+ .mk-prose > :first-child {
18
+ margin-block-start: 0;
19
+ }
20
+
21
+ .mk-prose p {
22
+ line-height: var(--mk-leading-normal);
23
+ }
24
+
25
+ /* Headings sit closer to the content they introduce, looser above. */
26
+ .mk-prose h2,
27
+ .mk-prose h3,
28
+ .mk-prose h4 {
29
+ margin-block: var(--mk-space-6) var(--mk-space-2);
30
+ }
31
+
32
+ .mk-prose h2:first-child,
33
+ .mk-prose h3:first-child,
34
+ .mk-prose h4:first-child {
35
+ margin-block-start: 0;
36
+ }
37
+
38
+ /* Lists */
39
+ .mk-prose ul,
40
+ .mk-prose ol {
41
+ padding-inline-start: var(--mk-space-5);
42
+ }
43
+
44
+ .mk-prose li {
45
+ margin-block: var(--mk-space-2) 0;
46
+ }
47
+
48
+ .mk-prose li::marker {
49
+ color: var(--mk-color-text-muted);
50
+ }
51
+
52
+ /* Nested lists tighten up. */
53
+ .mk-prose li > ul,
54
+ .mk-prose li > ol {
55
+ margin-block-start: var(--mk-space-2);
56
+ }
57
+
58
+ /* Inline code — tonal field chip, no border. */
59
+ .mk-prose code {
60
+ background: var(--mk-color-field);
61
+ color: var(--mk-color-text);
62
+ font-family: var(--mk-font-body);
63
+ font-size: 0.9em;
64
+ padding: 0.1em 0.4em;
65
+ }
66
+
67
+ /* Pre / fenced blocks — tonal field, horizontal scroll, no wrap. */
68
+ .mk-prose pre {
69
+ background: var(--mk-color-field);
70
+ color: var(--mk-color-text);
71
+ border-radius: var(--mk-radius-surface);
72
+ padding: var(--mk-space-4);
73
+ overflow-x: auto;
74
+ line-height: var(--mk-leading-normal);
75
+ -webkit-overflow-scrolling: touch;
76
+ }
77
+
78
+ /* Code inside pre resets the inline-chip treatment. */
79
+ .mk-prose pre code {
80
+ background: none;
81
+ padding: 0;
82
+ font-size: var(--mk-text-sm);
83
+ }
84
+
85
+ /* Blockquote — tonal field shift, no border-left. Indent via padding. */
86
+ .mk-prose blockquote {
87
+ margin-inline: 0;
88
+ padding: var(--mk-space-3) var(--mk-space-4);
89
+ background: var(--mk-color-surface-2);
90
+ color: var(--mk-color-text-muted);
91
+ }
92
+
93
+ .mk-prose blockquote > :first-child {
94
+ margin-block-start: 0;
95
+ }
96
+
97
+ /* kbd — raised tonal chip suggesting a physical key. */
98
+ .mk-prose kbd {
99
+ display: inline-block;
100
+ background: var(--mk-color-surface-3);
101
+ color: var(--mk-color-text);
102
+ font-family: var(--mk-font-body);
103
+ font-size: 0.85em;
104
+ font-weight: var(--mk-weight-medium);
105
+ line-height: 1;
106
+ padding: 0.25em 0.5em;
107
+ border-radius: var(--mk-radius-control);
108
+ }
109
+
110
+ /* Small / caption text. */
111
+ .mk-prose small {
112
+ font-size: var(--mk-text-sm);
113
+ color: var(--mk-color-text-muted);
114
+ }
115
+
116
+ /* Emphasis */
117
+ .mk-prose strong {
118
+ font-weight: var(--mk-weight-bold);
119
+ }
120
+
121
+ /* Links inherit base anchor styling; underline on hover only. */
122
+ }
package/src/reset.css ADDED
@@ -0,0 +1,17 @@
1
+ @layer mk.reset {
2
+ *,
3
+ *::before,
4
+ *::after {
5
+ box-sizing: border-box;
6
+ }
7
+
8
+ body {
9
+ margin: 0;
10
+ }
11
+
12
+ img,
13
+ svg {
14
+ display: block;
15
+ max-width: 100%;
16
+ }
17
+ }
@@ -0,0 +1,45 @@
1
+ @layer mk.components {
2
+ /*
3
+ * Spinner — CSS-only, terminal aesthetic. A square block sweeps a conic
4
+ * accent wedge in discrete steps() jumps rather than a smooth circle, so
5
+ * it reads like a stepping machine indicator, not a polished loader.
6
+ * Zero radius, zero border. The accent wedge rides on a faint track built
7
+ * from the same accent at low mix.
8
+ *
9
+ * Static fallback under reduced motion: a half-filled, non-spinning block
10
+ * still reads as "busy" without animating.
11
+ */
12
+ .mk-spinner {
13
+ display: inline-block;
14
+ width: 1.5rem;
15
+ height: 1.5rem;
16
+ border-radius: var(--mk-radius-control);
17
+ background: conic-gradient(
18
+ var(--mk-color-accent) 0deg 90deg,
19
+ color-mix(in oklch, var(--mk-color-accent), transparent 80%) 90deg 360deg
20
+ );
21
+ }
22
+
23
+ .mk-spinner--sm {
24
+ width: 1rem;
25
+ height: 1rem;
26
+ }
27
+
28
+ .mk-spinner--md {
29
+ width: 1.5rem;
30
+ height: 1.5rem;
31
+ }
32
+
33
+ @media (prefers-reduced-motion: no-preference) {
34
+ .mk-spinner {
35
+ /* slow token x3 keeps the loop tokenized (~960ms ≈ the original 1s). */
36
+ animation: mk-spinner-step calc(var(--mk-motion-slow) * 3) steps(8, end) infinite;
37
+ }
38
+ }
39
+
40
+ @keyframes mk-spinner-step {
41
+ to {
42
+ transform: rotate(1turn);
43
+ }
44
+ }
45
+ }
package/src/table.css ADDED
@@ -0,0 +1,46 @@
1
+ @layer mk.components {
2
+ /* Table — full width, mono-friendly, tonal header, no borders */
3
+ .mk-table {
4
+ width: 100%;
5
+ border-collapse: collapse;
6
+ font-size: var(--mk-text-sm);
7
+ color: var(--mk-color-text);
8
+ }
9
+
10
+ .mk-table th,
11
+ .mk-table td {
12
+ padding: var(--mk-space-3) var(--mk-space-4);
13
+ text-align: left;
14
+ vertical-align: top;
15
+ }
16
+
17
+ /* Header row — tonal background shift, muted bold label text */
18
+ .mk-table thead th {
19
+ background: var(--mk-color-surface-2);
20
+ color: var(--mk-color-text-muted);
21
+ font-weight: var(--mk-weight-medium);
22
+ white-space: nowrap;
23
+ }
24
+
25
+ /* Hover tonal row feedback (no zebra) */
26
+ .mk-table tbody tr {
27
+ transition: background var(--mk-motion-quick) var(--mk-ease-out);
28
+ }
29
+
30
+ .mk-table tbody tr:hover {
31
+ background: var(--mk-color-surface-2);
32
+ }
33
+
34
+ /* Numeric cells — right-aligned, tabular */
35
+ .mk-table__num {
36
+ text-align: right;
37
+ font-variant-numeric: tabular-nums;
38
+ }
39
+
40
+ /* Mobile scroll container — horizontal overflow without breaking layout */
41
+ .mk-table-wrap {
42
+ width: 100%;
43
+ overflow-x: auto;
44
+ -webkit-overflow-scrolling: touch;
45
+ }
46
+ }