From af3a92c7d2cd22464f43b2ae4e4dc7cf220727a0 Mon Sep 17 00:00:00 2001 From: 87owo <85057800+87owo@users.noreply.github.com> Date: Sun, 20 Sep 2026 16:27:08 +0800 Subject: [PATCH] Add files via upload --- Interface/static/css/style.css | 127 +++++++++++++++++++++++++-------- Interface/static/js/main.js | 81 +++++++++++++++++++-- Interface/templates/index.html | 64 ++++++++++++----- PYAS.py | 2 +- 4 files changed, 222 insertions(+), 52 deletions(-) diff --git a/Interface/static/css/style.css b/Interface/static/css/style.css index 5af4887..c4fba6e 100644 --- a/Interface/static/css/style.css +++ b/Interface/static/css/style.css @@ -325,6 +325,27 @@ body { gap: 16px; } +.item-icon { + width: 40px; + height: 40px; + flex: 0 0 40px; + stroke-width: 1.5; + color: var(--accent-color); + pointer-events: none; +} + +.item-icon-compact { + transform: scale(0.9); +} + +.item-icon-repair { + transform: translateX(2px) scale(1.1); +} + +.item-icon ~ :is(.toggle-switch, .icon-action-btn) { + flex-shrink: 0; +} + .item-info { flex: 1; min-width: 0; @@ -332,7 +353,8 @@ body { padding-bottom: 2px; } -.list-item[data-target]:hover, .list-item[data-action]:hover { +.list-item[data-target]:hover, .list-item[data-action]:hover, +#about_window .list-item:is(:hover, :focus-within) { background-color: var(--bg-hover); } @@ -566,39 +588,33 @@ select.hidden + .custom-select-wrapper { } .custom-select-icon svg { width: 16px; height: 16px; } .custom-select-options { - position: absolute; - top: calc(100% + 8px); - left: 0; - min-width: 100%; + position: fixed; + display: none; width: max-content; - white-space: nowrap; - background-color: var(--bg-panel); border: 1px solid var(--border-color); border-radius: 10px; box-shadow: var(--shadow-sm); - z-index: 100; + z-index: 1000; list-style: none; margin: 0; - max-height: 0; - padding: 0 6px; - border-width: 0; - overflow: hidden; - opacity: 0; - visibility: hidden; - transform: translateY(-10px); - transition: opacity 0.2s ease, transform 0.2s ease, visibility 0s 0.2s, max-height 0s 0.2s, padding 0s 0.2s, border-width 0s 0.2s, box-shadow 0s 0.2s; -} -.custom-select-wrapper.open .custom-select-options { - max-height: 260px; padding: 6px; - border-width: 1px; overflow-y: auto; - - opacity: 1; - visibility: visible; - transform: translateY(0); - transition: opacity 0.2s ease, transform 0.2s ease, visibility 0s 0s, max-height 0s 0s, padding 0s 0s, border-width 0s 0s, box-shadow 0s 0s; + overflow-x: hidden; + overscroll-behavior: contain; + overflow-wrap: anywhere; +} +.custom-select-options.open { + display: block; +} +@media (prefers-reduced-motion: no-preference) { + .custom-select-options.open { + animation: select-options-enter 160ms ease-out; + } + @keyframes select-options-enter { + from { opacity: 0; transform: translateY(var(--select-enter-offset)); } + to { opacity: 1; transform: translateY(0); } + } } .custom-select-option { padding: 8px 12px; @@ -627,13 +643,18 @@ select.hidden + .custom-select-wrapper { } .quick-actions { - flex: 1; + flex: 0 0 calc(50% + 40px); + min-width: 0; display: flex; flex-direction: column; gap: 12px; height: 100%; justify-content: center; -} +} + +.quick-actions .list-item { + gap: 12px; +} #loading_overlay { position: fixed; @@ -868,4 +889,54 @@ body.resizing-active * { ::-webkit-scrollbar { width: 6px; height: 6px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: var(--border-color); border-radius: 10px; } -::-webkit-scrollbar-thumb:hover { background: var(--text-secondary); } \ No newline at end of file +::-webkit-scrollbar-thumb:hover { background: var(--text-secondary); } +@media (prefers-reduced-motion: no-preference) { + :is(.nav-btn, .icon-action-btn, .icon-btn, .ctrl-btn, .search-box) > svg { + transform-origin: center; + transition: transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1); + } + + :is(.nav-btn, .icon-action-btn):not(:disabled):is(:hover, :focus-visible) > svg { + transform: scale(1.08); + } + + .list-item:is(:hover, :focus-within) + > .icon-action-btn:not(:disabled) > svg { + transform: translateX(3px); + } + + .icon-btn[data-target]:not(:disabled):is(:hover, :focus-visible) > svg { + transform: translateX(-3px); + } + + .ctrl-btn:not(:disabled):is(:hover, :focus-visible) > svg { + transform: scale(1.1); + } + + .search-box:focus-within > svg { + transform: rotate(-8deg) scale(1.08); + } + + :is(.nav-btn, .ctrl-btn, .icon-action-btn):not(:disabled):active > svg, + .icon-btn[data-target]:not(:disabled):active > svg, + .list-item:active + > .icon-action-btn:not(:disabled) > svg { + transform: scale(0.92); + transition-duration: 100ms; + } + + .app-container.fade-in #home_window.active .state-icon > polyline { + animation: pyas-check-draw 300ms ease-in backwards; + } + + @keyframes pyas-check-draw { + from { + stroke-dasharray: 12 12; + stroke-dashoffset: 12; + } + to { + stroke-dasharray: 12 12; + stroke-dashoffset: 0; + } + } +} diff --git a/Interface/static/js/main.js b/Interface/static/js/main.js index d6e4be9..f8cf518 100644 --- a/Interface/static/js/main.js +++ b/Interface/static/js/main.js @@ -53,9 +53,61 @@ document.addEventListener('DOMContentLoaded', () => { }); }; - document.addEventListener('click', () => { - document.querySelectorAll('.custom-select-wrapper').forEach(w => w.classList.remove('open')); + let openCustomSelect = null; + + const closeCustomSelect = () => { + if (!openCustomSelect) return; + const { wrapper, menu } = openCustomSelect; + wrapper.classList.remove('open'); + menu.classList.remove('open'); + menu.removeAttribute('style'); + if (wrapper.isConnected) wrapper.appendChild(menu); + else menu.remove(); + openCustomSelect = null; + }; + + const positionCustomSelect = () => { + if (!openCustomSelect) return; + const { wrapper, trigger, menu } = openCustomSelect; + const rect = trigger.getBoundingClientRect(); + const viewport = window.visualViewport; + const leftEdge = (viewport?.offsetLeft || 0) + 8; + const topEdge = (viewport?.offsetTop || 0) + 8; + const rightEdge = leftEdge + (viewport?.width || document.documentElement.clientWidth) - 16; + const bottomEdge = topEdge + (viewport?.height || document.documentElement.clientHeight) - 16; + if (!wrapper.isConnected || !trigger.getClientRects().length || + getComputedStyle(trigger).visibility === 'hidden' || + rect.bottom <= topEdge || rect.top >= bottomEdge || + rect.right <= leftEdge || rect.left >= rightEdge) { + closeCustomSelect(); + return; + } + const above = Math.max(0, rect.top - topEdge - 8); + const below = Math.max(0, bottomEdge - rect.bottom - 8); + const opensUp = above > below; + const available = opensUp ? above : below; + if (available < 32) { + closeCustomSelect(); + return; + } + menu.style.maxHeight = `${Math.min(260, available)}px`; + menu.style.maxWidth = `${rightEdge - leftEdge}px`; + menu.style.minWidth = `${Math.min(rect.width, rightEdge - leftEdge)}px`; + menu.style.setProperty('--select-enter-offset', opensUp ? '4px' : '-4px'); + menu.style.left = `${Math.max(leftEdge, Math.min(rect.left, rightEdge - menu.offsetWidth))}px`; + menu.style.top = `${opensUp ? rect.top - 8 - menu.offsetHeight : rect.bottom + 8}px`; + }; + + document.addEventListener('click', closeCustomSelect); + document.addEventListener('keydown', (event) => { + if (event.key === 'Escape') closeCustomSelect(); }); + document.addEventListener('scroll', (event) => { + if (openCustomSelect && !openCustomSelect.menu.contains(event.target)) closeCustomSelect(); + }, true); + window.addEventListener('resize', positionCustomSelect); + window.visualViewport?.addEventListener('resize', positionCustomSelect); + window.visualViewport?.addEventListener('scroll', positionCustomSelect); const getMsg = (key) => (dict[appState.lang] || dict["english_switch"])[key] || key; @@ -166,7 +218,7 @@ document.addEventListener('DOMContentLoaded', () => { triggerText.textContent = li.textContent; if (optKey) triggerText.setAttribute('data-i18n', optKey); - wrapper.classList.remove('open'); + closeCustomSelect(); optionsContainer.querySelectorAll('.custom-select-option').forEach(el => el.classList.remove('selected')); li.classList.add('selected'); select.dispatchEvent(new Event('change')); @@ -187,8 +239,14 @@ document.addEventListener('DOMContentLoaded', () => { trigger.addEventListener('click', (e) => { e.stopPropagation(); const isOpen = wrapper.classList.contains('open'); - document.querySelectorAll('.custom-select-wrapper').forEach(w => w.classList.remove('open')); - if (!isOpen) wrapper.classList.add('open'); + closeCustomSelect(); + if (!isOpen && !select.disabled) { + wrapper.classList.add('open'); + document.body.appendChild(optionsContainer); + optionsContainer.classList.add('open'); + openCustomSelect = { wrapper, trigger, menu: optionsContainer }; + positionCustomSelect(); + } }); }; @@ -197,6 +255,7 @@ document.addEventListener('DOMContentLoaded', () => { if (!select) return; select.value = val; const wrapper = select.nextElementSibling; + if (openCustomSelect?.wrapper === wrapper) closeCustomSelect(); if (wrapper && wrapper.classList.contains('custom-select-wrapper')) { const triggerText = wrapper.querySelector('.custom-select-text'); const selectedOpt = select.options[select.selectedIndex]; @@ -450,6 +509,7 @@ document.addEventListener('DOMContentLoaded', () => { const rebuildCustomSelect = (selectId) => { const select = document.getElementById(selectId); if (!select) return; + if (openCustomSelect?.wrapper === select.nextElementSibling) closeCustomSelect(); let sibling = select.nextElementSibling; while (sibling && sibling.classList.contains('custom-select-wrapper')) { const toRemove = sibling; @@ -662,6 +722,7 @@ document.addEventListener('DOMContentLoaded', () => { } } + closeCustomSelect(); document.querySelectorAll('.page, .nav-btn').forEach(el => el.classList.remove('active')); document.getElementById(targetId)?.classList.add('active'); document.querySelector(`aside .nav-btn[data-target="${targetId}"]`)?.classList.add('active'); @@ -1070,6 +1131,14 @@ document.addEventListener('DOMContentLoaded', () => { if (item) { const btn = item.querySelector('button'); + const isAboutItem = item.closest('#about_window') !== null; + + if (isAboutItem) { + item.style.cursor = 'pointer'; + item.dataset.action = key; + item.addEventListener('click', action); + return; + } if (btn) { btn.addEventListener('click', action); @@ -1185,4 +1254,4 @@ document.addEventListener('DOMContentLoaded', () => { document.querySelectorAll('.toggle-switch input').forEach(t => t.disabled = false); appState.firstLaunch = false; }; -}); \ No newline at end of file +}); diff --git a/Interface/templates/index.html b/Interface/templates/index.html index 0f2c440..e974968 100644 --- a/Interface/templates/index.html +++ b/Interface/templates/index.html @@ -67,18 +67,20 @@

此裝置已受到防護

-
-
-
-
-

智能掃描

此選項可以快速掃描裝置安全性

-
+
+
+
+ +
+

智能掃描

此選項可以快速掃描裝置安全性

+
-
-
-
-

日誌導出

此選項可以篩選日誌並匯出存檔

-
+
+
+ +
+

日誌導出

此選項可以篩選日誌並匯出存檔

+
@@ -115,48 +117,56 @@
+

進程防護

此選項可以終止惡意進程運行

+

進程暫停

此選項可以暫停進程等待掃描

+

加載防護

此選項可以攔截惡意進程加載

+

檔案防護

此選項可以隔離惡意檔案建立

+

系統防護

此選項可以實時修復系統異常

+

網路防護

此選項可以攔截惡意網路連接

+

驅動防護

此選項可以實時攔截惡意行為

+

自訂防護

此選項可以導入自訂防護規則

@@ -168,48 +178,56 @@
+

進程管理

此選項可以實時查看系統進程

+

啟動項管理

此選項可以管理各應用啟動項

+

垃圾清理

此選項可以清理暫存垃圾檔案

-
-
+
+
+

系統修復

此選項可以修復系統登錄檔項目

+

彈窗攔截

此選項可以選擇指定窗口攔截

+

流量監控

此選項可以實時監控網路流量

+

白名單

此選項可以查看並管理白名單

+

隔離區

此選項可以查看並管理隔離區

@@ -221,42 +239,49 @@
+

增強模式

此選項可以增強靜態掃描引擎

+

擴展引擎

此選項可以使用擴展規則引擎

+

雲端服務

此選項可以自動上報可疑檔案

+

副檔名過濾

此選項僅掃描指定檔案副檔名

+

開機啟動

此選項可以設定開機自動執行

+

選單掃描

此選項可以啟用右鍵掃描功能

+

顯色主題

此選項可以選擇主題顏色

@@ -271,6 +296,7 @@
+

顯示語言

此選項可以選擇顯示語言

@@ -289,6 +315,7 @@
+

重置選項

此選項可以重置所有設定

@@ -300,22 +327,25 @@
+

產品金鑰

此選項可以啟用產品金鑰

- +
+

官方網站

此選項可以前往官方網站

- +
+

檢查更新

此選項可以檢查可用更新

- +