clawport-ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/.env.example +35 -0
  2. package/BRANDING.md +131 -0
  3. package/CLAUDE.md +252 -0
  4. package/README.md +262 -0
  5. package/SETUP.md +337 -0
  6. package/app/agents/[id]/page.tsx +727 -0
  7. package/app/api/agents/route.ts +12 -0
  8. package/app/api/chat/[id]/route.ts +139 -0
  9. package/app/api/cron-runs/route.ts +13 -0
  10. package/app/api/crons/route.ts +12 -0
  11. package/app/api/kanban/chat/[id]/route.ts +119 -0
  12. package/app/api/kanban/chat-history/[ticketId]/route.ts +36 -0
  13. package/app/api/memory/route.ts +12 -0
  14. package/app/api/transcribe/route.ts +37 -0
  15. package/app/api/tts/route.ts +42 -0
  16. package/app/chat/[id]/page.tsx +10 -0
  17. package/app/chat/page.tsx +200 -0
  18. package/app/crons/page.tsx +870 -0
  19. package/app/docs/page.tsx +399 -0
  20. package/app/favicon.ico +0 -0
  21. package/app/globals.css +692 -0
  22. package/app/kanban/page.tsx +327 -0
  23. package/app/layout.tsx +45 -0
  24. package/app/memory/page.tsx +685 -0
  25. package/app/page.tsx +817 -0
  26. package/app/providers.tsx +37 -0
  27. package/app/settings/page.tsx +901 -0
  28. package/app/settings-provider.tsx +209 -0
  29. package/components/AgentAvatar.tsx +54 -0
  30. package/components/AgentNode.tsx +122 -0
  31. package/components/Breadcrumbs.tsx +126 -0
  32. package/components/DynamicFavicon.tsx +62 -0
  33. package/components/ErrorState.tsx +97 -0
  34. package/components/FeedView.tsx +494 -0
  35. package/components/GlobalSearch.tsx +571 -0
  36. package/components/GridView.tsx +532 -0
  37. package/components/ManorMap.tsx +157 -0
  38. package/components/MobileSidebar.tsx +251 -0
  39. package/components/NavLinks.tsx +271 -0
  40. package/components/OnboardingWizard.tsx +1067 -0
  41. package/components/Sidebar.tsx +115 -0
  42. package/components/ThemeToggle.tsx +108 -0
  43. package/components/chat/AgentList.tsx +537 -0
  44. package/components/chat/ConversationView.tsx +1047 -0
  45. package/components/chat/FileAttachment.tsx +140 -0
  46. package/components/chat/MediaPreview.tsx +111 -0
  47. package/components/chat/VoiceMessage.tsx +139 -0
  48. package/components/crons/PipelineGraph.tsx +327 -0
  49. package/components/crons/WeeklySchedule.tsx +630 -0
  50. package/components/docs/AgentsSection.tsx +209 -0
  51. package/components/docs/ApiReferenceSection.tsx +256 -0
  52. package/components/docs/ArchitectureSection.tsx +221 -0
  53. package/components/docs/ComponentsSection.tsx +253 -0
  54. package/components/docs/CronSystemSection.tsx +235 -0
  55. package/components/docs/DocSection.tsx +346 -0
  56. package/components/docs/GettingStartedSection.tsx +169 -0
  57. package/components/docs/ThemingSection.tsx +257 -0
  58. package/components/docs/TroubleshootingSection.tsx +200 -0
  59. package/components/kanban/AgentPicker.tsx +321 -0
  60. package/components/kanban/CreateTicketModal.tsx +333 -0
  61. package/components/kanban/KanbanBoard.tsx +70 -0
  62. package/components/kanban/KanbanColumn.tsx +166 -0
  63. package/components/kanban/TicketCard.tsx +245 -0
  64. package/components/kanban/TicketDetailPanel.tsx +850 -0
  65. package/components/ui/badge.tsx +48 -0
  66. package/components/ui/button.tsx +64 -0
  67. package/components/ui/card.tsx +92 -0
  68. package/components/ui/dialog.tsx +158 -0
  69. package/components/ui/scroll-area.tsx +58 -0
  70. package/components/ui/separator.tsx +28 -0
  71. package/components/ui/skeleton.tsx +27 -0
  72. package/components/ui/tabs.tsx +91 -0
  73. package/components/ui/tooltip.tsx +57 -0
  74. package/components.json +23 -0
  75. package/docs/API.md +648 -0
  76. package/docs/COMPONENTS.md +1059 -0
  77. package/docs/THEMING.md +795 -0
  78. package/lib/agents-registry.ts +35 -0
  79. package/lib/agents.json +282 -0
  80. package/lib/agents.test.ts +367 -0
  81. package/lib/agents.ts +32 -0
  82. package/lib/anthropic.test.ts +422 -0
  83. package/lib/anthropic.ts +220 -0
  84. package/lib/api-error.ts +16 -0
  85. package/lib/audio-recorder.test.ts +72 -0
  86. package/lib/audio-recorder.ts +169 -0
  87. package/lib/conversations.test.ts +331 -0
  88. package/lib/conversations.ts +117 -0
  89. package/lib/cron-pipelines.test.ts +69 -0
  90. package/lib/cron-pipelines.ts +58 -0
  91. package/lib/cron-runs.test.ts +118 -0
  92. package/lib/cron-runs.ts +67 -0
  93. package/lib/cron-utils.test.ts +222 -0
  94. package/lib/cron-utils.ts +160 -0
  95. package/lib/crons.test.ts +502 -0
  96. package/lib/crons.ts +114 -0
  97. package/lib/env.test.ts +44 -0
  98. package/lib/env.ts +14 -0
  99. package/lib/kanban/automation.test.ts +245 -0
  100. package/lib/kanban/automation.ts +143 -0
  101. package/lib/kanban/chat-store.test.ts +149 -0
  102. package/lib/kanban/chat-store.ts +81 -0
  103. package/lib/kanban/store.test.ts +238 -0
  104. package/lib/kanban/store.ts +98 -0
  105. package/lib/kanban/types.ts +50 -0
  106. package/lib/kanban/useAgentWork.ts +78 -0
  107. package/lib/memory.ts +45 -0
  108. package/lib/multimodal.test.ts +219 -0
  109. package/lib/multimodal.ts +68 -0
  110. package/lib/pipeline.integration.test.ts +343 -0
  111. package/lib/sanitize.ts +194 -0
  112. package/lib/settings.test.ts +137 -0
  113. package/lib/settings.ts +94 -0
  114. package/lib/styles.ts +24 -0
  115. package/lib/themes.ts +9 -0
  116. package/lib/transcribe.test.ts +141 -0
  117. package/lib/transcribe.ts +111 -0
  118. package/lib/types.ts +66 -0
  119. package/lib/utils.ts +6 -0
  120. package/lib/validation.test.ts +132 -0
  121. package/lib/validation.ts +80 -0
  122. package/next.config.ts +7 -0
  123. package/package.json +56 -0
  124. package/postcss.config.mjs +7 -0
  125. package/public/file.svg +1 -0
  126. package/public/globe.svg +1 -0
  127. package/public/next.svg +1 -0
  128. package/public/vercel.svg +1 -0
  129. package/public/window.svg +1 -0
  130. package/scripts/setup.mjs +215 -0
  131. package/tsconfig.json +34 -0
  132. package/vitest.config.ts +17 -0
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Transcribe audio via the server-side Whisper API.
3
+ * Returns the transcript text, or null if transcription failed.
4
+ */
5
+ export async function transcribeViaApi(audioBlob: Blob): Promise<string | null> {
6
+ try {
7
+ const form = new FormData()
8
+ form.append('audio', audioBlob, 'voice.webm')
9
+ const res = await fetch('/api/transcribe', { method: 'POST', body: form })
10
+ if (!res.ok) return null
11
+ const data = await res.json()
12
+ return data.text || null
13
+ } catch {
14
+ return null
15
+ }
16
+ }
17
+
18
+ /**
19
+ * Transcribe audio using the browser's Web Speech API (SpeechRecognition).
20
+ * This is a fallback that works without any server — uses the browser's
21
+ * built-in speech recognition engine.
22
+ *
23
+ * Note: This re-plays the audio through an Audio element and listens via
24
+ * SpeechRecognition. It requires the browser to support both APIs.
25
+ * Returns null if the browser doesn't support SpeechRecognition.
26
+ */
27
+ export async function transcribeViaBrowser(audioBlob: Blob): Promise<string | null> {
28
+ const SpeechRecognition = (
29
+ (globalThis as Record<string, unknown>).SpeechRecognition ||
30
+ (globalThis as Record<string, unknown>).webkitSpeechRecognition
31
+ ) as (new () => SpeechRecognitionInstance) | undefined
32
+
33
+ if (!SpeechRecognition) return null
34
+
35
+ return new Promise<string | null>((resolve) => {
36
+ const recognition = new SpeechRecognition()
37
+ recognition.continuous = false
38
+ recognition.interimResults = false
39
+ recognition.lang = 'en-US'
40
+
41
+ let resolved = false
42
+
43
+ recognition.onresult = (event: SpeechRecognitionEvent) => {
44
+ if (resolved) return
45
+ resolved = true
46
+ const transcript = event.results[0]?.[0]?.transcript || ''
47
+ resolve(transcript || null)
48
+ }
49
+
50
+ recognition.onerror = () => {
51
+ if (!resolved) { resolved = true; resolve(null) }
52
+ }
53
+
54
+ recognition.onnomatch = () => {
55
+ if (!resolved) { resolved = true; resolve(null) }
56
+ }
57
+
58
+ recognition.onend = () => {
59
+ if (!resolved) { resolved = true; resolve(null) }
60
+ }
61
+
62
+ // Play the audio so the mic picks it up — but SpeechRecognition
63
+ // uses the system mic, not internal audio. Instead, we start recognition
64
+ // alongside playback and hope the user's environment allows it.
65
+ // In practice this is best-effort.
66
+ const url = URL.createObjectURL(audioBlob)
67
+ const audio = new Audio(url)
68
+
69
+ recognition.start()
70
+ audio.play().catch(() => {})
71
+
72
+ // Timeout after 10 seconds
73
+ setTimeout(() => {
74
+ if (!resolved) {
75
+ resolved = true
76
+ recognition.stop()
77
+ resolve(null)
78
+ }
79
+ URL.revokeObjectURL(url)
80
+ }, 10000)
81
+ })
82
+ }
83
+
84
+ /**
85
+ * Transcribe audio with automatic fallback:
86
+ * 1. Try server-side Whisper API
87
+ * 2. Return result or null
88
+ */
89
+ export async function transcribe(audioBlob: Blob): Promise<{ text: string | null; source: 'whisper' | 'failed' }> {
90
+ const whisperResult = await transcribeViaApi(audioBlob)
91
+ if (whisperResult) return { text: whisperResult, source: 'whisper' }
92
+
93
+ return { text: null, source: 'failed' }
94
+ }
95
+
96
+ // Types for the Web Speech API (not in all TS libs)
97
+ interface SpeechRecognitionEvent {
98
+ results: { [index: number]: { [index: number]: { transcript: string } } }
99
+ }
100
+
101
+ interface SpeechRecognitionInstance {
102
+ continuous: boolean
103
+ interimResults: boolean
104
+ lang: string
105
+ onresult: ((event: SpeechRecognitionEvent) => void) | null
106
+ onerror: (() => void) | null
107
+ onnomatch: (() => void) | null
108
+ onend: (() => void) | null
109
+ start: () => void
110
+ stop: () => void
111
+ }
package/lib/types.ts ADDED
@@ -0,0 +1,66 @@
1
+ // Shared types for ClawPort
2
+
3
+ export interface Agent {
4
+ id: string // slug, e.g. "vera"
5
+ name: string // display name, e.g. "VERA"
6
+ title: string // role title, e.g. "Chief Strategy Officer"
7
+ reportsTo: string | null // parent agent id (null for root orchestrator)
8
+ directReports: string[] // child agent ids
9
+ soulPath: string | null // absolute path to SOUL.md
10
+ soul: string | null // full SOUL.md content
11
+ voiceId: string | null // ElevenLabs voice ID
12
+ color: string // hex color for node
13
+ emoji: string // emoji identifier
14
+ tools: string[] // list of tools this agent has access to
15
+ crons: CronJob[] // associated cron jobs
16
+ memoryPath: string | null
17
+ description: string // one-liner description
18
+ }
19
+
20
+ export interface CronDelivery {
21
+ mode: string
22
+ channel: string
23
+ to: string | null
24
+ }
25
+
26
+ export interface CronRun {
27
+ ts: number
28
+ jobId: string
29
+ status: 'ok' | 'error'
30
+ summary: string | null
31
+ error: string | null
32
+ durationMs: number
33
+ deliveryStatus: string | null
34
+ }
35
+
36
+ export interface CronJob {
37
+ id: string
38
+ name: string
39
+ schedule: string // raw cron expression
40
+ scheduleDescription: string // human-readable (e.g., "Daily at 8 AM")
41
+ timezone: string | null // extracted from schedule object if present
42
+ status: 'ok' | 'error' | 'idle'
43
+ lastRun: string | null
44
+ nextRun: string | null
45
+ lastError: string | null
46
+ agentId: string | null // which agent this belongs to (matched by name)
47
+ description: string | null
48
+ enabled: boolean
49
+ delivery: CronDelivery | null
50
+ lastDurationMs: number | null
51
+ consecutiveErrors: number
52
+ lastDeliveryStatus: string | null
53
+ }
54
+
55
+ export interface ChatMessage {
56
+ role: 'user' | 'assistant'
57
+ content: string
58
+ timestamp: number
59
+ }
60
+
61
+ export interface MemoryFile {
62
+ label: string
63
+ path: string
64
+ content: string
65
+ lastModified: string
66
+ }
package/lib/utils.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from "clsx"
2
+ import { twMerge } from "tailwind-merge"
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }
@@ -0,0 +1,132 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { validateMessages, validateChatMessages } from './validation'
3
+
4
+ // --- validateMessages ---
5
+
6
+ describe('validateMessages', () => {
7
+ it('accepts plain text messages', () => {
8
+ const result = validateMessages([
9
+ { role: 'user', content: 'hello' },
10
+ { role: 'assistant', content: 'hi there' },
11
+ ])
12
+ expect(result).toHaveLength(2)
13
+ expect(result[0].content).toBe('hello')
14
+ })
15
+
16
+ it('accepts multimodal content array with text parts', () => {
17
+ const result = validateMessages([
18
+ { role: 'user', content: [{ type: 'text', text: 'describe this' }] },
19
+ ])
20
+ expect(result).toHaveLength(1)
21
+ expect(Array.isArray(result[0].content)).toBe(true)
22
+ })
23
+
24
+ it('accepts image_url content parts', () => {
25
+ const result = validateMessages([
26
+ {
27
+ role: 'user',
28
+ content: [
29
+ { type: 'text', text: 'what is this?' },
30
+ { type: 'image_url', image_url: { url: 'data:image/png;base64,abc123' } },
31
+ ],
32
+ },
33
+ ])
34
+ expect(result).toHaveLength(1)
35
+ const parts = result[0].content as Array<{ type: string }>
36
+ expect(parts).toHaveLength(2)
37
+ expect(parts[1].type).toBe('image_url')
38
+ })
39
+
40
+ it('accepts system role messages', () => {
41
+ const result = validateMessages([
42
+ { role: 'system', content: 'You are helpful.' },
43
+ ])
44
+ expect(result[0].role).toBe('system')
45
+ })
46
+
47
+ it('throws on non-array input', () => {
48
+ expect(() => validateMessages('not an array')).toThrow('must be an array')
49
+ })
50
+
51
+ it('throws on invalid role', () => {
52
+ expect(() => validateMessages([{ role: 'admin', content: 'hi' }])).toThrow('role')
53
+ })
54
+
55
+ it('throws on missing content', () => {
56
+ expect(() => validateMessages([{ role: 'user' }])).toThrow('content')
57
+ })
58
+
59
+ it('throws on number content', () => {
60
+ expect(() => validateMessages([{ role: 'user', content: 42 }])).toThrow('content')
61
+ })
62
+
63
+ it('throws on empty content array', () => {
64
+ expect(() => validateMessages([{ role: 'user', content: [] }])).toThrow('content')
65
+ })
66
+
67
+ it('throws on malformed content part (missing type)', () => {
68
+ expect(() => validateMessages([
69
+ { role: 'user', content: [{ text: 'no type field' }] },
70
+ ])).toThrow('content')
71
+ })
72
+
73
+ it('throws on invalid content part type', () => {
74
+ expect(() => validateMessages([
75
+ { role: 'user', content: [{ type: 'video', url: 'x' }] },
76
+ ])).toThrow('content')
77
+ })
78
+
79
+ it('throws on image_url part without url string', () => {
80
+ expect(() => validateMessages([
81
+ { role: 'user', content: [{ type: 'image_url', image_url: {} }] },
82
+ ])).toThrow('content')
83
+ })
84
+
85
+ it('throws on text part without text string', () => {
86
+ expect(() => validateMessages([
87
+ { role: 'user', content: [{ type: 'text', text: 123 }] },
88
+ ])).toThrow('content')
89
+ })
90
+
91
+ it('throws on non-object message', () => {
92
+ expect(() => validateMessages([null])).toThrow('must be an object')
93
+ expect(() => validateMessages(['string'])).toThrow('must be an object')
94
+ })
95
+ })
96
+
97
+ // --- validateChatMessages (legacy wrapper) ---
98
+
99
+ describe('validateChatMessages', () => {
100
+ it('returns ok:true with validated messages', () => {
101
+ const result = validateChatMessages({
102
+ messages: [{ role: 'user', content: 'test' }],
103
+ })
104
+ expect(result.ok).toBe(true)
105
+ if (result.ok) {
106
+ expect(result.messages).toHaveLength(1)
107
+ }
108
+ })
109
+
110
+ it('returns ok:false for non-object body', () => {
111
+ const result = validateChatMessages(null)
112
+ expect(result.ok).toBe(false)
113
+ })
114
+
115
+ it('returns ok:false for invalid messages', () => {
116
+ const result = validateChatMessages({ messages: 'not an array' })
117
+ expect(result.ok).toBe(false)
118
+ })
119
+
120
+ it('accepts multimodal messages through legacy wrapper', () => {
121
+ const result = validateChatMessages({
122
+ messages: [{
123
+ role: 'user',
124
+ content: [
125
+ { type: 'text', text: 'check this' },
126
+ { type: 'image_url', image_url: { url: 'data:image/png;base64,x' } },
127
+ ],
128
+ }],
129
+ })
130
+ expect(result.ok).toBe(true)
131
+ })
132
+ })
@@ -0,0 +1,80 @@
1
+ // Validation for chat API messages — supports both plain text and multimodal (vision) content
2
+
3
+ export type TextContentPart = { type: 'text'; text: string }
4
+ export type ImageContentPart = { type: 'image_url'; image_url: { url: string } }
5
+ export type ContentPart = TextContentPart | ImageContentPart
6
+
7
+ export type MessageContent = string | ContentPart[]
8
+
9
+ export interface ApiMessage {
10
+ role: 'user' | 'assistant' | 'system'
11
+ content: MessageContent
12
+ }
13
+
14
+ function isValidContentPart(part: unknown): part is ContentPart {
15
+ if (typeof part !== 'object' || part === null) return false
16
+ const p = part as Record<string, unknown>
17
+
18
+ if (p.type === 'text') {
19
+ return typeof p.text === 'string'
20
+ }
21
+
22
+ if (p.type === 'image_url') {
23
+ const imgUrl = p.image_url
24
+ if (typeof imgUrl !== 'object' || imgUrl === null) return false
25
+ return typeof (imgUrl as Record<string, unknown>).url === 'string'
26
+ }
27
+
28
+ return false
29
+ }
30
+
31
+ function isValidContent(content: unknown): content is MessageContent {
32
+ if (typeof content === 'string') return true
33
+
34
+ if (Array.isArray(content)) {
35
+ return content.length > 0 && content.every(isValidContentPart)
36
+ }
37
+
38
+ return false
39
+ }
40
+
41
+ export function validateMessages(messages: unknown): ApiMessage[] {
42
+ if (!Array.isArray(messages)) {
43
+ throw new Error('messages must be an array')
44
+ }
45
+
46
+ return messages.map((msg, i) => {
47
+ if (typeof msg !== 'object' || msg === null) {
48
+ throw new Error(`messages[${i}] must be an object`)
49
+ }
50
+
51
+ const m = msg as Record<string, unknown>
52
+
53
+ if (typeof m.role !== 'string' || !['user', 'assistant', 'system'].includes(m.role)) {
54
+ throw new Error(`messages[${i}].role must be "user", "assistant", or "system"`)
55
+ }
56
+
57
+ if (!isValidContent(m.content)) {
58
+ throw new Error(`messages[${i}].content must be a string or array of content parts`)
59
+ }
60
+
61
+ return { role: m.role as ApiMessage['role'], content: m.content as MessageContent }
62
+ })
63
+ }
64
+
65
+ // Legacy export for backward compatibility with existing test
66
+ export type ValidatedChatMessage = ApiMessage
67
+ export type ValidationResult = { ok: true; messages: ApiMessage[] } | { ok: false; error: string }
68
+
69
+ export function validateChatMessages(body: unknown): ValidationResult {
70
+ if (body === null || typeof body !== 'object') {
71
+ return { ok: false, error: 'Request body must be a JSON object.' }
72
+ }
73
+ const { messages } = body as Record<string, unknown>
74
+ try {
75
+ const validated = validateMessages(messages)
76
+ return { ok: true, messages: validated }
77
+ } catch (err) {
78
+ return { ok: false, error: err instanceof Error ? err.message : 'Invalid messages' }
79
+ }
80
+ }
package/next.config.ts ADDED
@@ -0,0 +1,7 @@
1
+ import type { NextConfig } from "next";
2
+
3
+ const nextConfig: NextConfig = {
4
+ /* config options here */
5
+ };
6
+
7
+ export default nextConfig;
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "clawport-ui",
3
+ "version": "0.1.0",
4
+ "description": "Open-source dashboard for managing, monitoring, and chatting with your OpenClaw AI agents.",
5
+ "homepage": "https://clawport.dev",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/openclaw/clawport.git"
9
+ },
10
+ "bugs": {
11
+ "url": "https://github.com/openclaw/clawport/issues"
12
+ },
13
+ "keywords": [
14
+ "openclaw",
15
+ "ai-agents",
16
+ "dashboard",
17
+ "clawport",
18
+ "agent-management",
19
+ "next.js"
20
+ ],
21
+ "license": "MIT",
22
+ "scripts": {
23
+ "dev": "next dev",
24
+ "build": "next build",
25
+ "start": "next start",
26
+ "test": "vitest run",
27
+ "setup": "node scripts/setup.mjs"
28
+ },
29
+ "dependencies": {
30
+ "@xyflow/react": "^12.10.1",
31
+ "class-variance-authority": "^0.7.1",
32
+ "clsx": "^2.1.1",
33
+ "lucide-react": "^0.575.0",
34
+ "next": "16.1.6",
35
+ "openai": "^6.25.0",
36
+ "radix-ui": "^1.4.3",
37
+ "react": "19.2.3",
38
+ "react-dom": "19.2.3",
39
+ "tailwind-merge": "^3.5.0"
40
+ },
41
+ "devDependencies": {
42
+ "@tailwindcss/postcss": "^4",
43
+ "@testing-library/jest-dom": "^6.9.1",
44
+ "@testing-library/react": "^16.3.2",
45
+ "@types/node": "^20",
46
+ "@types/react": "^19",
47
+ "@types/react-dom": "^19",
48
+ "@vitejs/plugin-react": "^5.1.4",
49
+ "jsdom": "^28.1.0",
50
+ "shadcn": "^3.8.5",
51
+ "tailwindcss": "^4",
52
+ "tw-animate-css": "^1.4.0",
53
+ "typescript": "^5",
54
+ "vitest": "^4.0.18"
55
+ }
56
+ }
@@ -0,0 +1,7 @@
1
+ const config = {
2
+ plugins: {
3
+ "@tailwindcss/postcss": {},
4
+ },
5
+ };
6
+
7
+ export default config;
@@ -0,0 +1 @@
1
+ <svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>