@cognigy/chat-components-vue 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 (81) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +178 -0
  3. package/dist/assets/svg/ArrowBackIcon.vue.d.ts +9 -0
  4. package/dist/assets/svg/AudioPauseIcon.vue.d.ts +2 -0
  5. package/dist/assets/svg/AudioPlayIcon.vue.d.ts +2 -0
  6. package/dist/assets/svg/CloseIcon.vue.d.ts +2 -0
  7. package/dist/assets/svg/DownloadIcon.vue.d.ts +2 -0
  8. package/dist/assets/svg/VideoPlayIcon.vue.d.ts +9 -0
  9. package/dist/assets/svg/index.d.ts +7 -0
  10. package/dist/chat-components-vue.css +1 -0
  11. package/dist/chat-components-vue.js +9858 -0
  12. package/dist/components/Message.vue.d.ts +11 -0
  13. package/dist/components/common/ActionButton.vue.d.ts +59 -0
  14. package/dist/components/common/ActionButtons.vue.d.ts +36 -0
  15. package/dist/components/common/ChatBubble.vue.d.ts +22 -0
  16. package/dist/components/common/ChatEvent.vue.d.ts +20 -0
  17. package/dist/components/common/LinkIcon.vue.d.ts +2 -0
  18. package/dist/components/common/TypingIndicator.vue.d.ts +21 -0
  19. package/dist/components/common/Typography.vue.d.ts +38 -0
  20. package/dist/components/messages/AdaptiveCard.vue.d.ts +2 -0
  21. package/dist/components/messages/AudioMessage.vue.d.ts +5 -0
  22. package/dist/components/messages/DatePicker.vue.d.ts +2 -0
  23. package/dist/components/messages/FileMessage.vue.d.ts +2 -0
  24. package/dist/components/messages/Gallery.vue.d.ts +2 -0
  25. package/dist/components/messages/GalleryItem.vue.d.ts +7 -0
  26. package/dist/components/messages/ImageMessage.vue.d.ts +5 -0
  27. package/dist/components/messages/List.vue.d.ts +2 -0
  28. package/dist/components/messages/ListItem.vue.d.ts +16 -0
  29. package/dist/components/messages/TextMessage.vue.d.ts +15 -0
  30. package/dist/components/messages/TextWithButtons.vue.d.ts +2 -0
  31. package/dist/components/messages/VideoMessage.vue.d.ts +5 -0
  32. package/dist/composables/useChannelPayload.d.ts +47 -0
  33. package/dist/composables/useCollation.d.ts +47 -0
  34. package/dist/composables/useImageContext.d.ts +13 -0
  35. package/dist/composables/useMessageContext.d.ts +18 -0
  36. package/dist/composables/useSanitize.d.ts +8 -0
  37. package/dist/index.d.ts +33 -0
  38. package/dist/types/index.d.ts +275 -0
  39. package/dist/utils/helpers.d.ts +56 -0
  40. package/dist/utils/matcher.d.ts +20 -0
  41. package/dist/utils/sanitize.d.ts +28 -0
  42. package/dist/utils/theme.d.ts +18 -0
  43. package/package.json +94 -0
  44. package/src/assets/svg/ArrowBackIcon.vue +30 -0
  45. package/src/assets/svg/AudioPauseIcon.vue +20 -0
  46. package/src/assets/svg/AudioPlayIcon.vue +19 -0
  47. package/src/assets/svg/CloseIcon.vue +10 -0
  48. package/src/assets/svg/DownloadIcon.vue +10 -0
  49. package/src/assets/svg/VideoPlayIcon.vue +25 -0
  50. package/src/assets/svg/index.ts +7 -0
  51. package/src/components/Message.vue +152 -0
  52. package/src/components/common/ActionButton.vue +354 -0
  53. package/src/components/common/ActionButtons.vue +170 -0
  54. package/src/components/common/ChatBubble.vue +109 -0
  55. package/src/components/common/ChatEvent.vue +84 -0
  56. package/src/components/common/LinkIcon.vue +34 -0
  57. package/src/components/common/TypingIndicator.vue +202 -0
  58. package/src/components/common/Typography.vue +196 -0
  59. package/src/components/messages/AdaptiveCard.vue +292 -0
  60. package/src/components/messages/AudioMessage.vue +391 -0
  61. package/src/components/messages/DatePicker.vue +135 -0
  62. package/src/components/messages/FileMessage.vue +195 -0
  63. package/src/components/messages/Gallery.vue +296 -0
  64. package/src/components/messages/GalleryItem.vue +214 -0
  65. package/src/components/messages/ImageMessage.vue +368 -0
  66. package/src/components/messages/List.vue +149 -0
  67. package/src/components/messages/ListItem.vue +344 -0
  68. package/src/components/messages/TextMessage.vue +203 -0
  69. package/src/components/messages/TextWithButtons.vue +119 -0
  70. package/src/components/messages/VideoMessage.vue +343 -0
  71. package/src/composables/useChannelPayload.ts +101 -0
  72. package/src/composables/useCollation.ts +163 -0
  73. package/src/composables/useImageContext.ts +27 -0
  74. package/src/composables/useMessageContext.ts +41 -0
  75. package/src/composables/useSanitize.ts +25 -0
  76. package/src/index.ts +71 -0
  77. package/src/types/index.ts +373 -0
  78. package/src/utils/helpers.ts +164 -0
  79. package/src/utils/matcher.ts +283 -0
  80. package/src/utils/sanitize.ts +133 -0
  81. package/src/utils/theme.ts +58 -0
