@hearthsound/design 0.0.0-stage → 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.
Files changed (109) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +132 -2
  3. package/THIRD-PARTY.md +46 -0
  4. package/css/base.css +245 -0
  5. package/css/components/admin.css +257 -0
  6. package/css/components/banner.css +140 -0
  7. package/css/components/button.css +309 -0
  8. package/css/components/card.css +339 -0
  9. package/css/components/check.css +202 -0
  10. package/css/components/dialog.css +85 -0
  11. package/css/components/field.css +296 -0
  12. package/css/components/list.css +260 -0
  13. package/css/components/menu.css +282 -0
  14. package/css/components/page-header.css +230 -0
  15. package/css/components/pill.css +101 -0
  16. package/css/components/player.css +524 -0
  17. package/css/components/progress.css +173 -0
  18. package/css/components/section.css +86 -0
  19. package/css/components/segmented.css +64 -0
  20. package/css/components/shell.css +281 -0
  21. package/css/components/sidebar.css +170 -0
  22. package/css/components/slider.css +92 -0
  23. package/css/components/status-page.css +48 -0
  24. package/css/components/stepper.css +130 -0
  25. package/css/components/toast.css +88 -0
  26. package/css/components/tooltip.css +29 -0
  27. package/css/components/track.css +336 -0
  28. package/css/components/upload.css +70 -0
  29. package/css/index.css +35 -0
  30. package/css/tokens.css +375 -0
  31. package/dist/icons.d.ts +56 -0
  32. package/dist/icons.js +56 -0
  33. package/dist/icons.svg +55 -0
  34. package/fonts/OFL-BricolageGrotesque.txt +93 -0
  35. package/fonts/OFL-Geist.txt +92 -0
  36. package/fonts/bricolage-var.woff2 +0 -0
  37. package/fonts/geist-mono.woff2 +0 -0
  38. package/fonts/geist-var.woff2 +0 -0
  39. package/icons/LICENSE +10 -0
  40. package/icons/adwaita-account.svg +1 -0
  41. package/icons/adwaita-acquire.svg +1 -0
  42. package/icons/adwaita-add.svg +1 -0
  43. package/icons/adwaita-album.svg +1 -0
  44. package/icons/adwaita-artist.svg +1 -0
  45. package/icons/adwaita-back.svg +1 -0
  46. package/icons/adwaita-check.svg +1 -0
  47. package/icons/adwaita-close.svg +1 -0
  48. package/icons/adwaita-collapse.svg +1 -0
  49. package/icons/adwaita-dark-mode.svg +1 -0
  50. package/icons/adwaita-delete.svg +1 -0
  51. package/icons/adwaita-drag.svg +1 -0
  52. package/icons/adwaita-edit.svg +1 -0
  53. package/icons/adwaita-error.svg +1 -0
  54. package/icons/adwaita-expand.svg +1 -0
  55. package/icons/adwaita-folder.svg +1 -0
  56. package/icons/adwaita-forward.svg +1 -0
  57. package/icons/adwaita-home.svg +1 -0
  58. package/icons/adwaita-layers.svg +1 -0
  59. package/icons/adwaita-like.svg +1 -0
  60. package/icons/adwaita-lock.svg +1 -0
  61. package/icons/adwaita-menu.svg +1 -0
  62. package/icons/adwaita-more.svg +1 -0
  63. package/icons/adwaita-mute.svg +1 -0
  64. package/icons/adwaita-next.svg +1 -0
  65. package/icons/adwaita-pause.svg +1 -0
  66. package/icons/adwaita-play.svg +1 -0
  67. package/icons/adwaita-playlist.svg +1 -0
  68. package/icons/adwaita-plugin.svg +1 -0
  69. package/icons/adwaita-previous.svg +1 -0
  70. package/icons/adwaita-queue.svg +1 -0
  71. package/icons/adwaita-rating-outline.svg +1 -0
  72. package/icons/adwaita-rating.svg +1 -0
  73. package/icons/adwaita-refresh.svg +1 -0
  74. package/icons/adwaita-remote.svg +1 -0
  75. package/icons/adwaita-remove.svg +1 -0
  76. package/icons/adwaita-repeat.svg +1 -0
  77. package/icons/adwaita-reveal.svg +1 -0
  78. package/icons/adwaita-search.svg +1 -0
  79. package/icons/adwaita-settings.svg +1 -0
  80. package/icons/adwaita-shuffle.svg +1 -0
  81. package/icons/adwaita-sign-out.svg +1 -0
  82. package/icons/adwaita-song.svg +1 -0
  83. package/icons/adwaita-sort.svg +1 -0
  84. package/icons/adwaita-storage.svg +1 -0
  85. package/icons/adwaita-users.svg +1 -0
  86. package/icons/adwaita-volume.svg +1 -0
  87. package/icons/adwaita-warning.svg +1 -0
  88. package/icons/custom-dislike-outline.svg +1 -0
  89. package/icons/custom-dislike.svg +1 -0
  90. package/icons/custom-like-outline.svg +1 -0
  91. package/icons/custom-unknown.svg +1 -0
  92. package/icons/custom-upload.svg +1 -0
  93. package/identity/icons/app-icon-dark-16.png +0 -0
  94. package/identity/icons/app-icon-dark-180.png +0 -0
  95. package/identity/icons/app-icon-dark-192.png +0 -0
  96. package/identity/icons/app-icon-dark-32.png +0 -0
  97. package/identity/icons/app-icon-dark-512.png +0 -0
  98. package/identity/icons/app-icon-dark.svg +1 -0
  99. package/identity/icons/app-icon-light-16.png +0 -0
  100. package/identity/icons/app-icon-light-180.png +0 -0
  101. package/identity/icons/app-icon-light-192.png +0 -0
  102. package/identity/icons/app-icon-light-32.png +0 -0
  103. package/identity/icons/app-icon-light-512.png +0 -0
  104. package/identity/icons/app-icon-light.svg +1 -0
  105. package/identity/icons/favicon.svg +1 -0
  106. package/identity/mark/mark-dark.svg +1 -0
  107. package/identity/mark/mark-light.svg +1 -0
  108. package/identity/mark/mark-symbolic.svg +1 -0
  109. package/package.json +35 -4
