@yunlefun/vue 0.1.3 → 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.
- package/README.md +6 -0
- package/components/YlfAiPromptLauncher.vue +325 -0
- package/components/YlfSegmentedControl.vue +180 -0
- package/components/YlfSelect.vue +39 -11
- package/components/YlfTooltip.vue +3 -1
- package/components/_ai-providers/README.md +9 -0
- package/components/_ai-providers/chatgpt.svg +1 -0
- package/components/_ai-providers/claude.svg +1 -0
- package/components/_ai-providers/cursor.svg +1 -0
- package/components/_ai-providers/deepseek.svg +6 -0
- package/components/_ai-providers/doubao.png +0 -0
- package/components/_ai-providers/yuanbao.png +0 -0
- package/components/ai-prompt-launcher/demo.vue +14 -0
- package/components/ai-prompt-launcher/index.md +116 -0
- package/components/ai-prompt.ts +103 -0
- package/components/ai-provider-logos.ts +11 -0
- package/components/segmented-control/demo.vue +21 -0
- package/components/segmented-control/index.md +41 -0
- package/components/segmented-control.ts +8 -0
- package/components/select/index.md +4 -0
- package/components/tooltip/index.md +2 -0
- package/dist/ai-prompt.cjs +65 -0
- package/dist/ai-prompt.d.cts +36 -0
- package/dist/ai-prompt.d.mts +36 -0
- package/dist/ai-prompt.d.ts +36 -0
- package/dist/ai-prompt.mjs +61 -0
- package/dist/index.d.cts +10 -1
- package/dist/index.d.mts +10 -1
- package/dist/index.d.ts +10 -1
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -39,3 +39,9 @@ export default defineNuxtConfig({
|
|
|
39
39
|
根入口目前提供类型和组件目录信息,组件通过 `components/*.vue` 子路径或 Nuxt 模块使用;没有公开的 `YlfResolver`。
|
|
40
40
|
|
|
41
41
|
更多内容见 [Vue 接入](./index.md)、[子包职责](../guide/packages.md)与[设计体系](../guide/design-system.md)。
|
|
42
|
+
|
|
43
|
+
## AI 提示词入口
|
|
44
|
+
|
|
45
|
+
`YlfAiPromptLauncher` 统一元宝、豆包、DeepSeek、ChatGPT、Claude 与 Cursor 的 logo、带提示词跳转和完整复制兜底。`@yunlefun/vue/ai-prompt` 提供可用于 CMS 主进程的纯链接构建函数;Vue 组件通过 `launch` / `copyText` 对接宿主。
|
|
46
|
+
|
|
47
|
+
接口、站点能力与接入示例见 [AI 提示词入口](./components/ai-prompt-launcher/index.md)。
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { AiPromptLaunch, AiPromptLaunchResult, AiPromptOptions, AiPromptTarget, AiProviderId } from './ai-prompt'
|
|
3
|
+
import { computed, nextTick, shallowRef, useId, useTemplateRef, watch } from 'vue'
|
|
4
|
+
import { aiProviders, buildAiPromptTarget, copyAndOpenAiPrompt } from './ai-prompt'
|
|
5
|
+
import { aiProviderLogos } from './ai-provider-logos'
|
|
6
|
+
|
|
7
|
+
const props = withDefaults(defineProps<{
|
|
8
|
+
prompt: string
|
|
9
|
+
providers?: readonly AiProviderId[]
|
|
10
|
+
disabled?: boolean
|
|
11
|
+
locale?: 'zh-CN' | 'en'
|
|
12
|
+
options?: AiPromptOptions
|
|
13
|
+
/** Optional host adapter, e.g. a validated desktop IPC call. */
|
|
14
|
+
launch?: AiPromptLaunch
|
|
15
|
+
/** Optional native clipboard adapter. */
|
|
16
|
+
copyText?: (text: string) => Promise<void>
|
|
17
|
+
}>(), { locale: 'zh-CN' })
|
|
18
|
+
const emit = defineEmits<{
|
|
19
|
+
result: [result: AiPromptLaunchResult]
|
|
20
|
+
busy: [value: boolean]
|
|
21
|
+
/** User copied from the full-prompt field, including the manual fallback. */
|
|
22
|
+
manualCopy: [prompt: string]
|
|
23
|
+
}>()
|
|
24
|
+
const id = useId()
|
|
25
|
+
const busy = shallowRef(false)
|
|
26
|
+
const message = shallowRef('')
|
|
27
|
+
const expanded = shallowRef(false)
|
|
28
|
+
const fallback = shallowRef<AiPromptTarget>()
|
|
29
|
+
const manualPrompt = useTemplateRef<HTMLTextAreaElement>('manual-prompt')
|
|
30
|
+
const unavailable = computed(() => props.disabled || busy.value || !props.prompt.trim())
|
|
31
|
+
const targets = computed(() => [...new Set(props.providers ?? aiProviders.map(provider => provider.id))].map(provider => buildAiPromptTarget(provider, props.prompt, props.options)))
|
|
32
|
+
const text = (zh: string, en: string) => props.locale === 'en' ? en : zh
|
|
33
|
+
let revision = 0
|
|
34
|
+
watch(() => props.prompt, () => {
|
|
35
|
+
revision++
|
|
36
|
+
message.value = ''
|
|
37
|
+
fallback.value = undefined
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
function setBusy(value: boolean) {
|
|
41
|
+
busy.value = value
|
|
42
|
+
emit('busy', value)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function manualCopy() {
|
|
46
|
+
expanded.value = true
|
|
47
|
+
message.value = text('自动复制未成功,请手动复制下方完整提示词。', 'Copy failed. Select and copy the full prompt below.')
|
|
48
|
+
await nextTick()
|
|
49
|
+
manualPrompt.value?.focus()
|
|
50
|
+
manualPrompt.value?.select()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function copyOnly() {
|
|
54
|
+
if (unavailable.value)
|
|
55
|
+
return
|
|
56
|
+
const currentRevision = revision
|
|
57
|
+
setBusy(true)
|
|
58
|
+
try {
|
|
59
|
+
await (props.copyText ? props.copyText(props.prompt) : navigator.clipboard.writeText(props.prompt))
|
|
60
|
+
if (currentRevision === revision)
|
|
61
|
+
message.value = text('提示词已复制,可粘贴到你常用的 AI。', 'Prompt copied. Paste it into your preferred AI.')
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
if (currentRevision === revision)
|
|
65
|
+
await manualCopy()
|
|
66
|
+
}
|
|
67
|
+
finally { setBusy(false) }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function action(target: AiPromptTarget) {
|
|
71
|
+
return target.mode === 'link'
|
|
72
|
+
? text('带提示词打开', 'Open with prompt')
|
|
73
|
+
: text('复制并打开', 'Copy and open')
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function open(event: MouseEvent, target: AiPromptTarget) {
|
|
77
|
+
if (unavailable.value) {
|
|
78
|
+
event.preventDefault()
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
fallback.value = undefined
|
|
82
|
+
const currentRevision = revision
|
|
83
|
+
const prompt = props.prompt
|
|
84
|
+
if (target.mode === 'link' && !props.launch) {
|
|
85
|
+
message.value = text(`已前往 ${target.provider.name}。若未带入提示词,可在这里复制。`, `Opening ${target.provider.name}. If the prompt is missing, copy it here.`)
|
|
86
|
+
emit('result', { target, status: 'opened' })
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
event.preventDefault()
|
|
90
|
+
setBusy(true)
|
|
91
|
+
try {
|
|
92
|
+
const status = await (props.launch ? props.launch(target, prompt) : copyAndOpenAiPrompt(target, prompt))
|
|
93
|
+
emit('result', { target, status })
|
|
94
|
+
if (currentRevision !== revision)
|
|
95
|
+
return
|
|
96
|
+
if (status === 'copy-failed') {
|
|
97
|
+
fallback.value = target
|
|
98
|
+
await manualCopy()
|
|
99
|
+
}
|
|
100
|
+
else if (status === 'copied' || status === 'open-failed') {
|
|
101
|
+
fallback.value = target
|
|
102
|
+
message.value = status === 'copied'
|
|
103
|
+
? props.launch
|
|
104
|
+
? text('提示词已复制,但未能打开 AI,请重试或手动打开。', 'Prompt copied, but the AI could not open. Retry or open it manually.')
|
|
105
|
+
: text('提示词已复制,请点击下方链接继续。', 'Prompt copied. Use the link below to continue.')
|
|
106
|
+
: text('未能打开 AI,请重试或手动打开。', 'Could not open the AI. Retry or open it manually.')
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
message.value = target.mode === 'copy'
|
|
110
|
+
? text(`已复制,请在 ${target.provider.name} 粘贴并发送。`, `Copied. Paste and send in ${target.provider.name}.`)
|
|
111
|
+
: text(`已前往 ${target.provider.name}。若未带入提示词,可在这里复制。`, `Opening ${target.provider.name}. If the prompt is missing, copy it here.`)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
if (currentRevision === revision) {
|
|
116
|
+
fallback.value = target
|
|
117
|
+
message.value = text('未能打开 AI,请复制提示词后重试。', 'Could not open the AI. Copy the prompt and try again.')
|
|
118
|
+
}
|
|
119
|
+
emit('result', { target, status: 'open-failed' })
|
|
120
|
+
}
|
|
121
|
+
finally { setBusy(false) }
|
|
122
|
+
}
|
|
123
|
+
</script>
|
|
124
|
+
|
|
125
|
+
<template>
|
|
126
|
+
<div class="ylf-ai-launcher" :aria-busy="busy">
|
|
127
|
+
<nav class="ylf-ai-launcher__providers" :aria-label="text('选择 AI,带上提示词', 'Choose an AI for this prompt')">
|
|
128
|
+
<component
|
|
129
|
+
:is="target.mode === 'link' && !launch ? 'a' : 'button'"
|
|
130
|
+
v-for="target in targets"
|
|
131
|
+
:key="target.provider.id"
|
|
132
|
+
class="ylf-ai-launcher__provider"
|
|
133
|
+
:data-provider="target.provider.id"
|
|
134
|
+
:href="target.mode === 'link' && !launch && !unavailable ? target.url : undefined"
|
|
135
|
+
:type="target.mode === 'link' && !launch ? undefined : 'button'"
|
|
136
|
+
:disabled="target.mode === 'link' && !launch ? undefined : unavailable"
|
|
137
|
+
:aria-disabled="unavailable"
|
|
138
|
+
:tabindex="unavailable ? -1 : undefined"
|
|
139
|
+
:aria-label="`${action(target)} ${target.provider.name}`"
|
|
140
|
+
:title="target.reason === 'too-long' ? text('内容较长,将复制完整提示词后打开', 'Long prompt: copy the full text and open') : undefined"
|
|
141
|
+
:target="target.mode === 'link' && !launch ? '_blank' : undefined"
|
|
142
|
+
:rel="target.mode === 'link' && !launch ? 'noopener noreferrer' : undefined"
|
|
143
|
+
:referrerpolicy="target.mode === 'link' && !launch ? 'no-referrer' : undefined"
|
|
144
|
+
@click="open($event, target)"
|
|
145
|
+
>
|
|
146
|
+
<img :src="aiProviderLogos[target.provider.id]" alt="" width="40" height="40">
|
|
147
|
+
<span class="ylf-ai-launcher__label">
|
|
148
|
+
<strong>{{ target.provider.name }}</strong>
|
|
149
|
+
<span>{{ action(target) }}</span>
|
|
150
|
+
</span>
|
|
151
|
+
<svg class="ylf-ai-launcher__arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true"><path d="M7 17 17 7M7 7h10v10" /></svg>
|
|
152
|
+
</component>
|
|
153
|
+
</nav>
|
|
154
|
+
<div class="ylf-ai-launcher__actions">
|
|
155
|
+
<button type="button" :disabled="unavailable" @click="copyOnly">
|
|
156
|
+
{{ text('仅复制提示词', 'Copy prompt only') }}
|
|
157
|
+
</button>
|
|
158
|
+
<span>{{ text('支持时直接带入;内容过长时复制完整提示词。', 'Opens with your prompt when supported; copies long prompts in full.') }}</span>
|
|
159
|
+
</div>
|
|
160
|
+
<p v-if="message" class="ylf-ai-launcher__status" role="status">
|
|
161
|
+
{{ message }}
|
|
162
|
+
</p>
|
|
163
|
+
<a v-if="fallback && !launch" :href="fallback.provider.url" target="_blank" rel="noopener noreferrer" class="ylf-ai-launcher__fallback">{{ text('打开', 'Open') }} {{ fallback.provider.name }}</a>
|
|
164
|
+
<details class="ylf-ai-launcher__details" :open="expanded" @toggle="expanded = ($event.target as HTMLDetailsElement).open">
|
|
165
|
+
<summary>{{ text('查看 / 手动复制提示词', 'View / manually copy prompt') }}</summary>
|
|
166
|
+
<label :for="`${id}-prompt`">{{ text('完整提示词', 'Full prompt') }}</label>
|
|
167
|
+
<textarea :id="`${id}-prompt`" ref="manual-prompt" :value="prompt" readonly rows="8" @copy="emit('manualCopy', prompt)" />
|
|
168
|
+
</details>
|
|
169
|
+
</div>
|
|
170
|
+
</template>
|
|
171
|
+
|
|
172
|
+
<style scoped>
|
|
173
|
+
.ylf-ai-launcher {
|
|
174
|
+
--_ink: var(--ylf-ai-text, var(--ylf-c-text, #0f172a));
|
|
175
|
+
--_muted: var(--ylf-ai-muted, var(--ylf-c-text-2, #475569));
|
|
176
|
+
--_border: var(--ylf-ai-border, var(--ylf-c-border, #e2e8f0));
|
|
177
|
+
--_surface: var(--ylf-ai-surface, var(--ylf-c-surface, #fff));
|
|
178
|
+
--_soft: var(--ylf-ai-background, var(--ylf-c-bg-soft, #f8fafc));
|
|
179
|
+
--_brand: var(--ylf-ai-accent, var(--ylf-c-brand, #2563eb));
|
|
180
|
+
color: var(--_ink);
|
|
181
|
+
container: ylf-ai / inline-size;
|
|
182
|
+
}
|
|
183
|
+
.ylf-ai-launcher__providers {
|
|
184
|
+
display: grid;
|
|
185
|
+
grid-template-columns: repeat(var(--ylf-ai-columns, 3), minmax(0, 1fr));
|
|
186
|
+
gap: var(--ylf-ai-gap, 10px);
|
|
187
|
+
margin: 12px 0;
|
|
188
|
+
}
|
|
189
|
+
.ylf-ai-launcher__provider {
|
|
190
|
+
box-sizing: border-box;
|
|
191
|
+
display: flex;
|
|
192
|
+
align-items: center;
|
|
193
|
+
gap: var(--ylf-ai-item-gap, 10px);
|
|
194
|
+
min-width: 0;
|
|
195
|
+
min-height: var(--ylf-ai-item-height, 76px);
|
|
196
|
+
padding: var(--ylf-ai-item-padding, 12px);
|
|
197
|
+
border: 1px solid var(--_border);
|
|
198
|
+
border-radius: var(--ylf-ai-radius, var(--ylf-radius, 14px));
|
|
199
|
+
background: var(--_soft);
|
|
200
|
+
color: inherit;
|
|
201
|
+
text-decoration: none;
|
|
202
|
+
font: inherit;
|
|
203
|
+
text-align: left;
|
|
204
|
+
cursor: pointer;
|
|
205
|
+
transition:
|
|
206
|
+
border-color 160ms ease,
|
|
207
|
+
background-color 160ms ease;
|
|
208
|
+
}
|
|
209
|
+
.ylf-ai-launcher__provider:not([aria-disabled='true']):hover {
|
|
210
|
+
border-color: var(--_brand);
|
|
211
|
+
background: var(--_surface);
|
|
212
|
+
}
|
|
213
|
+
.ylf-ai-launcher__provider img {
|
|
214
|
+
box-sizing: border-box;
|
|
215
|
+
flex: none;
|
|
216
|
+
width: var(--ylf-ai-logo-size, 40px);
|
|
217
|
+
height: var(--ylf-ai-logo-size, 40px);
|
|
218
|
+
padding: 4px;
|
|
219
|
+
border-radius: 10px;
|
|
220
|
+
background: #fff;
|
|
221
|
+
object-fit: contain;
|
|
222
|
+
}
|
|
223
|
+
.ylf-ai-launcher__label {
|
|
224
|
+
display: grid;
|
|
225
|
+
gap: 3px;
|
|
226
|
+
min-width: 0;
|
|
227
|
+
}
|
|
228
|
+
.ylf-ai-launcher__label strong {
|
|
229
|
+
font-size: var(--ylf-ai-name-size, 14px);
|
|
230
|
+
line-height: 1.4;
|
|
231
|
+
}
|
|
232
|
+
.ylf-ai-launcher__label > span {
|
|
233
|
+
color: var(--_muted);
|
|
234
|
+
font-size: var(--ylf-ai-action-size, 12px);
|
|
235
|
+
line-height: 1.5;
|
|
236
|
+
}
|
|
237
|
+
.ylf-ai-launcher__arrow {
|
|
238
|
+
flex: none;
|
|
239
|
+
width: 16px;
|
|
240
|
+
height: 16px;
|
|
241
|
+
margin-left: auto;
|
|
242
|
+
color: var(--_muted);
|
|
243
|
+
}
|
|
244
|
+
.ylf-ai-launcher__actions {
|
|
245
|
+
display: flex;
|
|
246
|
+
flex-wrap: wrap;
|
|
247
|
+
align-items: center;
|
|
248
|
+
gap: 10px;
|
|
249
|
+
}
|
|
250
|
+
.ylf-ai-launcher__actions button {
|
|
251
|
+
min-height: 44px;
|
|
252
|
+
padding: 8px 14px;
|
|
253
|
+
border: 1px solid var(--_border);
|
|
254
|
+
border-radius: 999px;
|
|
255
|
+
background: var(--_surface);
|
|
256
|
+
color: var(--_ink);
|
|
257
|
+
font: inherit;
|
|
258
|
+
font-size: 13px;
|
|
259
|
+
cursor: pointer;
|
|
260
|
+
}
|
|
261
|
+
.ylf-ai-launcher__actions > span,
|
|
262
|
+
.ylf-ai-launcher__status {
|
|
263
|
+
color: var(--_muted);
|
|
264
|
+
font-size: 12px;
|
|
265
|
+
line-height: 1.7;
|
|
266
|
+
}
|
|
267
|
+
.ylf-ai-launcher__fallback {
|
|
268
|
+
display: inline-flex;
|
|
269
|
+
align-items: center;
|
|
270
|
+
min-height: 44px;
|
|
271
|
+
color: var(--_brand);
|
|
272
|
+
}
|
|
273
|
+
.ylf-ai-launcher__details {
|
|
274
|
+
margin-top: 8px;
|
|
275
|
+
font-size: 13px;
|
|
276
|
+
}
|
|
277
|
+
.ylf-ai-launcher__details summary {
|
|
278
|
+
display: list-item;
|
|
279
|
+
min-height: 44px;
|
|
280
|
+
padding: 12px 0;
|
|
281
|
+
box-sizing: border-box;
|
|
282
|
+
color: var(--_muted);
|
|
283
|
+
cursor: pointer;
|
|
284
|
+
}
|
|
285
|
+
.ylf-ai-launcher__details label {
|
|
286
|
+
display: block;
|
|
287
|
+
margin: 8px 0;
|
|
288
|
+
}
|
|
289
|
+
.ylf-ai-launcher__details textarea {
|
|
290
|
+
box-sizing: border-box;
|
|
291
|
+
width: 100%;
|
|
292
|
+
padding: 12px;
|
|
293
|
+
border: 1px solid var(--_border);
|
|
294
|
+
border-radius: 10px;
|
|
295
|
+
background: var(--_surface);
|
|
296
|
+
color: var(--_ink);
|
|
297
|
+
font: inherit;
|
|
298
|
+
font-size: 16px;
|
|
299
|
+
line-height: 1.7;
|
|
300
|
+
resize: vertical;
|
|
301
|
+
}
|
|
302
|
+
:is(button, a, textarea, summary):focus-visible {
|
|
303
|
+
outline: 3px solid var(--_brand);
|
|
304
|
+
outline-offset: 3px;
|
|
305
|
+
}
|
|
306
|
+
:is(button:disabled, [aria-disabled='true']) {
|
|
307
|
+
opacity: 0.5;
|
|
308
|
+
cursor: not-allowed;
|
|
309
|
+
}
|
|
310
|
+
@container ylf-ai (max-width: 620px) {
|
|
311
|
+
.ylf-ai-launcher__providers {
|
|
312
|
+
grid-template-columns: repeat(var(--ylf-ai-columns, 2), minmax(0, 1fr));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
@container ylf-ai (max-width: 400px) {
|
|
316
|
+
.ylf-ai-launcher__providers {
|
|
317
|
+
grid-template-columns: repeat(var(--ylf-ai-columns, 1), minmax(0, 1fr));
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
@media (prefers-reduced-motion: reduce) {
|
|
321
|
+
.ylf-ai-launcher__provider {
|
|
322
|
+
transition: none;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
</style>
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
<script setup lang="ts" generic="T extends string = string">
|
|
2
|
+
import type { YlfSegmentedOption } from './segmented-control'
|
|
3
|
+
import { ToggleGroupItem, ToggleGroupRoot } from 'reka-ui'
|
|
4
|
+
import YlfTooltip from './YlfTooltip.vue'
|
|
5
|
+
|
|
6
|
+
const props = withDefaults(defineProps<{
|
|
7
|
+
options: readonly YlfSegmentedOption<T>[]
|
|
8
|
+
/** Accessible name of the group, including for icon-only controls. */
|
|
9
|
+
label: string
|
|
10
|
+
size?: 'sm' | 'md'
|
|
11
|
+
iconOnly?: boolean
|
|
12
|
+
disabled?: boolean
|
|
13
|
+
}>(), {
|
|
14
|
+
size: 'md',
|
|
15
|
+
iconOnly: false,
|
|
16
|
+
disabled: false,
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
defineSlots<{
|
|
20
|
+
icon?: (props: { option: YlfSegmentedOption<T>, selected: boolean }) => unknown
|
|
21
|
+
}>()
|
|
22
|
+
const model = defineModel<T>({ required: true })
|
|
23
|
+
function updateValue(value: unknown): void {
|
|
24
|
+
const option = props.options.find(option => option.value === value)
|
|
25
|
+
// A view or filter always has one active choice, even when clicked again.
|
|
26
|
+
if (!props.disabled && option && !option.disabled)
|
|
27
|
+
model.value = option.value
|
|
28
|
+
}
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<template>
|
|
32
|
+
<ToggleGroupRoot
|
|
33
|
+
class="ylf-segmented"
|
|
34
|
+
:data-size="size"
|
|
35
|
+
:data-icon-only="iconOnly || undefined"
|
|
36
|
+
type="single"
|
|
37
|
+
orientation="horizontal"
|
|
38
|
+
:aria-label="label"
|
|
39
|
+
:disabled="disabled"
|
|
40
|
+
:model-value="model"
|
|
41
|
+
@update:model-value="updateValue"
|
|
42
|
+
>
|
|
43
|
+
<YlfTooltip v-for="option in options" :key="option.value" :content="option.label" :disabled="!iconOnly || disabled || option.disabled">
|
|
44
|
+
<ToggleGroupItem
|
|
45
|
+
class="ylf-segmented__item"
|
|
46
|
+
:value="option.value"
|
|
47
|
+
:disabled="option.disabled"
|
|
48
|
+
:aria-label="option.ariaLabel ?? option.label"
|
|
49
|
+
>
|
|
50
|
+
<span v-if="$slots.icon" class="ylf-segmented__icon" aria-hidden="true">
|
|
51
|
+
<slot name="icon" :option="option" :selected="model === option.value" />
|
|
52
|
+
</span>
|
|
53
|
+
<span v-if="!iconOnly || !$slots.icon" class="ylf-segmented__label">{{ option.label }}</span>
|
|
54
|
+
<span v-if="option.count !== undefined && !iconOnly" class="ylf-segmented__count">{{ option.count }}</span>
|
|
55
|
+
</ToggleGroupItem>
|
|
56
|
+
</YlfTooltip>
|
|
57
|
+
</ToggleGroupRoot>
|
|
58
|
+
</template>
|
|
59
|
+
|
|
60
|
+
<style>
|
|
61
|
+
.ylf-segmented {
|
|
62
|
+
--ylf-segmented-item-size: 36px;
|
|
63
|
+
display: inline-flex;
|
|
64
|
+
align-items: center;
|
|
65
|
+
flex: none;
|
|
66
|
+
box-sizing: border-box;
|
|
67
|
+
max-width: 100%;
|
|
68
|
+
gap: 2px;
|
|
69
|
+
padding: 3px;
|
|
70
|
+
border: 1px solid var(--ylf-c-border, #e2e8f0);
|
|
71
|
+
border-radius: var(--ylf-radius-sm, 10px);
|
|
72
|
+
background: var(--ylf-c-bg-soft, #f1f5f9);
|
|
73
|
+
font-family: var(--ylf-font-body, inherit);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.ylf-segmented[data-size='sm'] {
|
|
77
|
+
--ylf-segmented-item-size: 26px;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.ylf-segmented__item {
|
|
81
|
+
display: inline-flex;
|
|
82
|
+
align-items: center;
|
|
83
|
+
justify-content: center;
|
|
84
|
+
flex: 0 0 auto;
|
|
85
|
+
box-sizing: border-box;
|
|
86
|
+
min-width: var(--ylf-segmented-item-size);
|
|
87
|
+
height: var(--ylf-segmented-item-size);
|
|
88
|
+
gap: 6px;
|
|
89
|
+
padding: 0 10px;
|
|
90
|
+
border: 1px solid transparent;
|
|
91
|
+
border-radius: calc(var(--ylf-radius-sm, 10px) - 4px);
|
|
92
|
+
background: transparent;
|
|
93
|
+
color: var(--ylf-c-text-2, #475569);
|
|
94
|
+
font: inherit;
|
|
95
|
+
font-size: var(--ylf-text-sm, 14px);
|
|
96
|
+
font-weight: 500;
|
|
97
|
+
line-height: 1;
|
|
98
|
+
white-space: nowrap;
|
|
99
|
+
cursor: pointer;
|
|
100
|
+
transition:
|
|
101
|
+
background var(--ylf-duration-fast, 160ms),
|
|
102
|
+
color var(--ylf-duration-fast, 160ms),
|
|
103
|
+
box-shadow var(--ylf-duration-fast, 160ms);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.ylf-segmented[data-size='sm'] .ylf-segmented__item {
|
|
107
|
+
padding-inline: 8px;
|
|
108
|
+
font-size: 12px;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.ylf-segmented[data-icon-only] .ylf-segmented__item {
|
|
112
|
+
width: calc(var(--ylf-segmented-item-size) + 4px);
|
|
113
|
+
padding: 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.ylf-segmented__item:hover:not([data-disabled], [aria-pressed='true']) {
|
|
117
|
+
color: var(--ylf-c-text, #0f172a);
|
|
118
|
+
background: color-mix(in srgb, var(--ylf-c-surface, #fff) 60%, transparent);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.ylf-segmented__item[aria-pressed='true'] {
|
|
122
|
+
color: var(--ylf-c-brand, #2563eb);
|
|
123
|
+
background: var(--ylf-c-surface, #fff);
|
|
124
|
+
border-color: var(--ylf-c-border, #e2e8f0);
|
|
125
|
+
box-shadow: var(--ylf-shadow-sm);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.ylf-segmented__item:focus-visible {
|
|
129
|
+
position: relative;
|
|
130
|
+
z-index: 1;
|
|
131
|
+
outline: 2px solid var(--ylf-c-brand, #2563eb);
|
|
132
|
+
outline-offset: 2px;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.ylf-segmented__item[data-disabled] {
|
|
136
|
+
cursor: not-allowed;
|
|
137
|
+
opacity: 0.45;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.ylf-segmented__icon {
|
|
141
|
+
display: inline-flex;
|
|
142
|
+
align-items: center;
|
|
143
|
+
justify-content: center;
|
|
144
|
+
flex: none;
|
|
145
|
+
width: 16px;
|
|
146
|
+
height: 16px;
|
|
147
|
+
font-size: 16px;
|
|
148
|
+
line-height: 1;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.ylf-segmented__icon > :where(svg, i, span) {
|
|
152
|
+
width: 100%;
|
|
153
|
+
height: 100%;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.ylf-segmented__count {
|
|
157
|
+
min-width: 16px;
|
|
158
|
+
color: var(--ylf-c-text-3, #64748b);
|
|
159
|
+
font-size: 10px;
|
|
160
|
+
font-variant-numeric: tabular-nums;
|
|
161
|
+
text-align: center;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.ylf-segmented__item[aria-pressed='true'] .ylf-segmented__count {
|
|
165
|
+
color: inherit;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
@media (pointer: coarse) {
|
|
169
|
+
.ylf-segmented .ylf-segmented__item {
|
|
170
|
+
min-width: 44px;
|
|
171
|
+
min-height: 44px;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
@media (prefers-reduced-motion: reduce) {
|
|
176
|
+
.ylf-segmented__item {
|
|
177
|
+
transition: none;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
</style>
|
package/components/YlfSelect.vue
CHANGED
|
@@ -6,10 +6,13 @@ defineOptions({ inheritAttrs: false })
|
|
|
6
6
|
|
|
7
7
|
const props = withDefaults(defineProps<{
|
|
8
8
|
/** color 可使用 CSS 色值或公共 token,例如 var(--ylf-accent-blue) */
|
|
9
|
-
options: { label: string, value: T, disabled?: boolean, color?: string }[]
|
|
9
|
+
options: readonly { label: string, value: T, disabled?: boolean, color?: string }[]
|
|
10
|
+
/** Compact workbench or comfortable form density. */
|
|
11
|
+
size?: 'sm' | 'md'
|
|
10
12
|
placeholder?: string
|
|
11
13
|
disabled?: boolean
|
|
12
14
|
}>(), {
|
|
15
|
+
size: 'md',
|
|
13
16
|
placeholder: '请选择',
|
|
14
17
|
disabled: false,
|
|
15
18
|
})
|
|
@@ -20,19 +23,21 @@ const selectedOption = computed(() => props.options.find(option => option.value
|
|
|
20
23
|
|
|
21
24
|
<template>
|
|
22
25
|
<SelectRoot v-model="model" :disabled="disabled">
|
|
23
|
-
<SelectTrigger class="ylf-select__trigger" :aria-label="$attrs.id || $attrs['aria-labelledby'] ? undefined : placeholder" v-bind="$attrs">
|
|
26
|
+
<SelectTrigger class="ylf-select__trigger" :data-size="size" :aria-label="$attrs.id || $attrs['aria-labelledby'] ? undefined : placeholder" v-bind="$attrs">
|
|
24
27
|
<SelectValue :placeholder="placeholder" class="ylf-select__label">
|
|
25
28
|
<span v-if="selectedOption?.color" class="ylf-select__swatch" :style="{ backgroundColor: selectedOption.color }" aria-hidden="true" />
|
|
26
29
|
<span>{{ selectedOption?.label ?? placeholder }}</span>
|
|
27
30
|
</SelectValue>
|
|
28
31
|
<SelectIcon class="ylf-select__icon">
|
|
29
|
-
<
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
+
<slot name="icon">
|
|
33
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
34
|
+
<path d="M6 9l6 6 6-6" />
|
|
35
|
+
</svg>
|
|
36
|
+
</slot>
|
|
32
37
|
</SelectIcon>
|
|
33
38
|
</SelectTrigger>
|
|
34
39
|
<SelectPortal>
|
|
35
|
-
<SelectContent class="ylf-select__content" position="popper" :side-offset="6">
|
|
40
|
+
<SelectContent class="ylf-select__content" :data-size="size" position="popper" :side-offset="6">
|
|
36
41
|
<SelectViewport class="ylf-select__viewport">
|
|
37
42
|
<SelectItem
|
|
38
43
|
v-for="o in options"
|
|
@@ -46,9 +51,11 @@ const selectedOption = computed(() => props.options.find(option => option.value
|
|
|
46
51
|
<span>{{ o.label }}</span>
|
|
47
52
|
</SelectItemText>
|
|
48
53
|
<SelectItemIndicator class="ylf-select__check">
|
|
49
|
-
<
|
|
50
|
-
<
|
|
51
|
-
|
|
54
|
+
<slot name="indicator">
|
|
55
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
|
|
56
|
+
<path d="M5 12.5l4.5 4.5L19 7" />
|
|
57
|
+
</svg>
|
|
58
|
+
</slot>
|
|
52
59
|
</SelectItemIndicator>
|
|
53
60
|
</SelectItem>
|
|
54
61
|
</SelectViewport>
|
|
@@ -119,7 +126,7 @@ const selectedOption = computed(() => props.options.find(option => option.value
|
|
|
119
126
|
display: inline-flex;
|
|
120
127
|
color: var(--ylf-c-text-3, #64748b);
|
|
121
128
|
|
|
122
|
-
svg {
|
|
129
|
+
:where(svg, i, span) {
|
|
123
130
|
width: 18px;
|
|
124
131
|
height: 18px;
|
|
125
132
|
}
|
|
@@ -174,12 +181,33 @@ const selectedOption = computed(() => props.options.find(option => option.value
|
|
|
174
181
|
display: inline-flex;
|
|
175
182
|
color: var(--ylf-c-brand, #2563eb);
|
|
176
183
|
|
|
177
|
-
svg {
|
|
184
|
+
:where(svg, i, span) {
|
|
178
185
|
width: 15px;
|
|
179
186
|
height: 15px;
|
|
180
187
|
}
|
|
181
188
|
}
|
|
182
189
|
|
|
190
|
+
.ylf-select__trigger[data-size='sm'] {
|
|
191
|
+
min-width: 0;
|
|
192
|
+
min-height: 34px;
|
|
193
|
+
padding: 5px 10px;
|
|
194
|
+
font-size: 12px;
|
|
195
|
+
border-radius: var(--ylf-radius-sm, 10px);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.ylf-select__content[data-size='sm'] .ylf-select__item {
|
|
199
|
+
min-height: 30px;
|
|
200
|
+
padding: 5px 8px;
|
|
201
|
+
font-size: 12px;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
@media (pointer: coarse) {
|
|
205
|
+
.ylf-select__trigger[data-size='sm'],
|
|
206
|
+
.ylf-select__content[data-size='sm'] .ylf-select__item {
|
|
207
|
+
min-height: 44px;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
183
211
|
@keyframes ylf-pop-in {
|
|
184
212
|
from {
|
|
185
213
|
opacity: 0;
|
|
@@ -6,15 +6,17 @@ withDefaults(defineProps<{
|
|
|
6
6
|
content?: string
|
|
7
7
|
side?: 'top' | 'right' | 'bottom' | 'left'
|
|
8
8
|
delay?: number
|
|
9
|
+
disabled?: boolean
|
|
9
10
|
}>(), {
|
|
10
11
|
side: 'top',
|
|
11
12
|
delay: 300,
|
|
13
|
+
disabled: false,
|
|
12
14
|
})
|
|
13
15
|
</script>
|
|
14
16
|
|
|
15
17
|
<template>
|
|
16
18
|
<TooltipProvider :delay-duration="delay">
|
|
17
|
-
<TooltipRoot>
|
|
19
|
+
<TooltipRoot :disabled="disabled">
|
|
18
20
|
<TooltipTrigger as-child>
|
|
19
21
|
<slot />
|
|
20
22
|
</TooltipTrigger>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# AI provider marks
|
|
2
|
+
|
|
3
|
+
Bundled locally for recognition, not affiliation. Names and marks belong to their owners.
|
|
4
|
+
|
|
5
|
+
- Yuanbao: https://static.yuanbao.tencent.com/m/yuanbao-web/logo_with_bg.png
|
|
6
|
+
- Doubao: https://lf-flow-web-cdn.doubao.com/obj/flow-doubao/favicon/new-doubao/128x128.png
|
|
7
|
+
- DeepSeek: https://fe-static.deepseek.com/chat/favicon.svg
|
|
8
|
+
- Cursor: https://cursor.com/marketing-static/favicon-light.svg
|
|
9
|
+
- ChatGPT and Claude: `openai-icon` / `claude-icon` from `@iconify-json/logos@1.2.11` (SVG Logos collection). https://github.com/gilbarbara/logos
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 260"><path d="M239.184 106.203a64.72 64.72 0 0 0-5.576-53.103C219.452 28.459 191 15.784 163.213 21.74A65.586 65.586 0 0 0 52.096 45.22a64.72 64.72 0 0 0-43.23 31.36c-14.31 24.602-11.061 55.634 8.033 76.74a64.67 64.67 0 0 0 5.525 53.102c14.174 24.65 42.644 37.324 70.446 31.36a64.72 64.72 0 0 0 48.754 21.744c28.481.025 53.714-18.361 62.414-45.481a64.77 64.77 0 0 0 43.229-31.36c14.137-24.558 10.875-55.423-8.083-76.483m-97.56 136.338a48.4 48.4 0 0 1-31.105-11.255l1.535-.87l51.67-29.825a8.6 8.6 0 0 0 4.247-7.367v-72.85l21.845 12.636c.218.111.37.32.409.563v60.367c-.056 26.818-21.783 48.545-48.601 48.601M37.158 197.93a48.35 48.35 0 0 1-5.781-32.589l1.534.921l51.722 29.826a8.34 8.34 0 0 0 8.441 0l63.181-36.425v25.221a.87.87 0 0 1-.358.665l-52.335 30.184c-23.257 13.398-52.97 5.431-66.404-17.803M23.549 85.38a48.5 48.5 0 0 1 25.58-21.333v61.39a8.29 8.29 0 0 0 4.195 7.316l62.874 36.272l-21.845 12.636a.82.82 0 0 1-.767 0L41.353 151.53c-23.211-13.454-31.171-43.144-17.804-66.405zm179.466 41.695l-63.08-36.63L161.73 77.86a.82.82 0 0 1 .768 0l52.233 30.184a48.6 48.6 0 0 1-7.316 87.635v-61.391a8.54 8.54 0 0 0-4.4-7.213m21.742-32.69l-1.535-.922l-51.619-30.081a8.39 8.39 0 0 0-8.492 0L99.98 99.808V74.587a.72.72 0 0 1 .307-.665l52.233-30.133a48.652 48.652 0 0 1 72.236 50.391zM88.061 139.097l-21.845-12.585a.87.87 0 0 1-.41-.614V65.685a48.652 48.652 0 0 1 79.757-37.346l-1.535.87l-51.67 29.825a8.6 8.6 0 0 0-4.246 7.367zm11.868-25.58L128.067 97.3l28.188 16.218v32.434l-28.086 16.218l-28.188-16.218z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 257"><path fill="#d97757" d="m50.228 170.321l50.357-28.257l.843-2.463l-.843-1.361h-2.462l-8.426-.518l-28.775-.778l-24.952-1.037l-24.175-1.296l-6.092-1.297L0 125.796l.583-3.759l5.12-3.434l7.324.648l16.202 1.101l24.304 1.685l17.629 1.037l26.118 2.722h4.148l.583-1.685l-1.426-1.037l-1.101-1.037l-25.147-17.045l-27.22-18.017l-14.258-10.37l-7.713-5.25l-3.888-4.925l-1.685-10.758l7-7.713l9.397.649l2.398.648l9.527 7.323l20.35 15.75L94.817 91.9l3.889 3.24l1.555-1.102l.195-.777l-1.75-2.917l-14.453-26.118l-15.425-26.572l-6.87-11.018l-1.814-6.61c-.648-2.723-1.102-4.991-1.102-7.778l7.972-10.823L71.42 0l10.63 1.426l4.472 3.888l6.61 15.101l10.694 23.786l16.591 32.34l4.861 9.592l2.592 8.879l.973 2.722h1.685v-1.556l1.36-18.211l2.528-22.36l2.463-28.776l.843-8.1l4.018-9.722l7.971-5.25l6.222 2.981l5.12 7.324l-.713 4.73l-3.046 19.768l-5.962 30.98l-3.889 20.739h2.268l2.593-2.593l10.499-13.934l17.628-22.036l7.778-8.749l9.073-9.657l5.833-4.601h11.018l8.1 12.055l-3.628 12.443l-11.342 14.388l-9.398 12.184l-13.48 18.147l-8.426 14.518l.778 1.166l2.01-.194l30.46-6.481l16.462-2.982l19.637-3.37l8.88 4.148l.971 4.213l-3.5 8.62l-20.998 5.184l-24.628 4.926l-36.682 8.685l-.454.324l.519.648l16.526 1.555l7.065.389h17.304l32.21 2.398l8.426 5.574l5.055 6.805l-.843 5.184l-12.962 6.611l-17.498-4.148l-40.83-9.721l-14-3.5h-1.944v1.167l11.666 11.406l21.387 19.314l26.767 24.887l1.36 6.157l-3.434 4.86l-3.63-.518l-23.526-17.693l-9.073-7.972l-20.545-17.304h-1.36v1.814l4.73 6.935l25.017 37.59l1.296 11.536l-1.814 3.76l-6.481 2.268l-7.13-1.297l-14.647-20.544l-15.1-23.138l-12.185-20.739l-1.49.843l-7.194 77.448l-3.37 3.953l-7.778 2.981l-6.48-4.925l-3.436-7.972l3.435-15.749l4.148-20.544l3.37-16.333l3.046-20.285l1.815-6.74l-.13-.454l-1.49.194l-15.295 20.999l-23.267 31.433l-18.406 19.702l-4.407 1.75l-7.648-3.954l.713-7.064l4.277-6.286l25.47-32.405l15.36-20.092l9.917-11.6l-.065-1.686h-.583L44.07 198.125l-12.055 1.555l-5.185-4.86l.648-7.972l2.463-2.593l20.35-13.999z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" height="512" viewBox="0 0 512 512" width="512" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><clipPath id="a"><path d="m90 67h331.963v378.45h-331.963z"/></clipPath><path d="m511.998 325.499c0 7.124 0 14.24-.041 21.364-.034 5.999-.103 11.998-.267 17.99-.357 13.068-1.125 26.245-3.449 39.169-2.359 13.109-6.205 25.306-12.266 37.222-5.958 11.703-13.746 22.419-23.029 31.709-9.29 9.291-20 17.072-31.71 23.03-11.909 6.061-24.113 9.907-37.222 12.266-12.923 2.324-26.101 3.092-39.168 3.448-6 .165-11.992.233-17.991.268-7.123.048-14.24.041-21.364.041h-139c-7.124 0-14.24 0-21.364-.041-5.999-.035-11.998-.103-17.99-.268-13.068-.356-26.245-1.124-39.169-3.448-13.1089-2.359-25.3059-6.205-37.2218-12.266-11.7034-5.958-22.4195-13.746-31.7095-23.03-9.29-9.29-17.0717-19.999-23.0297-31.709-6.06075-11.909-9.90702-24.113-12.26552-37.222-2.32422-12.924-3.092103-26.101-3.448621-39.169-.164546-5.999-.2331072-11.991-.2673877-17.99-.02742442-7.124-.02742442-14.24-.02742442-21.364v-139c0-7.124 0-14.24.04113662-21.364.0342805-5.999.1028415-11.998.2673875-17.99.356518-13.068 1.124399-26.245 3.448619-39.169 2.3585-13.1091 6.20477-25.3061 12.26561-37.222 5.9579-11.7034 13.7464-22.4195 23.0296-31.7095 9.29-9.29 19.9993-17.0717 31.7095-23.0296 11.909-6.06082 24.1129-9.90709 37.2221-12.26559 12.923-2.32422 26.101-3.092101 39.169-3.448618 5.999-.164547 11.991-.2331075 17.99-.2673881 7.117-.03428046 14.233-.03428046 21.357-.03428046h139c7.124 0 14.24 0 21.364.04113656 5.999.0342806 11.998.102842 17.99.267388 13.068.356517 26.245 1.124402 39.169 3.448622 13.109 2.3585 25.306 6.20477 37.222 12.26553 11.703 5.958 22.419 13.7465 31.71 23.0297 9.29 9.29 17.071 19.9992 23.029 31.7094 6.061 11.9091 9.907 24.113 12.266 37.2222 2.324 12.923 3.092 26.101 3.448 39.169.165 5.999.233 11.991.268 17.99.048 7.123.041 14.24.041 21.364v139z" fill="#f7f7f4"/><path d="m186.498 3.99902h139c7.125 0 14.231-.00004 21.341.04102 5.985.0342 11.952.10221 17.903.26562h.001c13.003.35475 25.944 1.11709 38.569 3.3877 12.775 2.29828 24.592 6.03144 36.118 11.89354v-.001c10.974 5.5867 21.052 12.8384 29.848 21.4571l.847.8379c8.993 8.993 16.525 19.3593 22.292 30.6933l3.565-1.8135-3.565 1.8145c5.678 11.1575 9.36 22.6024 11.674 34.9208l.22 1.195c2.128 11.837 2.931 23.95 3.315 36.132l.072 2.438c.164 5.958.232 11.919.266 17.904v.004c.048 7.107.041 14.207.041 21.337v129.344l-.007-.007v9.656c0 7.125 0 14.231-.041 21.341-.034 5.985-.102 11.952-.266 17.903v.001c-.354 13.003-1.117 25.944-3.387 38.569-2.227 12.376-5.8 23.853-11.351 35.037l-.543 1.08c-5.766 11.327-13.306 21.701-22.293 30.695-8.993 8.993-19.361 16.526-30.695 22.293-11.518 5.861-23.342 9.595-36.116 11.894-11.837 2.128-23.95 2.931-36.132 3.315l-2.438.072c-5.958.164-11.919.232-17.904.266h-.004c-7.107.048-14.207.041-21.337.041h-139c-7.125 0-14.231 0-21.341-.041-5.985-.034-11.952-.102-17.903-.266h-.001c-13.003-.355-25.944-1.117-38.57-3.387-12.7742-2.299-24.5914-6.032-36.1164-11.894h.0009c-10.974-5.587-21.0533-12.837-29.8496-21.456l-.8466-.838c-8.7118-8.712-16.0531-18.713-21.7461-29.634l-.5459-1.06-.544-1.081c-5.3709-10.816-8.8904-21.919-11.12986-33.84l-.21973-1.196c-2.27061-12.625-3.03294-25.566-3.38769-38.569v-.001c-.16342-5.958-.23143-11.918-.26563-17.903-.02736-7.112-.02734-14.219-.02734-21.341v-139c0-7.125-.00005-14.231.04101-21.341.0342-5.985.10222-11.952.26563-17.903v-.001c.35475-13.003 1.11699-25.944 3.38769-38.57 2.29829-12.7743 6.03152-24.5916 11.89352-36.1166l-.0009-.001c5.7665-11.3269 13.3073-21.6999 22.2939-30.6934 8.9932-8.9932 19.36-16.5261 30.6943-22.2929h.001c11.5173-5.8615 23.3407-9.59617 36.1148-11.89455 11.837-2.12878 23.951-2.93017 36.133-3.31446l2.438-.07226c5.958-.16342 11.919-.23142 17.904-.26563l-.001-.00097c7.105-.03422 14.211-.03321 21.335-.03321z" stroke="#26251e" stroke-opacity=".2" stroke-width="8"/><g clip-path="url(#a)"><path d="m415.348 156.575-151.504-87.4699c-4.865-2.8094-10.868-2.8094-15.733 0l-151.4964 87.4699c-4.0897 2.361-6.6146 6.728-6.6146 11.458v176.383c0 4.73 2.5249 9.097 6.6146 11.459l151.5034 87.469c4.865 2.81 10.868 2.81 15.733 0l151.504-87.469c4.09-2.362 6.615-6.729 6.615-11.459v-176.383c0-4.73-2.525-9.097-6.615-11.458zm-9.517 18.528-146.254 253.319c-.989 1.707-3.599 1.01-3.599-.967v-165.871c0-3.314-1.771-6.38-4.645-8.044l-143.644-82.932c-1.707-.989-1.01-3.599.967-3.599h292.51c4.153 0 6.749 4.502 4.672 8.101h-.007z" fill="#26251e"/></g></svg>
|