@unitninety/site-kit 0.0.0-stage → 1.6.2

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 (206) hide show
  1. package/README.md +30 -2
  2. package/package.json +79 -4
  3. package/src/bridge/DraftBlocks.tsx +70 -0
  4. package/src/bridge/DraftEntries.tsx +41 -0
  5. package/src/bridge/PreviewCommitBoundary.tsx +19 -0
  6. package/src/bridge/U90Bridge.tsx +54 -0
  7. package/src/bridge/caret.ts +317 -0
  8. package/src/bridge/client.tsx +66 -0
  9. package/src/bridge/createBridge.ts +1015 -0
  10. package/src/bridge/hooks.tsx +56 -0
  11. package/src/bridge/index.ts +9 -0
  12. package/src/bridge/lexical.ts +177 -0
  13. package/src/bridge/lexicalDom.ts +212 -0
  14. package/src/bridge/preserveBlockMoves.ts +88 -0
  15. package/src/bridge/preview.ts +179 -0
  16. package/src/bridge/protocol.ts +273 -0
  17. package/src/bridge/registry.ts +30 -0
  18. package/src/bridge/styles.ts +137 -0
  19. package/src/components/DateText.tsx +9 -0
  20. package/src/components/Entries.tsx +16 -0
  21. package/src/components/File.tsx +9 -0
  22. package/src/components/Image.tsx +30 -0
  23. package/src/components/Link.tsx +24 -0
  24. package/src/components/NumberText.tsx +9 -0
  25. package/src/components/RichText.tsx +47 -0
  26. package/src/components/Text.tsx +15 -0
  27. package/src/components/Video.tsx +12 -0
  28. package/src/components/VideoPlayback.tsx +19 -0
  29. package/src/components/draft.ts +9 -0
  30. package/src/components/imageProps.ts +26 -0
  31. package/src/components/index.ts +13 -0
  32. package/src/components/videoProps.ts +6 -0
  33. package/src/elements/containers/group.ts +33 -0
  34. package/src/elements/containers/list.ts +31 -0
  35. package/src/elements/containers/zone.ts +41 -0
  36. package/src/elements/declaration.ts +118 -0
  37. package/src/elements/define.ts +195 -0
  38. package/src/elements/ids.ts +50 -0
  39. package/src/elements/index.ts +35 -0
  40. package/src/elements/jsonSchema.ts +36 -0
  41. package/src/elements/leaf.ts +98 -0
  42. package/src/elements/leaves/boolean.ts +30 -0
  43. package/src/elements/leaves/choice.ts +53 -0
  44. package/src/elements/leaves/date.ts +39 -0
  45. package/src/elements/leaves/file.ts +56 -0
  46. package/src/elements/leaves/image.ts +160 -0
  47. package/src/elements/leaves/link.ts +149 -0
  48. package/src/elements/leaves/number.ts +52 -0
  49. package/src/elements/leaves/query.ts +101 -0
  50. package/src/elements/leaves/relation.ts +69 -0
  51. package/src/elements/leaves/rich-text.ts +155 -0
  52. package/src/elements/leaves/selection.ts +40 -0
  53. package/src/elements/leaves/single.ts +29 -0
  54. package/src/elements/leaves/text.ts +103 -0
  55. package/src/elements/leaves/video.ts +108 -0
  56. package/src/elements/ops/expand.ts +94 -0
  57. package/src/elements/ops/overlay.ts +59 -0
  58. package/src/elements/ops/refs.ts +45 -0
  59. package/src/elements/ops/seed.ts +185 -0
  60. package/src/elements/ops/text.ts +20 -0
  61. package/src/elements/ops/translatable.ts +19 -0
  62. package/src/elements/ops/upgrade.ts +43 -0
  63. package/src/elements/ops/validate.ts +56 -0
  64. package/src/elements/order.ts +10 -0
  65. package/src/elements/path.ts +43 -0
  66. package/src/elements/prefix.ts +2 -0
  67. package/src/elements/registry.ts +48 -0
  68. package/src/elements/tokens.ts +14 -0
  69. package/src/elements/walk.ts +42 -0
  70. package/src/features/client/AskAiProvider.tsx +551 -0
  71. package/src/features/client/AskAiWidget.tsx +222 -0
  72. package/src/features/client/ConsentRuntime.tsx +95 -0
  73. package/src/features/client/FeedbackProvider.tsx +231 -0
  74. package/src/features/client/FeedbackWidget.tsx +101 -0
  75. package/src/features/client/PageReporter.tsx +26 -0
  76. package/src/features/client/README.md +131 -0
  77. package/src/features/client/SiteFeatures.tsx +250 -0
  78. package/src/features/client/askAiRecovery.ts +92 -0
  79. package/src/features/client/askAiStream.ts +59 -0
  80. package/src/features/client/context.tsx +24 -0
  81. package/src/features/client/index.ts +24 -0
  82. package/src/features/client/overlay.tsx +129 -0
  83. package/src/features/client/styles.css +337 -0
  84. package/src/features/client/transport.ts +310 -0
  85. package/src/features/client/useAskAI.ts +45 -0
  86. package/src/features/client/useConsent.ts +15 -0
  87. package/src/features/client/useFeedback.ts +33 -0
  88. package/src/features/client/useSiteSearch.ts +50 -0
  89. package/src/features/client/verification.ts +141 -0
  90. package/src/features/consent/README.md +70 -0
  91. package/src/features/consent/cookieScript.ts +53 -0
  92. package/src/features/consent/custom.ts +89 -0
  93. package/src/features/consent/defaults.ts +40 -0
  94. package/src/features/consent/drivers.ts +233 -0
  95. package/src/features/consent/loader.ts +248 -0
  96. package/src/features/consent/registry.ts +398 -0
  97. package/src/features/contracts/askAi.ts +166 -0
  98. package/src/features/contracts/askAiSettings.ts +42 -0
  99. package/src/features/contracts/base.ts +107 -0
  100. package/src/features/contracts/consent.ts +150 -0
  101. package/src/features/contracts/feedback.ts +22 -0
  102. package/src/features/contracts/feedbackSettings.ts +49 -0
  103. package/src/features/contracts/index.ts +61 -0
  104. package/src/features/contracts/knowledge.ts +99 -0
  105. package/src/features/contracts/knowledgePolicy.ts +249 -0
  106. package/src/features/contracts/search.ts +24 -0
  107. package/src/features/contracts/verification.ts +52 -0
  108. package/src/features/contracts/verificationConfig.ts +9 -0
  109. package/src/features/server/ConsentDefaults.tsx +15 -0
  110. package/src/features/server/README.md +185 -0
  111. package/src/features/server/gateway.ts +318 -0
  112. package/src/features/server/http.ts +179 -0
  113. package/src/features/server/index.ts +6 -0
  114. package/src/features/server/session.ts +23 -0
  115. package/src/features/server/verification.ts +39 -0
  116. package/src/features/server/verifyVisitor.ts +69 -0
  117. package/src/languages/config.d.mts +5 -0
  118. package/src/languages/config.mjs +53 -0
  119. package/src/languages/decide.ts +72 -0
  120. package/src/languages/index.ts +4 -0
  121. package/src/languages/proxy.ts +108 -0
  122. package/src/languages/routing.ts +93 -0
  123. package/src/languages/wire.ts +19 -0
  124. package/src/legacy/bridge.tsx +1987 -0
  125. package/src/legacy/caret.ts +246 -0
  126. package/src/legacy/cli.mjs +189 -0
  127. package/src/legacy/content.ts +993 -0
  128. package/src/legacy/contract.ts +664 -0
  129. package/src/legacy/defineBlocks.ts +1602 -0
  130. package/src/legacy/defineSite.ts +623 -0
  131. package/src/legacy/defineSite.types.ts +270 -0
  132. package/src/legacy/draft.tsx +128 -0
  133. package/src/legacy/image.ts +433 -0
  134. package/src/legacy/index.ts +209 -0
  135. package/src/legacy/language-switcher.tsx +58 -0
  136. package/src/legacy/languageHints.ts +153 -0
  137. package/src/legacy/languages/fields.ts +131 -0
  138. package/src/legacy/languages/hash.ts +98 -0
  139. package/src/legacy/languages/health.ts +97 -0
  140. package/src/legacy/languages/identity.ts +247 -0
  141. package/src/legacy/languages/links.ts +81 -0
  142. package/src/legacy/languages/resolution.ts +56 -0
  143. package/src/legacy/languages/routing.ts +169 -0
  144. package/src/legacy/languages/runtime.ts +216 -0
  145. package/src/legacy/languages/seo.ts +95 -0
  146. package/src/legacy/languages/settings.ts +211 -0
  147. package/src/legacy/languages/types.ts +85 -0
  148. package/src/legacy/languages.ts +8 -0
  149. package/src/legacy/lexical.ts +170 -0
  150. package/src/legacy/lexicalDom.ts +166 -0
  151. package/src/legacy/media.ts +272 -0
  152. package/src/legacy/nav.ts +176 -0
  153. package/src/legacy/preview.ts +79 -0
  154. package/src/legacy/redirects.ts +474 -0
  155. package/src/legacy/richText.tsx +104 -0
  156. package/src/legacy/routes.ts +414 -0
  157. package/src/legacy/seo.ts +327 -0
  158. package/src/legacy/shape.ts +412 -0
  159. package/src/legacy/uploads.ts +233 -0
  160. package/src/legacy/verification.ts +42 -0
  161. package/src/preview-lock/contracts.ts +42 -0
  162. package/src/preview-lock/index.ts +3 -0
  163. package/src/preview-lock/screen.tsx +244 -0
  164. package/src/preview-lock/server.ts +416 -0
  165. package/src/redirects/config.d.mts +5 -0
  166. package/src/redirects/config.mjs +22 -0
  167. package/src/redirects/index.ts +3 -0
  168. package/src/redirects/location.ts +31 -0
  169. package/src/redirects/match.ts +199 -0
  170. package/src/redirects/proxy.ts +91 -0
  171. package/src/redirects/wire.ts +30 -0
  172. package/src/seo/jsonLd.ts +70 -0
  173. package/src/seo/metadata.ts +127 -0
  174. package/src/seo/robots.ts +14 -0
  175. package/src/seo/sitemap.ts +22 -0
  176. package/src/seo/title.ts +22 -0
  177. package/src/server/adapters.ts +73 -0
  178. package/src/server/createU90.ts +96 -0
  179. package/src/server/handlers.ts +164 -0
  180. package/src/server/index.ts +6 -0
  181. package/src/server/read.ts +207 -0
  182. package/src/server/render.tsx +46 -0
  183. package/src/server/routes.tsx +171 -0
  184. package/src/site/assemble.ts +109 -0
  185. package/src/site/defineSite.ts +132 -0
  186. package/src/site/documents.ts +18 -0
  187. package/src/site/fields.ts +59 -0
  188. package/src/site/index.ts +14 -0
  189. package/src/site/replies.ts +118 -0
  190. package/src/site/store.ts +45 -0
  191. package/src/site/types.ts +75 -0
  192. package/src/site/version.ts +1 -0
  193. package/src/site/wire.ts +125 -0
  194. package/src/switcher/LanguageSwitcher.tsx +42 -0
  195. package/src/test-preview/README.md +90 -0
  196. package/src/test-preview/binding.ts +54 -0
  197. package/src/test-preview/bridge.ts +16 -0
  198. package/src/test-preview/guard.ts +186 -0
  199. package/src/test-preview/media.ts +23 -0
  200. package/src/test-preview/protocol.d.mts +67 -0
  201. package/src/test-preview/protocol.mjs +222 -0
  202. package/src/test-preview/runtime.ts +168 -0
  203. package/src/test-preview/server.ts +10 -0
  204. package/src/testing/fake.ts +148 -0
  205. package/src/testing/index.ts +2 -0
  206. package/src/testing/seed.ts +2 -0
