@birdycom/omniaui 0.11.1 → 0.12.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/dist/oc-theme.css CHANGED
@@ -1,4 +1,4 @@
1
- /* File: oc-theme.css — generated from tokens/semantic.json by @birdycom/omniacss 0.11.1. Do not edit. */
1
+ /* File: oc-theme.css — generated from tokens/semantic.json by @birdycom/omniacss 0.12.0. Do not edit. */
2
2
  /* Semantic theme tokens: every color is light-dark() of an Omnia palette token (oc-colors.css). */
3
3
  /* Same source as the desktop export: npx omniacss tokens --format json|dtcg|python */
4
4
  :root {
@@ -19,7 +19,7 @@
19
19
  .oc-fact-list--stacked dd { text-align: start; }
20
20
 
21
21
  /* Values in the accent color. */
22
- .oc-fact-list--accent dd { color: var(--oc-accent-from); }
22
+ .oc-fact-list--accent dd { color: light-dark(var(--oc-accent-from), oklch(from var(--oc-accent-from) max(l, 0.72) c h)); }
23
23
 
24
24
  /* Very narrow containers: wrap instead of squeezing label and value together. */
25
25
  .oc-fact-list > div { flex-wrap: wrap; }
@@ -20,7 +20,7 @@
20
20
  .oc-link-arrow--external:is(:hover, :focus-visible)::after { translate: 3px -3px; }
21
21
 
22
22
  /* Colors: accent of the element, or muted text that turns to the foreground color on hover. */
23
- .oc-link-arrow--accent { color: var(--oc-accent-from); }
23
+ .oc-link-arrow--accent { color: light-dark(var(--oc-accent-from), oklch(from var(--oc-accent-from) max(l, 0.72) c h)); }
24
24
  .oc-link-arrow--muted { color: var(--oc-muted-foreground); font-weight: 500; transition: color 0.2s; }
25
25
  .oc-link-arrow--muted:hover { color: var(--oc-foreground); }
26
26
 
@@ -47,7 +47,7 @@
47
47
  }
48
48
  .oc-next-teaser__tagline::after {
49
49
  content: "→";
50
- color: var(--oc-accent-from);
50
+ color: light-dark(var(--oc-accent-from), oklch(from var(--oc-accent-from) max(l, 0.72) c h));
51
51
  transition: translate 0.25s var(--oc-ease-out);
52
52
  }
53
53
 
@@ -55,7 +55,7 @@
55
55
  .oc-next-teaser--prev .oc-next-teaser__tagline::after { content: none; }
56
56
  .oc-next-teaser--prev .oc-next-teaser__tagline::before {
57
57
  content: "←";
58
- color: var(--oc-accent-from);
58
+ color: light-dark(var(--oc-accent-from), oklch(from var(--oc-accent-from) max(l, 0.72) c h));
59
59
  transition: translate 0.25s var(--oc-ease-out);
60
60
  }
61
61
 
@@ -58,7 +58,7 @@
58
58
  .oc-product-mark--soft {
59
59
  border: 1px solid color-mix(in oklch, var(--oc-accent-from) 40%, transparent);
60
60
  background: color-mix(in oklch, var(--oc-accent-from) 16%, var(--oc-card));
61
- color: var(--oc-accent-from);
61
+ color: light-dark(var(--oc-accent-from), oklch(from var(--oc-accent-from) max(l, 0.72) c h));
62
62
  box-shadow: none;
63
63
  }
64
64
 
@@ -50,6 +50,8 @@
50
50
  background:
51
51
  radial-gradient(circle at 80% 20%, color-mix(in oklch, var(--color-white-50) 30%, transparent), transparent 45%),
52
52
  linear-gradient(135deg, var(--oc-accent-from), var(--oc-accent-to));
53
+ /* The hover zoom grows down and sideways (clipped by the frame), never up over the title bar. */
54
+ transform-origin: 50% 0;
53
55
  transition: scale 0.5s var(--oc-ease-out);
54
56
  }
55
57
 
@@ -66,7 +68,7 @@
66
68
  padding: 0.2rem 0.6rem;
67
69
  border-radius: 999px;
68
70
  background: color-mix(in oklch, var(--oc-accent-from) 14%, transparent);
69
- color: var(--oc-accent-from);
71
+ color: light-dark(var(--oc-accent-from), oklch(from var(--oc-accent-from) max(l, 0.72) c h));
70
72
  font-size: 0.75rem;
71
73
  font-weight: 600;
72
74
  }
@@ -87,7 +89,7 @@
87
89
  gap: 0.4rem;
