@birdycom/omniaui 0.12.2 → 0.12.3

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/README.md CHANGED
@@ -15,7 +15,8 @@ Plus buttons, cards, badges, alerts, forms, avatar, progress, breadcrumb, pagina
15
15
 
16
16
  ```bash
17
17
  npm install @birdycom/omniaui # import "@birdycom/omniaui"
18
- npx omniacss add component modal # copy a snippet into your project
18
+ npx omniaui add modal # copy a component into your project (HTML, React, Vue or Blade)
19
+ npx omniaui copy-js # the scripts (data table, carousel) to public/js/oc-*.js
19
20
  ```
20
21
 
21
22
  MIT
@@ -0,0 +1,56 @@
1
+ // File: packages/ui/bin/lib/copy-js.mjs
2
+ // `omniaui copy-js`: copy the scripts of the installed OmniaUI packages into the project's public folder as
3
+ // oc-<name>.js, the name that makes them start on their own from a plain <script type="module"> tag.
4
+ // Map: scripts per package -> installed ones -> target folder (Laravel/Vite: public/js) -> copy (--force, --dry-run) -> script tags
5
+
6
+ import fs from "node:fs/promises";
7
+ import path from "node:path";
8
+ import { cwd, exists, pkgDir, posix } from "./project.mjs";
9
+
10
+ /** [package, file in the package, name in the project, what it does]. */
11
+ const SCRIPTS = [
12
+ ["@birdycom/omniaui", "dist/data-table.js", "oc-data-table.js", "list table: sort, filter, select, paginate"],
13
+ ["@birdycom/omniaui", "dist/carousel.js", "oc-carousel.js", "looping carousel (.oc-carousel--loop)"],
14
+ ["@birdycom/omniaui-pro", "dist/spotlight.js", "oc-spotlight.js", "spotlight cards (Pro)"],
15
+ ["@birdycom/omniaui-pro", "dist/ecosystem-map.js", "oc-ecosystem-map.js", "ecosystem map lines (Pro)"],
16
+ ["@birdycom/omniaui-pro", "dist/infinite-carousel.js", "oc-infinite-carousel.js", "infinite carousel (Pro)"],
17
+ ];
18
+
19
+ /** public/js when the project has a public folder (Laravel, Vite, Astro …), else js. */
20
+ async function defaultJsDir() {
21
+ return (await exists(path.join(cwd, "public"))) ? "public/js" : "js";
22
+ }
23
+
24
+ export async function cmdCopyJs(names, flags) {
25
+ const dir = path.resolve(cwd, flags.to || flags.dir || (await defaultJsDir()));
26
+ const wanted = names.map((n) => n.replace(/^oc-|\.js$/g, ""));
27
+ const picked = SCRIPTS.filter(([, , target]) => !wanted.length || wanted.includes(target.replace(/^oc-|\.js$/g, "")));
28
+ if (wanted.length && picked.length !== wanted.length) {
29
+ const known = SCRIPTS.map(([, , t]) => t.replace(/^oc-|\.js$/g, ""));
30
+ throw new Error(`unknown script: ${wanted.filter((w) => !known.includes(w)).join(", ")} (known: ${known.join(", ")})`);
31
+ }
32
+
33
+ const copied = [];
34
+ for (const [pkg, file, target, what] of picked) {
35
+ let from;
36
+ try { from = path.join(pkgDir(pkg), file); } catch { if (wanted.length) console.log(`skip ${target}: ${pkg} is not installed`); continue; }
37
+ if (!(await exists(from))) continue;
38
+ const to = path.join(dir, target);
39
+ const rel = posix(path.relative(cwd, to));
40
+ if ((await exists(to)) && !flags.force && !flags["dry-run"]) { console.log(`skip ${rel} (exists, use --force)`); continue; }
41
+ if (!flags["dry-run"]) {
42
+ await fs.mkdir(dir, { recursive: true });
43
+ await fs.copyFile(from, to);
44
+ }
45
+ console.log(`${flags["dry-run"] ? "would copy" : "copied"} ${rel} (${what})`);
46
+ copied.push(target);
47
+ }
48
+ if (!copied.length) {
49
+ console.log("No scripts copied. They come with @birdycom/omniaui and @birdycom/omniaui-pro: install those first.");
50
+ return;
51
+ }
52
+ // The URL the page uses: the folder under public/ is served from the site root.
53
+ const base = "/" + posix(path.relative(path.join(cwd, "public"), dir)).replace(/^(\.\.\/)+/, "");
54
+ console.log(`\nLoad the ones a page needs; each starts on its own:`);
55
+ for (const target of copied) console.log(` <script type="module" src="${base}/${target}"></script>`);
56
+ }
package/bin/omniaui.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  // File: packages/ui/bin/omniaui.mjs
3
3
  // OmniaUI CLI — copy components and blocks into your project (css | vue | react | blade), list the registry.
