@linhey/rhine-design 0.2.0 → 0.3.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.
@@ -1,69 +1,69 @@
1
- .tod-icon { display:inline-block; vertical-align:middle; flex:none; fill:none; stroke:currentColor; stroke-width:1.5; stroke-linecap:round; stroke-linejoin:round; }
2
- .tod-icon-button { padding:10px; width:44px; height:44px; flex:none; }
3
- .tod-link { color:var(--tod-green); text-decoration:underline; text-underline-offset:3px; overflow-wrap:anywhere; }
4
- .tod-link:hover { text-decoration-thickness:2px; }
5
- .tod-link>.tod-icon { margin-left:4px; }
6
- .tod-copy { display:inline-flex; align-items:center; gap:12px; flex-wrap:wrap; }
7
- .tod-copy>[role] { font-size:var(--tod-text-caption); }
8
- .tod-stack { display:flex; flex-direction:column; min-width:0; }
9
- .tod-inline { display:flex; align-items:center; flex-wrap:wrap; gap:var(--tod-space-3); min-width:0; }
10
- .tod-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(min(100%,var(--tod-grid-min)),1fr)); min-width:0; }
11
- .tod-grid>*,.tod-stack>* { min-width:0; }
12
- .tod-divider { border:0; border-top:1px solid var(--tod-line); margin:0; width:100%; }
13
- .tod-metric { min-width:0; display:grid; gap:8px; align-content:start; }
14
- .tod-metric__unit { font-size:var(--tod-text-caption); font-weight:400; color:var(--tod-muted); }
15
- .tod-key-value { margin:0; display:grid; gap:12px; }
16
- .tod-key-value>div { display:grid; grid-template-columns:minmax(80px,1fr) minmax(0,2fr); gap:16px; }
17
- .tod-key-value dt { color:var(--tod-muted); font-size:12px; }.tod-key-value dd { margin:0; overflow-wrap:anywhere; }
18
- .tod-data { display:grid; gap:12px; min-width:0; }
19
- .tod-table-scroll { overflow:auto; max-width:100%; }
20
- .tod-table { width:100%; min-width:500px; border-collapse:collapse; text-align:left; font-size:12px; }
21
- .tod-table caption { text-align:left; font-size:16px; font-weight:600; margin-bottom:16px; }
22
- .tod-table th,.tod-table td { padding:12px; border-bottom:1px solid var(--tod-line); max-width:240px; overflow-wrap:anywhere; }
23
- .tod-table th { color:var(--tod-muted); font-weight:500; }.tod-table td { font-variant-numeric:tabular-nums; }
24
- .tod-table [data-align=right] { text-align:right; }.tod-table tbody tr:hover { background:var(--tod-selected); }
25
- .tod-table th button { font:inherit; color:inherit; background:none; border:0; min-height:44px; cursor:pointer; padding:0; }
26
- .tod-table th button:disabled { cursor:default; }
27
- .tod-pagination { display:flex; flex-wrap:wrap; justify-content:flex-end; align-items:center; gap:12px; }
28
- .tod-check>label,.tod-radio-group>label { display:flex; align-items:center; gap:10px; min-height:44px; cursor:pointer; overflow-wrap:anywhere; }
29
- .tod-check input,.tod-radio-group input { width:18px; height:18px; accent-color:var(--tod-green); flex:none; margin:0; }
30
- .tod-check input:disabled+span,.tod-radio-group input:disabled+span,.tod-radio-group:disabled span { color:var(--tod-muted); cursor:default; }
31
- .tod-radio-group { border:0; padding:0; margin:0; min-width:0; }.tod-radio-group legend { font-weight:500; padding:0; }
32
- .tod-switch label { display:flex; position:relative; align-items:center; gap:12px; min-height:44px; cursor:pointer; }
33
- .tod-switch input { position:absolute; opacity:0; width:1px; height:1px; }
34
- .tod-switch__track { display:inline-flex; width:36px; height:22px; border:1px solid var(--tod-control); border-radius:12px; padding:3px; flex:none; background:var(--tod-surface); transition:background-color var(--tod-duration-hover) var(--tod-ease-standard); }
35
- .tod-switch__track>span { width:14px; height:14px; border-radius:50%; background:var(--tod-muted); transition:transform var(--tod-duration-hover) var(--tod-ease-standard); }
36
- .tod-switch input:checked+.tod-switch__track { background:var(--tod-green); border-color:var(--tod-green); }
37
- .tod-switch input:checked+.tod-switch__track>span { transform:translateX(14px); background:var(--tod-surface); }
38
- .tod-switch input:focus-visible+.tod-switch__track { outline:2px solid var(--tod-green); outline-offset:3px; }
39
- .tod-switch input:disabled~span { opacity:.6; cursor:default; }
40
- .tod-field textarea { font:var(--tod-text-body)/var(--tod-leading-body) var(--tod-font); color:var(--tod-ink); background:var(--tod-surface); border:1px solid var(--tod-control); border-radius:var(--tod-radius-control); padding:10px 12px; min-height:88px; resize:vertical; width:100%; transition:border-color var(--tod-duration-hover) var(--tod-ease-standard); }
41
- .tod-field textarea[aria-invalid=true] { border-color:var(--tod-error); }.tod-field textarea:disabled { color:var(--tod-muted); background:var(--tod-selected); }
42
- .tod-search>.tod-inline>input { flex:1 1 140px; width:auto; }.tod-search input::-webkit-search-cancel-button { appearance:none; }
43
- .tod-combobox { position:relative; }
44
- .tod-combobox__input { position:relative; }.tod-combobox__input input { padding-right:40px; }
45
- .tod-combobox__arrow { position:absolute; right:12px; top:50%; transform:translateY(-50%); pointer-events:none; }
46
- .tod-combobox__list { z-index:50; width:var(--radix-popover-trigger-width); max-width:calc(100vw - 24px); max-height:min(240px,var(--radix-popover-content-available-height)); overflow:auto; background:var(--tod-surface); color:var(--tod-ink); border:1px solid var(--tod-control); border-radius:4px; box-shadow:0 8px 24px #242b2818; }
47
- .tod-combobox__list [role=option],.tod-combobox__empty { padding:12px; min-height:44px; cursor:pointer; overflow-wrap:anywhere; }
48
- .tod-combobox__list [role=option][data-active=true],.tod-combobox__list [role=option][aria-selected=true] { background:var(--tod-selected); }
49
- .tod-combobox__list [role=option][aria-disabled=true],.tod-combobox__empty { color:var(--tod-muted); cursor:default; }
50
- .tod-overlay-anchor { display:inline-block; vertical-align:middle; max-width:100%; }
51
- .tod-overlay { position:fixed; inset:0; background:#242b2870; z-index:40; }
52
- .tod-dialog { position:fixed; z-index:41; top:50%; left:50%; transform:translate(-50%,-50%); width:min(480px,calc(100vw - 32px)); max-height:calc(100dvh - 32px); overflow:auto; padding:24px; border:1px solid var(--tod-line); border-radius:4px; background:var(--tod-surface); color:var(--tod-ink); box-shadow:0 16px 48px #242b2826; }
53
- .tod-dialog>p { margin-top:12px; }.tod-dialog>.tod-notice { margin-top:16px; }
54
- .tod-dialog__head { display:flex; align-items:center; justify-content:space-between; gap:12px; }
55
- .tod-dialog__head>:first-child { min-width:0; overflow-wrap:anywhere; }
56
- .tod-dialog__body { margin-top:24px; }.tod-dialog__actions { display:flex; justify-content:flex-end; flex-wrap:wrap; gap:12px; margin-top:24px; }
57
- .tod-popover,.tod-menu { z-index:50; background:var(--tod-surface); color:var(--tod-ink); border:1px solid var(--tod-control); border-radius:4px; box-shadow:0 8px 24px #242b2820; max-width:calc(100vw - 24px); overflow:auto; }
58
- .tod-popover { width:300px; padding:16px; max-height:var(--radix-popover-content-available-height); }
59
- .tod-menu { min-width:180px; max-height:var(--radix-dropdown-menu-content-available-height); padding:4px; }
60
- .tod-menu__item { min-height:44px; padding:10px 12px; cursor:pointer; outline:none; overflow-wrap:anywhere; }
61
- .tod-menu__item[data-highlighted] { background:var(--tod-selected); box-shadow:inset 0 0 0 2px var(--tod-green); }
62
- .tod-menu__item[data-disabled] { color:var(--tod-muted); cursor:default; }.tod-menu__item[data-danger=true] { color:var(--tod-error); }
63
- .tod-tooltip { z-index:60; background:var(--tod-ink); color:var(--tod-surface); padding:8px 12px; border-radius:3px; max-width:min(280px,calc(100vw - 24px)); font-size:12px; line-height:18px; overflow-wrap:anywhere; }
64
- .tod-toast-region { position:relative; }.tod-toast { display:flex; align-items:start; gap:12px; justify-content:space-between; padding:16px; background:var(--tod-tone-bg); border-left:3px solid var(--tod-tone); color:var(--tod-ink); animation:tod-enter var(--tod-duration-panel) var(--tod-ease-standard); }
65
- .tod-toast p { margin:4px 0 0; overflow-wrap:anywhere; }.tod-toast strong { overflow-wrap:anywhere; }.tod-toast>div { min-width:0; }
66
- .tod-dialog[data-state=open],.tod-overlay[data-state=open] { animation:tod-overlay-fade var(--tod-duration-overlay) var(--tod-ease-standard); }
67
- .tod-popover[data-state=open],.tod-menu[data-state=open],.tod-combobox__list[data-state=open],.tod-tooltip[data-state=delayed-open],.tod-tooltip[data-state=instant-open] { animation:tod-enter var(--tod-duration-overlay) var(--tod-ease-standard); }
68
- @keyframes tod-overlay-fade { from { opacity:0; } to { opacity:1; } }
69
- @media(forced-colors:active) { .tod-switch__track { border-color:ButtonText; }.tod-switch input:checked+.tod-switch__track { background:Highlight; }.tod-switch__track>span { background:ButtonText; }.tod-switch input:checked+.tod-switch__track>span { background:HighlightText; }.tod-dialog,.tod-tooltip,.tod-menu,.tod-popover { border:1px solid CanvasText; } }
1
+ .rhine-icon { display:inline-block; vertical-align:middle; flex:none; fill:none; stroke:currentColor; stroke-width:1.5; stroke-linecap:round; stroke-linejoin:round; }
2
+ .rhine-icon-button { padding:10px; width:44px; height:44px; flex:none; }
3
+ .rhine-link { color:var(--rhine-green); text-decoration:underline; text-underline-offset:3px; overflow-wrap:anywhere; }
4
+ .rhine-link:hover { text-decoration-thickness:2px; }
5
+ .rhine-link>.rhine-icon { margin-left:4px; }
6
+ .rhine-copy { display:inline-flex; align-items:center; gap:12px; flex-wrap:wrap; }
7
+ .rhine-copy>[role] { font-size:var(--rhine-text-caption); }
8
+ .rhine-stack { display:flex; flex-direction:column; min-width:0; }
9
+ .rhine-inline { display:flex; align-items:center; flex-wrap:wrap; gap:var(--rhine-space-3); min-width:0; }
10
+ .rhine-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(min(100%,var(--rhine-grid-min)),1fr)); min-width:0; }
11
+ .rhine-grid>*,.rhine-stack>* { min-width:0; }
12
+ .rhine-divider { border:0; border-top:1px solid var(--rhine-line); margin:0; width:100%; }
13
+ .rhine-metric { min-width:0; display:grid; gap:8px; align-content:start; }
14
+ .rhine-metric__unit { font-size:var(--rhine-text-caption); font-weight:400; color:var(--rhine-muted); }
15
+ .rhine-key-value { margin:0; display:grid; gap:12px; }
16
+ .rhine-key-value>div { display:grid; grid-template-columns:minmax(80px,1fr) minmax(0,2fr); gap:16px; }
17
+ .rhine-key-value dt { color:var(--rhine-muted); font-size:12px; }.rhine-key-value dd { margin:0; overflow-wrap:anywhere; }
18
+ .rhine-data { display:grid; gap:12px; min-width:0; }
19
+ .rhine-table-scroll { overflow:auto; max-width:100%; }
20
+ .rhine-table { width:100%; min-width:500px; border-collapse:collapse; text-align:left; font-size:12px; }
21
+ .rhine-table caption { text-align:left; font-size:16px; font-weight:600; margin-bottom:16px; }
22
+ .rhine-table th,.rhine-table td { padding:12px; border-bottom:1px solid var(--rhine-line); max-width:240px; overflow-wrap:anywhere; }
23
+ .rhine-table th { color:var(--rhine-muted); font-weight:500; }.rhine-table td { font-variant-numeric:tabular-nums; }
24
+ .rhine-table [data-align=right] { text-align:right; }.rhine-table tbody tr:hover { background:var(--rhine-selected); }
25
+ .rhine-table th button { font:inherit; color:inherit; background:none; border:0; min-height:44px; cursor:pointer; padding:0; }
26
+ .rhine-table th button:disabled { cursor:default; }
27
+ .rhine-pagination { display:flex; flex-wrap:wrap; justify-content:flex-end; align-items:center; gap:12px; }
28
+ .rhine-check>label,.rhine-radio-group>label { display:flex; align-items:center; gap:10px; min-height:44px; cursor:pointer; overflow-wrap:anywhere; }
29
+ .rhine-check input,.rhine-radio-group input { width:18px; height:18px; accent-color:var(--rhine-green); flex:none; margin:0; }
30
+ .rhine-check input:disabled+span,.rhine-radio-group input:disabled+span,.rhine-radio-group:disabled span { color:var(--rhine-muted); cursor:default; }
31
+ .rhine-radio-group { border:0; padding:0; margin:0; min-width:0; }.rhine-radio-group legend { font-weight:500; padding:0; }
32
+ .rhine-switch label { display:flex; position:relative; align-items:center; gap:12px; min-height:44px; cursor:pointer; }
33
+ .rhine-switch input { position:absolute; opacity:0; width:1px; height:1px; }
34
+ .rhine-switch__track { display:inline-flex; width:36px; height:22px; border:1px solid var(--rhine-control); border-radius:12px; padding:3px; flex:none; background:var(--rhine-surface); transition:background-color var(--rhine-duration-hover) var(--rhine-ease-standard); }
35
+ .rhine-switch__track>span { width:14px; height:14px; border-radius:50%; background:var(--rhine-muted); transition:transform var(--rhine-duration-hover) var(--rhine-ease-standard); }
36
+ .rhine-switch input:checked+.rhine-switch__track { background:var(--rhine-green); border-color:var(--rhine-green); }
37
+ .rhine-switch input:checked+.rhine-switch__track>span { transform:translateX(14px); background:var(--rhine-surface); }
38
+ .rhine-switch input:focus-visible+.rhine-switch__track { outline:2px solid var(--rhine-green); outline-offset:3px; }
39
+ .rhine-switch input:disabled~span { opacity:.6; cursor:default; }
40
+ .rhine-field textarea { font:var(--rhine-text-body)/var(--rhine-leading-body) var(--rhine-font); color:var(--rhine-ink); background:var(--rhine-surface); border:1px solid var(--rhine-control); border-radius:var(--rhine-radius-control); padding:10px 12px; min-height:88px; resize:vertical; width:100%; transition:border-color var(--rhine-duration-hover) var(--rhine-ease-standard); }
41
+ .rhine-field textarea[aria-invalid=true] { border-color:var(--rhine-error); }.rhine-field textarea:disabled { color:var(--rhine-muted); background:var(--rhine-selected); }
42
+ .rhine-search>.rhine-inline>input { flex:1 1 140px; width:auto; }.rhine-search input::-webkit-search-cancel-button { appearance:none; }
43
+ .rhine-combobox { position:relative; }
44
+ .rhine-combobox__input { position:relative; }.rhine-combobox__input input { padding-right:40px; }
45
+ .rhine-combobox__arrow { position:absolute; right:12px; top:50%; transform:translateY(-50%); pointer-events:none; }
46
+ .rhine-combobox__list { z-index:50; width:var(--radix-popover-trigger-width); max-width:calc(100vw - 24px); max-height:min(240px,var(--radix-popover-content-available-height)); overflow:auto; background:var(--rhine-surface); color:var(--rhine-ink); border:1px solid var(--rhine-control); border-radius:4px; box-shadow:0 8px 24px #242b2818; }
47
+ .rhine-combobox__list [role=option],.rhine-combobox__empty { padding:12px; min-height:44px; cursor:pointer; overflow-wrap:anywhere; }
48
+ .rhine-combobox__list [role=option][data-active=true],.rhine-combobox__list [role=option][aria-selected=true] { background:var(--rhine-selected); }
49
+ .rhine-combobox__list [role=option][aria-disabled=true],.rhine-combobox__empty { color:var(--rhine-muted); cursor:default; }
50
+ .rhine-overlay-anchor { display:inline-block; vertical-align:middle; max-width:100%; }
51
+ .rhine-overlay { position:fixed; inset:0; background:#242b2870; z-index:40; }
52
+ .rhine-dialog { position:fixed; z-index:41; top:50%; left:50%; transform:translate(-50%,-50%); width:min(480px,calc(100vw - 32px)); max-height:calc(100dvh - 32px); overflow:auto; padding:24px; border:1px solid var(--rhine-line); border-radius:4px; background:var(--rhine-surface); color:var(--rhine-ink); box-shadow:0 16px 48px #242b2826; }
53
+ .rhine-dialog>p { margin-top:12px; }.rhine-dialog>.rhine-notice { margin-top:16px; }
54
+ .rhine-dialog__head { display:flex; align-items:center; justify-content:space-between; gap:12px; }
55
+ .rhine-dialog__head>:first-child { min-width:0; overflow-wrap:anywhere; }
56
+ .rhine-dialog__body { margin-top:24px; }.rhine-dialog__actions { display:flex; justify-content:flex-end; flex-wrap:wrap; gap:12px; margin-top:24px; }
57
+ .rhine-popover,.rhine-menu { z-index:50; background:var(--rhine-surface); color:var(--rhine-ink); border:1px solid var(--rhine-control); border-radius:4px; box-shadow:0 8px 24px #242b2820; max-width:calc(100vw - 24px); overflow:auto; }
58
+ .rhine-popover { width:300px; padding:16px; max-height:var(--radix-popover-content-available-height); }
59
+ .rhine-menu { min-width:180px; max-height:var(--radix-dropdown-menu-content-available-height); padding:4px; }
60
+ .rhine-menu__item { min-height:44px; padding:10px 12px; cursor:pointer; outline:none; overflow-wrap:anywhere; }
61
+ .rhine-menu__item[data-highlighted] { background:var(--rhine-selected); box-shadow:inset 0 0 0 2px var(--rhine-green); }
62
+ .rhine-menu__item[data-disabled] { color:var(--rhine-muted); cursor:default; }.rhine-menu__item[data-danger=true] { color:var(--rhine-error); }
63
+ .rhine-tooltip { z-index:60; background:var(--rhine-ink); color:var(--rhine-surface); padding:8px 12px; border-radius:3px; max-width:min(280px,calc(100vw - 24px)); font-size:12px; line-height:18px; overflow-wrap:anywhere; }
64
+ .rhine-toast-region { position:relative; }.rhine-toast { display:flex; align-items:start; gap:12px; justify-content:space-between; padding:16px; background:var(--rhine-tone-bg); border-left:3px solid var(--rhine-tone); color:var(--rhine-ink); animation:rhine-enter var(--rhine-duration-panel) var(--rhine-ease-standard); }
65
+ .rhine-toast p { margin:4px 0 0; overflow-wrap:anywhere; }.rhine-toast strong { overflow-wrap:anywhere; }.rhine-toast>div { min-width:0; }
66
+ .rhine-dialog[data-state=open],.rhine-overlay[data-state=open] { animation:rhine-overlay-fade var(--rhine-duration-overlay) var(--rhine-ease-standard); }
67
+ .rhine-popover[data-state=open],.rhine-menu[data-state=open],.rhine-combobox__list[data-state=open],.rhine-tooltip[data-state=delayed-open],.rhine-tooltip[data-state=instant-open] { animation:rhine-enter var(--rhine-duration-overlay) var(--rhine-ease-standard); }
68
+ @keyframes rhine-overlay-fade { from { opacity:0; } to { opacity:1; } }
69
+ @media(forced-colors:active) { .rhine-switch__track { border-color:ButtonText; }.rhine-switch input:checked+.rhine-switch__track { background:Highlight; }.rhine-switch__track>span { background:ButtonText; }.rhine-switch input:checked+.rhine-switch__track>span { background:HighlightText; }.rhine-dialog,.rhine-tooltip,.rhine-menu,.rhine-popover { border:1px solid CanvasText; } }
@@ -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('tod-button', `tod-button--${variant}`, className)}
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 ? 'tod-button__ghost' : undefined}>{children}</span>
16
- {busy && <span className="tod-button__busy"><span className="tod-spinner" aria-hidden="true" /><span className="tod-sr-only">{busyLabel}</span></span>}
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={`tod-badge tod-tone-${tone}`}>{children}</span>;
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('tod-surface', className)} />;
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="tod-selection" data-selected={selected} data-disabled={disabled}>
29
- <span className="tod-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="tod-selection__content">{children}</span>
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('tod-field', className)}>
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="tod-field__hint">{hint}</span>}
41
- {error && <span id={`${id}-error`} className="tod-field__error" role="alert">{error}</span>}
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="tod-tabs">
50
- <div role="tablist" aria-label={label} className="tod-tabs__list" onKeyDown={event => {
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="tod-tabs__panel">{item.content}</div>)}
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="tod-segmented"><legend className="tod-sr-only">{label}</legend>
70
- {items.map(item => <label key={item.value} className="tod-segmented__option">
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={`tod-notice tod-tone-${tone}`} role={tone === 'error' ? 'alert' : 'status'}>
78
- <div><strong>{title}</strong>{children && <div className="tod-notice__body">{children}</div>}</div>{action}
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="tod-progress">
85
- <div className="tod-progress__label"><span>{label}</span><strong>{valueText ?? (amount === null ? '处理中' : `${amount}%`)}</strong></div>
86
- <div className="tod-progress__track" role="progressbar" aria-label={label} aria-valuemin={0} aria-valuemax={100}
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="tod-skeleton" role="status"><span className="tod-sr-only">{label}</span>
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('--tod-duration-notice')) !== 0) return;
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('--tod-duration-notice'));
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 ? '--tod-ease-standard' : '--tod-ease-exit').trim() || 'ease' });
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="tod-disclosure">
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="tod-disclosure__content">{children}</div>
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="tod-empty"><strong>{title}</strong><p>{description}</p>{action}</div>;
150
+ return <div className="rhine-empty"><strong>{title}</strong><p>{description}</p>{action}</div>;
151
151
  }