88
90
  margin-block-start: auto;
89
91
  padding-block-start: 0.6rem;
90
- color: var(--oc-accent-from);
92
+ color: light-dark(var(--oc-accent-from), oklch(from var(--oc-accent-from) max(l, 0.72) c h));
91
93
  font-size: 0.9rem;
92
94
  font-weight: 600;
93
95
  }
@@ -32,7 +32,7 @@
32
32
  display: inline-grid;
33
33
  place-items: center;
34
34
  inline-size: 1.1em;
35
- color: var(--oc-accent-from);
35
+ color: light-dark(var(--oc-accent-from), oklch(from var(--oc-accent-from) max(l, 0.72) c h));
36
36
  }
37
37
  .oc-stack-list__icon > :is(img, svg) { inline-size: 100%; block-size: auto; }
38
38
 
@@ -24,7 +24,7 @@
24
24
  /* Date or year label (use a <time> element when it is a real date).
25
25
  Scoped under .oc-timeline so it wins over the showcase rules ".oc-timeline span / strong". */
26
26
  .oc-timeline .oc-timeline__date {
27
- color: var(--oc-accent-from);
27
+ color: light-dark(var(--oc-accent-from), oklch(from var(--oc-accent-from) max(l, 0.72) c h));
28
28
  font-family: var(--oc-font-mono);
29
29
  font-size: 0.85rem;
30
30
  }
package/dist/showcase.css CHANGED
@@ -4,7 +4,8 @@
4
4
  fact/tag/check lists -> site header -> site footer -> reveal on scroll */
5
5
  /* Showcase components — modern site/marketing building blocks, pure CSS (no JavaScript).
6
6
  Colors: Omnia palette tokens only (OKLCH), via the theme tokens of components.css.
7
- Accent per element: --oc-accent-from / --oc-accent-to (or an .oc-accent--* preset). */
7
+ Accent per element: --oc-accent-from / --oc-accent-to (or an .oc-accent--* preset).
8
+ Accent as text color: in dark mode lifted to at least OKLCH L 0.72, so a dark custom accent stays readable. */
8
9
 
9
10
  :root {
10
11
  --oc-accent-from: var(--oc-brand-1);
@@ -47,7 +48,9 @@
47
48
  .oc-btn--pill { border-radius: 999px; }
48
49
  .oc-btn--gradient {
49
50
  border-radius: 999px;
50
- background: linear-gradient(100deg, var(--oc-accent-from), var(--oc-accent-to));
51
+ /* border-box: the gradient also runs under the transparent 1px border of .oc-btn; from the padding box it would
52
+ repeat in that border and show a thin stripe of the other end's color on both sides. */
53
+ background: linear-gradient(100deg, var(--oc-accent-from), var(--oc-accent-to)) border-box;
51
54
  color: var(--color-white-50);
52
55
  box-shadow: 0 10px 30px -10px var(--oc-accent-to);
53
56
  transition: transform 0.2s var(--oc-ease-out), box-shadow 0.2s;
@@ -173,7 +176,7 @@
173
176
  background:
174
177
  linear-gradient(135deg, oklch(from var(--oc-accent-from) l c h / 0.3), oklch(from var(--oc-accent-to) l c h / 0.18)),
175
178
  var(--oc-card);
176
- color: var(--oc-accent-from);
179
+ color: light-dark(var(--oc-accent-from), oklch(from var(--oc-accent-from) max(l, 0.72) c h));
177
180
  font-size: calc(var(--oc-tile-size) * 0.45);
178
181
  line-height: 1;
179
182
  }
@@ -213,7 +216,7 @@
213
216
  }
214
217
  .oc-product-card__top { display: flex; justify-content: space-between; align-items: flex-start; margin-block-end: 0.75rem; }
215
218
  .oc-product-card__name { margin: 0; font-size: 1.45rem; font-weight: 700; letter-spacing: -0.02em; }
216
- .oc-product-card__tagline { margin: 0; color: var(--oc-accent-from); font-weight: 600; font-size: 0.95rem; }
219
+ .oc-product-card__tagline { margin: 0; color: light-dark(var(--oc-accent-from), oklch(from var(--oc-accent-from) max(l, 0.72) c h)); font-weight: 600; font-size: 0.95rem; }
217
220
  .oc-product-card__summary { margin: 0; color: var(--oc-muted-foreground); font-size: 0.95rem; flex-grow: 1; }
218
221
  .oc-product-card__more { display: inline-flex; align-items: center; gap: 0.4rem; margin-block-start: 0.75rem; font-weight: 600; font-size: 0.92rem; }