4
- // Map: args -> commands (add | list | help | version) -> lib/add.mjs (lib/catalog.mjs, lib/project.mjs)
4
+ // Map: args -> commands (add | list | copy-js | help | version) -> lib/add.mjs (lib/catalog.mjs, lib/project.mjs), lib/copy-js.mjs
5
5
  // Author: Serge Cools | Framework: OmniaUI (on OmniaCSS)
6
6
  //
7
7
  // These commands used to be "omniacss add/list" (moved here in October 2026). The CSS build stays in
@@ -9,6 +9,7 @@
9
9
 
10
10
  import { parseArgs } from "node:util";
11
11
  import { cmdAdd, cmdList } from "./lib/add.mjs";
12
+ import { cmdCopyJs } from "./lib/copy-js.mjs";
12
13
  import { readJson } from "./lib/project.mjs";
13
14
 
14
15
  const HELP = `
@@ -18,6 +19,8 @@ OmniaUI CLI
18
19
  copy components/blocks into your project (you own the code)
19
20
  omniaui add <name...> -f vue --import print the import line for the npm package instead
20
21
  omniaui list [blocks|components] [-f vue] show what can be added in a flavour
22
+ omniaui copy-js [name...] [--to public/js] copy the scripts (data table, carousel; Pro: spotlight, ecosystem map,
23
+ infinite carousel) as oc-<name>.js, which start on their own from a script tag
21
24
 
22
25
  Options:
23
26
  -f, --framework <fw> css | vue | react | blade (default: "framework" in omnia.config.json, else detected
@@ -26,7 +29,7 @@ Options:
26
29
  --dir <dir> target folder (alias --to); defaults per flavour:
27
30
  vue/react: resources/js/components/oc (Laravel) or src/components/oc,
28
31
  css: resources/views/oc-snippets (Laravel) or oc-snippets,
29
- blade: resources/views/components/oc
32
+ blade: resources/views/components/oc; copy-js: public/js (or js)
30
33
  --all add every component (or block with --kind block)
31
34
  --force overwrite existing files
32
35
  --dry-run show what would be copied
@@ -60,6 +63,7 @@ async function main() {
60
63
  switch (command) {
61
64
  case "add": return cmdAdd(rest, values);
62
65
  case "list": return cmdList(rest[0], values);
66
+ case "copy-js": return cmdCopyJs(rest, values);
63
67
  default: console.log(HELP);
64
68
  }
65
69
  }
package/dist/carousel.js CHANGED
@@ -38,8 +38,17 @@ export function initCarousels(scope = document) {
38
38
  scope.querySelectorAll(".oc-carousel--loop").forEach((root) => bindCarousel(root));
39
39
  }
40
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")) {
41
+ // Starts on its own when loaded straight from a <script type="module"> tag: as /js/oc-carousel.js (the docs site,
42
+ // `npx omniaui copy-js`), with ?auto in the URL, or with data-oc-auto on the tag. An import from a bundler or a
43
+ // wrapper does not scan the page: call the init function yourself there.
44
+ function autoStart() {
45
+ if (typeof document === "undefined") return false;
46
+ const url = String(import.meta.url);
47
+ if (url.includes("oc-carousel") || /[?&]auto(=|&|$)/.test(url)) return true;
48
+ const file = url.split(/[?#]/)[0];
49
+ return [...document.querySelectorAll("script[data-oc-auto][src]")].some((s) => s.src.split(/[?#]/)[0] === file);
50
+ }
51
+ if (autoStart()) {
43
52
  const boot = () => initCarousels(document);
44
53
  if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", boot);
45
54
  else boot();
@@ -1,4 +1,4 @@
1
- /* File: oc-theme.css — generated from tokens/semantic.json by @birdycom/omniacss 0.12.2. Do not edit. */
1
+ /* File: oc-theme.css — generated from tokens/semantic.json by @birdycom/omniacss 0.12.3. 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 {
@@ -252,6 +252,10 @@
252
252
  color: var(--oc-foreground);
253
253
  }
254
254
 
255
+ /* The hidden attribute hides, also on components with their own display (.oc-field is flex, .oc-card is grid …).
256
+ hidden="until-found" keeps the browser's own behaviour, so find-in-page can still open it. */
257
+ [hidden]:not([hidden="until-found"]) { display: none !important; }
258
+
255
259
  /* ===================================================================
256
260
  BLOCK GRID HELPER
257
261
  =================================================================== */
@@ -433,6 +437,20 @@ flex-direction: column;
433
437
  opacity: 1;
434
438
  }
435
439
 
440
+ /* Inline — a link button inside running text: no button padding or height, the size of the text around it.
441
+ Two classes, so it also wins over the size modifiers (--sm, --lg) further down. */
442
+ .oc-btn.oc-btn--inline {
443
+ display: inline;
444
+ padding: 0;
445
+ border: 0;
446
+ border-radius: 2px;
447
+ font-size: inherit;
448
+ line-height: inherit;
449
+ vertical-align: baseline;
450
+ white-space: normal;
451
+ }
452
+ .oc-btn.oc-btn--inline:active { transform: none; }
453
+
436
454
  /* Destructive */
437
455
  .oc-btn--destructive {
438
456
  background: var(--oc-destructive);
@@ -1114,6 +1132,18 @@ textarea.oc-input {
1114
1132
  background: color-mix(in srgb, var(--oc-destructive) 5%, var(--oc-background));
1115
1133
  }
1116
1134
 
1135
+ /* Inline: a short message right under a field or a form (an error, a hint), tinted, without the box spacing. */
1136
+ .oc-alert--inline {
1137
+ padding: 8px 12px;
1138
+ border-radius: var(--oc-radius);
1139
+ font-size: 0.8125rem;
1140
+ line-height: 1.45;
1141
+ }
1142
+ .oc-alert--inline.oc-alert--danger { color: light-dark(var(--oc-destructive), oklch(from var(--oc-destructive) max(l, 0.75) c h)); }
1143
+ .oc-alert--inline .oc-alert-title { margin: 0; font-size: inherit; }
1144
+ .oc-alert--inline .oc-alert-title + .oc-alert-description { margin-block-start: 2px; }
1145
+ .oc-alert--inline .oc-alert-description { color: inherit; }
1146
+
1117
1147
  /* ===================================================================
1118
1148
  HERO (page intro block — used in docs index)
1119
1149
  =================================================================== */
@@ -1967,15 +1997,44 @@ a.oc-breadcrumb-item:hover {
1967
1997
  }
1968
1998
 
1969
1999
  .oc-progress-bar {
2000
+ --oc-progress-color: var(--oc-primary);
2001
+ display: block;
2002
+ max-width: 100%;
1970
2003
  height: 100%;
1971
- background: var(--oc-primary);
2004
+ background: var(--oc-progress-color);
1972
2005
  border-radius: 999px;
1973
2006
  transition: width 300ms ease;
1974
2007
  }
1975
2008
 
1976
- .oc-progress-bar--success { background: var(--oc-success); }
1977
- .oc-progress-bar--danger { background: var(--oc-destructive); }
1978
- .oc-progress-bar--warning { background: var(--oc-warning); }
2009
+ .oc-progress-bar--success { --oc-progress-color: var(--oc-success); }
2010
+ .oc-progress-bar--danger { --oc-progress-color: var(--oc-destructive); }
2011
+ .oc-progress-bar--warning { --oc-progress-color: var(--oc-warning); }
2012
+
2013
+ /* Sizes */
2014
+ .oc-progress--sm { height: 6px; }
2015
+ .oc-progress--xs { height: 4px; }
2016
+
2017
+ /* Glow: a light top edge and a soft glow in the bar color (meters, usage, quota). */
2018
+ .oc-progress--glow { overflow: visible; box-shadow: inset 0 1px 1px light-dark(oklch(0% 0 0deg / 0.06), oklch(0% 0 0deg / 0.35)); }
2019
+ .oc-progress--glow .oc-progress-bar {
2020
+ background: linear-gradient(180deg, color-mix(in oklch, var(--oc-progress-color) 78%, white), var(--oc-progress-color));
2021
+ box-shadow: 0 0 10px -2px color-mix(in oklch, var(--oc-progress-color) 60%, transparent);
2022
+ }
2023
+
2024
+ /* Marker: a short upright line at a target or schedule point, placed with style="left: 20%". Give it an aria-label
2025
+ on the progressbar or a text next to it; the line itself is decoration. */
2026
+ .oc-progress:has(> .oc-progress-marker) { position: relative; overflow: visible; }
2027
+ .oc-progress-marker {
2028
+ position: absolute;
2029
+ inset-block: -4px;
2030
+ inline-size: 2px;
2031
+ margin-inline-start: -1px;
2032
+ border-radius: 2px;
2033
+ background: var(--oc-foreground);
2034
+ box-shadow: 0 0 0 1.5px var(--oc-background);
2035
+ pointer-events: none;
2036
+ }
2037
+ .oc-progress-marker--muted { background: var(--oc-muted-foreground); }
1979
2038
 
1980
2039
  /* ===================================================================
1981
2040
  SKELETON LOADER
@@ -6202,6 +6261,58 @@ button.oc-stat-card:focus-visible { outline: 2px solid var(--oc-ring); outline-o
6202
6261
  button.oc-stat-card:hover { translate: none; }
6203
6262
  }
6204
6263
 
6264
+ /* parts/app-status-dot.css */
6265
+ /* File: packages/ui/src/parts/app-status-dot.css
6266
+ Map: dot (tone color + soft ring) -> tones (working pulses, waiting, done, error, idle) -> sizes -> status row (dot + text
6267
+ that cuts off with …) -> reduced motion
6268
+ Status dot: a small live state light for a job, agent, sync, server or person ("Working", "Waiting for input", "Done").
6269
+ Always put a text next to it or an aria-label on it: the color alone does not tell the state. */
6270
+
6271
+ .oc-status-dot {
6272
+ --oc-status-dot-color: var(--oc-muted-foreground);
6273
+ display: inline-block;
6274
+ flex: none;
6275
+ inline-size: 0.5rem;
6276
+ block-size: 0.5rem;
6277
+ border-radius: 50%;
6278
+ background: var(--oc-status-dot-color);
6279
+ box-shadow: 0 0 0 2px oklch(from var(--oc-status-dot-color) l c h / 0.2);
6280
+ vertical-align: middle;
6281
+ }
6282
+
6283
+ /* Tones. --working pulses a ring outwards. */
6284
+ .oc-status-dot--working {
6285
+ --oc-status-dot-color: var(--oc-primary);
6286
+ animation: oc-status-dot-pulse 1.8s ease-in-out infinite;
6287
+ }
6288
+ .oc-status-dot--waiting { --oc-status-dot-color: var(--oc-warning); }
6289
+ .oc-status-dot--done { --oc-status-dot-color: var(--oc-success); }
6290
+ .oc-status-dot--error { --oc-status-dot-color: var(--oc-destructive); }
6291
+ .oc-status-dot--idle { --oc-status-dot-color: var(--oc-muted-foreground); box-shadow: none; opacity: 0.7; }
6292
+ @keyframes oc-status-dot-pulse {
6293
+ 50% { box-shadow: 0 0 0 5px oklch(from var(--oc-status-dot-color) l c h / 0.08); }
6294
+ }
6295
+
6296
+ /* Sizes */
6297
+ .oc-status-dot--sm { inline-size: 0.375rem; block-size: 0.375rem; }
6298
+ .oc-status-dot--lg { inline-size: 0.625rem; block-size: 0.625rem; }
6299
+
6300
+ /* Status row: dot + one line of text that cuts off with … in a narrow card. */
6301
+ .oc-status {
6302
+ display: flex;
6303
+ align-items: center;
6304
+ gap: 0.5rem;
6305
+ min-inline-size: 0;
6306
+ margin: 0;
6307
+ font-size: 0.8125rem;
6308
+ }
6309
+ .oc-status__text { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
6310
+ .oc-status__meta { flex: none; margin-inline-start: auto; color: var(--oc-muted-foreground); font-variant-numeric: tabular-nums; }
6311
+
6312
+ @media (prefers-reduced-motion: reduce) {
6313
+ .oc-status-dot--working { animation: none; }
6314
+ }
6315
+
6205
6316
  /* parts/app-table-actions.css */
6206
6317
  /* File: packages/ui/src/parts/app-table-actions.css | Map: icon actions -> tone modifiers -> overflow trigger -> menu tweaks -> reduced motion */
6207
6318
  /* Table actions: row actions as small icon buttons or as one overflow ("more") menu.
@@ -6331,6 +6442,104 @@ button.oc-stat-card:focus-visible { outline: 2px solid var(--oc-ring); outline-o
6331
6442
  .oc-table-compact--plain > .oc-table tbody tr:nth-child(even),
6332
6443
  .oc-table-compact--plain > .oc-table thead th { background: none; }
6333
6444
 
6445
+ /* parts/app-titlebar.css */
6446
+ /* File: packages/ui/src/parts/app-titlebar.css
6447
+ Map: window (column, shares the scroll timeline) -> title bar (drag area) -> title + subtitle -> actions (no drag) ->
6448
+ icon buttons (close turns red) -> body (scrolls) -> hairline when the body scrolls (scroll-driven, no JavaScript) -> sizes
6449
+ Title bar: the top bar of a frameless desktop window (Electron, Tauri) or a widget: you drag the window by it, the
6450
+ buttons stay clickable. Inside .oc-titlebar-window the bar gets a hairline once .oc-titlebar-body scrolls. */
6451
+
6452
+ .oc-titlebar-window {
6453
+ display: flex;
6454
+ flex-direction: column;
6455
+ min-block-size: 0;
6456
+ timeline-scope: --oc-titlebar-scroll;
6457
+ }
6458
+
6459
+ .oc-titlebar {
6460
+ display: flex;
6461
+ flex: none;
6462
+ align-items: center;
6463
+ gap: 2px;
6464
+ min-block-size: 46px;
6465
+ padding: 0 8px 0 14px;
6466
+ border-block-end: 1px solid transparent;
6467
+ -webkit-user-select: none;
6468
+ user-select: none;
6469
+ /* The OS moves the window when you drag here (Electron and Tauri read this). */
6470
+ -webkit-app-region: drag;
6471
+ app-region: drag;
6472
+ }
6473
+ .oc-titlebar :is(button, a, input, select, textarea, label, [role="button"], [tabindex], .oc-titlebar__actions) {
6474
+ -webkit-app-region: no-drag;
6475
+ app-region: no-drag;
6476
+ }
6477
+ .oc-titlebar--bordered { border-block-end-color: var(--oc-glass-hairline, var(--oc-border)); }
6478
+
6479
+ /* Title area: grows, cuts the subtitle off with …. */
6480
+ .oc-titlebar__main { display: flex; flex: 1; align-items: center; gap: 8px; min-inline-size: 0; }
6481
+ .oc-titlebar__icon { flex: none; inline-size: 18px; block-size: 18px; }
6482
+ .oc-titlebar__title { flex: none; font-size: 0.8667rem; font-weight: 650; letter-spacing: -0.01em; }
6483
+ .oc-titlebar__subtitle {
6484
+ min-inline-size: 0;
6485
+ overflow: hidden;
6486
+ color: var(--oc-muted-foreground);
6487
+ font-size: 0.8rem;
6488
+ text-overflow: ellipsis;
6489
+ white-space: nowrap;
6490
+ }
6491
+ .oc-titlebar__subtitle::before { content: "·"; margin-inline-end: 8px; }
6492
+ .oc-titlebar__actions { display: flex; flex: none; align-items: center; gap: 2px; }
6493
+
6494
+ /* Compact icon buttons: muted, a light surface on hover, the close button red. */
6495
+ .oc-titlebar__btn {
6496
+ display: inline-grid;
6497
+ place-items: center;
6498
+ inline-size: 30px;
6499
+ block-size: 30px;
6500
+ padding: 0;
6501
+ border: 0;
6502
+ border-radius: 8px;
6503
+ background: transparent;
6504
+ color: var(--oc-muted-foreground);
6505
+ font: inherit;
6506
+ cursor: pointer;
6507
+ transition: background-color var(--oc-transition-fast), color var(--oc-transition-fast);
6508
+ }
6509
+ .oc-titlebar__btn :where(svg, iconify-icon) { inline-size: 16px; block-size: 16px; }
6510
+ .oc-titlebar__btn:hover { background: var(--oc-glass-panel-strong, var(--oc-accent)); color: var(--oc-foreground); }
6511
+ .oc-titlebar__btn:focus-visible { outline: 2px solid var(--oc-ring); outline-offset: -2px; }
6512
+ .oc-titlebar__btn--close:hover { background: var(--oc-destructive); color: var(--oc-destructive-foreground); }
6513
+
6514
+ /* Body: the part that scrolls under the bar. */
6515
+ .oc-titlebar-body {
6516
+ flex: 1;
6517
+ min-block-size: 0;
6518
+ overflow: auto;
6519
+ scroll-timeline: --oc-titlebar-scroll block;
6520
+ scrollbar-width: thin;
6521
+ }
6522
+
6523
+ /* Hairline + light surface on the bar once the body scrolls (browsers without scroll timelines keep the plain bar). */
6524
+ @supports (animation-timeline: scroll()) {
6525
+ .oc-titlebar-window > .oc-titlebar:not(.oc-titlebar--bordered) {
6526
+ animation: oc-titlebar-scrolled linear both;
6527
+ animation-timeline: --oc-titlebar-scroll;
6528
+ animation-range: 0 48px;
6529
+ }
6530
+ }
6531
+ @keyframes oc-titlebar-scrolled {
6532
+ to { border-block-end-color: var(--oc-glass-hairline, var(--oc-border)); background: var(--oc-glass-panel, var(--oc-muted)); }
6533
+ }
6534
+
6535
+ /* Sizes */
6536
+ .oc-titlebar--sm { min-block-size: 36px; padding-inline-start: 10px; }
6537
+ .oc-titlebar--sm .oc-titlebar__btn { inline-size: 26px; block-size: 26px; border-radius: 6px; }
6538
+
6539
+ @media (prefers-reduced-motion: reduce) {
6540
+ .oc-titlebar-window > .oc-titlebar { animation: none; }
6541
+ }
6542
+
6334
6543
  /* parts/app-top-progress.css */
6335
6544
  /* File: packages/ui/src/parts/app-top-progress.css | Map: bar (fixed, native progress) -> determinate value -> indeterminate animation -> color + static modifiers -> done state -> reduced motion */
6336
6545
  /* Top progress: thin loading bar at the very top of the page, on a native <progress> element.
@@ -8641,6 +8850,79 @@ a.oc-attachment-image:hover > :is(img, .oc-media-art) { filter: brightness(1.05)
8641
8850
  .oc-flip-card--hover:is(:hover, :focus-within) .oc-flip-card__face--back { opacity: 1; visibility: visible; }
8642
8851
  }
8643
8852
 
8853
+ /* parts/media-glass.css */
8854
+ /* File: packages/ui/src/parts/media-glass.css
8855
+ Map: tokens (light-dark) -> sheet (.oc-glass: see-through, blurred, light top edge, soft shadow) -> panel (.oc-glass-panel:
8856
+ a lighter layer inside) -> cards inside a sheet -> --flat (desktop window, the OS draws the blur) -> --strong -> reduced
8857
+ transparency
8858
+ Glass: see-through surfaces in the "liquid glass" style, over a photo, a gradient or the desktop (an Electron or Tauri
8859
+ window with Windows acrylic or macOS vibrancy behind a transparent page). Re-tint with the --oc-glass-* tokens. */
8860
+
8861
+ :root {
8862
+ --oc-glass-sheet: light-dark(oklch(from var(--oc-background) l c h / 0.72), oklch(from var(--oc-background) l c h / 0.62));
8863
+ --oc-glass-panel: light-dark(oklch(100% 0 0deg / 0.5), oklch(100% 0 0deg / 0.055));
8864
+ --oc-glass-panel-strong: light-dark(oklch(100% 0 0deg / 0.72), oklch(100% 0 0deg / 0.09));
8865
+ --oc-glass-edge: light-dark(oklch(100% 0 0deg / 0.85), oklch(100% 0 0deg / 0.11));
8866
+ --oc-glass-edge-hi: light-dark(oklch(100% 0 0deg / 1), oklch(100% 0 0deg / 0.16));
8867
+ --oc-glass-hairline: light-dark(oklch(from var(--oc-foreground) l c h / 0.1), oklch(100% 0 0deg / 0.08));
8868
+ --oc-glass-shadow: 0 24px 60px -20px light-dark(oklch(25% 0.06 255deg / 0.35), oklch(0% 0 0deg / 0.7));
8869
+ --oc-glass-panel-shadow: 0 8px 24px -16px light-dark(oklch(25% 0.06 255deg / 0.25), oklch(0% 0 0deg / 0.6));
8870
+ --oc-glass-blur: 28px;
8871
+ --oc-glass-radius: 18px;
8872
+ --oc-glass-panel-radius: 14px;
8873
+ }
8874
+
8875
+ /* Sheet: the outer glass surface (a widget, a floating panel, a dialog over a photo). */
8876
+ .oc-glass {
8877
+ position: relative;
8878
+ color: var(--oc-foreground);
8879
+ background: var(--oc-glass-sheet);
8880
+ border: 1px solid var(--oc-glass-edge);
8881
+ border-radius: var(--oc-glass-radius);
8882
+ box-shadow: var(--oc-glass-shadow), inset 0 1px 0 var(--oc-glass-edge-hi);
8883
+ backdrop-filter: blur(var(--oc-glass-blur)) saturate(1.5);
8884
+ /* OmniaUI cards inside become glass panels too. */
8885
+ --oc-card-background: var(--oc-glass-panel);
8886
+ --oc-card-border: var(--oc-glass-edge);
8887
+ }
8888
+
8889
+ /* Panel: a lighter layer inside a sheet (a group of settings, a meter, a list). */
8890
+ .oc-glass-panel {
8891
+ position: relative;
8892
+ background: var(--oc-glass-panel);
8893
+ border: 1px solid var(--oc-glass-edge);
8894
+ border-radius: var(--oc-glass-panel-radius);
8895
+ box-shadow: var(--oc-glass-panel-shadow), inset 0 1px 0 var(--oc-glass-edge-hi);
8896
+ }
8897
+ .oc-glass .oc-card {
8898
+ border-radius: var(--oc-glass-panel-radius);
8899
+ box-shadow: var(--oc-glass-panel-shadow), inset 0 1px 0 var(--oc-glass-edge-hi);
8900
+ }
8901
+
8902
+ /* Strong: more opaque, for text-heavy panels or a hovered row. */
8903
+ .oc-glass--strong,
8904
+ .oc-glass-panel--strong { background: var(--oc-glass-panel-strong); }
8905
+
8906
+ /* Flat: a frameless desktop window where the OS draws the blur (acrylic, vibrancy): no own blur, no drop shadow. */
8907
+ .oc-glass--flat {
8908
+ border-radius: 8px;
8909
+ box-shadow: inset 0 1px 0 var(--oc-glass-edge-hi);
8910
+ backdrop-filter: none;
8911
+ }
8912
+
8913
+ /* Hairline divider in glass (sections, header). */
8914
+ .oc-glass-divider { block-size: 1px; margin: 0; border: 0; background: var(--oc-glass-hairline); }
8915
+
8916
+ /* People who ask for less transparency get solid surfaces. */
8917
+ @media (prefers-reduced-transparency: reduce) {
8918
+ :root {
8919
+ --oc-glass-sheet: var(--oc-background);
8920
+ --oc-glass-panel: var(--oc-card);
8921
+ --oc-glass-panel-strong: var(--oc-muted);
8922
+ }
8923
+ .oc-glass { backdrop-filter: none; }
8924
+ }
8925
+
8644
8926
  /* parts/media-image-zoom.css */
8645
8927
  /* File: packages/ui/src/parts/media-image-zoom.css | Map: root + zoom variable -> toolbar + level chips
8646
8928
  -> viewport (scroll = pan) -> canvas (grows with the zoom) -> zoom levels (classes + radios via :has()) -> motion
@@ -10227,6 +10509,18 @@ a.oc-message__quote:focus-visible { outline: 2px solid var(--oc-ring); outline-o
10227
10509
  .oc-prose--lg { font-size: 1.125rem; line-height: 1.75; }
10228
10510
  .oc-prose--full { max-inline-size: none; }
10229
10511
 
10512
+ /* Compact: help text and about pages in a widget, side panel or popover. Smaller text, tighter rhythm and headings,
10513
+ full width of the panel. */
10514
+ .oc-prose--compact { max-inline-size: none; font-size: 0.8125rem; line-height: 1.5; }
10515
+ .oc-prose--compact :where(* + *) { margin-block-start: 0.7em; }
10516
+ .oc-prose--compact :where(h1) { font-size: 1.45em; }
10517
+ .oc-prose--compact :where(h2) { margin-block-start: 1.4em; font-size: 1.15em; }
10518
+ .oc-prose--compact :where(h3) { margin-block-start: 1.2em; font-size: 1.05em; }
10519
+ .oc-prose--compact :where(h4) { margin-block-start: 1em; }
10520
+ .oc-prose--compact :where(h1, h2, h3, h4) + :where(*) { margin-block-start: 0.35em; }
10521
+ .oc-prose--compact :where(.oc-prose-lead) { font-size: 1.08em; }
10522
+ .oc-prose--compact > :where(:first-child) { margin-block-start: 0; }
10523
+
10230
10524
  @media (prefers-reduced-motion: reduce) {
10231
10525
  .oc-prose :where(a) { transition: none; }
10232
10526
  }