@@ -2,108 +2,108 @@
2
2
  @import './typography.css';
3
3
  @import './common.css';
4
4
 
5
- .tod-theme { color:var(--tod-ink); background:var(--tod-bg); font:var(--tod-text-body)/var(--tod-leading-body) var(--tod-font); font-synthesis:none; }
6
- .tod-theme *, .tod-theme *::before, .tod-theme *::after { box-sizing:border-box; }
7
- .tod-theme [hidden] { display:none!important; }
8
- .tod-theme :is(button,input,select,textarea,summary,a,[tabindex]):focus-visible { outline:2px solid var(--tod-green); outline-offset:3px; }
9
- .tod-sr-only { position:absolute!important; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip-path:inset(50%); white-space:nowrap; border:0; }
10
- .tod-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(--tod-ink); border-radius:var(--tod-radius-control); font:500 14px/22px var(--tod-font); background:var(--tod-ink); color:var(--tod-surface); cursor:pointer; overflow-wrap:anywhere; transition:background-color var(--tod-duration-hover) var(--tod-ease-standard),border-color var(--tod-duration-hover) var(--tod-ease-standard),transform var(--tod-duration-press) var(--tod-ease-standard); }
11
- .tod-button:hover:not(:disabled):not([aria-disabled=true]) { background:var(--tod-green); border-color:var(--tod-green); }
12
- .tod-button:active:not(:disabled):not([aria-disabled=true]) { transform:translateY(1px); }
13
- .tod-button--secondary { background:var(--tod-surface); color:var(--tod-ink); border-color:var(--tod-control); }
14
- .tod-button--quiet { background:transparent; color:var(--tod-green); border-color:transparent; }
15
- .tod-button--secondary:hover:not(:disabled):not([aria-disabled=true]),.tod-button--quiet:hover:not(:disabled):not([aria-disabled=true]) { background:var(--tod-selected); }
16
- .tod-button--danger { background:var(--tod-error); border-color:var(--tod-error); }
17
- .tod-button--danger:hover:not(:disabled):not([aria-disabled=true]) { background:#81342e; border-color:#81342e; }
18
- .tod-button:disabled { background:var(--tod-selected); border-color:var(--tod-line); color:var(--tod-muted); cursor:default; }
19
- .tod-button[aria-busy=true] { cursor:wait; }
20
- .tod-button__ghost { visibility:hidden; }
21
- .tod-button__busy { position:absolute; inset:0; display:grid; place-items:center; }
22
- .tod-spinner { width:18px; height:18px; border:2px solid currentColor; border-right-color:transparent; border-radius:50%; animation:tod-spin var(--tod-duration-loading) linear infinite; }
23
- .tod-tone-neutral { --tod-tone:var(--tod-muted); --tod-tone-bg:var(--tod-selected); }
24
- .tod-tone-success { --tod-tone:var(--tod-green); --tod-tone-bg:var(--tod-selected); }
25
- .tod-tone-warning { --tod-tone:var(--tod-warning); --tod-tone-bg:var(--tod-warning-bg); }
26
- .tod-tone-error { --tod-tone:var(--tod-error); --tod-tone-bg:var(--tod-error-bg); }
27
- .tod-badge { display:inline-block; padding:2px 8px; border-radius:2px; background:var(--tod-tone-bg); color:var(--tod-tone); font:500 12px/20px var(--tod-font); }
28
- .tod-surface { padding:24px; border:1px solid var(--tod-line); border-radius:var(--tod-radius-card); background:var(--tod-surface); min-width:0; }
29
- .tod-selection { display:block; border:1px solid var(--tod-control); background:var(--tod-surface); border-radius:var(--tod-radius-card); padding:20px; cursor:pointer; min-width:0; transition:background-color var(--tod-duration-hover) var(--tod-ease-standard),border-color var(--tod-duration-hover) var(--tod-ease-standard),box-shadow var(--tod-duration-hover) var(--tod-ease-standard); }
30
- .tod-selection:hover:not([data-disabled=true]) { border-color:var(--tod-green); background:#f4f7ef; }
31
- .tod-selection[data-selected=true] { border-color:var(--tod-green); background:var(--tod-selected); box-shadow:inset 0 0 0 1px var(--tod-green); }
32
- .tod-selection[data-disabled=true] { cursor:default; color:var(--tod-muted); background:var(--tod-bg); }
33
- .tod-selection:focus-within { outline:2px solid var(--tod-green); outline-offset:3px; }
34
- .tod-selection__heading { display:flex; align-items:center; justify-content:space-between; gap:16px; font-size:16px; overflow-wrap:anywhere; }
35
- .tod-selection input { width:18px; height:18px; accent-color:var(--tod-green); flex-shrink:0; }
36
- .tod-selection__content { display:block; margin-top:16px; overflow-wrap:anywhere; }
37
- .tod-field { display:flex; flex-direction:column; gap:8px; min-width:0; }
38
- .tod-field>label { font-weight:500; }
39
- .tod-field input { width:100%; min-width:0; min-height:44px; padding:10px 12px; color:var(--tod-ink); background:var(--tod-surface); border:1px solid var(--tod-control); border-radius:var(--tod-radius-control); font:14px/22px var(--tod-font); transition:border-color var(--tod-duration-hover) var(--tod-ease-standard),background-color var(--tod-duration-hover) var(--tod-ease-standard); }
40
- .tod-field input::placeholder { color:var(--tod-muted); }
41
- .tod-field input[aria-invalid=true] { border-color:var(--tod-error); }
42
- .tod-field input:disabled { background:var(--tod-selected); color:var(--tod-muted); }
43
- .tod-field__hint,.tod-field__error { font-size:12px; line-height:18px; }
44
- .tod-field__hint { color:var(--tod-muted); }
45
- .tod-field__error { color:var(--tod-error); }
46
- .tod-tabs__list { display:flex; flex-wrap:wrap; gap:24px; border-bottom:1px solid var(--tod-line); }
47
- .tod-tabs__list button { position:relative; background:none; border:0; padding:12px 0; min-height:44px; color:var(--tod-muted); font:500 14px/22px var(--tod-font); cursor:pointer; transition:color var(--tod-duration-hover) var(--tod-ease-standard); }
48
- .tod-tabs__list button::after { content:''; position:absolute; bottom:-1px; inset-inline:0; height:2px; background:var(--tod-green); transform:scaleX(0); transform-origin:left; transition:transform var(--tod-duration-panel) var(--tod-ease-standard); }
49
- .tod-tabs__list button[aria-selected=true] { color:var(--tod-ink); }
50
- .tod-tabs__list button[aria-selected=true]::after { transform:scaleX(1); }
51
- .tod-tabs__list button:disabled { color:var(--tod-muted); opacity:.6; cursor:default; }
52
- .tod-tabs__panel { padding-top:20px; animation:tod-enter var(--tod-duration-panel) var(--tod-ease-standard); }
53
- .tod-segmented { display:inline-flex; flex-wrap:wrap; gap:3px; padding:3px; border:1px solid var(--tod-control); border-radius:var(--tod-radius-control); margin:0; min-width:0; }
54
- .tod-segmented__option { position:relative; display:flex; flex:1 0 auto; cursor:pointer; }
55
- .tod-segmented input { position:absolute; opacity:0; width:1px; height:1px; }
56
- .tod-segmented__option span { display:flex; justify-content:center; align-items:center; min-height:40px; padding:8px 12px; color:var(--tod-muted); font-size:14px; transition:background-color var(--tod-duration-hover) var(--tod-ease-standard),color var(--tod-duration-hover) var(--tod-ease-standard); }
57
- .tod-segmented input:checked+span { background:var(--tod-ink); color:var(--tod-surface); }
58
- .tod-segmented input:focus-visible+span { outline:2px solid var(--tod-green); outline-offset:3px; }
59
- .tod-segmented input:disabled+span { opacity:.6; cursor:default; }
60
- .tod-notice { padding:16px; border-left:3px solid var(--tod-tone); background:var(--tod-tone-bg); color:var(--tod-tone); display:flex; align-items:center; justify-content:space-between; gap:16px; flex-wrap:wrap; animation:tod-enter var(--tod-duration-notice) var(--tod-ease-standard); }
61
- .tod-notice>div { flex:1 1 200px; min-width:0; overflow-wrap:anywhere; }
62
- .tod-notice strong { font-size:14px; font-weight:600; }
63
- .tod-notice__body { margin-top:4px; font-size:12px; line-height:20px; }
64
- .tod-progress__label { display:flex; justify-content:space-between; gap:12px; font-size:12px; margin-bottom:8px; }
65
- .tod-progress__label strong { font-variant-numeric:tabular-nums; }
66
- .tod-progress__track { height:6px; background:var(--tod-line); overflow:hidden; }
67
- .tod-progress__track>span { display:block; height:100%; background:var(--tod-green); transform-origin:left; transition:transform var(--tod-duration-progress) var(--tod-ease-standard); }
68
- .tod-progress__track[data-indeterminate=true]>span { width:30%; animation:tod-progress var(--tod-duration-loading) var(--tod-ease-standard) infinite; }
69
- .tod-skeleton { position:relative; display:flex; flex-direction:column; gap:12px; }
70
- .tod-skeleton>span[aria-hidden] { display:block; height:16px; background:var(--tod-line); animation:tod-pulse var(--tod-duration-skeleton) ease-in-out infinite; }
71
- .tod-skeleton>span[aria-hidden]:last-child { width:65%; }
72
- .tod-disclosure { border-top:1px solid var(--tod-line); border-bottom:1px solid var(--tod-line); overflow:hidden; }
73
- .tod-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; }
74
- .tod-disclosure summary::-webkit-details-marker { display:none; }
75
- .tod-theme .tod-disclosure summary:focus-visible { outline-offset:-3px; }
76
- .tod-disclosure summary>span { font:20px var(--tod-mono); transition:transform var(--tod-duration-notice) var(--tod-ease-standard); }
77
- .tod-disclosure[data-expanded=true] summary>span { transform:rotate(45deg); }
78
- .tod-disclosure__content { padding:4px 0 20px; }
79
- .tod-empty { padding:32px 24px; text-align:center; border:1px solid var(--tod-line); background:var(--tod-surface); }
80
- .tod-empty strong { font-size:16px; }.tod-empty p { max-width:400px; margin:8px auto 20px; color:var(--tod-muted); }
81
- @keyframes tod-spin { to { transform:rotate(360deg); } }
82
- .tod-logo { display:inline-flex; flex:none; align-items:center; vertical-align:middle; color:var(--tod-green); line-height:1; }
83
- .tod-logo__symbol { display:block; width:var(--tod-logo-size); height:var(--tod-logo-size); flex:none; fill:currentColor; }
84
- .tod-logo--icon { width:var(--tod-logo-size); height:var(--tod-logo-size); border-radius:22%; background:var(--tod-bg); }
85
- .tod-logo[data-theme=dark] { color:var(--tod-bg); }
86
- .tod-logo--icon[data-theme=dark] { background:var(--tod-green); }
87
- .tod-logo--lockup { gap:12px; }
88
- .tod-logo__wordmark { font:600 20px/1 var(--tod-font); letter-spacing:.08em; white-space:nowrap; }
89
- .tod-logo__plate--left { --tod-logo-x:calc(var(--tod-logo-travel) * -.5); --tod-logo-y:calc(var(--tod-logo-travel) * -.866); }
90
- .tod-logo__plate--right { --tod-logo-x:calc(var(--tod-logo-travel) * .5); --tod-logo-y:calc(var(--tod-logo-travel) * -.866); }
91
- .tod-logo__plate--base { --tod-logo-x:0px; --tod-logo-y:var(--tod-logo-travel); }
92
- .tod-logo[data-animate=true] .tod-logo__plate { animation:tod-logo-enter var(--tod-duration-brand) var(--tod-ease-standard) both; }
93
- @keyframes tod-logo-enter { from { opacity:0; transform:translate(var(--tod-logo-x),var(--tod-logo-y)); } to { opacity:1; transform:translate(0,0); } }
5
+ .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; }
6
+ .rhine-theme *, .rhine-theme *::before, .rhine-theme *::after { box-sizing:border-box; }
7
+ .rhine-theme [hidden] { display:none!important; }
8
+ .rhine-theme :is(button,input,select,textarea,summary,a,[tabindex]):focus-visible { outline:2px solid var(--rhine-green); outline-offset:3px; }
9
+ .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; }
10
+ .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:500 14px/22px 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); }
11
+ .rhine-button:hover:not(:disabled):not([aria-disabled=true]) { background:var(--rhine-green); border-color:var(--rhine-green); }
12
+ .rhine-button:active:not(:disabled):not([aria-disabled=true]) { transform:translateY(1px); }
13
+ .rhine-button--secondary { background:var(--rhine-surface); color:var(--rhine-ink); border-color:var(--rhine-control); }
14
+ .rhine-button--quiet { background:transparent; color:var(--rhine-green); border-color:transparent; }
15
+ .rhine-button--secondary:hover:not(:disabled):not([aria-disabled=true]),.rhine-button--quiet:hover:not(:disabled):not([aria-disabled=true]) { background:var(--rhine-selected); }
16
+ .rhine-button--danger { background:var(--rhine-error); border-color:var(--rhine-error); }
17
+ .rhine-button--danger:hover:not(:disabled):not([aria-disabled=true]) { background:#81342e; border-color:#81342e; }
18
+ .rhine-button:disabled { background:var(--rhine-selected); border-color:var(--rhine-line); color:var(--rhine-muted); cursor:default; }
19
+ .rhine-button[aria-busy=true] { cursor:wait; }
20
+ .rhine-button__ghost { visibility:hidden; }
21
+ .rhine-button__busy { position:absolute; inset:0; display:grid; place-items:center; }
22
+ .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; }
23
+ .rhine-tone-neutral { --rhine-tone:var(--rhine-muted); --rhine-tone-bg:var(--rhine-selected); }
24
+ .rhine-tone-success { --rhine-tone:var(--rhine-green); --rhine-tone-bg:var(--rhine-selected); }
25
+ .rhine-tone-warning { --rhine-tone:var(--rhine-warning); --rhine-tone-bg:var(--rhine-warning-bg); }
26
+ .rhine-tone-error { --rhine-tone:var(--rhine-error); --rhine-tone-bg:var(--rhine-error-bg); }
27
+ .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); }
28
+ .rhine-surface { padding:24px; border:1px solid var(--rhine-line); border-radius:var(--rhine-radius-card); background:var(--rhine-surface); min-width:0; }
29
+ .rhine-selection { display:block; border:1px solid var(--rhine-control); background:var(--rhine-surface); border-radius:var(--rhine-radius-card); padding:20px; 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); }
30
+ .rhine-selection:hover:not([data-disabled=true]) { border-color:var(--rhine-green); background:#f4f7ef; }
31
+ .rhine-selection[data-selected=true] { border-color:var(--rhine-green); background:var(--rhine-selected); box-shadow:inset 0 0 0 1px var(--rhine-green); }
32
+ .rhine-selection[data-disabled=true] { cursor:default; color:var(--rhine-muted); background:var(--rhine-bg); }
33
+ .rhine-selection:focus-within { outline:2px solid var(--rhine-green); outline-offset:3px; }
34
+ .rhine-selection__heading { display:flex; align-items:center; justify-content:space-between; gap:16px; font-size:16px; overflow-wrap:anywhere; }
35
+ .rhine-selection input { width:18px; height:18px; accent-color:var(--rhine-green); flex-shrink:0; }
36
+ .rhine-selection__content { display:block; margin-top:16px; overflow-wrap:anywhere; }
37
+ .rhine-field { display:flex; flex-direction:column; gap:8px; min-width:0; }
38
+ .rhine-field>label { font-weight:500; }
39
+ .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:14px/22px var(--rhine-font); transition:border-color var(--rhine-duration-hover) var(--rhine-ease-standard),background-color var(--rhine-duration-hover) var(--rhine-ease-standard); }
40
+ .rhine-field input::placeholder { color:var(--rhine-muted); }
41
+ .rhine-field input[aria-invalid=true] { border-color:var(--rhine-error); }
42
+ .rhine-field input:disabled { background:var(--rhine-selected); color:var(--rhine-muted); }
43
+ .rhine-field__hint,.rhine-field__error { font-size:12px; line-height:18px; }
44
+ .rhine-field__hint { color:var(--rhine-muted); }
45
+ .rhine-field__error { color:var(--rhine-error); }
46
+ .rhine-tabs__list { display:flex; flex-wrap:wrap; gap:24px; border-bottom:1px solid var(--rhine-line); }
47
+ .rhine-tabs__list button { position:relative; background:none; border:0; padding:12px 0; min-height:44px; color:var(--rhine-muted); font:500 14px/22px var(--rhine-font); cursor:pointer; transition:color var(--rhine-duration-hover) var(--rhine-ease-standard); }
48
+ .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); }
49
+ .rhine-tabs__list button[aria-selected=true] { color:var(--rhine-ink); }
50
+ .rhine-tabs__list button[aria-selected=true]::after { transform:scaleX(1); }
51
+ .rhine-tabs__list button:disabled { color:var(--rhine-muted); opacity:.6; cursor:default; }
52
+ .rhine-tabs__panel { padding-top:20px; animation:rhine-enter var(--rhine-duration-panel) var(--rhine-ease-standard); }
53
+ .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; }
54
+ .rhine-segmented__option { position:relative; display:flex; flex:1 0 auto; cursor:pointer; }
55
+ .rhine-segmented input { position:absolute; opacity:0; width:1px; height:1px; }
56
+ .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); }
57
+ .rhine-segmented input:checked+span { background:var(--rhine-ink); color:var(--rhine-surface); }
58
+ .rhine-segmented input:focus-visible+span { outline:2px solid var(--rhine-green); outline-offset:3px; }
59
+ .rhine-segmented input:disabled+span { opacity:.6; cursor:default; }
60
+ .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); }
61
+ .rhine-notice>div { flex:1 1 200px; min-width:0; overflow-wrap:anywhere; }
62
+ .rhine-notice strong { font-size:14px; font-weight:600; }
63
+ .rhine-notice__body { margin-top:4px; font-size:12px; line-height:20px; }
64
+ .rhine-progress__label { display:flex; justify-content:space-between; gap:12px; font-size:12px; margin-bottom:8px; }
65
+ .rhine-progress__label strong { font-variant-numeric:tabular-nums; }
66
+ .rhine-progress__track { height:6px; background:var(--rhine-line); overflow:hidden; }
67
+ .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); }
68
+ .rhine-progress__track[data-indeterminate=true]>span { width:30%; animation:rhine-progress var(--rhine-duration-loading) var(--rhine-ease-standard) infinite; }
69
+ .rhine-skeleton { position:relative; display:flex; flex-direction:column; gap:12px; }
70
+ .rhine-skeleton>span[aria-hidden] { display:block; height:16px; background:var(--rhine-line); animation:rhine-pulse var(--rhine-duration-skeleton) ease-in-out infinite; }
71
+ .rhine-skeleton>span[aria-hidden]:last-child { width:65%; }
72
+ .rhine-disclosure { border-top:1px solid var(--rhine-line); border-bottom:1px solid var(--rhine-line); overflow:hidden; }
73
+ .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; }
74
+ .rhine-disclosure summary::-webkit-details-marker { display:none; }
75
+ .rhine-theme .rhine-disclosure summary:focus-visible { outline-offset:-3px; }
76
+ .rhine-disclosure summary>span { font:20px var(--rhine-mono); transition:transform var(--rhine-duration-notice) var(--rhine-ease-standard); }
77
+ .rhine-disclosure[data-expanded=true] summary>span { transform:rotate(45deg); }
78
+ .rhine-disclosure__content { padding:4px 0 20px; }
79
+ .rhine-empty { padding:32px 24px; text-align:center; border:1px solid var(--rhine-line); background:var(--rhine-surface); }
80
+ .rhine-empty strong { font-size:16px; }.rhine-empty p { max-width:400px; margin:8px auto 20px; color:var(--rhine-muted); }
81
+ @keyframes rhine-spin { to { transform:rotate(360deg); } }
82
+ .rhine-logo { display:inline-flex; flex:none; align-items:center; vertical-align:middle; color:var(--rhine-green); line-height:1; }
83
+ .rhine-logo__symbol { display:block; width:var(--rhine-logo-size); height:var(--rhine-logo-size); flex:none; fill:currentColor; }
84
+ .rhine-logo--icon { width:var(--rhine-logo-size); height:var(--rhine-logo-size); border-radius:22%; background:var(--rhine-bg); }
85
+ .rhine-logo[data-theme=dark] { color:var(--rhine-bg); }
86
+ .rhine-logo--icon[data-theme=dark] { background:var(--rhine-green); }
87
+ .rhine-logo--lockup { gap:12px; }
88
+ .rhine-logo__wordmark { font:600 20px/1 var(--rhine-font); letter-spacing:.08em; white-space:nowrap; }
89
+ .rhine-logo__plate--left { --rhine-logo-x:calc(var(--rhine-logo-travel) * -.5); --rhine-logo-y:calc(var(--rhine-logo-travel) * -.866); }
90
+ .rhine-logo__plate--right { --rhine-logo-x:calc(var(--rhine-logo-travel) * .5); --rhine-logo-y:calc(var(--rhine-logo-travel) * -.866); }
91
+ .rhine-logo__plate--base { --rhine-logo-x:0px; --rhine-logo-y:var(--rhine-logo-travel); }
92
+ .rhine-logo[data-animate=true] .rhine-logo__plate { animation:rhine-logo-enter var(--rhine-duration-brand) var(--rhine-ease-standard) both; }
93
+ @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
94
  @media(prefers-color-scheme:dark) {
95
- .tod-logo[data-theme=auto] { color:var(--tod-bg); }
96
- .tod-logo--icon[data-theme=auto] { background:var(--tod-green); }
95
+ .rhine-logo[data-theme=auto] { color:var(--rhine-bg); }
96
+ .rhine-logo--icon[data-theme=auto] { background:var(--rhine-green); }
97
97
  }
