@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/carousel.d.ts +6 -0
- package/dist/carousel.js +46 -0
- package/dist/components-extra.css +39 -0
- package/dist/components.css +166 -29
- package/dist/components.min.css +1 -1
- package/dist/oc-theme.css +1 -1
- package/dist/parts/site-fact-list.css +1 -1
- package/dist/parts/site-link-arrow.css +1 -1
- package/dist/parts/site-next-teaser.css +2 -2
- package/dist/parts/site-product-mark.css +1 -1
- package/dist/parts/site-project-card.css +4 -2
- package/dist/parts/site-stack-list.css +1 -1
- package/dist/parts/site-timeline.css +1 -1
- package/dist/showcase.css +21 -8
- package/manifest.json +8 -0
- package/package.json +3 -2
- package/snippets/carousel-loop.html +20 -0
- package/snippets/lightbox.html +13 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// File: packages/ui/src/carousel.d.ts
|
|
2
|
+
// Types for the looping carousel script (.oc-carousel--loop prev/next buttons).
|
|
3
|
+
|
|
4
|
+
export function loopTarget(position: number, max: number, step: number, direction: 1 | -1): number;
|
|
5
|
+
export function bindCarousel(root: HTMLElement): void;
|
|
6
|
+
export function initCarousels(scope?: ParentNode): void;
|
package/dist/carousel.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// File: packages/ui/src/carousel.js
|
|
2
|
+
// Map: next scroll position (wraps at the ends) -> bind one .oc-carousel--loop (prev/next buttons) -> boot
|
|
3
|
+
// Loop for the CSS carousel: the browser's ::scroll-button arrows stop at the first and last slide, so a looping
|
|
4
|
+
// carousel has two real buttons (.oc-carousel-prev / .oc-carousel-next) and this script moves it one slide, from the
|
|
5
|
+
// last slide back to the first and from the first to the last. Swipe, snap, dots and keyboard stay CSS.
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Where to scroll for one step. `position` and `max` are distances from the start edge (positive, also in RTL),
|
|
9
|
+
* `step` is one slide plus the gap. At an end (within 2px, scroll snapping rounds) it wraps around.
|
|
10
|
+
*/
|
|
11
|
+
export function loopTarget(position, max, step, direction) {
|
|
12
|
+
if (direction > 0) return position >= max - 2 ? 0 : Math.min(max, position + step);
|
|
13
|
+
return position <= 2 ? max : Math.max(0, position - step);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Wire the prev/next buttons of one looping carousel (once). */
|
|
17
|
+
export function bindCarousel(root) {
|
|
18
|
+
if (root.hasAttribute("data-oc-loop")) return;
|
|
19
|
+
const viewport = root.querySelector(".oc-carousel-viewport");
|
|
20
|
+
const items = viewport?.querySelectorAll(".oc-carousel-item");
|
|
21
|
+
if (!viewport || !items?.length) return;
|
|
22
|
+
const rtl = getComputedStyle(viewport).direction === "rtl";
|
|
23
|
+
const move = (direction) => {
|
|
24
|
+
const max = viewport.scrollWidth - viewport.clientWidth;
|
|
25
|
+
// One slide plus the gap: the distance between the first two slides (one slide wide when there is only one).
|
|
26
|
+
const step = items.length > 1 ? Math.abs(items[1].offsetLeft - items[0].offsetLeft) : viewport.clientWidth;
|
|
27
|
+
const position = Math.abs(viewport.scrollLeft);
|
|
28
|
+
const target = loopTarget(position, max, step, direction);
|
|
29
|
+
viewport.scrollTo({ left: rtl ? -target : target });
|
|
30
|
+
};
|
|
31
|
+
root.querySelector(".oc-carousel-prev")?.addEventListener("click", () => move(-1));
|
|
32
|
+
root.querySelector(".oc-carousel-next")?.addEventListener("click", () => move(1));
|
|
33
|
+
root.setAttribute("data-oc-loop", "");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Wire every looping carousel under scope that is not wired yet. */
|
|
37
|
+
export function initCarousels(scope = document) {
|
|
38
|
+
scope.querySelectorAll(".oc-carousel--loop").forEach((root) => bindCarousel(root));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// The docs site loads this file as /js/oc-carousel.js. A wrapper import must not scan the page.
|
|
42
|
+
if (typeof document !== "undefined" && String(import.meta.url).includes("oc-carousel")) {
|
|
43
|
+
const boot = () => initCarousels(document);
|
|
44
|
+
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", boot);
|
|
45
|
+
else boot();
|
|
46
|
+
}
|
|
@@ -503,6 +503,45 @@ a.oc-item:focus-visible { outline: 2px solid var(--oc-ring); outline-offset: 1px
|
|
|
503
503
|
.oc-carousel-viewport::scroll-button(inline-end):disabled { opacity: 0.35; cursor: default; }
|
|
504
504
|
}
|
|
505
505
|
|
|
506
|
+
/* Loop (.oc-carousel--loop): after the last slide comes the first again. CSS scroll buttons stop at the ends, so this
|
|
507
|
+
variant has two real buttons (.oc-carousel-prev / .oc-carousel-next) driven by carousel.js; swipe, snap, dots and
|
|
508
|
+
keyboard stay CSS. Until the script has run (data-oc-loop), the buttons stay hidden and the CSS arrows remain. */
|
|
509
|
+
.oc-carousel-prev,
|
|
510
|
+
.oc-carousel-next {
|
|
511
|
+
position: absolute;
|
|
512
|
+
inset-block-start: 50%;
|
|
513
|
+
translate: 0 -50%;
|
|
514
|
+
z-index: 1;
|
|
515
|
+
inline-size: 2.25rem;
|
|
516
|
+
block-size: 2.25rem;
|
|
517
|
+
display: grid;
|
|
518
|
+
place-items: center;
|
|
519
|
+
padding: 0;
|
|
520
|
+
font-size: 1rem;
|
|
521
|
+
color: var(--oc-foreground);
|
|
522
|
+
background: var(--oc-background);
|
|
523
|
+
border: 1px solid var(--oc-border);
|
|
524
|
+
border-radius: 50%;
|
|
525
|
+
box-shadow: 0 2px 8px oklch(from var(--color-black-60) l c h / 0.18);
|
|
526
|
+
cursor: pointer;
|
|
527
|
+
}
|
|
528
|
+
.oc-carousel-prev { inset-inline-start: 0.5rem; }
|
|
529
|
+
.oc-carousel-next { inset-inline-end: 0.5rem; }
|
|
530
|
+
.oc-carousel-prev:hover,
|
|
531
|
+
.oc-carousel-next:hover { background: var(--oc-accent); }
|
|
532
|
+
.oc-carousel-prev:focus-visible,
|
|
533
|
+
.oc-carousel-next:focus-visible { outline: 2px solid var(--oc-ring); outline-offset: 2px; }
|
|
534
|
+
.oc-carousel--loop:not([data-oc-loop]) :is(.oc-carousel-prev, .oc-carousel-next) { display: none; }
|
|
535
|
+
/* Centered on the viewport, not on the carousel with its dots underneath. */
|
|
536
|
+
@supports (anchor-name: --a) {
|
|
537
|
+
.oc-carousel-prev,
|
|
538
|
+
.oc-carousel-next { position-anchor: --oc-carousel; inset-block-start: anchor(center); }
|
|
539
|
+
}
|
|
540
|
+
@supports selector(::scroll-button(inline-start)) {
|
|
541
|
+
.oc-carousel--loop[data-oc-loop] .oc-carousel-viewport::scroll-button(inline-start),
|
|
542
|
+
.oc-carousel--loop[data-oc-loop] .oc-carousel-viewport::scroll-button(inline-end) { content: none; }
|
|
543
|
+
}
|
|
544
|
+
|
|
506
545
|
/* ===================================================================
|
|
507
546
|
RESIZABLE — split panes using the native CSS `resize` property (drag the grip in the corner of the resizable panel).
|
|
508
547
|
A full-length drag handle between panes with keyboard support needs script: wrapper responsibility.
|
package/dist/components.css
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* File: oc-theme.css — generated from tokens/semantic.json by @birdycom/omniacss 0.
|
|
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 {
|
|
@@ -1363,37 +1363,120 @@ textarea.oc-input {
|
|
|
1363
1363
|
|
|
1364
1364
|
/* ===================================================================
|
|
1365
1365
|
TOOLTIP
|
|
1366
|
-
Pure CSS
|
|
1366
|
+
Pure CSS. Text: the data-tooltip attribute (drawn by ::after). Richer content, such as a title above lines
|
|
1367
|
+
of values: a child .oc-tooltip__content. With anchor positioning the bubble is anchored to its element and
|
|
1368
|
+
moves to the side that has room; otherwise it sits on the preferred side (top by default).
|
|
1367
1369
|
=================================================================== */
|
|
1368
1370
|
|
|
1369
1371
|
.oc-tooltip {
|
|
1372
|
+
--oc-tooltip-bg: oklch(from var(--oc-popover) l c h / 0.94);
|
|
1373
|
+
--oc-tooltip-gap: 8px;
|
|
1370
1374
|
position: relative;
|
|
1371
1375
|
cursor: help;
|
|
1372
1376
|
}
|
|
1373
1377
|
|
|
1374
|
-
.oc-tooltip::after
|
|
1375
|
-
|
|
1378
|
+
.oc-tooltip::after,
|
|
1379
|
+
.oc-tooltip > .oc-tooltip__content {
|
|
1376
1380
|
position: absolute;
|
|
1377
|
-
bottom: calc(100% +
|
|
1381
|
+
bottom: calc(100% + var(--oc-tooltip-gap));
|
|
1378
1382
|
left: 50%;
|
|
1379
|
-
|
|
1383
|
+
translate: -50% 0;
|
|
1380
1384
|
padding: 6px 10px;
|
|
1381
1385
|
font-size: 0.75rem;
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
+
font-weight: 500;
|
|
1387
|
+
line-height: 1.45;
|
|
1388
|
+
color: var(--oc-popover-foreground);
|
|
1389
|
+
background: var(--oc-tooltip-bg);
|
|
1390
|
+
border: 1px solid var(--oc-border);
|
|
1391
|
+
border-radius: var(--oc-radius);
|
|
1392
|
+
box-shadow: 0 10px 28px -8px oklch(from var(--color-black-60) l c h / 0.3), 0 2px 6px -2px oklch(from var(--color-black-60) l c h / 0.15);
|
|
1393
|
+
backdrop-filter: blur(12px) saturate(1.4);
|
|
1386
1394
|
white-space: nowrap;
|
|
1387
1395
|
pointer-events: none;
|
|
1388
1396
|
opacity: 0;
|
|
1389
|
-
|
|
1397
|
+
scale: 0.96;
|
|
1398
|
+
transition: opacity var(--oc-transition-fast), scale 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
1390
1399
|
z-index: 50;
|
|
1391
1400
|
}
|
|
1392
1401
|
|
|
1402
|
+
.oc-tooltip::after { content: attr(data-tooltip); }
|
|
1403
|
+
/* Rich content only: no empty ::after bubble. */
|
|
1404
|
+
.oc-tooltip:not([data-tooltip])::after { content: none; }
|
|
1405
|
+
|
|
1393
1406
|
/* Also on keyboard focus of the element inside (Tab), not only on mouse hover. */
|
|
1394
1407
|
.oc-tooltip:hover::after,
|
|
1395
|
-
.oc-tooltip:focus-within::after
|
|
1408
|
+
.oc-tooltip:focus-within::after,
|
|
1409
|
+
.oc-tooltip:hover > .oc-tooltip__content,
|
|
1410
|
+
.oc-tooltip:focus-within > .oc-tooltip__content {
|
|
1396
1411
|
opacity: 1;
|
|
1412
|
+
scale: 1;
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1416
|
+
.oc-tooltip::after,
|
|
1417
|
+
.oc-tooltip > .oc-tooltip__content { transition: opacity var(--oc-transition-fast); scale: 1; }
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
/* Several lines: line breaks in data-tooltip (or in .oc-tooltip__content) start a new line. */
|
|
1421
|
+
.oc-tooltip--multiline::after,
|
|
1422
|
+
.oc-tooltip > .oc-tooltip__content {
|
|
1423
|
+
white-space: pre-line;
|
|
1424
|
+
inline-size: max-content;
|
|
1425
|
+
max-inline-size: 20rem;
|
|
1426
|
+
padding: 8px 12px;
|
|
1427
|
+
text-align: start;
|
|
1428
|
+
font-variant-numeric: tabular-nums;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
/* Title line of the rich content: a bit larger, always starting with a capital. */
|
|
1432
|
+
.oc-tooltip__title {
|
|
1433
|
+
display: block;
|
|
1434
|
+
margin-block-end: 2px;
|
|
1435
|
+
font-size: 0.875rem;
|
|
1436
|
+
font-weight: 700;
|
|
1437
|
+
line-height: 1.35;
|
|
1438
|
+
}
|
|
1439
|
+
.oc-tooltip__title::first-letter { text-transform: uppercase; }
|
|
1440
|
+
|
|
1441
|
+
/* Preferred side (default: top). Without anchor positioning these are fixed places next to the element. */
|
|
1442
|
+
.oc-tooltip--bottom::after,
|
|
1443
|
+
.oc-tooltip--bottom > .oc-tooltip__content { bottom: auto; top: calc(100% + var(--oc-tooltip-gap)); }
|
|
1444
|
+
.oc-tooltip--left::after,
|
|
1445
|
+
.oc-tooltip--left > .oc-tooltip__content { bottom: auto; left: auto; top: 50%; right: calc(100% + var(--oc-tooltip-gap)); translate: 0 -50%; }
|
|
1446
|
+
.oc-tooltip--right::after,
|
|
1447
|
+
.oc-tooltip--right > .oc-tooltip__content { bottom: auto; top: 50%; left: calc(100% + var(--oc-tooltip-gap)); translate: 0 -50%; }
|
|
1448
|
+
|
|
1449
|
+
/* Anchor positioning: fixed, so a scroll container (a wide table, a sidebar) does not clip it, and flipped to the
|
|
1450
|
+
opposite side (then the other axis) when the preferred side has no room. ::after uses its element as implicit
|
|
1451
|
+
anchor; .oc-tooltip__content needs a named anchor, scoped to its own .oc-tooltip (anchor-scope). */
|
|
1452
|
+
@supports (position-area: top) {
|
|
1453
|
+
.oc-tooltip::after {
|
|
1454
|
+
position: fixed;
|
|
1455
|
+
inset: auto;
|
|
1456
|
+
translate: none;
|
|
1457
|
+
margin: var(--oc-tooltip-gap);
|
|
1458
|
+
position-area: top;
|
|
1459
|
+
position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
|
|
1460
|
+
}
|
|
1461
|
+
.oc-tooltip--bottom::after { position-area: bottom; }
|
|
1462
|
+
.oc-tooltip--left::after { position-area: left; position-try-fallbacks: flip-inline, flip-block, flip-block flip-inline; }
|
|
1463
|
+
.oc-tooltip--right::after { position-area: right; position-try-fallbacks: flip-inline, flip-block, flip-block flip-inline; }
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
@supports (position-area: top) and (anchor-scope: all) {
|
|
1467
|
+
.oc-tooltip { anchor-name: --oc-tooltip; anchor-scope: --oc-tooltip; }
|
|
1468
|
+
.oc-tooltip > .oc-tooltip__content {
|
|
1469
|
+
position: fixed;
|
|
1470
|
+
inset: auto;
|
|
1471
|
+
translate: none;
|
|
1472
|
+
margin: var(--oc-tooltip-gap);
|
|
1473
|
+
position-anchor: --oc-tooltip;
|
|
1474
|
+
position-area: top;
|
|
1475
|
+
position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
|
|
1476
|
+
}
|
|
1477
|
+
.oc-tooltip--bottom > .oc-tooltip__content { position-area: bottom; }
|
|
1478
|
+
.oc-tooltip--left > .oc-tooltip__content { position-area: left; position-try-fallbacks: flip-inline, flip-block, flip-block flip-inline; }
|
|
1479
|
+
.oc-tooltip--right > .oc-tooltip__content { position-area: right; position-try-fallbacks: flip-inline, flip-block, flip-block flip-inline; }
|
|
1397
1480
|
}
|
|
1398
1481
|
|
|
1399
1482
|
/* ===================================================================
|
|
@@ -3502,7 +3585,8 @@ details.oc-collapsible[open]::details-content { block-size: auto; }
|
|
|
3502
3585
|
fact/tag/check lists -> site header -> site footer -> reveal on scroll */
|
|
3503
3586
|
/* Showcase components — modern site/marketing building blocks, pure CSS (no JavaScript).
|
|
3504
3587
|
Colors: Omnia palette tokens only (OKLCH), via the theme tokens of components.css.
|
|
3505
|
-
Accent per element: --oc-accent-from / --oc-accent-to (or an .oc-accent--* preset).
|
|
3588
|
+
Accent per element: --oc-accent-from / --oc-accent-to (or an .oc-accent--* preset).
|
|
3589
|
+
Accent as text color: in dark mode lifted to at least OKLCH L 0.72, so a dark custom accent stays readable. */
|
|
3506
3590
|
|
|
3507
3591
|
:root {
|
|
3508
3592
|
--oc-accent-from: var(--oc-brand-1);
|
|
@@ -3545,7 +3629,9 @@ details.oc-collapsible[open]::details-content { block-size: auto; }
|
|
|
3545
3629
|
.oc-btn--pill { border-radius: 999px; }
|
|
3546
3630
|
.oc-btn--gradient {
|
|
3547
3631
|
border-radius: 999px;
|
|
3548
|
-
|
|
3632
|
+
/* border-box: the gradient also runs under the transparent 1px border of .oc-btn; from the padding box it would
|
|
3633
|
+
repeat in that border and show a thin stripe of the other end's color on both sides. */
|
|
3634
|
+
background: linear-gradient(100deg, var(--oc-accent-from), var(--oc-accent-to)) border-box;
|
|
3549
3635
|
color: var(--color-white-50);
|
|
3550
3636
|
box-shadow: 0 10px 30px -10px var(--oc-accent-to);
|
|
3551
3637
|
transition: transform 0.2s var(--oc-ease-out), box-shadow 0.2s;
|
|
@@ -3671,7 +3757,7 @@ details.oc-collapsible[open]::details-content { block-size: auto; }
|
|
|
3671
3757
|
background:
|
|
3672
3758
|
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)),
|
|
3673
3759
|
var(--oc-card);
|
|
3674
|
-
color: var(--oc-accent-from);
|
|
3760
|
+
color: light-dark(var(--oc-accent-from), oklch(from var(--oc-accent-from) max(l, 0.72) c h));
|
|
3675
3761
|
font-size: calc(var(--oc-tile-size) * 0.45);
|
|
3676
3762
|
line-height: 1;
|
|
3677
3763
|
}
|
|
@@ -3711,7 +3797,7 @@ details.oc-collapsible[open]::details-content { block-size: auto; }
|
|
|
3711
3797
|
}
|
|
3712
3798
|
.oc-product-card__top { display: flex; justify-content: space-between; align-items: flex-start; margin-block-end: 0.75rem; }
|
|
3713
3799
|
.oc-product-card__name { margin: 0; font-size: 1.45rem; font-weight: 700; letter-spacing: -0.02em; }
|
|
3714
|
-
.oc-product-card__tagline { margin: 0; color: var(--oc-accent-from); font-weight: 600; font-size: 0.95rem; }
|
|
3800
|
+
.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; }
|
|
3715
3801
|
.oc-product-card__summary { margin: 0; color: var(--oc-muted-foreground); font-size: 0.95rem; flex-grow: 1; }
|
|
3716
3802
|
.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; }
|
|
3717
3803
|
.oc-product-card__more::after { content: "→"; transition: translate 0.25s var(--oc-ease-out); }
|
|
@@ -3733,18 +3819,23 @@ details.oc-collapsible[open]::details-content { block-size: auto; }
|
|
|
3733
3819
|
.oc-pill--accent {
|
|
3734
3820
|
border-color: oklch(from var(--oc-accent-from) l c h / 0.45);
|
|
3735
3821
|
background: oklch(from var(--oc-accent-from) l c h / 0.14);
|
|
3736
|
-
color: var(--oc-accent-from);
|
|
3822
|
+
color: light-dark(var(--oc-accent-from), oklch(from var(--oc-accent-from) max(l, 0.72) c h));
|
|
3737
3823
|
}
|
|
3824
|
+
/* Pulse: the ring starts at --oc-status-dot-pulse-alpha of the accent and grows to --oc-status-dot-pulse-size.
|
|
3825
|
+
--strong starts at the full accent with a wider ring. */
|
|
3738
3826
|
.oc-status-dot {
|
|
3827
|
+
--oc-status-dot-pulse-alpha: 0.6;
|
|
3828
|
+
--oc-status-dot-pulse-size: 8px;
|
|
3739
3829
|
inline-size: 0.5rem;
|
|
3740
3830
|
aspect-ratio: 1;
|
|
3741
3831
|
border-radius: 50%;
|
|
3742
3832
|
background: var(--oc-accent-from);
|
|
3743
3833
|
animation: oc-pulse 2s infinite;
|
|
3744
3834
|
}
|
|
3835
|
+
.oc-status-dot--strong { --oc-status-dot-pulse-alpha: 1; --oc-status-dot-pulse-size: 10px; }
|
|
3745
3836
|
@keyframes oc-pulse {
|
|
3746
|
-
0% { box-shadow: 0 0 0 0 oklch(from var(--oc-accent-from) l c h /
|
|
3747
|
-
70%, 100% { box-shadow: 0 0 0
|
|
3837
|
+
0% { box-shadow: 0 0 0 0 oklch(from var(--oc-accent-from) l c h / var(--oc-status-dot-pulse-alpha)); }
|
|
3838
|
+
70%, 100% { box-shadow: 0 0 0 var(--oc-status-dot-pulse-size) transparent; }
|
|
3748
3839
|
}
|
|
3749
3840
|
|
|
3750
3841
|
/* Stat strip: big numbers in a bordered row (pairs with .oc-counter for counting up) */
|
|
@@ -3775,7 +3866,7 @@ details.oc-collapsible[open]::details-content { block-size: auto; }
|
|
|
3775
3866
|
var(--oc-card);
|
|
3776
3867
|
}
|
|
3777
3868
|
.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; }
|
|
3778
|
-
.oc-spotlight__tagline { color: var(--oc-accent-from); font-size: 1.2rem; font-weight: 600; }
|
|
3869
|
+
.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; }
|
|
3779
3870
|
.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; }
|
|
3780
3871
|
.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; }
|
|
3781
3872
|
|
|
@@ -3850,9 +3941,14 @@ details.oc-collapsible[open]::details-content { block-size: auto; }
|
|
|
3850
3941
|
}
|
|
3851
3942
|
@media (max-width: 860px) {
|
|
3852
3943
|
.oc-site-header__toggle { display: block; }
|
|
3944
|
+
/* The author rule display:flex above beats the UA rule that hides a closed popover. */
|
|
3945
|
+
.oc-site-header__nav[popover]:not(:popover-open) { display: none; }
|
|
3853
3946
|
.oc-site-header__nav[popover]:popover-open {
|
|
3854
3947
|
position: fixed;
|
|
3855
3948
|
inset: 4.5rem 0 auto;
|
|
3949
|
+
/* The UA popover style sizes to fit-content; let inset span the full width. */
|
|
3950
|
+
inline-size: auto;
|
|
3951
|
+
block-size: auto;
|
|
3856
3952
|
display: flex;
|
|
3857
3953
|
flex-direction: column;
|
|
3858
3954
|
align-items: stretch;
|
|
@@ -4674,6 +4770,45 @@ a.oc-item:focus-visible { outline: 2px solid var(--oc-ring); outline-offset: 1px
|
|
|
4674
4770
|
.oc-carousel-viewport::scroll-button(inline-end):disabled { opacity: 0.35; cursor: default; }
|
|
4675
4771
|
}
|
|
4676
4772
|
|
|
4773
|
+
/* Loop (.oc-carousel--loop): after the last slide comes the first again. CSS scroll buttons stop at the ends, so this
|
|
4774
|
+
variant has two real buttons (.oc-carousel-prev / .oc-carousel-next) driven by carousel.js; swipe, snap, dots and
|
|
4775
|
+
keyboard stay CSS. Until the script has run (data-oc-loop), the buttons stay hidden and the CSS arrows remain. */
|
|
4776
|
+
.oc-carousel-prev,
|
|
4777
|
+
.oc-carousel-next {
|
|
4778
|
+
position: absolute;
|
|
4779
|
+
inset-block-start: 50%;
|
|
4780
|
+
translate: 0 -50%;
|
|
4781
|
+
z-index: 1;
|
|
4782
|
+
inline-size: 2.25rem;
|
|
4783
|
+
block-size: 2.25rem;
|
|
4784
|
+
display: grid;
|
|
4785
|
+
place-items: center;
|
|
4786
|
+
padding: 0;
|
|
4787
|
+
font-size: 1rem;
|
|
4788
|
+
color: var(--oc-foreground);
|
|
4789
|
+
background: var(--oc-background);
|
|
4790
|
+
border: 1px solid var(--oc-border);
|
|
4791
|
+
border-radius: 50%;
|
|
4792
|
+
box-shadow: 0 2px 8px oklch(from var(--color-black-60) l c h / 0.18);
|
|
4793
|
+
cursor: pointer;
|
|
4794
|
+
}
|
|
4795
|
+
.oc-carousel-prev { inset-inline-start: 0.5rem; }
|
|
4796
|
+
.oc-carousel-next { inset-inline-end: 0.5rem; }
|
|
4797
|
+
.oc-carousel-prev:hover,
|
|
4798
|
+
.oc-carousel-next:hover { background: var(--oc-accent); }
|
|
4799
|
+
.oc-carousel-prev:focus-visible,
|
|
4800
|
+
.oc-carousel-next:focus-visible { outline: 2px solid var(--oc-ring); outline-offset: 2px; }
|
|
4801
|
+
.oc-carousel--loop:not([data-oc-loop]) :is(.oc-carousel-prev, .oc-carousel-next) { display: none; }
|
|
4802
|
+
/* Centered on the viewport, not on the carousel with its dots underneath. */
|
|
4803
|
+
@supports (anchor-name: --a) {
|
|
4804
|
+
.oc-carousel-prev,
|
|
4805
|
+
.oc-carousel-next { position-anchor: --oc-carousel; inset-block-start: anchor(center); }
|
|
4806
|
+
}
|
|
4807
|
+
@supports selector(::scroll-button(inline-start)) {
|
|
4808
|
+
.oc-carousel--loop[data-oc-loop] .oc-carousel-viewport::scroll-button(inline-start),
|
|
4809
|
+
.oc-carousel--loop[data-oc-loop] .oc-carousel-viewport::scroll-button(inline-end) { content: none; }
|
|
4810
|
+
}
|
|
4811
|
+
|
|
4677
4812
|
/* ===================================================================
|
|
4678
4813
|
RESIZABLE — split panes using the native CSS `resize` property (drag the grip in the corner of the resizable panel).
|
|
4679
4814
|
A full-length drag handle between panes with keyboard support needs script: wrapper responsibility.
|
|
@@ -9346,7 +9481,7 @@ a.oc-message__quote:focus-visible { outline: 2px solid var(--oc-ring); outline-o
|
|
|
9346
9481
|
.oc-fact-list--stacked dd { text-align: start; }
|
|
9347
9482
|
|
|
9348
9483
|
/* Values in the accent color. */
|
|
9349
|
-
.oc-fact-list--accent dd { color: var(--oc-accent-from); }
|
|
9484
|
+
.oc-fact-list--accent dd { color: light-dark(var(--oc-accent-from), oklch(from var(--oc-accent-from) max(l, 0.72) c h)); }
|
|
9350
9485
|
|
|
9351
9486
|
/* Very narrow containers: wrap instead of squeezing label and value together. */
|
|
9352
9487
|
.oc-fact-list > div { flex-wrap: wrap; }
|
|
@@ -9374,7 +9509,7 @@ a.oc-message__quote:focus-visible { outline: 2px solid var(--oc-ring); outline-o
|
|
|
9374
9509
|
.oc-link-arrow--external:is(:hover, :focus-visible)::after { translate: 3px -3px; }
|
|
9375
9510
|
|
|
9376
9511
|
/* Colors: accent of the element, or muted text that turns to the foreground color on hover. */
|
|
9377
|
-
.oc-link-arrow--accent { color: var(--oc-accent-from); }
|
|
9512
|
+
.oc-link-arrow--accent { color: light-dark(var(--oc-accent-from), oklch(from var(--oc-accent-from) max(l, 0.72) c h)); }
|
|
9378
9513
|
.oc-link-arrow--muted { color: var(--oc-muted-foreground); font-weight: 500; transition: color 0.2s; }
|
|
9379
9514
|
.oc-link-arrow--muted:hover { color: var(--oc-foreground); }
|
|
9380
9515
|
|
|
@@ -9473,7 +9608,7 @@ a.oc-message__quote:focus-visible { outline: 2px solid var(--oc-ring); outline-o
|
|
|
9473
9608
|
}
|
|
9474
9609
|
.oc-next-teaser__tagline::after {
|
|
9475
9610
|
content: "→";
|
|
9476
|
-
color: var(--oc-accent-from);
|
|
9611
|
+
color: light-dark(var(--oc-accent-from), oklch(from var(--oc-accent-from) max(l, 0.72) c h));
|
|
9477
9612
|
transition: translate 0.25s var(--oc-ease-out);
|
|
9478
9613
|
}
|
|
9479
9614
|
|
|
@@ -9481,7 +9616,7 @@ a.oc-message__quote:focus-visible { outline: 2px solid var(--oc-ring); outline-o
|
|
|
9481
9616
|
.oc-next-teaser--prev .oc-next-teaser__tagline::after { content: none; }
|
|
9482
9617
|
.oc-next-teaser--prev .oc-next-teaser__tagline::before {
|
|
9483
9618
|
content: "←";
|
|
9484
|
-
color: var(--oc-accent-from);
|
|
9619
|
+
color: light-dark(var(--oc-accent-from), oklch(from var(--oc-accent-from) max(l, 0.72) c h));
|
|
9485
9620
|
transition: translate 0.25s var(--oc-ease-out);
|
|
9486
9621
|
}
|
|
9487
9622
|
|
|
@@ -9654,7 +9789,7 @@ a.oc-message__quote:focus-visible { outline: 2px solid var(--oc-ring); outline-o
|
|
|
9654
9789
|
.oc-product-mark--soft {
|
|
9655
9790
|
border: 1px solid color-mix(in oklch, var(--oc-accent-from) 40%, transparent);
|
|
9656
9791
|
background: color-mix(in oklch, var(--oc-accent-from) 16%, var(--oc-card));
|
|
9657
|
-
color: var(--oc-accent-from);
|
|
9792
|
+
color: light-dark(var(--oc-accent-from), oklch(from var(--oc-accent-from) max(l, 0.72) c h));
|
|
9658
9793
|
box-shadow: none;
|
|
9659
9794
|
}
|
|
9660
9795
|
|
|
@@ -9714,6 +9849,8 @@ a.oc-message__quote:focus-visible { outline: 2px solid var(--oc-ring); outline-o
|
|
|
9714
9849
|
background:
|
|
9715
9850
|
radial-gradient(circle at 80% 20%, color-mix(in oklch, var(--color-white-50) 30%, transparent), transparent 45%),
|
|
9716
9851
|
linear-gradient(135deg, var(--oc-accent-from), var(--oc-accent-to));
|
|
9852
|
+
/* The hover zoom grows down and sideways (clipped by the frame), never up over the title bar. */
|
|
9853
|
+
transform-origin: 50% 0;
|
|
9717
9854
|
transition: scale 0.5s var(--oc-ease-out);
|
|
9718
9855
|
}
|
|
9719
9856
|
|
|
@@ -9730,7 +9867,7 @@ a.oc-message__quote:focus-visible { outline: 2px solid var(--oc-ring); outline-o
|
|
|
9730
9867
|
padding: 0.2rem 0.6rem;
|
|
9731
9868
|
border-radius: 999px;
|
|
9732
9869
|
background: color-mix(in oklch, var(--oc-accent-from) 14%, transparent);
|
|
9733
|
-
color: var(--oc-accent-from);
|
|
9870
|
+
color: light-dark(var(--oc-accent-from), oklch(from var(--oc-accent-from) max(l, 0.72) c h));
|
|
9734
9871
|
font-size: 0.75rem;
|
|
9735
9872
|
font-weight: 600;
|
|
9736
9873
|
}
|
|
@@ -9751,7 +9888,7 @@ a.oc-message__quote:focus-visible { outline: 2px solid var(--oc-ring); outline-o
|
|
|
9751
9888
|
gap: 0.4rem;
|
|
9752
9889
|
margin-block-start: auto;
|
|
9753
9890
|
padding-block-start: 0.6rem;
|
|
9754
|
-
color: var(--oc-accent-from);
|
|
9891
|
+
color: light-dark(var(--oc-accent-from), oklch(from var(--oc-accent-from) max(l, 0.72) c h));
|
|
9755
9892
|
font-size: 0.9rem;
|
|
9756
9893
|
font-weight: 600;
|
|
9757
9894
|
}
|
|
@@ -9935,7 +10072,7 @@ a.oc-message__quote:focus-visible { outline: 2px solid var(--oc-ring); outline-o
|
|
|
9935
10072
|
display: inline-grid;
|
|
9936
10073
|
place-items: center;
|
|
9937
10074
|
inline-size: 1.1em;
|
|
9938
|
-
color: var(--oc-accent-from);
|
|
10075
|
+
color: light-dark(var(--oc-accent-from), oklch(from var(--oc-accent-from) max(l, 0.72) c h));
|
|
9939
10076
|
}
|
|
9940
10077
|
.oc-stack-list__icon > :is(img, svg) { inline-size: 100%; block-size: auto; }
|
|
9941
10078
|
|
|
@@ -9977,7 +10114,7 @@ a.oc-message__quote:focus-visible { outline: 2px solid var(--oc-ring); outline-o
|
|
|
9977
10114
|
/* Date or year label (use a <time> element when it is a real date).
|
|
9978
10115
|
Scoped under .oc-timeline so it wins over the showcase rules ".oc-timeline span / strong". */
|
|
9979
10116
|
.oc-timeline .oc-timeline__date {
|
|
9980
|
-
color: var(--oc-accent-from);
|
|
10117
|
+
color: light-dark(var(--oc-accent-from), oklch(from var(--oc-accent-from) max(l, 0.72) c h));
|
|
9981
10118
|
font-family: var(--oc-font-mono);
|
|
9982
10119
|
font-size: 0.85rem;
|
|
9983
10120
|
}
|