@linhey/rhine-design 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/CONTRIBUTING.md +6 -0
- package/README.md +70 -26
- package/components/Actions.tsx +6 -4
- package/components/COMMON_COMPONENTS.md +5 -1
- package/components/Data.tsx +8 -8
- package/components/FormWorkflow.tsx +25 -0
- package/components/Forms.tsx +11 -11
- package/components/Layout.tsx +4 -4
- package/components/Logo.tsx +7 -7
- package/components/MOTION.md +7 -3
- package/components/Navigation.tsx +42 -0
- package/components/Overlays.tsx +17 -11
- package/components/README.md +19 -11
- package/components/SPACING.md +82 -0
- package/components/Select.tsx +5 -5
- package/components/StatusTimeline.tsx +29 -0
- package/components/TYPOGRAPHY.md +35 -3
- package/components/Toast.tsx +2 -2
- package/components/Toolbar.tsx +3 -3
- package/components/Typography.tsx +1 -1
- package/components/common.css +78 -69
- package/components/components.tsx +28 -28
- package/components/index.ts +7 -1
- package/components/patterns.css +39 -0
- package/components/styles.css +105 -101
- package/components/theme-arknights.css +301 -0
- package/components/theme-neo-brutalism.css +84 -0
- package/components/theme.tsx +58 -0
- package/components/timeline.css +23 -0
- package/components/tokens.css +61 -56
- package/components/typography.css +31 -31
- package/docs/DESIGN_SYSTEM.md +24 -13
- package/docs/DESIGN_SYSTEM_AUDIT.md +4 -4
- package/docs/INTERACTION_PATTERNS.md +129 -0
- package/docs/REFERENCE_RESEARCH.md +36 -0
- package/docs/THEME_NEO_BRUTALISM.md +37 -0
- package/docs/THEMING.md +104 -0
- package/package.json +5 -1
- package/preview/CommonGallery.tsx +8 -8
- package/preview/GalleryShell.tsx +106 -0
- package/preview/LineHeightGallery.tsx +27 -0
- package/preview/MotionGallery.tsx +9 -5
- package/preview/PatternGallery.tsx +52 -0
- package/preview/SpacingGallery.tsx +32 -0
- package/preview/StartupGallery.tsx +1 -1
- package/preview/SubscriptionGallery.tsx +84 -0
- package/preview/brand-assembly.css +8 -8
- package/preview/line-height.css +2 -0
- package/preview/main.tsx +39 -25
- package/preview/motion.css +14 -14
- package/preview/patterns.css +7 -0
- package/preview/preview.css +15 -21
- package/preview/shell.css +35 -0
- package/preview/spacing.css +2 -0
- package/preview/startup.css +15 -15
- package/preview/subscriptions.css +27 -0
- package/preview/theme-arknights.css +234 -0
- package/preview/theme-neo-brutalism.css +11 -0
- package/scripts/check-build.mjs +1 -1
- package/skills/rhine-design/SKILL.md +6 -4
- package/skills/rhine-design/references/API_MAP.md +7 -3
- package/skills/rhine-design/references/TASK_MAP.md +4 -1
- package/skills/rhine-design/references/integration.md +6 -4
- package/tests/CommonComponents.test.tsx +1 -1
- package/tests/GalleryShell.test.tsx +98 -0
- package/tests/Patterns.test.tsx +75 -0
- package/tests/StartupGallery.test.tsx +3 -3
- package/tests/StatusTimeline.test.tsx +15 -0
- package/tests/Theme.test.tsx +112 -0
|
@@ -10,35 +10,35 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
|
10
10
|
}
|
|
11
11
|
export function Button({ variant = 'primary', busy = false, busyLabel = '处理中', disabled, className, children, onClick, type = 'button', ...props }: ButtonProps) {
|
|
12
12
|
return <button {...props} type={type} disabled={disabled} aria-disabled={busy || disabled || undefined}
|
|
13
|
-
aria-busy={busy || undefined} className={classes('
|
|
13
|
+
aria-busy={busy || undefined} className={classes('rhine-button', `rhine-button--${variant}`, className)}
|
|
14
14
|
onClick={event => { if (busy) { event.preventDefault(); return; } onClick?.(event); }}>
|
|
15
|
-
<span className={busy ? '
|
|
16
|
-
{busy && <span className="
|
|
15
|
+
<span className={busy ? 'rhine-button__ghost' : undefined}>{children}</span>
|
|
16
|
+
{busy && <span className="rhine-button__busy"><span className="rhine-spinner" aria-hidden="true" /><span className="rhine-sr-only">{busyLabel}</span></span>}
|
|
17
17
|
</button>;
|
|
18
18
|
}
|
|
19
19
|
export function Badge({ tone = 'neutral', children }: { tone?: Tone; children: ReactNode }) {
|
|
20
|
-
return <span className={`
|
|
20
|
+
return <span className={`rhine-badge rhine-tone-${tone}`}>{children}</span>;
|
|
21
21
|
}
|
|
22
22
|
export function Surface({ className, ...props }: HTMLAttributes<HTMLDivElement>) {
|
|
23
|
-
return <div {...props} className={classes('
|
|
23
|
+
return <div {...props} className={classes('rhine-surface', className)} />;
|
|
24
24
|
}
|
|
25
25
|
export function SelectionCard({ label, selected, onSelectedChange, disabled = false, children }: {
|
|
26
26
|
label: string; selected: boolean; onSelectedChange: (value: boolean) => void; disabled?: boolean; children: ReactNode;
|
|
27
27
|
}) {
|
|
28
|
-
return <label className="
|
|
29
|
-
<span className="
|
|
30
|
-
<span className="
|
|
28
|
+
return <label className="rhine-selection" data-selected={selected} data-disabled={disabled}>
|
|
29
|
+
<span className="rhine-selection__heading"><strong>{label}</strong><input type="checkbox" aria-label={label} checked={selected} disabled={disabled} onChange={event => onSelectedChange(event.target.checked)} /></span>
|
|
30
|
+
<span className="rhine-selection__content">{children}</span>
|
|
31
31
|
</label>;
|
|
32
32
|
}
|
|
33
33
|
export function TextField({ label, hint, error, id: providedId, className, ...props }: InputHTMLAttributes<HTMLInputElement> & { label: string; hint?: string; error?: string }) {
|
|
34
34
|
const generatedId = useId();
|
|
35
35
|
const id = providedId || generatedId;
|
|
36
36
|
const describedBy = [props['aria-describedby'], hint && `${id}-hint`, error && `${id}-error`].filter(Boolean).join(' ');
|
|
37
|
-
return <div className={classes('
|
|
37
|
+
return <div className={classes('rhine-field', className)}>
|
|
38
38
|
<label htmlFor={id}>{label}</label>
|
|
39
39
|
<input {...props} id={id} aria-invalid={error ? true : props['aria-invalid']} aria-describedby={describedBy || undefined} />
|
|
40
|
-
{hint && <span id={`${id}-hint`} className="
|
|
41
|
-
{error && <span id={`${id}-error`} className="
|
|
40
|
+
{hint && <span id={`${id}-hint`} className="rhine-field__hint">{hint}</span>}
|
|
41
|
+
{error && <span id={`${id}-error`} className="rhine-field__error" role="alert">{error}</span>}
|
|
42
42
|
</div>;
|
|
43
43
|
}
|
|
44
44
|
export interface Choice { value: string; label: string; disabled?: boolean }
|
|
@@ -46,8 +46,8 @@ export interface TabItem extends Choice { content: ReactNode }
|
|
|
46
46
|
export function Tabs({ label, items, value, onValueChange }: { label: string; items: TabItem[]; value: string; onValueChange: (value: string) => void }) {
|
|
47
47
|
const id = useId();
|
|
48
48
|
const buttons = useRef<(HTMLButtonElement | null)[]>([]);
|
|
49
|
-
return <div className="
|
|
50
|
-
<div role="tablist" aria-label={label} className="
|
|
49
|
+
return <div className="rhine-tabs">
|
|
50
|
+
<div role="tablist" aria-label={label} className="rhine-tabs__list" onKeyDown={event => {
|
|
51
51
|
if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) return;
|
|
52
52
|
const available = items.map((item, index) => ({ ...item, index })).filter(item => !item.disabled);
|
|
53
53
|
if (!available.length) return;
|
|
@@ -61,36 +61,36 @@ export function Tabs({ label, items, value, onValueChange }: { label: string; it
|
|
|
61
61
|
aria-selected={value === item.value} tabIndex={value === item.value ? 0 : -1} onClick={() => onValueChange(item.value)}>{item.label}</button>)}
|
|
62
62
|
</div>
|
|
63
63
|
{items.map((item, index) => <div key={item.value} role="tabpanel" id={`${id}-panel-${index}`} aria-labelledby={`${id}-tab-${index}`}
|
|
64
|
-
hidden={value !== item.value} tabIndex={0} className="
|
|
64
|
+
hidden={value !== item.value} tabIndex={0} className="rhine-tabs__panel">{item.content}</div>)}
|
|
65
65
|
</div>;
|
|
66
66
|
}
|
|
67
67
|
export function SegmentedControl({ label, items, value, onValueChange }: { label: string; items: Choice[]; value: string; onValueChange: (value: string) => void }) {
|
|
68
68
|
const name = useId();
|
|
69
|
-
return <fieldset className="
|
|
70
|
-
{items.map(item => <label key={item.value} className="
|
|
69
|
+
return <fieldset className="rhine-segmented"><legend className="rhine-sr-only">{label}</legend>
|
|
70
|
+
{items.map(item => <label key={item.value} className="rhine-segmented__option">
|
|
71
71
|
<input type="radio" name={name} value={item.value} checked={value === item.value} disabled={item.disabled} onChange={() => onValueChange(item.value)} />
|
|
72
72
|
<span>{item.label}</span>
|
|
73
73
|
</label>)}
|
|
74
74
|
</fieldset>;
|
|
75
75
|
}
|
|
76
76
|
export function Notice({ tone = 'neutral', title, children, action }: { tone?: Tone; title: string; children?: ReactNode; action?: ReactNode }) {
|
|
77
|
-
return <div className={`
|
|
78
|
-
<div><strong>{title}</strong>{children && <div className="
|
|
77
|
+
return <div className={`rhine-notice rhine-tone-${tone}`} role={tone === 'error' ? 'alert' : 'status'}>
|
|
78
|
+
<div><strong>{title}</strong>{children && <div className="rhine-notice__body">{children}</div>}</div>{action}
|
|
79
79
|
</div>;
|
|
80
80
|
}
|
|
81
81
|
export function Progress({ label, value, valueText }: { label: string; value: number | null; valueText?: string }) {
|
|
82
82
|
const determinate = value !== null && Number.isFinite(value);
|
|
83
83
|
const amount = determinate ? Math.max(0, Math.min(100, value)) : null;
|
|
84
|
-
return <div className="
|
|
85
|
-
<div className="
|
|
86
|
-
<div className="
|
|
84
|
+
return <div className="rhine-progress">
|
|
85
|
+
<div className="rhine-progress__label"><span>{label}</span><strong>{valueText ?? (amount === null ? '处理中' : `${amount}%`)}</strong></div>
|
|
86
|
+
<div className="rhine-progress__track" role="progressbar" aria-label={label} aria-valuemin={0} aria-valuemax={100}
|
|
87
87
|
aria-valuenow={amount ?? undefined} aria-valuetext={valueText ?? (amount === null ? '进度未知' : `${amount}%`)} data-indeterminate={amount === null}>
|
|
88
88
|
<span style={amount === null ? undefined : { transform: `scaleX(${amount / 100})` }} />
|
|
89
89
|
</div>
|
|
90
90
|
</div>;
|
|
91
91
|
}
|
|
92
92
|
export function Skeleton({ label = '正在加载', lines = 3 }: { label?: string; lines?: number }) {
|
|
93
|
-
return <div className="
|
|
93
|
+
return <div className="rhine-skeleton" role="status"><span className="rhine-sr-only">{label}</span>
|
|
94
94
|
{Array.from({ length: Math.max(1, Math.min(8, Math.floor(lines) || 1)) }, (_, index) => <span aria-hidden="true" key={index} />)}
|
|
95
95
|
</div>;
|
|
96
96
|
}
|
|
@@ -111,7 +111,7 @@ export function Disclosure({ title, children, defaultOpen = false }: { title: st
|
|
|
111
111
|
}, []);
|
|
112
112
|
useEffect(() => {
|
|
113
113
|
if (!animation.current || !node.current) return;
|
|
114
|
-
if (parseFloat(getComputedStyle(node.current).getPropertyValue('--
|
|
114
|
+
if (parseFloat(getComputedStyle(node.current).getPropertyValue('--rhine-duration-notice')) !== 0) return;
|
|
115
115
|
animation.current.cancel(); animation.current = null;
|
|
116
116
|
node.current.open = intent.current; setOpen(intent.current);
|
|
117
117
|
});
|
|
@@ -130,22 +130,22 @@ export function Disclosure({ title, children, defaultOpen = false }: { title: st
|
|
|
130
130
|
const reduced = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
|
|
131
131
|
if (reduced || !el.animate) { el.open = expanded; setOpen(expanded); return; }
|
|
132
132
|
const style = getComputedStyle(el);
|
|
133
|
-
const parsedDuration = parseFloat(style.getPropertyValue('--
|
|
133
|
+
const parsedDuration = parseFloat(style.getPropertyValue('--rhine-duration-notice'));
|
|
134
134
|
const duration = Number.isFinite(parsedDuration) ? parsedDuration : 200;
|
|
135
135
|
if (duration === 0) { el.open = expanded; setOpen(expanded); return; }
|
|
136
136
|
const to = expanded ? el.getBoundingClientRect().height : summary.getBoundingClientRect().height + parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth);
|
|
137
|
-
const next = el.animate([{ height: `${from}px` }, { height: `${to}px` }], { duration, easing: style.getPropertyValue(expanded ? '--
|
|
137
|
+
const next = el.animate([{ height: `${from}px` }, { height: `${to}px` }], { duration, easing: style.getPropertyValue(expanded ? '--rhine-ease-standard' : '--rhine-ease-exit').trim() || 'ease' });
|
|
138
138
|
animation.current = next;
|
|
139
139
|
next.onfinish = () => {
|
|
140
140
|
if (animation.current !== next) return;
|
|
141
141
|
el.open = expanded; setOpen(expanded); animation.current = null;
|
|
142
142
|
};
|
|
143
143
|
}
|
|
144
|
-
return <details ref={node} open={open} data-expanded={open} className="
|
|
144
|
+
return <details ref={node} open={open} data-expanded={open} className="rhine-disclosure">
|
|
145
145
|
<summary onClick={event => { event.preventDefault(); toggle(); }}>{title}<span aria-hidden="true">+</span></summary>
|
|
146
|
-
<div className="
|
|
146
|
+
<div className="rhine-disclosure__content">{children}</div>
|
|
147
147
|
</details>;
|
|
148
148
|
}
|
|
149
149
|
export function EmptyState({ title, description, action }: { title: string; description: string; action?: ReactNode }) {
|
|
150
|
-
return <div className="
|
|
150
|
+
return <div className="rhine-empty"><strong>{title}</strong><p>{description}</p>{action}</div>;
|
|
151
151
|
}
|
package/components/index.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import './styles.css';
|
|
2
|
+
export { StatusTimeline, TimelineLegend } from './StatusTimeline';
|
|
3
|
+
export type { TimelineSegment, TimelineStatus } from './StatusTimeline';
|
|
2
4
|
|
|
3
5
|
export { Logo } from './Logo';
|
|
4
6
|
export type { LogoProps } from './Logo';
|
|
@@ -16,9 +18,13 @@ export { Metric, KeyValue, DataTable, Pagination } from './Data';
|
|
|
16
18
|
export type { DataColumn, DataTableProps, TableSort } from './Data';
|
|
17
19
|
export { Checkbox, RadioGroup, Switch, Textarea, SearchField, Combobox } from './Forms';
|
|
18
20
|
export type { ComboboxProps } from './Forms';
|
|
19
|
-
export { Dialog, ConfirmDialog, Popover, Menu, Tooltip } from './Overlays';
|
|
21
|
+
export { Dialog, Drawer, ConfirmDialog, Popover, Menu, Tooltip } from './Overlays';
|
|
20
22
|
export type { DialogProps, MenuItem } from './Overlays';
|
|
21
23
|
export { Toast } from './Toast';
|
|
24
|
+
export { SideNav, Breadcrumbs, Steps } from './Navigation';
|
|
25
|
+
export type { NavItem, NavGroup, SideNavProps, BreadcrumbItem, BreadcrumbsProps, StepItem, StepsProps } from './Navigation';
|
|
26
|
+
export { Fieldset, ErrorSummary } from './FormWorkflow';
|
|
27
|
+
export type { FieldsetProps, FieldError, ErrorSummaryProps } from './FormWorkflow';
|
|
22
28
|
|
|
23
29
|
export { Button, Badge, Surface, SelectionCard, TextField, Tabs, SegmentedControl, Notice, Progress, Skeleton, Disclosure, EmptyState } from './components';
|
|
24
30
|
export type { ButtonProps, Tone, Choice, TabItem } from './components';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
.rhine-side-nav__group { margin-top:var(--rhine-space-4); }
|
|
2
|
+
.rhine-side-nav__group:first-child { margin-top:0; }
|
|
3
|
+
.rhine-side-nav__group>p { margin:0 var(--rhine-space-3) var(--rhine-space-2); font-size:var(--rhine-text-caption); line-height:var(--rhine-leading-caption); color:var(--rhine-muted); }
|
|
4
|
+
.rhine-side-nav ul,.rhine-breadcrumbs ol,.rhine-steps { margin:0; padding:0; list-style:none; }
|
|
5
|
+
.rhine-side-nav ul { display:grid; gap:var(--rhine-space-1); }
|
|
6
|
+
.rhine-side-nav a { min-height:44px; padding:var(--rhine-space-2) var(--rhine-space-3); display:flex; align-items:center; gap:var(--rhine-space-3); border-inline-start:2px solid transparent; border-radius:var(--rhine-radius-control); text-decoration:none; font-size:var(--rhine-text-body); line-height:var(--rhine-leading-body); color:var(--rhine-ink); transition:background-color var(--rhine-duration-hover) var(--rhine-ease-standard); overflow-wrap:anywhere; }
|
|
7
|
+
.rhine-side-nav__label { min-width:0; }
|
|
8
|
+
.rhine-side-nav .rhine-icon { flex-shrink:0; color:var(--rhine-muted); }
|
|
9
|
+
.rhine-side-nav a:hover { background:var(--rhine-bg); }
|
|
10
|
+
.rhine-side-nav a[aria-current=page] { background:var(--rhine-selected); border-color:var(--rhine-green); color:var(--rhine-green); font-weight:600; }
|
|
11
|
+
.rhine-side-nav a[aria-current=page] .rhine-icon { color:var(--rhine-green); }
|
|
12
|
+
.rhine-breadcrumbs ol { display:flex; flex-wrap:wrap; align-items:center; gap:4px 12px; }
|
|
13
|
+
.rhine-breadcrumbs li { display:flex; align-items:center; gap:12px; min-width:0; overflow-wrap:anywhere; font-size:var(--rhine-text-caption); line-height:var(--rhine-leading-caption); color:var(--rhine-muted); }
|
|
14
|
+
.rhine-breadcrumbs a { display:inline-flex; align-items:center; min-height:44px; color:var(--rhine-green); text-underline-offset:3px; }
|
|
15
|
+
.rhine-breadcrumbs [aria-current=page] { color:var(--rhine-ink); font-weight:600; }
|
|
16
|
+
.rhine-breadcrumbs__separator { color:var(--rhine-control); }
|
|
17
|
+
.rhine-steps { display:grid; gap:0; }
|
|
18
|
+
.rhine-steps li { border-inline-start:2px solid var(--rhine-line); }
|
|
19
|
+
.rhine-steps li>div,.rhine-steps button { display:flex; gap:16px; align-items:flex-start; padding:16px; width:100%; text-align:start; color:var(--rhine-muted); }
|
|
20
|
+
.rhine-steps button { border:0; background:none; font:inherit; cursor:pointer; }
|
|
21
|
+
.rhine-steps button:hover { background:var(--rhine-selected); }
|
|
22
|
+
.rhine-steps strong,.rhine-steps small { display:block; overflow-wrap:anywhere; }
|
|
23
|
+
.rhine-steps small { margin-top:4px; font-size:12px; font-weight:400; }
|
|
24
|
+
.rhine-steps__number { flex:0 0 28px; font:14px/24px var(--rhine-mono); }
|
|
25
|
+
.rhine-steps li[data-state=current],.rhine-steps li[data-state=complete] { border-color:var(--rhine-green); }
|
|
26
|
+
.rhine-steps li[data-state=current]>div { background:var(--rhine-selected); color:var(--rhine-green); animation:rhine-pattern-enter var(--rhine-duration-panel) var(--rhine-ease-standard); }
|
|
27
|
+
.rhine-steps li[data-state=error] { border-color:var(--rhine-error); }
|
|
28
|
+
.rhine-steps li[data-state=error]>div { color:var(--rhine-error); background:var(--rhine-error-bg); }
|
|
29
|
+
.rhine-fieldset { min-width:0; margin:0; padding:20px; border:1px solid var(--rhine-line); border-radius:var(--rhine-radius-card); }
|
|
30
|
+
.rhine-fieldset legend { padding:0 8px; font-weight:600; }
|
|
31
|
+
.rhine-fieldset>p { margin:0 0 20px; color:var(--rhine-muted); font-size:12px; }
|
|
32
|
+
.rhine-fieldset__body { display:grid; gap:16px; }
|
|
33
|
+
.rhine-error-summary { padding:20px; border:1px solid var(--rhine-error); border-inline-start:3px solid var(--rhine-error); background:var(--rhine-error-bg); }
|
|
34
|
+
.rhine-error-summary h3 { margin:0 0 8px; font-size:16px; }
|
|
35
|
+
.rhine-error-summary ul { padding-inline-start:20px; margin:0; }
|
|
36
|
+
.rhine-error-summary a { display:inline-block; padding:10px 0; color:var(--rhine-error); text-underline-offset:3px; overflow-wrap:anywhere; }
|
|
37
|
+
.rhine-dialog.rhine-drawer { inset-block:0; inset-inline-end:0; inset-inline-start:auto; width:min(440px,100%); max-height:100dvh; margin:0; transform:none; border-radius:0; animation:rhine-drawer-enter var(--rhine-duration-overlay) var(--rhine-ease-standard); }
|
|
38
|
+
@keyframes rhine-drawer-enter { from { opacity:0; transform:translateX(3px); } to { opacity:1; transform:translateX(0); } }
|
|
39
|
+
@keyframes rhine-pattern-enter { from { opacity:.65; transform:translateY(3px); } to { opacity:1; transform:translateY(0); } }
|
package/components/styles.css
CHANGED
|
@@ -1,109 +1,113 @@
|
|
|
1
1
|
@import './tokens.css';
|
|
2
2
|
@import './typography.css';
|
|
3
3
|
@import './common.css';
|
|
4
|
+
@import './patterns.css';
|
|
5
|
+
@import './timeline.css';
|
|
4
6
|
|
|
5
|
-
.
|
|
6
|
-
.
|
|
7
|
-
.
|
|
8
|
-
.
|
|
9
|
-
.
|
|
10
|
-
.
|
|
11
|
-
.
|
|
12
|
-
.
|
|
13
|
-
.
|
|
14
|
-
.
|
|
15
|
-
.
|
|
16
|
-
.
|
|
17
|
-
.
|
|
18
|
-
.
|
|
19
|
-
.
|
|
20
|
-
.
|
|
21
|
-
.
|
|
22
|
-
.
|
|
23
|
-
.
|
|
24
|
-
.
|
|
25
|
-
.
|
|
26
|
-
.
|
|
27
|
-
.
|
|
28
|
-
.
|
|
29
|
-
.
|
|
30
|
-
.
|
|
31
|
-
.
|
|
32
|
-
.
|
|
33
|
-
.
|
|
34
|
-
.
|
|
35
|
-
.
|
|
36
|
-
.
|
|
37
|
-
.
|
|
38
|
-
.
|
|
39
|
-
.
|
|
40
|
-
.
|
|
41
|
-
.
|
|
42
|
-
.
|
|
43
|
-
.
|
|
44
|
-
.
|
|
45
|
-
.
|
|
46
|
-
.
|
|
47
|
-
.
|
|
48
|
-
.
|
|
49
|
-
.
|
|
50
|
-
.
|
|
51
|
-
.
|
|
52
|
-
.
|
|
53
|
-
.
|
|
54
|
-
.
|
|
55
|
-
.
|
|
56
|
-
.
|
|
57
|
-
.
|
|
58
|
-
.
|
|
59
|
-
.
|
|
60
|
-
.
|
|
61
|
-
.
|
|
62
|
-
.
|
|
63
|
-
.
|
|
64
|
-
.
|
|
65
|
-
.
|
|
66
|
-
.
|
|
67
|
-
.
|
|
68
|
-
.
|
|
69
|
-
.
|
|
70
|
-
.
|
|
71
|
-
.
|
|
72
|
-
.
|
|
73
|
-
.
|
|
74
|
-
.
|
|
75
|
-
.
|
|
76
|
-
.
|
|
77
|
-
.
|
|
78
|
-
.
|
|
79
|
-
.
|
|
80
|
-
.
|
|
81
|
-
|
|
82
|
-
.
|
|
83
|
-
|
|
84
|
-
.
|
|
85
|
-
.
|
|
86
|
-
.
|
|
87
|
-
.
|
|
88
|
-
.
|
|
89
|
-
.
|
|
90
|
-
.
|
|
91
|
-
.
|
|
92
|
-
.
|
|
93
|
-
|
|
7
|
+
.rhine-theme { color:var(--rhine-ink); background:var(--rhine-bg); font:var(--rhine-text-body)/var(--rhine-leading-body) var(--rhine-font); font-synthesis:none; }
|
|
8
|
+
.rhine-theme[data-rhine-theme="rhine"] { color-scheme:light; }
|
|
9
|
+
.rhine-theme *, .rhine-theme *::before, .rhine-theme *::after { box-sizing:border-box; }
|
|
10
|
+
.rhine-theme [hidden] { display:none!important; }
|
|
11
|
+
.rhine-theme :is(button,input,select,textarea,summary,a,[tabindex]):focus-visible { outline:2px solid var(--rhine-green); outline-offset:3px; }
|
|
12
|
+
.rhine-sr-only { position:absolute!important; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip-path:inset(50%); white-space:nowrap; border:0; }
|
|
13
|
+
.rhine-button { position:relative; display:inline-flex; align-items:center; justify-content:center; min-height:44px; min-width:44px; padding:10px 16px; border:1px solid var(--rhine-ink); border-radius:var(--rhine-radius-control); font:var(--rhine-weight-label) var(--rhine-text-body)/var(--rhine-leading-body) var(--rhine-font); background:var(--rhine-ink); color:var(--rhine-surface); cursor:pointer; overflow-wrap:anywhere; transition:background-color var(--rhine-duration-hover) var(--rhine-ease-standard),border-color var(--rhine-duration-hover) var(--rhine-ease-standard),transform var(--rhine-duration-press) var(--rhine-ease-standard); }
|
|
14
|
+
.rhine-button:hover:not(:disabled):not([aria-disabled=true]) { background:var(--rhine-green); border-color:var(--rhine-green); }
|
|
15
|
+
.rhine-button:active:not(:disabled):not([aria-disabled=true]) { transform:translateY(1px); }
|
|
16
|
+
.rhine-button--secondary { background:var(--rhine-surface); color:var(--rhine-ink); border-color:var(--rhine-control); }
|
|
17
|
+
.rhine-button--quiet { background:transparent; color:var(--rhine-green); border-color:transparent; }
|
|
18
|
+
.rhine-button--secondary:hover:not(:disabled):not([aria-disabled=true]),.rhine-button--quiet:hover:not(:disabled):not([aria-disabled=true]) { background:var(--rhine-selected); }
|
|
19
|
+
.rhine-button--danger { background:var(--rhine-error); border-color:var(--rhine-error); }
|
|
20
|
+
.rhine-button--danger:hover:not(:disabled):not([aria-disabled=true]) { background:#81342e; border-color:#81342e; }
|
|
21
|
+
.rhine-button:disabled { background:var(--rhine-selected); border-color:var(--rhine-line); color:var(--rhine-muted); cursor:default; }
|
|
22
|
+
.rhine-button[aria-busy=true] { cursor:wait; }
|
|
23
|
+
.rhine-button__ghost { visibility:hidden; }
|
|
24
|
+
.rhine-button__busy { position:absolute; inset:0; display:grid; place-items:center; }
|
|
25
|
+
.rhine-spinner { width:18px; height:18px; border:2px solid currentColor; border-right-color:transparent; border-radius:50%; animation:rhine-spin var(--rhine-duration-loading) linear infinite; }
|
|
26
|
+
.rhine-tone-neutral { --rhine-tone:var(--rhine-muted); --rhine-tone-bg:var(--rhine-selected); }
|
|
27
|
+
.rhine-tone-success { --rhine-tone:var(--rhine-green); --rhine-tone-bg:var(--rhine-selected); }
|
|
28
|
+
.rhine-tone-warning { --rhine-tone:var(--rhine-warning); --rhine-tone-bg:var(--rhine-warning-bg); }
|
|
29
|
+
.rhine-tone-error { --rhine-tone:var(--rhine-error); --rhine-tone-bg:var(--rhine-error-bg); }
|
|
30
|
+
.rhine-badge { display:inline-block; padding:2px 8px; border-radius:2px; background:var(--rhine-tone-bg); color:var(--rhine-tone); font:500 12px/20px var(--rhine-font); }
|
|
31
|
+
.rhine-surface { padding:var(--rhine-space-6); border:1px solid var(--rhine-line); border-radius:var(--rhine-radius-card); background:var(--rhine-surface); min-width:0; }
|
|
32
|
+
.rhine-selection { display:block; border:1px solid var(--rhine-control); background:var(--rhine-surface); border-radius:var(--rhine-radius-card); padding:var(--rhine-space-6); cursor:pointer; min-width:0; transition:background-color var(--rhine-duration-hover) var(--rhine-ease-standard),border-color var(--rhine-duration-hover) var(--rhine-ease-standard),box-shadow var(--rhine-duration-hover) var(--rhine-ease-standard); }
|
|
33
|
+
.rhine-selection:hover:not([data-disabled=true]) { border-color:var(--rhine-green); background:#f4f7ef; }
|
|
34
|
+
.rhine-selection[data-selected=true] { border-color:var(--rhine-green); background:var(--rhine-selected); box-shadow:inset 0 0 0 1px var(--rhine-green); }
|
|
35
|
+
.rhine-selection[data-disabled=true] { cursor:default; color:var(--rhine-muted); background:var(--rhine-bg); }
|
|
36
|
+
.rhine-selection:focus-within { outline:2px solid var(--rhine-green); outline-offset:3px; }
|
|
37
|
+
.rhine-selection__heading { display:flex; align-items:center; justify-content:space-between; gap:var(--rhine-space-4); font-size:var(--rhine-text-heading); line-height:var(--rhine-leading-heading); overflow-wrap:anywhere; }
|
|
38
|
+
.rhine-selection__heading>strong { font-weight:var(--rhine-weight-heading); }
|
|
39
|
+
.rhine-selection input { width:18px; height:18px; accent-color:var(--rhine-green); flex-shrink:0; }
|
|
40
|
+
.rhine-selection__content { display:block; margin-top:var(--rhine-space-2); overflow-wrap:anywhere; }
|
|
41
|
+
.rhine-field { display:flex; flex-direction:column; gap:8px; min-width:0; }
|
|
42
|
+
.rhine-field>label { font-weight:500; }
|
|
43
|
+
.rhine-field input { width:100%; min-width:0; min-height:44px; padding:10px 12px; color:var(--rhine-ink); background:var(--rhine-surface); border:1px solid var(--rhine-control); border-radius:var(--rhine-radius-control); font:var(--rhine-text-body)/var(--rhine-leading-body) var(--rhine-font); transition:border-color var(--rhine-duration-hover) var(--rhine-ease-standard),background-color var(--rhine-duration-hover) var(--rhine-ease-standard); }
|
|
44
|
+
.rhine-field input::placeholder { color:var(--rhine-muted); }
|
|
45
|
+
.rhine-field input[aria-invalid=true] { border-color:var(--rhine-error); }
|
|
46
|
+
.rhine-field input:disabled { background:var(--rhine-selected); color:var(--rhine-muted); }
|
|
47
|
+
.rhine-field__hint,.rhine-field__error { font-size:12px; line-height:18px; }
|
|
48
|
+
.rhine-field__hint { color:var(--rhine-muted); }
|
|
49
|
+
.rhine-field__error { color:var(--rhine-error); }
|
|
50
|
+
.rhine-tabs__list { display:flex; flex-wrap:wrap; gap:24px; border-bottom:1px solid var(--rhine-line); }
|
|
51
|
+
.rhine-tabs__list button { position:relative; background:none; border:0; padding:12px 0; min-height:44px; color:var(--rhine-muted); font:var(--rhine-weight-label) var(--rhine-text-body)/var(--rhine-leading-body) var(--rhine-font); cursor:pointer; transition:color var(--rhine-duration-hover) var(--rhine-ease-standard); }
|
|
52
|
+
.rhine-tabs__list button::after { content:''; position:absolute; bottom:-1px; inset-inline:0; height:2px; background:var(--rhine-green); transform:scaleX(0); transform-origin:left; transition:transform var(--rhine-duration-panel) var(--rhine-ease-standard); }
|
|
53
|
+
.rhine-tabs__list button[aria-selected=true] { color:var(--rhine-ink); }
|
|
54
|
+
.rhine-tabs__list button[aria-selected=true]::after { transform:scaleX(1); }
|
|
55
|
+
.rhine-tabs__list button:disabled { color:var(--rhine-muted); opacity:.6; cursor:default; }
|
|
56
|
+
.rhine-tabs__panel { padding-top:20px; animation:rhine-enter var(--rhine-duration-panel) var(--rhine-ease-standard); }
|
|
57
|
+
.rhine-segmented { display:inline-flex; flex-wrap:wrap; gap:3px; padding:3px; border:1px solid var(--rhine-control); border-radius:var(--rhine-radius-control); margin:0; min-width:0; }
|
|
58
|
+
.rhine-segmented__option { position:relative; display:flex; flex:1 0 auto; cursor:pointer; }
|
|
59
|
+
.rhine-segmented input { position:absolute; opacity:0; width:1px; height:1px; }
|
|
60
|
+
.rhine-segmented__option span { display:flex; justify-content:center; align-items:center; min-height:40px; padding:8px 12px; color:var(--rhine-muted); font-size:14px; transition:background-color var(--rhine-duration-hover) var(--rhine-ease-standard),color var(--rhine-duration-hover) var(--rhine-ease-standard); }
|
|
61
|
+
.rhine-segmented input:checked+span { background:var(--rhine-ink); color:var(--rhine-surface); }
|
|
62
|
+
.rhine-segmented input:focus-visible+span { outline:2px solid var(--rhine-green); outline-offset:3px; }
|
|
63
|
+
.rhine-segmented input:disabled+span { opacity:.6; cursor:default; }
|
|
64
|
+
.rhine-notice { padding:16px; border-left:3px solid var(--rhine-tone); background:var(--rhine-tone-bg); color:var(--rhine-tone); display:flex; align-items:center; justify-content:space-between; gap:16px; flex-wrap:wrap; animation:rhine-enter var(--rhine-duration-notice) var(--rhine-ease-standard); }
|
|
65
|
+
.rhine-notice>div { flex:1 1 200px; min-width:0; overflow-wrap:anywhere; }
|
|
66
|
+
.rhine-notice strong { font-size:14px; font-weight:600; }
|
|
67
|
+
.rhine-notice__body { margin-top:4px; font-size:12px; line-height:20px; }
|
|
68
|
+
.rhine-progress__label { display:flex; justify-content:space-between; gap:var(--rhine-space-3); font-size:var(--rhine-text-caption); line-height:var(--rhine-leading-caption); margin-bottom:var(--rhine-space-2); }
|
|
69
|
+
.rhine-progress__label strong { font-variant-numeric:tabular-nums; }
|
|
70
|
+
.rhine-progress__track { height:6px; background:var(--rhine-line); overflow:hidden; }
|
|
71
|
+
.rhine-progress__track>span { display:block; height:100%; background:var(--rhine-green); transform-origin:left; transition:transform var(--rhine-duration-progress) var(--rhine-ease-standard); }
|
|
72
|
+
.rhine-progress__track[data-indeterminate=true]>span { width:30%; animation:rhine-progress var(--rhine-duration-loading) var(--rhine-ease-standard) infinite; }
|
|
73
|
+
.rhine-skeleton { position:relative; display:flex; flex-direction:column; gap:12px; }
|
|
74
|
+
.rhine-skeleton>span[aria-hidden] { display:block; height:16px; background:var(--rhine-line); animation:rhine-pulse var(--rhine-duration-skeleton) ease-in-out infinite; }
|
|
75
|
+
.rhine-skeleton>span[aria-hidden]:last-child { width:65%; }
|
|
76
|
+
.rhine-disclosure { border-top:1px solid var(--rhine-line); border-bottom:1px solid var(--rhine-line); overflow:hidden; }
|
|
77
|
+
.rhine-disclosure summary { min-height:48px; padding:12px 0; list-style:none; cursor:pointer; display:flex; justify-content:space-between; align-items:center; gap:16px; font-weight:500; }
|
|
78
|
+
.rhine-disclosure summary::-webkit-details-marker { display:none; }
|
|
79
|
+
.rhine-theme .rhine-disclosure summary:focus-visible { outline-offset:-3px; }
|
|
80
|
+
.rhine-disclosure summary>span { font:20px var(--rhine-mono); transition:transform var(--rhine-duration-notice) var(--rhine-ease-standard); }
|
|
81
|
+
.rhine-disclosure[data-expanded=true] summary>span { transform:rotate(45deg); }
|
|
82
|
+
.rhine-disclosure__content { padding:4px 0 20px; }
|
|
83
|
+
.rhine-empty { padding:32px 24px; text-align:center; border:1px solid var(--rhine-line); background:var(--rhine-surface); }
|
|
84
|
+
.rhine-empty strong { font-size:16px; }.rhine-empty p { max-width:400px; margin:8px auto 20px; color:var(--rhine-muted); }
|
|
85
|
+
@keyframes rhine-spin { to { transform:rotate(360deg); } }
|
|
86
|
+
.rhine-logo { display:inline-flex; flex:none; align-items:center; vertical-align:middle; color:var(--rhine-green); line-height:1; }
|
|
87
|
+
.rhine-logo__symbol { display:block; width:var(--rhine-logo-size); height:var(--rhine-logo-size); flex:none; fill:currentColor; }
|
|
88
|
+
.rhine-logo--icon { width:var(--rhine-logo-size); height:var(--rhine-logo-size); border-radius:22%; background:var(--rhine-bg); }
|
|
89
|
+
.rhine-logo[data-theme=dark] { color:var(--rhine-bg); }
|
|
90
|
+
.rhine-logo--icon[data-theme=dark] { background:var(--rhine-green); }
|
|
91
|
+
.rhine-logo--lockup { gap:12px; }
|
|
92
|
+
.rhine-logo__wordmark { font:600 20px/1 var(--rhine-font); letter-spacing:.08em; white-space:nowrap; }
|
|
93
|
+
.rhine-logo__plate--left { --rhine-logo-x:calc(var(--rhine-logo-travel) * -.5); --rhine-logo-y:calc(var(--rhine-logo-travel) * -.866); }
|
|
94
|
+
.rhine-logo__plate--right { --rhine-logo-x:calc(var(--rhine-logo-travel) * .5); --rhine-logo-y:calc(var(--rhine-logo-travel) * -.866); }
|
|
95
|
+
.rhine-logo__plate--base { --rhine-logo-x:0px; --rhine-logo-y:var(--rhine-logo-travel); }
|
|
96
|
+
.rhine-logo[data-animate=true] .rhine-logo__plate { animation:rhine-logo-enter var(--rhine-duration-brand) var(--rhine-ease-standard) both; }
|
|
97
|
+
@keyframes rhine-logo-enter { from { opacity:0; transform:translate(var(--rhine-logo-x),var(--rhine-logo-y)); } to { opacity:1; transform:translate(0,0); } }
|
|
94
98
|
@media(prefers-color-scheme:dark) {
|
|
95
|
-
.
|
|
96
|
-
.
|
|
99
|
+
.rhine-logo[data-theme=auto] { color:var(--rhine-bg); }
|
|
100
|
+
.rhine-logo--icon[data-theme=auto] { background:var(--rhine-green); }
|
|
97
101
|
}
|
|
98
|
-
@media(prefers-reduced-motion:reduce) { .
|
|
99
|
-
@keyframes
|
|
100
|
-
@keyframes
|
|
101
|
-
@keyframes
|
|
102
|
-
.
|
|
103
|
-
.
|
|
104
|
-
.
|
|
102
|
+
@media(prefers-reduced-motion:reduce) { .rhine-logo[data-animate=true] .rhine-logo__plate { animation:none; } }
|
|
103
|
+
@keyframes rhine-enter { from { opacity:0; transform:translateY(var(--rhine-motion-distance)); } to { opacity:1; transform:translateY(0); } }
|
|
104
|
+
@keyframes rhine-progress { from { transform:translateX(-100%); } to { transform:translateX(340%); } }
|
|
105
|
+
@keyframes rhine-pulse { 50% { opacity:.55; } }
|
|
106
|
+
.rhine-theme[data-rhine-motion=reduced] { --rhine-duration-hover:0ms; --rhine-duration-press:0ms; --rhine-duration-panel:0ms; --rhine-duration-notice:0ms; --rhine-duration-progress:0ms; }
|
|
107
|
+
.rhine-theme[data-rhine-motion=reduced] *, .rhine-theme[data-rhine-motion=reduced] *::before, .rhine-theme[data-rhine-motion=reduced] *::after { animation:none!important; transition:none!important; }
|
|
108
|
+
.rhine-theme[data-rhine-motion=reduced] .rhine-button:active { transform:none; }
|
|
105
109
|
@media(prefers-reduced-motion:reduce) {
|
|
106
|
-
.
|
|
107
|
-
.
|
|
108
|
-
.
|
|
110
|
+
.rhine-theme { --rhine-duration-hover:0ms; --rhine-duration-press:0ms; --rhine-duration-panel:0ms; --rhine-duration-notice:0ms; --rhine-duration-progress:0ms; }
|
|
111
|
+
.rhine-theme *, .rhine-theme *::before, .rhine-theme *::after { animation:none!important; transition:none!important; }
|
|
112
|
+
.rhine-theme .rhine-button:active { transform:none; }
|
|
109
113
|
}
|