@thehoneyjar/sigil-hud 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.
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@thehoneyjar/sigil-hud",
3
+ "version": "0.1.0",
4
+ "description": "Diagnostic HUD for Sigil - composable React components for development",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "src"
18
+ ],
19
+ "dependencies": {
20
+ "zustand": "^4.5.0"
21
+ },
22
+ "peerDependencies": {
23
+ "react": ">=18.0.0"
24
+ },
25
+ "optionalDependencies": {
26
+ "@thehoneyjar/sigil-anchor": "4.3.1",
27
+ "@thehoneyjar/sigil-diagnostics": "0.1.0",
28
+ "@thehoneyjar/sigil-lens": "0.1.0",
29
+ "@thehoneyjar/sigil-simulation": "0.1.0",
30
+ "@thehoneyjar/sigil-fork": "0.1.0"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^20.11.0",
34
+ "@types/react": "^18.0.0",
35
+ "react": "^18.0.0",
36
+ "tsup": "^8.0.0",
37
+ "typescript": "^5.0.0",
38
+ "@thehoneyjar/sigil-anchor": "4.3.1",
39
+ "@thehoneyjar/sigil-simulation": "0.1.0",
40
+ "@thehoneyjar/sigil-fork": "0.1.0",
41
+ "@thehoneyjar/sigil-lens": "0.1.0",
42
+ "@thehoneyjar/sigil-diagnostics": "0.1.0"
43
+ },
44
+ "sideEffects": false,
45
+ "license": "MIT",
46
+ "publishConfig": {
47
+ "access": "public",
48
+ "registry": "https://registry.npmjs.org/"
49
+ },
50
+ "repository": {
51
+ "type": "git",
52
+ "url": "https://github.com/0xHoneyJar/sigil.git",
53
+ "directory": "packages/hud"
54
+ },
55
+ "bugs": {
56
+ "url": "https://github.com/0xHoneyJar/sigil/issues"
57
+ },
58
+ "homepage": "https://github.com/0xHoneyJar/sigil/tree/main/packages/hud#readme",
59
+ "engines": {
60
+ "node": ">=20.0.0"
61
+ },
62
+ "scripts": {
63
+ "build": "tsup",
64
+ "dev": "tsup --watch",
65
+ "typecheck": "tsc --noEmit",
66
+ "clean": "rm -rf dist"
67
+ }
68
+ }
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Data Source Indicator Component
3
+ *
4
+ * Shows the source and staleness of displayed data values.
5
+ * Full implementation in TASK-202.
6
+ */
7
+
8
+ /**
9
+ * Data source type
10
+ */
11
+ export type DataSourceType = 'on-chain' | 'indexed' | 'cached' | 'unknown'
12
+
13
+ /**
14
+ * Props for DataSourceIndicator
15
+ */
16
+ export interface DataSourceIndicatorProps {
17
+ /** Data source type */
18
+ source: DataSourceType
19
+ /** Block number for on-chain data */
20
+ blockNumber?: number
21
+ /** Current block for staleness calculation */
22
+ currentBlock?: number
23
+ /** Timestamp for time-based staleness */
24
+ timestamp?: number
25
+ /** Whether to show expanded details */
26
+ expanded?: boolean
27
+ /** Callback when clicked */
28
+ onClick?: () => void
29
+ /** Custom class name */
30
+ className?: string
31
+ }
32
+
33
+ /**
34
+ * Source type colors and labels
35
+ */
36
+ const sourceConfig: Record<
37
+ DataSourceType,
38
+ { label: string; color: string; bgColor: string }
39
+ > = {
40
+ 'on-chain': { label: 'On-Chain', color: '#22c55e', bgColor: 'rgba(34, 197, 94, 0.1)' },
41
+ indexed: { label: 'Indexed', color: '#3b82f6', bgColor: 'rgba(59, 130, 246, 0.1)' },
42
+ cached: { label: 'Cached', color: '#eab308', bgColor: 'rgba(234, 179, 8, 0.1)' },
43
+ unknown: { label: 'Unknown', color: '#666', bgColor: 'rgba(100, 100, 100, 0.1)' },
44
+ }
45
+
46
+ /**
47
+ * Calculate staleness status
48
+ */
49
+ function getStaleness(props: DataSourceIndicatorProps): {
50
+ level: 'fresh' | 'stale' | 'very-stale'
51
+ label: string
52
+ } {
53
+ const { source, blockNumber, currentBlock, timestamp } = props
54
+
55
+ // Block-based staleness for on-chain data
56
+ if (source === 'on-chain' && blockNumber && currentBlock) {
57
+ const blocksBehind = currentBlock - blockNumber
58
+ if (blocksBehind <= 1) return { level: 'fresh', label: 'current' }
59
+ if (blocksBehind <= 5) return { level: 'stale', label: `${blocksBehind} blocks behind` }
60
+ return { level: 'very-stale', label: `${blocksBehind} blocks behind` }
61
+ }
62
+
63
+ // Time-based staleness for indexed/cached
64
+ if (timestamp) {
65
+ const secondsAgo = Math.floor((Date.now() - timestamp) / 1000)
66
+ if (secondsAgo < 30) return { level: 'fresh', label: 'just now' }
67
+ if (secondsAgo < 60) return { level: 'fresh', label: `${secondsAgo}s ago` }
68
+ if (secondsAgo < 300) return { level: 'stale', label: `${Math.floor(secondsAgo / 60)}m ago` }
69
+ return { level: 'very-stale', label: `${Math.floor(secondsAgo / 60)}m ago` }
70
+ }
71
+
72
+ return { level: 'fresh', label: '' }
73
+ }
74
+
75
+ /**
76
+ * Staleness color mapping
77
+ */
78
+ const stalenessColors = {
79
+ fresh: '#22c55e',
80
+ stale: '#eab308',
81
+ 'very-stale': '#ef4444',
82
+ }
83
+
84
+ /**
85
+ * Data Source Indicator component
86
+ */
87
+ export function DataSourceIndicator({
88
+ source,
89
+ blockNumber,
90
+ currentBlock,
91
+ timestamp,
92
+ expanded = false,
93
+ onClick,
94
+ className = '',
95
+ }: DataSourceIndicatorProps) {
96
+ const config = sourceConfig[source]
97
+ const staleness = getStaleness({ source, blockNumber, currentBlock, timestamp })
98
+
99
+ return (
100
+ <button
101
+ onClick={onClick}
102
+ className={className}
103
+ style={{
104
+ ...styles.container,
105
+ cursor: onClick ? 'pointer' : 'default',
106
+ }}
107
+ >
108
+ {/* Source Badge */}
109
+ <span
110
+ style={{
111
+ ...styles.badge,
112
+ backgroundColor: config.bgColor,
113
+ borderColor: `${config.color}40`,
114
+ color: config.color,
115
+ }}
116
+ >
117
+ {config.label}
118
+ </span>
119
+
120
+ {/* Staleness Indicator */}
121
+ {staleness.label && (
122
+ <span style={{ ...styles.staleness, color: stalenessColors[staleness.level] }}>
123
+ <span style={styles.dot}>●</span>
124
+ {staleness.label}
125
+ </span>
126
+ )}
127
+
128
+ {/* Expanded Details */}
129
+ {expanded && blockNumber && (
130
+ <div style={styles.expanded}>
131
+ <span style={styles.detailLabel}>Block:</span>
132
+ <span style={styles.detailValue}>#{blockNumber.toLocaleString()}</span>
133
+ </div>
134
+ )}
135
+ </button>
136
+ )
137
+ }
138
+
139
+ /**
140
+ * Styles
141
+ */
142
+ const styles: Record<string, React.CSSProperties> = {
143
+ container: {
144
+ display: 'inline-flex',
145
+ alignItems: 'center',
146
+ gap: '6px',
147
+ padding: '0',
148
+ backgroundColor: 'transparent',
149
+ border: 'none',
150
+ fontSize: '10px',
151
+ fontFamily: 'inherit',
152
+ },
153
+ badge: {
154
+ padding: '2px 6px',
155
+ borderRadius: '4px',
156
+ fontSize: '9px',
157
+ fontWeight: 600,
158
+ border: '1px solid',
159
+ textTransform: 'uppercase',
160
+ letterSpacing: '0.3px',
161
+ },
162
+ staleness: {
163
+ display: 'flex',
164
+ alignItems: 'center',
165
+ gap: '3px',
166
+ fontSize: '10px',
167
+ },
168
+ dot: {
169
+ fontSize: '6px',
170
+ },
171
+ expanded: {
172
+ display: 'flex',
173
+ gap: '4px',
174
+ marginLeft: '4px',
175
+ paddingLeft: '4px',
176
+ borderLeft: '1px solid rgba(255, 255, 255, 0.1)',
177
+ },
178
+ detailLabel: {
179
+ color: '#666',
180
+ },
181
+ detailValue: {
182
+ color: '#888',
183
+ fontFamily: 'monospace',
184
+ },
185
+ }
@@ -0,0 +1,444 @@
1
+ /**
2
+ * Diagnostics Panel Component
3
+ *
4
+ * Panel for physics compliance checking and issue detection.
5
+ * Implements TASK-201 requirements:
6
+ * - Physics analysis display
7
+ * - Data source indicators
8
+ * - Issue detection
9
+ * - Quick action buttons
10
+ */
11
+
12
+ import { useState, useCallback, useEffect } from 'react'
13
+ import { useHud } from '../providers/HudProvider'
14
+ import { PhysicsAnalysis } from './PhysicsAnalysis'
15
+ import { IssueList } from './IssueList'
16
+ import { DataSourceIndicator } from './DataSourceIndicator'
17
+ import type { EffectType, ComplianceResult, DiagnosticIssue } from '../types'
18
+
19
+ /**
20
+ * Props for DiagnosticsPanel
21
+ */
22
+ export interface DiagnosticsPanelProps {
23
+ /** Custom class name */
24
+ className?: string
25
+ }
26
+
27
+ /**
28
+ * Current analysis state
29
+ */
30
+ interface AnalysisState {
31
+ component: string | null
32
+ effect: EffectType | null
33
+ compliance: ComplianceResult | null
34
+ issues: DiagnosticIssue[]
35
+ isLoading: boolean
36
+ }
37
+
38
+ const initialAnalysisState: AnalysisState = {
39
+ component: null,
40
+ effect: null,
41
+ compliance: null,
42
+ issues: [],
43
+ isLoading: false,
44
+ }
45
+
46
+ /**
47
+ * Diagnostics panel for physics analysis
48
+ */
49
+ export function DiagnosticsPanel({ className = '' }: DiagnosticsPanelProps) {
50
+ const { diagnosticsService, activePanel, config } = useHud()
51
+ const [analysis, setAnalysis] = useState<AnalysisState>(initialAnalysisState)
52
+ const [symptom, setSymptom] = useState('')
53
+ const [diagnosis, setDiagnosis] = useState<string | null>(null)
54
+ const [isLoading, setIsLoading] = useState(false)
55
+ const [showSymptomInput, setShowSymptomInput] = useState(false)
56
+
57
+ // Analyze component when selected (placeholder - could be wired to component selection)
58
+ const analyzeComponent = useCallback(
59
+ async (componentName: string, code?: string) => {
60
+ if (!diagnosticsService) return
61
+
62
+ setAnalysis((prev) => ({ ...prev, isLoading: true }))
63
+ try {
64
+ const result = await diagnosticsService.analyze(componentName, code)
65
+ setAnalysis({
66
+ component: result.component,
67
+ effect: result.effect,
68
+ compliance: result.compliance,
69
+ issues: result.issues,
70
+ isLoading: false,
71
+ })
72
+ } catch (error) {
73
+ setAnalysis({
74
+ ...initialAnalysisState,
75
+ isLoading: false,
76
+ })
77
+ }
78
+ },
79
+ [diagnosticsService]
80
+ )
81
+
82
+ // Handle diagnose symptom
83
+ const handleDiagnose = useCallback(async () => {
84
+ if (!diagnosticsService || !symptom) return
85
+
86
+ setIsLoading(true)
87
+ try {
88
+ const result = diagnosticsService.diagnose(symptom)
89
+ setDiagnosis(result)
90
+ } catch (error) {
91
+ setDiagnosis(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`)
92
+ } finally {
93
+ setIsLoading(false)
94
+ }
95
+ }, [diagnosticsService, symptom])
96
+
97
+ // Handle clear
98
+ const handleClear = useCallback(() => {
99
+ setSymptom('')
100
+ setDiagnosis(null)
101
+ setShowSymptomInput(false)
102
+ }, [])
103
+
104
+ // Handle capture observation (quick action)
105
+ const handleCaptureObservation = useCallback(() => {
106
+ // Trigger observation capture modal via keyboard shortcut simulation
107
+ const event = new KeyboardEvent('keydown', {
108
+ key: 'o',
109
+ metaKey: true,
110
+ shiftKey: true,
111
+ })
112
+ window.dispatchEvent(event)
113
+ }, [])
114
+
115
+ // Handle record signal (quick action)
116
+ const handleRecordSignal = useCallback(() => {
117
+ // TODO: Implement signal recording - TASK-204
118
+ console.log('Record signal triggered')
119
+ }, [])
120
+
121
+ // Handle issue click
122
+ const handleIssueClick = useCallback((issue: DiagnosticIssue) => {
123
+ // Could open a detail panel or link to docs
124
+ console.log('Issue clicked:', issue.code)
125
+ }, [])
126
+
127
+ // Don't render if not the active panel
128
+ if (activePanel !== 'diagnostics') return null
129
+
130
+ // Show message if diagnostics service not available
131
+ if (!diagnosticsService) {
132
+ return (
133
+ <div className={className} style={styles.container}>
134
+ <div style={styles.header}>
135
+ <span style={styles.title}>Diagnostics</span>
136
+ </div>
137
+ <div style={styles.unavailable}>
138
+ <p style={styles.unavailableText}>Diagnostics service not available.</p>
139
+ <p style={styles.unavailableHint}>
140
+ Install @sigil/diagnostics to enable physics analysis.
141
+ </p>
142
+ </div>
143
+ </div>
144
+ )
145
+ }
146
+
147
+ return (
148
+ <div className={className} style={styles.container}>
149
+ {/* Header with Quick Actions */}
150
+ <div style={styles.header}>
151
+ <span style={styles.title}>Diagnostics</span>
152
+ <div style={styles.quickActions}>
153
+ {config.observationCapture && (
154
+ <button
155
+ onClick={handleCaptureObservation}
156
+ style={styles.quickActionButton}
157
+ title="Capture Observation (Cmd+Shift+O)"
158
+ >
159
+ 📸
160
+ </button>
161
+ )}
162
+ {config.signalCapture && (
163
+ <button
164
+ onClick={handleRecordSignal}
165
+ style={styles.quickActionButton}
166
+ title="Record Signal"
167
+ >
168
+ 📡
169
+ </button>
170
+ )}
171
+ </div>
172
+ </div>
173
+
174
+ {/* Physics Analysis Section */}
175
+ <PhysicsAnalysis
176
+ effect={analysis.effect}
177
+ compliance={analysis.compliance}
178
+ isLoading={analysis.isLoading}
179
+ />
180
+
181
+ {/* Data Source Indicator (placeholder for TASK-202) */}
182
+ <div style={styles.section}>
183
+ <span style={styles.sectionLabel}>Data Sources</span>
184
+ <div style={styles.dataSourceRow}>
185
+ <DataSourceIndicator source="on-chain" blockNumber={19234567} currentBlock={19234569} />
186
+ </div>
187
+ </div>
188
+
189
+ {/* Issues Section */}
190
+ <div style={styles.section}>
191
+ <IssueList
192
+ issues={analysis.issues}
193
+ onIssueClick={handleIssueClick}
194
+ maxVisible={3}
195
+ />
196
+ </div>
197
+
198
+ {/* Symptom Input (toggleable) */}
199
+ {!showSymptomInput ? (
200
+ <button
201
+ onClick={() => setShowSymptomInput(true)}
202
+ style={styles.showDiagnoseButton}
203
+ >
204
+ + Describe a symptom to diagnose
205
+ </button>
206
+ ) : (
207
+ <div style={styles.diagnoseSection}>
208
+ <label style={styles.inputLabel}>Describe the issue or symptom</label>
209
+ <textarea
210
+ value={symptom}
211
+ onChange={(e) => setSymptom(e.target.value)}
212
+ placeholder="e.g., Dialog flickers on open, Hydration mismatch, Button feels slow..."
213
+ rows={2}
214
+ style={styles.textarea}
215
+ />
216
+ <div style={styles.diagnoseActions}>
217
+ <button
218
+ onClick={handleDiagnose}
219
+ disabled={!symptom || isLoading}
220
+ style={{
221
+ ...styles.diagnoseButton,
222
+ opacity: symptom ? 1 : 0.5,
223
+ cursor: symptom ? 'pointer' : 'not-allowed',
224
+ }}
225
+ >
226
+ {isLoading ? 'Analyzing...' : 'Diagnose'}
227
+ </button>
228
+ <button onClick={handleClear} style={styles.cancelButton}>
229
+ Cancel
230
+ </button>
231
+ </div>
232
+ </div>
233
+ )}
234
+
235
+ {/* Diagnosis Result */}
236
+ {diagnosis && (
237
+ <div style={styles.diagnosisResult}>
238
+ <div
239
+ style={styles.diagnosisContent}
240
+ dangerouslySetInnerHTML={{
241
+ __html: formatDiagnosis(diagnosis),
242
+ }}
243
+ />
244
+ </div>
245
+ )}
246
+
247
+ {/* Quick Diagnose Buttons */}
248
+ <div style={styles.quickDiagnose}>
249
+ <span style={styles.quickDiagnoseLabel}>Quick Diagnose</span>
250
+ <div style={styles.quickDiagnoseGrid}>
251
+ {['hydration mismatch', 'dialog glitch', 'slow performance', 'layout shift'].map(
252
+ (quick) => (
253
+ <button
254
+ key={quick}
255
+ onClick={() => {
256
+ setSymptom(quick)
257
+ setShowSymptomInput(true)
258
+ }}
259
+ style={styles.quickDiagnoseButton}
260
+ >
261
+ {quick}
262
+ </button>
263
+ )
264
+ )}
265
+ </div>
266
+ </div>
267
+ </div>
268
+ )
269
+ }
270
+
271
+ /**
272
+ * Format diagnosis for display (simple markdown-like formatting)
273
+ */
274
+ function formatDiagnosis(text: string): string {
275
+ return text
276
+ .replace(/\*\*(.*?)\*\*/g, '<strong style="color: #10b981">$1</strong>')
277
+ .replace(
278
+ /`([^`]+)`/g,
279
+ '<code style="background: rgba(255,255,255,0.1); padding: 2px 4px; border-radius: 2px;">$1</code>'
280
+ )
281
+ .replace(
282
+ /```(\w+)?\n([\s\S]*?)```/g,
283
+ '<pre style="background: rgba(0,0,0,0.3); padding: 8px; border-radius: 4px; overflow-x: auto; margin: 8px 0;">$2</pre>'
284
+ )
285
+ }
286
+
287
+ /**
288
+ * Styles
289
+ */
290
+ const styles: Record<string, React.CSSProperties> = {
291
+ container: {
292
+ display: 'flex',
293
+ flexDirection: 'column',
294
+ gap: '16px',
295
+ },
296
+ header: {
297
+ display: 'flex',
298
+ justifyContent: 'space-between',
299
+ alignItems: 'center',
300
+ },
301
+ title: {
302
+ fontSize: '12px',
303
+ fontWeight: 600,
304
+ color: '#fff',
305
+ },
306
+ quickActions: {
307
+ display: 'flex',
308
+ gap: '4px',
309
+ },
310
+ quickActionButton: {
311
+ width: '28px',
312
+ height: '28px',
313
+ display: 'flex',
314
+ alignItems: 'center',
315
+ justifyContent: 'center',
316
+ backgroundColor: 'rgba(255, 255, 255, 0.05)',
317
+ border: '1px solid rgba(255, 255, 255, 0.1)',
318
+ borderRadius: '4px',
319
+ cursor: 'pointer',
320
+ fontSize: '12px',
321
+ transition: 'background-color 0.15s ease-out',
322
+ },
323
+ unavailable: {
324
+ padding: '16px',
325
+ textAlign: 'center',
326
+ },
327
+ unavailableText: {
328
+ color: '#666',
329
+ fontSize: '11px',
330
+ margin: 0,
331
+ },
332
+ unavailableHint: {
333
+ color: '#555',
334
+ fontSize: '10px',
335
+ marginTop: '4px',
336
+ },
337
+ section: {
338
+ display: 'flex',
339
+ flexDirection: 'column',
340
+ gap: '8px',
341
+ },
342
+ sectionLabel: {
343
+ fontSize: '10px',
344
+ fontWeight: 600,
345
+ color: '#888',
346
+ textTransform: 'uppercase',
347
+ letterSpacing: '0.5px',
348
+ },
349
+ dataSourceRow: {
350
+ display: 'flex',
351
+ alignItems: 'center',
352
+ gap: '8px',
353
+ },
354
+ showDiagnoseButton: {
355
+ width: '100%',
356
+ padding: '8px 12px',
357
+ backgroundColor: 'rgba(255, 255, 255, 0.02)',
358
+ border: '1px dashed rgba(255, 255, 255, 0.1)',
359
+ borderRadius: '4px',
360
+ color: '#666',
361
+ fontSize: '11px',
362
+ cursor: 'pointer',
363
+ textAlign: 'left',
364
+ },
365
+ diagnoseSection: {
366
+ display: 'flex',
367
+ flexDirection: 'column',
368
+ gap: '8px',
369
+ },
370
+ inputLabel: {
371
+ fontSize: '10px',
372
+ color: '#888',
373
+ },
374
+ textarea: {
375
+ width: '100%',
376
+ padding: '8px',
377
+ backgroundColor: 'rgba(255, 255, 255, 0.05)',
378
+ border: '1px solid rgba(255, 255, 255, 0.1)',
379
+ borderRadius: '4px',
380
+ color: '#fff',
381
+ fontSize: '11px',
382
+ fontFamily: 'inherit',
383
+ resize: 'vertical',
384
+ },
385
+ diagnoseActions: {
386
+ display: 'flex',
387
+ gap: '8px',
388
+ },
389
+ diagnoseButton: {
390
+ flex: 1,
391
+ padding: '6px 12px',
392
+ backgroundColor: 'rgba(16, 185, 129, 0.2)',
393
+ border: '1px solid rgba(16, 185, 129, 0.3)',
394
+ borderRadius: '4px',
395
+ color: '#10b981',
396
+ fontSize: '11px',
397
+ cursor: 'pointer',
398
+ },
399
+ cancelButton: {
400
+ padding: '6px 12px',
401
+ backgroundColor: 'rgba(255, 255, 255, 0.05)',
402
+ border: '1px solid rgba(255, 255, 255, 0.1)',
403
+ borderRadius: '4px',
404
+ color: '#888',
405
+ fontSize: '11px',
406
+ cursor: 'pointer',
407
+ },
408
+ diagnosisResult: {
409
+ padding: '12px',
410
+ backgroundColor: 'rgba(16, 185, 129, 0.05)',
411
+ border: '1px solid rgba(16, 185, 129, 0.2)',
412
+ borderRadius: '4px',
413
+ },
414
+ diagnosisContent: {
415
+ fontSize: '11px',
416
+ color: '#fff',
417
+ whiteSpace: 'pre-wrap',
418
+ lineHeight: 1.6,
419
+ },
420
+ quickDiagnose: {
421
+ display: 'flex',
422
+ flexDirection: 'column',
423
+ gap: '8px',
424
+ },
425
+ quickDiagnoseLabel: {
426
+ fontSize: '10px',
427
+ color: '#888',
428
+ },
429
+ quickDiagnoseGrid: {
430
+ display: 'grid',
431
+ gridTemplateColumns: 'repeat(2, 1fr)',
432
+ gap: '4px',
433
+ },
434
+ quickDiagnoseButton: {
435
+ padding: '6px 8px',
436
+ backgroundColor: 'rgba(255, 255, 255, 0.02)',
437
+ border: '1px solid rgba(255, 255, 255, 0.05)',
438
+ borderRadius: '4px',
439
+ color: '#888',
440
+ fontSize: '10px',
441
+ cursor: 'pointer',
442
+ textAlign: 'center',
443
+ },
444
+ }