diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 2294149..7ff7f6c 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -85,6 +85,7 @@ jobs: --mainExe ${{ matrix.main_exe }} \ --packTitle "${{ env.VELOPACK_APP_TITLE }}" \ --channel ${{ inputs.channel }}-${{ matrix.platform }} \ + --shortcutLocations StartMenuRoot \ --outputDir releases - name: Upload release artifacts diff --git a/pdm.lock b/pdm.lock index 401c4e0..a8eef4f 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "build", "lint", "test"] strategy = ["inherit_metadata"] lock_version = "4.5.0" -content_hash = "sha256:fa692cc2241180fe0ae2814f1552ab87e376d5b7ae399b7fb0a9db7095faba95" +content_hash = "sha256:8be743eaadbd30a791172a10d1a19f2cfd3ec16037ed1b4c219483a414a70f4f" [[metadata.targets]] requires_python = ">=3.14,<3.15" @@ -471,20 +471,20 @@ files = [ [[package]] name = "pyrefly" -version = "0.57.0" +version = "0.57.1" requires_python = ">=3.8" summary = "A fast type checker and language server for Python with powerful IDE features" groups = ["lint"] files = [ - {file = "pyrefly-0.57.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:da29dfd8182e67357aa45c1734dca03aabfbeaa2339f06a97fcd26f24c4827bf"}, - {file = "pyrefly-0.57.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:bdac6feeaa8aa45fd88dc650a9430a104ac4850aacba73e95138f4338e06ddfd"}, - {file = "pyrefly-0.57.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69cf1d5bb350aa63a797b8d1b660ff1d8ea281358558ec602f7bce6b051380"}, - {file = "pyrefly-0.57.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1413ac880ecf0cc4def339892a3b8ca065f1ae7dff94d1a8c8160fbf0068be3a"}, - {file = "pyrefly-0.57.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dd048bb1db13ab42281e440306b57308fe89fde362fe622b507f3db81037d38"}, - {file = "pyrefly-0.57.0-py3-none-win32.whl", hash = "sha256:aa6e9d9e4c7e276409f99b83d964fa39c7ba567b5521244b6b1a1eaf6ec399b9"}, - {file = "pyrefly-0.57.0-py3-none-win_amd64.whl", hash = "sha256:4169abc722fc9a957366c4fc8fdd7a927a5fb890ec0aae5178011e41c45a560e"}, - {file = "pyrefly-0.57.0-py3-none-win_arm64.whl", hash = "sha256:19e2c14533c00ae6e63ac38c6000e32badde5f573ca5c1cd969ecab129de89ec"}, - {file = "pyrefly-0.57.0.tar.gz", hash = "sha256:2e1d67165d6db8bf1da8df3b88d16dd899980367bbae16867404f11ff287cfff"}, + {file = "pyrefly-0.57.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:91974bfbe951eebf5a7bc959c1f3921f0371c789cad84761511d695e9ab2265f"}, + {file = "pyrefly-0.57.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:808087298537c70f5e7cdccb5bbaad482e7e056e947c0adf00fb612cbace9fdc"}, + {file = "pyrefly-0.57.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b01f454fa5539e070c0cba17ddec46b3d2107d571d519bd8eca8f3142ba02a6"}, + {file = "pyrefly-0.57.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02ad59ea722191f51635f23e37574662116b82ca9d814529f7cb5528f041f381"}, + {file = "pyrefly-0.57.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54bc0afe56776145e37733ff763e7e9679ee8a76c467b617dc3f227d4124a9e2"}, + {file = "pyrefly-0.57.1-py3-none-win32.whl", hash = "sha256:468e5839144b25bb0dce839bfc5fd879c9f38e68ebf5de561f30bed9ae19d8ca"}, + {file = "pyrefly-0.57.1-py3-none-win_amd64.whl", hash = "sha256:46db9c97093673c4fb7fab96d610e74d140661d54688a92d8e75ad885a56c141"}, + {file = "pyrefly-0.57.1-py3-none-win_arm64.whl", hash = "sha256:feb1bbe3b0d8d5a70121dcdf1476e6a99cc056a26a49379a156f040729244dcb"}, + {file = "pyrefly-0.57.1.tar.gz", hash = "sha256:b05f6f5ee3a6a5d502ca19d84cb9ab62d67f05083819964a48c1510f2993efc6"}, ] [[package]] @@ -563,7 +563,7 @@ files = [ [[package]] name = "pytest-cov" -version = "7.0.0" +version = "7.1.0" requires_python = ">=3.9" summary = "Pytest plugin for measuring coverage." groups = ["test"] @@ -573,8 +573,8 @@ dependencies = [ "pytest>=7", ] files = [ - {file = "pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861"}, - {file = "pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1"}, + {file = "pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678"}, + {file = "pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2"}, ] [[package]] @@ -631,29 +631,29 @@ files = [ [[package]] name = "ruff" -version = "0.15.6" +version = "0.15.7" requires_python = ">=3.7" summary = "An extremely fast Python linter and code formatter, written in Rust." groups = ["lint"] files = [ - {file = "ruff-0.15.6-py3-none-linux_armv6l.whl", hash = "sha256:7c98c3b16407b2cf3d0f2b80c80187384bc92c6774d85fefa913ecd941256fff"}, - {file = "ruff-0.15.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ee7dcfaad8b282a284df4aa6ddc2741b3f4a18b0555d626805555a820ea181c3"}, - {file = "ruff-0.15.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3bd9967851a25f038fc8b9ae88a7fbd1b609f30349231dffaa37b6804923c4bb"}, - {file = "ruff-0.15.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13f4594b04e42cd24a41da653886b04d2ff87adbf57497ed4f728b0e8a4866f8"}, - {file = "ruff-0.15.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e2ed8aea2f3fe57886d3f00ea5b8aae5bf68d5e195f487f037a955ff9fbaac9e"}, - {file = "ruff-0.15.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70789d3e7830b848b548aae96766431c0dc01a6c78c13381f423bf7076c66d15"}, - {file = "ruff-0.15.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:542aaf1de3154cea088ced5a819ce872611256ffe2498e750bbae5247a8114e9"}, - {file = "ruff-0.15.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c22e6f02c16cfac3888aa636e9eba857254d15bbacc9906c9689fdecb1953ab"}, - {file = "ruff-0.15.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98893c4c0aadc8e448cfa315bd0cc343a5323d740fe5f28ef8a3f9e21b381f7e"}, - {file = "ruff-0.15.6-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:70d263770d234912374493e8cc1e7385c5d49376e41dfa51c5c3453169dc581c"}, - {file = "ruff-0.15.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:55a1ad63c5a6e54b1f21b7514dfadc0c7fb40093fa22e95143cf3f64ebdcd512"}, - {file = "ruff-0.15.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8dc473ba093c5ec238bb1e7429ee676dca24643c471e11fbaa8a857925b061c0"}, - {file = "ruff-0.15.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:85b042377c2a5561131767974617006f99f7e13c63c111b998f29fc1e58a4cfb"}, - {file = "ruff-0.15.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cef49e30bc5a86a6a92098a7fbf6e467a234d90b63305d6f3ec01225a9d092e0"}, - {file = "ruff-0.15.6-py3-none-win32.whl", hash = "sha256:bbf67d39832404812a2d23020dda68fee7f18ce15654e96fb1d3ad21a5fe436c"}, - {file = "ruff-0.15.6-py3-none-win_amd64.whl", hash = "sha256:aee25bc84c2f1007ecb5037dff75cef00414fdf17c23f07dc13e577883dca406"}, - {file = "ruff-0.15.6-py3-none-win_arm64.whl", hash = "sha256:c34de3dd0b0ba203be50ae70f5910b17188556630e2178fd7d79fc030eb0d837"}, - {file = "ruff-0.15.6.tar.gz", hash = "sha256:8394c7bb153a4e3811a4ecdacd4a8e6a4fa8097028119160dffecdcdf9b56ae4"}, + {file = "ruff-0.15.7-py3-none-linux_armv6l.whl", hash = "sha256:a81cc5b6910fb7dfc7c32d20652e50fa05963f6e13ead3c5915c41ac5d16668e"}, + {file = "ruff-0.15.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:722d165bd52403f3bdabc0ce9e41fc47070ac56d7a91b4e0d097b516a53a3477"}, + {file = "ruff-0.15.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7fbc2448094262552146cbe1b9643a92f66559d3761f1ad0656d4991491af49e"}, + {file = "ruff-0.15.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b39329b60eba44156d138275323cc726bbfbddcec3063da57caa8a8b1d50adf"}, + {file = "ruff-0.15.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87768c151808505f2bfc93ae44e5f9e7c8518943e5074f76ac21558ef5627c85"}, + {file = "ruff-0.15.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb0511670002c6c529ec66c0e30641c976c8963de26a113f3a30456b702468b0"}, + {file = "ruff-0.15.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0d19644f801849229db8345180a71bee5407b429dd217f853ec515e968a6912"}, + {file = "ruff-0.15.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4806d8e09ef5e84eb19ba833d0442f7e300b23fe3f0981cae159a248a10f0036"}, + {file = "ruff-0.15.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dce0896488562f09a27b9c91b1f58a097457143931f3c4d519690dea54e624c5"}, + {file = "ruff-0.15.7-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:1852ce241d2bc89e5dc823e03cff4ce73d816b5c6cdadd27dbfe7b03217d2a12"}, + {file = "ruff-0.15.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5f3e4b221fb4bd293f79912fc5e93a9063ebd6d0dcbd528f91b89172a9b8436c"}, + {file = "ruff-0.15.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b15e48602c9c1d9bdc504b472e90b90c97dc7d46c7028011ae67f3861ceba7b4"}, + {file = "ruff-0.15.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b4705e0e85cedc74b0a23cf6a179dbb3df184cb227761979cc76c0440b5ab0d"}, + {file = "ruff-0.15.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:112c1fa316a558bb34319282c1200a8bf0495f1b735aeb78bfcb2991e6087580"}, + {file = "ruff-0.15.7-py3-none-win32.whl", hash = "sha256:6d39e2d3505b082323352f733599f28169d12e891f7dd407f2d4f54b4c2886de"}, + {file = "ruff-0.15.7-py3-none-win_amd64.whl", hash = "sha256:4d53d712ddebcd7dace1bc395367aec12c057aacfe9adbb6d832302575f4d3a1"}, + {file = "ruff-0.15.7-py3-none-win_arm64.whl", hash = "sha256:18e8d73f1c3fdf27931497972250340f92e8c861722161a9caeb89a58ead6ed2"}, + {file = "ruff-0.15.7.tar.gz", hash = "sha256:04f1ae61fc20fe0b148617c324d9d009b5f63412c0b16474f3d5f1a1a665f7ac"}, ] [[package]] diff --git a/pyproject.toml b/pyproject.toml index dcadc17..adf9968 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,12 +29,12 @@ build = [ "pyinstaller>=6.19.0", ] lint = [ - "ruff>=0.15.6", - "pyrefly>=0.57.0", + "ruff>=0.15.7", + "pyrefly>=0.57.1", ] test = [ "pytest>=9.0.2", - "pytest-cov>=7.0.0", + "pytest-cov>=7.1.0", "pytest-mock>=3.15.1", ] diff --git a/synodic_client/application/screen/plugin_row.py b/synodic_client/application/screen/plugin_row.py index f743295..5e85dcd 100644 --- a/synodic_client/application/screen/plugin_row.py +++ b/synodic_client/application/screen/plugin_row.py @@ -25,6 +25,7 @@ from synodic_client.application.screen.spinner import SpinnerCanvas from synodic_client.application.theme import ( FILTER_CHIP_STYLE, + PLUGIN_CHECK_STYLE, PLUGIN_KIND_HEADER_STYLE, PLUGIN_PROVIDER_NAME_STYLE, PLUGIN_PROVIDER_RUNTIME_TAG_DEFAULT_STYLE, @@ -138,6 +139,9 @@ class PluginProviderHeader(QFrame): auto_update_toggled = Signal(str, bool) """Emitted with ``(plugin_name, enabled)`` when the auto-update toggle changes.""" + check_requested = Signal(str) + """Emitted with the plugin name when the manual check-for-updates button is clicked.""" + update_requested = Signal(str) """Emitted with the plugin name when the per-plugin *Update* button is clicked.""" @@ -159,6 +163,7 @@ def __init__( self._runtime_tag = '' self._signal_key = plugin.name self._update_btn: QPushButton | None = None + self._check_btn: QPushButton | None = None self._checking_spinner: _RowSpinner | None = None self._layout = QHBoxLayout(self) @@ -230,8 +235,8 @@ def _build_controls( auto_update: bool, has_updates: bool, ) -> None: - """Build Auto/Update control buttons.""" - toggle_btn = QPushButton('Auto') + """Build auto-update toggle, check, and Update control buttons.""" + toggle_btn = QPushButton('\u21ba') toggle_btn.setCheckable(True) toggle_btn.setChecked(auto_update) toggle_btn.setStyleSheet(PLUGIN_TOGGLE_STYLE) @@ -241,6 +246,15 @@ def _build_controls( ) layout.addWidget(toggle_btn) + check_btn = QPushButton('\u27f3') + check_btn.setStyleSheet(PLUGIN_CHECK_STYLE) + check_btn.setToolTip('Check for updates now') + check_btn.clicked.connect( + lambda: self.check_requested.emit(self._signal_key), + ) + self._check_btn = check_btn + layout.addWidget(check_btn) + self._checking_spinner = _RowSpinner(self) layout.addWidget(self._checking_spinner) @@ -258,6 +272,8 @@ def _build_controls( toggle_btn.setEnabled(False) toggle_btn.setChecked(False) toggle_btn.setToolTip('Not installed \u2014 cannot auto-update') + check_btn.setEnabled(False) + check_btn.setToolTip('Not installed \u2014 cannot check for updates') update_btn.setEnabled(False) update_btn.setToolTip('Not installed \u2014 cannot update') @@ -281,10 +297,14 @@ def set_checking(self, checking: bool) -> None: return if checking: self._checking_spinner.start() + if self._check_btn is not None: + self._check_btn.hide() if self._update_btn is not None: self._update_btn.hide() else: self._checking_spinner.stop() + if self._check_btn is not None: + self._check_btn.show() def set_error(self, message: str) -> None: """Show a transient inline error that auto-hides after ~5 seconds.""" @@ -443,7 +463,7 @@ def _build_controls(self, layout: QHBoxLayout, data: PluginRowData) -> None: def _build_toggle(self, layout: QHBoxLayout, data: PluginRowData) -> None: """Add the auto-update toggle button.""" - toggle_btn = QPushButton('Auto') + toggle_btn = QPushButton('\u21ba') toggle_btn.setCheckable(True) toggle_btn.setChecked(data.auto_update) toggle_btn.setStyleSheet(PLUGIN_ROW_TOGGLE_STYLE) diff --git a/synodic_client/application/screen/screen.py b/synodic_client/application/screen/screen.py index 62537a6..b2f62e6 100644 --- a/synodic_client/application/screen/screen.py +++ b/synodic_client/application/screen/screen.py @@ -95,6 +95,9 @@ class ToolsView(QWidget): update_all_requested = Signal() """Emitted when the global *Update All* button is clicked.""" + plugin_check_requested = Signal(str) + """Emitted with a plugin name when its manual check-for-updates button is clicked.""" + plugin_update_requested = Signal(str) """Emitted with a plugin name when its per-plugin *Update* button is clicked.""" @@ -495,6 +498,7 @@ def _sort_key(rt: RuntimePackageResult) -> tuple[int, str]: ) provider.set_runtime(rt.tag, label=tag_text) provider.auto_update_toggled.connect(self._on_auto_update_toggled) + provider.check_requested.connect(self.plugin_check_requested.emit) provider.update_requested.connect(self.plugin_update_requested.emit) self._insert_section_widget(provider) @@ -558,6 +562,7 @@ def _build_plugin_section( parent=self._container, ) provider.auto_update_toggled.connect(self._on_auto_update_toggled) + provider.check_requested.connect(self.plugin_check_requested.emit) provider.update_requested.connect(self.plugin_update_requested.emit) self._insert_section_widget(provider) diff --git a/synodic_client/application/screen/tool_update_controller.py b/synodic_client/application/screen/tool_update_controller.py index 8b10f0b..1e13cbd 100644 --- a/synodic_client/application/screen/tool_update_controller.py +++ b/synodic_client/application/screen/tool_update_controller.py @@ -148,6 +148,7 @@ def _on_periodic_tool_update(self) -> None: def connect_tools_view(self, tools_view: ToolsView) -> None: """Wire ToolsView signals once the view is lazily created.""" tools_view.update_all_requested.connect(self.on_tool_update) + tools_view.plugin_check_requested.connect(self.on_single_plugin_update) tools_view.plugin_update_requested.connect(self.on_single_plugin_update) tools_view.package_update_requested.connect(self.on_single_package_update) tools_view.package_remove_requested.connect(self.on_single_package_remove) diff --git a/synodic_client/application/theme.py b/synodic_client/application/theme.py index 24cac22..4c45fbc 100644 --- a/synodic_client/application/theme.py +++ b/synodic_client/application/theme.py @@ -187,13 +187,16 @@ """Host-tool annotation label (e.g. "→ pdm") for injected packages.""" PLUGIN_ROW_TOGGLE_STYLE = ( - 'QPushButton { padding: 1px 4px; border: 1px solid palette(mid); border-radius: 2px;' - ' font-size: 10px; min-width: 36px; max-width: 36px; }' - 'QPushButton:checked { background: #89d185; color: black; }' - 'QPushButton:disabled { color: palette(mid); border-color: palette(mid); background: transparent; }' - 'QPushButton:checked:disabled { background: transparent; color: palette(mid); }' + 'QPushButton { padding: 0px; border: 1px solid palette(mid); border-radius: 11px;' + ' font-size: 14px; min-width: 22px; max-width: 22px; min-height: 22px; max-height: 22px;' + ' color: palette(mid); background: transparent; }' + 'QPushButton:hover { border-color: palette(light); color: palette(light); }' + 'QPushButton:checked { background: #89d185; color: black; border-color: #89d185; }' + 'QPushButton:checked:hover { background: #a0d896; border-color: #a0d896; }' + 'QPushButton:disabled { color: palette(dark); border-color: palette(dark); background: transparent; }' + 'QPushButton:checked:disabled { background: transparent; color: palette(dark); border-color: palette(dark); }' ) -"""Small inline auto-update toggle for individual package rows.""" +"""Small circular auto-update toggle (↺ icon) for individual package rows.""" PLUGIN_ROW_UPDATE_STYLE = ( 'QPushButton { padding: 1px 4px; border: 1px solid palette(mid); border-radius: 2px;' @@ -345,14 +348,28 @@ ) """Filter toggle button style when an active filter is in effect.""" -# Retained from previous design — auto-update & per-plugin update buttons +# Auto-update toggle button for plugin provider header rows — circular, stateful PLUGIN_TOGGLE_STYLE = ( - 'QPushButton { padding: 2px 8px; border: 1px solid palette(mid); border-radius: 3px;' - ' min-width: 60px; max-width: 60px; }' - 'QPushButton:checked { background: #89d185; color: black; }' - 'QPushButton:disabled { color: palette(mid); border-color: palette(mid); background: transparent; }' - 'QPushButton:checked:disabled { background: transparent; color: palette(mid); }' -) + 'QPushButton { padding: 0px; border: 1px solid palette(mid); border-radius: 12px;' + ' font-size: 15px; min-width: 24px; max-width: 24px; min-height: 24px; max-height: 24px;' + ' color: palette(mid); background: transparent; }' + 'QPushButton:hover { border-color: palette(light); color: palette(light); }' + 'QPushButton:checked { background: #89d185; color: black; border-color: #89d185; }' + 'QPushButton:checked:hover { background: #a0d896; border-color: #a0d896; }' + 'QPushButton:disabled { color: palette(dark); border-color: palette(dark); background: transparent; }' + 'QPushButton:checked:disabled { background: transparent; color: palette(dark); border-color: palette(dark); }' +) + +# Manual check-for-updates button for plugin provider header rows — rect, one-shot action +PLUGIN_CHECK_STYLE = ( + 'QPushButton { padding: 0px; border: 1px solid palette(mid); border-radius: 3px;' + ' font-size: 15px; min-width: 24px; max-width: 24px; min-height: 24px; max-height: 24px;' + ' color: palette(mid); background: transparent; }' + 'QPushButton:hover { border-color: #569cd6; color: #569cd6; }' + 'QPushButton:pressed { background: rgba(86, 156, 214, 0.15); }' + 'QPushButton:disabled { color: palette(dark); border-color: palette(dark); background: transparent; }' +) +"""One-shot check-for-updates icon button used in the plugin provider header.""" PLUGIN_UPDATE_STYLE = ( 'QPushButton { padding: 2px 8px; border: 1px solid palette(mid); border-radius: 3px;'