@@ -0,0 +1,129 @@
1
+ 'use client'
2
+ import { useEffect, useRef, type ReactNode } from 'react'
3
+ import { useFeatureOverlay, useSiteFeatures, type OverlayName } from './context'
4
+
5
+ /** Geometry-neutral dialog primitive for default and fully custom presentations. */
6
+ export function FeatureDialog({
7
+ name,
8
+ label,
9
+ children,
10
+ className = '',
11
+ }: {
12
+ name: OverlayName
13
+ label: string
14
+ children: ReactNode
15
+ className?: string
16
+ }) {
17
+ const { active, close } = useFeatureOverlay()
18
+ const { error, refresh } = useSiteFeatures()
19
+ const ref = useRef<HTMLDivElement>(null)
20
+ const opened = active === name
21
+ useEffect(() => {
22
+ if (!opened || !ref.current) return
23
+ const panel = ref.current
24
+ const focusable = () =>
25
+ Array.from(
26
+ panel.querySelectorAll<HTMLElement>(
27
+ 'button:not([disabled]), a[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])',
28
+ ),
29
+ ).filter(
30
+ (node) =>
31
+ node.tabIndex >= 0 &&
32
+ !node.matches(':disabled') &&
33
+ !node.closest('[hidden], [inert], [aria-hidden="true"]') &&
34
+ // Layout boxes account for display:none on any ancestor (and closed
35
+ // details), unlike checking only the control's own computed display.
36
+ node.getClientRects().length > 0 &&
37
+ getComputedStyle(node).visibility === 'visible',
38
+ )
39
+ // The provider owns its foreground modal (including native top-layer
40
+ // inertness). Keep this feature mounted, but do not compete for its focus.
41
+ const externalModalOwnsFocus = (target: EventTarget | null = document.activeElement) => {
42
+ if (
43
+ Array.from(document.querySelectorAll('dialog[open]')).some(
44
+ (dialog) => dialog.matches(':modal') && !dialog.contains(panel),
45
+ )
46
+ )
47
+ return true
48
+ const modal =
49
+ target instanceof HTMLElement
50
+ ? target.closest(
51
+ '[role="dialog"][aria-modal="true"], [role="alertdialog"][aria-modal="true"]',
52
+ )
53
+ : null
54
+ return Boolean(modal && modal !== panel && !modal.contains(panel))
55
+ }
56
+ const focus = () => (focusable()[0] ?? panel).focus()
57
+ if (!externalModalOwnsFocus()) focus()
58
+ // Capture ownership while a native dialog is still in the top layer. Its
59
+ // target handler may close/remove it before the bubbling feature handler.
60
+ const externalModalKeydowns = new WeakSet<KeyboardEvent>()
61
+ const captureKeydown = (event: KeyboardEvent) => {
62
+ if (externalModalOwnsFocus(event.target) || externalModalOwnsFocus()) {
63
+ externalModalKeydowns.add(event)
64
+ }
65
+ }
66
+ const keydown = (event: KeyboardEvent) => {
67
+ // Use the event origin too: a provider can close/remove its modal before
68
+ // Escape bubbles here. That same Escape must not dismiss this feature.
69
+ if (
70
+ externalModalKeydowns.has(event) ||
71
+ externalModalOwnsFocus(event.target) ||
72
+ externalModalOwnsFocus()
73
+ )
74
+ return
75
+ if (event.key === 'Escape') {
76
+ event.preventDefault()
77
+ close()
78
+ }
79
+ if (event.key !== 'Tab') return
80
+ const nodes = focusable()
81
+ const first = nodes[0] ?? panel
82
+ const last = nodes.at(-1) ?? panel
83
+ if (
84
+ !panel.contains(document.activeElement) ||
85
+ (event.shiftKey && document.activeElement === first) ||
86
+ (!event.shiftKey && document.activeElement === last) ||
87
+ document.activeElement === panel
88
+ ) {
89
+ event.preventDefault()
90
+ ;(event.shiftKey ? last : first).focus()
91
+ }
92
+ }
93
+ const contain = (event: FocusEvent) => {
94
+ if (!externalModalOwnsFocus(event.target) && !panel.contains(event.target as Node)) focus()
95
+ }
96
+ document.addEventListener('keydown', captureKeydown, true)
97
+ document.addEventListener('keydown', keydown)
98
+ document.addEventListener('focusin', contain)
99
+ return () => {
100
+ document.removeEventListener('keydown', captureKeydown, true)
101
+ document.removeEventListener('keydown', keydown)
102
+ document.removeEventListener('focusin', contain)
103
+ }
104
+ }, [opened, close])
105
+ if (!opened) return null
106
+ return (
107
+ <div
108
+ ref={ref}
109
+ role="dialog"
110
+ aria-modal={true}
111
+ aria-label={label}
112
+ tabIndex={-1}
113
+ className={`u90-feature-dialog ${className}`}
114
+ >
115
+ {children}
116
+ {error && (
117
+ <div className="u90-feature-retry">
118
+ {error}
119
+ <button type="button" onClick={() => void refresh()}>
120
+ Retry configuration
121
+ </button>
122
+ </div>
123
+ )}
124
+ <button type="button" onClick={close} aria-label={`Close ${label}`}>
125
+ Close
126
+ </button>
127
+ </div>
128
+ )
129
+ }
@@ -0,0 +1,337 @@
1
+ .u90-features {
2
+ --u90-feature-background: #fff;
3
+ --u90-feature-foreground: #17202a;
4
+ --u90-feature-accent: #1746a2;
5
+ --u90-feature-radius: 1rem;
6
+ --u90-feature-shadow: 0 8px 40px #0003;
7
+ color: var(--u90-feature-foreground);
8
+ font:
9
+ 1rem/1.5 system-ui,
10
+ sans-serif;
11
+ }
12
+ .u90-features .u90-feature-dialog {
13
+ background: var(--u90-feature-background);
14
+ color: var(--u90-feature-foreground);
15
+ border-radius: var(--u90-feature-radius);
16
+ box-shadow: var(--u90-feature-shadow);
17
+ padding: 1.5rem;
18
+ }
19
+ .u90-features :focus-visible {
20
+ outline: 3px solid var(--u90-feature-accent);
21
+ outline-offset: 3px;
22
+ }
23
+ .u90-features .u90-feature-announcement {
24
+ position: absolute;
25
+ width: 1px;
26
+ height: 1px;
27
+ padding: 0;
28
+ overflow: hidden;
29
+ clip-path: inset(50%);
30
+ white-space: nowrap;
31
+ }
32
+ .u90-features .u90-feature-retry {
33
+ background: var(--u90-feature-background);
34
+ color: var(--u90-feature-foreground);
35
+ padding: 1rem;
36
+ }
37
+ .u90-features .u90-feedback-launcher {
38
+ position: fixed;
39
+ right: 1rem;
40
+ bottom: 1rem;
41
+ z-index: 90;
42
+ background: var(--u90-feature-accent);
43
+ color: #fff;
44
+ padding: 0.75rem 1.25rem;
45
+ border: 0;
46
+ border-radius: var(--u90-feature-radius);
47
+ cursor: pointer;
48
+ }
49
+ .u90-features .u90-feedback-panel {
50
+ position: fixed;
51
+ z-index: 100;
52
+ right: 1rem;
53
+ bottom: 4.5rem;
54
+ width: min(24rem, calc(100vw - 2rem));
55
+ max-height: calc(100dvh - 6rem);
56
+ overflow: auto;
57
+ }
58
+ .u90-features .u90-feedback-panel h2 {
59
+ font-size: 1.25rem;
60
+ font-weight: 700;
61
+ }
62
+ .u90-features .u90-feedback-page {
63
+ font-size: 0.875rem;
64
+ overflow-wrap: anywhere;
65
+ margin: 0.75rem 0;
66
+ }
67
+ .u90-features .u90-feedback-ratings {
68
+ display: flex;
69
+ gap: 1rem;
70
+ margin: 0.75rem 0;
71
+ }
72
+ .u90-features .u90-feedback-ratings label {
73
+ display: flex;
74
+ align-items: center;
75
+ gap: 0.25rem;
76
+ }
77
+ .u90-features .u90-feedback-comment {
78
+ display: block;
79
+ margin: 1rem 0;
80
+ }
81
+ .u90-features .u90-feedback-comment textarea {
82
+ display: block;
83
+ width: 100%;
84
+ padding: 0.5rem;
85
+ border: 1px solid currentColor;
86
+ border-radius: 0.25rem;
87
+ background: var(--u90-feature-background);
88
+ color: var(--u90-feature-foreground);
89
+ }
90
+ .u90-features .u90-feedback-panel button {
91
+ cursor: pointer;
92
+ padding: 0.5rem 0.75rem;
93
+ margin: 0.25rem 0.25rem 0.25rem 0;
94
+ border: 1px solid currentColor;
95
+ border-radius: 0.25rem;
96
+ }
97
+ .u90-features .u90-feedback-submit {
98
+ background: var(--u90-feature-accent);
99
+ color: #fff;
100
+ }
101
+ @media (max-width: 767px) {
102
+ .u90-features .u90-feedback-panel {
103
+ bottom: 0;
104
+ right: 0;
105
+ width: 100%;
106
+ max-height: 85dvh;
107
+ border-radius: var(--u90-feature-radius) var(--u90-feature-radius) 0 0;
108
+ }
109
+ }
110
+ /* Ask AI owns its presentation variables; saved appearance does not recolour Feedback. */
111
+ .u90-features .u90-ai-launcher {
112
+ position: fixed;
113
+ right: 1rem;
114
+ bottom: 4.5rem;
115
+ z-index: 90;
116
+ background: var(--u90-ai-accent);
117
+ color: var(--u90-ai-on-accent);
118
+ padding: 0.75rem 1.25rem;
119
+ border: 0;
120
+ border-radius: var(--u90-ai-radius);
121
+ cursor: pointer;
122
+ }
123
+ .u90-features .u90-ai-panel {
124
+ position: fixed;
125
+ z-index: 100;
126
+ right: 1rem;
127
+ top: calc(var(--u90-ai-top) + 1rem);
128
+ width: min(28rem, calc(100vw - 2rem));
129
+ height: min(42rem, calc(var(--u90-ai-height) - 2rem));
130
+ display: flex;
131
+ flex-direction: column;
132
+ gap: 0.5rem;
133
+ border-radius: var(--u90-ai-radius);
134
+ box-sizing: border-box;
135
+ overflow: hidden;
136
+ }
137
+ .u90-ai-panel h2 {
138
+ font-size: 1.4rem;
139
+ font-weight: 700;
140
+ flex-shrink: 0;
141
+ }
142
+ .u90-ai-panel button {
143
+ padding: 0.4rem 0.65rem;
144
+ border: 1px solid currentColor;
145
+ border-radius: 0.3rem;
146
+ cursor: pointer;
147
+ font: inherit;
148
+ }
149
+ .u90-ai-panel button:disabled {
150
+ opacity: 0.5;
151
+ cursor: default;
152
+ }
153
+ .u90-ai-panel :focus-visible {
154
+ outline: 3px solid currentColor;
155
+ outline-offset: 3px;
156
+ }
157
+ .u90-ai-log {
158
+ flex: 1;
159
+ min-height: 4rem;
160
+ overflow: auto;
161
+ overscroll-behavior: contain;
162
+ padding: 0.4rem;
163
+ }
164
+ .u90-ai-message {
165
+ margin: 0 0 1rem;
166
+ padding: 0.75rem;
167
+ background: #f2f4f6;
168
+ border-radius: 0.5rem;
169
+ overflow-wrap: anywhere;
170
+ }
171
+ .u90-ai-user {
172
+ background: #e8edf6;
173
+ }
174
+ .u90-ai-role {
175
+ font-size: 0.8rem;
176
+ font-weight: 700;
177
+ margin: 0 0 0.25rem;
178
+ }
179
+ .u90-ai-text {
180
+ white-space: pre-wrap;
181
+ margin: 0;
182
+ }
183
+ .u90-ai-message ul {
184
+ margin: 0.6rem 0;
185
+ padding-left: 1.2rem;
186
+ }
187
+ .u90-ai-message a {
188
+ text-decoration: underline;
189
+ }
190
+ .u90-ai-ratings {
191
+ display: flex;
192
+ gap: 0.5rem;
193
+ margin-top: 0.5rem;
194
+ font-size: 0.8rem;
195
+ }
196
+ .u90-ai-ratings [aria-pressed='true'] {
197
+ font-weight: bold;
198
+ outline: 2px solid currentColor;
199
+ }
200
+ .u90-ai-composer {
201
+ flex-shrink: 0;
202
+ }
203
+ .u90-ai-composer label {
204
+ display: block;
205
+ font-size: 0.85rem;
206
+ }
207
+ .u90-ai-composer textarea {
208
+ display: block;
209
+ box-sizing: border-box;
210
+ width: 100%;
211
+ resize: none;
212
+ border: 1px solid #64748b;
213
+ border-radius: 0.25rem;
214
+ background: #fff;
215
+ color: #17202a;
216
+ padding: 0.5rem;
217
+ font: inherit;
218
+ font-size: 1rem;
219
+ }
220
+ .u90-ai-composer > div {
221
+ display: flex;
222
+ gap: 0.5rem;
223
+ margin-top: 0.4rem;
224
+ flex-wrap: wrap;
225
+ }
226
+ .u90-ai-panel .u90-ai-send {
227
+ background: var(--u90-ai-accent, #1746a2);
228
+ color: var(--u90-ai-on-accent, #fff);
229
+ border-color: transparent;
230
+ }
231
+ .u90-ai-progress {
232
+ font-size: 0.8rem;
233
+ min-height: 1.2em;
234
+ margin: 0;
235
+ }
236
+ .u90-ai-error {
237
+ font-size: 0.85rem;
238
+ max-height: 7rem;
239
+ overflow: auto;
240
+ color: #8c2525;
241
+ }
242
+ .u90-ai-error p {
243
+ margin: 0.2rem 0;
244
+ }
245
+ .u90-ai-error button {
246
+ margin-right: 0.4rem;
247
+ }
248
+ .u90-ai-state {
249
+ font-size: 0.8rem;
250
+ font-weight: 600;
251
+ }
252
+ .u90-ai-suggestions {
253
+ display: flex;
254
+ gap: 0.4rem;
255
+ flex-wrap: wrap;
256
+ font-size: 0.85rem;
257
+ }
258
+ @media (max-width: 767px) {
259
+ .u90-features .u90-ai-panel {
260
+ right: 0;
261
+ top: calc(var(--u90-ai-top) + 0.25rem);
262
+ width: 100%;
263
+ height: calc(var(--u90-ai-height) - 0.5rem);
264
+ padding: 1rem;
265
+ border-radius: var(--u90-ai-radius) var(--u90-ai-radius) 0 0;
266
+ }
267
+ .u90-ai-composer {
268
+ padding-bottom: env(safe-area-inset-bottom);
269
+ }
270
+ }
271
+ @media (prefers-reduced-motion: reduce) {
272
+ .u90-ai * {
273
+ scroll-behavior: auto !important;
274
+ animation: none !important;
275
+ transition: none !important;
276
+ }
277
+ }
278
+
279
+ /* Native verification dialogs live outside .u90-features in the top layer.
280
+ * Scope their host-reset protection and preserve the shared theme override hooks. */
281
+ .u90-verification-dialog {
282
+ position: fixed;
283
+ inset: 0;
284
+ box-sizing: border-box;
285
+ margin: auto;
286
+ width: min(24rem, calc(100vw - 2rem));
287
+ height: fit-content;
288
+ max-height: calc(100dvh - 2rem);
289
+ overflow: auto;
290
+ padding: 1.25rem;
291
+ border: 1px solid #8886;
292
+ border-radius: var(--u90-feature-radius, 1rem);
293
+ background: var(--u90-feature-background, #fff);
294
+ color: var(--u90-feature-foreground, #17202a);
295
+ box-shadow: var(--u90-feature-shadow, 0 8px 40px #0003);
296
+ font:
297
+ 1rem/1.5 system-ui,
298
+ sans-serif;
299
+ }
300
+ .u90-verification-dialog[open] {
301
+ display: flex;
302
+ flex-direction: column;
303
+ gap: 1rem;
304
+ }
305
+ .u90-verification-dialog::backdrop {
306
+ background: #0006;
307
+ }
308
+ .u90-verification-dialog > p {
309
+ color: inherit;
310
+ margin: 0;
311
+ font-weight: 600;
312
+ }
313
+ .u90-verification-dialog > div {
314
+ max-width: 100%;
315
+ }
316
+ .u90-verification-dialog button {
317
+ padding: 0.6rem 1rem;
318
+ border: 1px solid currentColor;
319
+ border-radius: 999px;
320
+ background: transparent;
321
+ color: inherit;
322
+ font: inherit;
323
+ cursor: pointer;
324
+ }
325
+ .u90-verification-dialog :focus-visible {
326
+ outline: 3px solid var(--u90-feature-accent, #1746a2);
327
+ outline-offset: 3px;
328
+ }
329
+ @media (prefers-color-scheme: dark) {
330
+ .u90-verification-dialog {
331
+ background: var(--u90-feature-background, #17202a);
332
+ color: var(--u90-feature-foreground, #f8fafc);
333
+ }
334
+ .u90-verification-dialog :focus-visible {
335
+ outline-color: var(--u90-feature-accent, #8ab4ff);
336
+ }
337
+ }