@birdycom/omniaui 0.0.0-stage → 0.11.1
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/LICENSE +21 -0
- package/README.md +21 -3
- package/bin/lib/add.mjs +126 -0
- package/bin/lib/catalog.mjs +169 -0
- package/bin/lib/project.mjs +90 -0
- package/bin/omniaui.mjs +70 -0
- package/dist/code-block.css +163 -0
- package/dist/components-extra.css +916 -0
- package/dist/components.css +10171 -0
- package/dist/components.min.css +1 -0
- package/dist/data-table.d.ts +9 -0
- package/dist/data-table.js +505 -0
- package/dist/docs-ui.css +267 -0
- package/dist/oc-theme.css +69 -0
- package/dist/parts/.gitkeep +0 -0
- package/dist/parts/app-accordion-accent.css +60 -0
- package/dist/parts/app-alpha-tabs.css +68 -0
- package/dist/parts/app-busy-overlay.css +25 -0
- package/dist/parts/app-column-menu.css +85 -0
- package/dist/parts/app-confirm-dialog.css +70 -0
- package/dist/parts/app-cookie-consent.css +63 -0
- package/dist/parts/app-data-table.css +174 -0
- package/dist/parts/app-greeting.css +47 -0
- package/dist/parts/app-list-toolbar.css +81 -0
- package/dist/parts/app-page-header.css +54 -0
- package/dist/parts/app-severity-card.css +91 -0
- package/dist/parts/app-sidebar-dark.css +15 -0
- package/dist/parts/app-sortable-row.css +52 -0
- package/dist/parts/app-stat-card.css +68 -0
- package/dist/parts/app-table-actions.css +71 -0
- package/dist/parts/app-table-compact.css +54 -0
- package/dist/parts/app-top-progress.css +68 -0
- package/dist/parts/app-tree.css +104 -0
- package/dist/parts/app-upgrade-banner.css +90 -0
- package/dist/parts/app-version-banner.css +80 -0
- package/dist/parts/data-chart-line.css +167 -0
- package/dist/parts/forms-affix-input.css +75 -0
- package/dist/parts/forms-barcode-input.css +75 -0
- package/dist/parts/forms-chip-filter.css +91 -0
- package/dist/parts/forms-color-select.css +97 -0
- package/dist/parts/forms-decimal-input.css +51 -0
- package/dist/parts/forms-filter-counts.css +94 -0
- package/dist/parts/forms-language-select.css +89 -0
- package/dist/parts/forms-number-input.css +98 -0
- package/dist/parts/forms-password-requirements.css +55 -0
- package/dist/parts/forms-phone-input.css +85 -0
- package/dist/parts/forms-time-input.css +74 -0
- package/dist/parts/forms-toggle-field.css +69 -0
- package/dist/parts/media-art.css +21 -0
- package/dist/parts/media-attachment.css +160 -0
- package/dist/parts/media-browser-frame.css +73 -0
- package/dist/parts/media-card.css +83 -0
- package/dist/parts/media-chat-composer.css +90 -0
- package/dist/parts/media-doc-viewer.css +151 -0
- package/dist/parts/media-emoji-picker.css +88 -0
- package/dist/parts/media-entity-card.css +52 -0
- package/dist/parts/media-flip-card.css +78 -0
- package/dist/parts/media-image-zoom.css +68 -0
- package/dist/parts/media-lightbox.css +131 -0
- package/dist/parts/media-message-thread.css +136 -0
- package/dist/parts/media-message.css +160 -0
- package/dist/parts/media-row.css +78 -0
- package/dist/parts/media-stack-card.css +82 -0
- package/dist/parts/media-terminal-log.css +76 -0
- package/dist/parts/media-thumb.css +71 -0
- package/dist/parts/site-fact-list.css +25 -0
- package/dist/parts/site-link-arrow.css +34 -0
- package/dist/parts/site-marquee.css +34 -0
- package/dist/parts/site-next-teaser.css +75 -0
- package/dist/parts/site-price-card.css +91 -0
- package/dist/parts/site-product-mark.css +66 -0
- package/dist/parts/site-project-card.css +111 -0
- package/dist/parts/site-prose.css +124 -0
- package/dist/parts/site-stack-list.css +48 -0
- package/dist/parts/site-timeline.css +54 -0
- package/dist/showcase.css +404 -0
- package/manifest.json +1108 -0
- package/package.json +69 -6
- package/snippets/accordion-accent.html +24 -0
- package/snippets/accordion.html +11 -0
- package/snippets/affix-input.html +30 -0
- package/snippets/alert-dialog.html +22 -0
- package/snippets/alert.html +5 -0
- package/snippets/alpha-tabs.html +37 -0
- package/snippets/aspect-ratio.html +15 -0
- package/snippets/attachment.html +43 -0
- package/snippets/avatar.html +6 -0
- package/snippets/back-link.html +5 -0
- package/snippets/badge.html +5 -0
- package/snippets/barcode-input.html +18 -0
- package/snippets/breadcrumb.html +6 -0
- package/snippets/browser-frame.html +18 -0
- package/snippets/busy-overlay.html +31 -0
- package/snippets/button-group.html +21 -0
- package/snippets/button.html +8 -0
- package/snippets/calendar.html +43 -0
- package/snippets/card.html +17 -0
- package/snippets/carousel.html +21 -0
- package/snippets/chart-line.html +46 -0
- package/snippets/chart.html +27 -0
- package/snippets/chat-composer.html +21 -0
- package/snippets/checkbox.html +5 -0
- package/snippets/checklist.html +23 -0
- package/snippets/chip-filter.html +14 -0
- package/snippets/code-window.html +20 -0
- package/snippets/collapsible.html +19 -0
- package/snippets/color-field.html +13 -0
- package/snippets/color-select.html +18 -0
- package/snippets/column-menu.html +38 -0
- package/snippets/combobox.html +19 -0
- package/snippets/command.html +24 -0
- package/snippets/confirm-dialog.html +32 -0
- package/snippets/context-menu.html +25 -0
- package/snippets/cookie-consent.html +70 -0
- package/snippets/copy-field.html +19 -0
- package/snippets/counter.html +2 -0
- package/snippets/data-table.html +151 -0
- package/snippets/date-picker.html +32 -0
- package/snippets/decimal-input.html +20 -0
- package/snippets/definition-list.html +20 -0
- package/snippets/demo-panel.html +18 -0
- package/snippets/doc-viewer.html +58 -0
- package/snippets/drawer.html +30 -0
- package/snippets/dropdown-menu.html +27 -0
- package/snippets/dropdown.html +9 -0
- package/snippets/dropzone.html +13 -0
- package/snippets/emoji-picker.html +51 -0
- package/snippets/empty-state.html +17 -0
- package/snippets/entity-card.html +26 -0
- package/snippets/expandable-table.html +26 -0
- package/snippets/fact-list.html +14 -0
- package/snippets/filter-counts.html +30 -0
- package/snippets/flip-card.html +22 -0
- package/snippets/form-field.html +6 -0
- package/snippets/greeting.html +22 -0
- package/snippets/heading.html +14 -0
- package/snippets/hover-card.html +18 -0
- package/snippets/image-zoom.html +30 -0
- package/snippets/input-group.html +10 -0
- package/snippets/input.html +5 -0
- package/snippets/item.html +26 -0
- package/snippets/kbd.html +13 -0
- package/snippets/label.html +9 -0
- package/snippets/language-select.html +26 -0
- package/snippets/launcher-card.html +14 -0
- package/snippets/lightbox.html +50 -0
- package/snippets/link-arrow.html +10 -0
- package/snippets/list-toolbar.html +33 -0
- package/snippets/marquee.html +15 -0
- package/snippets/media-card.html +28 -0
- package/snippets/media-row.html +21 -0
- package/snippets/media-thumb.html +12 -0
- package/snippets/menubar.html +30 -0
- package/snippets/message-thread.html +123 -0
- package/snippets/message.html +51 -0
- package/snippets/modal.html +15 -0
- package/snippets/native-select.html +15 -0
- package/snippets/navmenu.html +23 -0
- package/snippets/next-teaser.html +15 -0
- package/snippets/number-input.html +27 -0
- package/snippets/otp-input.html +9 -0
- package/snippets/page-header.html +24 -0
- package/snippets/pagination.html +6 -0
- package/snippets/password-input.html +24 -0
- package/snippets/password-requirements.html +30 -0
- package/snippets/phone-input.html +22 -0
- package/snippets/popover.html +15 -0
- package/snippets/price-card.html +34 -0
- package/snippets/product-mark.html +13 -0
- package/snippets/progress.html +4 -0
- package/snippets/project-card.html +23 -0
- package/snippets/prose.html +54 -0
- package/snippets/radio.html +17 -0
- package/snippets/recovery-codes.html +20 -0
- package/snippets/resizable.html +16 -0
- package/snippets/scroll-area.html +20 -0
- package/snippets/search.html +8 -0
- package/snippets/segmented.html +11 -0
- package/snippets/select.html +7 -0
- package/snippets/separator.html +15 -0
- package/snippets/severity-card.html +35 -0
- package/snippets/sheet.html +32 -0
- package/snippets/sidebar-docs.html +24 -0
- package/snippets/sidebar.html +55 -0
- package/snippets/site-footer.html +6 -0
- package/snippets/site-header.html +26 -0
- package/snippets/skeleton.html +4 -0
- package/snippets/slider.html +17 -0
- package/snippets/sortable-row.html +31 -0
- package/snippets/spinner.html +14 -0
- package/snippets/stack-card.html +17 -0
- package/snippets/stack-list.html +16 -0
- package/snippets/stat-card.html +24 -0
- package/snippets/stepper.html +13 -0
- package/snippets/switch.html +6 -0
- package/snippets/table-actions.html +39 -0
- package/snippets/table-compact.html +24 -0
- package/snippets/table.html +29 -0
- package/snippets/tabs.html +9 -0
- package/snippets/tag-input.html +60 -0
- package/snippets/terminal-log.html +23 -0
- package/snippets/textarea.html +3 -0
- package/snippets/theme-switch.html +6 -0
- package/snippets/time-input.html +26 -0
- package/snippets/timeline.html +28 -0
- package/snippets/toast-stack.html +10 -0
- package/snippets/toast.html +36 -0
- package/snippets/toc.html +13 -0
- package/snippets/toggle-field.html +20 -0
- package/snippets/toggle.html +22 -0
- package/snippets/tooltip.html +4 -0
- package/snippets/top-progress.html +12 -0
- package/snippets/tree.html +38 -0
- package/snippets/upgrade-banner.html +28 -0
- package/snippets/user.html +15 -0
- package/snippets/version-banner.html +15 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/* File: packages/ui/src/parts/forms-password-requirements.css
|
|
2
|
+
Map: list -> rule (icon via ::before) -> met state -> --inline variant
|
|
3
|
+
Password requirements: a live checklist of password rules under a password field.
|
|
4
|
+
A rule is "met" with the modifier .oc-password-requirement--met (set by the tiny script or a wrapper).
|
|
5
|
+
The list uses aria-live="polite" so screen readers hear the changes. */
|
|
6
|
+
|
|
7
|
+
/* List: compact, no bullets. */
|
|
8
|
+
.oc-password-requirements {
|
|
9
|
+
display: grid;
|
|
10
|
+
gap: 0.25rem;
|
|
11
|
+
margin: 0.5rem 0 0;
|
|
12
|
+
padding: 0;
|
|
13
|
+
list-style: none;
|
|
14
|
+
font-size: 0.8rem;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* One rule: icon + text. Not met = muted with a cross. */
|
|
18
|
+
.oc-password-requirement {
|
|
19
|
+
display: flex;
|
|
20
|
+
align-items: center;
|
|
21
|
+
gap: 0.375rem;
|
|
22
|
+
color: var(--oc-muted-foreground);
|
|
23
|
+
transition: color var(--oc-transition-fast);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* Icon in a small circle (text glyph, no icon font needed). */
|
|
27
|
+
.oc-password-requirement::before {
|
|
28
|
+
content: "\2715";
|
|
29
|
+
display: inline-grid;
|
|
30
|
+
place-items: center;
|
|
31
|
+
flex: none;
|
|
32
|
+
inline-size: 1rem;
|
|
33
|
+
block-size: 1rem;
|
|
34
|
+
border-radius: 999px;
|
|
35
|
+
background: var(--oc-surface-strong);
|
|
36
|
+
font-size: 0.6rem;
|
|
37
|
+
font-weight: 700;
|
|
38
|
+
line-height: 1;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* Met: success color with a check mark. */
|
|
42
|
+
.oc-password-requirement--met { color: var(--oc-success); }
|
|
43
|
+
|
|
44
|
+
.oc-password-requirement--met::before {
|
|
45
|
+
content: "\2713";
|
|
46
|
+
background: var(--oc-success);
|
|
47
|
+
color: var(--oc-success-foreground);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* Inline variant: rules side by side, wrapping (short rule texts). */
|
|
51
|
+
.oc-password-requirements--inline {
|
|
52
|
+
display: flex;
|
|
53
|
+
flex-wrap: wrap;
|
|
54
|
+
gap: 0.25rem 1rem;
|
|
55
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/* File: packages/ui/src/parts/forms-phone-input.css
|
|
2
|
+
Map: wrapper (one border around both parts) -> dial-code select -> number input -> states (focus, invalid, disabled) -> sizes
|
|
3
|
+
Phone input: a country dial code (native <select>) and the national number (input type="tel") in one frame.
|
|
4
|
+
No JavaScript needed; the React/Vue wrappers only add digit grouping ("485 12 34 56"). */
|
|
5
|
+
|
|
6
|
+
/* Wrapper: draws the single border, so both controls look like one field. */
|
|
7
|
+
.oc-phone-input {
|
|
8
|
+
display: flex;
|
|
9
|
+
align-items: stretch;
|
|
10
|
+
inline-size: 100%;
|
|
11
|
+
border: 1px solid var(--oc-input);
|
|
12
|
+
border-radius: var(--oc-radius);
|
|
13
|
+
background: transparent;
|
|
14
|
+
overflow: hidden;
|
|
15
|
+
transition: border-color var(--oc-transition-fast), box-shadow var(--oc-transition-fast);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/* Focus ring on the whole frame while either part has focus. */
|
|
19
|
+
.oc-phone-input:focus-within {
|
|
20
|
+
border-color: var(--oc-ring);
|
|
21
|
+
box-shadow: 0 0 0 2px color-mix(in srgb, var(--oc-ring) 25%, transparent);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* Dial code: compact native select, separated by a divider line. */
|
|
25
|
+
.oc-phone-input-dial {
|
|
26
|
+
flex: none;
|
|
27
|
+
padding: 0.5rem 0.5rem 0.5rem 0.75rem;
|
|
28
|
+
border: 0;
|
|
29
|
+
border-inline-end: 1px solid var(--oc-input);
|
|
30
|
+
background: var(--oc-surface);
|
|
31
|
+
color: var(--oc-foreground);
|
|
32
|
+
font: inherit;
|
|
33
|
+
font-size: 0.875rem;
|
|
34
|
+
font-variant-numeric: tabular-nums;
|
|
35
|
+
cursor: pointer;
|
|
36
|
+
outline: none;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* Dark mode: keep the opened option list readable (same as .oc-select option). */
|
|
40
|
+
.oc-phone-input-dial option {
|
|
41
|
+
background: var(--oc-background);
|
|
42
|
+
color: var(--oc-foreground);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* National number: borderless, fills the rest of the frame. */
|
|
46
|
+
.oc-phone-input-number {
|
|
47
|
+
flex: 1;
|
|
48
|
+
min-inline-size: 0;
|
|
49
|
+
padding: 0.5rem 0.75rem;
|
|
50
|
+
border: 0;
|
|
51
|
+
background: transparent;
|
|
52
|
+
color: var(--oc-foreground);
|
|
53
|
+
font: inherit;
|
|
54
|
+
font-size: 0.875rem;
|
|
55
|
+
font-variant-numeric: tabular-nums;
|
|
56
|
+
letter-spacing: 0.02em;
|
|
57
|
+
outline: none;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.oc-phone-input-number::placeholder { color: var(--oc-muted-foreground); }
|
|
61
|
+
|
|
62
|
+
/* Invalid: modifier, aria-invalid on the input, or the browser's own check after user input. */
|
|
63
|
+
.oc-phone-input--error,
|
|
64
|
+
.oc-phone-input:has([aria-invalid="true"]),
|
|
65
|
+
.oc-phone-input:has(.oc-phone-input-number:user-invalid) {
|
|
66
|
+
border-color: var(--oc-destructive);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.oc-phone-input--error:focus-within,
|
|
70
|
+
.oc-phone-input:has([aria-invalid="true"]):focus-within {
|
|
71
|
+
box-shadow: 0 0 0 2px color-mix(in srgb, var(--oc-destructive) 25%, transparent);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* Disabled: dim the whole frame when the number input is disabled. */
|
|
75
|
+
.oc-phone-input:has(.oc-phone-input-number:disabled) {
|
|
76
|
+
opacity: 0.5;
|
|
77
|
+
cursor: not-allowed;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* Small size for toolbars and dense forms. */
|
|
81
|
+
.oc-phone-input--sm .oc-phone-input-dial,
|
|
82
|
+
.oc-phone-input--sm .oc-phone-input-number {
|
|
83
|
+
padding-block: 0.25rem;
|
|
84
|
+
font-size: 0.8rem;
|
|
85
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/* File: packages/ui/src/parts/forms-time-input.css
|
|
2
|
+
Map: 24h text field (HH:mm) -> invalid state -> slot list (radio chips) -> slot states -> --sm
|
|
3
|
+
Time input: a short text field with pattern="HH:mm" and a <datalist> of suggested slots,
|
|
4
|
+
plus an optional slot list (radio chips) to pick a time without typing. No JavaScript needed. */
|
|
5
|
+
|
|
6
|
+
/* Field: add next to .oc-input. Narrow, centered digits. */
|
|
7
|
+
.oc-time-input {
|
|
8
|
+
inline-size: 6.5rem;
|
|
9
|
+
text-align: center;
|
|
10
|
+
font-variant-numeric: tabular-nums;
|
|
11
|
+
letter-spacing: 0.04em;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/* Wrong format (e.g. 25:00) after the user typed: native pattern check. */
|
|
15
|
+
.oc-time-input:user-invalid { border-color: var(--oc-destructive); }
|
|
16
|
+
|
|
17
|
+
/* Slot list: a wrapping grid of radio chips (role="radiogroup"). */
|
|
18
|
+
.oc-time-slots {
|
|
19
|
+
display: grid;
|
|
20
|
+
grid-template-columns: repeat(auto-fill, minmax(4.25rem, 1fr));
|
|
21
|
+
gap: 0.375rem;
|
|
22
|
+
/* Fill the row (also inside flex parents) so auto-fill can create several columns. */
|
|
23
|
+
inline-size: 100%;
|
|
24
|
+
max-block-size: 12rem;
|
|
25
|
+
overflow-y: auto;
|
|
26
|
+
padding: 0.25rem;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* One slot: a label that wraps a hidden radio input. */
|
|
30
|
+
.oc-time-slot {
|
|
31
|
+
position: relative;
|
|
32
|
+
padding: 0.375rem 0.25rem;
|
|
33
|
+
border: 1px solid var(--oc-border);
|
|
34
|
+
border-radius: calc(var(--oc-radius) - 2px);
|
|
35
|
+
color: var(--oc-foreground);
|
|
36
|
+
font-size: 0.8rem;
|
|
37
|
+
font-variant-numeric: tabular-nums;
|
|
38
|
+
text-align: center;
|
|
39
|
+
cursor: pointer;
|
|
40
|
+
transition: background var(--oc-transition-fast), border-color var(--oc-transition-fast);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* The radio stays in the tab order (keyboard works) but is invisible. */
|
|
44
|
+
.oc-time-slot input {
|
|
45
|
+
position: absolute;
|
|
46
|
+
inset: 0;
|
|
47
|
+
margin: 0;
|
|
48
|
+
opacity: 0;
|
|
49
|
+
cursor: pointer;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.oc-time-slot:hover { background: var(--oc-surface); }
|
|
53
|
+
|
|
54
|
+
.oc-time-slot:has(input:checked) {
|
|
55
|
+
border-color: var(--oc-primary);
|
|
56
|
+
background: var(--oc-primary);
|
|
57
|
+
color: var(--oc-primary-foreground);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.oc-time-slot:has(input:focus-visible) {
|
|
61
|
+
outline: 2px solid var(--oc-ring);
|
|
62
|
+
outline-offset: 1px;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* Booked / unavailable slot. */
|
|
66
|
+
.oc-time-slot:has(input:disabled) {
|
|
67
|
+
opacity: 0.45;
|
|
68
|
+
text-decoration: line-through;
|
|
69
|
+
cursor: not-allowed;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* Small slots for dense layouts. */
|
|
73
|
+
.oc-time-slots--sm { grid-template-columns: repeat(auto-fill, minmax(3.5rem, 1fr)); }
|
|
74
|
+
.oc-time-slots--sm .oc-time-slot { padding-block: 0.25rem; font-size: 0.72rem; }
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/* File: packages/ui/src/parts/forms-toggle-field.css
|
|
2
|
+
Map: row (label wraps text + switch) -> text block (label + description) -> states -> --card / --reverse
|
|
3
|
+
Toggle field: a settings row with a title, a description and a switch.
|
|
4
|
+
The whole row is a <label>, so clicking the text flips the switch. The switch inside is a
|
|
5
|
+
<span class="oc-switch"> (a label inside a label is not valid HTML). */
|
|
6
|
+
|
|
7
|
+
/* Row: text on the left, switch on the right. */
|
|
8
|
+
.oc-toggle-field {
|
|
9
|
+
display: flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
justify-content: space-between;
|
|
12
|
+
gap: 1rem;
|
|
13
|
+
padding-block: 0.5rem;
|
|
14
|
+
cursor: pointer;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* Text block. */
|
|
18
|
+
.oc-toggle-field-text {
|
|
19
|
+
display: grid;
|
|
20
|
+
gap: 0.125rem;
|
|
21
|
+
min-inline-size: 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.oc-toggle-field-label {
|
|
25
|
+
color: var(--oc-foreground);
|
|
26
|
+
font-size: 0.875rem;
|
|
27
|
+
font-weight: 500;
|
|
28
|
+
line-height: 1.3;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.oc-toggle-field-description {
|
|
32
|
+
color: var(--oc-muted-foreground);
|
|
33
|
+
font-size: 0.8rem;
|
|
34
|
+
line-height: 1.4;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* Disabled switch: dim the row and show it can't be used. */
|
|
38
|
+
.oc-toggle-field:has(input:disabled) {
|
|
39
|
+
opacity: 0.55;
|
|
40
|
+
cursor: not-allowed;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.oc-toggle-field:has(input:disabled) .oc-switch { cursor: not-allowed; }
|
|
44
|
+
|
|
45
|
+
/* Card: bordered box; the border turns primary while the switch is on. */
|
|
46
|
+
.oc-toggle-field--card {
|
|
47
|
+
padding: 0.875rem 1rem;
|
|
48
|
+
border: 1px solid var(--oc-border);
|
|
49
|
+
border-radius: var(--oc-radius);
|
|
50
|
+
background: var(--oc-card);
|
|
51
|
+
transition: border-color var(--oc-transition-fast), background var(--oc-transition-fast);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.oc-toggle-field--card:hover { background: var(--oc-surface); }
|
|
55
|
+
|
|
56
|
+
.oc-toggle-field--card:has(input:checked) {
|
|
57
|
+
border-color: color-mix(in srgb, var(--oc-primary) 55%, var(--oc-border));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.oc-toggle-field--card:has(input:focus-visible) {
|
|
61
|
+
outline: 2px solid var(--oc-ring);
|
|
62
|
+
outline-offset: 2px;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* Reverse: switch first, text after it. */
|
|
66
|
+
.oc-toggle-field--reverse {
|
|
67
|
+
flex-direction: row-reverse;
|
|
68
|
+
justify-content: flex-end;
|
|
69
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/* File: packages/ui/src/parts/media-art.css | Map: base placeholder -> palette variants (--2 … --6)
|
|
2
|
+
Placeholder artwork for media demos: a soft palette gradient instead of an image file.
|
|
3
|
+
Use it wherever an <img> would go: <div class="oc-media-art oc-media-art--3" role="img" aria-label="…"></div>.
|
|
4
|
+
Only Omnia palette tokens, so no hex and no external image URLs. */
|
|
5
|
+
|
|
6
|
+
.oc-media-art {
|
|
7
|
+
display: block;
|
|
8
|
+
inline-size: 100%;
|
|
9
|
+
block-size: 100%;
|
|
10
|
+
/* Light spot top-left + diagonal gradient; the two colors come from the variant. */
|
|
11
|
+
background:
|
|
12
|
+
radial-gradient(circle at 28% 30%, oklch(from var(--color-white-50) l c h / 0.35), transparent 45%),
|
|
13
|
+
linear-gradient(135deg, var(--oc-media-art-from, var(--color-vividblue-60)), var(--oc-media-art-to, var(--color-purple-50)));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/* Variants: just another pair of palette colors. */
|
|
17
|
+
.oc-media-art--2 { --oc-media-art-from: var(--color-teal-50); --oc-media-art-to: var(--color-skycyan-50); }
|
|
18
|
+
.oc-media-art--3 { --oc-media-art-from: var(--color-orange-60); --oc-media-art-to: var(--color-red-50); }
|
|
19
|
+
.oc-media-art--4 { --oc-media-art-from: var(--color-green-40); --oc-media-art-to: var(--color-teal-70); }
|
|
20
|
+
.oc-media-art--5 { --oc-media-art-from: var(--color-magenta-50); --oc-media-art-to: var(--color-indigo-50); }
|
|
21
|
+
.oc-media-art--6 { --oc-media-art-from: var(--color-yellow-60); --oc-media-art-to: var(--color-peru-60); }
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/* File: packages/ui/src/parts/media-attachment.css | Map: list -> file chip (icon by type, name, size, open/remove) -> type tones
|
|
2
|
+
-> uploading progress + error -> image preview (in a message) -> thumbnail chip (in a composer) -> reduced motion
|
|
3
|
+
Attachment: a file chip or an image preview inside a chat message or a composer. The icon tile shows the extension and is
|
|
4
|
+
tinted per type (--pdf, --doc, --sheet, --zip, --image). Based on the attachment picker of the Korrekt question chat. */
|
|
5
|
+
|
|
6
|
+
.oc-attachment-list { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0; padding: 0; list-style: none; }
|
|
7
|
+
.oc-attachment-list--stack { flex-direction: column; flex-wrap: nowrap; }
|
|
8
|
+
|
|
9
|
+
/* ---- File chip ---- */
|
|
10
|
+
.oc-attachment {
|
|
11
|
+
--oc-attachment-tone: var(--oc-muted-foreground);
|
|
12
|
+
position: relative;
|
|
13
|
+
display: flex;
|
|
14
|
+
align-items: center;
|
|
15
|
+
gap: 0.6rem;
|
|
16
|
+
min-inline-size: 0;
|
|
17
|
+
max-inline-size: 20rem;
|
|
18
|
+
padding: 0.4rem 0.5rem 0.4rem 0.4rem;
|
|
19
|
+
overflow: hidden;
|
|
20
|
+
border: 1px solid var(--oc-border);
|
|
21
|
+
border-radius: calc(var(--oc-radius) + 2px);
|
|
22
|
+
background: var(--oc-card);
|
|
23
|
+
color: var(--oc-foreground);
|
|
24
|
+
font-size: 0.8rem;
|
|
25
|
+
line-height: 1.3;
|
|
26
|
+
text-decoration: none;
|
|
27
|
+
transition: background var(--oc-transition-normal), border-color var(--oc-transition-normal);
|
|
28
|
+
}
|
|
29
|
+
.oc-attachment-list--stack > .oc-attachment,
|
|
30
|
+
.oc-attachment-list--stack > li > .oc-attachment { max-inline-size: none; }
|
|
31
|
+
a.oc-attachment:hover { border-color: var(--oc-border-strong, var(--oc-border)); background: var(--oc-surface-strong); }
|
|
32
|
+
a.oc-attachment:focus-visible { outline: 2px solid var(--oc-ring); outline-offset: 2px; }
|
|
33
|
+
|
|
34
|
+
/* Icon tile: a page with a folded corner, the extension in the type tone */
|
|
35
|
+
.oc-attachment__icon {
|
|
36
|
+
flex: none;
|
|
37
|
+
display: grid;
|
|
38
|
+
place-items: end center;
|
|
39
|
+
inline-size: 2.1rem;
|
|
40
|
+
block-size: 2.5rem;
|
|
41
|
+
padding-block-end: 0.3rem;
|
|
42
|
+
overflow: hidden;
|
|
43
|
+
border-radius: 0.3rem;
|
|
44
|
+
background:
|
|
45
|
+
linear-gradient(225deg, color-mix(in oklch, var(--oc-attachment-tone) 35%, var(--oc-card)) 0.45rem, transparent 0.45rem),
|
|
46
|
+
color-mix(in oklch, var(--oc-attachment-tone) 14%, var(--oc-card));
|
|
47
|
+
color: var(--oc-attachment-tone);
|
|
48
|
+
font-size: 0.6rem;
|
|
49
|
+
font-weight: 750;
|
|
50
|
+
letter-spacing: 0.03em;
|
|
51
|
+
clip-path: polygon(0 0, calc(100% - 0.45rem) 0, 100% 0.45rem, 100% 100%, 0 100%);
|
|
52
|
+
}
|
|
53
|
+
.oc-attachment__icon:has(> :is(img, .oc-media-art)) { padding: 0; clip-path: none; }
|
|
54
|
+
.oc-attachment__icon > :is(img, .oc-media-art) { inline-size: 100%; block-size: 100%; object-fit: cover; }
|
|
55
|
+
.oc-attachment__body { display: grid; flex: 1 1 auto; min-inline-size: 0; }
|
|
56
|
+
.oc-attachment__name { overflow: hidden; font-weight: 600; white-space: nowrap; text-overflow: ellipsis; }
|
|
57
|
+
.oc-attachment__size { color: var(--oc-muted-foreground); font-size: 0.72rem; font-variant-numeric: tabular-nums; }
|
|
58
|
+
|
|
59
|
+
.oc-attachment__remove {
|
|
60
|
+
position: relative;
|
|
61
|
+
z-index: 1;
|
|
62
|
+
flex: none;
|
|
63
|
+
display: grid;
|
|
64
|
+
place-items: center;
|
|
65
|
+
inline-size: 1.6rem;
|
|
66
|
+
block-size: 1.6rem;
|
|
67
|
+
padding: 0;
|
|
68
|
+
border: 0;
|
|
69
|
+
border-radius: 50%;
|
|
70
|
+
background: transparent;
|
|
71
|
+
color: var(--oc-muted-foreground);
|
|
72
|
+
font-size: 0.8rem;
|
|
73
|
+
cursor: pointer;
|
|
74
|
+
}
|
|
75
|
+
.oc-attachment__remove:hover { background: oklch(from var(--oc-destructive) l c h / 0.12); color: var(--oc-destructive); }
|
|
76
|
+
.oc-attachment__remove:focus-visible { outline: 2px solid var(--oc-ring); outline-offset: 1px; }
|
|
77
|
+
|
|
78
|
+
/* ---- Type tones (theme tokens, so both color schemes stay readable) ---- */
|
|
79
|
+
.oc-attachment--pdf { --oc-attachment-tone: var(--oc-destructive); }
|
|
80
|
+
.oc-attachment--doc { --oc-attachment-tone: var(--oc-chart-1, var(--oc-primary)); }
|
|
81
|
+
.oc-attachment--sheet { --oc-attachment-tone: var(--oc-success); }
|
|
82
|
+
.oc-attachment--zip { --oc-attachment-tone: var(--oc-warning); }
|
|
83
|
+
.oc-attachment--image { --oc-attachment-tone: var(--oc-chart-4, var(--oc-primary)); }
|
|
84
|
+
|
|
85
|
+
/* ---- Upload state: a thin progress line along the bottom (--oc-attachment-progress) and an error state ---- */
|
|
86
|
+
.oc-attachment--uploading::after {
|
|
87
|
+
content: "";
|
|
88
|
+
position: absolute;
|
|
89
|
+
inset-inline-start: 0;
|
|
90
|
+
inset-block-end: 0;
|
|
91
|
+
inline-size: var(--oc-attachment-progress, 40%);
|
|
92
|
+
block-size: 3px;
|
|
93
|
+
background: var(--oc-primary);
|
|
94
|
+
transition: inline-size var(--oc-transition-normal);
|
|
95
|
+
}
|
|
96
|
+
.oc-attachment--uploading .oc-attachment__size { color: var(--oc-primary); }
|
|
97
|
+
.oc-attachment--error { border-color: oklch(from var(--oc-destructive) l c h / 0.55); }
|
|
98
|
+
.oc-attachment--error .oc-attachment__size { color: var(--oc-destructive); }
|
|
99
|
+
|
|
100
|
+
/* ---- Image preview inside a message: the picture with an optional caption ---- */
|
|
101
|
+
.oc-attachment-image {
|
|
102
|
+
position: relative;
|
|
103
|
+
display: block;
|
|
104
|
+
inline-size: min(100%, 18rem);
|
|
105
|
+
overflow: hidden;
|
|
106
|
+
border: 1px solid var(--oc-border);
|
|
107
|
+
border-radius: calc(var(--oc-radius) + 2px);
|
|
108
|
+
background: var(--oc-muted);
|
|
109
|
+
color: var(--color-white-50);
|
|
110
|
+
text-decoration: none;
|
|
111
|
+
}
|
|
112
|
+
.oc-attachment-image > :is(img, .oc-media-art) {
|
|
113
|
+
display: block;
|
|
114
|
+
inline-size: 100%;
|
|
115
|
+
block-size: auto;
|
|
116
|
+
max-block-size: 14rem;
|
|
117
|
+
aspect-ratio: 4 / 3;
|
|
118
|
+
object-fit: cover;
|
|
119
|
+
}
|
|
120
|
+
.oc-attachment-image__caption {
|
|
121
|
+
position: absolute;
|
|
122
|
+
inset: auto 0 0;
|
|
123
|
+
padding: 1.25rem 0.6rem 0.4rem;
|
|
124
|
+
overflow: hidden;
|
|
125
|
+
background: linear-gradient(transparent, oklch(from var(--color-black-60) l c h / 0.6));
|
|
126
|
+
font-size: 0.72rem;
|
|
127
|
+
font-weight: 600;
|
|
128
|
+
white-space: nowrap;
|
|
129
|
+
text-overflow: ellipsis;
|
|
130
|
+
}
|
|
131
|
+
a.oc-attachment-image:focus-visible { outline: 2px solid var(--oc-ring); outline-offset: 2px; }
|
|
132
|
+
a.oc-attachment-image:hover > :is(img, .oc-media-art) { filter: brightness(1.05); }
|
|
133
|
+
|
|
134
|
+
/* ---- Thumbnail chip (pasted screenshot in a composer): square image with a remove button on top ---- */
|
|
135
|
+
.oc-attachment-thumb {
|
|
136
|
+
position: relative;
|
|
137
|
+
flex: none;
|
|
138
|
+
inline-size: 4rem;
|
|
139
|
+
block-size: 4rem;
|
|
140
|
+
overflow: hidden;
|
|
141
|
+
border: 1px solid var(--oc-border);
|
|
142
|
+
border-radius: calc(var(--oc-radius) + 2px);
|
|
143
|
+
background: var(--oc-muted);
|
|
144
|
+
}
|
|
145
|
+
.oc-attachment-thumb > :is(img, .oc-media-art) { inline-size: 100%; block-size: 100%; object-fit: cover; }
|
|
146
|
+
.oc-attachment-thumb > .oc-attachment__remove {
|
|
147
|
+
position: absolute;
|
|
148
|
+
inset-block-start: 0.2rem;
|
|
149
|
+
inset-inline-end: 0.2rem;
|
|
150
|
+
inline-size: 1.35rem;
|
|
151
|
+
block-size: 1.35rem;
|
|
152
|
+
background: oklch(from var(--color-black-60) l c h / 0.6);
|
|
153
|
+
color: var(--color-white-50);
|
|
154
|
+
font-size: 0.65rem;
|
|
155
|
+
}
|
|
156
|
+
.oc-attachment-thumb > .oc-attachment__remove:hover { background: var(--oc-destructive); color: var(--color-white-50); }
|
|
157
|
+
|
|
158
|
+
@media (prefers-reduced-motion: reduce) {
|
|
159
|
+
.oc-attachment, .oc-attachment--uploading::after { transition: none; }
|
|
160
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/* File: packages/ui/src/parts/media-browser-frame.css | Map: frame -> chrome bar (dots, address pill, actions)
|
|
2
|
+
-> view (screenshot slot, ratio) -> caption -> minimal / dark / tilt modifiers -> motion
|
|
3
|
+
Browser frame: fake browser chrome around a screenshot or any live content (portfolio, product shots). */
|
|
4
|
+
|
|
5
|
+
.oc-browser-frame {
|
|
6
|
+
--oc-browser-frame-ratio: 16 / 10;
|
|
7
|
+
display: grid;
|
|
8
|
+
inline-size: 100%;
|
|
9
|
+
margin: 0;
|
|
10
|
+
overflow: hidden;
|
|
11
|
+
border: 1px solid var(--oc-border);
|
|
12
|
+
border-radius: calc(var(--oc-radius) + 4px);
|
|
13
|
+
background: var(--oc-card);
|
|
14
|
+
color: var(--oc-foreground);
|
|
15
|
+
box-shadow: 0 30px 60px -30px oklch(from var(--color-black-60) l c h / 0.45);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/* ---- Chrome bar ---- */
|
|
19
|
+
.oc-browser-frame__bar {
|
|
20
|
+
display: flex;
|
|
21
|
+
align-items: center;
|
|
22
|
+
gap: 0.45rem;
|
|
23
|
+
padding: 0.55rem 0.8rem;
|
|
24
|
+
border-block-end: 1px solid var(--oc-border);
|
|
25
|
+
background: var(--oc-muted);
|
|
26
|
+
}
|
|
27
|
+
.oc-browser-frame__bar > i { flex: none; inline-size: 0.65rem; aspect-ratio: 1; border-radius: 50%; background: var(--color-red-50); }
|
|
28
|
+
.oc-browser-frame__bar > i:nth-of-type(2) { background: var(--color-orange-60); }
|
|
29
|
+
.oc-browser-frame__bar > i:nth-of-type(3) { background: var(--color-green-20); }
|
|
30
|
+
|
|
31
|
+
/* Address pill with a lock; the text is cut off with "…" when the frame is narrow. */
|
|
32
|
+
.oc-browser-frame__address {
|
|
33
|
+
flex: 1;
|
|
34
|
+
min-inline-size: 0;
|
|
35
|
+
max-inline-size: 28rem;
|
|
36
|
+
margin-inline: auto;
|
|
37
|
+
padding: 0.2rem 0.75rem;
|
|
38
|
+
overflow: hidden;
|
|
39
|
+
border-radius: 999px;
|
|
40
|
+
background: var(--oc-card);
|
|
41
|
+
color: var(--oc-muted-foreground);
|
|
42
|
+
font-size: 0.75rem;
|
|
43
|
+
text-align: center;
|
|
44
|
+
text-overflow: ellipsis;
|
|
45
|
+
white-space: nowrap;
|
|
46
|
+
}
|
|
47
|
+
.oc-browser-frame__address::before { content: "\1F512\FE0E" / ""; margin-inline-end: 0.35rem; font-size: 0.65rem; opacity: 0.7; }
|
|
48
|
+
/* Balance the dots on the left so the address stays centered. */
|
|
49
|
+
.oc-browser-frame__actions { display: flex; gap: 0.35rem; min-inline-size: 2.4rem; justify-content: flex-end; color: var(--oc-muted-foreground); font-size: 0.8rem; }
|
|
50
|
+
|
|
51
|
+
/* ---- View: the screenshot (or any content) ---- */
|
|
52
|
+
.oc-browser-frame__view { position: relative; aspect-ratio: var(--oc-browser-frame-ratio); overflow: hidden; background: var(--oc-background); }
|
|
53
|
+
.oc-browser-frame__view > :is(img, video, iframe, .oc-media-art) { display: block; inline-size: 100%; block-size: 100%; border: 0; object-fit: cover; object-position: top; }
|
|
54
|
+
/* Free height: the content decides (live HTML inside the frame). */
|
|
55
|
+
.oc-browser-frame--auto .oc-browser-frame__view { aspect-ratio: auto; }
|
|
56
|
+
|
|
57
|
+
.oc-browser-frame__caption { padding: 0.6rem 0.9rem; border-block-start: 1px solid var(--oc-border); font-size: 0.8125rem; color: var(--oc-muted-foreground); }
|
|
58
|
+
|
|
59
|
+
/* ---- Modifiers ---- */
|
|
60
|
+
.oc-browser-frame--minimal .oc-browser-frame__address,
|
|
61
|
+
.oc-browser-frame--minimal .oc-browser-frame__actions { display: none; }
|
|
62
|
+
/* Dark chrome in both themes (light-dark() tokens resolve to their dark side). */
|
|
63
|
+
.oc-browser-frame--dark { color-scheme: dark; }
|
|
64
|
+
|
|
65
|
+
/* Tilt: a slight 3D angle that straightens on hover / focus. Static with reduced motion. */
|
|
66
|
+
.oc-browser-frame--tilt { transform: perspective(80rem) rotateX(6deg) rotateY(-8deg); transform-origin: center; }
|
|
67
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
68
|
+
.oc-browser-frame--tilt { transition: transform 0.5s cubic-bezier(0.2, 0.7, 0.2, 1); }
|
|
69
|
+
.oc-browser-frame--tilt:is(:hover, :focus-within) { transform: none; }
|
|
70
|
+
}
|
|
71
|
+
@media (prefers-reduced-motion: reduce) {
|
|
72
|
+
.oc-browser-frame--tilt { transform: none; }
|
|
73
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/* File: packages/ui/src/parts/media-card.css | Map: card grid -> body row (avatar | text | menu) -> title (stretched link)
|
|
2
|
+
-> meta -> menu button -> compact modifier -> hover
|
|
3
|
+
Media card: a .oc-media-thumb on top, then avatar + title + meta + an overflow menu button.
|
|
4
|
+
The title link stretches over the whole card (::after), the menu button sits above it. */
|
|
5
|
+
|
|
6
|
+
.oc-media-card {
|
|
7
|
+
position: relative;
|
|
8
|
+
display: grid;
|
|
9
|
+
gap: 0.75rem;
|
|
10
|
+
align-content: start;
|
|
11
|
+
inline-size: 100%;
|
|
12
|
+
color: var(--oc-foreground);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* Avatar | text | menu. */
|
|
16
|
+
.oc-media-card__body { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 0.75rem; align-items: start; }
|
|
17
|
+
.oc-media-card__body:not(:has(.oc-media-card__avatar)) { grid-template-columns: minmax(0, 1fr) auto; }
|
|
18
|
+
|
|
19
|
+
.oc-media-card__avatar {
|
|
20
|
+
display: grid;
|
|
21
|
+
place-items: center;
|
|
22
|
+
inline-size: 2.25rem;
|
|
23
|
+
aspect-ratio: 1;
|
|
24
|
+
overflow: hidden;
|
|
25
|
+
border-radius: 50%;
|
|
26
|
+
background: var(--oc-muted);
|
|
27
|
+
color: var(--oc-foreground);
|
|
28
|
+
font-size: 0.8rem;
|
|
29
|
+
font-weight: 600;
|
|
30
|
+
}
|
|
31
|
+
.oc-media-card__avatar > * { inline-size: 100%; block-size: 100%; object-fit: cover; }
|
|
32
|
+
|
|
33
|
+
.oc-media-card__text { display: grid; gap: 0.2rem; min-inline-size: 0; }
|
|
34
|
+
|
|
35
|
+
/* Two lines max, then "…". */
|
|
36
|
+
.oc-media-card__title {
|
|
37
|
+
margin: 0;
|
|
38
|
+
font-size: 0.95rem;
|
|
39
|
+
font-weight: 600;
|
|
40
|
+
line-height: 1.35;
|
|
41
|
+
display: -webkit-box;
|
|
42
|
+
-webkit-box-orient: vertical;
|
|
43
|
+
-webkit-line-clamp: 2;
|
|
44
|
+
line-clamp: 2;
|
|
45
|
+
overflow: hidden;
|
|
46
|
+
}
|
|
47
|
+
.oc-media-card__title > a { color: inherit; text-decoration: none; }
|
|
48
|
+
.oc-media-card__title > a::after { content: ""; position: absolute; inset: 0; border-radius: var(--oc-radius); }
|
|
49
|
+
.oc-media-card__title > a:focus-visible { outline: none; }
|
|
50
|
+
.oc-media-card__title > a:focus-visible::after { outline: 2px solid var(--oc-ring); outline-offset: 4px; }
|
|
51
|
+
|
|
52
|
+
.oc-media-card__meta { margin: 0; font-size: 0.8125rem; color: var(--oc-muted-foreground); }
|
|
53
|
+
.oc-media-card__meta > a { position: relative; z-index: 1; color: inherit; text-decoration: none; }
|
|
54
|
+
.oc-media-card__meta > a:hover { color: var(--oc-foreground); }
|
|
55
|
+
|
|
56
|
+
/* Overflow menu (⋮): above the stretched link, so it gets its own clicks. */
|
|
57
|
+
.oc-media-card__menu {
|
|
58
|
+
position: relative;
|
|
59
|
+
z-index: 1;
|
|
60
|
+
display: grid;
|
|
61
|
+
place-items: center;
|
|
62
|
+
inline-size: 2rem;
|
|
63
|
+
aspect-ratio: 1;
|
|
64
|
+
padding: 0;
|
|
65
|
+
border: 0;
|
|
66
|
+
border-radius: 50%;
|
|
67
|
+
background: transparent;
|
|
68
|
+
color: var(--oc-muted-foreground);
|
|
69
|
+
font-size: 1.1rem;
|
|
70
|
+
cursor: pointer;
|
|
71
|
+
}
|
|
72
|
+
.oc-media-card__menu:hover { background: var(--oc-surface-strong); color: var(--oc-foreground); }
|
|
73
|
+
.oc-media-card__menu:focus-visible { outline: 2px solid var(--oc-ring); outline-offset: 1px; }
|
|
74
|
+
|
|
75
|
+
/* Compact: smaller avatar and text, e.g. for a dense grid. */
|
|
76
|
+
.oc-media-card--compact { gap: 0.5rem; }
|
|
77
|
+
.oc-media-card--compact .oc-media-card__avatar { inline-size: 1.75rem; }
|
|
78
|
+
.oc-media-card--compact .oc-media-card__title { font-size: 0.875rem; }
|
|
79
|
+
|
|
80
|
+
/* Hovering the card zooms its thumb a little (the stretched link covers the thumb). */
|
|
81
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
82
|
+
.oc-media-card:hover .oc-media-thumb > :is(img, video, .oc-media-art) { transform: scale(1.04); }
|
|
83
|
+
}
|