@@ -0,0 +1,109 @@
1
+ <script setup lang="ts">
2
+ import { computed, useCssModule, CSSProperties } from 'vue'
3
+ import { useMessageContext } from '../../composables/useMessageContext'
4
+
5
+ interface Props {
6
+ className?: string
7
+ }
8
+
9
+ const props = withDefaults(defineProps<Props>(), {
10
+ className: '',
11
+ })
12
+
13
+ const styles = useCssModule()
14
+ const { message, config } = useMessageContext()
15
+
16
+ // Get direction mapping from config
17
+ const directionMapping = computed(() => config?.settings?.widgetSettings?.sourceDirectionMapping)
18
+ const disableBotOutputBorder = computed(() => config?.settings?.layout?.disableBotOutputBorder)
19
+ const botOutputMaxWidthPercentage = computed(() => config?.settings?.layout?.botOutputMaxWidthPercentage)
20
+
21
+ // Determine message type
22
+ const isUserMessage = computed(() => message.source === 'user')
23
+ const isBotMessage = computed(() => message.source === 'bot')
24
+ const isAgentMessage = computed(() => message.source === 'agent')
25
+ const isEngagementMessage = computed(() => message.source === 'engagement')
26
+
27
+ // Determine if border should be disabled
28
+ const disableBorder = computed(() =>
29
+ (isBotMessage.value || isEngagementMessage.value) && disableBotOutputBorder.value
30
+ )
31
+
32
+ // Get message direction based on source
33
+ const userMessageDirection = computed(() => directionMapping.value?.user || 'outgoing')
34
+ const botMessageDirection = computed(() => directionMapping.value?.bot || 'incoming')
35
+ const agentMessageDirection = computed(() => directionMapping.value?.agent || 'incoming')
36
+
37
+ // Compute CSS classes
38
+ const bubbleClasses = computed(() => {
39
+ const classes = [styles.bubble, 'chat-bubble']
40
+
41
+ if (props.className) classes.push(props.className)
42
+ if (isUserMessage.value) classes.push(styles[userMessageDirection.value])
43
+ if (isBotMessage.value) classes.push(styles[botMessageDirection.value])
44
+ if (isAgentMessage.value) classes.push(styles[agentMessageDirection.value])
45
+ if (disableBorder.value) classes.push(styles.disableBorder)
46
+
47
+ return classes
48
+ })
49
+
50
+ // Compute inline style
51
+ const bubbleStyle = computed((): CSSProperties => {
52
+ if ((isBotMessage.value || isEngagementMessage.value) && botOutputMaxWidthPercentage.value) {
53
+ return { maxWidth: `${botOutputMaxWidthPercentage.value}%` }
54
+ }
55
+ return {}
56
+ })
57
+ </script>
58
+
59
+ <template>
60
+ <div :class="bubbleClasses" :style="bubbleStyle">
61
+ <slot />
62
+ </div>
63
+ </template>
64
+
65
+ <style module>
66
+ .bubble {
67
+ border-radius: 15px;
68
+ border: 1px solid var(--cc-border-bot-message, transparent);
69
+ padding: 12px;
70
+ max-width: 295px;
71
+ width: max-content;
72
+ box-sizing: border-box;
73
+ margin-block-end: 12px;
74
+ word-break: break-word;
75
+ white-space: pre-wrap;
76
+ }
77
+
78
+ .bubble.disableBorder {
79
+ border: none;
80
+ background: none !important;
81
+ }
82
+
83
+ article:global(.bot) .bubble {
84
+ background: var(--cc-background-bot-message, #f5f5f5);
85
+ color: var(--cc-bot-message-contrast-color, #1a1a1a);
86
+ }
87
+
88
+ article:global(.agent) .bubble {
89
+ background: var(--cc-background-agent-message, #e8f4fd);
90
+ border-color: var(--cc-border-agent-message, transparent);
91
+ color: var(--cc-agent-message-contrast-color, #1a1a1a);
92
+ }
93
+
94
+ article:global(.user) .bubble {
95
+ background: var(--cc-background-user-message, #1976d2);
96
+ border-color: var(--cc-border-user-message, transparent);
97
+ color: var(--cc-user-message-contrast-color, #ffffff);
98
+ }
99
+
100
+ article:not(:global(.user)) .incoming,
101
+ article:global(.user) .incoming {
102
+ margin-inline-start: none;
103
+ }
104
+
105
+ article:not(:global(.user)) .outgoing,
106
+ article:global(.user) .outgoing {
107
+ margin-inline-start: auto;
108
+ }
109
+ </style>
@@ -0,0 +1,84 @@
1
+ <template>
2
+ <div
3
+ :id="id"
4
+ :class="eventClasses"
5
+ role="status"
6
+ aria-live="assertive"
7
+ >
8
+ <div :class="styles.eventPillTextWrapper">
9
+ <div :class="styles.eventText">
10
+ {{ text }}
11
+ </div>
12
+ </div>
13
+ </div>
14
+ </template>
15
+
16
+ <script setup lang="ts">
17
+ import { computed, useCssModule } from 'vue'
18
+
19
+ /**
20
+ * ChatEvent - Display chat event notifications
21
+ *
22
+ * Used for system messages like "Conversation started", "Agent joined", etc.
23
+ * These are informational messages that are not part of the conversation flow.
24
+ */
25
+
26
+ interface Props {
27
+ /** Event text to display */
28
+ text?: string
29
+ /** Additional CSS class names */
30
+ className?: string
31
+ /** Element ID */
32
+ id?: string
33
+ }
34
+
35
+ const props = withDefaults(defineProps<Props>(), {
36
+ text: '',
37
+ className: '',
38
+ id: undefined,
39
+ })
40
+
41
+ // Access CSS module classes
42
+ const styles = useCssModule()
43
+
44
+ /**
45
+ * Compute combined class names
46
+ */
47
+ const eventClasses = computed(() => {
48
+ const classes = [styles.eventPill]
49
+
50
+ if (props.className) {
51
+ classes.push(props.className)
52
+ }
53
+
54
+ return classes
55
+ })
56
+ </script>
57
+
58
+ <style module>
59
+ .eventPill {
60
+ display: flex;
61
+ max-width: 250px;
62
+ justify-content: center;
63
+ align-items: center;
64
+ gap: 10px;
65
+ margin-inline: auto;
66
+ margin-block-start: 40px;
67
+ }
68
+
69
+ .eventPillTextWrapper {
70
+ border-radius: 15px;
71
+ background: var(--cc-black-80, rgba(0, 0, 0, 0.8));
72
+ color: var(--cc-black-20, rgba(0, 0, 0, 0.2));
73
+ padding: 8px 12px;
74
+ }
75
+
76
+ .eventText {
77
+ /* Typography variant: title2-semibold equivalent */
78
+ font-family: 'Figtree', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
79
+ font-size: 14px;
80
+ font-weight: 600;
81
+ line-height: 1.4;
82
+ margin: 0;
83
+ }
84
+ </style>
@@ -0,0 +1,34 @@
1
+ <script setup lang="ts">
2
+ // Simple SVG icon component for external links
3
+ </script>
4
+
5
+ <template>
6
+ <svg
7
+ width="16"
8
+ height="16"
9
+ viewBox="0 0 16 16"
10
+ fill="none"
11
+ xmlns="http://www.w3.org/2000/svg"
12
+ aria-hidden="true"
13
+ >
14
+ <g clip-path="url(#clip0_1309_801)">
15
+ <path
16
+ fill-rule="evenodd"
17
+ clip-rule="evenodd"
18
+ d="M14.0208 1C14.5731 1 15.0208 1.44772 15.0208 2L15.0208 11C15.0208 11.5523 14.5731 12 14.0208 12C13.4686 12 13.0208 11.5523 13.0208 11L13.0208 4.41421L2.70714 14.7279C2.31661 15.1184 1.68345 15.1184 1.29292 14.7279C0.902399 14.3374 0.902399 13.7042 1.29292 13.3137L11.6066 3L5.02085 3C4.46856 3 4.02085 2.55228 4.02085 2C4.02084 1.44771 4.46856 1 5.02085 1L14.0208 1Z"
19
+ fill="currentColor"
20
+ />
21
+ </g>
22
+ <defs>
23
+ <clipPath id="clip0_1309_801">
24
+ <rect width="16" height="16" fill="white" />
25
+ </clipPath>
26
+ </defs>
27
+ </svg>
28
+ </template>
29
+
30
+ <style scoped>
31
+ svg {
32
+ flex-shrink: 0;
33
+ }
34
+ </style>
@@ -0,0 +1,202 @@
1
+ <template>
2
+ <div
3
+ :class="indicatorClasses"
4
+ role="status"
5
+ aria-live="polite"
6
+ aria-label="Bot is typing"
7
+ >
8
+ <div :class="styles.dot"></div>
9
+ <div :class="styles.dot"></div>
10
+ <div :class="styles.dot"></div>
11
+ </div>
12
+ </template>
13
+
14
+ <script setup lang="ts">
15
+ import { computed, useCssModule } from 'vue'
16
+
17
+ /**
18
+ * TypingIndicator - Animated typing indicator
19
+ *
20
+ * Displays three bouncing dots to indicate that bot or agent is typing.
21
+ * Purely presentational - parent components control visibility via v-if.
22
+ */
23
+
24
+ type SourceDirection = 'incoming' | 'outgoing'
25
+
26
+ interface Props {
27
+ /** Additional CSS class names */
28
+ className?: string
29
+ /** Direction of the indicator (incoming: left-aligned, outgoing: right-aligned) */
30
+ direction?: SourceDirection
31
+ /** Remove border styling */
32
+ disableBorder?: boolean
33
+ }
34
+
35
+ const props = withDefaults(defineProps<Props>(), {
36
+ className: '',
37
+ direction: 'incoming',
38
+ disableBorder: false,
39
+ })
40
+
41
+ // Access CSS module classes
42
+ const styles = useCssModule()
43
+
44
+ /**
45
+ * Compute combined class names
46
+ */
47
+ const indicatorClasses = computed(() => {
48
+ const classes = [
49
+ styles.typingIndicator,
50
+ 'webchat-typing-indicator', // Global class for external styling
51
+ ]
52
+
53
+ if (props.className) {
54
+ classes.push(props.className)
55
+ }
56
+
57
+ if (props.direction) {
58
+ classes.push(styles[props.direction])
59
+ }
60
+
61
+ if (props.disableBorder) {
62
+ classes.push(styles.disableBorder)
63
+ }
64
+
65
+ return classes
66
+ })
67
+ </script>
68
+
69
+ <style module>
70
+ .typingIndicator {
71
+ align-items: center;
72
+ background-color: var(--cc-white, #ffffff);
73
+ border-radius: 15px;
74
+ border: 1px solid var(--cc-black-80, rgba(0, 0, 0, 0.8));
75
+ box-sizing: border-box;
76
+ display: flex;
77
+ gap: 6px;
78
+ height: 44px;
79
+ justify-content: center;
80
+ padding-block: 10px;
81
+ padding-inline: 10px;
82
+ width: 62px;
83
+ margin-block: var(--webchat-message-margin-block, 24px);
84
+ margin-inline: var(--webchat-message-margin-inline, 20px);
85
+ }
86
+
87
+ .typingIndicator.disableBorder {
88
+ border: none;
89
+ background: none !important;
90
+ }
91
+
92
+ .incoming {
93
+ margin-inline-start: none;
94
+ }
95
+
96
+ .outgoing {
97
+ margin-inline-start: auto;
98
+ }
99
+
100
+ .dot {
101
+ background: var(--cc-black-40, rgba(0, 0, 0, 0.4));
102
+ border-radius: 999px;
103
+ width: 6px;
104
+ height: 6px;
105
+ }
106
+
107
+ .dot:nth-child(1),
108
+ .dot:nth-child(2),
109
+ .dot:nth-child(3) {
110
+ animation-duration: 1.2s;
111
+ animation-timing-function: ease-in-out;
112
+ animation-fill-mode: both;
113
+ animation-iteration-count: infinite;
114
+ animation-delay: -1000ms;
115
+ }
116
+
117
+ .dot:nth-child(1) {
118
+ animation-name: dot1;
119
+ }
120
+
121
+ .dot:nth-child(2) {
122
+ animation-name: dot2;
123
+ }
124
+
125
+ .dot:nth-child(3) {
126
+ animation-name: dot3;
127
+ }
128
+
129
+ /* Respect user's motion preferences */
130
+ @media (prefers-reduced-motion) {
131
+ .dot {
132
+ animation: none !important;
133
+ }
134
+ }
135
+
136
+ /* Animation keyframes for sequential bouncing effect */
137
+ @keyframes dot1 {
138
+ 0% {
139
+ transform: translateY(0);
140
+ }
141
+ 10% {
142
+ transform: translateY(-3px);
143
+ }
144
+ 20% {
145
+ transform: translateY(-6px);
146
+ }
147
+ 30% {
148
+ transform: translateY(-3px);
149
+ }
150
+ 40% {
151
+ transform: translateY(0);
152
+ }
153
+ 50% {
154
+ transform: translateY(0);
155
+ }
156
+ }
157
+
158
+ @keyframes dot2 {
159
+ 0% {
160
+ transform: translateY(0);
161
+ }
162
+ 10% {
163
+ transform: translateY(0);
164
+ }
165
+ 20% {
166
+ transform: translateY(-3px);
167
+ }
168
+ 30% {
169
+ transform: translateY(-6px);
170
+ }
171
+ 40% {
172
+ transform: translateY(-3px);
173
+ }
174
+ 50% {
175
+ transform: translateY(0);
176
+ }
177
+ }
178
+
179
+ @keyframes dot3 {
180
+ 0% {
181
+ transform: translateY(0);
182
+ }
183
+ 10% {
184
+ transform: translateY(0);
185
+ }
186
+ 20% {
187
+ transform: translateY(0);
188
+ }
189
+ 30% {
190
+ transform: translateY(-3px);
191
+ }
192
+ 40% {
193
+ transform: translateY(-6px);
194
+ }
195
+ 50% {
196
+ transform: translateY(-3px);
197
+ }
198
+ 65% {
199
+ transform: translateY(0);
200
+ }
201
+ }
202
+ </style>
@@ -0,0 +1,196 @@
1
+ <script setup lang="ts">
2
+ import { computed, useCssModule, useAttrs, CSSProperties } from 'vue'
3
+
4
+ export type TagVariant =
5
+ | 'h1-semibold'
6
+ | 'h2-regular'
7
+ | 'h2-semibold'
8
+ | 'title1-semibold'
9
+ | 'title1-regular'
10
+ | 'title2-semibold'
11
+ | 'title2-regular'
12
+ | 'body-regular'
13
+ | 'body-semibold'
14
+ | 'copy-medium'
15
+ | 'cta-semibold'
16
+
17
+ type ColorVariant = 'primary' | 'secondary'
18
+
19
+ interface Props {
20
+ variant?: TagVariant
21
+ component?: string
22
+ className?: string
23
+ style?: CSSProperties
24
+ color?: string
25
+ id?: string
26
+ ariaHidden?: boolean
27
+ tabIndex?: number
28
+ }
29
+
30
+ const props = withDefaults(defineProps<Props>(), {
31
+ variant: 'body-regular',
32
+ component: undefined,
33
+ className: '',
34
+ style: undefined,
35
+ color: undefined,
36
+ id: undefined,
37
+ ariaHidden: undefined,
38
+ tabIndex: undefined,
39
+ })
40
+
41
+ const attrs = useAttrs()
42
+ const styles = useCssModule()
43
+
44
+ // Mapping between variants and default HTML tags
45
+ const variantsMapping: Record<TagVariant, string> = {
46
+ 'h1-semibold': 'h1',
47
+ 'h2-regular': 'h2',
48
+ 'h2-semibold': 'h2',
49
+ 'title1-semibold': 'h3',
50
+ 'title1-regular': 'h4',
51
+ 'title2-semibold': 'h5',
52
+ 'title2-regular': 'h6',
53
+ 'body-semibold': 'p',
54
+ 'body-regular': 'p',
55
+ 'copy-medium': 'p',
56
+ 'cta-semibold': 'p',
57
+ }
58
+
59
+ // Color mapping for predefined colors
60
+ const colorsMapping: Record<ColorVariant, string> = {
61
+ primary: 'var(--cc-primary-color)',
62
+ secondary: 'var(--cc-secondary-color)',
63
+ }
64
+
65
+ // Determine which HTML element to render
66
+ const componentTag = computed(() => {
67
+ return props.component ?? variantsMapping[props.variant]
68
+ })
69
+
70
+ // Compute the color value
71
+ const typographyColor = computed(() => {
72
+ if (!props.color) return undefined
73
+ return colorsMapping[props.color as ColorVariant] ?? props.color
74
+ })
75
+
76
+ // Compute CSS classes
77
+ const componentClasses = computed(() => {
78
+ const classes = [styles[props.variant]]
79
+ if (props.className) classes.push(props.className)
80
+ if (props.color) classes.push(props.color)
81
+ return classes.filter(Boolean).join(' ')
82
+ })
83
+
84
+ // Compute merged styles
85
+ const componentStyle = computed(() => {
86
+ const mergedStyle: CSSProperties = { ...props.style }
87
+ if (typographyColor.value) {
88
+ mergedStyle.color = typographyColor.value
89
+ }
90
+ return mergedStyle
91
+ })
92
+ </script>
93
+
94
+ <template>
95
+ <component
96
+ :is="componentTag"
97
+ :id="id"
98
+ :class="componentClasses"
99
+ :style="componentStyle"
100
+ :aria-hidden="ariaHidden"
101
+ :tabIndex="tabIndex"
102
+ v-bind="attrs"
103
+ >
104
+ <slot />
105
+ </component>
106
+ </template>
107
+
108
+ <style module>
109
+ .h1-semibold {
110
+ font-family: Figtree;
111
+ font-size: 2.125rem; /* 34px */
112
+ font-style: normal;
113
+ font-weight: 600;
114
+ line-height: 2.55rem; /* 40.8px */
115
+ }
116
+
117
+ .h2-regular {
118
+ font-family: Figtree;
119
+ font-size: 1.125rem; /* 18px */
120
+ font-style: normal;
121
+ font-weight: 400;
122
+ line-height: 1.4625rem; /* 23.4px */
123
+ }
124
+
125
+ .h2-semibold {
126
+ font-family: Figtree;
127
+ font-size: 1.125rem; /* 18px */
128
+ font-style: normal;
129
+ font-weight: 600;
130
+ line-height: 1.4625rem; /* 23.4px */
131
+ }
132
+
133
+ .title1-regular {
134
+ font-family: Figtree;
135
+ font-size: 1rem; /* 16px */
136
+ font-style: normal;
137
+ font-weight: 400;
138
+ line-height: 1.4rem; /* 22.4px */
139
+ }
140
+
141
+ .title1-semibold {
142
+ font-family: Figtree;
143
+ font-size: 1rem; /* 16px */
144
+ font-style: normal;
145
+ font-weight: 600;
146
+ line-height: 1.4rem; /* 22.4px */
147
+ }
148
+
149
+ .title2-regular {
150
+ font-family: Figtree;
151
+ font-size: 0.75rem; /* 12px */
152
+ font-style: normal;
153
+ font-weight: 400;
154
+ line-height: 1.05rem; /* 16.8px */
155
+ }
156
+
157
+ .title2-semibold {
158
+ font-family: Figtree;
159
+ font-size: 0.75rem; /* 12px */
160
+ font-style: normal;
161
+ font-weight: 600;
162
+ line-height: 0.975rem; /* 15.6px */
163
+ }
164
+
165
+ .body-regular {
166
+ font-family: Figtree;
167
+ font-size: 0.875rem; /* 14px */
168
+ font-style: normal;
169
+ font-weight: 400;
170
+ line-height: 1.225rem; /* 19.6px */
171
+ }
172
+
173
+ .body-semibold {
174
+ font-family: Figtree;
175
+ font-size: 0.875rem; /* 14px */
176
+ font-style: normal;
177
+ font-weight: 600;
178
+ line-height: 1.1375rem; /* 18.2px */
179
+ }
180
+
181
+ .copy-medium {
182
+ font-family: Figtree;
183
+ font-size: 0.75rem; /* 12px */
184
+ font-style: normal;
185
+ font-weight: 500;
186
+ line-height: 1.05rem; /* 16.8px */
187
+ }
188
+
189
+ .cta-semibold {
190
+ font-family: Figtree;
191
+ font-size: 0.875rem; /* 14px */
192
+ font-style: normal;
193
+ font-weight: 600;
194
+ line-height: 1.1375rem; /* 18.2px */
195
+ }
196
+ </style>