@sethlivingston/cathode 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,206 @@
1
+ /* cathode/forms.css — fields, inputs, selects, checks, switch */
2
+
3
+ .ct-field {
4
+ display: flex;
5
+ flex-direction: column;
6
+ gap: var(--ct-space-2);
7
+ margin-bottom: var(--ct-space-4);
8
+ }
9
+
10
+ .ct-label {
11
+ font-size: var(--ct-text-sm);
12
+ font-weight: 500;
13
+ color: var(--ct-text-dim);
14
+ }
15
+
16
+ .ct-label--required::after {
17
+ content: " *";
18
+ color: var(--ct-red);
19
+ }
20
+
21
+ .ct-input,
22
+ .ct-select,
23
+ .ct-textarea {
24
+ font-family: var(--ct-font-sans);
25
+ font-size: var(--ct-text-md);
26
+ color: var(--ct-text);
27
+ background: var(--ct-bg-1);
28
+ border: 1px solid var(--ct-border);
29
+ border-radius: var(--ct-radius);
30
+ padding: var(--ct-space-2) var(--ct-space-3);
31
+ min-height: 36px;
32
+ width: 100%;
33
+ transition: border-color var(--ct-quick) var(--ct-ease);
34
+ }
35
+
36
+ .ct-input:hover,
37
+ .ct-select:hover,
38
+ .ct-textarea:hover {
39
+ border-color: var(--ct-border-bright);
40
+ }
41
+
42
+ .ct-input:focus,
43
+ .ct-select:focus,
44
+ .ct-textarea:focus {
45
+ outline: none;
46
+ border-color: var(--ct-accent);
47
+ box-shadow: 0 0 0 2px var(--ct-accent-glow);
48
+ }
49
+
50
+ .ct-input::placeholder,
51
+ .ct-textarea::placeholder {
52
+ color: var(--ct-text-faint);
53
+ }
54
+
55
+ /* Mono input — for IDs, tokens, code-like values */
56
+ .ct-input--mono {
57
+ font-family: var(--ct-font-mono);
58
+ font-size: var(--ct-text-sm);
59
+ }
60
+
61
+ .ct-textarea {
62
+ min-height: 96px;
63
+ resize: vertical;
64
+ line-height: var(--ct-leading);
65
+ }
66
+
67
+ .ct-select {
68
+ appearance: none;
69
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 4.5L6 8l3.5-3.5' fill='none' stroke='%23a2a2b3' stroke-width='1.5'/%3E%3C/svg%3E");
70
+ background-repeat: no-repeat;
71
+ background-position: right var(--ct-space-3) center;
72
+ padding-right: var(--ct-space-6);
73
+ cursor: pointer;
74
+ }
75
+
76
+ /* Validation */
77
+ .ct-input[aria-invalid="true"],
78
+ .ct-select[aria-invalid="true"],
79
+ .ct-textarea[aria-invalid="true"] {
80
+ border-color: var(--ct-red);
81
+ }
82
+
83
+ .ct-input[aria-invalid="true"]:focus,
84
+ .ct-textarea[aria-invalid="true"]:focus {
85
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--ct-red) 40%, transparent);
86
+ }
87
+
88
+ .ct-help {
89
+ font-size: var(--ct-text-sm);
90
+ color: var(--ct-text-faint);
91
+ }
92
+
93
+ .ct-error {
94
+ font-size: var(--ct-text-sm);
95
+ color: var(--ct-red);
96
+ }
97
+
98
+ /* Checkbox & radio — square pixels, round dots */
99
+ .ct-check,
100
+ .ct-radio {
101
+ display: inline-flex;
102
+ align-items: center;
103
+ gap: var(--ct-space-2);
104
+ font-size: var(--ct-text-md);
105
+ cursor: pointer;
106
+ user-select: none;
107
+ }
108
+
109
+ .ct-check input,
110
+ .ct-radio input {
111
+ appearance: none;
112
+ width: 16px;
113
+ height: 16px;
114
+ margin: 0;
115
+ background: var(--ct-bg-1);
116
+ border: 1px solid var(--ct-border-bright);
117
+ cursor: pointer;
118
+ flex-shrink: 0;
119
+ transition:
120
+ background var(--ct-quick) var(--ct-ease),
121
+ border-color var(--ct-quick) var(--ct-ease);
122
+ }
123
+
124
+ .ct-check input {
125
+ border-radius: var(--ct-radius);
126
+ }
127
+
128
+ .ct-radio input {
129
+ border-radius: 50%;
130
+ }
131
+
132
+ .ct-check input:checked,
133
+ .ct-radio input:checked {
134
+ background: var(--ct-accent);
135
+ border-color: var(--ct-accent);
136
+ }
137
+
138
+ .ct-check input:checked {
139
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10'%3E%3Cpath d='M1.5 5.5l2.5 2.5 4.5-5.5' fill='none' stroke='%23150420' stroke-width='2'/%3E%3C/svg%3E");
140
+ background-repeat: no-repeat;
141
+ background-position: center;
142
+ }
143
+
144
+ .ct-radio input:checked {
145
+ box-shadow: inset 0 0 0 3px var(--ct-bg-1);
146
+ }
147
+
148
+ /* Switch */
149
+ .ct-switch {
150
+ display: inline-flex;
151
+ align-items: center;
152
+ gap: var(--ct-space-2);
153
+ font-size: var(--ct-text-md);
154
+ cursor: pointer;
155
+ user-select: none;
156
+ }
157
+
158
+ .ct-switch input {
159
+ appearance: none;
160
+ width: 32px;
161
+ height: 18px;
162
+ margin: 0;
163
+ background: var(--ct-bg-3);
164
+ border: 1px solid var(--ct-border-bright);
165
+ border-radius: var(--ct-radius);
166
+ position: relative;
167
+ cursor: pointer;
168
+ flex-shrink: 0;
169
+ transition: background var(--ct-quick) var(--ct-ease);
170
+ }
171
+
172
+ .ct-switch input::before {
173
+ content: "";
174
+ position: absolute;
175
+ top: 2px;
176
+ left: 2px;
177
+ width: 12px;
178
+ height: 12px;
179
+ background: var(--ct-text-dim);
180
+ border-radius: var(--ct-radius);
181
+ transition:
182
+ translate var(--ct-quick) var(--ct-ease),
183
+ background var(--ct-quick) var(--ct-ease);
184
+ }
185
+
186
+ .ct-switch input:checked {
187
+ background: var(--ct-accent);
188
+ border-color: var(--ct-accent);
189
+ }
190
+
191
+ .ct-switch input:checked::before {
192
+ translate: 14px 0;
193
+ background: var(--ct-on-accent);
194
+ }
195
+
196
+ /* Inline form row */
197
+ .ct-form-row {
198
+ display: flex;
199
+ gap: var(--ct-space-3);
200
+ align-items: flex-end;
201
+ }
202
+
203
+ .ct-form-row .ct-field {
204
+ flex: 1;
205
+ margin-bottom: 0;
206
+ }
@@ -0,0 +1,76 @@
1
+ /* cathode/layout.css — app shell and composition helpers */
2
+
3
+ /* App shell: navbar on top, sidebar + main below
4
+ <body class="ct-shell">
5
+ <nav class="ct-navbar">…</nav>
6
+ <aside class="ct-sidebar">…</aside>
7
+ <main class="ct-main">…</main>
8
+ </body> */
9
+ .ct-shell {
10
+ display: grid;
11
+ grid-template-columns: auto 1fr;
12
+ grid-template-rows: auto 1fr;
13
+ grid-template-areas:
14
+ "nav nav"
15
+ "side main";
16
+ min-height: 100vh;
17
+ }
18
+
19
+ .ct-shell > .ct-navbar { grid-area: nav; }
20
+ .ct-shell > .ct-sidebar { grid-area: side; }
21
+ .ct-shell > .ct-main { grid-area: main; }
22
+
23
+ .ct-main {
24
+ padding: var(--ct-space-5);
25
+ overflow-y: auto;
26
+ min-width: 0;
27
+ }
28
+
29
+ /* Centered content page (docs, marketing, settings) */
30
+ .ct-container {
31
+ width: min(720px, 100% - var(--ct-space-6));
32
+ margin-inline: auto;
33
+ }
34
+
35
+ .ct-container--wide {
36
+ width: min(1100px, 100% - var(--ct-space-6));
37
+ }
38
+
39
+ /* Vertical rhythm */
40
+ .ct-stack {
41
+ display: flex;
42
+ flex-direction: column;
43
+ gap: var(--ct-space-4);
44
+ }
45
+
46
+ .ct-stack--tight { gap: var(--ct-space-2); }
47
+ .ct-stack--loose { gap: var(--ct-space-6); }
48
+
49
+ /* Horizontal cluster, wraps */
50
+ .ct-cluster {
51
+ display: flex;
52
+ flex-wrap: wrap;
53
+ align-items: center;
54
+ gap: var(--ct-space-2);
55
+ }
56
+
57
+ /* Responsive card grid */
58
+ .ct-grid {
59
+ display: grid;
60
+ grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
61
+ gap: var(--ct-space-4);
62
+ }
63
+
64
+ /* Mobile: sidebar collapses under navbar */
65
+ @media (max-width: 720px) {
66
+ .ct-shell {
67
+ grid-template-columns: 1fr;
68
+ grid-template-areas:
69
+ "nav"
70
+ "main";
71
+ }
72
+
73
+ .ct-shell > .ct-sidebar {
74
+ display: none;
75
+ }
76
+ }
@@ -0,0 +1,151 @@
1
+ /* cathode/nav.css — navbar, sidebar, tabs, breadcrumb */
2
+
3
+ /* Top navbar */
4
+ .ct-navbar {
5
+ display: flex;
6
+ align-items: center;
7
+ gap: var(--ct-space-4);
8
+ padding: 0 var(--ct-space-4);
9
+ height: 52px;
10
+ background: var(--ct-bg-1);
11
+ border-bottom: 1px solid var(--ct-border);
12
+ position: sticky;
13
+ top: 0;
14
+ z-index: var(--ct-z-nav);
15
+ }
16
+
17
+ .ct-navbar__brand {
18
+ font-family: var(--ct-font-mono);
19
+ font-size: var(--ct-text-md);
20
+ font-weight: 600;
21
+ letter-spacing: var(--ct-tracking-mono);
22
+ text-transform: uppercase;
23
+ color: var(--ct-text);
24
+ }
25
+
26
+ .ct-navbar__spacer {
27
+ flex: 1;
28
+ }
29
+
30
+ /* Sidebar */
31
+ .ct-sidebar {
32
+ display: flex;
33
+ flex-direction: column;
34
+ gap: var(--ct-space-1);
35
+ width: 220px;
36
+ padding: var(--ct-space-3);
37
+ background: var(--ct-bg-1);
38
+ border-right: 1px solid var(--ct-border);
39
+ overflow-y: auto;
40
+ }
41
+
42
+ .ct-sidebar__section {
43
+ font-family: var(--ct-font-mono);
44
+ font-size: var(--ct-text-xs);
45
+ font-weight: 600;
46
+ letter-spacing: var(--ct-tracking-mono);
47
+ text-transform: uppercase;
48
+ color: var(--ct-text-faint);
49
+ padding: var(--ct-space-3) var(--ct-space-2) var(--ct-space-1);
50
+ }
51
+
52
+ .ct-nav-item {
53
+ display: flex;
54
+ align-items: center;
55
+ gap: var(--ct-space-2);
56
+ font-size: var(--ct-text-md);
57
+ color: var(--ct-text-dim);
58
+ padding: var(--ct-space-2) var(--ct-space-3);
59
+ border: 1px solid transparent;
60
+ border-radius: var(--ct-radius);
61
+ text-decoration: none;
62
+ cursor: pointer;
63
+ transition:
64
+ color var(--ct-quick) var(--ct-ease),
65
+ background var(--ct-quick) var(--ct-ease);
66
+ }
67
+
68
+ .ct-nav-item:hover {
69
+ color: var(--ct-text);
70
+ background: var(--ct-bg-2);
71
+ text-decoration: none;
72
+ }
73
+
74
+ .ct-nav-item[aria-current="page"],
75
+ .ct-nav-item--active {
76
+ color: var(--ct-text);
77
+ background: var(--ct-bg-2);
78
+ border-color: var(--ct-border);
79
+ }
80
+
81
+ .ct-nav-item[aria-current="page"]::before,
82
+ .ct-nav-item--active::before {
83
+ content: "";
84
+ width: 2px;
85
+ align-self: stretch;
86
+ margin: 2px 0;
87
+ background: var(--ct-accent);
88
+ }
89
+
90
+ /* Tabs */
91
+ .ct-tabs {
92
+ display: flex;
93
+ gap: var(--ct-space-1);
94
+ border-bottom: 1px solid var(--ct-border);
95
+ }
96
+
97
+ .ct-tab {
98
+ font-family: var(--ct-font-sans);
99
+ font-size: var(--ct-text-md);
100
+ font-weight: 500;
101
+ color: var(--ct-text-dim);
102
+ background: none;
103
+ border: 0;
104
+ border-bottom: 2px solid transparent;
105
+ padding: var(--ct-space-2) var(--ct-space-3);
106
+ margin-bottom: -1px;
107
+ cursor: pointer;
108
+ transition: color var(--ct-quick) var(--ct-ease);
109
+ }
110
+
111
+ .ct-tab:hover {
112
+ color: var(--ct-text);
113
+ }
114
+
115
+ .ct-tab[aria-selected="true"],
116
+ .ct-tab--active {
117
+ color: var(--ct-text);
118
+ border-bottom-color: var(--ct-accent);
119
+ }
120
+
121
+ /* Breadcrumb — mono path, like a filesystem */
122
+ .ct-breadcrumb {
123
+ display: flex;
124
+ align-items: center;
125
+ gap: var(--ct-space-2);
126
+ font-family: var(--ct-font-mono);
127
+ font-size: var(--ct-text-sm);
128
+ color: var(--ct-text-faint);
129
+ list-style: none;
130
+ padding: 0;
131
+ margin: 0;
132
+ }
133
+
134
+ .ct-breadcrumb li {
135
+ display: flex;
136
+ align-items: center;
137
+ gap: var(--ct-space-2);
138
+ }
139
+
140
+ .ct-breadcrumb li + li::before {
141
+ content: "/";
142
+ color: var(--ct-text-faint);
143
+ }
144
+
145
+ .ct-breadcrumb a {
146
+ color: var(--ct-text-dim);
147
+ }
148
+
149
+ .ct-breadcrumb li:last-child {
150
+ color: var(--ct-text);
151
+ }
@@ -0,0 +1,126 @@
1
+ /* cathode/overlay.css — modal (native <dialog>), toast, tooltip */
2
+
3
+ /* Modal — use <dialog class="ct-modal"> with .showModal() */
4
+ .ct-modal {
5
+ background: var(--ct-bg-1);
6
+ color: var(--ct-text);
7
+ border: 1px solid var(--ct-border-bright);
8
+ border-radius: var(--ct-radius);
9
+ padding: 0;
10
+ width: min(480px, calc(100vw - var(--ct-space-6)));
11
+ max-height: calc(100vh - var(--ct-space-8));
12
+ }
13
+
14
+ .ct-modal::backdrop {
15
+ background: rgba(5, 5, 8, 0.7);
16
+ }
17
+
18
+ .ct-modal[open] {
19
+ animation: ct-modal-in var(--ct-slow) var(--ct-ease);
20
+ }
21
+
22
+ @keyframes ct-modal-in {
23
+ from {
24
+ opacity: 0;
25
+ transform: translateY(8px);
26
+ }
27
+ }
28
+
29
+ .ct-modal__header {
30
+ display: flex;
31
+ align-items: center;
32
+ justify-content: space-between;
33
+ padding: var(--ct-space-4);
34
+ border-bottom: 1px solid var(--ct-border);
35
+ }
36
+
37
+ .ct-modal__title {
38
+ font-size: var(--ct-text-lg);
39
+ font-weight: 600;
40
+ margin: 0;
41
+ }
42
+
43
+ .ct-modal__body {
44
+ padding: var(--ct-space-4);
45
+ overflow-y: auto;
46
+ }
47
+
48
+ .ct-modal__footer {
49
+ display: flex;
50
+ justify-content: flex-end;
51
+ gap: var(--ct-space-2);
52
+ padding: var(--ct-space-4);
53
+ border-top: 1px solid var(--ct-border);
54
+ }
55
+
56
+ /* Toasts — fixed stack, bottom right */
57
+ .ct-toasts {
58
+ position: fixed;
59
+ bottom: var(--ct-space-4);
60
+ right: var(--ct-space-4);
61
+ display: flex;
62
+ flex-direction: column;
63
+ gap: var(--ct-space-2);
64
+ z-index: var(--ct-z-toast);
65
+ }
66
+
67
+ .ct-toast {
68
+ display: flex;
69
+ align-items: center;
70
+ gap: var(--ct-space-3);
71
+ background: var(--ct-bg-2);
72
+ border: 1px solid var(--ct-border-bright);
73
+ border-left: 2px solid var(--toast-hue, var(--ct-border-bright));
74
+ border-radius: var(--ct-radius);
75
+ padding: var(--ct-space-3) var(--ct-space-4);
76
+ min-width: 280px;
77
+ max-width: 400px;
78
+ font-size: var(--ct-text-md);
79
+ animation: ct-toast-in var(--ct-slow) var(--ct-ease);
80
+ }
81
+
82
+ @keyframes ct-toast-in {
83
+ from {
84
+ opacity: 0;
85
+ transform: translateX(16px);
86
+ }
87
+ }
88
+
89
+ .ct-toast--success { --toast-hue: var(--ct-green); }
90
+ .ct-toast--danger { --toast-hue: var(--ct-red); }
91
+ .ct-toast--warning { --toast-hue: var(--ct-amber); }
92
+ .ct-toast--info { --toast-hue: var(--ct-cyan); }
93
+
94
+ .ct-toast__title {
95
+ font-weight: 600;
96
+ }
97
+
98
+ /* Tooltip — CSS-only: <button data-ct-tip="Copied!"> */
99
+ [data-ct-tip] {
100
+ position: relative;
101
+ }
102
+
103
+ [data-ct-tip]::after {
104
+ content: attr(data-ct-tip);
105
+ position: absolute;
106
+ bottom: calc(100% + 6px);
107
+ left: 50%;
108
+ transform: translateX(-50%);
109
+ background: var(--ct-bg-3);
110
+ color: var(--ct-text);
111
+ border: 1px solid var(--ct-border-bright);
112
+ border-radius: var(--ct-radius);
113
+ padding: var(--ct-space-1) var(--ct-space-2);
114
+ font-family: var(--ct-font-mono);
115
+ font-size: var(--ct-text-xs);
116
+ white-space: nowrap;
117
+ opacity: 0;
118
+ pointer-events: none;
119
+ transition: opacity var(--ct-quick) var(--ct-ease);
120
+ z-index: var(--ct-z-tooltip);
121
+ }
122
+
123
+ [data-ct-tip]:hover::after,
124
+ [data-ct-tip]:focus-visible::after {
125
+ opacity: 1;
126
+ }
@@ -0,0 +1,57 @@
1
+ /* cathode/table.css — data tables. Numerics get .ct-num (mono, right-aligned). */
2
+
3
+ .ct-table {
4
+ width: 100%;
5
+ font-size: var(--ct-text-md);
6
+ border: 1px solid var(--ct-border);
7
+ border-radius: var(--ct-radius);
8
+ }
9
+
10
+ .ct-table th {
11
+ font-family: var(--ct-font-mono);
12
+ font-size: var(--ct-text-xs);
13
+ font-weight: 600;
14
+ letter-spacing: var(--ct-tracking-mono);
15
+ text-transform: uppercase;
16
+ color: var(--ct-text-faint);
17
+ text-align: left;
18
+ padding: var(--ct-space-2) var(--ct-space-3);
19
+ border-bottom: 1px solid var(--ct-border);
20
+ background: var(--ct-bg-1);
21
+ }
22
+
23
+ .ct-table td {
24
+ padding: var(--ct-space-2) var(--ct-space-3);
25
+ border-bottom: 1px solid var(--ct-border);
26
+ }
27
+
28
+ .ct-table tbody tr:last-child td {
29
+ border-bottom: 0;
30
+ }
31
+
32
+ .ct-table tbody tr {
33
+ transition: background var(--ct-quick) var(--ct-ease);
34
+ }
35
+
36
+ .ct-table tbody tr:hover {
37
+ background: var(--ct-bg-1);
38
+ }
39
+
40
+ .ct-table tbody tr[aria-selected="true"] {
41
+ background: color-mix(in srgb, var(--ct-accent) 8%, transparent);
42
+ }
43
+
44
+ /* Numeric/data cells */
45
+ .ct-table .ct-num,
46
+ .ct-table th.ct-num {
47
+ font-family: var(--ct-font-mono);
48
+ font-size: var(--ct-text-sm);
49
+ font-variant-numeric: tabular-nums;
50
+ text-align: right;
51
+ }
52
+
53
+ /* Compact variant for dense dashboards */
54
+ .ct-table--compact th,
55
+ .ct-table--compact td {
56
+ padding: var(--ct-space-1) var(--ct-space-2);
57
+ }
package/src/fonts.css ADDED
@@ -0,0 +1,65 @@
1
+ /* cathode/fonts.css — self-hosted IBM Plex (OFL-1.1), latin subset */
2
+
3
+ @font-face {
4
+ font-family: "IBM Plex Sans";
5
+ font-style: normal;
6
+ font-weight: 400;
7
+ font-display: swap;
8
+ src: url("../fonts/ibm-plex-sans-latin-400-normal.woff2") format("woff2");
9
+ }
10
+
11
+ @font-face {
12
+ font-family: "IBM Plex Sans";
13
+ font-style: italic;
14
+ font-weight: 400;
15
+ font-display: swap;
16
+ src: url("../fonts/ibm-plex-sans-latin-400-italic.woff2") format("woff2");
17
+ }
18
+
19
+ @font-face {
20
+ font-family: "IBM Plex Sans";
21
+ font-style: normal;
22
+ font-weight: 500;
23
+ font-display: swap;
24
+ src: url("../fonts/ibm-plex-sans-latin-500-normal.woff2") format("woff2");
25
+ }
26
+
27
+ @font-face {
28
+ font-family: "IBM Plex Sans";
29
+ font-style: normal;
30
+ font-weight: 600;
31
+ font-display: swap;
32
+ src: url("../fonts/ibm-plex-sans-latin-600-normal.woff2") format("woff2");
33
+ }
34
+
35
+ @font-face {
36
+ font-family: "IBM Plex Sans";
37
+ font-style: normal;
38
+ font-weight: 700;
39
+ font-display: swap;
40
+ src: url("../fonts/ibm-plex-sans-latin-700-normal.woff2") format("woff2");
41
+ }
42
+
43
+ @font-face {
44
+ font-family: "IBM Plex Mono";
45
+ font-style: normal;
46
+ font-weight: 400;
47
+ font-display: swap;
48
+ src: url("../fonts/ibm-plex-mono-latin-400-normal.woff2") format("woff2");
49
+ }
50
+
51
+ @font-face {
52
+ font-family: "IBM Plex Mono";
53
+ font-style: normal;
54
+ font-weight: 500;
55
+ font-display: swap;
56
+ src: url("../fonts/ibm-plex-mono-latin-500-normal.woff2") format("woff2");
57
+ }
58
+
59
+ @font-face {
60
+ font-family: "IBM Plex Mono";
61
+ font-style: normal;
62
+ font-weight: 600;
63
+ font-display: swap;
64
+ src: url("../fonts/ibm-plex-mono-latin-600-normal.woff2") format("woff2");
65
+ }