From 6da4acee09d8ec7780089cd0710001badf20c841 Mon Sep 17 00:00:00 2001 From: lysec Date: Sun, 12 Oct 2025 17:51:07 +0200 Subject: [PATCH] Dock, Tray, ActiveWindow, Taskbar: add theming for app/tray icons appicon_colorize: create simple shader to colorize icons by theme color --- Assets/Translations/de.json | 18 +++++++++++ Assets/Translations/en.json | 18 +++++++++++ Assets/Translations/es.json | 18 +++++++++++ Assets/Translations/fr.json | 18 +++++++++++ Assets/Translations/pt.json | 18 +++++++++++ Assets/Translations/zh-CN.json | 18 +++++++++++ Assets/settings-default.json | 3 +- Commons/Settings.qml | 1 + Modules/Bar/Widgets/ActiveWindow.qml | 18 +++++++++++ Modules/Bar/Widgets/Taskbar.qml | 9 ++++++ Modules/Bar/Widgets/Tray.qml | 8 +++++ Modules/Dock/Dock.qml | 9 ++++++ .../WidgetSettings/ActiveWindowSettings.qml | 10 ++++++ .../Bar/WidgetSettings/TaskbarSettings.qml | 10 ++++++ .../Bar/WidgetSettings/TraySettings.qml | 13 +++++++- Modules/Settings/Tabs/DockTab.qml | 14 +++++++++ Services/BarWidgetRegistry.qml | 9 ++++-- Shaders/frag/appicon_colorize.frag | 29 ++++++++++++++++++ Shaders/qsb/appicon_colorize.frag.qsb | Bin 0 -> 1957 bytes 19 files changed, 236 insertions(+), 5 deletions(-) create mode 100644 Shaders/frag/appicon_colorize.frag create mode 100644 Shaders/qsb/appicon_colorize.frag.qsb diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 16d3042d..b97c91b8 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -318,6 +318,10 @@ "floating-distance": { "label": "Dock-Schwebeabstand", "description": "Schwebeabstand vom Bildschirmrand anpassen." + }, + "colorize-icons": { + "label": "Symbole einfärben", + "description": "Theme-Farben auf Dock-App-Symbole anwenden (nur nicht fokussierte Apps)." } }, "monitors": { @@ -929,6 +933,10 @@ "width": { "description": "Steuert die horizontale Größe des Widgets.", "label": "Widget-Breite" + }, + "colorize-icons": { + "label": "Symbole einfärben", + "description": "Theme-Farben auf das aktive Fenster-Symbol anwenden." } }, "system-monitor": { @@ -1122,6 +1130,16 @@ "only-same-output": { "label": "Nur vom gleichen Bildschirm", "description": "Zeige nur Apps vom dem Bildschirm an, wo sich die Taskbar befindet." + }, + "colorize-icons": { + "label": "Symbole einfärben", + "description": "Theme-Farben auf Taskbar-Symbole anwenden." + } + }, + "tray": { + "colorize-icons": { + "label": "Symbole einfärben", + "description": "Theme-Farben auf Tray-Symbole anwenden." } } } diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 41800513..1a0409a2 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -318,6 +318,10 @@ "floating-distance": { "label": "Dock floating distance", "description": "Adjust the floating distance from the screen edge." + }, + "colorize-icons": { + "label": "Colorize Icons", + "description": "Apply theme colors to dock app icons (non-focused apps only)." } }, "monitors": { @@ -912,6 +916,10 @@ "width": { "label": "Widget Width", "description": "Controls the horizontal size of the widget." + }, + "colorize-icons": { + "label": "Colorize Icons", + "description": "Apply theme colors to active window icon." } }, "system-monitor": { @@ -1105,6 +1113,16 @@ "only-same-output": { "label": "Only from same output", "description": "Show only apps from the output where the bar is located." + }, + "colorize-icons": { + "label": "Colorize Icons", + "description": "Apply theme colors to taskbar icons." + } + }, + "tray": { + "colorize-icons": { + "label": "Colorize Icons", + "description": "Apply theme colors to tray icons." } } } diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 6602519f..10aa1ce5 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -318,6 +318,10 @@ "floating-distance": { "label": "Distancia de flotación del dock", "description": "Ajusta la distancia de flotación desde el borde de la pantalla." + }, + "colorize-icons": { + "label": "Colorear iconos", + "description": "Aplicar colores del tema a los iconos de aplicaciones del dock (solo aplicaciones no enfocadas)." } }, "monitors": { @@ -912,6 +916,10 @@ "width": { "description": "Controla el tamaño horizontal del widget.", "label": "Ancho del widget" + }, + "colorize-icons": { + "label": "Colorear iconos", + "description": "Aplicar colores del tema al icono de la ventana activa." } }, "system-monitor": { @@ -1105,6 +1113,16 @@ "only-same-output": { "description": "Muestra solo las aplicaciones del resultado donde se encuentra la barra.", "label": "Solo de la misma salida" + }, + "colorize-icons": { + "label": "Colorear iconos", + "description": "Aplicar colores del tema a los iconos de la barra de tareas." + } + }, + "tray": { + "colorize-icons": { + "label": "Colorear iconos", + "description": "Aplicar colores del tema a los iconos de la bandeja del sistema." } } } diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index d62cace9..004a1224 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -318,6 +318,10 @@ "floating-distance": { "label": "Distance de flottaison du dock", "description": "Ajustez la distance de flottaison par rapport au bord de l'écran." + }, + "colorize-icons": { + "label": "Coloriser les icônes", + "description": "Appliquer les couleurs du thème aux icônes d'applications du dock (applications non focalisées uniquement)." } }, "monitors": { @@ -912,6 +916,10 @@ "width": { "description": "Contrôle la taille horizontale du widget.", "label": "Largeur du widget" + }, + "colorize-icons": { + "label": "Coloriser les icônes", + "description": "Appliquer les couleurs du thème à l'icône de la fenêtre active." } }, "system-monitor": { @@ -1105,6 +1113,16 @@ "only-same-output": { "description": "Afficher uniquement les applications de la sortie où la barre est située.", "label": "Seulement à partir de la même sortie" + }, + "colorize-icons": { + "label": "Coloriser les icônes", + "description": "Appliquer les couleurs du thème aux icônes de la barre des tâches." + } + }, + "tray": { + "colorize-icons": { + "label": "Coloriser les icônes", + "description": "Appliquer les couleurs du thème aux icônes de la barre système." } } } diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 25522957..03b3d37a 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -318,6 +318,10 @@ "floating-distance": { "label": "Distância de flutuação da dock", "description": "Ajuste a distância de flutuação da borda da tela." + }, + "colorize-icons": { + "label": "Colorir ícones", + "description": "Aplicar cores do tema aos ícones de aplicativos da dock (apenas aplicativos não focados)." } }, "monitors": { @@ -912,6 +916,10 @@ "width": { "description": "Controla o tamanho horizontal do widget.", "label": "Largura do Widget" + }, + "colorize-icons": { + "label": "Colorir ícones", + "description": "Aplicar cores do tema ao ícone da janela ativa." } }, "system-monitor": { @@ -1105,6 +1113,16 @@ "only-same-output": { "description": "Mostrar apenas os aplicativos da saída onde a barra está localizada.", "label": "Apenas da mesma saída" + }, + "colorize-icons": { + "label": "Colorir ícones", + "description": "Aplicar cores do tema aos ícones da barra de tarefas." + } + }, + "tray": { + "colorize-icons": { + "label": "Colorir ícones", + "description": "Aplicar cores do tema aos ícones da bandeja do sistema." } } } diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 3bbe1d89..609bdb31 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -318,6 +318,10 @@ "floating-distance": { "label": "Dock 浮动距离", "description": "调整距离屏幕边缘的浮动距离。" + }, + "colorize-icons": { + "label": "着色图标", + "description": "将主题颜色应用到 Dock 应用图标(仅限非聚焦应用)。" } }, "monitors": { @@ -912,6 +916,10 @@ "width": { "description": "控制小部件的水平尺寸。", "label": "小部件宽度" + }, + "colorize-icons": { + "label": "着色图标", + "description": "将主题颜色应用到活动窗口图标。" } }, "system-monitor": { @@ -1105,6 +1113,16 @@ "only-same-output": { "label": "仅显示同屏幕", "description": "仅显示任务栏所在屏幕上的应用程序" + }, + "colorize-icons": { + "label": "着色图标", + "description": "将主题颜色应用到任务栏图标。" + } + }, + "tray": { + "colorize-icons": { + "label": "着色图标", + "description": "将主题颜色应用到系统托盘图标。" } } } diff --git a/Assets/settings-default.json b/Assets/settings-default.json index 9b63d3ae..55d8bf07 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -138,7 +138,8 @@ "floatingRatio": 1, "onlySameOutput": true, "monitors": [], - "pinnedApps": [] + "pinnedApps": [], + "colorizeIcons": false }, "network": { "wifiEnabled": true diff --git a/Commons/Settings.qml b/Commons/Settings.qml index af6c0598..676c4575 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -272,6 +272,7 @@ Singleton { property list monitors: [] // Desktop entry IDs pinned to the dock (e.g., "org.kde.konsole", "firefox.desktop") property list pinnedApps: [] + property bool colorizeIcons: false } // network diff --git a/Modules/Bar/Widgets/ActiveWindow.qml b/Modules/Bar/Widgets/ActiveWindow.qml index de10760d..817d7cc4 100644 --- a/Modules/Bar/Widgets/ActiveWindow.qml +++ b/Modules/Bar/Widgets/ActiveWindow.qml @@ -149,6 +149,15 @@ Item { asynchronous: true smooth: true visible: source !== "" + + // Apply dock shader to active window icon (always themed) + layer.enabled: widgetSettings.colorizeIcons !== false + layer.effect: ShaderEffect { + property color targetColor: Color.mOnSurface + property real colorizeMode: 0.0 // Dock mode (grayscale) + + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb") + } } } @@ -315,6 +324,15 @@ Item { asynchronous: true smooth: true visible: source !== "" + + // Apply dock shader to active window icon (always themed) + layer.enabled: widgetSettings.colorizeIcons !== false + layer.effect: ShaderEffect { + property color targetColor: Color.mOnSurface + property real colorizeMode: 0.0 // Dock mode (grayscale) + + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb") + } } } } diff --git a/Modules/Bar/Widgets/Taskbar.qml b/Modules/Bar/Widgets/Taskbar.qml index e9035002..6f1782f3 100644 --- a/Modules/Bar/Widgets/Taskbar.qml +++ b/Modules/Bar/Widgets/Taskbar.qml @@ -81,6 +81,15 @@ Rectangle { asynchronous: true opacity: modelData.isFocused ? Style.opacityFull : 0.6 + // Apply dock shader to all taskbar icons + layer.enabled: widgetSettings.colorizeIcons !== false + layer.effect: ShaderEffect { + property color targetColor: Color.mOnSurface + property real colorizeMode: 0.0 // Dock mode (grayscale) + + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb") + } + Rectangle { anchors.bottomMargin: -2 * scaling anchors.bottom: parent.bottom diff --git a/Modules/Bar/Widgets/Tray.qml b/Modules/Bar/Widgets/Tray.qml index 0365cc2e..5237a82e 100644 --- a/Modules/Bar/Widgets/Tray.qml +++ b/Modules/Bar/Widgets/Tray.qml @@ -193,6 +193,14 @@ Rectangle { } opacity: status === Image.Ready ? 1 : 0 + layer.enabled: widgetSettings.colorizeIcons !== false + layer.effect: ShaderEffect { + property color targetColor: Color.mOnSurface + property real colorizeMode: 1.0 // Tray mode (intensity-based) + + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb") + } + MouseArea { anchors.fill: parent hoverEnabled: true diff --git a/Modules/Dock/Dock.qml b/Modules/Dock/Dock.qml index b23c90bc..c0dd0a5a 100644 --- a/Modules/Dock/Dock.qml +++ b/Modules/Dock/Dock.qml @@ -401,6 +401,15 @@ Variants { scale: appButton.hovered ? 1.15 : 1.0 + // Apply dock-specific colorization shader only to non-focused apps + layer.enabled: !appButton.isActive && Settings.data.dock.colorizeIcons + layer.effect: ShaderEffect { + property color targetColor: Color.mOnSurface + property real colorizeMode: 0.0 // Dock mode (grayscale) + + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb") + } + Behavior on scale { NumberAnimation { duration: Style.animationNormal diff --git a/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml b/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml index 3d1be163..81184595 100644 --- a/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml +++ b/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml @@ -18,6 +18,7 @@ ColumnLayout { property bool valueAutoHide: widgetData.autoHide !== undefined ? widgetData.autoHide : widgetMetadata.autoHide property string valueScrollingMode: widgetData.scrollingMode || widgetMetadata.scrollingMode property int valueWidth: widgetData.width !== undefined ? widgetData.width : widgetMetadata.width + property bool valueColorizeIcons: widgetData.colorizeIcons !== undefined ? widgetData.colorizeIcons : widgetMetadata.colorizeIcons function saveSettings() { var settings = Object.assign({}, widgetData || {}) @@ -25,6 +26,7 @@ ColumnLayout { settings.showIcon = valueShowIcon settings.scrollingMode = valueScrollingMode settings.width = parseInt(widthInput.text) || widgetMetadata.width + settings.colorizeIcons = valueColorizeIcons return settings } @@ -44,6 +46,14 @@ ColumnLayout { onToggled: checked => root.valueShowIcon = checked } + NToggle { + Layout.fillWidth: true + label: I18n.tr("bar.widget-settings.active-window.colorize-icons.label") + description: I18n.tr("bar.widget-settings.active-window.colorize-icons.description") + checked: root.valueColorizeIcons + onToggled: checked => root.valueColorizeIcons = checked + } + NTextInput { id: widthInput Layout.fillWidth: true diff --git a/Modules/Settings/Bar/WidgetSettings/TaskbarSettings.qml b/Modules/Settings/Bar/WidgetSettings/TaskbarSettings.qml index c80dd09f..5ff3b418 100644 --- a/Modules/Settings/Bar/WidgetSettings/TaskbarSettings.qml +++ b/Modules/Settings/Bar/WidgetSettings/TaskbarSettings.qml @@ -16,11 +16,13 @@ ColumnLayout { // Local state property bool valueOnlyActiveWorkspaces: widgetData.onlyActiveWorkspaces !== undefined ? widgetData.onlyActiveWorkspaces : widgetMetadata.onlyActiveWorkspaces property bool valueOnlySameOutput: widgetData.onlySameOutput !== undefined ? widgetData.onlySameOutput : widgetMetadata.onlySameOutput + property bool valueColorizeIcons: widgetData.colorizeIcons !== undefined ? widgetData.colorizeIcons : widgetMetadata.colorizeIcons function saveSettings() { var settings = Object.assign({}, widgetData || {}) settings.onlySameOutput = valueOnlySameOutput settings.onlyActiveWorkspaces = valueOnlyActiveWorkspaces + settings.colorizeIcons = valueColorizeIcons return settings } @@ -39,4 +41,12 @@ ColumnLayout { checked: root.valueOnlyActiveWorkspaces onToggled: checked => root.valueOnlyActiveWorkspaces = checked } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("bar.widget-settings.taskbar.colorize-icons.label") + description: I18n.tr("bar.widget-settings.taskbar.colorize-icons.description") + checked: root.valueColorizeIcons + onToggled: checked => root.valueColorizeIcons = checked + } } diff --git a/Modules/Settings/Bar/WidgetSettings/TraySettings.qml b/Modules/Settings/Bar/WidgetSettings/TraySettings.qml index 25d08bce..d1f38261 100644 --- a/Modules/Settings/Bar/WidgetSettings/TraySettings.qml +++ b/Modules/Settings/Bar/WidgetSettings/TraySettings.qml @@ -5,12 +5,14 @@ import qs.Commons import qs.Widgets ColumnLayout { + id: root // Properties to receive data from parent property var widgetData: ({}) // Expected by BarWidgetSettingsDialog property var widgetMetadata: ({}) // Expected by BarWidgetSettingsDialog - // Local state for the blacklist + // Local state property var localBlacklist: widgetData.blacklist || [] + property bool valueColorizeIcons: widgetData.colorizeIcons !== undefined ? widgetData.colorizeIcons : widgetMetadata.colorizeIcons ListModel { id: blacklistModel @@ -27,6 +29,14 @@ ColumnLayout { spacing: Style.marginM * scaling + NToggle { + Layout.fillWidth: true + label: I18n.tr("bar.widget-settings.tray.colorize-icons.label") + description: I18n.tr("bar.widget-settings.tray.colorize-icons.description") + checked: root.valueColorizeIcons + onToggled: checked => root.valueColorizeIcons = checked + } + ColumnLayout { Layout.fillWidth: true spacing: Style.marginS * scaling @@ -135,6 +145,7 @@ ColumnLayout { // Return the updated settings for this widget instance var settings = Object.assign({}, widgetData || {}) settings.blacklist = newBlacklist + settings.colorizeIcons = root.valueColorizeIcons return settings } } diff --git a/Modules/Settings/Tabs/DockTab.qml b/Modules/Settings/Tabs/DockTab.qml index 5d31c074..d272aacf 100644 --- a/Modules/Settings/Tabs/DockTab.qml +++ b/Modules/Settings/Tabs/DockTab.qml @@ -93,6 +93,20 @@ ColumnLayout { Layout.bottomMargin: Style.marginXL * scaling } + NToggle { + Layout.fillWidth: true + label: I18n.tr("settings.dock.appearance.colorize-icons.label") + description: I18n.tr("settings.dock.appearance.colorize-icons.description") + checked: Settings.data.dock.colorizeIcons + onToggled: checked => Settings.data.dock.colorizeIcons = checked + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } + // Monitor Configuration ColumnLayout { spacing: Style.marginM * scaling diff --git a/Services/BarWidgetRegistry.qml b/Services/BarWidgetRegistry.qml index efb85537..f2809e14 100644 --- a/Services/BarWidgetRegistry.qml +++ b/Services/BarWidgetRegistry.qml @@ -43,7 +43,8 @@ Singleton { "showIcon": true, "autoHide": false, "scrollingMode": "hover", - "width": 145 + "width": 145, + "colorizeIcons": false }, "Battery": { "allowUserSettings": true, @@ -114,11 +115,13 @@ Singleton { "Taskbar": { "allowUserSettings": true, "onlySameOutput": true, - "onlyActiveWorkspaces": true + "onlyActiveWorkspaces": true, + "colorizeIcons": false }, "Tray": { "allowUserSettings": true, - "blacklist": [] + "blacklist": [], + "colorizeIcons": false }, "Workspace": { "allowUserSettings": true, diff --git a/Shaders/frag/appicon_colorize.frag b/Shaders/frag/appicon_colorize.frag new file mode 100644 index 00000000..ae108701 --- /dev/null +++ b/Shaders/frag/appicon_colorize.frag @@ -0,0 +1,29 @@ +#version 450 +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; +layout(binding = 1) uniform sampler2D source; +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + vec4 targetColor; + float colorizeMode; // 0.0 = dock mode (grayscale), 1.0 = tray mode (intensity) +} ubuf; + +void main() { + vec4 tex = texture(source, qt_TexCoord0); + + float intensity; + + if (ubuf.colorizeMode < 0.5) { + // Dock mode: Convert to grayscale using proper luminance weights + intensity = dot(tex.rgb, vec3(0.299, 0.587, 0.114)); + } else { + // Tray mode: Use the maximum RGB channel value as intensity + intensity = max(max(tex.r, tex.g), tex.b); + + // Normalize intensity to make all icons more uniform + intensity = smoothstep(0.1, 0.9, intensity); + } + + fragColor = vec4(ubuf.targetColor.rgb * intensity, tex.a) * ubuf.qt_Opacity; +} diff --git a/Shaders/qsb/appicon_colorize.frag.qsb b/Shaders/qsb/appicon_colorize.frag.qsb new file mode 100644 index 0000000000000000000000000000000000000000..15618b86dc21605375f53137209ccf520f623aa7 GIT binary patch literal 1957 zcmV;W2U_?502|48ob6fNbKAxdU-3hfVK+`3N4A?bIa^5zIw}M3o1#fMk?h#16*-Y6 z?F>i5KqNq;B>@IFIFvJb?pvQao#|`;iv9(8O8b^SBCmOA+L=x}d$%BukSwc7C!+`7 z3<>Pse*AWCZ*d?2;2Z#C0AK+CuEJ-)p$ofU!vr?K2Ok3Hg8=|30D%9{fMW(4xC9Oa zV8K8r4~4ok|4h|I*oO!l09b-(df!*1(99)5xCIt)2*8C20Gua@+lusy@y%sY*@iJ# z&;u7Z9FWu7F}mbf1`ZYkFaR6)kzgLG_SJC}dQ(vso`4No;6op5^7rY}{Hd9^03rAg zK>$6Ws8iZ-?gx)Wx+;myy^&6>zfi7VIlv2$ zo+7>oIT03qMwaVIeSo&eQ(EwIXah3v69BkQdNF;2d?Jl{uTAMRyh62i4>IB&M^@0; zDV?MBPABz5%Va*A>{6u%hH8+4ym^nL*VzhC}yJEnC2_{*K;^_b2Bz}J&+TC1=q^ly@mPIHL& zmxLF`+e8-}~a2#hH<7g4)9OVn?wh{;CXI)vR+jO5( z{M}8+ZxNmev|NT z!;)CPw}|(qvNtgPKSGx3Ny^+LtSiL(C!~Z=w7*K&F&FQU{eptsAbu=UBpFkYDG~ny z3=};_mtZFkYe0Yd~XvUG3V$R@PPQ3w>I(q4GU10-8Vzewgq~A zQcF&KxANEyLf7}0X&Bl#uzRkc9=U@@W9AHfi#Lz3U5__4E#yJe<7_{2v@bNq7#mr< z%s%7YEsF>4q$zm90y4M9R?p=JlKa^1l^M5!fz3Dkp&zKSJ!H5~>@B}H#P0C6&$$U06i7a{jNRP@cp1~;GFOF8RkRJ z6LN{ip0E~v^C+;3o4T~MI;Wjik-8qYy>J?o>o6VR71RjWEoKxdc_AQu3DH=SwOHTh zI=3f<$-$FV1X9wCLa|YA;3zj5rJ~WOm+JLu&8*idt7xrWGYrEpi={@bR;`+5saQ9Q zV#+kjIAs_{rBOA?)pEI3tCSi>wOGj$^h5D&4@3I|Oe1Te<453H#herl^78tG>?Bpf zk?-?IA-Bi6Q7{RGS#B7nSvAc@sa~#CD^&?o2G^)n%}U)Y7ma$OUM^v9Q-gD02gB~& zz#42?-0B#6tRJJTF1vQ<+>qOWzHh{Z68N8YN7irsKvr#eQWieC)ADC)Y;5&@=Q~blbGBf1 z+fEe@QCJJP#oeC7lH7vo-26VIPO|gx{>PtmHv&HlyZ60u#3|RuSYvB!_s(xNwzogt zG-N=>*w~JE!ieIQt+Aa?y1OE+WJ0t96zIE+#1+j7w_X5 znycNGDp8oLCHAAwY1<1LJFyz!d&>XohCY$&7;6e7&uhBA^xZm|Hy{q#TD1< z4I?bjk$rEnY41GMsUhBo zr#&~2JB-S}NT1dTIvt^_8~MGxV>Dgwgg(^iM4=t%L%-+E)Y0_F_7vChPLB;T*JGVd zTm^f3tE}gHA-6n{oVP^%u}()mgaj^8oT6j*WyhX2>`o^>sYtoFH_Pdr(Rhdv5?9E? zML9!~{Afz2`bs$k*1e+s*FI#u3R+Zv9#3`@RPy rrDgcCQ(&veirS%*Q{wa_|2V^O%-=YP-g2UB{KH#PM~wdh_)P1Oa0Tkw literal 0 HcmV?d00001