98
- @media(prefers-reduced-motion:reduce) { .tod-logo[data-animate=true] .tod-logo__plate { animation:none; } }
99
- @keyframes tod-enter { from { opacity:0; transform:translateY(var(--tod-motion-distance)); } to { opacity:1; transform:translateY(0); } }
100
- @keyframes tod-progress { from { transform:translateX(-100%); } to { transform:translateX(340%); } }
101
- @keyframes tod-pulse { 50% { opacity:.55; } }
102
- .tod-theme[data-tod-motion=reduced] { --tod-duration-hover:0ms; --tod-duration-press:0ms; --tod-duration-panel:0ms; --tod-duration-notice:0ms; --tod-duration-progress:0ms; }
103
- .tod-theme[data-tod-motion=reduced] *, .tod-theme[data-tod-motion=reduced] *::before, .tod-theme[data-tod-motion=reduced] *::after { animation:none!important; transition:none!important; }
104
- .tod-theme[data-tod-motion=reduced] .tod-button:active { transform:none; }
98
+ @media(prefers-reduced-motion:reduce) { .rhine-logo[data-animate=true] .rhine-logo__plate { animation:none; } }
99
+ @keyframes rhine-enter { from { opacity:0; transform:translateY(var(--rhine-motion-distance)); } to { opacity:1; transform:translateY(0); } }
100
+ @keyframes rhine-progress { from { transform:translateX(-100%); } to { transform:translateX(340%); } }
101
+ @keyframes rhine-pulse { 50% { opacity:.55; } }
102
+ .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; }
103
+ .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; }
104
+ .rhine-theme[data-rhine-motion=reduced] .rhine-button:active { transform:none; }
105
105
  @media(prefers-reduced-motion:reduce) {
106
- .tod-theme { --tod-duration-hover:0ms; --tod-duration-press:0ms; --tod-duration-panel:0ms; --tod-duration-notice:0ms; --tod-duration-progress:0ms; }
107
- .tod-theme *, .tod-theme *::before, .tod-theme *::after { animation:none!important; transition:none!important; }
108
- .tod-theme .tod-button:active { transform:none; }
106
+ .rhine-theme { --rhine-duration-hover:0ms; --rhine-duration-press:0ms; --rhine-duration-panel:0ms; --rhine-duration-notice:0ms; --rhine-duration-progress:0ms; }
107
+ .rhine-theme *, .rhine-theme *::before, .rhine-theme *::after { animation:none!important; transition:none!important; }
108
+ .rhine-theme .rhine-button:active { transform:none; }
109
109
  }