219
222
  .oc-product-card__more::after { content: "→"; transition: translate 0.25s var(--oc-ease-out); }
@@ -235,18 +238,23 @@
235
238
  .oc-pill--accent {
236
239
  border-color: oklch(from var(--oc-accent-from) l c h / 0.45);
237
240
  background: oklch(from var(--oc-accent-from) l c h / 0.14);
238
- color: var(--oc-accent-from);
241
+ color: light-dark(var(--oc-accent-from), oklch(from var(--oc-accent-from) max(l, 0.72) c h));
239
242
  }
243
+ /* Pulse: the ring starts at --oc-status-dot-pulse-alpha of the accent and grows to --oc-status-dot-pulse-size.
244
+ --strong starts at the full accent with a wider ring. */
240
245
  .oc-status-dot {
246
+ --oc-status-dot-pulse-alpha: 0.6;
247
+ --oc-status-dot-pulse-size: 8px;
241
248
  inline-size: 0.5rem;
242
249
  aspect-ratio: 1;
243
250
  border-radius: 50%;
244
251
  background: var(--oc-accent-from);
245
252
  animation: oc-pulse 2s infinite;
246
253
  }
254
+ .oc-status-dot--strong { --oc-status-dot-pulse-alpha: 1; --oc-status-dot-pulse-size: 10px; }
247
255
  @keyframes oc-pulse {
248
- 0% { box-shadow: 0 0 0 0 oklch(from var(--oc-accent-from) l c h / 0.6); }
249
- 70%, 100% { box-shadow: 0 0 0 8px transparent; }
256
+ 0% { box-shadow: 0 0 0 0 oklch(from var(--oc-accent-from) l c h / var(--oc-status-dot-pulse-alpha)); }
257
+ 70%, 100% { box-shadow: 0 0 0 var(--oc-status-dot-pulse-size) transparent; }
250
258
  }
251
259
 
252
260
  /* Stat strip: big numbers in a bordered row (pairs with .oc-counter for counting up) */
@@ -277,7 +285,7 @@
277
285
  var(--oc-card);
278
286
  }
279
287
  .oc-spotlight__title { display: flex; align-items: center; gap: 1rem; margin: 1.25rem 0 0.5rem; font-size: clamp(2.2rem, 5vw, 3.5rem); line-height: 1.1; }
280
- .oc-spotlight__tagline { color: var(--oc-accent-from); font-size: 1.2rem; font-weight: 600; }
288
+ .oc-spotlight__tagline { color: light-dark(var(--oc-accent-from), oklch(from var(--oc-accent-from) max(l, 0.72) c h)); font-size: 1.2rem; font-weight: 600; }
281
289
  .oc-spotlight__modules { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr)); gap: 0.75rem; margin: 0; padding: 0; list-style: none; }
282
290
  .oc-spotlight__modules > li { display: flex; align-items: center; gap: 0.75rem; padding: 0.9rem; border: 1px solid var(--oc-border); border-radius: var(--oc-radius-md); background: var(--oc-surface); font-size: 0.95rem; }
283
291
 
@@ -352,9 +360,14 @@
352
360
  }
353
361
  @media (max-width: 860px) {
354
362
  .oc-site-header__toggle { display: block; }
363
+ /* The author rule display:flex above beats the UA rule that hides a closed popover. */
364
+ .oc-site-header__nav[popover]:not(:popover-open) { display: none; }
355
365
  .oc-site-header__nav[popover]:popover-open {
356
366
  position: fixed;
357
367
  inset: 4.5rem 0 auto;
368
+ /* The UA popover style sizes to fit-content; let inset span the full width. */
369
+ inline-size: auto;
370
+ block-size: auto;
358
371
  display: flex;
359
372
  flex-direction: column;
360
373
  align-items: stretch;
package/manifest.json CHANGED
@@ -1103,6 +1103,14 @@
1103
1103
  "requires": [
1104
1104
  "@birdycom/omniaui"
1105
1105
  ]
1106
+ },
1107
+ {
1108
+ "name": "carousel-loop",
1109
+ "file": "snippets/carousel-loop.html",
1110
+ "description": "Looping carousel: after the last slide the next button goes back to the first",
1111
+ "requires": [
1112
+ "@birdycom/omniaui"
1113
+ ]
1106
1114
  }
1107
1115
  ]
1108
1116
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@birdycom/omniaui",
3
- "version": "0.11.1",
3
+ "version": "0.12.0",
4
4
  "description": "OmniaUI components: CSS-first UI built on native HTML (dialog, popover, details) with light-dark() theming — no JavaScript required. Includes the omniaui CLI (add/list).",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -29,6 +29,7 @@
