@electerm/electerm-react 2.2.0 → 2.3.6
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/client/components/bookmark-form/common/serial-path-selector.jsx +1 -1
- package/client/components/common/opacity.jsx +66 -0
- package/client/components/file-transfer/transfer.styl +1 -1
- package/client/components/main/main.jsx +2 -6
- package/client/components/main/term-fullscreen.styl +1 -1
- package/client/components/main/wrapper.styl +1 -1
- package/client/components/quick-commands/qm.styl +1 -2
- package/client/components/session/session.styl +1 -1
- package/client/components/sftp/sftp-entry.jsx +50 -9
- package/client/components/sftp/sftp.styl +1 -9
- package/client/components/sidebar/info.styl +11 -12
- package/client/components/sidebar/sidebar.styl +1 -2
- package/client/components/sys-menu/sys-menu.styl +5 -6
- package/client/components/tabs/tabs.styl +6 -5
- package/client/components/terminal/terminal-search-bar.jsx +1 -1
- package/client/components/terminal/terminal.styl +5 -9
- package/client/components/terminal-info/terminal-info.styl +1 -1
- package/client/components/tree-list/tree-list.styl +1 -1
- package/client/css/basic.styl +2 -5
- package/client/views/index.pug +8 -1
- package/package.json +1 -1
- package/client/components/terminal/zmodem.styl +0 -14
|
@@ -30,7 +30,7 @@ export default function SerialPathSelector ({
|
|
|
30
30
|
/>
|
|
31
31
|
</FormItem>
|
|
32
32
|
<Spin spinning={loaddingSerials}>
|
|
33
|
-
<span onClick={store.handleGetSerials}>
|
|
33
|
+
<span onClick={store.handleGetSerials} className='pointer'>
|
|
34
34
|
<ReloadOutlined /> {e('reload')} serials
|
|
35
35
|
</span>
|
|
36
36
|
</Spin>
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { useEffect } from 'react'
|
|
2
|
+
import { useDelta, useConditionalEffect } from 'react-delta-hooks'
|
|
3
|
+
import eq from 'fast-deep-equal'
|
|
4
|
+
|
|
5
|
+
const opacityDomId = 'opacity-style'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Opacity component
|
|
9
|
+
* Handles conditional CSS rendering based on opacity setting
|
|
10
|
+
* @param {Object} props
|
|
11
|
+
* @param {number} props.opacity - Opacity value from store.config
|
|
12
|
+
* @returns {null}
|
|
13
|
+
*/
|
|
14
|
+
export default function Opacity ({ opacity }) {
|
|
15
|
+
// Default to 1 if opacity is not provided
|
|
16
|
+
const currentOpacity = opacity !== undefined ? opacity : 1
|
|
17
|
+
const delta = useDelta(currentOpacity)
|
|
18
|
+
|
|
19
|
+
function applyOpacity () {
|
|
20
|
+
let styleElement = document.getElementById(opacityDomId)
|
|
21
|
+
|
|
22
|
+
// Create style element if it doesn't exist
|
|
23
|
+
if (!styleElement) {
|
|
24
|
+
styleElement = document.createElement('style')
|
|
25
|
+
styleElement.id = opacityDomId
|
|
26
|
+
document.head.appendChild(styleElement)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Update style content based on opacity value
|
|
30
|
+
if (currentOpacity === 1) {
|
|
31
|
+
styleElement.innerHTML = ''
|
|
32
|
+
window.pre.runGlobalAsync('setBackgroundColor', '#333333')
|
|
33
|
+
} else {
|
|
34
|
+
window.pre.runGlobalAsync('setBackgroundColor', '#33333300')
|
|
35
|
+
styleElement.innerHTML = `
|
|
36
|
+
html {
|
|
37
|
+
background: transparent !important;
|
|
38
|
+
}
|
|
39
|
+
body {
|
|
40
|
+
background: transparent !important;
|
|
41
|
+
}
|
|
42
|
+
#outside-context {
|
|
43
|
+
opacity: ${currentOpacity} !important;
|
|
44
|
+
}
|
|
45
|
+
`
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
applyOpacity()
|
|
51
|
+
|
|
52
|
+
// Cleanup function
|
|
53
|
+
return () => {
|
|
54
|
+
const styleElement = document.getElementById(opacityDomId)
|
|
55
|
+
if (styleElement) {
|
|
56
|
+
document.head.removeChild(styleElement)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}, [])
|
|
60
|
+
|
|
61
|
+
useConditionalEffect(() => {
|
|
62
|
+
applyOpacity()
|
|
63
|
+
}, delta && !eq(delta.prev, delta.curr))
|
|
64
|
+
|
|
65
|
+
return null
|
|
66
|
+
}
|
|
@@ -28,6 +28,7 @@ import ConnectionHoppingWarning from './connection-hopping-warnning'
|
|
|
28
28
|
import SshConfigLoadNotify from '../ssh-config/ssh-config-load-notify'
|
|
29
29
|
import LoadSshConfigs from '../ssh-config/load-ssh-configs'
|
|
30
30
|
import AIChat from '../ai/ai-chat'
|
|
31
|
+
import Opacity from '../common/opacity'
|
|
31
32
|
import { pick } from 'lodash-es'
|
|
32
33
|
import deepCopy from 'json-deep-copy'
|
|
33
34
|
import './wrapper.styl'
|
|
@@ -152,11 +153,6 @@ export default auto(function Index (props) {
|
|
|
152
153
|
const themeProps = {
|
|
153
154
|
themeConfig: store.getUiThemeConfig()
|
|
154
155
|
}
|
|
155
|
-
const outerProps = {
|
|
156
|
-
style: {
|
|
157
|
-
opacity: config.opacity
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
156
|
const copiedTransfer = deepCopy(fileTransfers)
|
|
161
157
|
const copiedHistory = deepCopy(transferHistory)
|
|
162
158
|
const sidebarProps = {
|
|
@@ -260,6 +256,7 @@ export default auto(function Index (props) {
|
|
|
260
256
|
{...confsCss}
|
|
261
257
|
wsInited={wsInited}
|
|
262
258
|
/>
|
|
259
|
+
<Opacity opacity={config.opacity} />
|
|
263
260
|
<TerminalInteractive />
|
|
264
261
|
<UiTheme
|
|
265
262
|
{...themeProps}
|
|
@@ -277,7 +274,6 @@ export default auto(function Index (props) {
|
|
|
277
274
|
<BatchOp {...batchOpProps} />
|
|
278
275
|
<div
|
|
279
276
|
id='outside-context'
|
|
280
|
-
{...outerProps}
|
|
281
277
|
>
|
|
282
278
|
<Sidebar {...sidebarProps} />
|
|
283
279
|
<Layout
|
|
@@ -27,7 +27,7 @@ import fs from '../../common/fs'
|
|
|
27
27
|
import ListTable from './list-table-ui'
|
|
28
28
|
import deepCopy from 'json-deep-copy'
|
|
29
29
|
import isValidPath from '../../common/is-valid-path'
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
import * as owner from './owner-list'
|
|
32
32
|
import AddressBar from './address-bar'
|
|
33
33
|
import getProxy from '../../common/get-proxy'
|
|
@@ -101,6 +101,9 @@ export default class Sftp extends Component {
|
|
|
101
101
|
this.timer4 = null
|
|
102
102
|
clearTimeout(this.timer5)
|
|
103
103
|
this.timer5 = null
|
|
104
|
+
// Clear sort cache to prevent memory leaks
|
|
105
|
+
this._sortCache?.clear()
|
|
106
|
+
this._lastSortArgs = null
|
|
104
107
|
}
|
|
105
108
|
|
|
106
109
|
initFtpData = async () => {
|
|
@@ -167,19 +170,36 @@ export default class Sftp extends Component {
|
|
|
167
170
|
}, {})
|
|
168
171
|
}
|
|
169
172
|
|
|
170
|
-
sort
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
173
|
+
// Cache for memoized sort results
|
|
174
|
+
_sortCache = new Map()
|
|
175
|
+
_lastSortArgs = null
|
|
176
|
+
|
|
177
|
+
sort = (list, type, sortDirection, sortProp) => {
|
|
178
|
+
// Create a cache key from the arguments
|
|
179
|
+
const cacheKey = JSON.stringify({
|
|
180
|
+
listLength: list?.length || 0,
|
|
181
|
+
listHash: this._hashList(list),
|
|
182
|
+
type,
|
|
183
|
+
sortDirection,
|
|
184
|
+
sortProp
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
// Check if we have a cached result and if args haven't changed
|
|
188
|
+
if (this._lastSortArgs && isEqual(this._lastSortArgs, [list, type, sortDirection, sortProp])) {
|
|
189
|
+
const cached = this._sortCache.get(cacheKey)
|
|
190
|
+
if (cached) {
|
|
191
|
+
return cached
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Compute the result
|
|
176
196
|
if (!list || !list.length) {
|
|
177
197
|
return []
|
|
178
198
|
}
|
|
179
199
|
|
|
180
200
|
const isDesc = sortDirection === 'desc'
|
|
181
201
|
|
|
182
|
-
|
|
202
|
+
const result = list.slice().sort((a, b) => {
|
|
183
203
|
// Handle items with no id first
|
|
184
204
|
if (!a.id && b.id) return -1
|
|
185
205
|
if (a.id && !b.id) return 1
|
|
@@ -207,7 +227,28 @@ export default class Sftp extends Component {
|
|
|
207
227
|
if (aValue > bValue) return isDesc ? -1 : 1
|
|
208
228
|
return 0
|
|
209
229
|
})
|
|
210
|
-
|
|
230
|
+
|
|
231
|
+
// Cache the result
|
|
232
|
+
this._lastSortArgs = [list, type, sortDirection, sortProp]
|
|
233
|
+
this._sortCache.set(cacheKey, result)
|
|
234
|
+
|
|
235
|
+
// Limit cache size to prevent memory leaks
|
|
236
|
+
if (this._sortCache.size > 10) {
|
|
237
|
+
const firstKey = this._sortCache.keys().next().value
|
|
238
|
+
this._sortCache.delete(firstKey)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return result
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Helper method to create a simple hash of the list for cache key
|
|
245
|
+
_hashList = (list) => {
|
|
246
|
+
if (!list || !list.length) return 0
|
|
247
|
+
return list.reduce((hash, item, index) => {
|
|
248
|
+
const str = `${item.id || ''}${item.name || ''}${item.modifyTime || ''}${index}`
|
|
249
|
+
return hash + str.length
|
|
250
|
+
}, 0)
|
|
251
|
+
}
|
|
211
252
|
|
|
212
253
|
isActive () {
|
|
213
254
|
const { currentBatchTabId, pane, sshSftpSplitView } = this.props
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
&.focused
|
|
42
42
|
height auto
|
|
43
43
|
border 1px solid main-dark
|
|
44
|
-
box-shadow 0px 0px 3px 3px
|
|
44
|
+
box-shadow 0px 0px 3px 3px lighten(main, 5%)
|
|
45
45
|
|
|
46
46
|
.sftp-history-item
|
|
47
47
|
padding 5px 10px
|
|
@@ -72,7 +72,6 @@
|
|
|
72
72
|
top 0
|
|
73
73
|
z-index 1
|
|
74
74
|
height 100%
|
|
75
|
-
opacity 0
|
|
76
75
|
padding 0
|
|
77
76
|
margin 0
|
|
78
77
|
|
|
@@ -86,13 +85,6 @@
|
|
|
86
85
|
width 100%
|
|
87
86
|
.file-props
|
|
88
87
|
margin-left 70px
|
|
89
|
-
.opacity-loop
|
|
90
|
-
opacity 0.3
|
|
91
|
-
animation blinker 5s linear infinite
|
|
92
|
-
|
|
93
|
-
@keyframes blinker
|
|
94
|
-
50%
|
|
95
|
-
opacity .8
|
|
96
88
|
|
|
97
89
|
.sftp-sort-btn
|
|
98
90
|
color text
|
|
@@ -2,23 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
// from https://freefrontend.com/css-border-animations/
|
|
4
4
|
.morph-shape
|
|
5
|
-
background linear-gradient(45deg,
|
|
6
|
-
animation morph 8s ease-in-out infinite
|
|
5
|
+
background linear-gradient(45deg, #08c 0%, #09c 100%)
|
|
7
6
|
border-radius 60% 40% 30% 70% / 60% 30% 70% 40%
|
|
8
7
|
transition all 1s ease-in-out
|
|
9
8
|
z-index 5
|
|
10
9
|
|
|
11
|
-
@keyframes morph
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
// @keyframes morph
|
|
11
|
+
// 0%
|
|
12
|
+
// border-radius 60% 40% 30% 70% / 60% 30% 70% 40%
|
|
13
|
+
// background linear-gradient(45deg, primary 0%, success 100%)
|
|
14
|
+
// 50%
|
|
15
|
+
// border-radius 30% 60% 70% 40% / 50% 60% 30% 60%
|
|
16
|
+
// background linear-gradient(45deg, main 0%, success 100%)
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
// 100%
|
|
19
|
+
// border-radius 60% 40% 30% 70% / 60% 30% 70% 40%
|
|
20
|
+
// background linear-gradient(45deg, primary 0%, success 100%)
|
|
22
21
|
.info-modal
|
|
23
22
|
.ant-modal-header
|
|
24
23
|
border none
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
z-index 100
|
|
24
24
|
bottom 0
|
|
25
25
|
background main-dark
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
.item-list
|
|
28
28
|
padding-right 0
|
|
29
29
|
.sidebar-pin-top
|
|
@@ -73,7 +73,6 @@
|
|
|
73
73
|
bottom 0
|
|
74
74
|
z-index 200
|
|
75
75
|
width 0
|
|
76
|
-
border-right 1px solid darken(main, 30%)
|
|
77
76
|
|
|
78
77
|
.pinned
|
|
79
78
|
.sidebar-list
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
color #eee
|
|
16
16
|
cursor pointer
|
|
17
17
|
&.disabled
|
|
18
|
-
color
|
|
18
|
+
color lighten(text, 15%)
|
|
19
19
|
&:hover
|
|
20
20
|
color #777
|
|
21
21
|
background #333
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
position absolute
|
|
31
31
|
right 16px
|
|
32
32
|
top 0
|
|
33
|
-
|
|
33
|
+
color lighten(text, 20%)
|
|
34
34
|
|
|
35
35
|
.context-menu
|
|
36
36
|
hr
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
left 100%
|
|
45
45
|
top 0
|
|
46
46
|
background main
|
|
47
|
-
box-shadow 0px 0px 3px 3px
|
|
47
|
+
box-shadow 0px 0px 3px 3px lighten(main, 35%)
|
|
48
48
|
max-height 300px
|
|
49
49
|
overflow-y scroll
|
|
50
50
|
.with-sub-menu
|
|
@@ -68,15 +68,14 @@
|
|
|
68
68
|
width 28px
|
|
69
69
|
height 28px
|
|
70
70
|
border-radius 30px
|
|
71
|
-
color text
|
|
71
|
+
color lighten(text, 20%)
|
|
72
72
|
font-size 16px
|
|
73
73
|
text-align center
|
|
74
74
|
display inline-block
|
|
75
75
|
line-height 28px
|
|
76
76
|
cursor pointer
|
|
77
|
-
opacity .6
|
|
78
77
|
&:hover
|
|
79
|
-
|
|
78
|
+
color text
|
|
80
79
|
.is-main
|
|
81
80
|
.menu-control
|
|
82
81
|
img
|
|
@@ -71,11 +71,11 @@
|
|
|
71
71
|
color text-light
|
|
72
72
|
@keyframes blink
|
|
73
73
|
0%
|
|
74
|
-
|
|
74
|
+
background-color #e0e0e0
|
|
75
75
|
50%
|
|
76
|
-
|
|
76
|
+
background-color #ffffff
|
|
77
77
|
100%
|
|
78
|
-
|
|
78
|
+
background-color #e0e0e0
|
|
79
79
|
|
|
80
80
|
.tab-terminal-feed
|
|
81
81
|
.tab-traffic
|
|
@@ -96,6 +96,7 @@
|
|
|
96
96
|
.is-transporting .tab-traffic
|
|
97
97
|
display block
|
|
98
98
|
animation blink 2s infinite
|
|
99
|
+
/* Remove opacity animation, use background-color */
|
|
99
100
|
.tab-traffic
|
|
100
101
|
display none
|
|
101
102
|
left 10px
|
|
@@ -105,6 +106,7 @@
|
|
|
105
106
|
.is-terminal-active .tab-terminal-feed
|
|
106
107
|
display block
|
|
107
108
|
animation blink 2s infinite
|
|
109
|
+
/* Remove opacity animation, use background-color */
|
|
108
110
|
.tab-terminal-feed
|
|
109
111
|
display none
|
|
110
112
|
left 20px
|
|
@@ -217,7 +219,7 @@
|
|
|
217
219
|
margin-right 5px
|
|
218
220
|
font-size 14px
|
|
219
221
|
.no-sessions
|
|
220
|
-
background main
|
|
222
|
+
background main
|
|
221
223
|
text-align center
|
|
222
224
|
padding 50px 0
|
|
223
225
|
position absolute
|
|
@@ -231,7 +233,6 @@
|
|
|
231
233
|
padding 0 4px
|
|
232
234
|
height 20px
|
|
233
235
|
line-height 21px
|
|
234
|
-
opacity 0.8
|
|
235
236
|
.no-session-history
|
|
236
237
|
position absolute
|
|
237
238
|
top 280px
|
|
@@ -26,7 +26,7 @@ export default function SearchResultBar ({
|
|
|
26
26
|
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
|
27
27
|
matches.forEach((match, index) => {
|
|
28
28
|
const y = (match / totalLines) * containerHeight
|
|
29
|
-
ctx.fillStyle = index === matchIndex ? '
|
|
29
|
+
ctx.fillStyle = index === matchIndex ? '#f34309' : '#f3c409'
|
|
30
30
|
ctx.fillRect(0, y, 16, 2)
|
|
31
31
|
})
|
|
32
32
|
}
|
|
@@ -41,11 +41,7 @@
|
|
|
41
41
|
height 100%
|
|
42
42
|
background-repeat no-repeat
|
|
43
43
|
background-position center
|
|
44
|
-
background-color
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
.terminal-not-active .xterm-text-layer
|
|
48
|
-
opacity .74
|
|
44
|
+
background-color transparent !important
|
|
49
45
|
|
|
50
46
|
.terminal-normal-buffer
|
|
51
47
|
position absolute
|
|
@@ -54,7 +50,7 @@
|
|
|
54
50
|
width 100%
|
|
55
51
|
background main
|
|
56
52
|
color text
|
|
57
|
-
box-shadow 0px 3px 3px 0px
|
|
53
|
+
box-shadow 0px 3px 3px 0px lighten(main, 25%)
|
|
58
54
|
z-index 66
|
|
59
55
|
padding-bottom 32px
|
|
60
56
|
.terminal-normal-buffer-body
|
|
@@ -73,7 +69,7 @@
|
|
|
73
69
|
width 100%
|
|
74
70
|
height 32px
|
|
75
71
|
line-height 32px
|
|
76
|
-
box-shadow 0px -3px 3px 0px
|
|
72
|
+
box-shadow 0px -3px 3px 0px lighten(main, 25%)
|
|
77
73
|
background main-light
|
|
78
74
|
.batch-input-wrap
|
|
79
75
|
width calc(100% - 80px)
|
|
@@ -128,9 +124,9 @@
|
|
|
128
124
|
.suggestion-type
|
|
129
125
|
margin-left 5px
|
|
130
126
|
font-size 0.8em
|
|
131
|
-
|
|
127
|
+
color lighten(text, 20%)
|
|
132
128
|
&:hover
|
|
133
|
-
|
|
129
|
+
color text
|
|
134
130
|
|
|
135
131
|
.suggestion-delete
|
|
136
132
|
margin-left 5px
|
package/client/css/basic.styl
CHANGED
|
@@ -2,20 +2,17 @@
|
|
|
2
2
|
@require './antd-overwrite'
|
|
3
3
|
@require './includes/theme-default'
|
|
4
4
|
|
|
5
|
-
html
|
|
6
|
-
background rgba(0,0,0,.01)
|
|
7
|
-
|
|
8
5
|
body
|
|
9
6
|
color text
|
|
10
7
|
overflow hidden
|
|
11
|
-
background
|
|
8
|
+
background main
|
|
12
9
|
font-size 12px
|
|
13
10
|
line-height 1.5715
|
|
14
11
|
font-family -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'
|
|
15
12
|
font-variant: tabular-nums
|
|
16
13
|
|
|
17
14
|
::-webkit-scrollbar
|
|
18
|
-
background
|
|
15
|
+
background transparent
|
|
19
16
|
width 7px
|
|
20
17
|
|
|
21
18
|
.term-wrap:hover
|
package/client/views/index.pug
CHANGED
|
@@ -16,7 +16,6 @@ html
|
|
|
16
16
|
top: 0;
|
|
17
17
|
width: 100%;
|
|
18
18
|
height: 100%;
|
|
19
|
-
background: #141314 50% 50% no-repeat url("./images/electerm-watermark.png");
|
|
20
19
|
display: flex;
|
|
21
20
|
flex-direction: column;
|
|
22
21
|
justify-content: center;
|
|
@@ -25,6 +24,14 @@ html
|
|
|
25
24
|
.electerm-logo-bg {
|
|
26
25
|
background: transparent 50% 50% no-repeat url("./images/electerm-watermark.png");
|
|
27
26
|
}
|
|
27
|
+
.morph-shape {
|
|
28
|
+
background: linear-gradient(45deg, #08c 0%, #09c 100%);
|
|
29
|
+
animation: morph 8s ease-in-out infinite;
|
|
30
|
+
border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%;
|
|
31
|
+
transition: all 1s ease-in-out;
|
|
32
|
+
z-index: 5;
|
|
33
|
+
}
|
|
34
|
+
|
|
28
35
|
- if (!isDev)
|
|
29
36
|
link(rel='stylesheet', href='css/' + version + '-basic.css')
|
|
30
37
|
link(rel='stylesheet', href='css/' + version + '-electerm.css')
|
package/package.json
CHANGED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
@require '../../css/includes/theme-default'
|
|
2
|
-
.zmodem-transfer
|
|
3
|
-
position absolute
|
|
4
|
-
left 0
|
|
5
|
-
right 0
|
|
6
|
-
height 100%
|
|
7
|
-
width 100%
|
|
8
|
-
background alpha(main-dark, .95)
|
|
9
|
-
display flex
|
|
10
|
-
flex-direction column
|
|
11
|
-
align-items center
|
|
12
|
-
justify-content center
|
|
13
|
-
z-index 20
|
|
14
|
-
padding 20px 180px 20px 20px
|