@acta-dev/web 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/LICENSE +21 -0
  2. package/astro.config.mjs +31 -0
  3. package/package.json +62 -0
  4. package/public/favicon.png +0 -0
  5. package/src/components/DocumentSearchList.astro +150 -0
  6. package/src/components/DocumentView.astro +198 -0
  7. package/src/components/LanguageSwitcher.tsx +115 -0
  8. package/src/components/SidebarToggle.tsx +42 -0
  9. package/src/components/ThemeToggle.tsx +129 -0
  10. package/src/components/graph/DocumentGraphIsland.tsx +226 -0
  11. package/src/components/graph/GraphContext.ts +15 -0
  12. package/src/components/graph/graph.css +330 -0
  13. package/src/components/graph/layout.ts +48 -0
  14. package/src/components/graph/nodes.tsx +80 -0
  15. package/src/components/ui/Button.astro +105 -0
  16. package/src/components/ui/Chip.astro +147 -0
  17. package/src/components/ui/Field.astro +29 -0
  18. package/src/components/ui/Input.astro +34 -0
  19. package/src/components/ui/Pill.astro +71 -0
  20. package/src/components/ui/SegmentedControl.astro +105 -0
  21. package/src/components/ui/Select.astro +41 -0
  22. package/src/components/ui/Tooltip.astro +94 -0
  23. package/src/layouts/BaseLayout.astro +147 -0
  24. package/src/lib/documents.test.ts +175 -0
  25. package/src/lib/documents.ts +156 -0
  26. package/src/lib/i18n-client.ts +32 -0
  27. package/src/lib/i18n.ts +92 -0
  28. package/src/lib/project.test.ts +24 -0
  29. package/src/lib/project.ts +120 -0
  30. package/src/lib/search-client.ts +192 -0
  31. package/src/lib/search.test.ts +94 -0
  32. package/src/lib/search.ts +153 -0
  33. package/src/locales/en/common.json +6 -0
  34. package/src/locales/en/dashboard.json +8 -0
  35. package/src/locales/en/documents.json +63 -0
  36. package/src/locales/en/graph.json +19 -0
  37. package/src/locales/en/search.json +7 -0
  38. package/src/locales/en/sidebar.json +25 -0
  39. package/src/locales/en/validation.json +13 -0
  40. package/src/locales/ru/common.json +6 -0
  41. package/src/locales/ru/dashboard.json +8 -0
  42. package/src/locales/ru/documents.json +63 -0
  43. package/src/locales/ru/graph.json +19 -0
  44. package/src/locales/ru/search.json +7 -0
  45. package/src/locales/ru/sidebar.json +25 -0
  46. package/src/locales/ru/validation.json +13 -0
  47. package/src/pages/documents/[id]/index.astro +39 -0
  48. package/src/pages/graph.astro +54 -0
  49. package/src/pages/index.astro +41 -0
  50. package/src/pages/ru/documents/[id]/index.astro +39 -0
  51. package/src/pages/ru/graph.astro +54 -0
  52. package/src/pages/ru/index.astro +41 -0
  53. package/src/pages/ru/search-index-full.json.ts +1 -0
  54. package/src/pages/ru/search.astro +27 -0
  55. package/src/pages/ru/validation.astro +63 -0
  56. package/src/pages/search-index-full.json.ts +12 -0
  57. package/src/pages/search-index.json.ts +12 -0
  58. package/src/pages/search.astro +27 -0
  59. package/src/pages/validation.astro +63 -0
  60. package/src/styles/global.css +1391 -0
  61. package/src/styles/themes/dark.css +61 -0
  62. package/src/styles/themes/light.css +63 -0
  63. package/src/styles/tokens/primitives.css +32 -0
  64. package/src/styles/tokens/semantic.css +34 -0
  65. package/src/styles/tokens/typography.css +28 -0
  66. package/tsconfig.json +11 -0
