@birdycom/omniaui 0.12.2 → 0.12.4
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 +2 -1
- package/bin/lib/copy-js.mjs +56 -0
- package/bin/omniaui.mjs +6 -2
- package/dist/carousel.js +11 -2
- package/dist/components.css +313 -13
- package/dist/components.min.css +1 -1
- package/dist/data-table.js +11 -2
- package/dist/oc-theme.css +1 -1
- package/dist/parts/app-status-dot.css +50 -0
- package/dist/parts/app-titlebar.css +96 -0
- package/dist/parts/media-glass.css +71 -0
- package/dist/parts/site-price-card.css +14 -8
- package/dist/parts/site-prose.css +12 -0
- package/manifest.json +24 -0
- package/package.json +2 -2
- package/snippets/glass.html +24 -0
- package/snippets/status-dot.html +15 -0
- package/snippets/titlebar.html +29 -0
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
|
|
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
|
-
//
|
|
42
|
-
|
|
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();
|
package/dist/components.css
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* File: oc-theme.css — generated from tokens/semantic.json by @birdycom/omniacss 0.12.
|
|
1
|
+
/* File: oc-theme.css — generated from tokens/semantic.json by @birdycom/omniacss 0.12.4. 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-
|
|
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 {
|
|
1977
|
-
.oc-progress-bar--danger {
|
|
1978
|
-
.oc-progress-bar--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
|
|
@@ -9831,9 +10113,9 @@ a.oc-message__quote:focus-visible { outline: 2px solid var(--oc-ring); outline-o
|
|
|
9831
10113
|
|
|
9832
10114
|
/* parts/site-price-card.css */
|
|
9833
10115
|
/* File: packages/ui/src/parts/site-price-card.css
|
|
9834
|
-
Map: card -> head + discount badge -> period toggle -> monthly / yearly price (:has) -> features -> featured
|
|
9835
|
-
Price card: one plan with a monthly / yearly switch (radio inputs + :has(),
|
|
9836
|
-
The toggle is the segmented component; give every card its own radio name. */
|
|
10116
|
+
Map: card -> head + discount badge -> period toggle -> monthly / yearly / lifetime price (:has) -> features -> featured
|
|
10117
|
+
Price card: one plan with a monthly / yearly switch, optionally a third "lifetime" option (radio inputs + :has(),
|
|
10118
|
+
no JavaScript). The toggle is the segmented component; give every card its own radio name. */
|
|
9837
10119
|
|
|
9838
10120
|
.oc-price-card {
|
|
9839
10121
|
display: flex;
|
|
@@ -9863,9 +10145,12 @@ a.oc-message__quote:focus-visible { outline: 2px solid var(--oc-ring); outline-o
|
|
|
9863
10145
|
}
|
|
9864
10146
|
.oc-price-card:has(.oc-price-card__toggle input[value="monthly"]:checked) .oc-price-card__discount { opacity: 0.55; }
|
|
9865
10147
|
|
|
9866
|
-
.oc-price-card__toggle { align-self: flex-start; }
|
|
10148
|
+
.oc-price-card__toggle { align-self: flex-start; max-inline-size: 100%; }
|
|
10149
|
+
/* Three options (monthly / yearly / lifetime) take the full card width, in equal parts, so they fit a narrow card. */
|
|
10150
|
+
.oc-price-card__toggle:has(> :nth-child(3)) { align-self: stretch; }
|
|
10151
|
+
.oc-price-card__toggle:has(> :nth-child(3)) .oc-segmented-item { flex: 1; min-inline-size: 0; padding-inline: 0.25rem; letter-spacing: 0; text-align: center; }
|
|
9867
10152
|
|
|
9868
|
-
/* Price rows: monthly shows by default, yearly when its radio is checked. */
|
|
10153
|
+
/* Price rows: monthly shows by default, yearly or lifetime when its radio is checked. */
|
|
9869
10154
|
.oc-price-card__price {
|
|
9870
10155
|
display: flex;
|
|
9871
10156
|
flex-wrap: wrap;
|
|
@@ -9873,9 +10158,12 @@ a.oc-message__quote:focus-visible { outline: 2px solid var(--oc-ring); outline-o
|
|
|
9873
10158
|
gap: 0.35rem;
|
|
9874
10159
|
margin: 0;
|
|
9875
10160
|
}
|
|
9876
|
-
.oc-price-card__price--yearly
|
|
9877
|
-
.oc-price-
|
|
9878
|
-
.oc-price-card:has(.oc-price-card__toggle input[value="yearly"]:checked) .oc-price-card__price--
|
|
10161
|
+
.oc-price-card__price--yearly,
|
|
10162
|
+
.oc-price-card__price--lifetime { display: none; }
|
|
10163
|
+
.oc-price-card:has(.oc-price-card__toggle input[value="yearly"]:checked) .oc-price-card__price--yearly,
|
|
10164
|
+
.oc-price-card:has(.oc-price-card__toggle input[value="lifetime"]:checked) .oc-price-card__price--lifetime { display: flex; }
|
|
10165
|
+
.oc-price-card:has(.oc-price-card__toggle input:is([value="yearly"], [value="lifetime"]):checked) .oc-price-card__price--monthly,
|
|
10166
|
+
.oc-price-card:has(.oc-price-card__toggle input[value="lifetime"]:checked) .oc-price-card__price--yearly { display: none; }
|
|
9879
10167
|
|
|
9880
10168
|
.oc-price-card__amount { font-size: 2.5rem; font-weight: 700; line-height: 1; letter-spacing: -0.03em; }
|
|
9881
10169
|
.oc-price-card__period { color: var(--oc-muted-foreground); font-size: 0.9rem; }
|
|
@@ -10227,6 +10515,18 @@ a.oc-message__quote:focus-visible { outline: 2px solid var(--oc-ring); outline-o
|
|
|
10227
10515
|
.oc-prose--lg { font-size: 1.125rem; line-height: 1.75; }
|
|
10228
10516
|
.oc-prose--full { max-inline-size: none; }
|
|
10229
10517
|
|
|
10518
|
+
/* Compact: help text and about pages in a widget, side panel or popover. Smaller text, tighter rhythm and headings,
|
|
10519
|
+
full width of the panel. */
|
|
10520
|
+
.oc-prose--compact { max-inline-size: none; font-size: 0.8125rem; line-height: 1.5; }
|
|
10521
|
+
.oc-prose--compact :where(* + *) { margin-block-start: 0.7em; }
|
|
10522
|
+
.oc-prose--compact :where(h1) { font-size: 1.45em; }
|
|
10523
|
+
.oc-prose--compact :where(h2) { margin-block-start: 1.4em; font-size: 1.15em; }
|
|
10524
|
+
.oc-prose--compact :where(h3) { margin-block-start: 1.2em; font-size: 1.05em; }
|
|
10525
|
+
.oc-prose--compact :where(h4) { margin-block-start: 1em; }
|
|
10526
|
+
.oc-prose--compact :where(h1, h2, h3, h4) + :where(*) { margin-block-start: 0.35em; }
|
|
10527
|
+
.oc-prose--compact :where(.oc-prose-lead) { font-size: 1.08em; }
|
|
10528
|
+
.oc-prose--compact > :where(:first-child) { margin-block-start: 0; }
|
|
10529
|
+
|
|
10230
10530
|
@media (prefers-reduced-motion: reduce) {
|
|
10231
10531
|
.oc-prose :where(a) { transition: none; }
|
|
10232
10532
|
}
|