@@ -0,0 +1,140 @@
1
+ /*
2
+ * k21 B - banner: a rounded tinted card; icon, text and one regular button.
3
+ * Placement l11b: floating at the bottom of the content column, level-2 shadow, above
4
+ * the player bar or the mini player card, not scrolling with the page.
5
+ *
6
+ * Markup: .hs-overlay-dock wraps the banner and the toast region in DOM order
7
+ * (banner first). Its column-reverse stacking puts the banner on the player edge and the
8
+ * toast 12 px above it (l11d A), and it is placed before the content in the document
9
+ * even though it floats visually. Warnings use role="status", errors role="alert"; the
10
+ * glyph is aria-hidden. Regular inline banners (sign-in session expiry, setup index
11
+ * errors) omit .hs-banner--floating.
12
+ *
13
+ * SPEC GAP: the phase brief names an info banner, but components.md k21 and the approved
14
+ * .bn styles define only warning (the base tone), error and success. The base
15
+ * .hs-banner keeps the approved warning tone; no separate info tone is invented.
16
+ */
17
+
18
+ /* ------------------------------------------------------------------ */
19
+ /* l11b, l11d - the floating dock */
20
+ /* ------------------------------------------------------------------ */
21
+
22
+ .hs-overlay-dock {
23
+ position: fixed;
24
+ z-index: 6; /* above the page and its header (5), below the dialog scrim (9) */
25
+ left: 240px;
26
+ right: 0;
27
+ bottom: 100px; /* 88 px player bar + 12 px */
28
+ display: flex;
29
+ flex-direction: column-reverse;
30
+ align-items: center;
31
+ gap: 12px;
32
+ pointer-events: none;
33
+ }
34
+
35
+ .hs-overlay-dock > * {
36
+ pointer-events: auto;
37
+ }
38
+
39
+ @media (max-width: 1280px) {
40
+ .hs-overlay-dock {
41
+ left: 200px;
42
+ }
43
+ }
44
+
45
+ @media (max-width: 720px) {
46
+ .hs-overlay-dock {
47
+ left: 0;
48
+ bottom: 84px; /* 8 px inset + 64 px mini card + 12 px */
49
+ }
50
+ }
51
+
52
+ /* ------------------------------------------------------------------ */
53
+ /* k21 - anatomy */
54
+ /* ------------------------------------------------------------------ */
55
+
56
+ .hs-banner {
57
+ display: flex;
58
+ align-items: center;
59
+ gap: 12px;
60
+ padding: 12px 14px;
61
+ box-sizing: border-box;
62
+ width: 100%;
63
+ border-radius: var(--hs-radius-card);
64
+ font: 400 14px/1.45 var(--hs-font-body);
65
+ color: var(--hs-fg);
66
+ background: color-mix(in srgb, var(--hs-warning) var(--hs-message-tint), var(--hs-view-bg));
67
+ }
68
+
69
+ .hs-banner__glyph {
70
+ --hs-icon-size: 18px;
71
+ flex: none;
72
+ color: var(--hs-warning);
73
+ }
74
+
75
+ .hs-banner__text {
76
+ flex: 1;
77
+ min-width: 0;
78
+ display: flex;
79
+ flex-direction: column;
80
+ gap: 2px;
81
+ }
82
+
83
+ .hs-banner__actions {
84
+ display: flex;
85
+ align-items: center;
86
+ gap: 8px;
87
+ flex: none;
88
+ }
89
+
90
+ /* Warning is the base tone; the modifier exists so the state is explicit in markup. */
91
+ .hs-banner--warning {
92
+ background: color-mix(in srgb, var(--hs-warning) var(--hs-message-tint), var(--hs-view-bg));
93
+ }
94
+
95
+ .hs-banner--warning .hs-banner__glyph {
96
+ color: var(--hs-warning);
97
+ }
98
+
99
+ .hs-banner--error {
100
+ background: color-mix(in srgb, var(--hs-error) var(--hs-message-tint), var(--hs-view-bg));
101
+ }
102
+
103
+ .hs-banner--error .hs-banner__glyph {
104
+ color: var(--hs-error);
105
+ }
106
+
107
+ .hs-banner--success {
108
+ background: color-mix(in srgb, var(--hs-success) var(--hs-message-tint), var(--hs-view-bg));
109
+ }
110
+
111
+ .hs-banner--success .hs-banner__glyph {
112
+ color: var(--hs-success);
113
+ }
114
+
115
+ /* l11b C: floating, level-2 shadow, content margins 24 px desktop and 14 px phones,
116
+ * width capped with the l08 1440 px content cap. */
117
+ .hs-banner--floating {
118
+ width: calc(100% - 48px);
119
+ max-width: 1440px;
120
+ box-shadow: var(--hs-elevation-2);
121
+ }
122
+
123
+ /* ------------------------------------------------------------------ */
124
+ /* Phone (l11b, k21): the button wraps below, right-aligned */
125
+ /* ------------------------------------------------------------------ */
126
+
127
+ @media (max-width: 720px) {
128
+ .hs-banner {
129
+ flex-wrap: wrap;
130
+ }
131
+
132
+ .hs-banner--floating {
133
+ width: calc(100% - 28px);
134
+ }
135
+
136
+ .hs-banner__actions {
137
+ flex-basis: 100%;
138
+ justify-content: flex-end;
139
+ }
140
+ }
@@ -0,0 +1,309 @@
1
+ /*
2
+ * k01 buttons - k01 A tonal (current), k01b A focus for filled buttons, k01d A disabled,
3
+ * the busy-rule loading state - and k02 transport and the play/pause disc.
4
+ *
5
+ * Markup: <button class="hs-button">Label</button>. A label that must be replaced by the
6
+ * spinner while busy has to be an element, because the busy rule hides the button's
7
+ * children and keeps the accessible name (busy-rule).
8
+ */
9
+
10
+ /* ------------------------------------------------------------------ */
11
+ /* k01 - anatomy */
12
+ /* ------------------------------------------------------------------ */
13
+
14
+ .hs-button {
15
+ display: inline-flex;
16
+ align-items: center;
17
+ justify-content: center;
18
+ gap: 6px;
19
+ height: 36px;
20
+ padding: 0 14px;
21
+ box-sizing: border-box;
22
+ margin: 0;
23
+ border: 0;
24
+ border-radius: var(--hs-radius-control);
25
+ font: 500 14px var(--hs-font-body);
26
+ color: var(--hs-fg);
27
+ background: var(--hs-control-tone);
28
+ white-space: nowrap;
29
+ cursor: default;
30
+ user-select: none;
31
+ appearance: none;
32
+ transition:
33
+ background var(--hs-duration-hover) var(--hs-ease),
34
+ box-shadow var(--hs-duration-hover) var(--hs-ease),
35
+ transform var(--hs-duration-press) var(--hs-ease),
36
+ color var(--hs-duration-hover) var(--hs-ease);
37
+ }
38
+
39
+ .hs-button__icon {
40
+ --hs-icon-size: 16px;
41
+ }
42
+
43
+ /* Sizes and shapes. */
44
+ .hs-button--large {
45
+ height: 44px;
46
+ padding: 0 20px;
47
+ font-size: 15px;
48
+ }
49
+
50
+ .hs-button--pill {
51
+ border-radius: var(--hs-radius-full);
52
+ padding: 0 18px;
53
+ }
54
+
55
+ .hs-button--circular {
56
+ width: 36px;
57
+ padding: 0;
58
+ border-radius: var(--hs-radius-full);
59
+ }
60
+
61
+ .hs-button--icon {
62
+ width: 36px;
63
+ padding: 0;
64
+ }
65
+
66
+ /* Fills. */
67
+ .hs-button--suggested {
68
+ background: var(--hs-accent-bg);
69
+ color: var(--hs-accent-fg);
70
+ }
71
+
72
+ .hs-button--destructive {
73
+ background: var(--hs-destructive-bg);
74
+ color: var(--hs-destructive-fg);
75
+ }
76
+
77
+ .hs-button--flat {
78
+ background: transparent;
79
+ }
80
+
81
+ /* Menu button: tighter right padding and a 14 px trailing chevron at 80% opacity. */
82
+ .hs-button--menu {
83
+ padding-right: 10px;
84
+ }
85
+
86
+ .hs-button--menu .hs-button__icon:last-child {
87
+ --hs-icon-size: 14px;
88
+ opacity: 0.8;
89
+ }
90
+
91
+ /* Split button: the action first, the 30 px arrow second (inside hs-button-group). */
92
+ .hs-button--split-arrow {
93
+ width: 30px;
94
+ padding: 0;
95
+ }
96
+
97
+ .hs-button--split-arrow .hs-button__icon {
98
+ --hs-icon-size: 14px;
99
+ opacity: 0.8;
100
+ }
101
+
102
+ /* Header button (p08, p12-p26 page headers; pagekit.css): icon plus label on desktop and
103
+ * tablet, icon-only on phones. The label element is also what the busy rule can hide. */
104
+ @media (max-width: 720px) {
105
+ .hs-button--header {
106
+ width: 36px;
107
+ padding: 0;
108
+ justify-content: center;
109
+ }
110
+
111
+ .hs-button--header .hs-button__label {
112
+ display: none;
113
+ }
114
+ }
115
+
116
+ /* Linked group: 1 px gaps, outer corners only. */
117
+ .hs-button-group {
118
+ display: inline-flex;
119
+ gap: 1px;
120
+ }
121
+
122
+ .hs-button-group .hs-button {
123
+ border-radius: 0;
124
+ }
125
+
126
+ .hs-button-group .hs-button:first-child {
127
+ border-radius: var(--hs-radius-control) 0 0 var(--hs-radius-control);
128
+ }
129
+
130
+ .hs-button-group .hs-button:last-child {
131
+ border-radius: 0 var(--hs-radius-control) var(--hs-radius-control) 0;
132
+ }
133
+
134
+ .hs-button-group .hs-button:not(:first-child):not(:last-child) {
135
+ padding: 0 12px;
136
+ }
137
+
138
+ /* ------------------------------------------------------------------ */
139
+ /* k01 - states */
140
+ /* ------------------------------------------------------------------ */
141
+
142
+ .hs-button:hover {
143
+ background: color-mix(in srgb, var(--hs-fg) 14%, transparent);
144
+ }
145
+
146
+ .hs-button:active {
147
+ background: color-mix(in srgb, var(--hs-fg) 20%, transparent);
148
+ }
149
+
150
+ .hs-button--flat:hover {
151
+ background: var(--hs-control-tone);
152
+ }
153
+
154
+ .hs-button--flat:active {
155
+ background: color-mix(in srgb, var(--hs-fg) 15%, transparent);
156
+ }
157
+
158
+ .hs-button--suggested:hover {
159
+ background: color-mix(in srgb, var(--hs-accent-bg) 90%, #000);
160
+ }
161
+
162
+ .hs-button--suggested:active {
163
+ background: color-mix(in srgb, var(--hs-accent-bg) 80%, #000);
164
+ }
165
+
166
+ .hs-button--destructive:hover {
167
+ background: color-mix(in srgb, var(--hs-destructive-bg) 88%, #000);
168
+ }
169
+
170
+ .hs-button--destructive:active {
171
+ background: color-mix(in srgb, var(--hs-destructive-bg) 76%, #000);
172
+ }
173
+
174
+ /* Toggle on: the 24% tone and a heavier label. Declared after hover and press so the
175
+ * on state holds while the pointer is over it. */
176
+ .hs-button--on,
177
+ .hs-button[aria-pressed='true'] {
178
+ background: color-mix(in srgb, var(--hs-fg) 24%, transparent);
179
+ font-weight: 650;
180
+ }
181
+
182
+ /* f13-r3: tonal and flat buttons take the tint. */
183
+ .hs-button:focus-visible {
184
+ outline: none;
185
+ background: var(--hs-accent-tint);
186
+ color: var(--hs-accent-text);
187
+ box-shadow: var(--hs-focus-shadow);
188
+ transform: translateY(-1px);
189
+ }
190
+
191
+ /* k01b A: filled buttons deepen their own fill and keep their own label colour. */
192
+ .hs-button--suggested:focus-visible {
193
+ background: color-mix(in srgb, var(--hs-accent-bg) 72%, #000);
194
+ color: var(--hs-accent-fg);
195
+ }
196
+
197
+ .hs-button--destructive:focus-visible {
198
+ background: var(--hs-destructive-focus-bg);
199
+ color: var(--hs-destructive-fg);
200
+ }
201
+
202
+ /* k01d A: 40% opacity, inert. Every button fades the same way, destructive included. */
203
+ .hs-button:disabled,
204
+ .hs-button[aria-disabled='true'] {
205
+ opacity: 0.4;
206
+ pointer-events: none;
207
+ }
208
+
209
+ /* busy-rule: the button keeps its exact size, the spinner takes the label's place in
210
+ * the button's own colours (progress.css) and presses are ignored. The label stays in
211
+ * the accessibility tree, so the accessible name is unchanged. */
212
+ .hs-button[aria-busy='true'] {
213
+ position: relative;
214
+ pointer-events: none;
215
+ }
216
+
217
+ .hs-button[aria-busy='true'] > :not(.hs-spinner) {
218
+ opacity: 0;
219
+ }
220
+
221
+ .hs-button[aria-busy='true'] > .hs-spinner {
222
+ position: absolute;
223
+ inset: 0;
224
+ margin: auto;
225
+ }
226
+
227
+ /* ------------------------------------------------------------------ */
228
+ /* k02 - transport and play/pause */
229
+ /* ------------------------------------------------------------------ */
230
+
231
+ .hs-transport {
232
+ display: inline-flex;
233
+ align-items: center;
234
+ gap: 6px;
235
+ }
236
+
237
+ /* The now-playing view: wider gaps and 48 px flat round previous/next buttons with
238
+ * 24 px glyphs (k02). */
239
+ .hs-transport--large {
240
+ gap: 14px;
241
+ }
242
+
243
+ .hs-transport--large .hs-button--circular {
244
+ width: 48px;
245
+ height: 48px;
246
+ }
247
+
248
+ .hs-transport--large .hs-button__icon {
249
+ --hs-icon-size: 24px;
250
+ }
251
+
252
+ /* The disc: 44 px in the player bar and the mini player, 64 px in the full player. */
253
+ .hs-play-button {
254
+ display: inline-flex;
255
+ align-items: center;
256
+ justify-content: center;
257
+ flex: none;
258
+ width: 44px;
259
+ height: 44px;
260
+ padding: 0;
261
+ margin: 0;
262
+ border: 0;
263
+ border-radius: var(--hs-radius-full);
264
+ background: var(--hs-accent-bg);
265
+ color: var(--hs-accent-fg);
266
+ cursor: default;
267
+ appearance: none;
268
+ transition:
269
+ background var(--hs-duration-hover) var(--hs-ease),
270
+ box-shadow var(--hs-duration-hover) var(--hs-ease),
271
+ transform var(--hs-duration-press) var(--hs-ease);
272
+ }
273
+
274
+ .hs-play-button .hs-icon {
275
+ --hs-icon-size: 20px;
276
+ }
277
+
278
+ .hs-play-button--large {
279
+ width: 64px;
280
+ height: 64px;
281
+ }
282
+
283
+ .hs-play-button--large .hs-icon {
284
+ --hs-icon-size: 28px;
285
+ }
286
+
287
+ .hs-play-button:hover {
288
+ background: color-mix(in srgb, var(--hs-accent-bg) 90%, #000);
289
+ }
290
+
291
+ .hs-play-button:active {
292
+ background: color-mix(in srgb, var(--hs-accent-bg) 80%, #000);
293
+ }
294
+
295
+ .hs-play-button:focus-visible {
296
+ outline: none;
297
+ background: color-mix(in srgb, var(--hs-accent-bg) 72%, #000);
298
+ box-shadow: var(--hs-focus-shadow);
299
+ transform: translateY(-1px);
300
+ }
301
+
302
+ .hs-play-button:disabled,
303
+ .hs-play-button[aria-disabled='true'] {
304
+ opacity: 0.4;
305
+ pointer-events: none;
306
+ }
307
+
308
+ /* p19b, p19-r1: while a song is being fetched the disc holds a k09 spinner; on failure the
309
+ * same disc holds adwaita/refresh and is named "Retry". progress.css sizes and colours it. */