style.css 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841
  1. :root {
  2. /* Colors */
  3. --bg-primary: #0a0a0f;
  4. --bg-card: rgba(18, 18, 26, 0.7);
  5. --bg-card-hover: rgba(26, 26, 40, 0.85);
  6. --text-main: #f0f0f5;
  7. --text-muted: #8b8b9e;
  8. --border-subtle: rgba(255, 255, 255, 0.08);
  9. --border-highlight: rgba(255, 255, 255, 0.15);
  10. --accent-red: #ff3366;
  11. --accent-red-hover: #ff4d79;
  12. --accent-blue: #00d4ff;
  13. --accent-blue-hover: #33ddff;
  14. --accent-green: #00e676;
  15. --status-active: #ff4757;
  16. --status-intermittent: #ffa502;
  17. --status-stored: #2ed573;
  18. --status-disconnected: #636e72;
  19. /* Fonts */
  20. --font-display: 'Outfit', system-ui, sans-serif;
  21. --font-mono: 'JetBrains Mono', monospace;
  22. }
  23. * {
  24. box-sizing: border-box;
  25. margin: 0;
  26. padding: 0;
  27. }
  28. body {
  29. background-color: var(--bg-primary);
  30. background-image:
  31. radial-gradient(circle at 15% 50%, rgba(0, 212, 255, 0.05), transparent 25%),
  32. radial-gradient(circle at 85% 30%, rgba(255, 51, 102, 0.05), transparent 25%);
  33. background-attachment: fixed;
  34. color: var(--text-main);
  35. font-family: var(--font-display);
  36. line-height: 1.5;
  37. height: 100vh;
  38. overflow: hidden;
  39. }
  40. .app-container {
  41. display: flex;
  42. flex-direction: column;
  43. height: 100vh;
  44. padding: 1rem;
  45. gap: 1rem;
  46. }
  47. /* Glassmorphism utility */
  48. .glass-panel {
  49. background: var(--bg-card);
  50. backdrop-filter: blur(12px);
  51. -webkit-backdrop-filter: blur(12px);
  52. border: 1px solid var(--border-subtle);
  53. border-radius: 12px;
  54. box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.3);
  55. }
  56. /* Typography */
  57. h1 {
  58. font-size: 1.5rem;
  59. font-weight: 800;
  60. letter-spacing: 1px;
  61. }
  62. h1 span {
  63. color: var(--accent-red);
  64. font-weight: 300;
  65. }
  66. h2 {
  67. font-size: 1.2rem;
  68. font-weight: 600;
  69. color: var(--text-main);
  70. }
  71. h3 {
  72. font-size: 1rem;
  73. font-weight: 600;
  74. }
  75. .font-mono {
  76. font-family: var(--font-mono);
  77. }
  78. /* Header */
  79. .header {
  80. display: flex;
  81. justify-content: space-between;
  82. align-items: center;
  83. padding: 1rem 1.5rem;
  84. flex-shrink: 0;
  85. }
  86. .logo-area {
  87. display: flex;
  88. align-items: center;
  89. gap: 1rem;
  90. }
  91. .rings {
  92. display: flex;
  93. gap: -8px; /* Overlap */
  94. }
  95. .ring {
  96. width: 24px;
  97. height: 24px;
  98. border: 3px solid var(--text-main);
  99. border-radius: 50%;
  100. margin-right: -8px;
  101. }
  102. .connection-area {
  103. display: flex;
  104. align-items: center;
  105. gap: 1rem;
  106. }
  107. .port-selector {
  108. display: flex;
  109. align-items: center;
  110. gap: 0.5rem;
  111. }
  112. select, input {
  113. background: rgba(0,0,0,0.3);
  114. border: 1px solid var(--border-subtle);
  115. color: var(--text-main);
  116. padding: 0.5rem 1rem;
  117. border-radius: 6px;
  118. font-family: var(--font-display);
  119. font-size: 0.9rem;
  120. outline: none;
  121. transition: all 0.2s;
  122. }
  123. select:focus, input:focus {
  124. border-color: var(--accent-blue);
  125. box-shadow: 0 0 0 2px rgba(0, 212, 255, 0.2);
  126. }
  127. /* Buttons */
  128. .btn {
  129. padding: 0.5rem 1.2rem;
  130. border: none;
  131. border-radius: 6px;
  132. font-family: var(--font-display);
  133. font-size: 0.9rem;
  134. font-weight: 600;
  135. cursor: pointer;
  136. transition: all 0.2s ease;
  137. text-transform: uppercase;
  138. letter-spacing: 0.5px;
  139. }
  140. .btn:disabled {
  141. opacity: 0.5;
  142. cursor: not-allowed;
  143. }
  144. .btn:active {
  145. transform: scale(0.98);
  146. }
  147. .btn-primary {
  148. background: var(--accent-blue);
  149. color: #000;
  150. }
  151. .btn-primary:hover:not(:disabled) {
  152. background: var(--accent-blue-hover);
  153. box-shadow: 0 0 15px rgba(0, 212, 255, 0.4);
  154. }
  155. .btn-secondary {
  156. background: rgba(255,255,255,0.1);
  157. color: var(--text-main);
  158. }
  159. .btn-secondary:hover:not(:disabled) {
  160. background: rgba(255,255,255,0.2);
  161. }
  162. .btn-danger {
  163. background: rgba(255, 51, 102, 0.2);
  164. color: var(--accent-red);
  165. border: 1px solid var(--accent-red);
  166. }
  167. .btn-danger:hover:not(:disabled) {
  168. background: var(--accent-red);
  169. color: #fff;
  170. box-shadow: 0 0 15px rgba(255, 51, 102, 0.4);
  171. }
  172. .icon-btn {
  173. background: none;
  174. border: none;
  175. color: var(--text-muted);
  176. font-size: 1.2rem;
  177. cursor: pointer;
  178. transition: color 0.2s;
  179. }
  180. .icon-btn:hover {
  181. color: var(--text-main);
  182. }
  183. /* Status Indicator */
  184. .status-indicator {
  185. display: flex;
  186. align-items: center;
  187. gap: 0.5rem;
  188. font-size: 0.85rem;
  189. font-weight: 600;
  190. text-transform: uppercase;
  191. width: 100px;
  192. }
  193. .dot {
  194. width: 10px;
  195. height: 10px;
  196. border-radius: 50%;
  197. }
  198. .dot.disconnected { background: var(--status-disconnected); }
  199. .dot.connected {
  200. background: var(--accent-green);
  201. box-shadow: 0 0 10px var(--accent-green);
  202. animation: pulse 2s infinite;
  203. }
  204. @keyframes pulse {
  205. 0% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(0, 230, 118, 0.7); }
  206. 70% { transform: scale(1); box-shadow: 0 0 0 6px rgba(0, 230, 118, 0); }
  207. 100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(0, 230, 118, 0); }
  208. }
  209. /* Layout Utilities */
  210. .hidden { display: none !important; }
  211. .main-content {
  212. display: flex;
  213. flex-direction: column;
  214. gap: 1rem;
  215. flex: 1;
  216. overflow-y: auto;
  217. overflow-x: hidden;
  218. }
  219. /* ECU Info Bar */
  220. .ecu-info-bar {
  221. display: flex;
  222. padding: 1rem 1.5rem;
  223. gap: 2rem;
  224. flex-shrink: 0;
  225. }
  226. .ecu-item {
  227. display: flex;
  228. flex-direction: column;
  229. }
  230. .ecu-item .label {
  231. font-size: 0.7rem;
  232. color: var(--text-muted);
  233. text-transform: uppercase;
  234. letter-spacing: 1px;
  235. }
  236. .ecu-item .value {
  237. font-size: 1.1rem;
  238. font-weight: 600;
  239. color: var(--accent-blue);
  240. }
  241. /* Dashboard Grid */
  242. .dashboard-grid {
  243. display: grid;
  244. grid-template-columns: 1fr 1.5fr;
  245. gap: 1rem;
  246. flex: 1;
  247. min-height: 500px;
  248. }
  249. .panel {
  250. display: flex;
  251. flex-direction: column;
  252. padding: 1.5rem;
  253. }
  254. .panel-header {
  255. display: flex;
  256. justify-content: space-between;
  257. align-items: center;
  258. margin-bottom: 1.5rem;
  259. }
  260. .panel-actions {
  261. display: flex;
  262. gap: 0.5rem;
  263. }
  264. .scrollable {
  265. overflow-y: auto;
  266. /* Custom Scrollbar */
  267. scrollbar-width: thin;
  268. scrollbar-color: var(--border-highlight) transparent;
  269. }
  270. .scrollable::-webkit-scrollbar { width: 6px; }
  271. .scrollable::-webkit-scrollbar-track { background: transparent; }
  272. .scrollable::-webkit-scrollbar-thumb { background-color: var(--border-highlight); border-radius: 3px; }
  273. /* DTC List */
  274. .dtc-list {
  275. display: flex;
  276. flex-direction: column;
  277. gap: 0.8rem;
  278. flex: 1;
  279. }
  280. .empty-state {
  281. display: flex;
  282. flex-direction: column;
  283. align-items: center;
  284. justify-content: center;
  285. height: 100%;
  286. color: var(--text-muted);
  287. text-align: center;
  288. }
  289. .empty-state .icon {
  290. font-size: 3rem;
  291. margin-bottom: 1rem;
  292. color: var(--border-highlight);
  293. }
  294. .empty-state.success .icon {
  295. color: var(--accent-green);
  296. }
  297. .dtc-card {
  298. background: rgba(0,0,0,0.2);
  299. border: 1px solid var(--border-subtle);
  300. border-radius: 8px;
  301. padding: 1rem;
  302. transition: transform 0.2s;
  303. }
  304. .dtc-card:hover {
  305. transform: translateY(-2px);
  306. background: rgba(255,255,255,0.02);
  307. }
  308. .dtc-header {
  309. display: flex;
  310. justify-content: space-between;
  311. align-items: flex-start;
  312. margin-bottom: 0.5rem;
  313. }
  314. .dtc-code {
  315. font-family: var(--font-mono);
  316. font-size: 1.2rem;
  317. font-weight: 700;
  318. color: var(--text-main);
  319. }
  320. .dtc-badge {
  321. padding: 0.2rem 0.6rem;
  322. border-radius: 12px;
  323. font-size: 0.75rem;
  324. font-weight: 600;
  325. text-transform: uppercase;
  326. }
  327. .dtc-badge.active { background: rgba(255, 71, 87, 0.2); color: var(--status-active); border: 1px solid var(--status-active); }
  328. .dtc-badge.intermittent { background: rgba(255, 165, 2, 0.2); color: var(--status-intermittent); border: 1px solid var(--status-intermittent); }
  329. .dtc-badge.stored { background: rgba(46, 213, 115, 0.2); color: var(--status-stored); border: 1px solid var(--status-stored); }
  330. .dtc-desc {
  331. font-size: 0.95rem;
  332. color: var(--text-main);
  333. margin-bottom: 0.5rem;
  334. }
  335. .dtc-details {
  336. font-size: 0.8rem;
  337. color: var(--text-muted);
  338. }
  339. /* Measurements */
  340. .group-selector {
  341. display: flex;
  342. align-items: center;
  343. gap: 0.5rem;
  344. }
  345. .group-pills {
  346. display: flex;
  347. gap: 0.3rem;
  348. }
  349. .pill {
  350. background: rgba(255,255,255,0.05);
  351. border: 1px solid var(--border-subtle);
  352. color: var(--text-muted);
  353. padding: 0.3rem 0.6rem;
  354. border-radius: 4px;
  355. cursor: pointer;
  356. font-weight: 600;
  357. transition: all 0.2s;
  358. }
  359. .pill:hover { background: rgba(255,255,255,0.1); color: var(--text-main); }
  360. .pill.active { background: var(--accent-blue); color: #000; border-color: var(--accent-blue); }
  361. #customGroupInput {
  362. width: 60px;
  363. padding: 0.3rem 0.5rem;
  364. }
  365. .meas-controls {
  366. display: flex;
  367. justify-content: space-between;
  368. align-items: center;
  369. margin-bottom: 1.5rem;
  370. background: rgba(0,0,0,0.2);
  371. padding: 0.8rem 1rem;
  372. border-radius: 8px;
  373. }
  374. .group-name {
  375. font-weight: 600;
  376. color: var(--accent-blue);
  377. font-size: 1.1rem;
  378. }
  379. /* Toggle Switch */
  380. .switch {
  381. position: relative;
  382. display: inline-block;
  383. width: 44px;
  384. height: 24px;
  385. }
  386. .switch input { opacity: 0; width: 0; height: 0; }
  387. .slider {
  388. position: absolute;
  389. cursor: pointer;
  390. top: 0; left: 0; right: 0; bottom: 0;
  391. background-color: rgba(255,255,255,0.1);
  392. transition: .4s;
  393. }
  394. .slider:before {
  395. position: absolute;
  396. content: "";
  397. height: 16px; width: 16px;
  398. left: 4px; bottom: 4px;
  399. background-color: var(--text-muted);
  400. transition: .4s;
  401. }
  402. input:checked + .slider { background-color: rgba(0, 212, 255, 0.3); border: 1px solid var(--accent-blue); }
  403. input:checked + .slider:before { transform: translateX(20px); background-color: var(--accent-blue); }
  404. .slider.round { border-radius: 24px; }
  405. .slider.round:before { border-radius: 50%; }
  406. .live-toggle {
  407. display: flex;
  408. align-items: center;
  409. gap: 0.8rem;
  410. font-size: 0.9rem;
  411. font-weight: 600;
  412. }
  413. /* Measurement Grid */
  414. .meas-grid {
  415. display: grid;
  416. grid-template-columns: 1fr 1fr;
  417. grid-template-rows: 1fr 1fr;
  418. gap: 1rem;
  419. flex: 1;
  420. }
  421. .meas-card {
  422. background: rgba(0,0,0,0.2);
  423. border: 1px solid var(--border-subtle);
  424. border-radius: 12px;
  425. padding: 1.5rem;
  426. display: flex;
  427. flex-direction: column;
  428. justify-content: space-between;
  429. position: relative;
  430. overflow: hidden;
  431. transition: border-color 0.3s;
  432. }
  433. .meas-card.updated {
  434. border-color: rgba(0, 212, 255, 0.5);
  435. }
  436. .meas-label {
  437. font-size: 0.85rem;
  438. color: var(--text-muted);
  439. font-weight: 600;
  440. text-transform: uppercase;
  441. z-index: 2;
  442. }
  443. .meas-value-area {
  444. display: flex;
  445. align-items: baseline;
  446. gap: 0.3rem;
  447. z-index: 2;
  448. }
  449. .meas-val {
  450. font-family: var(--font-mono);
  451. font-size: 2.5rem;
  452. font-weight: 700;
  453. color: var(--text-main);
  454. }
  455. .meas-unit {
  456. font-size: 1.2rem;
  457. color: var(--accent-blue);
  458. font-weight: 600;
  459. }
  460. .meas-sparkline {
  461. position: absolute;
  462. bottom: 0;
  463. left: 0;
  464. width: 100%;
  465. height: 40px;
  466. opacity: 0.4;
  467. z-index: 1;
  468. }
  469. /* Log Panel */
  470. .log-panel {
  471. flex-shrink: 0;
  472. display: flex;
  473. flex-direction: column;
  474. }
  475. .log-header {
  476. display: flex;
  477. justify-content: space-between;
  478. align-items: center;
  479. padding: 0.8rem 1.5rem;
  480. border-bottom: 1px solid var(--border-subtle);
  481. }
  482. .log-content {
  483. height: 150px;
  484. padding: 1rem 1.5rem;
  485. font-size: 0.8rem;
  486. transition: height 0.3s ease;
  487. }
  488. .log-content.collapsed {
  489. height: 0;
  490. padding: 0 1.5rem;
  491. border: none;
  492. }
  493. .log-line {
  494. margin-bottom: 0.3rem;
  495. color: var(--text-muted);
  496. }
  497. .log-line.system { color: var(--accent-blue); }
  498. .log-line.error { color: var(--accent-red); }
  499. .log-line.data { color: var(--text-main); }
  500. /* Modal */
  501. .modal {
  502. position: fixed;
  503. top: 0; left: 0; right: 0; bottom: 0;
  504. background: rgba(0,0,0,0.6);
  505. backdrop-filter: blur(5px);
  506. display: flex;
  507. align-items: center;
  508. justify-content: center;
  509. z-index: 1000;
  510. }
  511. .modal-content {
  512. padding: 2rem;
  513. width: 400px;
  514. text-align: center;
  515. }
  516. .modal h3 {
  517. font-size: 1.5rem;
  518. margin-bottom: 1rem;
  519. }
  520. .warning {
  521. color: var(--accent-red);
  522. margin-bottom: 2rem;
  523. }
  524. .modal-actions {
  525. display: flex;
  526. justify-content: center;
  527. gap: 1rem;
  528. }
  529. /* Responsive */
  530. @media (max-width: 1024px) {
  531. .dashboard-grid {
  532. grid-template-columns: 1fr;
  533. overflow-y: auto;
  534. }
  535. .main-content {
  536. overflow-y: auto;
  537. }
  538. .dtc-panel {
  539. height: 400px;
  540. flex-shrink: 0;
  541. }
  542. .meas-panel {
  543. min-height: 500px;
  544. flex-shrink: 0;
  545. }
  546. .gauge-grid {
  547. grid-template-columns: repeat(3, 1fr) !important;
  548. }
  549. }
  550. /* ECU Actions */
  551. .ecu-actions {
  552. margin-left: auto;
  553. display: flex;
  554. align-items: center;
  555. }
  556. /* Export Buttons */
  557. .btn-export {
  558. display: flex;
  559. align-items: center;
  560. gap: 0.5rem;
  561. padding: 0.5rem 1.2rem;
  562. background: linear-gradient(135deg, rgba(0, 212, 255, 0.15), rgba(0, 230, 118, 0.15));
  563. border: 1px solid rgba(0, 212, 255, 0.3);
  564. color: var(--accent-blue);
  565. border-radius: 6px;
  566. font-family: var(--font-display);
  567. font-size: 0.85rem;
  568. font-weight: 600;
  569. cursor: pointer;
  570. text-transform: uppercase;
  571. letter-spacing: 0.5px;
  572. transition: all 0.3s ease;
  573. }
  574. .btn-export:hover {
  575. background: linear-gradient(135deg, rgba(0, 212, 255, 0.25), rgba(0, 230, 118, 0.25));
  576. border-color: var(--accent-blue);
  577. box-shadow: 0 0 20px rgba(0, 212, 255, 0.2);
  578. transform: translateY(-1px);
  579. }
  580. .btn-export svg {
  581. flex-shrink: 0;
  582. }
  583. .btn-export-sm {
  584. padding: 0.35rem 0.7rem;
  585. background: rgba(0, 212, 255, 0.1);
  586. border: 1px solid rgba(0, 212, 255, 0.2);
  587. color: var(--accent-blue);
  588. border-radius: 4px;
  589. font-family: var(--font-display);
  590. font-size: 0.75rem;
  591. font-weight: 600;
  592. cursor: pointer;
  593. transition: all 0.2s ease;
  594. }
  595. .btn-export-sm:hover {
  596. background: rgba(0, 212, 255, 0.2);
  597. border-color: var(--accent-blue);
  598. }
  599. /* Gauge Dashboard */
  600. .gauge-dashboard {
  601. padding: 1.5rem;
  602. flex-shrink: 0;
  603. }
  604. .gauge-grid {
  605. display: grid;
  606. grid-template-columns: repeat(6, 1fr);
  607. gap: 1rem;
  608. }
  609. .gauge-card {
  610. display: flex;
  611. flex-direction: column;
  612. align-items: center;
  613. justify-content: center;
  614. padding: 1rem 0.5rem;
  615. background: rgba(0, 0, 0, 0.25);
  616. border: 1px solid var(--border-subtle);
  617. border-radius: 12px;
  618. transition: all 0.3s ease;
  619. position: relative;
  620. }
  621. .gauge-card:hover {
  622. border-color: rgba(0, 212, 255, 0.3);
  623. background: rgba(0, 0, 0, 0.35);
  624. }
  625. .gauge-card canvas {
  626. margin-bottom: 0.3rem;
  627. }
  628. .gauge-info {
  629. display: flex;
  630. align-items: baseline;
  631. gap: 0.2rem;
  632. margin-bottom: 0.2rem;
  633. }
  634. .gauge-value {
  635. font-family: var(--font-mono);
  636. font-size: 1.6rem;
  637. font-weight: 700;
  638. color: var(--text-main);
  639. }
  640. .gauge-unit {
  641. font-size: 0.85rem;
  642. color: var(--accent-blue);
  643. font-weight: 600;
  644. }
  645. .gauge-label {
  646. font-size: 0.7rem;
  647. color: var(--text-muted);
  648. text-transform: uppercase;
  649. letter-spacing: 1px;
  650. font-weight: 600;
  651. text-align: center;
  652. }
  653. /* Compressor Status */
  654. .compressor-status {
  655. margin-bottom: 0.3rem;
  656. }
  657. .compressor-ring {
  658. transition: all 0.4s ease;
  659. }
  660. .compressor-ring.off {
  661. color: var(--text-muted);
  662. opacity: 0.4;
  663. }
  664. .compressor-ring.on {
  665. color: var(--accent-green);
  666. filter: drop-shadow(0 0 8px rgba(0, 230, 118, 0.6));
  667. animation: compressor-pulse 1.5s ease-in-out infinite;
  668. }
  669. @keyframes compressor-pulse {
  670. 0%, 100% { filter: drop-shadow(0 0 6px rgba(0, 230, 118, 0.4)); }
  671. 50% { filter: drop-shadow(0 0 14px rgba(0, 230, 118, 0.8)); }
  672. }
  673. /* Gauge arc colors */
  674. .gauge-card[data-warn] {
  675. border-color: rgba(255, 165, 2, 0.3);
  676. }
  677. .gauge-card[data-danger] {
  678. border-color: rgba(255, 51, 102, 0.3);
  679. }
  680. /* Car Sensor Diagram */
  681. .car-diagram-section {
  682. padding: 1.5rem;
  683. flex-shrink: 0;
  684. }
  685. .car-diagram-container {
  686. display: flex;
  687. justify-content: center;
  688. padding: 0.5rem 0;
  689. }
  690. #carDiagram {
  691. width: 100%;
  692. max-width: 850px;
  693. height: auto;
  694. }
  695. .diagram-legend {
  696. display: flex;
  697. gap: 1.2rem;
  698. align-items: center;
  699. }
  700. .legend-item {
  701. display: flex;
  702. align-items: center;
  703. gap: 0.4rem;
  704. font-size: 0.8rem;
  705. color: var(--text-muted);
  706. font-weight: 600;
  707. }
  708. .legend-dot {
  709. width: 10px;
  710. height: 10px;
  711. border-radius: 50%;
  712. }
  713. .legend-dot.ok {
  714. background: var(--accent-blue);
  715. box-shadow: 0 0 6px rgba(0, 212, 255, 0.5);
  716. }
  717. .legend-dot.warn {
  718. background: #ffa502;
  719. box-shadow: 0 0 6px rgba(255, 165, 2, 0.5);
  720. }
  721. .legend-dot.fault {
  722. background: var(--accent-red);
  723. box-shadow: 0 0 6px rgba(255, 51, 102, 0.5);
  724. animation: fault-blink 1.5s ease-in-out infinite;
  725. }
  726. @keyframes fault-blink {
  727. 0%, 100% { opacity: 1; }
  728. 50% { opacity: 0.4; }
  729. }
  730. .sensor-group {
  731. cursor: pointer;
  732. transition: opacity 0.3s;
  733. }
  734. .sensor-group:hover {
  735. opacity: 0.8;
  736. }
  737. @keyframes sensor-pulse-fault {
  738. 0%, 100% { r: 8; opacity: 1; }
  739. 50% { r: 12; opacity: 0.6; }
  740. }