barua-ui 0.2.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,280 @@
1
+ @layer components {
2
+ /* ==========================================================================
3
+ Marketing — the anatomy of a product page.
4
+
5
+ A landing page is a sequence of full-width chapters, each one a single
6
+ idea: a headline, a sentence, and a picture that carries the weight. The
7
+ rhythm is what makes a family of product pages feel like one company, so
8
+ the spacing and the measure are the components, not decoration.
9
+ ========================================================================== */
10
+
11
+ /* ---- The page ------------------------------------------------------------ */
12
+ .b-landing {
13
+ --b-chapter-gap: clamp(4.5rem, 9vw, 8.5rem);
14
+ display: flex;
15
+ flex-direction: column;
16
+ }
17
+
18
+ /* ---- Slim marketing bar: quieter than an app's chrome -------------------- */
19
+ .b-marketing-nav {
20
+ position: sticky;
21
+ inset-block-start: 0;
22
+ z-index: var(--b-z-sticky);
23
+ display: flex;
24
+ align-items: center;
25
+ gap: var(--b-space-5);
26
+ height: 2.75rem;
27
+ padding-inline: var(--b-space-6);
28
+ font-size: var(--b-text-caption);
29
+ background: var(--b-material-chrome-bg);
30
+ -webkit-backdrop-filter: var(--b-glass);
31
+ backdrop-filter: var(--b-glass);
32
+ border-block-end: 1px solid var(--b-hairline);
33
+ }
34
+ .b-marketing-nav__brand { font-weight: var(--b-weight-semibold); font-size: var(--b-text-subheadline); }
35
+ .b-marketing-nav__links { display: flex; gap: var(--b-space-5); margin-inline: auto; }
36
+ .b-marketing-nav a { color: var(--b-text-secondary); text-decoration: none; }
37
+ .b-marketing-nav a:hover { color: var(--b-text); }
38
+
39
+ /* ---- Chapter: one idea, centred, with room around it --------------------- */
40
+ .b-chapter {
41
+ padding-block: var(--b-chapter-gap);
42
+ padding-inline: var(--b-space-6);
43
+ text-align: center;
44
+ }
45
+ .b-chapter--tight { padding-block: calc(var(--b-chapter-gap) * 0.55); }
46
+ .b-chapter--start { text-align: start; }
47
+ .b-chapter__eyebrow {
48
+ font-size: var(--b-text-subheadline);
49
+ font-weight: var(--b-weight-semibold);
50
+ color: var(--b-color-accent-text);
51
+ }
52
+ .b-chapter__title {
53
+ max-width: 20ch;
54
+ margin: var(--b-space-2) auto 0;
55
+ font-size: clamp(2rem, 5.2vw, 3.5rem);
56
+ font-weight: var(--b-weight-bold);
57
+ line-height: 1.06;
58
+ letter-spacing: var(--b-tracking-tight);
59
+ text-wrap: balance;
60
+ }
61
+ .b-chapter__lede {
62
+ max-width: 46ch;
63
+ margin: var(--b-space-4) auto 0;
64
+ font-size: var(--b-text-callout);
65
+ line-height: var(--b-leading-body);
66
+ color: var(--b-text-secondary);
67
+ text-wrap: pretty;
68
+ }
69
+ .b-chapter--start :is(.b-chapter__title, .b-chapter__lede) { margin-inline: 0; }
70
+ .b-chapter__actions {
71
+ display: flex;
72
+ flex-wrap: wrap;
73
+ gap: var(--b-space-4);
74
+ justify-content: center;
75
+ margin-block-start: var(--b-space-6);
76
+ }
77
+ .b-chapter--start .b-chapter__actions { justify-content: flex-start; }
78
+ .b-feature-row .b-chapter__actions { justify-content: flex-start; margin-block-start: var(--b-space-5); }
79
+
80
+ /* ---- The hero: the first chapter, with the product beneath it ------------ */
81
+ .b-hero { padding-block-start: clamp(3.5rem, 7vw, 6rem); }
82
+ .b-hero__title { font-size: clamp(2.5rem, 7vw, 4.75rem); }
83
+ .b-hero__media { margin-block-start: clamp(2.5rem, 5vw, 4rem); }
84
+
85
+ /* ---- Figures: a picture is furniture, so it has a shape ------------------ */
86
+ .b-figure { margin: 0; }
87
+ .b-figure__frame {
88
+ overflow: hidden;
89
+ border-radius: var(--b-radius-2xl);
90
+ box-shadow: var(--b-elevation-4);
91
+ background: var(--b-surface-2);
92
+ }
93
+ .b-figure__frame :is(img, video) { display: block; width: 100%; height: auto; }
94
+ .b-figure__caption {
95
+ margin-block-start: var(--b-space-3);
96
+ font-size: var(--b-text-footnote);
97
+ color: var(--b-text-tertiary);
98
+ }
99
+ .b-figure--bleed { margin-inline: calc(var(--b-space-6) * -1); }
100
+ .b-figure--bleed .b-figure__frame { border-radius: 0; }
101
+
102
+ /* ---- Alternating rows: picture beside prose ------------------------------ */
103
+ .b-feature-row {
104
+ display: grid;
105
+ gap: clamp(var(--b-space-6), 5vw, var(--b-space-10));
106
+ align-items: center;
107
+ text-align: start;
108
+ }
109
+ @media (min-width: 768px) {
110
+ .b-feature-row { grid-template-columns: 1fr 1fr; }
111
+ .b-feature-row--flip > :first-child { order: 2; }
112
+ }
113
+ .b-feature-row__title {
114
+ font-size: clamp(1.5rem, 3vw, 2.25rem);
115
+ font-weight: var(--b-weight-semibold);
116
+ line-height: var(--b-leading-tight);
117
+ letter-spacing: var(--b-tracking-tight);
118
+ }
119
+ .b-feature-row__body {
120
+ margin-block-start: var(--b-space-3);
121
+ color: var(--b-text-secondary);
122
+ line-height: var(--b-leading-body);
123
+ }
124
+
125
+ /* ---- Bento: tiles of unequal weight, one grid ---------------------------- */
126
+ .b-bento {
127
+ display: grid;
128
+ gap: var(--b-space-4);
129
+ grid-template-columns: repeat(auto-fit, minmax(min(17rem, 100%), 1fr));
130
+ text-align: start;
131
+ }
132
+ .b-bento__tile {
133
+ display: flex;
134
+ flex-direction: column;
135
+ gap: var(--b-space-2);
136
+ padding: var(--b-space-6);
137
+ background: var(--b-surface-2);
138
+ border: 1px solid var(--b-separator);
139
+ border-radius: var(--b-radius-2xl);
140
+ }
141
+ .b-bento__tile--wide { grid-column: span 2; }
142
+ @media (max-width: 767px) { .b-bento__tile--wide { grid-column: auto; } }
143
+ .b-bento__title { font-size: var(--b-text-headline); font-weight: var(--b-weight-semibold); }
144
+ .b-bento__body { font-size: var(--b-text-subheadline); color: var(--b-text-secondary); }
145
+
146
+ /* ---- A strip of numbers -------------------------------------------------- */
147
+ .b-spec-strip {
148
+ display: grid;
149
+ gap: var(--b-space-6);
150
+ grid-template-columns: repeat(auto-fit, minmax(min(11rem, 100%), 1fr));
151
+ text-align: center;
152
+ }
153
+ .b-spec__value {
154
+ display: block;
155
+ font-size: clamp(1.75rem, 3.5vw, 2.5rem);
156
+ font-weight: var(--b-weight-bold);
157
+ letter-spacing: var(--b-tracking-tight);
158
+ }
159
+ .b-spec__label { display: block; margin-block-start: var(--b-space-1); font-size: var(--b-text-footnote); color: var(--b-text-secondary); }
160
+
161
+ /* ---- A chapter that inverts ---------------------------------------------
162
+ Product pages alternate light and dark full-bleed sections so a page has
163
+ chapters you can feel while scrolling. Flipping color-scheme rather than
164
+ hardcoding colours means every token inside resolves for the dark ground,
165
+ so components dropped in keep working. */
166
+ .b-chapter--dark {
167
+ color-scheme: dark;
168
+ background: var(--b-bg);
169
+ color: var(--b-text);
170
+ }
171
+
172
+ /* ---- Pill switcher: one product, several platforms ----------------------- */
173
+ .b-pill-nav {
174
+ display: flex;
175
+ flex-wrap: wrap;
176
+ justify-content: center;
177
+ gap: var(--b-space-2);
178
+ padding: var(--b-space-4) var(--b-space-6);
179
+ }
180
+ .b-pill-nav a {
181
+ padding: var(--b-space-2) var(--b-space-4);
182
+ font-size: var(--b-text-subheadline);
183
+ color: var(--b-text-secondary);
184
+ text-decoration: none;
185
+ background: var(--b-fill-quaternary);
186
+ border-radius: var(--b-radius-full);
187
+ transition: background var(--b-duration-fast) var(--b-ease-out), color var(--b-duration-fast) var(--b-ease-out);
188
+ }
189
+ .b-pill-nav a:hover { background: var(--b-fill-tertiary); color: var(--b-text); }
190
+ .b-pill-nav a.is-active { background: var(--b-text); color: var(--b-bg); }
191
+
192
+ /* ---- Card rail: a row you push sideways ---------------------------------- */
193
+ .b-card-rail {
194
+ display: grid;
195
+ grid-auto-flow: column;
196
+ grid-auto-columns: minmax(min(17rem, 78vw), 21rem);
197
+ gap: var(--b-space-4);
198
+ padding-block-end: var(--b-space-3);
199
+ overflow-x: auto;
200
+ overscroll-behavior-inline: contain;
201
+ scroll-snap-type: x mandatory;
202
+ scrollbar-width: none;
203
+ text-align: start;
204
+ }
205
+ .b-card-rail::-webkit-scrollbar { display: none; }
206
+ .b-card-rail > * { scroll-snap-align: start; }
207
+ /* Photographs arrive at whatever shape they were taken; a rail needs one
208
+ shape, or the captions sit at four different heights. */
209
+ .b-card-rail .b-figure__frame { aspect-ratio: 4 / 3; }
210
+ .b-card-rail .b-figure__frame :is(img, video) { height: 100%; object-fit: cover; }
211
+ .b-rail-card__body { margin-block-start: var(--b-space-3); }
212
+ .b-rail-card__title { font-weight: var(--b-weight-semibold); }
213
+ .b-rail-card__text { margin-block-start: var(--b-space-1); font-size: var(--b-text-subheadline); color: var(--b-text-secondary); }
214
+
215
+ /* ---- Promo band: one offer, full width, its own colour ------------------- */
216
+ .b-promo-band {
217
+ color-scheme: dark;
218
+ padding: clamp(3rem, 6vw, 5rem) var(--b-space-6);
219
+ text-align: center;
220
+ color: #fff;
221
+ background: linear-gradient(135deg,
222
+ var(--b-color-accent),
223
+ color-mix(in srgb, var(--b-color-indigo) 80%, var(--b-color-purple)));
224
+ }
225
+ .b-promo-band__title {
226
+ font-size: clamp(1.75rem, 4vw, 2.75rem);
227
+ font-weight: var(--b-weight-bold);
228
+ letter-spacing: var(--b-tracking-tight);
229
+ }
230
+ .b-promo-band__body {
231
+ max-width: 52ch;
232
+ margin: var(--b-space-3) auto 0;
233
+ line-height: var(--b-leading-body);
234
+ color: rgba(255, 255, 255, 0.86);
235
+ }
236
+
237
+ /* ---- Footnotes: the small print a claim earns ---------------------------- */
238
+ .b-footnotes {
239
+ max-width: var(--b-container-xl);
240
+ margin-inline: auto;
241
+ padding: var(--b-space-8) var(--b-space-6) 0;
242
+ font-size: var(--b-text-caption);
243
+ line-height: var(--b-leading-body);
244
+ color: var(--b-text-tertiary);
245
+ }
246
+ .b-footnotes ol { padding-inline-start: var(--b-space-5); }
247
+ .b-footnotes li + li { margin-block-start: var(--b-space-2); }
248
+
249
+ /* ---- The footer: small, dense, and unafraid of links --------------------- */
250
+ .b-site-footer {
251
+ padding: var(--b-space-10) var(--b-space-6) var(--b-space-8);
252
+ font-size: var(--b-text-caption);
253
+ color: var(--b-text-secondary);
254
+ background: var(--b-surface-2);
255
+ border-block-start: 1px solid var(--b-separator);
256
+ }
257
+ .b-site-footer__groups {
258
+ display: grid;
259
+ gap: var(--b-space-6) var(--b-space-8);
260
+ grid-template-columns: repeat(auto-fit, minmax(min(11rem, 100%), 1fr));
261
+ max-width: var(--b-container-xl);
262
+ margin-inline: auto;
263
+ }
264
+ .b-site-footer__heading {
265
+ margin-block-end: var(--b-space-2);
266
+ font-size: var(--b-text-caption);
267
+ font-weight: var(--b-weight-semibold);
268
+ color: var(--b-text);
269
+ }
270
+ .b-site-footer__group a { display: block; padding-block: 0.2rem; color: var(--b-text-secondary); text-decoration: none; }
271
+ .b-site-footer__group a:hover { color: var(--b-text); text-decoration: underline; }
272
+ .b-site-footer__legal {
273
+ max-width: var(--b-container-xl);
274
+ margin: var(--b-space-8) auto 0;
275
+ padding-block-start: var(--b-space-5);
276
+ border-block-start: 1px solid var(--b-separator);
277
+ color: var(--b-text-tertiary);
278
+ line-height: var(--b-leading-body);
279
+ }
280
+ }
@@ -0,0 +1,321 @@
1
+ /* ==========================================================================
2
+ Barua UI — Media
3
+ Image Viewer, Image Gallery, Carousel, Video Player frame, Audio Player,
4
+ Media Controls, Thumbnail, Media Placeholder, Upload Preview grid.
5
+ ========================================================================== */
6
+
7
+ @layer components {
8
+ /* ---- Image Viewer -------------------------------------------------------- */
9
+ .b-image-viewer {
10
+ position: relative;
11
+ background: light-dark(#0e0e12, #000);
12
+ border-radius: var(--b-radius-xl);
13
+ overflow: hidden;
14
+ display: grid;
15
+ place-items: center;
16
+ }
17
+ .b-image-viewer img { max-height: 70vh; object-fit: contain; }
18
+ .b-image-viewer__bar {
19
+ position: absolute;
20
+ inset-inline: 0;
21
+ inset-block-end: 0;
22
+ display: flex;
23
+ align-items: center;
24
+ justify-content: space-between;
25
+ padding: var(--b-space-2) var(--b-space-3);
26
+ color: #fff;
27
+ background: linear-gradient(transparent, rgba(0, 0, 0, 0.55));
28
+ font-size: var(--b-text-footnote);
29
+ }
30
+
31
+ /* ---- Image Gallery -------------------------------------------------------- */
32
+ .b-gallery {
33
+ display: grid;
34
+ grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
35
+ gap: var(--b-space-2);
36
+ }
37
+ .b-gallery a, .b-gallery button {
38
+ display: block;
39
+ padding: 0;
40
+ border: none;
41
+ background: none;
42
+ border-radius: var(--b-radius-md);
43
+ overflow: hidden;
44
+ cursor: zoom-in;
45
+ }
46
+ .b-gallery img {
47
+ width: 100%;
48
+ aspect-ratio: 1;
49
+ object-fit: cover;
50
+ transition: scale var(--b-duration-normal) var(--b-ease-out);
51
+ }
52
+ .b-gallery :is(a, button):hover img { scale: 1.05; }
53
+
54
+ /* ---- Carousel (scroll-snap) ------------------------------------------------ */
55
+ .b-carousel { position: relative; }
56
+ .b-carousel__track {
57
+ display: flex;
58
+ gap: var(--b-space-3);
59
+ overflow-x: auto;
60
+ scroll-snap-type: x mandatory;
61
+ scroll-padding-inline: var(--b-space-1);
62
+ scrollbar-width: none;
63
+ padding-block: var(--b-space-1);
64
+ }
65
+ .b-carousel__track::-webkit-scrollbar { display: none; }
66
+ .b-carousel__slide {
67
+ flex: 0 0 var(--b-slide-w, 70%);
68
+ scroll-snap-align: start;
69
+ border-radius: var(--b-radius-lg);
70
+ overflow: hidden;
71
+ }
72
+ .b-carousel__slide img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; }
73
+ .b-carousel__nav {
74
+ position: absolute;
75
+ inset-block-start: 50%;
76
+ translate: 0 -50%;
77
+ z-index: var(--b-z-raised);
78
+ }
79
+ .b-carousel__nav--prev { inset-inline-start: var(--b-space-2); }
80
+ .b-carousel__nav--next { inset-inline-end: var(--b-space-2); }
81
+ .b-carousel__dots {
82
+ display: flex;
83
+ justify-content: center;
84
+ gap: var(--b-space-1);
85
+ margin-block-start: var(--b-space-2);
86
+ }
87
+ .b-carousel__dots button {
88
+ width: 0.45rem; height: 0.45rem;
89
+ padding: 0;
90
+ background: var(--b-fill);
91
+ border: none;
92
+ border-radius: 50%;
93
+ cursor: pointer;
94
+ transition: background var(--b-duration-fast), width var(--b-duration-normal) var(--b-ease-out);
95
+ }
96
+ .b-carousel__dots button.is-active {
97
+ width: 1.1rem;
98
+ border-radius: var(--b-radius-full);
99
+ background: var(--b-color-accent);
100
+ }
101
+
102
+ /* ---- Video Player frame ------------------------------------------------------ */
103
+ .b-video {
104
+ position: relative;
105
+ background: #000;
106
+ border-radius: var(--b-radius-xl);
107
+ overflow: hidden;
108
+ aspect-ratio: 16 / 9;
109
+ }
110
+ .b-video video { width: 100%; height: 100%; object-fit: contain; }
111
+ .b-video__overlay {
112
+ position: absolute;
113
+ inset: 0;
114
+ display: grid;
115
+ place-items: center;
116
+ background: rgba(0, 0, 0, 0.25);
117
+ opacity: 0;
118
+ transition: opacity var(--b-duration-normal);
119
+ }
120
+ .b-video:hover .b-video__overlay, .b-video.is-paused .b-video__overlay { opacity: 1; }
121
+ .b-video__play {
122
+ display: grid;
123
+ place-items: center;
124
+ width: 4rem; height: 4rem;
125
+ font-size: 1.4rem;
126
+ color: #fff;
127
+ background: rgba(20, 20, 26, 0.55);
128
+ -webkit-backdrop-filter: var(--b-glass);
129
+ backdrop-filter: var(--b-glass);
130
+ border: 1px solid rgba(255, 255, 255, 0.2);
131
+ border-radius: 50%;
132
+ cursor: pointer;
133
+ transition: scale var(--b-duration-fast) var(--b-ease-bounce);
134
+ }
135
+ .b-video__play:hover { scale: 1.08; }
136
+
137
+ /* ---- Media Controls (shared control bar) --------------------------------------- */
138
+ .b-media-controls {
139
+ position: absolute;
140
+ inset-inline: 0;
141
+ inset-block-end: 0;
142
+ display: flex;
143
+ align-items: center;
144
+ gap: var(--b-space-2);
145
+ padding: var(--b-space-2) var(--b-space-3);
146
+ color: #fff;
147
+ background: linear-gradient(transparent, rgba(0, 0, 0, 0.65));
148
+ color-scheme: dark;
149
+ }
150
+ .b-media-controls .b-icon-btn {
151
+ color: #fff;
152
+ background: transparent;
153
+ }
154
+ .b-media-controls .b-icon-btn:hover { background: rgba(255, 255, 255, 0.16); }
155
+ .b-media-controls .b-slider { flex: 1; }
156
+ .b-media-controls__time {
157
+ font-size: var(--b-text-caption);
158
+ font-variant-numeric: tabular-nums;
159
+ opacity: 0.85;
160
+ }
161
+
162
+ /* ---- Audio Player ---------------------------------------------------------------- */
163
+ .b-audio {
164
+ display: flex;
165
+ align-items: center;
166
+ gap: var(--b-space-3);
167
+ padding: var(--b-space-3) var(--b-space-4);
168
+ background: var(--b-surface);
169
+ border: 1px solid var(--b-separator);
170
+ border-radius: var(--b-radius-xl);
171
+ }
172
+ .b-audio--glass {
173
+ background: var(--b-material-regular-bg);
174
+ -webkit-backdrop-filter: var(--b-glass);
175
+ backdrop-filter: var(--b-glass);
176
+ border-color: var(--b-hairline);
177
+ }
178
+ .b-audio__art {
179
+ width: 3rem; height: 3rem;
180
+ border-radius: var(--b-radius-md);
181
+ object-fit: cover;
182
+ background: var(--b-fill-tertiary);
183
+ flex: none;
184
+ }
185
+ .b-audio__meta { flex: 1; min-width: 0; }
186
+ .b-audio__title { font-size: var(--b-text-footnote); font-weight: var(--b-weight-semibold); }
187
+ .b-audio__artist { font-size: var(--b-text-caption); color: var(--b-text-secondary); }
188
+ .b-audio__controls { display: flex; align-items: center; gap: var(--b-space-1); }
189
+ .b-audio .b-progress { margin-block-start: var(--b-space-1); }
190
+
191
+ /* Waveform bars (decorative) */
192
+ .b-waveform {
193
+ display: flex;
194
+ align-items: center;
195
+ gap: 2px;
196
+ height: 1.6rem;
197
+ flex: 1;
198
+ }
199
+ .b-waveform i {
200
+ flex: 1;
201
+ height: calc(var(--v, 0.4) * 100%);
202
+ min-width: 2px;
203
+ background: var(--b-fill);
204
+ border-radius: var(--b-radius-full);
205
+ }
206
+ .b-waveform i.is-played { background: var(--b-color-accent); }
207
+
208
+ /* ---- Async image (SwiftUI AsyncImage) ------------------------------------
209
+ <span class="b-async-img"><img data-b-async src="…"></span>
210
+ Shimmers until the image loads, then fades it in (barua.js).
211
+ --------------------------------------------------------------------------- */
212
+ .b-async-img {
213
+ position: relative;
214
+ display: block;
215
+ overflow: hidden;
216
+ border-radius: var(--b-radius-lg);
217
+ background: var(--b-fill-tertiary);
218
+ }
219
+ .b-async-img::before {
220
+ content: "";
221
+ position: absolute;
222
+ inset: 0;
223
+ translate: -100% 0;
224
+ background: linear-gradient(90deg, transparent,
225
+ light-dark(rgba(255, 255, 255, 0.6), rgba(255, 255, 255, 0.06)), transparent);
226
+ animation: b-shimmer 1.6s var(--b-ease-standard) infinite;
227
+ }
228
+ .b-async-img img {
229
+ width: 100%;
230
+ height: 100%;
231
+ object-fit: cover;
232
+ opacity: 0;
233
+ transition: opacity var(--b-duration-slow) var(--b-ease-out);
234
+ }
235
+ .b-async-img.is-loaded::before { display: none; }
236
+ .b-async-img.is-loaded img { opacity: 1; }
237
+
238
+ /* ---- Thumbnail --------------------------------------------------------------------- */
239
+ .b-thumb {
240
+ position: relative;
241
+ display: inline-block;
242
+ width: var(--b-thumb-size, 4.5rem);
243
+ aspect-ratio: 1;
244
+ border-radius: var(--b-radius-md);
245
+ overflow: hidden;
246
+ background: var(--b-fill-tertiary);
247
+ flex: none;
248
+ }
249
+ .b-thumb img { width: 100%; height: 100%; object-fit: cover; }
250
+ .b-thumb--video::after {
251
+ content: "▶";
252
+ position: absolute;
253
+ inset: 0;
254
+ display: grid;
255
+ place-items: center;
256
+ font-size: 1rem;
257
+ color: #fff;
258
+ background: rgba(0, 0, 0, 0.3);
259
+ }
260
+ .b-thumb__duration {
261
+ position: absolute;
262
+ inset-block-end: 0.25rem;
263
+ inset-inline-end: 0.25rem;
264
+ padding: 0.05rem 0.3rem;
265
+ font-size: var(--b-text-caption2);
266
+ font-variant-numeric: tabular-nums;
267
+ color: #fff;
268
+ background: rgba(0, 0, 0, 0.65);
269
+ border-radius: var(--b-radius-xs);
270
+ }
271
+
272
+ /* ---- Media Placeholder --------------------------------------------------------------- */
273
+ .b-media-placeholder {
274
+ display: grid;
275
+ place-items: center;
276
+ aspect-ratio: 16 / 9;
277
+ color: var(--b-text-quaternary);
278
+ background:
279
+ repeating-linear-gradient(45deg,
280
+ var(--b-fill-quaternary) 0 10px, transparent 10px 20px),
281
+ var(--b-fill-quaternary);
282
+ border: 1px dashed var(--b-border);
283
+ border-radius: var(--b-radius-lg);
284
+ }
285
+ .b-media-placeholder svg { width: 2rem; height: 2rem; }
286
+
287
+ /* ---- Upload Preview grid --------------------------------------------------------------- */
288
+ .b-upload-previews {
289
+ display: grid;
290
+ grid-template-columns: repeat(auto-fill, minmax(6rem, 1fr));
291
+ gap: var(--b-space-2);
292
+ }
293
+ .b-upload-preview {
294
+ position: relative;
295
+ aspect-ratio: 1;
296
+ border-radius: var(--b-radius-md);
297
+ overflow: hidden;
298
+ background: var(--b-fill-tertiary);
299
+ }
300
+ .b-upload-preview img { width: 100%; height: 100%; object-fit: cover; }
301
+ .b-upload-preview__remove {
302
+ position: absolute;
303
+ inset-block-start: 0.25rem;
304
+ inset-inline-end: 0.25rem;
305
+ display: grid;
306
+ place-items: center;
307
+ width: 1.4rem; height: 1.4rem;
308
+ font-size: 0.8rem;
309
+ color: #fff;
310
+ background: rgba(0, 0, 0, 0.55);
311
+ border: none;
312
+ border-radius: 50%;
313
+ cursor: pointer;
314
+ }
315
+ .b-upload-preview__remove:hover { background: var(--b-color-danger); }
316
+ .b-upload-preview .b-progress {
317
+ position: absolute;
318
+ inset-inline: 0.4rem;
319
+ inset-block-end: 0.4rem;
320
+ }
321
+ }