index.vue 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733
  1. <template>
  2. <div :class="prefixCls" class="authorization-page">
  3. <div class="case-header-bar flex items-center justify-between">
  4. <AppLogo :showTitle="true" />
  5. <div class="flex items-center">
  6. <WindowMinimize />
  7. <WindowMaximize />
  8. <WindowClose />
  9. </div>
  10. </div>
  11. <div class="authorization-content">
  12. <section class="left-pane">
  13. <div class="left-pane__content">
  14. <div class="left-pane__header">
  15. <div class="left-pane__badge">
  16. <SafetyCertificateOutlined />
  17. <span>系统授权入口</span>
  18. </div>
  19. <h1 class="left-pane__title">智能研判分析平台</h1>
  20. <p class="left-pane__subtitle">
  21. 面向案件管理、对象关联和多源数据研判的专业系统,帮助团队更高效地完成线索梳理、关系挖掘与专题分析。
  22. </p>
  23. </div>
  24. <div class="left-pane__visual">
  25. <div class="device-illustration">
  26. <div class="device-illustration__screen-shell">
  27. <div class="device-illustration__screen-dots">
  28. <span></span>
  29. <span></span>
  30. <span></span>
  31. </div>
  32. <div class="device-illustration__screen-body">
  33. <div class="device-illustration__line device-illustration__line--lg"></div>
  34. <div class="device-illustration__line device-illustration__line--md"></div>
  35. <div class="device-illustration__chart">
  36. <span></span>
  37. <span></span>
  38. <span></span>
  39. </div>
  40. </div>
  41. </div>
  42. <div class="device-illustration__stand"></div>
  43. <div class="device-illustration__shadow"></div>
  44. <div class="floating-card floating-card--left">
  45. <div class="floating-card__icon">
  46. <DesktopOutlined />
  47. </div>
  48. <div class="floating-card__content">
  49. <div class="floating-card__label">对象画像</div>
  50. <div class="floating-card__title">多源归集</div>
  51. </div>
  52. </div>
  53. <div class="floating-card floating-card--right">
  54. <div class="floating-card__icon floating-card__icon--indigo">
  55. <ApartmentOutlined />
  56. </div>
  57. <div class="floating-card__content">
  58. <div class="floating-card__label">关系分析</div>
  59. <div class="floating-card__title">图谱联动</div>
  60. </div>
  61. </div>
  62. </div>
  63. </div>
  64. <div class="left-pane__features">
  65. <div v-for="item in capabilityList" :key="item.title" class="feature-item">
  66. <div class="feature-item__icon">
  67. <component :is="item.icon" />
  68. </div>
  69. <div class="feature-item__content">
  70. <h3 class="feature-item__title">{{ item.title }}</h3>
  71. <p class="feature-item__desc">{{ item.desc }}</p>
  72. </div>
  73. </div>
  74. </div>
  75. <div class="left-pane__footer">
  76. <p class="left-pane__footer-line">© 2026 重庆恒时乐科科技有限公司 | 向量探索团队</p>
  77. <p class="left-pane__footer-line">联系电话:18908093240</p>
  78. </div>
  79. </div>
  80. </section>
  81. <section class="right-pane">
  82. <svg class="right-pane__curve" preserveAspectRatio="none" viewBox="0 0 100 100">
  83. <path d="M100,0 Q-40,50 100,100 Z" fill="currentColor" />
  84. </svg>
  85. <div class="right-pane__scroll">
  86. <div class="auth-card">
  87. <div class="auth-card__header">
  88. <span class="auth-card__eyebrow">离线授权流程</span>
  89. <h2 class="auth-card__title">未授权时请先完成离线授权</h2>
  90. <div v-if="authorizationWarningMessage" class="auth-card__warning">
  91. {{ authorizationWarningMessage }}
  92. </div>
  93. <p class="auth-card__intro">请按下方流程完成离线授权,随后即可进入系统继续使用。</p>
  94. </div>
  95. <div class="auth-card__steps">
  96. <div v-for="(item, index) in authorizationSteps" :key="item.title" class="step-card">
  97. <div class="step-card__index">{{ index + 1 }}</div>
  98. <h4 class="step-card__title">{{ item.title }}</h4>
  99. <p class="step-card__desc">{{ item.desc }}</p>
  100. <RightOutlined v-if="index < authorizationSteps.length - 1" class="step-card__arrow" />
  101. </div>
  102. </div>
  103. <div class="auth-card__section">
  104. <div class="auth-card__section-head">
  105. <h3 class="auth-card__section-title">获取机器码</h3>
  106. <span class="auth-card__section-tip">用于申请当前设备的离线授权文件</span>
  107. </div>
  108. <div class="machine-panel">
  109. <div class="machine-panel__value">
  110. {{ machineCode || '点击“获取机器码”后显示当前设备机器码' }}
  111. </div>
  112. <div class="machine-panel__actions">
  113. <Button class="ui-btn ui-btn--ghost" :loading="machineCodeLoading" @click="handleGetMachineCode">
  114. 获取机器码
  115. </Button>
  116. <Button class="ui-btn ui-btn--ghost" :disabled="!machineCode" @click="handleCopyMachineCode">
  117. <CopyOutlined />
  118. <span>复制</span>
  119. </Button>
  120. </div>
  121. </div>
  122. </div>
  123. <div class="auth-card__section">
  124. <div class="auth-card__section-head">
  125. <h3 class="auth-card__section-title">上传授权文件</h3>
  126. <span class="auth-card__section-tip">选择本地授权文件后执行离线授权</span>
  127. </div>
  128. <div class="upload-panel">
  129. <div class="upload-panel__dropzone" @click="handleSelectLicenseFile">
  130. <UploadOutlined class="upload-panel__dropzone-icon" />
  131. <span class="upload-panel__dropzone-text">
  132. {{ licenseFilePath || '请选择离线授权文件' }}
  133. </span>
  134. </div>
  135. <div class="upload-panel__actions">
  136. <Button class="ui-btn ui-btn--ghost" @click="handleSelectLicenseFile">选择授权文件</Button>
  137. <Button
  138. type="primary"
  139. class="ui-btn ui-btn--primary"
  140. :loading="offlineAuthorizationLoading"
  141. :disabled="!licenseFilePath"
  142. @click="handleOfflineAuthorization"
  143. >
  144. 离线授权
  145. </Button>
  146. </div>
  147. </div>
  148. </div>
  149. </div>
  150. </div>
  151. </section>
  152. </div>
  153. </div>
  154. </template>
  155. <script lang="ts" setup>
  156. import { onMounted, ref } from 'vue';
  157. import { useRoute, useRouter } from 'vue-router';
  158. import { Button } from 'ant-design-vue';
  159. import {
  160. ApartmentOutlined,
  161. CopyOutlined,
  162. DatabaseOutlined,
  163. DesktopOutlined,
  164. NotificationOutlined,
  165. RightOutlined,
  166. SafetyCertificateOutlined,
  167. UploadOutlined,
  168. } from '@ant-design/icons-vue';
  169. import { PageEnum } from '@jeesite/core/enums/pageEnum';
  170. import {
  171. authorizationInfoApi,
  172. checkAuthApi,
  173. machineCodeApi,
  174. offlineAuthorizationApi,
  175. } from '@jeesite/core/api/sys/login';
  176. import { useCopyToClipboard } from '@jeesite/core/hooks/web/useCopyToClipboard';
  177. import { useDesign } from '@jeesite/core/hooks/web/useDesign';
  178. import { useMessage } from '@jeesite/core/hooks/web/useMessage';
  179. import { useUserStore } from '@jeesite/core/store/modules/case';
  180. import { AppLogo } from '@jeesite/core/components/Application';
  181. import { WindowMinimize, WindowMaximize, WindowClose } from '@jeesite/core/layouts/default/header/components';
  182. import { initGlobalCaseConfig } from '@jeesite/core/logics/caseConfigGuide';
  183. import {
  184. extractAuthorizationCache,
  185. getAuthorizationExpiredMessage,
  186. isAuthorizationExpired,
  187. } from '@jeesite/core/utils/license';
  188. const router = useRouter();
  189. const route = useRoute();
  190. const { prefixCls } = useDesign('authorization-entry');
  191. const { showMessage } = useMessage();
  192. const { clipboardRef, isSuccessRef } = useCopyToClipboard();
  193. const userStore = useUserStore();
  194. const machineCode = ref('');
  195. const machineCodeLoading = ref(false);
  196. const offlineAuthorizationLoading = ref(false);
  197. const licenseFilePath = ref('');
  198. const authorizationWarningMessage = ref('');
  199. const capabilityList = [
  200. {
  201. title: '多源数据融合',
  202. desc: '整合资金流水、话单、基站轨迹等异构数据,提升数据归集效率。',
  203. icon: DatabaseOutlined,
  204. },
  205. {
  206. title: '关系图谱分析',
  207. desc: '围绕对象、团伙与行为链路开展多端关联研判,快速定位关键关系。',
  208. icon: ApartmentOutlined,
  209. },
  210. {
  211. title: '专题预警支撑',
  212. desc: '结合时序分析与规则配置,辅助线索核查、案件推进和复盘总结。',
  213. icon: NotificationOutlined,
  214. },
  215. ];
  216. const authorizationSteps = [
  217. {
  218. title: '获取机器码',
  219. desc: '提取当前设备机器码,并提交给授权方生成对应的离线授权文件。',
  220. },
  221. {
  222. title: '选择授权文件',
  223. desc: '从本地导入授权文件,系统会自动进行授权校验。',
  224. },
  225. {
  226. title: '执行离线授权',
  227. desc: '授权成功后即可进入系统。',
  228. },
  229. ];
  230. function syncAuthorizationCache(source?: unknown) {
  231. const { authorizationLabel, overDateTime, authorizationUnit } = extractAuthorizationCache(source);
  232. userStore.setPageCache('authorizationLabel', authorizationLabel);
  233. userStore.setPageCache('licenseOverDateTime', overDateTime);
  234. userStore.setPageCache('authorizationUnit', authorizationUnit);
  235. return {
  236. authorizationLabel,
  237. overDateTime,
  238. authorizationUnit,
  239. };
  240. }
  241. function clearAuthorizationCache() {
  242. syncAuthorizationCache();
  243. }
  244. async function resolveAuthorizationCache(source?: unknown) {
  245. const cache = syncAuthorizationCache(source);
  246. if (cache.overDateTime) {
  247. return cache;
  248. }
  249. try {
  250. const res = await authorizationInfoApi();
  251. return syncAuthorizationCache(res);
  252. } catch {
  253. return cache;
  254. }
  255. }
  256. function showExpiredAuthorizationMessage(overDateTime?: unknown) {
  257. authorizationWarningMessage.value = getAuthorizationExpiredMessage(overDateTime);
  258. showMessage(authorizationWarningMessage.value, 'warning');
  259. }
  260. async function loadMachineCode() {
  261. machineCodeLoading.value = true;
  262. try {
  263. const data = await machineCodeApi();
  264. machineCode.value = data || '';
  265. } catch (error) {
  266. machineCode.value = '';
  267. } finally {
  268. machineCodeLoading.value = false;
  269. }
  270. }
  271. function handleCopyMachineCode() {
  272. if (!machineCode.value) return;
  273. clipboardRef.value = machineCode.value;
  274. if (isSuccessRef.value) {
  275. showMessage('机器码复制成功', 'success');
  276. }
  277. }
  278. async function handleGetMachineCode() {
  279. await loadMachineCode();
  280. if (machineCode.value) {
  281. showMessage('机器码获取成功', 'success');
  282. }
  283. }
  284. async function handleSelectLicenseFile() {
  285. const ipcRenderer = (window as any)?.electron?.ipcRenderer;
  286. if (!ipcRenderer?.invoke) {
  287. showMessage('无法获取文件路径,请在 Electron 环境下选择授权文件');
  288. return;
  289. }
  290. try {
  291. const resolvedPath = await ipcRenderer.invoke('controller/effect/selectFile');
  292. const filePaths = Array.isArray(resolvedPath)
  293. ? resolvedPath.filter((item: unknown): item is string => typeof item === 'string' && !!item.trim())
  294. : typeof resolvedPath === 'string' && resolvedPath.trim()
  295. ? [resolvedPath]
  296. : [];
  297. if (!filePaths.length) return;
  298. licenseFilePath.value = filePaths[0];
  299. } catch (error: any) {
  300. showMessage(error?.message || '选择授权文件失败,请稍后重试');
  301. }
  302. }
  303. async function handleOfflineAuthorization() {
  304. if (!licenseFilePath.value) {
  305. showMessage('请先选择授权文件');
  306. return;
  307. }
  308. offlineAuthorizationLoading.value = true;
  309. try {
  310. const res = await offlineAuthorizationApi(licenseFilePath.value);
  311. authorizationWarningMessage.value = '';
  312. if (res?.code === 200) {
  313. const entered = await checkAuthAndEnterSystem();
  314. if (entered) {
  315. showMessage('离线授权成功', 'success');
  316. }
  317. } else {
  318. showMessage(res?.message || '离线授权失败');
  319. }
  320. } catch (error: any) {
  321. showMessage(error?.message || '离线授权失败,请稍后重试');
  322. } finally {
  323. offlineAuthorizationLoading.value = false;
  324. }
  325. }
  326. async function checkAuthAndEnterSystem(showUnauthorizedMessage = true) {
  327. try {
  328. const res = await checkAuthApi();
  329. const code = res?.code;
  330. if (code === 401) {
  331. clearAuthorizationCache();
  332. if (showUnauthorizedMessage) {
  333. showMessage('当前设备未授权,请先完成授权!');
  334. }
  335. return false;
  336. }
  337. if (code === 200) {
  338. const cache = await resolveAuthorizationCache(res);
  339. if (isAuthorizationExpired(cache.overDateTime)) {
  340. showExpiredAuthorizationMessage(cache.overDateTime);
  341. return false;
  342. }
  343. await initGlobalCaseConfig();
  344. await router.replace(PageEnum.BASE_CASE);
  345. return true;
  346. }
  347. clearAuthorizationCache();
  348. if (showUnauthorizedMessage) {
  349. showMessage('授权校验失败,请稍后重试!');
  350. }
  351. return false;
  352. } catch (error: any) {
  353. const status = error?.response?.status || error?.status;
  354. if (status === 401) {
  355. clearAuthorizationCache();
  356. if (showUnauthorizedMessage) {
  357. showMessage('当前设备未授权,请先完成授权!');
  358. }
  359. return false;
  360. }
  361. clearAuthorizationCache();
  362. if (showUnauthorizedMessage) {
  363. showMessage(error?.message || '授权校验失败,请稍后重试!');
  364. }
  365. return false;
  366. }
  367. }
  368. onMounted(() => {
  369. if (route.query.expired === '1') {
  370. authorizationWarningMessage.value = String(
  371. route.query.message || '当前授权已过期,请重新导入有效授权文件或联系管理员。',
  372. );
  373. showMessage(authorizationWarningMessage.value, 'warning');
  374. }
  375. if (route.query.forceAuth === '1' || route.query.skipAuthCheck === '1') {
  376. return;
  377. }
  378. void checkAuthAndEnterSystem(false);
  379. });
  380. </script>
  381. <style lang="less">
  382. @prefix-cls: ~'jeesite-authorization-entry';
  383. .@{prefix-cls} {
  384. height: 100vh;
  385. width: 100%;
  386. display: flex;
  387. flex-direction: column;
  388. overflow: hidden;
  389. background: #f4f7f9;
  390. font-family:
  391. -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
  392. .authorization-page {
  393. position: relative;
  394. display: flex;
  395. width: 100%;
  396. height: 100vh;
  397. overflow: hidden;
  398. }
  399. .case-header-bar {
  400. height: 48px;
  401. padding: 0 12px 0 0;
  402. background: #fff;
  403. flex-shrink: 0;
  404. -webkit-app-region: drag;
  405. .jeesite-app-logo {
  406. -webkit-app-region: no-drag;
  407. &__title {
  408. display: inline-flex;
  409. align-items: baseline;
  410. gap: 6px;
  411. font-size: 18px;
  412. font-weight: bold;
  413. color: #555;
  414. font-family: Arial, 'Microsoft YaHei', sans-serif;
  415. cursor: default;
  416. user-select: none;
  417. transition: all 0.5s;
  418. }
  419. }
  420. .jeesite-app-logo img {
  421. width: 32px;
  422. height: 32px;
  423. margin-right: 2px;
  424. }
  425. .jeesite-header-action-button {
  426. display: inline-flex;
  427. align-items: center;
  428. justify-content: center;
  429. width: 36px;
  430. height: 36px;
  431. border-radius: 10px;
  432. color: #333;
  433. font-size: 18px;
  434. cursor: pointer;
  435. transition: background-color 0.2s ease;
  436. -webkit-app-region: no-drag;
  437. &:hover {
  438. background-color: #f6f6f6;
  439. }
  440. }
  441. }
  442. .authorization-content {
  443. flex: 1;
  444. display: flex;
  445. min-height: 0;
  446. }
  447. .left-pane {
  448. position: relative;
  449. z-index: 0;
  450. display: none;
  451. flex-direction: column;
  452. width: 40%;
  453. padding: 32px 40px 24px;
  454. color: #fff;
  455. background: #165dff;
  456. min-height: 0;
  457. overflow: hidden;
  458. &__content {
  459. position: relative;
  460. z-index: 1;
  461. display: flex;
  462. flex: 1;
  463. min-height: 0;
  464. flex-direction: column;
  465. justify-content: space-between;
  466. gap: clamp(12px, 2vh, 24px);
  467. width: 100%;
  468. max-width: 520px;
  469. margin: 0 auto;
  470. }
  471. &__header {
  472. flex-shrink: 0;
  473. margin-top: 4px;
  474. }
  475. &__badge {
  476. display: none;
  477. align-items: center;
  478. gap: 6px;
  479. padding: 6px 12px;
  480. margin-bottom: 24px;
  481. font-size: 13px;
  482. font-weight: 500;
  483. color: #fff;
  484. letter-spacing: 0.04em;
  485. background: rgba(255, 255, 255, 0.1);
  486. border: 1px solid rgba(255, 255, 255, 0.12);
  487. border-radius: 999px;
  488. box-shadow: none;
  489. }
  490. &__title {
  491. margin: 0 0 16px;
  492. font-size: 36px;
  493. font-weight: 700;
  494. line-height: 1.18;
  495. letter-spacing: -0.02em;
  496. color: #fff;
  497. text-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
  498. }
  499. &__subtitle {
  500. max-width: 430px;
  501. margin: 0;
  502. font-size: 15px;
  503. font-weight: 500;
  504. line-height: 1.8;
  505. color: rgba(219, 234, 254, 0.92);
  506. }
  507. &__visual {
  508. display: flex;
  509. flex: 1 1 auto;
  510. align-items: center;
  511. justify-content: center;
  512. min-height: 60px;
  513. max-height: 200px;
  514. padding: 4px 0;
  515. }
  516. &__features {
  517. display: flex;
  518. flex-shrink: 0;
  519. flex-direction: column;
  520. gap: 18px;
  521. padding-bottom: 0;
  522. }
  523. &__footer {
  524. flex-shrink: 0;
  525. padding: 12px 0 0;
  526. }
  527. &__footer-line {
  528. margin: 0;
  529. font-size: 12px;
  530. line-height: 1.8;
  531. color: rgba(255, 255, 255, 0.5);
  532. }
  533. }
  534. .device-illustration {
  535. position: relative;
  536. width: 100%;
  537. max-width: 340px;
  538. aspect-ratio: 1 / 1;
  539. &__screen-shell {
  540. position: absolute;
  541. top: 50%;
  542. left: 50%;
  543. display: flex;
  544. flex-direction: column;
  545. width: 280px;
  546. height: 180px;
  547. padding: 20px;
  548. background: rgba(255, 255, 255, 0.1);
  549. border: 2px solid rgba(255, 255, 255, 0.2);
  550. border-radius: 18px;
  551. box-shadow: 0 24px 54px rgba(7, 24, 66, 0.24);
  552. backdrop-filter: blur(14px);
  553. transform: translate(-50%, -50%);
  554. }
  555. &__screen-dots {
  556. display: flex;
  557. gap: 8px;
  558. margin-bottom: 16px;
  559. span {
  560. width: 10px;
  561. height: 10px;
  562. background: rgba(255, 255, 255, 0.3);
  563. border-radius: 999px;
  564. }
  565. }
  566. &__screen-body {
  567. display: flex;
  568. flex: 1;
  569. flex-direction: column;
  570. gap: 10px;
  571. padding: 14px;
  572. background: rgba(255, 255, 255, 0.05);
  573. border: 1px solid rgba(255, 255, 255, 0.1);
  574. border-radius: 12px;
  575. }
  576. &__line {
  577. height: 10px;
  578. background: rgba(255, 255, 255, 0.2);
  579. border-radius: 999px;
  580. &--lg {
  581. width: 75%;
  582. }
  583. &--md {
  584. width: 50%;
  585. }
  586. }
  587. &__chart {
  588. display: flex;
  589. gap: 8px;
  590. align-items: center;
  591. justify-content: center;
  592. width: 100%;
  593. height: 54px;
  594. margin-top: auto;
  595. background: rgba(255, 255, 255, 0.1);
  596. border: 1px solid rgba(255, 255, 255, 0.1);
  597. border-radius: 10px;
  598. span {
  599. width: 16px;
  600. height: 16px;
  601. background: rgba(96, 165, 250, 0.5);
  602. border-radius: 4px;
  603. }
  604. }
  605. &__stand {
  606. position: absolute;
  607. bottom: calc(50% - 126px);
  608. left: 50%;
  609. width: 96px;
  610. height: 24px;
  611. background: rgba(255, 255, 255, 0.1);
  612. backdrop-filter: blur(12px);
  613. transform: translateX(-50%);
  614. &::before {
  615. content: '';
  616. position: absolute;
  617. bottom: -10px;
  618. left: 50%;
  619. width: 144px;
  620. height: 8px;
  621. background: rgba(255, 255, 255, 0.2);
  622. border-radius: 999px;
  623. filter: blur(2px);
  624. transform: translateX(-50%);
  625. }
  626. }
  627. &__shadow {
  628. position: absolute;
  629. inset: auto 22px 42px;
  630. height: 110px;
  631. background: radial-gradient(circle, rgba(255, 255, 255, 0.14) 0%, rgba(255, 255, 255, 0) 72%);
  632. filter: blur(22px);
  633. }
  634. }
  635. .floating-card {
  636. position: absolute;
  637. z-index: 2;
  638. display: flex;
  639. gap: 12px;
  640. align-items: center;
  641. padding: 16px;
  642. color: #165dff;
  643. background: #fff;
  644. border-radius: 16px;
  645. box-shadow: 0 18px 36px rgba(10, 22, 70, 0.18);
  646. &--left {
  647. top: 25%;
  648. left: -40px;
  649. animation: float-bounce-left 4s infinite;
  650. }
  651. &--right {
  652. right: -48px;
  653. bottom: 32%;
  654. animation: float-bounce-right 5s infinite 0.5s;
  655. }
  656. &__icon {
  657. display: flex;
  658. align-items: center;
  659. justify-content: center;
  660. width: 36px;
  661. height: 36px;
  662. font-size: 20px;
  663. color: #165dff;
  664. background: #eff6ff;
  665. border-radius: 12px;
  666. &--indigo {
  667. color: #4f46e5;
  668. background: #eef2ff;
  669. }
  670. }
  671. &__label {
  672. margin-bottom: 2px;
  673. font-size: 12px;
  674. font-weight: 500;
  675. color: #6b7280;
  676. }
  677. &__title {
  678. font-size: 14px;
  679. font-weight: 700;
  680. color: #111827;
  681. }
  682. }
  683. .feature-item {
  684. display: flex;
  685. gap: 16px;
  686. align-items: flex-start;
  687. &__icon {
  688. display: flex;
  689. align-items: center;
  690. justify-content: center;
  691. flex-shrink: 0;
  692. width: 48px;
  693. height: 48px;
  694. font-size: 24px;
  695. color: #fff;
  696. background: rgba(255, 255, 255, 0.1);
  697. border: 1px solid rgba(255, 255, 255, 0.1);
  698. border-radius: 14px;
  699. box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
  700. }
  701. &__title {
  702. margin: 0 0 4px;
  703. font-size: 16px;
  704. font-weight: 600;
  705. letter-spacing: 0.02em;
  706. color: #fff;
  707. }
  708. &__desc {
  709. max-width: 320px;
  710. margin: 0;
  711. font-size: 14px;
  712. line-height: 1.7;
  713. color: rgba(219, 234, 254, 0.82);
  714. }
  715. }
  716. .right-pane {
  717. position: relative;
  718. z-index: 10;
  719. display: flex;
  720. flex: 1;
  721. align-items: center;
  722. justify-content: center;
  723. padding: 24px;
  724. background: transparent;
  725. box-sizing: border-box;
  726. min-height: 0;
  727. overflow: visible;
  728. &__curve {
  729. position: absolute;
  730. top: 0;
  731. left: 0;
  732. z-index: 0;
  733. display: none;
  734. width: 120px;
  735. height: 100%;
  736. color: #f4f7f9;
  737. transform: translateX(-99%);
  738. pointer-events: none;
  739. }
  740. &__scroll {
  741. position: relative;
  742. z-index: 1;
  743. display: flex;
  744. flex: 1;
  745. align-items: center;
  746. justify-content: center;
  747. height: 100%;
  748. min-height: 0;
  749. overflow-y: auto;
  750. overflow-x: visible;
  751. scrollbar-width: none;
  752. &::-webkit-scrollbar {
  753. width: 0;
  754. height: 0;
  755. }
  756. }
  757. }
  758. .auth-card {
  759. position: relative;
  760. z-index: 10;
  761. width: 100%;
  762. max-width: 540px;
  763. padding: 16px;
  764. margin: auto 0;
  765. background: #fff;
  766. border: 1px solid #f3f4f6;
  767. border-radius: 18px;
  768. box-shadow: 0 8px 40px -12px rgba(0, 0, 0, 0.08);
  769. &__header {
  770. margin-bottom: 10px;
  771. }
  772. &__eyebrow {
  773. display: inline-block;
  774. padding: 1px 8px;
  775. margin-bottom: 6px;
  776. font-size: 11px;
  777. line-height: 18px;
  778. font-weight: 700;
  779. letter-spacing: 0.04em;
  780. color: #2563eb;
  781. background: #eff6ff;
  782. border: 1px solid #dbeafe;
  783. border-radius: 6px;
  784. }
  785. &__title {
  786. margin: 0 0 4px;
  787. font-size: 20px;
  788. font-weight: 700;
  789. letter-spacing: -0.02em;
  790. color: #111827;
  791. }
  792. &__intro {
  793. margin: 0;
  794. padding: 7px 9px;
  795. font-size: 13px;
  796. line-height: 1.5;
  797. color: #6b7280;
  798. background: #f8fafc;
  799. border: 1px solid #f3f4f6;
  800. border-radius: 12px;
  801. strong {
  802. font-weight: 700;
  803. color: #374151;
  804. }
  805. }
  806. &__warning {
  807. padding: 10px 12px;
  808. margin-bottom: 8px;
  809. font-size: 13px;
  810. font-weight: 700;
  811. line-height: 1.6;
  812. color: #b91c1c;
  813. background: #fef2f2;
  814. border: 1px solid #fecaca;
  815. border-radius: 10px;
  816. }
  817. &__steps {
  818. display: grid;
  819. grid-template-columns: 1fr;
  820. gap: 6px;
  821. margin-bottom: 10px;
  822. }
  823. &__section {
  824. margin-bottom: 8px;
  825. }
  826. &__section-head {
  827. display: flex;
  828. justify-content: space-between;
  829. gap: 8px;
  830. align-items: baseline;
  831. margin-bottom: 3px;
  832. }
  833. &__section-title {
  834. margin: 0;
  835. font-size: 16px;
  836. font-weight: 700;
  837. color: #111827;
  838. }
  839. &__section-tip {
  840. font-size: 12px;
  841. font-weight: 500;
  842. color: #9ca3af;
  843. }
  844. &__file-input {
  845. display: none;
  846. }
  847. }
  848. .step-card {
  849. position: relative;
  850. padding: 8px;
  851. background: #f8fafc;
  852. border: 1px solid #f3f4f6;
  853. border-radius: 10px;
  854. transition:
  855. background-color 0.2s ease,
  856. border-color 0.2s ease,
  857. transform 0.2s ease;
  858. &:hover {
  859. background: rgba(239, 246, 255, 0.6);
  860. border-color: #bfdbfe;
  861. transform: translateY(-1px);
  862. .step-card__index {
  863. transform: scale(1.1);
  864. }
  865. }
  866. &__index {
  867. display: flex;
  868. align-items: center;
  869. justify-content: center;
  870. width: 18px;
  871. height: 18px;
  872. margin-bottom: 4px;
  873. font-size: 9px;
  874. font-weight: 700;
  875. color: #fff;
  876. background: #2563eb;
  877. border-radius: 999px;
  878. box-shadow: 0 4px 12px rgba(37, 99, 235, 0.2);
  879. transition: transform 0.2s ease;
  880. }
  881. &__title {
  882. margin: 0 0 2px;
  883. font-size: 13px;
  884. font-weight: 700;
  885. color: #111827;
  886. }
  887. &__desc {
  888. margin: 0;
  889. font-size: 12px;
  890. line-height: 1.45;
  891. color: #6b7280;
  892. }
  893. &__arrow {
  894. position: absolute;
  895. top: 18px;
  896. right: -10px;
  897. z-index: 10;
  898. display: none;
  899. font-size: 14px;
  900. color: #d1d5db;
  901. }
  902. }
  903. .machine-panel,
  904. .upload-panel {
  905. padding: 7px;
  906. background: #f8fafc;
  907. border: 1px solid #e5e7eb;
  908. border-radius: 10px;
  909. box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
  910. }
  911. .machine-panel {
  912. &__value {
  913. min-height: 40px;
  914. margin-bottom: 6px;
  915. padding: 7px 9px;
  916. font-family: Consolas, 'Courier New', monospace;
  917. font-size: 12px;
  918. line-height: 1.5;
  919. color: #4b5563;
  920. word-break: break-all;
  921. background: #fff;
  922. border: 1px solid #e5e7eb;
  923. border-radius: 8px;
  924. box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
  925. }
  926. &__actions {
  927. display: flex;
  928. justify-content: flex-end;
  929. gap: 4px;
  930. }
  931. }
  932. .upload-panel {
  933. &__dropzone {
  934. display: flex;
  935. flex-direction: column;
  936. align-items: center;
  937. justify-content: center;
  938. gap: 2px;
  939. min-height: 64px;
  940. padding: 8px;
  941. margin-bottom: 6px;
  942. text-align: center;
  943. cursor: pointer;
  944. background: #fff;
  945. border: 2px dashed #d1d5db;
  946. border-radius: 8px;
  947. transition:
  948. background-color 0.2s ease,
  949. border-color 0.2s ease,
  950. transform 0.2s ease;
  951. &:hover {
  952. background: rgba(239, 246, 255, 0.6);
  953. border-color: #93c5fd;
  954. .upload-panel__dropzone-icon {
  955. transform: scale(1.1);
  956. }
  957. .upload-panel__dropzone-text {
  958. color: #3b82f6;
  959. }
  960. }
  961. }
  962. &__dropzone-icon {
  963. font-size: 18px;
  964. color: #3b82f6;
  965. transition: transform 0.2s ease;
  966. }
  967. &__dropzone-text {
  968. font-size: 12px;
  969. font-weight: 500;
  970. line-height: 1.45;
  971. color: #9ca3af;
  972. word-break: break-all;
  973. transition: color 0.2s ease;
  974. }
  975. &__actions {
  976. display: flex;
  977. justify-content: flex-end;
  978. gap: 4px;
  979. }
  980. }
  981. .ui-btn {
  982. display: inline-flex;
  983. gap: 4px;
  984. align-items: center;
  985. justify-content: center;
  986. height: auto;
  987. padding: 4px 9px;
  988. font-size: 12px;
  989. font-weight: 500;
  990. border-radius: 8px;
  991. transition:
  992. background-color 0.2s ease,
  993. border-color 0.2s ease,
  994. color 0.2s ease,
  995. box-shadow 0.2s ease,
  996. transform 0.2s ease;
  997. &.ant-btn {
  998. box-shadow: none;
  999. }
  1000. &.ant-btn > span {
  1001. display: inline-flex;
  1002. gap: 4px;
  1003. align-items: center;
  1004. }
  1005. &:active {
  1006. transform: scale(0.99);
  1007. }
  1008. &--ghost {
  1009. color: #374151;
  1010. background: #fff;
  1011. border-color: #d1d5db;
  1012. box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05);
  1013. &:hover,
  1014. &:focus {
  1015. color: #374151;
  1016. background: #f9fafb;
  1017. border-color: #d1d5db;
  1018. }
  1019. }
  1020. &--primary {
  1021. color: #fff;
  1022. background: #165dff;
  1023. border-color: #165dff;
  1024. box-shadow: 0 6px 18px rgba(22, 93, 255, 0.28);
  1025. &:hover,
  1026. &:focus,
  1027. &.ant-btn-primary:hover,
  1028. &.ant-btn-primary:focus {
  1029. color: #fff;
  1030. background: #0f56f0;
  1031. border-color: #0f56f0;
  1032. }
  1033. &.ant-btn-primary,
  1034. &.ant-btn-color-primary,
  1035. &.ant-btn-variant-solid {
  1036. color: #fff;
  1037. background: #165dff;
  1038. border-color: #165dff;
  1039. }
  1040. &.ant-btn-primary:disabled,
  1041. &.ant-btn-primary[disabled],
  1042. &.ant-btn-color-primary:disabled,
  1043. &.ant-btn-color-primary[disabled],
  1044. &.ant-btn-variant-solid:disabled,
  1045. &.ant-btn-variant-solid[disabled] {
  1046. color: rgba(255, 255, 255, 0.88);
  1047. background: rgba(22, 93, 255, 0.55);
  1048. border-color: rgba(22, 93, 255, 0.55);
  1049. box-shadow: none;
  1050. opacity: 1;
  1051. }
  1052. }
  1053. }
  1054. @keyframes float-bounce-left {
  1055. 0%,
  1056. 100% {
  1057. transform: translate3d(0, 0, 0);
  1058. }
  1059. 50% {
  1060. transform: translate3d(0, -12px, 0);
  1061. }
  1062. }
  1063. @keyframes float-bounce-right {
  1064. 0%,
  1065. 100% {
  1066. transform: translate3d(0, 0, 0);
  1067. }
  1068. 50% {
  1069. transform: translate3d(0, -10px, 0);
  1070. }
  1071. }
  1072. }
  1073. @media screen and (min-width: 992px) {
  1074. .@{prefix-cls} {
  1075. .auth-card {
  1076. padding: 40px;
  1077. }
  1078. }
  1079. }
  1080. @media screen and (min-width: 1280px) {
  1081. .@{prefix-cls} {
  1082. .left-pane {
  1083. display: flex;
  1084. }
  1085. .right-pane {
  1086. padding: 48px;
  1087. &__curve {
  1088. display: block;
  1089. }
  1090. }
  1091. .auth-card {
  1092. &__steps {
  1093. grid-template-columns: repeat(3, minmax(0, 1fr));
  1094. }
  1095. }
  1096. .step-card {
  1097. &__arrow {
  1098. display: block;
  1099. }
  1100. }
  1101. }
  1102. }
  1103. @media screen and (min-width: 1536px) {
  1104. .@{prefix-cls} {
  1105. .left-pane {
  1106. width: 45%;
  1107. padding: 56px;
  1108. &__title {
  1109. font-size: 42px;
  1110. }
  1111. &__subtitle {
  1112. font-size: 16px;
  1113. }
  1114. }
  1115. .right-pane__curve {
  1116. width: 160px;
  1117. }
  1118. .device-illustration {
  1119. max-width: 340px;
  1120. &__screen-shell {
  1121. width: 280px;
  1122. height: 180px;
  1123. }
  1124. }
  1125. .floating-card {
  1126. padding: 16px;
  1127. }
  1128. .feature-item {
  1129. &__icon {
  1130. width: 48px;
  1131. height: 48px;
  1132. font-size: 24px;
  1133. }
  1134. &__title {
  1135. font-size: 16px;
  1136. }
  1137. &__desc {
  1138. font-size: 14px;
  1139. }
  1140. }
  1141. }
  1142. }
  1143. @media screen and (max-width: 1279px) {
  1144. .@{prefix-cls} {
  1145. .left-pane {
  1146. display: none;
  1147. }
  1148. .right-pane {
  1149. align-items: center;
  1150. padding: 24px 20px;
  1151. }
  1152. .right-pane__scroll {
  1153. align-items: center;
  1154. }
  1155. .auth-card {
  1156. margin: auto 0;
  1157. }
  1158. }
  1159. }
  1160. @media screen and (max-width: 767px) {
  1161. .@{prefix-cls} {
  1162. .right-pane {
  1163. padding: 16px;
  1164. align-items: flex-start;
  1165. }
  1166. .right-pane__scroll {
  1167. align-items: flex-start;
  1168. }
  1169. .auth-card {
  1170. padding: 22px 18px;
  1171. margin: 0;
  1172. &__header {
  1173. margin-bottom: 24px;
  1174. }
  1175. &__section-head {
  1176. flex-direction: column;
  1177. align-items: flex-start;
  1178. }
  1179. }
  1180. .machine-panel__actions,
  1181. .upload-panel__actions {
  1182. flex-wrap: wrap;
  1183. }
  1184. .machine-panel__actions > *,
  1185. .upload-panel__actions > * {
  1186. flex: 1 1 calc(50% - 5px);
  1187. }
  1188. }
  1189. }
  1190. @media screen and (max-width: 1366px) and (min-width: 1280px) and (max-height: 768px) {
  1191. .@{prefix-cls} {
  1192. .left-pane {
  1193. width: 37%;
  1194. padding: 20px 24px 16px;
  1195. &__content {
  1196. max-width: 420px;
  1197. gap: 14px;
  1198. }
  1199. &__header {
  1200. margin-top: 2px;
  1201. }
  1202. &__badge {
  1203. margin-bottom: 14px;
  1204. padding: 5px 10px;
  1205. font-size: 12px;
  1206. }
  1207. &__title {
  1208. margin-bottom: 10px;
  1209. font-size: 28px;
  1210. line-height: 1.14;
  1211. }
  1212. &__subtitle {
  1213. max-width: 360px;
  1214. font-size: 13px;
  1215. line-height: 1.6;
  1216. }
  1217. &__visual {
  1218. min-height: 40px;
  1219. padding: 2px 0;
  1220. }
  1221. &__features {
  1222. gap: 10px;
  1223. padding-bottom: 0;
  1224. }
  1225. }
  1226. .device-illustration {
  1227. max-width: 236px;
  1228. &__screen-shell {
  1229. width: 208px;
  1230. height: 136px;
  1231. padding: 14px;
  1232. border-radius: 16px;
  1233. }
  1234. &__screen-dots {
  1235. gap: 6px;
  1236. margin-bottom: 12px;
  1237. span {
  1238. width: 8px;
  1239. height: 8px;
  1240. }
  1241. }
  1242. &__screen-body {
  1243. gap: 8px;
  1244. padding: 10px;
  1245. }
  1246. &__line {
  1247. height: 8px;
  1248. }
  1249. &__chart {
  1250. height: 40px;
  1251. span {
  1252. width: 13px;
  1253. height: 13px;
  1254. }
  1255. }
  1256. &__stand {
  1257. bottom: calc(50% - 94px);
  1258. width: 78px;
  1259. height: 18px;
  1260. &::before {
  1261. bottom: -8px;
  1262. width: 112px;
  1263. height: 6px;
  1264. }
  1265. }
  1266. &__shadow {
  1267. inset: auto 18px 32px;
  1268. height: 72px;
  1269. }
  1270. }
  1271. .floating-card {
  1272. gap: 10px;
  1273. padding: 10px 12px;
  1274. border-radius: 14px;
  1275. &--left {
  1276. top: 23%;
  1277. left: -12px;
  1278. }
  1279. &--right {
  1280. right: -16px;
  1281. bottom: 27%;
  1282. }
  1283. &__icon {
  1284. width: 30px;
  1285. height: 30px;
  1286. font-size: 16px;
  1287. border-radius: 10px;
  1288. }
  1289. &__label {
  1290. font-size: 10px;
  1291. }
  1292. &__title {
  1293. font-size: 13px;
  1294. }
  1295. }
  1296. .feature-item {
  1297. gap: 10px;
  1298. &__icon {
  1299. width: 36px;
  1300. height: 36px;
  1301. font-size: 18px;
  1302. border-radius: 12px;
  1303. }
  1304. &__title {
  1305. margin-bottom: 2px;
  1306. font-size: 14px;
  1307. }
  1308. &__desc {
  1309. max-width: 280px;
  1310. font-size: 12px;
  1311. line-height: 1.45;
  1312. }
  1313. }
  1314. .right-pane {
  1315. align-items: center;
  1316. padding: 14px 18px;
  1317. }
  1318. .right-pane__scroll {
  1319. align-items: center;
  1320. }
  1321. .auth-card {
  1322. max-width: 522px;
  1323. padding: 20px 20px 18px;
  1324. border-radius: 18px;
  1325. &__header {
  1326. margin-bottom: 18px;
  1327. }
  1328. &__eyebrow {
  1329. margin-bottom: 10px;
  1330. font-size: 11px;
  1331. }
  1332. &__title {
  1333. margin-bottom: 8px;
  1334. font-size: 18px;
  1335. }
  1336. &__intro {
  1337. padding: 10px 12px;
  1338. font-size: 12px;
  1339. line-height: 1.55;
  1340. border-radius: 12px;
  1341. }
  1342. &__steps {
  1343. gap: 8px;
  1344. margin-bottom: 18px;
  1345. }
  1346. &__section {
  1347. margin-bottom: 14px;
  1348. }
  1349. &__section-head {
  1350. margin-bottom: 6px;
  1351. }
  1352. &__section-title {
  1353. font-size: 13px;
  1354. }
  1355. &__section-tip {
  1356. font-size: 11px;
  1357. }
  1358. &__footer {
  1359. padding-top: 14px;
  1360. margin-top: 18px;
  1361. }
  1362. }
  1363. .step-card {
  1364. padding: 12px 10px;
  1365. border-radius: 12px;
  1366. &__index {
  1367. width: 22px;
  1368. height: 22px;
  1369. margin-bottom: 8px;
  1370. font-size: 10px;
  1371. }
  1372. &__title {
  1373. margin-bottom: 5px;
  1374. font-size: 12px;
  1375. }
  1376. &__desc {
  1377. font-size: 11px;
  1378. line-height: 1.5;
  1379. }
  1380. &__arrow {
  1381. top: 23px;
  1382. right: -7px;
  1383. font-size: 14px;
  1384. }
  1385. }
  1386. .machine-panel,
  1387. .upload-panel {
  1388. padding: 10px;
  1389. border-radius: 12px;
  1390. }
  1391. .machine-panel {
  1392. &__value {
  1393. min-height: 54px;
  1394. margin-bottom: 10px;
  1395. padding: 10px 12px;
  1396. font-size: 11px;
  1397. line-height: 1.6;
  1398. border-radius: 10px;
  1399. }
  1400. &__actions {
  1401. gap: 8px;
  1402. }
  1403. }
  1404. .upload-panel {
  1405. &__dropzone {
  1406. min-height: 84px;
  1407. padding: 12px;
  1408. margin-bottom: 10px;
  1409. }
  1410. &__dropzone-icon {
  1411. font-size: 22px;
  1412. }
  1413. &__dropzone-text {
  1414. font-size: 12px;
  1415. line-height: 1.5;
  1416. }
  1417. &__actions {
  1418. gap: 8px;
  1419. }
  1420. }
  1421. .ui-btn {
  1422. padding: 7px 14px;
  1423. font-size: 12px;
  1424. border-radius: 9px;
  1425. }
  1426. }
  1427. }
  1428. @media screen and (max-height: 860px) and (min-width: 1280px) {
  1429. .@{prefix-cls} {
  1430. .left-pane {
  1431. padding: 24px 32px 16px;
  1432. &__content {
  1433. gap: 18px;
  1434. }
  1435. &__title {
  1436. margin-bottom: 10px;
  1437. font-size: 30px;
  1438. }
  1439. &__subtitle {
  1440. font-size: 14px;
  1441. line-height: 1.6;
  1442. }
  1443. &__visual {
  1444. min-height: 80px;
  1445. padding: 4px 0;
  1446. }
  1447. &__features {
  1448. gap: 14px;
  1449. }
  1450. }
  1451. .device-illustration {
  1452. max-width: 290px;
  1453. &__screen-shell {
  1454. width: 244px;
  1455. height: 160px;
  1456. padding: 16px;
  1457. }
  1458. &__screen-body {
  1459. padding: 12px;
  1460. gap: 8px;
  1461. }
  1462. &__chart {
  1463. height: 48px;
  1464. }
  1465. }
  1466. .floating-card {
  1467. padding: 12px 14px;
  1468. &--left {
  1469. left: -22px;
  1470. }
  1471. &--right {
  1472. right: -28px;
  1473. }
  1474. }
  1475. .feature-item {
  1476. gap: 12px;
  1477. &__icon {
  1478. width: 42px;
  1479. height: 42px;
  1480. font-size: 20px;
  1481. }
  1482. &__title {
  1483. font-size: 15px;
  1484. }
  1485. &__desc {
  1486. font-size: 13px;
  1487. line-height: 1.6;
  1488. }
  1489. }
  1490. .right-pane {
  1491. align-items: flex-start;
  1492. padding-top: 18px;
  1493. padding-bottom: 18px;
  1494. }
  1495. .right-pane__scroll {
  1496. align-items: flex-start;
  1497. }
  1498. .auth-card {
  1499. padding: 26px 26px 22px;
  1500. }
  1501. .auth-card__steps {
  1502. margin-bottom: 28px;
  1503. }
  1504. .step-card {
  1505. padding: 14px;
  1506. }
  1507. }
  1508. }
  1509. </style>