@@ -0,0 +1,48 @@
1
+ import type { DocumentGraph } from "@acta-dev/core";
2
+ import dagre from "@dagrejs/dagre";
3
+ import type { Edge, Node } from "@xyflow/react";
4
+
5
+ const NODE_WIDTH = 220;
6
+ const NODE_HEIGHT = 80;
7
+
8
+ export function computeLayout(graph: DocumentGraph): { nodes: Node[]; edges: Edge[] } {
9
+ const g = new dagre.graphlib.Graph();
10
+ g.setDefaultEdgeLabel(() => ({}));
11
+ g.setGraph({ rankdir: "LR", nodesep: 40, ranksep: 100, marginx: 36, marginy: 36 });
12
+
13
+ for (const node of graph.nodes) {
14
+ g.setNode(node.id, { width: NODE_WIDTH, height: NODE_HEIGHT });
15
+ }
16
+
17
+ for (const edge of graph.edges) {
18
+ g.setEdge(edge.source, edge.target);
19
+ }
20
+
21
+ dagre.layout(g);
22
+
23
+ const nodes: Node[] = graph.nodes.map((node) => {
24
+ const pos = g.node(node.id);
25
+ return {
26
+ id: node.id,
27
+ type: node.kind,
28
+ position: { x: pos.x - NODE_WIDTH / 2, y: pos.y - NODE_HEIGHT / 2 },
29
+ data: {
30
+ label: node.title,
31
+ status: node.status,
32
+ kind: node.kind,
33
+ tags: node.tags,
34
+ },
35
+ };
36
+ });
37
+
38
+ const edges: Edge[] = graph.edges.map((edge, i) => ({
39
+ id: `e-${i}-${edge.source}-${edge.target}`,
40
+ source: edge.source,
41
+ target: edge.target,
42
+ type: "smoothstep",
43
+ data: { linkType: edge.type },
44
+ className: `edge-${edge.type}`,
45
+ }));
46
+
47
+ return { nodes, edges };
48
+ }
@@ -0,0 +1,80 @@
1
+ import type { NodeProps } from "@xyflow/react";
2
+ import { Handle, Position } from "@xyflow/react";
3
+ import React from "react";
4
+ import { useGraphContext } from "./GraphContext.js";
5
+
6
+ type GraphNodeData = {
7
+ label: string;
8
+ status: string;
9
+ kind: "adr" | "spec";
10
+ tags: string[];
11
+ href?: string;
12
+ };
13
+
14
+ function truncate(value: string, max: number): string {
15
+ return value.length > max ? `${value.slice(0, max - 1)}…` : value;
16
+ }
17
+
18
+ function GraphNodeCard({
19
+ id,
20
+ data,
21
+ kind,
22
+ }: {
23
+ id: string;
24
+ data: GraphNodeData;
25
+ kind: "adr" | "spec";
26
+ }) {
27
+ const { selectedId, connectedIds } = useGraphContext();
28
+
29
+ const dimmed = selectedId !== null && !connectedIds.has(id);
30
+ const highlighted = selectedId !== null && connectedIds.has(id);
31
+
32
+ const href = data.href;
33
+
34
+ const statusKey = data.status.toLowerCase().replace(/\s+/g, "-");
35
+ const inner = (
36
+ <div
37
+ data-status={statusKey}
38
+ className={[
39
+ "graph-rf-node",
40
+ `graph-rf-node--${kind}`,
41
+ dimmed ? "graph-rf-node--dimmed" : "",
42
+ highlighted ? "graph-rf-node--highlighted" : "",
43
+ ]
44
+ .filter(Boolean)
45
+ .join(" ")}
46
+ >
47
+ <Handle type="target" position={Position.Left} />
48
+ <Handle type="source" position={Position.Right} />
49
+ <div className="graph-rf-node__head">
50
+ <span className="graph-rf-node__id">{id}</span>
51
+ <span className={`graph-rf-node__kind graph-rf-node__kind--${kind}`}>
52
+ {kind.toUpperCase()}
53
+ </span>
54
+ </div>
55
+ <span className="graph-rf-node__title">{truncate(data.label, 30)}</span>
56
+ <span className="graph-rf-node__status">{data.status}</span>
57
+ </div>
58
+ );
59
+
60
+ return href ? (
61
+ <a href={href} className="graph-rf-node-link" title={data.label}>
62
+ {inner}
63
+ </a>
64
+ ) : (
65
+ inner
66
+ );
67
+ }
68
+
69
+ export function AdrNode(props: NodeProps) {
70
+ return <GraphNodeCard id={props.id} kind="adr" data={props.data as GraphNodeData} />;
71
+ }
72
+
73
+ export function SpecNode(props: NodeProps) {
74
+ return <GraphNodeCard id={props.id} kind="spec" data={props.data as GraphNodeData} />;
75
+ }
76
+
77
+ export const nodeTypes = {
78
+ adr: AdrNode,
79
+ spec: SpecNode,
80
+ };
@@ -0,0 +1,105 @@
1
+ ---
2
+ type ButtonVariant = "primary" | "secondary" | "ghost";
3
+ type ButtonSize = "sm" | "md";
4
+
5
+ interface Props {
6
+ variant?: ButtonVariant;
7
+ size?: ButtonSize;
8
+ as?: "a" | "button";
9
+ href?: string;
10
+ type?: "button" | "submit" | "reset";
11
+ hidden?: boolean;
12
+ class?: string;
13
+ [key: `data-${string}`]: string | number | boolean | undefined;
14
+ }
15
+
16
+ const {
17
+ variant = "secondary",
18
+ size = "md",
19
+ as = "button",
20
+ href,
21
+ type = "button",
22
+ class: className,
23
+ ...rest
24
+ } = Astro.props as Props & Record<string, unknown>;
25
+
26
+ const classes = ["ui-button", `ui-button--${variant}`, `ui-button--${size}`, className]
27
+ .filter(Boolean)
28
+ .join(" ");
29
+ ---
30
+
31
+ {
32
+ as === "a" ? (
33
+ <a class={classes} href={href} {...rest}>
34
+ <slot />
35
+ </a>
36
+ ) : (
37
+ <button class={classes} type={type} {...rest}>
38
+ <slot />
39
+ </button>
40
+ )
41
+ }
42
+
43
+ <style>
44
+ .ui-button[hidden] {
45
+ display: none;
46
+ }
47
+
48
+ .ui-button {
49
+ display: inline-flex;
50
+ flex: none;
51
+ align-items: center;
52
+ justify-content: center;
53
+ gap: var(--space-2);
54
+ padding: var(--space-2) var(--space-3-5);
55
+ border: 1px solid var(--color-text);
56
+ border-radius: 0;
57
+ background: transparent;
58
+ color: var(--color-text);
59
+ font-family: var(--font-sans);
60
+ font-size: 11px;
61
+ font-weight: 500;
62
+ letter-spacing: 0.12em;
63
+ text-transform: uppercase;
64
+ text-decoration: none;
65
+ cursor: pointer;
66
+ transition:
67
+ background var(--duration-hover) var(--ease-out),
68
+ color var(--duration-hover) var(--ease-out),
69
+ transform var(--duration-press) var(--ease-out);
70
+ }
71
+
72
+ .ui-button:hover {
73
+ background: var(--color-text);
74
+ color: var(--color-bg);
75
+ }
76
+
77
+ .ui-button:active {
78
+ transform: scale(0.97);
79
+ }
80
+
81
+ .ui-button--sm {
82
+ padding: var(--space-1-5) var(--space-3);
83
+ font-size: 10px;
84
+ }
85
+
86
+ .ui-button--primary {
87
+ background: var(--color-accent);
88
+ border-color: var(--color-accent);
89
+ color: var(--color-accent-ink);
90
+ }
91
+
92
+ .ui-button--primary:hover {
93
+ background: color-mix(in srgb, var(--color-accent) 85%, var(--color-text));
94
+ color: var(--color-accent-ink);
95
+ }
96
+
97
+ .ui-button--ghost {
98
+ border-color: transparent;
99
+ }
100
+
101
+ .ui-button--ghost:hover {
102
+ background: transparent;
103
+ color: var(--color-accent);
104
+ }
105
+ </style>
@@ -0,0 +1,147 @@
1
+ ---
2
+ interface Props {
3
+ as?: "a" | "span";
4
+ href?: string;
5
+ status?: string;
6
+ kind?: string;
7
+ interactive?: boolean;
8
+ class?: string;
9
+ [key: `data-${string}`]: string | number | boolean | undefined;
10
+ }
11
+
12
+ const {
13
+ as = "span",
14
+ href,
15
+ status,
16
+ kind,
17
+ interactive,
18
+ class: className,
19
+ ...rest
20
+ } = Astro.props as Props & Record<string, unknown>;
21
+
22
+ const isInteractive = interactive ?? as === "a";
23
+ const classes = [
24
+ "ui-chip",
25
+ isInteractive ? "ui-chip--interactive" : "",
26
+ status ? `ui-chip--status` : "",
27
+ kind ? `ui-chip--kind` : "",
28
+ className,
29
+ ]
30
+ .filter(Boolean)
31
+ .join(" ");
32
+
33
+ const dataProps: Record<string, string> = {};
34
+ if (status) dataProps["data-status"] = status;
35
+ if (kind) dataProps["data-kind"] = kind;
36
+ ---
37
+
38
+ {
39
+ as === "a" ? (
40
+ <a class={classes} href={href} {...dataProps} {...rest}>
41
+ <slot />
42
+ </a>
43
+ ) : (
44
+ <span class={classes} {...dataProps} {...rest}>
45
+ <slot />
46
+ </span>
47
+ )
48
+ }
49
+
50
+ <style>
51
+ /* Base / interactive chip — used for external references */
52
+ .ui-chip {
53
+ display: inline-flex;
54
+ align-items: center;
55
+ padding: var(--space-1) var(--space-2);
56
+ border: 1px solid var(--color-border);
57
+ background: transparent;
58
+ color: var(--color-text-muted);
59
+ font-family: var(--font-sans);
60
+ font-size: 12px;
61
+ text-decoration: none;
62
+ overflow-wrap: anywhere;
63
+ }
64
+
65
+ .ui-chip--interactive {
66
+ transition:
67
+ border-color var(--duration-hover) var(--ease-out),
68
+ color var(--duration-hover) var(--ease-out);
69
+ }
70
+
71
+ .ui-chip--interactive:hover {
72
+ border-color: var(--color-accent);
73
+ color: var(--color-accent);
74
+ }
75
+
76
+ /* Kind badge — ADR = solid ink, SPEC = outlined ink (read as siblings) */
77
+ .ui-chip--kind {
78
+ padding: 2px 5px;
79
+ border: 0;
80
+ background: var(--color-text);
81
+ color: var(--color-bg);
82
+ font-family: var(--font-sans);
83
+ font-size: 9px;
84
+ font-weight: 600;
85
+ letter-spacing: 0.14em;
86
+ text-transform: uppercase;
87
+ }
88
+
89
+ .ui-chip--kind[data-kind="spec"] {
90
+ padding: 1px var(--space-1);
91
+ background: transparent;
92
+ color: var(--color-text);
93
+ box-shadow: inset 0 0 0 1px var(--color-text);
94
+ }
95
+
96
+ /* Status — colored ink + leading dot, no chrome */
97
+ .ui-chip--status {
98
+ padding: 0;
99
+ border: 0;
100
+ background: transparent;
101
+ gap: var(--space-1-5);
102
+ font-family: var(--font-sans);
103
+ font-size: 11px;
104
+ font-weight: 500;
105
+ letter-spacing: 0.1em;
106
+ text-transform: uppercase;
107
+ }
108
+
109
+ .ui-chip--status::before {
110
+ content: "";
111
+ width: 7px;
112
+ height: 7px;
113
+ border-radius: 50%;
114
+ background: currentColor;
115
+ }
116
+
117
+ .ui-chip--status[data-status="proposed"] {
118
+ color: var(--status-proposed-fg);
119
+ }
120
+ .ui-chip--status[data-status="accepted"] {
121
+ color: var(--status-accepted-fg);
122
+ }
123
+ .ui-chip--status[data-status="rejected"] {
124
+ color: var(--status-rejected-fg);
125
+ }
126
+ .ui-chip--status[data-status="deprecated"] {
127
+ color: var(--status-deprecated-fg);
128
+ }
129
+ .ui-chip--status[data-status="superseded"] {
130
+ color: var(--status-superseded-fg);
131
+ }
132
+ .ui-chip--status[data-status="draft"] {
133
+ color: var(--status-draft-fg);
134
+ }
135
+ .ui-chip--status[data-status="active"] {
136
+ color: var(--status-active-fg);
137
+ }
138
+ .ui-chip--status[data-status="paused"] {
139
+ color: var(--status-paused-fg);
140
+ }
141
+ .ui-chip--status[data-status="implemented"] {
142
+ color: var(--status-implemented-fg);
143
+ }
144
+ .ui-chip--status[data-status="obsolete"] {
145
+ color: var(--status-obsolete-fg);
146
+ }
147
+ </style>
@@ -0,0 +1,29 @@
1
+ ---
2
+ interface Props {
3
+ label: string;
4
+ class?: string;
5
+ }
6
+
7
+ const { label, class: className } = Astro.props;
8
+ const classes = ["ui-field", className].filter(Boolean).join(" ");
9
+ ---
10
+
11
+ <label class={classes}>
12
+ <span class="ui-field__label">{label}</span>
13
+ <slot />
14
+ </label>
15
+
16
+ <style>
17
+ .ui-field {
18
+ display: flex;
19
+ flex-direction: column;
20
+ gap: var(--space-1);
21
+ min-width: 0;
22
+ font-family: var(--font-sans);
23
+ font-size: 10px;
24
+ letter-spacing: 0.16em;
25
+ text-transform: uppercase;
26
+ color: var(--color-text-muted);
27
+ font-weight: 500;
28
+ }
29
+ </style>
@@ -0,0 +1,34 @@
1
+ ---
2
+ import type { HTMLAttributes } from "astro/types";
3
+
4
+ type Props = HTMLAttributes<"input">;
5
+
6
+ const { class: className, type = "text", ...rest } = Astro.props;
7
+ const classes = ["ui-input", className].filter(Boolean).join(" ");
8
+ ---
9
+
10
+ <input class={classes} type={type} {...rest} />
11
+
12
+ <style>
13
+ .ui-input {
14
+ width: 100%;
15
+ padding: var(--space-1-5) 0;
16
+ box-sizing: border-box;
17
+ border: 0;
18
+ border-bottom: 1px solid var(--color-border);
19
+ background: transparent;
20
+ color: var(--color-text);
21
+ font-family: var(--font-serif);
22
+ font-size: 16px;
23
+ outline: none;
24
+ }
25
+
26
+ .ui-input::placeholder {
27
+ color: var(--color-text-muted);
28
+ font-style: italic;
29
+ }
30
+
31
+ .ui-input:focus-visible {
32
+ border-bottom-color: var(--color-accent);
33
+ }
34
+ </style>
@@ -0,0 +1,71 @@
1
+ ---
2
+ type PillTone = "neutral" | "success" | "danger" | "warning";
3
+
4
+ interface Props {
5
+ as?: "a" | "span";
6
+ href?: string;
7
+ tone?: PillTone;
8
+ class?: string;
9
+ [key: `data-${string}`]: string | number | boolean | undefined;
10
+ }
11
+
12
+ const {
13
+ as = "span",
14
+ href,
15
+ tone = "neutral",
16
+ class: className,
17
+ ...rest
18
+ } = Astro.props as Props & Record<string, unknown>;
19
+
20
+ const classes = ["ui-pill", `ui-pill--${tone}`, className].filter(Boolean).join(" ");
21
+ ---
22
+
23
+ {
24
+ as === "a" ? (
25
+ <a class={classes} href={href} {...rest}>
26
+ <slot />
27
+ </a>
28
+ ) : (
29
+ <span class={classes} {...rest}>
30
+ <slot />
31
+ </span>
32
+ )
33
+ }
34
+
35
+ <style>
36
+ .ui-pill {
37
+ display: inline-flex;
38
+ flex: none;
39
+ align-items: center;
40
+ gap: var(--space-2);
41
+ padding: 0;
42
+ background: transparent;
43
+ color: var(--color-text-muted);
44
+ font-family: var(--font-sans);
45
+ font-size: 11px;
46
+ font-weight: 500;
47
+ letter-spacing: 0.12em;
48
+ text-transform: uppercase;
49
+ text-decoration: none;
50
+ }
51
+
52
+ .ui-pill::before {
53
+ content: "";
54
+ width: 7px;
55
+ height: 7px;
56
+ border-radius: 50%;
57
+ background: currentColor;
58
+ }
59
+
60
+ .ui-pill--success {
61
+ color: var(--status-active-fg);
62
+ }
63
+
64
+ .ui-pill--danger {
65
+ color: var(--color-danger);
66
+ }
67
+
68
+ .ui-pill--warning {
69
+ color: var(--color-warning);
70
+ }
71
+ </style>
@@ -0,0 +1,105 @@
1
+ ---
2
+ interface Option {
3
+ value: string;
4
+ label: string;
5
+ }
6
+
7
+ interface Props {
8
+ name: string;
9
+ legend: string;
10
+ options: Option[];
11
+ value?: string;
12
+ dataAttr?: string;
13
+ class?: string;
14
+ }
15
+
16
+ const { name, legend, options, value, dataAttr, class: className } = Astro.props;
17
+ const classes = ["ui-segmented", className].filter(Boolean).join(" ");
18
+ const dataProps: Record<string, string> = dataAttr ? { [dataAttr]: "" } : {};
19
+ ---
20
+
21
+ <fieldset class={classes} aria-label={legend}>
22
+ <legend>{legend}</legend>
23
+ {
24
+ options.map((option) => (
25
+ <label>
26
+ <input
27
+ type="radio"
28
+ name={name}
29
+ value={option.value}
30
+ checked={value ? value === option.value : undefined}
31
+ {...dataProps}
32
+ />
33
+ <span>{option.label}</span>
34
+ </label>
35
+ ))
36
+ }
37
+ </fieldset>
38
+
39
+ <style>
40
+ .ui-segmented {
41
+ display: flex;
42
+ align-self: end;
43
+ gap: 0;
44
+ margin: 0;
45
+ padding: 0;
46
+ border: 1px solid var(--color-text);
47
+ }
48
+
49
+ .ui-segmented legend {
50
+ position: absolute;
51
+ width: 1px;
52
+ height: 1px;
53
+ padding: 0;
54
+ margin: -1px;
55
+ overflow: hidden;
56
+ clip: rect(0, 0, 0, 0);
57
+ white-space: nowrap;
58
+ border: 0;
59
+ }
60
+
61
+ .ui-segmented label {
62
+ display: inline-flex;
63
+ }
64
+
65
+ .ui-segmented input {
66
+ position: absolute;
67
+ width: 1px;
68
+ height: 1px;
69
+ opacity: 0;
70
+ }
71
+
72
+ .ui-segmented span {
73
+ display: inline-flex;
74
+ align-items: center;
75
+ justify-content: center;
76
+ padding: var(--space-2) var(--space-3);
77
+ background: transparent;
78
+ color: var(--color-text);
79
+ font-family: var(--font-sans);
80
+ font-size: 11px;
81
+ letter-spacing: 0.12em;
82
+ text-transform: uppercase;
83
+ font-weight: 500;
84
+ line-height: 1;
85
+ cursor: pointer;
86
+ box-sizing: border-box;
87
+ transition:
88
+ background var(--duration-hover) var(--ease-out),
89
+ color var(--duration-hover) var(--ease-out);
90
+ }
91
+
92
+ .ui-segmented label:active span {
93
+ transform: scale(0.97);
94
+ }
95
+
96
+ .ui-segmented input:checked + span {
97
+ background: var(--color-text);
98
+ color: var(--color-bg);
99
+ }
100
+
101
+ .ui-segmented input:focus-visible + span {
102
+ outline: 2px solid var(--color-accent);
103
+ outline-offset: -2px;
104
+ }
105
+ </style>
@@ -0,0 +1,41 @@
1
+ ---
2
+ interface Props {
3
+ name?: string;
4
+ value?: string;
5
+ class?: string;
6
+ [key: `data-${string}`]: string | number | boolean | undefined;
7
+ }
8
+
9
+ const { class: className, ...rest } = Astro.props as Props & Record<string, unknown>;
10
+ const classes = ["ui-select", className].filter(Boolean).join(" ");
11
+ ---
12
+
13
+ <select class={classes} {...rest}>
14
+ <slot />
15
+ </select>
16
+
17
+ <style>
18
+ .ui-select {
19
+ width: 100%;
20
+ padding: var(--space-1-5) var(--space-4) var(--space-1-5) 0;
21
+ box-sizing: border-box;
22
+ border: 0;
23
+ border-bottom: 1px solid var(--color-border);
24
+ background: transparent;
25
+ color: var(--color-text);
26
+ font-family: var(--font-sans);
27
+ font-size: 13px;
28
+ outline: none;
29
+ appearance: none;
30
+ background-image: linear-gradient(45deg, transparent 50%, var(--color-text-muted) 50%),
31
+ linear-gradient(135deg, var(--color-text-muted) 50%, transparent 50%);
32
+ background-position: calc(100% - 6px) 12px, calc(100% - 2px) 12px;
33
+ background-size: 4px 4px;
34
+ background-repeat: no-repeat;
35
+ cursor: pointer;
36
+ }
37
+
38
+ .ui-select:focus-visible {
39
+ border-bottom-color: var(--color-accent);
40
+ }
41
+ </style>