29
29
  "./showcase.css": "./dist/showcase.css",
30
30
  "./snippets/*": "./snippets/*",
31
31
  "./data-table.js": "./dist/data-table.js",
32
+ "./carousel.js": "./dist/carousel.js",
32
33
  "./manifest.json": "./manifest.json",
33
34
  "./package.json": "./package.json"
34
35
  },
@@ -56,7 +57,7 @@
56
57
  "omniaui": "./bin/omniaui.mjs"
57
58
  },
58
59
  "peerDependencies": {
59
- "@birdycom/omniacss": "^0.11.1"
60
+ "@birdycom/omniacss": "^0.12.0"
60
61
  },
61
62
  "peerDependenciesMeta": {
62
63
  "@birdycom/omniacss": {
@@ -0,0 +1,20 @@
1
+ <!-- OmniaUI component: carousel-loop - Looping carousel: after the last slide the next button goes back to the first. Needs @birdycom/omniaui CSS and @birdycom/omniaui/carousel.js.
2
+ Class API: .oc-carousel--loop on the carousel, with .oc-carousel-prev / .oc-carousel-next buttons.
3
+ Script: import { initCarousels } from "@birdycom/omniaui/carousel.js"; initCarousels();
4
+ Without the script the buttons stay hidden and the CSS arrows (which stop at the ends) remain. -->
5
+ <div class="oc-carousel oc-carousel--loop">
6
+ <div class="oc-carousel-viewport" id="loop-carousel" tabindex="0" role="region" aria-roledescription="carousel" aria-label="Highlights">
7
+ <div class="oc-carousel-item" id="loop-slide-1" role="group" aria-roledescription="slide" aria-label="1 of 4">Slide 1</div>
8
+ <div class="oc-carousel-item" id="loop-slide-2" role="group" aria-roledescription="slide" aria-label="2 of 4">Slide 2</div>
9
+ <div class="oc-carousel-item" id="loop-slide-3" role="group" aria-roledescription="slide" aria-label="3 of 4">Slide 3</div>
10
+ <div class="oc-carousel-item" id="loop-slide-4" role="group" aria-roledescription="slide" aria-label="4 of 4">Slide 4</div>
11
+ </div>
12
+ <button class="oc-carousel-prev" type="button" aria-controls="loop-carousel" aria-label="Previous slide">&larr;</button>
13
+ <button class="oc-carousel-next" type="button" aria-controls="loop-carousel" aria-label="Next slide">&rarr;</button>
14
+ <nav class="oc-carousel-dots" aria-label="Choose a slide">
15
+ <a class="oc-carousel-dot" href="#loop-slide-1" aria-label="Slide 1"></a>
16
+ <a class="oc-carousel-dot" href="#loop-slide-2" aria-label="Slide 2"></a>
17
+ <a class="oc-carousel-dot" href="#loop-slide-3" aria-label="Slide 3"></a>
18
+ <a class="oc-carousel-dot" href="#loop-slide-4" aria-label="Slide 4"></a>
19
+ </nav>
20
+ </div>
@@ -47,4 +47,17 @@ document.addEventListener("command", (e) => {
47
47
  const id = e.source?.dataset.ocLightboxSlide;
48
48
  if (id) requestAnimationFrame(() => document.getElementById(id)?.scrollIntoView({ behavior: "instant", block: "nearest", inline: "center" }));
49
49
  }, true);
50
+ // Optional: prev / next scroll without putting #slide in the address and the history (without this script they
51
+ // are plain links), and closing removes a slide hash of this viewer from the address.
52
+ document.addEventListener("click", (e) => {
53
+ const nav = e.target.closest?.("dialog.oc-lightbox a.oc-lightbox__nav");
54
+ const slide = nav && document.getElementById(decodeURIComponent(nav.hash.slice(1)));
55
+ if (!slide) return;
56
+ e.preventDefault();
57
+ slide.scrollIntoView({ behavior: matchMedia("(prefers-reduced-motion: reduce)").matches ? "instant" : "smooth", block: "nearest", inline: "center" });
58
+ });
59
+ document.addEventListener("close", (e) => {
60
+ const slide = e.target.matches?.("dialog.oc-lightbox") && location.hash && document.getElementById(decodeURIComponent(location.hash.slice(1)));
61
+ if (slide && e.target.contains(slide)) history.replaceState(history.state, "", location.pathname + location.search);
62
+ }, true);
50
63
  </script>