From 2a098e5b16b47c095509732673453e1aae1174ef Mon Sep 17 00:00:00 2001 From: Eva Marco Date: Tue, 16 Jun 2026 19:46:03 +0200 Subject: [PATCH] :tada: Add background blur (#10034) * :tada: Add background blur * :tada: Add test * :tada: Add background blur info to plugins API * :tada: Suport in wasm for both layer and background blur * :bug: Fix failing test * :recycle: Fix comments --------- Co-authored-by: alonso.torres --- common/src/app/common/geom/shapes/bounds.cljc | 16 +- .../src/app/common/geom/shapes/effects.cljc | 4 + common/src/app/common/types/component.cljc | 1 + common/src/app/common/types/modifiers.cljc | 3 + common/src/app/common/types/path/bool.cljc | 2 +- common/src/app/common/types/shape.cljc | 6 +- common/src/app/common/types/shape/attrs.cljc | 9 + .../common/types/shape/background_blur.cljc | 16 + common/src/app/common/types/shape/blur.cljc | 5 +- .../render-wasm/get-file-background-blur.json | 12 +- .../playwright/ui/specs/design-tab.spec.js | 112 ++++- .../app/main/data/workspace/modifiers.cljs | 1 + frontend/src/app/main/render.cljs | 2 +- .../src/app/main/ui/components/title_bar.cljs | 45 +- .../src/app/main/ui/components/title_bar.scss | 1 + .../app/main/ui/inspect/attributes/blur.cljs | 56 ++- frontend/src/app/main/ui/inspect/styles.cljs | 4 +- .../main/ui/inspect/styles/panels/blur.cljs | 30 +- .../app/main/ui/inspect/styles/style_box.cljs | 39 +- frontend/src/app/main/ui/shapes/attrs.cljs | 1 + .../src/app/main/ui/shapes/custom_stroke.cljs | 4 +- frontend/src/app/main/ui/shapes/frame.cljs | 1 + .../workspace/sidebar/options/menus/blur.cljs | 385 +++++++++++++----- .../workspace/sidebar/options/menus/blur.scss | 188 +++++---- .../sidebar/options/rows/color_row.cljs | 2 +- .../sidebar/options/shapes/bool.cljs | 2 +- .../sidebar/options/shapes/circle.cljs | 2 +- .../sidebar/options/shapes/frame.cljs | 2 +- .../sidebar/options/shapes/multiple.cljs | 9 + .../sidebar/options/shapes/path.cljs | 2 +- .../sidebar/options/shapes/rect.cljs | 2 +- .../sidebar/options/shapes/svg_raw.cljs | 2 +- .../sidebar/options/shapes/text.cljs | 2 +- frontend/src/app/plugins/format.cljs | 4 +- frontend/src/app/plugins/parser.cljs | 2 - frontend/src/app/plugins/shape.cljs | 33 +- frontend/src/app/render_wasm/api.cljs | 23 +- frontend/src/app/render_wasm/shape.cljs | 4 + frontend/src/app/render_wasm/wasm.cljs | 1 - .../app/util/code_gen/style_css_formats.cljs | 3 +- .../app/util/code_gen/style_css_values.cljs | 6 +- .../plugins/context_shapes_test.cljs | 1 - frontend/translations/en.po | 28 ++ frontend/translations/es.po | 28 ++ plugins/libs/plugin-types/index.d.ts | 12 +- render-wasm/src/render.rs | 30 +- render-wasm/src/shapes.rs | 50 ++- render-wasm/src/wasm/blurs.rs | 15 +- 48 files changed, 840 insertions(+), 368 deletions(-) create mode 100644 common/src/app/common/types/shape/background_blur.cljc diff --git a/common/src/app/common/geom/shapes/bounds.cljc b/common/src/app/common/geom/shapes/bounds.cljc index 95b215c686..794d578c7d 100644 --- a/common/src/app/common/geom/shapes/bounds.cljc +++ b/common/src/app/common/geom/shapes/bounds.cljc @@ -38,13 +38,10 @@ (d/concat-vec [{:id "BackgroundImageFix" :type :image-fix}] - ;; Background blur won't work in current SVG specification - ;; We can revisit this in the future - #_(->> shape :blur (into []) (blur-filters :background-blur)) - (->> shape :shadow (apply-filters :style :drop-shadow)) [{:id "shape" :type :blend-filters}] (->> shape :shadow (apply-filters :style :inner-shadow)) + (->> shape :blur list (apply-filters :type :layer-blur)))) (defn- calculate-filter-bounds @@ -100,17 +97,13 @@ ;; shapes because selrect stores the unrotated rectangle, not the screen-space bbox. (and (empty? (-> shape :shadow)) (or (nil? (:blur shape)) - (not= :layer-blur (-> shape :blur :type)) (zero? (-> shape :blur :value (or 0))))) (-> (dm/get-prop shape :points) (grc/points->rect)) :else (let [filters (shape->filters shape) - blur-value (case (-> shape :blur :type) - :layer-blur (or (-> shape :blur :value) 0) - :background-blur 0 - 0) + blur-value (or (-> shape :blur :value) 0) srect (-> (dm/get-prop shape :points) (grc/points->rect))] (get-rect-filter-bounds srect filters blur-value ignore-shadow-margin?))))) @@ -242,10 +235,7 @@ (not (cfh/frame-shape? shape)) (or (:children-bounds shape))) filters (shape->filters shape) - blur-value (case (-> shape :blur :type) - :layer-blur (or (-> shape :blur :value) 0) - :background-blur 0 - 0)] + blur-value (or (-> shape :blur :value) 0)] (get-rect-filter-bounds children-bounds filters blur-value ignore-shadow-margin?)))) diff --git a/common/src/app/common/geom/shapes/effects.cljc b/common/src/app/common/geom/shapes/effects.cljc index 66859380c7..7e1096e6b8 100644 --- a/common/src/app/common/geom/shapes/effects.cljc +++ b/common/src/app/common/geom/shapes/effects.cljc @@ -24,3 +24,7 @@ [shape scale] (update-in shape [:blur :value] * scale)) +(defn update-background-blur-scale + [shape scale] + (update-in shape [:background-blur :value] * scale)) + diff --git a/common/src/app/common/types/component.cljc b/common/src/app/common/types/component.cljc index 5edb8c3675..46337ca813 100644 --- a/common/src/app/common/types/component.cljc +++ b/common/src/app/common/types/component.cljc @@ -91,6 +91,7 @@ :blend-mode :layer-effects-group :shadow :shadow-group :blur :blur-group + :background-blur :blur-group :masked-group :mask-group :constraints-h :constraints-group :constraints-v :constraints-group diff --git a/common/src/app/common/types/modifiers.cljc b/common/src/app/common/types/modifiers.cljc index 68ab9e2584..dbc0b52296 100644 --- a/common/src/app/common/types/modifiers.cljc +++ b/common/src/app/common/types/modifiers.cljc @@ -692,6 +692,9 @@ (some? (:blur shape)) (gse/update-blur-scale value) + (some? (:background-blur shape)) + (gse/update-background-blur-scale value) + (ctl/flex-layout? shape) (ctl/update-flex-scale value) diff --git a/common/src/app/common/types/path/bool.cljc b/common/src/app/common/types/path/bool.cljc index 0c01ddf2ff..d2fdf01cb7 100644 --- a/common/src/app/common/types/path/bool.cljc +++ b/common/src/app/common/types/path/bool.cljc @@ -25,7 +25,7 @@ fills))) (def group-style-properties - #{:shadow :blur}) + #{:shadow :blur :background-blur}) ;; FIXME: revisit (def style-properties diff --git a/common/src/app/common/types/shape.cljc b/common/src/app/common/types/shape.cljc index c3275b421f..c7334d1717 100644 --- a/common/src/app/common/types/shape.cljc +++ b/common/src/app/common/types/shape.cljc @@ -24,6 +24,7 @@ [app.common.types.path :as path] [app.common.types.plugins :as ctpg] [app.common.types.shape.attrs :refer [default-color]] + [app.common.types.shape.background-blur :as ctsbb] [app.common.types.shape.blur :as ctsb] [app.common.types.shape.export :as ctse] [app.common.types.shape.interactions :as ctsi] @@ -222,6 +223,7 @@ [:shadow {:optional true} [:vector {:gen/max 1} ctss/schema:shadow]] [:blur {:optional true} ctsb/schema:blur] + [:background-blur {:optional true} ctsbb/schema:background-blur] [:grow-type {:optional true} [::sm/one-of grow-types]] [:applied-tokens {:optional true} cto/schema:applied-tokens] @@ -415,7 +417,7 @@ :remote-synced :shape-ref :touched :blocked :collapsed :locked :hidden :masked-group :fills :proportion :proportion-lock :constraints-h :constraints-v :fixed-scroll :r1 :r2 :r3 :r4 :rotation :opacity :grids :exports - :strokes :blend-mode :interactions :shadow :blur :grow-type :applied-tokens + :strokes :blend-mode :interactions :shadow :blur :background-blur :grow-type :applied-tokens :plugin-data}) (def ^:private allowed-shape-geom-attrs #{:x :y :width :height}) @@ -657,6 +659,7 @@ ;; - Contraints ;; - Shadow ;; - Blur +;; - Background blur ;; - Border radius (def ^:private basic-extract-props #{:fills @@ -681,6 +684,7 @@ :shadow :blur + :background-blur ;; Radius :r1 diff --git a/common/src/app/common/types/shape/attrs.cljc b/common/src/app/common/types/shape/attrs.cljc index 34d4ed62bb..0d76f5afa4 100644 --- a/common/src/app/common/types/shape/attrs.cljc +++ b/common/src/app/common/types/shape/attrs.cljc @@ -32,6 +32,7 @@ :shadow :blur + :background-blur :fills :fill-color @@ -108,6 +109,7 @@ :shadow :blur + :background-blur :exports @@ -166,6 +168,7 @@ :shadow :blur + :background-blur :exports @@ -223,6 +226,7 @@ :shadow :blur + :background-blur :exports @@ -280,6 +284,7 @@ :shadow :blur + :background-blur :exports @@ -336,6 +341,7 @@ :shadow :blur + :background-blur :typography-ref-id :typography-ref-file @@ -398,6 +404,7 @@ :shadow :blur + :background-blur :exports @@ -456,6 +463,7 @@ :shadow :blur + :background-blur :exports @@ -513,6 +521,7 @@ :shadow :blur + :background-blur :exports diff --git a/common/src/app/common/types/shape/background_blur.cljc b/common/src/app/common/types/shape/background_blur.cljc new file mode 100644 index 0000000000..214629a580 --- /dev/null +++ b/common/src/app/common/types/shape/background_blur.cljc @@ -0,0 +1,16 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC Sucursal en España SL + +(ns app.common.types.shape.background-blur + (:require + [app.common.schema :as sm])) + +(def schema:background-blur + [:map {:title "BackgroundBlur"} + [:id ::sm/uuid] + [:type [:enum :background-blur]] + [:value ::sm/safe-number] + [:hidden :boolean]]) \ No newline at end of file diff --git a/common/src/app/common/types/shape/blur.cljc b/common/src/app/common/types/shape/blur.cljc index 90eae19fb3..e0a149d2bc 100644 --- a/common/src/app/common/types/shape/blur.cljc +++ b/common/src/app/common/types/shape/blur.cljc @@ -8,12 +8,9 @@ (:require [app.common.schema :as sm])) -(def schema:blur-type - [:enum :layer-blur :background-blur]) - (def schema:blur [:map {:title "Blur"} [:id ::sm/uuid] - [:type schema:blur-type] + [:type [:enum :layer-blur]] [:value ::sm/safe-number] [:hidden :boolean]]) diff --git a/frontend/playwright/data/render-wasm/get-file-background-blur.json b/frontend/playwright/data/render-wasm/get-file-background-blur.json index 34bbba7ad8..52912bae7a 100644 --- a/frontend/playwright/data/render-wasm/get-file-background-blur.json +++ b/frontend/playwright/data/render-wasm/get-file-background-blur.json @@ -123,12 +123,12 @@ "~u00000000-0000-0000-0000-000000000000": "[\"~#shape\",[\"^ \",\"~:y\",0,\"~:hide-fill-on-export\",false,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:name\",\"Root Frame\",\"~:width\",0.01,\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",0.0,\"~:y\",0.0]],[\"^:\",[\"^ \",\"~:x\",0.01,\"~:y\",0.0]],[\"^:\",[\"^ \",\"~:x\",0.01,\"~:y\",0.01]],[\"^:\",[\"^ \",\"~:x\",0.0,\"~:y\",0.01]]],\"~:r2\",0,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^3\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",0,\"~:r1\",0,\"~:id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",0,\"~:proportion\",1.0,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",0,\"~:y\",0,\"^6\",0.01,\"~:height\",0.01,\"~:x1\",0,\"~:y1\",0,\"~:x2\",0.01,\"~:y2\",0.01]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#FFFFFF\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^H\",0.01,\"~:flip-y\",null,\"~:shapes\",[\"~u11813dac-4dc3-80d8-8007-b2804a521773\",\"~u11813dac-4dc3-80d8-8007-b280a6cd14ac\",\"~u11813dac-4dc3-80d8-8007-b28053db9865\",\"~u11813dac-4dc3-80d8-8007-b2806dfd8a36\",\"~u11813dac-4dc3-80d8-8007-b280a6cd14a4\",\"~u5b58e018-fa5e-805d-8007-b27294ad5af1\",\"~u11813dac-4dc3-80d8-8007-b280a6cd14a7\",\"~u11813dac-4dc3-80d8-8007-b280a6cd14af\",\"~u11813dac-4dc3-80d8-8007-b28053db9864\",\"~u11813dac-4dc3-80d8-8007-b2806dfde19d\",\"~u11813dac-4dc3-80d8-8007-b280a6cd14aa\",\"~u11813dac-4dc3-80d8-8007-b280a6cd14ab\"]]]", "~u11813dac-4dc3-80d8-8007-b2806dfd8a36": "[\"~#shape\",[\"^ \",\"~:y\",93.99999856948853,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:index\",2,\"~:name\",\"Group\",\"~:width\",303,\"~:type\",\"~:group\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",1307.9999368190765,\"~:y\",93.99999856948853]],[\"^:\",[\"^ \",\"~:x\",1610.9999368190765,\"~:y\",93.99999856948853]],[\"^:\",[\"^ \",\"~:x\",1610.9999368190765,\"~:y\",396.9999985694885]],[\"^:\",[\"^ \",\"~:x\",1307.9999368190765,\"~:y\",396.9999985694885]]],\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:id\",\"~u11813dac-4dc3-80d8-8007-b2806dfd8a36\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",1307.9999368190765,\"~:proportion\",1,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",1307.9999368190765,\"~:y\",93.99999856948853,\"^6\",303,\"~:height\",303,\"~:x1\",1307.9999368190765,\"~:y1\",93.99999856948853,\"~:x2\",1610.9999368190765,\"~:y2\",396.9999985694885]],\"~:fills\",[],\"~:flip-x\",null,\"^D\",303,\"~:flip-y\",null,\"~:shapes\",[\"~u11813dac-4dc3-80d8-8007-b2806dfd8a37\",\"~u11813dac-4dc3-80d8-8007-b2806dfde19c\"]]]", "~u11813dac-4dc3-80d8-8007-b2806dfd8a37": "[\"~#shape\",[\"^ \",\"~:y\",93.99999856948853,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Ellipse\",\"~:width\",194,\"~:type\",\"~:circle\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",1416.9999368190765,\"~:y\",93.99999856948853]],[\"^<\",[\"^ \",\"~:x\",1610.9999368190765,\"~:y\",93.99999856948853]],[\"^<\",[\"^ \",\"~:x\",1610.9999368190765,\"~:y\",293.9999985694885]],[\"^<\",[\"^ \",\"~:x\",1416.9999368190765,\"~:y\",293.9999985694885]]],\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:id\",\"~u11813dac-4dc3-80d8-8007-b2806dfd8a37\",\"~:parent-id\",\"~u11813dac-4dc3-80d8-8007-b2806dfd8a36\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",1416.9999368190765,\"~:proportion\",1,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",1416.9999368190765,\"~:y\",93.99999856948853,\"^8\",194,\"~:height\",200,\"~:x1\",1416.9999368190765,\"~:y1\",93.99999856948853,\"~:x2\",1610.9999368190765,\"~:y2\",293.9999985694885]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#174be9\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^F\",200,\"~:flip-y\",null]]", - "~u5b58e018-fa5e-805d-8007-b27294ad5af1": "[\"~#shape\",[\"^ \",\"~:y\",-22.00000122055286,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle\",\"~:width\",445.0000023918125,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",162.9999992063131,\"~:y\",-22.000001220552832]],[\"^<\",[\"^ \",\"~:x\",608.0000015981256,\"~:y\",-22.000001220552832]],[\"^<\",[\"^ \",\"~:x\",608.0000015981256,\"~:y\",423.0000182011825]],[\"^<\",[\"^ \",\"~:x\",162.9999992063131,\"~:y\",423.0000182011825]]],\"~:r2\",20,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",20,\"~:blur\",[\"^ \",\"~:id\",\"~u5b58e018-fa5e-805d-8007-b2729af5ed71\",\"^9\",\"~:background-blur\",\"~:value\",4,\"~:hidden\",false],\"~:r1\",20,\"^B\",\"~u5b58e018-fa5e-805d-8007-b27294ad5af1\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",162.99999920631308,\"~:proportion\",1,\"~:r4\",20,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",162.99999920631308,\"~:y\",-22.00000122055286,\"^8\",445.0000023918125,\"~:height\",445.0000194217354,\"~:x1\",162.99999920631308,\"~:y1\",-22.00000122055286,\"~:x2\",608.0000015981256,\"~:y2\",423.0000182011825]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#ffffff\",\"~:fill-opacity\",0.30392156862745096]],\"~:flip-x\",null,\"^N\",445.0000194217354,\"~:flip-y\",null]]", + "~u5b58e018-fa5e-805d-8007-b27294ad5af1": "[\"~#shape\",[\"^ \",\"~:y\",-22.00000122055286,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle\",\"~:width\",445.0000023918125,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",162.9999992063131,\"~:y\",-22.000001220552832]],[\"^<\",[\"^ \",\"~:x\",608.0000015981256,\"~:y\",-22.000001220552832]],[\"^<\",[\"^ \",\"~:x\",608.0000015981256,\"~:y\",423.0000182011825]],[\"^<\",[\"^ \",\"~:x\",162.9999992063131,\"~:y\",423.0000182011825]]],\"~:r2\",20,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",20,\"~:background-blur\",[\"^ \",\"~:id\",\"~u5b58e018-fa5e-805d-8007-b2729af5ed71\",\"^9\",\"~:background-blur\",\"~:value\",4,\"~:hidden\",false],\"~:r1\",20,\"^B\",\"~u5b58e018-fa5e-805d-8007-b27294ad5af1\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",162.99999920631308,\"~:proportion\",1,\"~:r4\",20,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",162.99999920631308,\"~:y\",-22.00000122055286,\"^8\",445.0000023918125,\"~:height\",445.0000194217354,\"~:x1\",162.99999920631308,\"~:y1\",-22.00000122055286,\"~:x2\",608.0000015981256,\"~:y2\",423.0000182011825]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#ffffff\",\"~:fill-opacity\",0.30392156862745096]],\"~:flip-x\",null,\"^N\",445.0000194217354,\"~:flip-y\",null]]", "~u11813dac-4dc3-80d8-8007-b2804a521773": "[\"~#shape\",[\"^ \",\"~:y\",49.00000846385956,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:index\",2,\"~:name\",\"Group\",\"~:width\",303,\"~:type\",\"~:group\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",234.00000047683716,\"~:y\",49.00000846385956]],[\"^:\",[\"^ \",\"~:x\",537.0000004768372,\"~:y\",49.00000846385956]],[\"^:\",[\"^ \",\"~:x\",537.0000004768372,\"~:y\",352.00000846385956]],[\"^:\",[\"^ \",\"~:x\",234.00000047683716,\"~:y\",352.00000846385956]]],\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:id\",\"~u11813dac-4dc3-80d8-8007-b2804a521773\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",234.00000047683716,\"~:proportion\",1,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",234.00000047683716,\"~:y\",49.00000846385956,\"^6\",303,\"~:height\",303,\"~:x1\",234.00000047683716,\"~:y1\",49.00000846385956,\"~:x2\",537.0000004768372,\"~:y2\",352.00000846385956]],\"~:fills\",[],\"~:flip-x\",null,\"^D\",303,\"~:flip-y\",null,\"~:shapes\",[\"~u5b58e018-fa5e-805d-8007-b27288b0cd80\",\"~u5b58e018-fa5e-805d-8007-b2728b102fe6\"]]]", "~u11813dac-4dc3-80d8-8007-b2806dfde19c": "[\"~#shape\",[\"^ \",\"~:y\",189.99999856948853,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle\",\"~:width\",206,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",1307.9999368190765,\"~:y\",189.99999856948853]],[\"^<\",[\"^ \",\"~:x\",1513.9999368190765,\"~:y\",189.99999856948853]],[\"^<\",[\"^ \",\"~:x\",1513.9999368190765,\"~:y\",396.9999985694885]],[\"^<\",[\"^ \",\"~:x\",1307.9999368190765,\"~:y\",396.9999985694885]]],\"~:r2\",0,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",0,\"~:r1\",0,\"~:id\",\"~u11813dac-4dc3-80d8-8007-b2806dfde19c\",\"~:parent-id\",\"~u11813dac-4dc3-80d8-8007-b2806dfd8a36\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",1307.9999368190765,\"~:proportion\",1,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",1307.9999368190765,\"~:y\",189.99999856948853,\"^8\",206,\"~:height\",207,\"~:x1\",1307.9999368190765,\"~:y1\",189.99999856948853,\"~:x2\",1513.9999368190765,\"~:y2\",396.9999985694885]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#c8f00d\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^J\",207,\"~:flip-y\",null]]", - "~u11813dac-4dc3-80d8-8007-b2806dfde19d": "[\"~#shape\",[\"^ \",\"~:y\",-32.00000238488809,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle\",\"~:width\",302.9999596227117,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",1307.9999330492521,\"~:y\",-32.00000238488809]],[\"^<\",[\"^ \",\"~:x\",1610.9998926719638,\"~:y\",-32.00000238488809]],[\"^<\",[\"^ \",\"~:x\",1610.9998926719638,\"~:y\",268.00000735984116]],[\"^<\",[\"^ \",\"~:x\",1307.9999330492521,\"~:y\",268.00000735984116]]],\"~:r2\",20,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",20,\"~:blur\",[\"^ \",\"~:id\",\"~u5b58e018-fa5e-805d-8007-b2729af5ed71\",\"^9\",\"~:background-blur\",\"~:value\",4,\"~:hidden\",false],\"~:r1\",20,\"^B\",\"~u11813dac-4dc3-80d8-8007-b2806dfde19d\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",1307.9999330492521,\"~:proportion\",1,\"~:r4\",20,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",1307.9999330492521,\"~:y\",-32.00000238488809,\"^8\",302.9999596227117,\"~:height\",300.00000974472925,\"~:x1\",1307.9999330492521,\"~:y1\",-32.00000238488809,\"~:x2\",1610.9998926719638,\"~:y2\",268.00000735984116]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#ffffff\",\"~:fill-opacity\",0.30392156862745096]],\"~:flip-x\",null,\"^N\",300.00000974472925,\"~:flip-y\",null]]", + "~u11813dac-4dc3-80d8-8007-b2806dfde19d": "[\"~#shape\",[\"^ \",\"~:y\",-32.00000238488809,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle\",\"~:width\",302.9999596227117,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",1307.9999330492521,\"~:y\",-32.00000238488809]],[\"^<\",[\"^ \",\"~:x\",1610.9998926719638,\"~:y\",-32.00000238488809]],[\"^<\",[\"^ \",\"~:x\",1610.9998926719638,\"~:y\",268.00000735984116]],[\"^<\",[\"^ \",\"~:x\",1307.9999330492521,\"~:y\",268.00000735984116]]],\"~:r2\",20,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",20,\"~:background-blur\",[\"^ \",\"~:id\",\"~u5b58e018-fa5e-805d-8007-b2729af5ed71\",\"^9\",\"~:background-blur\",\"~:value\",4,\"~:hidden\",false],\"~:r1\",20,\"^B\",\"~u11813dac-4dc3-80d8-8007-b2806dfde19d\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",1307.9999330492521,\"~:proportion\",1,\"~:r4\",20,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",1307.9999330492521,\"~:y\",-32.00000238488809,\"^8\",302.9999596227117,\"~:height\",300.00000974472925,\"~:x1\",1307.9999330492521,\"~:y1\",-32.00000238488809,\"~:x2\",1610.9998926719638,\"~:y2\",268.00000735984116]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#ffffff\",\"~:fill-opacity\",0.30392156862745096]],\"~:flip-x\",null,\"^N\",300.00000974472925,\"~:flip-y\",null]]", "~u11813dac-4dc3-80d8-8007-b280a6cd14a4": "[\"~#shape\",[\"^ \",\"~:y\",592.0000023841858,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:index\",2,\"~:name\",\"Group\",\"~:width\",303,\"~:type\",\"~:group\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",817.9999825954437,\"~:y\",592.0000023841858]],[\"^:\",[\"^ \",\"~:x\",1120.9999825954437,\"~:y\",592.0000023841858]],[\"^:\",[\"^ \",\"~:x\",1120.9999825954437,\"~:y\",895.0000023841858]],[\"^:\",[\"^ \",\"~:x\",817.9999825954437,\"~:y\",895.0000023841858]]],\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:id\",\"~u11813dac-4dc3-80d8-8007-b280a6cd14a4\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",817.9999825954437,\"~:proportion\",1,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",817.9999825954437,\"~:y\",592.0000023841858,\"^6\",303,\"~:height\",303,\"~:x1\",817.9999825954437,\"~:y1\",592.0000023841858,\"~:x2\",1120.9999825954437,\"~:y2\",895.0000023841858]],\"~:fills\",[],\"~:flip-x\",null,\"^D\",303,\"~:flip-y\",null,\"~:shapes\",[\"~u11813dac-4dc3-80d8-8007-b280a6cd14a5\",\"~u11813dac-4dc3-80d8-8007-b280a6cd14a6\"]]]", - "~u11813dac-4dc3-80d8-8007-b28053db9864": "[\"~#shape\",[\"^ \",\"~:y\",97.00000524450644,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle\",\"~:width\",299.9999982638824,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",770.9999871603877,\"~:y\",97.00000524450644]],[\"^<\",[\"^ \",\"~:x\",1070.99998542427,\"~:y\",97.00000524450644]],[\"^<\",[\"^ \",\"~:x\",1070.99998542427,\"~:y\",397.0000149892357]],[\"^<\",[\"^ \",\"~:x\",770.9999871603877,\"~:y\",397.0000149892357]]],\"~:r2\",20,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",20,\"~:blur\",[\"^ \",\"~:id\",\"~u5b58e018-fa5e-805d-8007-b2729af5ed71\",\"^9\",\"~:background-blur\",\"~:value\",4,\"~:hidden\",false],\"~:r1\",20,\"^B\",\"~u11813dac-4dc3-80d8-8007-b28053db9864\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",770.9999871603877,\"~:proportion\",1,\"~:r4\",20,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",770.9999871603877,\"~:y\",97.00000524450644,\"^8\",299.9999982638824,\"~:height\",300.00000974472925,\"~:x1\",770.9999871603877,\"~:y1\",97.00000524450644,\"~:x2\",1070.99998542427,\"~:y2\",397.0000149892357]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#ffffff\",\"~:fill-opacity\",0.30392156862745096]],\"~:flip-x\",null,\"^N\",300.00000974472925,\"~:flip-y\",null]]", + "~u11813dac-4dc3-80d8-8007-b28053db9864": "[\"~#shape\",[\"^ \",\"~:y\",97.00000524450644,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle\",\"~:width\",299.9999982638824,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",770.9999871603877,\"~:y\",97.00000524450644]],[\"^<\",[\"^ \",\"~:x\",1070.99998542427,\"~:y\",97.00000524450644]],[\"^<\",[\"^ \",\"~:x\",1070.99998542427,\"~:y\",397.0000149892357]],[\"^<\",[\"^ \",\"~:x\",770.9999871603877,\"~:y\",397.0000149892357]]],\"~:r2\",20,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",20,\"~:background-blur\",[\"^ \",\"~:id\",\"~u5b58e018-fa5e-805d-8007-b2729af5ed71\",\"^9\",\"~:background-blur\",\"~:value\",4,\"~:hidden\",false],\"~:r1\",20,\"^B\",\"~u11813dac-4dc3-80d8-8007-b28053db9864\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",770.9999871603877,\"~:proportion\",1,\"~:r4\",20,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",770.9999871603877,\"~:y\",97.00000524450644,\"^8\",299.9999982638824,\"~:height\",300.00000974472925,\"~:x1\",770.9999871603877,\"~:y1\",97.00000524450644,\"~:x2\",1070.99998542427,\"~:y2\",397.0000149892357]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#ffffff\",\"~:fill-opacity\",0.30392156862745096]],\"~:flip-x\",null,\"^N\",300.00000974472925,\"~:flip-y\",null]]", "~u5b58e018-fa5e-805d-8007-b2728b102fe6": "[\"~#shape\",[\"^ \",\"~:y\",145.00000846385956,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle\",\"~:width\",206,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",234.00000047683716,\"~:y\",145.00000846385956]],[\"^<\",[\"^ \",\"~:x\",440.00000047683716,\"~:y\",145.00000846385956]],[\"^<\",[\"^ \",\"~:x\",440.00000047683716,\"~:y\",352.00000846385956]],[\"^<\",[\"^ \",\"~:x\",234.00000047683716,\"~:y\",352.00000846385956]]],\"~:r2\",0,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",0,\"~:r1\",0,\"~:id\",\"~u5b58e018-fa5e-805d-8007-b2728b102fe6\",\"~:parent-id\",\"~u11813dac-4dc3-80d8-8007-b2804a521773\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",234.00000047683716,\"~:proportion\",1,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",234.00000047683716,\"~:y\",145.00000846385956,\"^8\",206,\"~:height\",207,\"~:x1\",234.00000047683716,\"~:y1\",145.00000846385956,\"~:x2\",440.00000047683716,\"~:y2\",352.00000846385956]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#c8f00d\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^J\",207,\"~:flip-y\",null]]", "~u11813dac-4dc3-80d8-8007-b280a6cd14a5": "[\"~#shape\",[\"^ \",\"~:y\",592.0000023841858,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Ellipse\",\"~:width\",194,\"~:type\",\"~:circle\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",926.9999825954437,\"~:y\",592.0000023841858]],[\"^<\",[\"^ \",\"~:x\",1120.9999825954437,\"~:y\",592.0000023841858]],[\"^<\",[\"^ \",\"~:x\",1120.9999825954437,\"~:y\",792.0000023841858]],[\"^<\",[\"^ \",\"~:x\",926.9999825954437,\"~:y\",792.0000023841858]]],\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:id\",\"~u11813dac-4dc3-80d8-8007-b280a6cd14a5\",\"~:parent-id\",\"~u11813dac-4dc3-80d8-8007-b280a6cd14a4\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",926.9999825954437,\"~:proportion\",1,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",926.9999825954437,\"~:y\",592.0000023841858,\"^8\",194,\"~:height\",200,\"~:x1\",926.9999825954437,\"~:y1\",592.0000023841858,\"~:x2\",1120.9999825954437,\"~:y2\",792.0000023841858]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#174be9\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^F\",200,\"~:flip-y\",null]]", "~u11813dac-4dc3-80d8-8007-b28053db9865": "[\"~#shape\",[\"^ \",\"~:y\",48.00000238418579,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:index\",2,\"~:name\",\"Group\",\"~:width\",303,\"~:type\",\"~:group\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",817.9999825954437,\"~:y\",48.00000238418579]],[\"^:\",[\"^ \",\"~:x\",1120.9999825954437,\"~:y\",48.00000238418579]],[\"^:\",[\"^ \",\"~:x\",1120.9999825954437,\"~:y\",351.0000023841858]],[\"^:\",[\"^ \",\"~:x\",817.9999825954437,\"~:y\",351.0000023841858]]],\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:id\",\"~u11813dac-4dc3-80d8-8007-b28053db9865\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",817.9999825954437,\"~:proportion\",1,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",817.9999825954437,\"~:y\",48.00000238418579,\"^6\",303,\"~:height\",303,\"~:x1\",817.9999825954437,\"~:y1\",48.00000238418579,\"~:x2\",1120.9999825954437,\"~:y2\",351.0000023841858]],\"~:fills\",[],\"~:flip-x\",null,\"^D\",303,\"~:flip-y\",null,\"~:shapes\",[\"~u11813dac-4dc3-80d8-8007-b28053db9866\",\"~u11813dac-4dc3-80d8-8007-b28053db9867\"]]]", @@ -140,11 +140,11 @@ "~u11813dac-4dc3-80d8-8007-b280a6cd14ac": "[\"~#shape\",[\"^ \",\"~:y\",593.0000084638596,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:index\",2,\"~:name\",\"Group\",\"~:width\",303,\"~:type\",\"~:group\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",234.00000047683716,\"~:y\",593.0000084638596]],[\"^:\",[\"^ \",\"~:x\",537.0000004768372,\"~:y\",593.0000084638596]],[\"^:\",[\"^ \",\"~:x\",537.0000004768372,\"~:y\",896.0000084638596]],[\"^:\",[\"^ \",\"~:x\",234.00000047683716,\"~:y\",896.0000084638596]]],\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:id\",\"~u11813dac-4dc3-80d8-8007-b280a6cd14ac\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",234.00000047683716,\"~:proportion\",1,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",234.00000047683716,\"~:y\",593.0000084638596,\"^6\",303,\"~:height\",303,\"~:x1\",234.00000047683716,\"~:y1\",593.0000084638596,\"~:x2\",537.0000004768372,\"~:y2\",896.0000084638596]],\"~:fills\",[],\"~:flip-x\",null,\"^D\",303,\"~:flip-y\",null,\"~:shapes\",[\"~u11813dac-4dc3-80d8-8007-b280a6cd14ad\",\"~u11813dac-4dc3-80d8-8007-b280a6cd14ae\"]]]", "~u11813dac-4dc3-80d8-8007-b280a6cd14ad": "[\"~#shape\",[\"^ \",\"~:y\",593.0000084638596,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Ellipse\",\"~:width\",194,\"~:type\",\"~:circle\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",343.00000047683716,\"~:y\",593.0000084638596]],[\"^<\",[\"^ \",\"~:x\",537.0000004768372,\"~:y\",593.0000084638596]],[\"^<\",[\"^ \",\"~:x\",537.0000004768372,\"~:y\",793.0000084638596]],[\"^<\",[\"^ \",\"~:x\",343.00000047683716,\"~:y\",793.0000084638596]]],\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:id\",\"~u11813dac-4dc3-80d8-8007-b280a6cd14ad\",\"~:parent-id\",\"~u11813dac-4dc3-80d8-8007-b280a6cd14ac\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",343.00000047683716,\"~:proportion\",1,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",343.00000047683716,\"~:y\",593.0000084638596,\"^8\",194,\"~:height\",200,\"~:x1\",343.00000047683716,\"~:y1\",593.0000084638596,\"~:x2\",537.0000004768372,\"~:y2\",793.0000084638596]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#174be9\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^F\",200,\"~:flip-y\",null]]", "~u11813dac-4dc3-80d8-8007-b280a6cd14ae": "[\"~#shape\",[\"^ \",\"~:y\",689.0000084638596,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle\",\"~:width\",206,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",234.00000047683716,\"~:y\",689.0000084638596]],[\"^<\",[\"^ \",\"~:x\",440.00000047683716,\"~:y\",689.0000084638596]],[\"^<\",[\"^ \",\"~:x\",440.00000047683716,\"~:y\",896.0000084638596]],[\"^<\",[\"^ \",\"~:x\",234.00000047683716,\"~:y\",896.0000084638596]]],\"~:r2\",0,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",0,\"~:r1\",0,\"~:id\",\"~u11813dac-4dc3-80d8-8007-b280a6cd14ae\",\"~:parent-id\",\"~u11813dac-4dc3-80d8-8007-b280a6cd14ac\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",234.00000047683716,\"~:proportion\",1,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",234.00000047683716,\"~:y\",689.0000084638596,\"^8\",206,\"~:height\",207,\"~:x1\",234.00000047683716,\"~:y1\",689.0000084638596,\"~:x2\",440.00000047683716,\"~:y2\",896.0000084638596]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#c8f00d\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^J\",207,\"~:flip-y\",null]]", - "~u11813dac-4dc3-80d8-8007-b280a6cd14af": "[\"~#shape\",[\"^ \",\"~:y\",512.0000283168957,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle\",\"~:width\",445.0000023918125,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",157.99998871589563,\"~:y\",512.0000283168957]],[\"^<\",[\"^ \",\"~:x\",602.9999911077081,\"~:y\",512.0000283168957]],[\"^<\",[\"^ \",\"~:x\",602.9999911077081,\"~:y\",957.000047738631]],[\"^<\",[\"^ \",\"~:x\",157.99998871589563,\"~:y\",957.000047738631]]],\"~:r2\",20,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",20,\"~:blur\",[\"^ \",\"~:id\",\"~u5b58e018-fa5e-805d-8007-b2729af5ed71\",\"^9\",\"~:background-blur\",\"~:value\",50,\"~:hidden\",false],\"~:r1\",20,\"^B\",\"~u11813dac-4dc3-80d8-8007-b280a6cd14af\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",157.9999887158956,\"~:proportion\",1,\"~:r4\",20,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",157.9999887158956,\"~:y\",512.0000283168957,\"^8\",445.0000023918125,\"~:height\",445.00001942173526,\"~:x1\",157.9999887158956,\"~:y1\",512.0000283168957,\"~:x2\",602.9999911077081,\"~:y2\",957.000047738631]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#ffffff\",\"~:fill-opacity\",0.30392156862745096]],\"~:flip-x\",null,\"^N\",445.00001942173526,\"~:flip-y\",null]]", + "~u11813dac-4dc3-80d8-8007-b280a6cd14af": "[\"~#shape\",[\"^ \",\"~:y\",512.0000283168957,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle\",\"~:width\",445.0000023918125,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",157.99998871589563,\"~:y\",512.0000283168957]],[\"^<\",[\"^ \",\"~:x\",602.9999911077081,\"~:y\",512.0000283168957]],[\"^<\",[\"^ \",\"~:x\",602.9999911077081,\"~:y\",957.000047738631]],[\"^<\",[\"^ \",\"~:x\",157.99998871589563,\"~:y\",957.000047738631]]],\"~:r2\",20,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",20,\"~:background-blur\",[\"^ \",\"~:id\",\"~u5b58e018-fa5e-805d-8007-b2729af5ed71\",\"^9\",\"~:background-blur\",\"~:value\",50,\"~:hidden\",false],\"~:r1\",20,\"^B\",\"~u11813dac-4dc3-80d8-8007-b280a6cd14af\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",157.9999887158956,\"~:proportion\",1,\"~:r4\",20,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",157.9999887158956,\"~:y\",512.0000283168957,\"^8\",445.0000023918125,\"~:height\",445.00001942173526,\"~:x1\",157.9999887158956,\"~:y1\",512.0000283168957,\"~:x2\",602.9999911077081,\"~:y2\",957.000047738631]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#ffffff\",\"~:fill-opacity\",0.30392156862745096]],\"~:flip-x\",null,\"^N\",445.00001942173526,\"~:flip-y\",null]]", "~u11813dac-4dc3-80d8-8007-b280a6cd14a8": "[\"~#shape\",[\"^ \",\"~:y\",637.9999985694885,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Ellipse\",\"~:width\",194,\"~:type\",\"~:circle\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",1417.000058889389,\"~:y\",637.9999985694885]],[\"^<\",[\"^ \",\"~:x\",1611.000058889389,\"~:y\",637.9999985694885]],[\"^<\",[\"^ \",\"~:x\",1611.000058889389,\"~:y\",837.9999985694885]],[\"^<\",[\"^ \",\"~:x\",1417.000058889389,\"~:y\",837.9999985694885]]],\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:id\",\"~u11813dac-4dc3-80d8-8007-b280a6cd14a8\",\"~:parent-id\",\"~u11813dac-4dc3-80d8-8007-b280a6cd14a7\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",1417.000058889389,\"~:proportion\",1,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",1417.000058889389,\"~:y\",637.9999985694885,\"^8\",194,\"~:height\",200,\"~:x1\",1417.000058889389,\"~:y1\",637.9999985694885,\"~:x2\",1611.000058889389,\"~:y2\",837.9999985694885]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#174be9\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^F\",200,\"~:flip-y\",null]]", "~u11813dac-4dc3-80d8-8007-b280a6cd14a9": "[\"~#shape\",[\"^ \",\"~:y\",733.9999985694885,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle\",\"~:width\",206,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",1308.000058889389,\"~:y\",733.9999985694885]],[\"^<\",[\"^ \",\"~:x\",1514.000058889389,\"~:y\",733.9999985694885]],[\"^<\",[\"^ \",\"~:x\",1514.000058889389,\"~:y\",940.9999985694885]],[\"^<\",[\"^ \",\"~:x\",1308.000058889389,\"~:y\",940.9999985694885]]],\"~:r2\",0,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",0,\"~:r1\",0,\"~:id\",\"~u11813dac-4dc3-80d8-8007-b280a6cd14a9\",\"~:parent-id\",\"~u11813dac-4dc3-80d8-8007-b280a6cd14a7\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",1308.000058889389,\"~:proportion\",1,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",1308.000058889389,\"~:y\",733.9999985694885,\"^8\",206,\"~:height\",207,\"~:x1\",1308.000058889389,\"~:y1\",733.9999985694885,\"~:x2\",1514.000058889389,\"~:y2\",940.9999985694885]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#c8f00d\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^J\",207,\"~:flip-y\",null]]", - "~u11813dac-4dc3-80d8-8007-b280a6cd14aa": "[\"~#shape\",[\"^ \",\"~:y\",511.9999976151119,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle\",\"~:width\",302.9999596227117,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",1308.0000551195646,\"~:y\",511.9999976151119]],[\"^<\",[\"^ \",\"~:x\",1611.0000147422763,\"~:y\",511.9999976151119]],[\"^<\",[\"^ \",\"~:x\",1611.0000147422763,\"~:y\",812.0000073598412]],[\"^<\",[\"^ \",\"~:x\",1308.0000551195646,\"~:y\",812.0000073598412]]],\"~:r2\",20,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",20,\"~:blur\",[\"^ \",\"~:id\",\"~u5b58e018-fa5e-805d-8007-b2729af5ed71\",\"^9\",\"~:background-blur\",\"~:value\",50,\"~:hidden\",false],\"~:r1\",20,\"^B\",\"~u11813dac-4dc3-80d8-8007-b280a6cd14aa\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",1308.0000551195646,\"~:proportion\",1,\"~:r4\",20,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",1308.0000551195646,\"~:y\",511.9999976151119,\"^8\",302.9999596227117,\"~:height\",300.0000097447293,\"~:x1\",1308.0000551195646,\"~:y1\",511.9999976151119,\"~:x2\",1611.0000147422763,\"~:y2\",812.0000073598412]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#ffffff\",\"~:fill-opacity\",0.30392156862745096]],\"~:flip-x\",null,\"^N\",300.0000097447293,\"~:flip-y\",null]]", - "~u11813dac-4dc3-80d8-8007-b280a6cd14ab": "[\"~#shape\",[\"^ \",\"~:y\",512.00002813269,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle\",\"~:width\",299.9999982638824,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",743.9999566428096,\"~:y\",512.00002813269]],[\"^<\",[\"^ \",\"~:x\",1043.999954906692,\"~:y\",512.00002813269]],[\"^<\",[\"^ \",\"~:x\",1043.999954906692,\"~:y\",812.0000378774193]],[\"^<\",[\"^ \",\"~:x\",743.9999566428096,\"~:y\",812.0000378774193]]],\"~:r2\",20,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",20,\"~:blur\",[\"^ \",\"~:id\",\"~u5b58e018-fa5e-805d-8007-b2729af5ed71\",\"^9\",\"~:background-blur\",\"~:value\",50,\"~:hidden\",false],\"~:r1\",20,\"^B\",\"~u11813dac-4dc3-80d8-8007-b280a6cd14ab\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",743.9999566428096,\"~:proportion\",1,\"~:r4\",20,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",743.9999566428096,\"~:y\",512.00002813269,\"^8\",299.9999982638824,\"~:height\",300.00000974472937,\"~:x1\",743.9999566428096,\"~:y1\",512.00002813269,\"~:x2\",1043.999954906692,\"~:y2\",812.0000378774193]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#ffffff\",\"~:fill-opacity\",0.30392156862745096]],\"~:flip-x\",null,\"^N\",300.00000974472937,\"~:flip-y\",null]]" + "~u11813dac-4dc3-80d8-8007-b280a6cd14aa": "[\"~#shape\",[\"^ \",\"~:y\",511.9999976151119,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle\",\"~:width\",302.9999596227117,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",1308.0000551195646,\"~:y\",511.9999976151119]],[\"^<\",[\"^ \",\"~:x\",1611.0000147422763,\"~:y\",511.9999976151119]],[\"^<\",[\"^ \",\"~:x\",1611.0000147422763,\"~:y\",812.0000073598412]],[\"^<\",[\"^ \",\"~:x\",1308.0000551195646,\"~:y\",812.0000073598412]]],\"~:r2\",20,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",20,\"~:background-blur\",[\"^ \",\"~:id\",\"~u5b58e018-fa5e-805d-8007-b2729af5ed71\",\"^9\",\"~:background-blur\",\"~:value\",50,\"~:hidden\",false],\"~:r1\",20,\"^B\",\"~u11813dac-4dc3-80d8-8007-b280a6cd14aa\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",1308.0000551195646,\"~:proportion\",1,\"~:r4\",20,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",1308.0000551195646,\"~:y\",511.9999976151119,\"^8\",302.9999596227117,\"~:height\",300.0000097447293,\"~:x1\",1308.0000551195646,\"~:y1\",511.9999976151119,\"~:x2\",1611.0000147422763,\"~:y2\",812.0000073598412]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#ffffff\",\"~:fill-opacity\",0.30392156862745096]],\"~:flip-x\",null,\"^N\",300.0000097447293,\"~:flip-y\",null]]", + "~u11813dac-4dc3-80d8-8007-b280a6cd14ab": "[\"~#shape\",[\"^ \",\"~:y\",512.00002813269,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle\",\"~:width\",299.9999982638824,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",743.9999566428096,\"~:y\",512.00002813269]],[\"^<\",[\"^ \",\"~:x\",1043.999954906692,\"~:y\",512.00002813269]],[\"^<\",[\"^ \",\"~:x\",1043.999954906692,\"~:y\",812.0000378774193]],[\"^<\",[\"^ \",\"~:x\",743.9999566428096,\"~:y\",812.0000378774193]]],\"~:r2\",20,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",20,\"~:background-blur\",[\"^ \",\"~:id\",\"~u5b58e018-fa5e-805d-8007-b2729af5ed71\",\"^9\",\"~:background-blur\",\"~:value\",50,\"~:hidden\",false],\"~:r1\",20,\"^B\",\"~u11813dac-4dc3-80d8-8007-b280a6cd14ab\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",743.9999566428096,\"~:proportion\",1,\"~:r4\",20,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",743.9999566428096,\"~:y\",512.00002813269,\"^8\",299.9999982638824,\"~:height\",300.00000974472937,\"~:x1\",743.9999566428096,\"~:y1\",512.00002813269,\"~:x2\",1043.999954906692,\"~:y2\",812.0000378774193]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#ffffff\",\"~:fill-opacity\",0.30392156862745096]],\"~:flip-x\",null,\"^N\",300.00000974472937,\"~:flip-y\",null]]" } }, "~:id": "~u93bfc923-66b2-813c-8007-b2725507ba09", diff --git a/frontend/playwright/ui/specs/design-tab.spec.js b/frontend/playwright/ui/specs/design-tab.spec.js index 0cf953a302..27be52b4f7 100644 --- a/frontend/playwright/ui/specs/design-tab.spec.js +++ b/frontend/playwright/ui/specs/design-tab.spec.js @@ -257,12 +257,26 @@ test.describe("Background blur", () => { // Click the first Rectangle (which has background-blur type) await workspace.clickLeafLayer("Rectangle"); + const blurSection = workspace.page.getByRole("region", { + name: "Blur effects", + }); + await expect(blurSection).toBeVisible(); + // The blur type select should show "Background blur" as the current value - const blurTypeSelect = workspace.page - .getByTestId("blur-info") - .getByRole("combobox"); + const blurTypeSelect = workspace.page.getByRole("combobox", { + name: "Blur type select", + }); await expect(blurTypeSelect).toBeVisible(); await expect(blurTypeSelect).toContainText("Background blur"); + + // Select first group layer, which has not blur effect + await workspace.layers.getByTestId("layer-row").nth(5).click(); + + await expect(blurTypeSelect).not.toBeVisible(); + await blurSection.getByRole("button", { name: "Add blur" }).click(); + await expect(blurTypeSelect).toBeVisible(); + + await expect(blurTypeSelect).toContainText("Layer blur"); }); test("Shows both layer-blur and background-blur options in the blur type dropdown", async ({ @@ -279,25 +293,66 @@ test.describe("Background blur", () => { }); await workspace.clickLeafLayer("Rectangle"); + const blurSection = workspace.page.getByRole("region", { + name: "Blur effects", + }); + await expect(blurSection).toBeVisible(); // Open the blur type dropdown - const blurTypeSelect = workspace.page - .getByTestId("blur-info") - .getByRole("combobox"); + const blurTypeSelect = blurSection.getByRole("combobox", { + name: "Blur type select", + }); + await expect(blurTypeSelect).toBeVisible(); + await blurTypeSelect.click(); // Both options should be visible - const layerBlurOption = workspace.page.getByRole("option", { + const layerBlurOption = blurSection.getByRole("option", { name: "Layer blur", }); - const backgroundBlurOption = workspace.page.getByRole("option", { + const backgroundBlurOption = blurSection.getByRole("option", { name: "Background blur", }); await expect(layerBlurOption).toBeVisible(); await expect(backgroundBlurOption).toBeVisible(); }); - test("Does not show background blur option when background-blur flag is not active", async ({ + test("Shape can have both layer blur and background blur effects at the same time", async ({ + page, + }) => { + const workspace = new WasmWorkspacePage(page); + await workspace.mockConfigFlags(["enable-background-blur", tokenInputFlag]); + await workspace.setupEmptyFile(); + await workspace.mockGetFile("render-wasm/get-file-background-blur.json"); + + await workspace.goToWorkspace({ + fileId: "93bfc923-66b2-813c-8007-b2725507ba08", + pageId: "93bfc923-66b2-813c-8007-b2725507ba09", + }); + + await workspace.clickLeafLayer("Rectangle"); + const blurSection = workspace.page.getByRole("region", { + name: "Blur effects", + }); + await expect(blurSection).toBeVisible(); + + const addBlurButton = blurSection.getByRole("button", { name: "Add blur" }); + await expect(addBlurButton).toBeVisible(); + await addBlurButton.click(); + + const blurTypeSelect = blurSection.getByRole("combobox", { + name: "Blur type select", + }); + await expect(blurTypeSelect).toHaveCount(2); + + const backgroundBlurLabel = blurSection.getByText("Background blur"); + await expect(backgroundBlurLabel).toBeVisible(); + + const layerBlurLabel = blurSection.getByText("Layer blur"); + await expect(layerBlurLabel).toBeVisible(); + }); + + test("Show background blur disabled when flag is not active", async ({ page, }) => { const workspace = new WasmWorkspacePage(page); @@ -312,15 +367,42 @@ test.describe("Background blur", () => { await workspace.clickLeafLayer("Rectangle"); + // When there is no background blur flag the section has the old name "blur" instead of "blur effects" + const blurSection = workspace.page.getByRole("region", { + name: "Blur", + }); + await expect(blurSection).toBeVisible(); // Without the background-blur flag, no blur type dropdown should appear. - // Instead, a plain "Blur" label is shown. - const blurTypeSelect = workspace.page - .getByTestId("blur-info") - .getByRole("combobox"); + // Instead, a plain "Background blur" label is shown and more option button is disabled. + const blurTypeSelect = blurSection.getByRole("combobox", { + name: "Blur type select", + }); await expect(blurTypeSelect).not.toBeVisible(); - const blurLabel = workspace.page.getByTestId("blur-info").getByText("Blur"); - await expect(blurLabel).toBeVisible(); + const backgroundBlurLabel = blurSection.getByText("Background blur"); + await expect(backgroundBlurLabel).toBeVisible(); + + const showMoreOptionsButton = blurSection.getByRole("button", { + name: "Show/hide more options", + }); + await expect(showMoreOptionsButton).toBeDisabled(); + + const showAndHideButton = blurSection.getByRole("button", { + name: "Toggle blur", + }); + await expect(showAndHideButton).toBeDisabled(); + + const addBlurButton = blurSection.getByRole("button", { name: "Add blur" }); + + // We can add a layer blur, but not a background blur, and the type select should not appear + await expect(addBlurButton).toBeVisible(); + await addBlurButton.click(); + + await expect(blurTypeSelect).not.toBeVisible(); + await expect(backgroundBlurLabel).toBeVisible(); + + const blurLabel = blurSection.getByText("Blur", { exact: true }); + await expect(blurLabel).toHaveCount(2); }); }); diff --git a/frontend/src/app/main/data/workspace/modifiers.cljs b/frontend/src/app/main/data/workspace/modifiers.cljs index add4f050b4..96a1605704 100644 --- a/frontend/src/app/main/data/workspace/modifiers.cljs +++ b/frontend/src/app/main/data/workspace/modifiers.cljs @@ -73,6 +73,7 @@ :r4 :shadow :blur + :background-blur :strokes :width :height diff --git a/frontend/src/app/main/render.cljs b/frontend/src/app/main/render.cljs index 5bd1a63f6d..35183701f5 100644 --- a/frontend/src/app/main/render.cljs +++ b/frontend/src/app/main/render.cljs @@ -281,7 +281,7 @@ (grc/fix-aspect-ratio aspect-ratio)) ;; Bounds without shadows/blur will be the bounds of the thumbnail - bounds2 (gsb/get-object-bounds objects (dissoc frame :shadow :blur)) + bounds2 (gsb/get-object-bounds objects (dissoc frame :shadow :blur :background-blur)) delta-bounds (gpt/point (:x bounds) (:y bounds)) vector (gpt/negate delta-bounds) diff --git a/frontend/src/app/main/ui/components/title_bar.cljs b/frontend/src/app/main/ui/components/title_bar.cljs index 1eb6631015..d294504dd4 100644 --- a/frontend/src/app/main/ui/components/title_bar.cljs +++ b/frontend/src/app/main/ui/components/title_bar.cljs @@ -14,33 +14,32 @@ (mf/defc title-bar* [{:keys [class collapsable collapsed title children btn-icon btn-title add-icon-gap - title-class on-collapsed on-btn-click]}] - [:div {:class [(stl/css :title-bar) - class]} + title-class on-collapsed on-btn-click] :rest props}] + (let [props (mf/spread-props props {:class (stl/css :icon-text-btn) + :on-click on-collapsed})] - (if ^boolean collapsable - [:div {:class [(stl/css :title-wrapper) title-class]} + [:div {:class [(stl/css :title-bar) class]} + (if ^boolean collapsable + [:div {:class [(stl/css :title-wrapper) title-class]} + (let [icon-id (if collapsed "arrow-right" "arrow-down")] + [:> :button props + [:> icon* {:icon-id icon-id + :size "s" + :class (stl/css :icon)}] + [:div {:class (stl/css :title)} title]])] - (let [icon-id (if collapsed "arrow-right" "arrow-down")] - [:button {:class (stl/css :icon-text-btn) - :on-click on-collapsed} - [:> icon* {:icon-id icon-id - :size "s" - :class (stl/css :icon)}] - [:div {:class (stl/css :title)} title]])] + [:div {:class [(stl/css-case :title-only true + :title-only-icon-gap add-icon-gap) + title-class]} + title]) - [:div {:class [(stl/css-case :title-only true - :title-only-icon-gap add-icon-gap) - title-class]} - title]) + children - children - - (when (some? on-btn-click) - [:> icon-button* {:variant "ghost" - :aria-label btn-title - :on-click on-btn-click - :icon btn-icon}])]) + (when (some? on-btn-click) + [:> icon-button* {:variant "ghost" + :aria-label btn-title + :on-click on-btn-click + :icon btn-icon}])])) (mf/defc inspect-title-bar* diff --git a/frontend/src/app/main/ui/components/title_bar.scss b/frontend/src/app/main/ui/components/title_bar.scss index 1dc26b6bf1..3f8045feeb 100644 --- a/frontend/src/app/main/ui/components/title_bar.scss +++ b/frontend/src/app/main/ui/components/title_bar.scss @@ -25,6 +25,7 @@ display: grid; align-items: center; justify-content: flex-start; + text-align: start; grid-auto-flow: column; height: 100%; min-height: deprecated.$s-32; diff --git a/frontend/src/app/main/ui/inspect/attributes/blur.cljs b/frontend/src/app/main/ui/inspect/attributes/blur.cljs index 831580fb13..821df98814 100644 --- a/frontend/src/app/main/ui/inspect/attributes/blur.cljs +++ b/frontend/src/app/main/ui/inspect/attributes/blur.cljs @@ -8,6 +8,8 @@ (:require-macros [app.main.style :as stl]) (:require [app.common.data.macros :as dm] + [app.config :as cf] + [app.main.features :as features] [app.main.ui.components.copy-button :refer [copy-button*]] [app.main.ui.components.title-bar :refer [inspect-title-bar*]] [app.util.code-gen.style-css :as css] @@ -15,35 +17,53 @@ [rumext.v2 :as mf])) (defn- has-blur? [shape] - (:blur shape)) - -(defn- blur-css-property [shape] - (if (= :background-blur (get-in shape [:blur :type])) - :backdrop-filter - :filter)) + (or (:blur shape) + (:background-blur shape))) (mf/defc blur-panel [{:keys [objects shapes]}] - (let [shapes (->> shapes (filter has-blur?))] + (let [render-wasm? (features/use-feature "render-wasm/v1") + bg-blur? (and render-wasm? + (contains? cf/flags :background-blur)) + shapes (->> shapes (filter #(has-blur? %))) + title (if bg-blur? + (tr "labels.blur-effects") + (tr "labels.blur"))] (when (seq shapes) [:div {:class (stl/css :attributes-block)} [:> inspect-title-bar* - {:title (tr "inspect.attributes.blur") + {:title title :class (stl/css :title-wrapper) :title-class (stl/css :blur-attr-title)} (when (= (count shapes) 1) - (let [prop (blur-css-property (first shapes))] - [:> copy-button* {:data (css/get-css-property objects (first shapes) prop) - :class (stl/css :copy-btn-title)}]))] + (let [background-blur (:background-blur (first shapes)) + layer-blur (:blur (first shapes))] + (when background-blur + [:> copy-button* {:data (css/get-css-property objects (first shapes) :backdrop-filter) + :class (stl/css :copy-btn-title)}]) + (when layer-blur + [:> copy-button* {:data (css/get-css-property objects (first shapes) :filter) + :class (stl/css :copy-btn-title)}])))] [:div {:class (stl/css :attributes-content)} (for [shape shapes] - (let [prop (blur-css-property shape)] + (let [background-blur (:background-blur (first shapes)) + layer-blur (:blur (first shapes))] [:div {:class (stl/css :blur-row) :key (dm/str "block-" (:id shape) "-blur")} - [:div {:class (stl/css :global/attr-label)} - (if (= prop :backdrop-filter) "Backdrop Filter" "Filter")] - [:div {:class (stl/css :global/attr-value)} - [:> copy-button* {:data (css/get-css-property objects shape prop)} - [:div {:class (stl/css :button-children)} - (css/get-css-value objects shape prop)]]]]))]]))) + (when background-blur + [:div {:key (dm/str "block-" (:id shape) "-background-blur")} + [:div {:class (stl/css :global/attr-label)} + "Backdrop Filter"] + [:div {:class (stl/css :global/attr-value)} + [:> copy-button* {:data (css/get-css-property objects shape :backdrop-filter)} + [:div {:class (stl/css :button-children)} + (css/get-css-value objects shape :backdrop-filter)]]]]) + (when layer-blur + [:div {:key (dm/str "block-" (:id shape) "-layer-blur")} + [:div {:class (stl/css :global/attr-label)} + "Filter"] + [:div {:class (stl/css :global/attr-value)} + [:> copy-button* {:data (css/get-css-property objects shape :filter)} + [:div {:class (stl/css :button-children)} + (css/get-css-value objects shape :filter)]]]])]))]]))) diff --git a/frontend/src/app/main/ui/inspect/styles.cljs b/frontend/src/app/main/ui/inspect/styles.cljs index 307fcb66fd..7b8238e40c 100644 --- a/frontend/src/app/main/ui/inspect/styles.cljs +++ b/frontend/src/app/main/ui/inspect/styles.cljs @@ -51,7 +51,6 @@ :grid-column :grid-row]) - (def type->panel-group {:multiple [:fill :stroke :text :shadow :blur :layout-element] :frame [:visibility :geometry :fill :stroke :shadow :blur :layout :layout-element] @@ -72,7 +71,8 @@ (seq (:strokes shape))) (defn- has-blur? [shape] - (:blur shape)) + (or (:blur shape) + (:background-blur shape))) (defn- has-text? [shape] (:content shape)) diff --git a/frontend/src/app/main/ui/inspect/styles/panels/blur.cljs b/frontend/src/app/main/ui/inspect/styles/panels/blur.cljs index d7fd9b7202..b67c466d88 100644 --- a/frontend/src/app/main/ui/inspect/styles/panels/blur.cljs +++ b/frontend/src/app/main/ui/inspect/styles/panels/blur.cljs @@ -18,12 +18,24 @@ [:div {:class (stl/css :blur-panel)} (for [shape shapes] [:div {:key (:id shape) :class (stl/css :blur-shape)} - (let [property :filter - value (css/get-css-value objects shape property) - property-name (cmm/get-css-rule-humanized property) - property-value (css/get-css-property objects shape property)] - [:> properties-row* {:key (dm/str "blur-property-" property) - :term property-name - :detail (dm/str value) - :property property-value - :copiable true}])])]) + (let [blur-property :filter + blur-value (css/get-css-value objects shape blur-property) + background-blur-property :backdrop-filter + blur-property-name (cmm/get-css-rule-humanized blur-property) + blur-property-value (css/get-css-property objects shape blur-property) + background-blur-value (css/get-css-value objects shape background-blur-property) + background-blur-property-name (cmm/get-css-rule-humanized background-blur-property) + background-blur-property-value (css/get-css-property objects shape background-blur-property)] + [:div + (when blur-property-value + [:> properties-row* {:key (dm/str "blur-property-" blur-property) + :term blur-property-name + :detail (dm/str blur-value) + :property blur-property-value + :copiable true}]) + (when background-blur-property-value + [:> properties-row* {:key (dm/str "blur-property-" background-blur-property) + :term background-blur-property-name + :detail (dm/str background-blur-value) + :property background-blur-property-value + :copiable true}])])])]) diff --git a/frontend/src/app/main/ui/inspect/styles/style_box.cljs b/frontend/src/app/main/ui/inspect/styles/style_box.cljs index e285a867f6..c106197fa3 100644 --- a/frontend/src/app/main/ui/inspect/styles/style_box.cljs +++ b/frontend/src/app/main/ui/inspect/styles/style_box.cljs @@ -8,6 +8,8 @@ (:require-macros [app.main.style :as stl]) (:require [app.common.data :as d] + [app.config :as cf] + [app.main.features :as features] [app.main.ui.ds.buttons.icon-button :refer [icon-button*]] [app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i] [app.util.clipboard :as clipboard] @@ -16,22 +18,27 @@ (defn- panel->title [type] - (case type - :variant (tr "inspect.tabs.styles.variants-panel") - :token (tr "inspect.tabs.styles.token-panel") - :geometry (tr "inspect.attributes.size") - :fill (tr "labels.fill") - :stroke (tr "labels.stroke") - :text (tr "labels.text") - :blur (tr "labels.blur") - :shadow (tr "labels.shadow") - :layout (tr "labels.layout") - :flex-element "Flex Element" - :grid-element "Grid Element" - :layout-element "Layout Element" - :visibility (tr "labels.visibility") - :svg (tr "labels.svg") - nil)) + (let [render-wasm? (features/use-feature "render-wasm/v1") + bg-blur? (and render-wasm? + (contains? cf/flags :background-blur))] + (case type + :variant (tr "inspect.tabs.styles.variants-panel") + :token (tr "inspect.tabs.styles.token-panel") + :geometry (tr "inspect.attributes.size") + :fill (tr "labels.fill") + :stroke (tr "labels.stroke") + :text (tr "labels.text") + :blur (if bg-blur? + (tr "labels.blur-effects") + (tr "labels.blur")) + :shadow (tr "labels.shadow") + :layout (tr "labels.layout") + :flex-element "Flex Element" + :grid-element "Grid Element" + :layout-element "Layout Element" + :visibility (tr "labels.visibility") + :svg (tr "labels.svg") + nil))) (mf/defc style-box* [{:keys [panel shorthand children]}] diff --git a/frontend/src/app/main/ui/shapes/attrs.cljs b/frontend/src/app/main/ui/shapes/attrs.cljs index bf2642bc19..118812762e 100644 --- a/frontend/src/app/main/ui/shapes/attrs.cljs +++ b/frontend/src/app/main/ui/shapes/attrs.cljs @@ -183,6 +183,7 @@ ([props shape position render-id] (let [shape-fills (get shape :fills) shape-shadow (get shape :shadow) + shape-blur (get shape :blur) svg-attrs (get-svg-props shape render-id) diff --git a/frontend/src/app/main/ui/shapes/custom_stroke.cljs b/frontend/src/app/main/ui/shapes/custom_stroke.cljs index 201673302e..dfb0a5a1b8 100644 --- a/frontend/src/app/main/ui/shapes/custom_stroke.cljs +++ b/frontend/src/app/main/ui/shapes/custom_stroke.cljs @@ -480,7 +480,8 @@ stroke-id (dm/str (dm/fmt "strokes-%-%" prefix shape-id)) - shape-blur (get shape :blur) + shape-blur (get shape :blur) + shape-fills (get shape :fills) shape-shadow (get shape :shadow) shape-strokes (not-empty strokes) @@ -498,6 +499,7 @@ open-path? (and ^boolean (cfh/path-shape? shape) ^boolean (path/shape-with-open-path? shape))] + (when-not ^boolean (cfh/frame-shape? shape) (when (and (some? shape-blur) (not ^boolean (:hidden shape-blur))) diff --git a/frontend/src/app/main/ui/shapes/frame.cljs b/frontend/src/app/main/ui/shapes/frame.cljs index 82acf0a70b..b95c887dd8 100644 --- a/frontend/src/app/main/ui/shapes/frame.cljs +++ b/frontend/src/app/main/ui/shapes/frame.cljs @@ -67,6 +67,7 @@ filter-id-blur (dm/fmt "filter-blur-%" render-id) filter-id-shadows (dm/fmt "filter-shadow-%" render-id) + filter-str-blur (filters/filter-str filter-id-blur (dissoc shape :shadow)) filter-str-shadows (filters/filter-str filter-id-shadows (dissoc shape :blur)) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/blur.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/blur.cljs index 82bd76c1ef..334061eb2e 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/blur.cljs @@ -14,146 +14,305 @@ [app.main.data.workspace.shapes :as dwsh] [app.main.features :as features] [app.main.store :as st] - [app.main.ui.components.numeric-input :refer [numeric-input*]] - [app.main.ui.components.select :refer [select]] [app.main.ui.components.title-bar :refer [title-bar*]] [app.main.ui.ds.buttons.icon-button :refer [icon-button*]] + [app.main.ui.ds.controls.numeric-input :refer [numeric-input*]] + [app.main.ui.ds.controls.select :refer [select*]] [app.main.ui.ds.foundations.assets.icon :as i] - [app.main.ui.icons :as deprecated-icon] + [app.main.ui.ds.tooltip.tooltip :refer [tooltip*]] [app.util.i18n :as i18n :refer [tr]] [rumext.v2 :as mf])) -(def blur-attrs [:blur]) +(def blur-attrs [:blur :background-blur]) -(defn create-blur [] +(defn create-blur [type] (let [id (uuid/next)] {:id id - :type :layer-blur + :type type :value 4 :hidden false})) -(mf/defc blur-menu* [{:keys [ids type values]}] - (let [blur (:blur values) - has-value? (not (nil? blur)) - render-wasm? (features/use-feature "render-wasm/v1") - bg-blur? (and render-wasm? - (contains? cf/flags :background-blur)) +(mf/defc blur-menu-content* + [{:keys [blur-key value change-fn blur-values]}] + (let [render-wasm? (features/use-feature "render-wasm/v1") + bg-blur? (and render-wasm? + (contains? cf/flags :background-blur)) + is-hidden (get value :hidden) + show-more-options* (mf/use-state false) + show-more-options (deref show-more-options*) + toggle-more-options (mf/use-fn #(swap! show-more-options* not)) - state* (mf/use-state {:show-content true - :show-more-options false}) + handle-delete + (mf/use-fn + (mf/deps change-fn blur-key) + (fn [] + (change-fn #(dissoc % blur-key)))) + + handle-toggle-visibility + (mf/use-fn + (mf/deps change-fn blur-key) + (fn [] + (change-fn #(update-in % [blur-key :hidden] not)))) + + handle-change + (mf/use-fn + (mf/deps change-fn blur-key) + (fn [value] + (change-fn #(assoc-in % [blur-key :value] value)))) + + handle-type-change + (mf/use-fn + (mf/deps change-fn value blur-key) + (fn [type] + (let [type-kw (keyword type) + target-key (if (= type-kw :layer-blur) :blur :background-blur)] + (change-fn + (fn [shape] + (cond + ;; mismo tipo + (= blur-key target-key) + shape + + ;; ya existe un blur del tipo destino + (contains? shape target-key) + shape + + ;; blur origen no existe + (not (contains? shape blur-key)) + shape + + :else + (let [blur (get shape blur-key)] + (-> shape + (dissoc blur-key) + (assoc target-key + (assoc blur :type type-kw)))))))))) + + bb-disabled? (and (= 2 (count blur-values)) + (not= blur-key :background-blur)) + lb-disabled? (and (= 2 (count blur-values)) + (not= blur-key :blur)) + label-ref (mf/use-ref nil) + + type-options + [{:value "layer-blur" :disabled lb-disabled? :id "layer-blur" :label (tr "workspace.options.blur-options.layer-blur")} + {:value "background-blur" :disabled bb-disabled? :id "background-blur" :label (tr "workspace.options.blur-options.background-blur")}] + + + background-blur-disabled? + (and (= blur-key :background-blur) + (not bg-blur?)) + + label-text + (cond + (= blur-key :background-blur) + (tr "workspace.options.blur-options.background-blur") + + bg-blur? + (tr "workspace.options.blur-options.layer-blur") + + :else + (tr "labels.blur")) + + label + (mf/html [:span {:aria-labelledby "background-blur-disabled-label" + :ref label-ref + :class (stl/css-case :label true + :disabled-label background-blur-disabled?)} + label-text])] + + [:* + [:div {:class (stl/css-case :first-row true + :hidden is-hidden)} + [:div {:class (stl/css :blur-info) + :data-testid "blur-info"} + [:> icon-button* {:class (stl/css-case :show-more true + :selected show-more-options) + :on-click toggle-more-options + :selected show-more-options + :variant "ghost" + :disabled (or + is-hidden + (and (= blur-key :background-blur) + (= false bg-blur?))) + :aria-label (tr "workspace.options.blur-options.toggle-more-options") + :icon i/menu}] + (cond bg-blur? + [:> select* + {:class (stl/css :blur-type-select) + :default-selected (d/name (:type value)) + :aria-label (tr "workspace.options.blur-options.blur-type-select") + :options type-options + :disabled is-hidden + :on-change handle-type-change}] + background-blur-disabled? + [:> tooltip* + {:trigger-ref label-ref + :id "background-blur-disabled-label" + :class (stl/css :disabled-label-tooltip) + :content (tr "workspace.options.blur-options.disabled-blur-label")} + label] + :else + label)] + + [:div {:class (stl/css :actions)} + [:> icon-button* {:variant "ghost" + :aria-label (tr "workspace.options.blur-options.toggle-blur") + :on-click handle-toggle-visibility + :disabled (and (= blur-key :background-blur) + (= false bg-blur?)) + :tooltip-placement "top-left" + :icon (if (or is-hidden + (and (= blur-key :background-blur) + (= false bg-blur?))) i/hide i/shown)}] + [:> icon-button* {:variant "ghost" + :aria-label (tr "workspace.options.blur-options.remove-blur") + :on-click handle-delete + :tooltip-placement "top-left" + :icon i/remove}]]] + + (when show-more-options + [:div {:class (stl/css :second-row)} + [:> numeric-input* + {:class (stl/css :numeric-input) + :placeholder "--" + :min 0 + :text-icon "value" + :on-change handle-change + :name "blur-value" + :value (:value value)}]])])) + +(defn get-blurs [values] + (cond-> [] + (:blur values) + (conj {:key :blur + :value (:blur values)}) + + (:background-blur values) + (conj {:key :background-blur + :value (:background-blur values)}))) + +(defn- check-props + "A blur-menu specific memoize check function that only checks if + specific values are changed on provided props. This allows pass the + whole shape as values without adding additional rerenders when other + shape properties changes." + [n-props o-props] + (and (identical? (unchecked-get n-props "ids") + (unchecked-get o-props "ids")) + (let [o-vals (unchecked-get o-props "values") + n-vals (unchecked-get n-props "values") + o-blur-values (get o-vals :blur) + n-blur-values (get n-vals :blur) + o-background-blur-values (get o-vals :background-blur) + n-background-blur-values (get n-vals :background-blur)] + (and (identical? o-blur-values n-blur-values) + (identical? o-background-blur-values n-background-blur-values))))) + +(mf/defc blur-menu* + {::mf/wrap [#(mf/memo' % check-props)]} + [{:keys [ids type values]}] + (let [render-wasm? (features/use-feature "render-wasm/v1") + bg-blur? (and render-wasm? + (contains? cf/flags :background-blur)) + + + + blur-values (get-blurs values) + + mixed-state (and (or (= :group type) + (= :multiple type)) + (boolean + (some #(= :multiple (:value %)) blur-values))) + + state* (mf/use-state {:show-content true}) state (deref state*) open? (:show-content state) - more-options? (:show-more-options state) toggle-content (mf/use-fn #(swap! state* update :show-content not)) - toggle-more-options (mf/use-fn #(swap! state* update :show-more-options not)) - hidden? (:hidden blur) - change! (mf/use-fn (mf/deps ids) (fn [update-fn] - (st/emit! (dwsh/update-shapes ids update-fn)))) + (st/emit! (dwsh/update-shapes ids update-fn) + (udw/trigger-bounding-box-cloaking ids)))) + + handle-delete-all + (mf/use-fn + (mf/deps change!) + (fn [] + (change! #(dissoc % :blur :background-blur)))) handle-add (mf/use-fn - (mf/deps change! ids) + (mf/deps change! blur-values) (fn [] - (st/emit! (udw/trigger-bounding-box-cloaking ids)) - (change! #(assoc % :blur (create-blur))))) + (cond + (= 1 (count blur-values)) + (let [existing-key (:key (first blur-values)) + new-key (if (= existing-key :blur) + :background-blur + :blur)] + (change! #(assoc % new-key (create-blur (if (= :blur new-key) + :layer-blur + :background-blur))))) + (= 0 (count blur-values)) + (change! #(assoc % :blur (create-blur :layer-blur)))) + :else + blur-values))] - handle-delete - (mf/use-fn - (mf/deps change! ids) - (fn [] - (st/emit! (udw/trigger-bounding-box-cloaking ids)) - (change! #(dissoc % :blur)))) - - handle-change - (mf/use-fn - (mf/deps change! ids) - (fn [value] - (st/emit! (udw/trigger-bounding-box-cloaking ids)) - (change! #(cond-> % - (not (contains? % :blur)) - (assoc :blur (create-blur)) - - :always - (assoc-in [:blur :value] value))))) - - handle-type-change - (mf/use-fn - (mf/deps change! ids) - (fn [value] - (st/emit! (udw/trigger-bounding-box-cloaking ids)) - (change! #(assoc-in % [:blur :type] (keyword value))))) - - handle-toggle-visibility - (mf/use-fn - (mf/deps change! ids) - (fn [] - (st/emit! (udw/trigger-bounding-box-cloaking ids)) - (change! #(update-in % [:blur :hidden] not)))) - - type-options - (mf/with-memo [bg-blur?] - (cond-> [{:value "layer-blur" :label (tr "workspace.options.blur-options.layer-blur")}] - bg-blur? - (conj {:value "background-blur" :label (tr "workspace.options.blur-options.background-blur")})))] - - [:div {:class (stl/css :element-set)} + [:section {:class (stl/css :element-set) + :hidden (not open?) + :aria-label (if bg-blur? + (tr "labels.blur-effects") + (tr "labels.blur"))} [:div {:class (stl/css :element-title)} - [:> title-bar* {:collapsable has-value? + [:> title-bar* {:collapsable (seq blur-values) :collapsed (not open?) :on-collapsed toggle-content - :title (case type - :multiple (tr "workspace.options.blur-options.title.multiple") - :group (tr "workspace.options.blur-options.title.group") - (tr "workspace.options.blur-options.title")) - :class (stl/css-case :title-spacing-blur (not has-value?))} - (when-not has-value? - [:> icon-button* {:variant "ghost" - :aria-label (tr "workspace.options.blur-options.add-blur") - :on-click handle-add - :icon i/add - :data-testid "add-blur"}])]] - (when (and open? has-value?) - [:div {:class (stl/css :element-set-content)} - [:div {:class (stl/css-case :first-row true - :hidden hidden?)} - [:div {:class (stl/css :blur-info) - :data-testid "blur-info"} - [:button {:class (stl/css-case :show-more true - :selected more-options?) - :on-click toggle-more-options} - deprecated-icon/menu] - (if bg-blur? - [:& select {:class (stl/css :blur-type-select) - :default-value (d/name (:type blur)) - :options type-options - :disabled hidden? - :on-change handle-type-change}] - [:span {:class (stl/css :label)} - (tr "workspace.options.blur-options.title")])] - [:div {:class (stl/css :actions)} - [:> icon-button* {:variant "ghost" - :aria-label (tr "workspace.options.blur-options.toggle-blur") - :on-click handle-toggle-visibility - :icon (if hidden? i/hide i/shown)}] - [:> icon-button* {:variant "ghost" - :aria-label (tr "workspace.options.blur-options.remove-blur") - :on-click handle-delete - :icon i/remove}]]] - (when more-options? - [:div {:class (stl/css :second-row)} - [:label {:class (stl/css :label) - :for "blur-input-sidebar"} - (tr "inspect.attributes.blur.value")] - [:> numeric-input* - {:className (stl/css :numeric-input) - :placeholder "--" - :id "blur-input-sidebar" - :min "0" - :on-change handle-change - :value (:value blur)}]])])])) + :aria-expanded open? + :aria-controls "blur-content" + :title (if bg-blur? + (cond + (= type :multiple) (tr "workspace.options.blur-effects-options.title.multiple") + (= type :group) (tr "workspace.options.blur-effects-options.title.group") + :else (tr "labels.blur-effects")) + (cond + (= type :multiple) (tr "workspace.options.blur-options.title.multiple") + (= type :group) (tr "workspace.options.blur-options.title.group") + :else (tr "labels.blur"))) + :class (stl/css-case :title-spacing-blur (not (seq blur-values)) + :long-title true)} + (when (and (not mixed-state) + (if bg-blur? + (< (count blur-values) 2) + (nil? (:blur values)))) + [:> icon-button* + {:variant "ghost" + :aria-label (tr "workspace.options.blur-options.add-blur") + :on-click handle-add + :icon i/add + :tooltip-placement "top-left" + :data-testid "add-blur"}])]] + (when (and open? (seq blur-values)) + [:div {:class (stl/css :element-set-content) + :hidden (not open?) + :id "blur-content"} + (if mixed-state + [:div {:class (stl/css :first-row)} + [:span {:class (stl/css :mixed-label)} + (tr "labels.mixed-values")] + [:> icon-button* {:variant "ghost" + :aria-label (tr "workspace.options.blur-options.remove-blur") + :on-click handle-delete-all + :tooltip-placement "top-left" + :icon i/remove}]] + + (for [{:keys [key value]} blur-values] + [:> blur-menu-content* + {:key key + :blur-key key + :value value + :blur-values blur-values + :change-fn change!}]))])])) \ No newline at end of file diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/blur.scss b/frontend/src/app/main/ui/workspace/sidebar/options/menus/blur.scss index f3e4f60ba1..d738d59e21 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/blur.scss +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/blur.scss @@ -4,114 +4,130 @@ // // Copyright (c) KALEIDOS INC Sucursal en España SL -@use "refactor/common-refactor.scss" as deprecated; +@use "ds/_borders.scss" as *; +@use "ds/typography.scss" as t; +@use "ds/_sizes.scss" as *; +@use "ds/_utils.scss" as *; @use "../../../sidebar/common/sidebar.scss" as sidebar; -.element-set { - @include sidebar.option-grid-structure; -} - -.element-title { - grid-column: span 8; -} - .title-spacing-blur { - padding-left: deprecated.$s-2; + padding-left: var(--sp-xxs); margin: 0; } -.element-set-content { - @include deprecated.flex-column; +.long-title { + height: auto; +} - margin-bottom: deprecated.$s-8; +.element-set-content { + display: flex; + flex-direction: column; + gap: var(--sp-xs); + margin-bottom: var(--sp-s); } .first-row { @include sidebar.option-grid-structure; +} - .blur-info { - grid-column: span 6; - display: flex; - align-items: center; - gap: deprecated.$s-1; - flex-grow: 1; - border-radius: deprecated.$br-8; - background-color: var(--input-details-color); +.show-more { + background-color: var(--color-background-tertiary); + border: $b-1 solid var(--color-background-tertiary); + border-radius: $br-8 0 0 $br-8; - .show-more { - @extend %button-secondary; - - height: deprecated.$s-32; - width: deprecated.$s-28; - border-radius: deprecated.$br-8 0 0 deprecated.$br-8; - box-sizing: border-box; - border: deprecated.$s-1 solid var(--button-secondary-background-color-rest); - - svg { - @extend %button-icon; - } - - &.selected { - background-color: var(--button-radio-background-color-active); - - svg { - stroke: var(--button-radio-foreground-color-active); - } - } - } - - .label { - @include deprecated.body-small-typography; - - flex-grow: 1; - display: flex; - align-items: center; - height: deprecated.$s-32; - padding: 0 deprecated.$s-8; - border-radius: 0 deprecated.$br-8 deprecated.$br-8 0; - background-color: var(--input-background-color); - color: var(--menu-foreground-color); - box-sizing: border-box; - border: deprecated.$s-1 solid var(--input-border-color); - } - - .blur-type-select { - flex-grow: 1; - border-radius: 0 deprecated.$br-8 deprecated.$br-8 0; - } + &:hover { + background-color: var(--color-background-quaternary); + border: $b-1 solid var(--color-background-quaternary); } - .actions { - @include deprecated.flex-row; + &:disabled { + background-color: var(--color-background-primary); + border-block-start: $b-1 solid var(--color-background-quaternary); + border-block-end: $b-1 solid var(--color-background-quaternary); + border-inline-start: $b-1 solid var(--color-background-quaternary); } +} - &.hidden { - .blur-info { - @include deprecated.hidden-element; +.blur-info { + grid-column: span 6; + display: flex; + align-items: center; + flex-grow: 1; + gap: 1px; + border-radius: $br-8; + background-color: var(--color-background-primary); +} - .show-more { - @include deprecated.hidden-element; +.mixed-label { + @include t.use-typography("body-small"); - border: deprecated.$s-1 solid var(--input-border-color-disabled); - } + grid-column: span 7; + gap: 1px; + border-radius: $br-8; + display: flex; + align-items: center; + flex-grow: 1; + height: $sz-32; + padding: 0 var(--sp-s); + background-color: var(--color-background-tertiary); + border: $b-1 solid var(--color-background-tertiary); + color: var(--color-foreground-primary); + box-sizing: border-box; +} - .label { - @include deprecated.hidden-element; +.disabled-label-tooltip { + flex-grow: 1; +} - border: deprecated.$s-1 solid var(--input-border-color-disabled); - } - } - } +.label { + @include t.use-typography("body-small"); + + display: flex; + align-items: center; + flex-grow: 1; + height: $sz-32; + padding: 0 var(--sp-s); + border-radius: 0 $br-8 $br-8 0; + background-color: var(--color-background-tertiary); + border: $b-1 solid var(--color-background-tertiary); + color: var(--color-foreground-primary); + box-sizing: border-box; +} + +.blur-type-select { + flex-grow: 1; + border-radius: 0 $br-8 $br-8 0; +} + +.actions { + display: flex; + align-items: center; + gap: var(--sp-xs); +} + +.hidden .blur-info, +.hidden .blur-info .label { + cursor: default; + pointer-events: none; + box-sizing: border-box; + color: var(--color-foreground-secondary); + stroke: var(--color-foreground-secondary); + background-color: transparent; +} + +.hidden .blur-info .label { + border: $b-1 solid var(--color-background-quaternary); } .second-row { - @extend %input-element; - @include deprecated.body-small-typography; - - width: deprecated.$s-92; - - .label { - padding-left: deprecated.$s-8; - width: deprecated.$s-60; - } + width: var(--three-columns-width); +} + +.disabled-label { + color: var(--color-foreground-secondary); + background-color: var(--color-background-primary); + border-top: $b-1 solid var(--color-background-quaternary); + border-bottom: $b-1 solid var(--color-background-quaternary); + border-right: $b-1 solid var(--color-background-quaternary); + border-left: $b-1 solid transparent; } diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs index dcc29cd0a8..701ba4d134 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs @@ -451,5 +451,5 @@ [:> icon-button* {:variant "ghost" :aria-label (tr "settings.select-this-color") :on-click handle-select - :tooltip-position "top-left" + :tooltip-placement "top-left" :icon i/move}])])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs index f598da0abc..b5259e0e5d 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs @@ -135,7 +135,7 @@ [:> shadow-menu* {:ids ids :values (get shape :shadow)}] [:> blur-menu* {:ids ids - :values (select-keys shape [:blur])}] + :values (select-keys shape [:blur :background-blur])}] [:> exports-menu* {:type type :ids ids diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs index d006be2d70..ed77629c22 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs @@ -132,7 +132,7 @@ :applied-tokens applied-tokens}] [:> shadow-menu* {:ids ids :values (get shape :shadow)}] [:> blur-menu* {:ids ids - :values (select-keys shape [:blur])}] + :values (select-keys shape [:blur :background-blur])}] [:> svg-attrs-menu* {:ids ids :values (select-keys shape [:svg-attrs])}] [:> exports-menu* {:type type diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs index 059e92b1d3..a57cb7c853 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs @@ -159,7 +159,7 @@ :libraries libraries}] [:> shadow-menu* {:ids ids :values (get shape :shadow)}] [:> blur-menu* {:ids ids - :values (select-keys shape [:blur])}] + :values (select-keys shape [:blur :background-blur])}] [:> frame-grid* {:shape shape}] [:> exports-menu* {:type shape-type :ids ids diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs index 24b5b7afa3..5e99bcb51a 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs @@ -48,6 +48,7 @@ :fill :shape :shadow :shape :blur :shape + :background-blur :shape :stroke :shape :text :children :exports :shape @@ -61,6 +62,7 @@ :fill :children :shadow :shape :blur :shape + :background-blur :shape :stroke :children :text :children :exports :shape @@ -74,6 +76,7 @@ :fill :shape :shadow :shape :blur :shape + :background-blur :shape :stroke :shape :text :ignore :exports :shape @@ -87,6 +90,7 @@ :fill :text :shadow :shape :blur :shape + :background-blur :shape :stroke :shape :text :text :exports :shape @@ -100,6 +104,7 @@ :fill :ignore :shadow :shape :blur :shape + :background-blur :shape :stroke :ignore :text :ignore :exports :shape @@ -113,6 +118,7 @@ :fill :shape :shadow :shape :blur :shape + :background-blur :shape :stroke :shape :text :ignore :exports :shape @@ -126,6 +132,7 @@ :fill :shape :shadow :shape :blur :shape + :background-blur :shape :stroke :shape :text :ignore :exports :shape @@ -139,6 +146,7 @@ :fill :shape :shadow :shape :blur :shape + :background-blur :shape :stroke :shape :text :ignore :exports :shape @@ -152,6 +160,7 @@ :fill :shape :shadow :shape :blur :shape + :background-blur :shape :stroke :shape :text :ignore :exports :shape diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs index 6105a1e69b..0ba8e24a76 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs @@ -133,7 +133,7 @@ :applied-tokens applied-tokens}] [:> shadow-menu* {:ids ids :values (get shape :shadow)}] [:> blur-menu* {:ids ids - :values (select-keys shape [:blur])}] + :values (select-keys shape [:blur :background-blur])}] [:> svg-attrs-menu* {:ids ids :values (select-keys shape [:svg-attrs])}] [:> exports-menu* {:type type diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs index 3e1162cd8a..fb34713817 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs @@ -134,7 +134,7 @@ [:> shadow-menu* {:ids ids :values (get shape :shadow)}] [:> blur-menu* {:ids ids - :values (select-keys shape [:blur])}] + :values (select-keys shape [:blur :background-blur])}] [:> svg-attrs-menu* {:ids ids :values (select-keys shape [:svg-attrs])}] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs index 7e1f9f8497..929ddda4e4 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs @@ -201,7 +201,7 @@ [:> shadow-menu* {:ids ids :values (get shape :shadow)}] [:> blur-menu* {:ids ids - :values (select-keys shape [:blur])}] + :values (select-keys shape [:blur :background-blur])}] [:> svg-attrs-menu* {:ids ids :values (select-keys shape [:svg-attrs])}] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs index 98c8f66bb8..34d8614b1c 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs @@ -211,7 +211,7 @@ [:> blur-menu* {:ids ids - :values (select-keys shape [:blur])}] + :values (select-keys shape [:blur :background-blur])}] [:> exports-menu* {:type type :ids ids diff --git a/frontend/src/app/plugins/format.cljs b/frontend/src/app/plugins/format.cljs index 58bb8c0182..c0b556bdab 100644 --- a/frontend/src/app/plugins/format.cljs +++ b/frontend/src/app/plugins/format.cljs @@ -232,16 +232,14 @@ ;; export interface Blur { ;; id?: string; -;; type?: 'layer-blur'; ;; value?: number; ;; hidden?: boolean; ;; } (defn format-blur - [{:keys [id type value hidden] :as blur}] + [{:keys [id value hidden] :as blur}] (when (some? blur) (obj/without-empty #js {:id (format-id id) - :type (format-key type) :value value :hidden hidden}))) diff --git a/frontend/src/app/plugins/parser.cljs b/frontend/src/app/plugins/parser.cljs index ca006dbf4c..3d6b481b50 100644 --- a/frontend/src/app/plugins/parser.cljs +++ b/frontend/src/app/plugins/parser.cljs @@ -237,7 +237,6 @@ ;; export interface Blur { ;; id?: string; -;; type?: 'layer-blur'; ;; value?: number; ;; hidden?: boolean; ;; } @@ -246,7 +245,6 @@ (when (some? blur) (d/without-nils {:id (-> (obj/get blur "id") parse-id) - :type (-> (obj/get blur "type") parse-keyword) :value (obj/get blur "value") :hidden (obj/get blur "hidden")}))) diff --git a/frontend/src/app/plugins/shape.cljs b/frontend/src/app/plugins/shape.cljs index 70c75fe43f..cbe2b22831 100644 --- a/frontend/src/app/plugins/shape.cljs +++ b/frontend/src/app/plugins/shape.cljs @@ -24,6 +24,7 @@ [app.common.types.grid :as ctg] [app.common.types.path :as path] [app.common.types.shape :as cts] + [app.common.types.shape.background-blur :as ctsbb] [app.common.types.shape.blur :as ctsb] [app.common.types.shape.export :as ctse] [app.common.types.shape.interactions :as ctsi] @@ -176,6 +177,15 @@ :hidden false} blur)) +(defn- background-blur-defaults + [blur] + (d/patch-object + {:id (uuid/next) + :type :background-blur + :value 4 + :hidden false} + blur)) + (defn commit-fills! [plugin-id ^js self value] (let [shape (u/proxy->shape self) @@ -499,7 +509,8 @@ (if (nil? value) (st/emit! (dwsh/update-shapes [id] #(dissoc % :blur))) (let [id (obj/get self "$id") - value (blur-defaults (parser/parse-blur value))] + value (blur-defaults (parser/parse-blur value)) + value (assoc value :type :layer-blur)] (cond (not (sm/validate ctsb/schema:blur value)) (u/not-valid plugin-id :blur value) @@ -510,6 +521,26 @@ :else (st/emit! (dwsh/update-shapes [id] #(assoc % :blur value)))))))} + :background-blur + {:this true + :get #(-> % u/proxy->shape :background-blur format/format-blur) + :set + (fn [self value] + (if (nil? value) + (st/emit! (dwsh/update-shapes [id] #(dissoc % :background-blur))) + (let [id (obj/get self "$id") + value (background-blur-defaults (parser/parse-blur value)) + value (assoc value :type :background-blur)] + (cond + (not (sm/validate ctsbb/schema:background-blur value)) + (u/not-valid plugin-id :background-blur value) + + (not (r/check-permission plugin-id "content:write")) + (u/not-valid plugin-id :background-blur "Plugin doesn't have 'content:write' permission") + + :else + (st/emit! (dwsh/update-shapes [id] #(assoc % :background-blur value)))))))} + :exports {:this true :get #(-> % u/proxy->shape :exports format/format-exports) diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 1b609a53f3..a7e03e627e 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -959,12 +959,21 @@ (defn set-shape-blur [blur] - (if (some? blur) - (let [type (-> blur :type sr/translate-blur-type) - hidden (:hidden blur) - value (:value blur)] - (h/call wasm/internal-module "_set_shape_blur" type hidden value)) - (h/call wasm/internal-module "_clear_shape_blur"))) + (let [type (sr/translate-blur-type :layer-blur)] + (if (some? blur) + (let [hidden (:hidden blur) + value (:value blur)] + (h/call wasm/internal-module "_set_shape_blur" type hidden value)) + (h/call wasm/internal-module "_clear_shape_blur" type)))) + +(defn set-shape-background-blur + [background-blur] + (let [type (sr/translate-blur-type :background-blur)] + (if (some? background-blur) + (let [hidden (:hidden background-blur) + value (:value background-blur)] + (h/call wasm/internal-module "_set_shape_blur" type hidden value)) + (h/call wasm/internal-module "_clear_shape_blur" type)))) (defn set-shape-corners [corners] @@ -1346,6 +1355,7 @@ bool-type (get shape :bool-type) grow-type (get shape :grow-type) blur (get shape :blur) + background-blur (get shape :background-blur) svg-attrs (get shape :svg-attrs) shadows (get shape :shadow)] @@ -1354,6 +1364,7 @@ ;; Remaining properties that need separate calls (variable-length or conditional) (set-shape-children children) (set-shape-blur blur) + (set-shape-background-blur background-blur) (when (= type :group) (set-masked (boolean masked))) (when (= type :bool) diff --git a/frontend/src/app/render_wasm/shape.cljs b/frontend/src/app/render_wasm/shape.cljs index 83a58a2499..feed61d674 100644 --- a/frontend/src/app/render_wasm/shape.cljs +++ b/frontend/src/app/render_wasm/shape.cljs @@ -187,6 +187,9 @@ :blur (api/set-shape-blur v) + :background-blur + (api/set-shape-background-blur v) + :shadow (api/set-shape-shadows v) @@ -230,6 +233,7 @@ ;; Always update fills/blur/shadow to clear previous state if filters disappear (api/set-shape-fills id (:fills shape) false) (api/set-shape-blur (:blur shape)) + (api/set-shape-background-blur (:background-blur shape)) (api/set-shape-shadows (:shadow shape))) :masked-group diff --git a/frontend/src/app/render_wasm/wasm.cljs b/frontend/src/app/render_wasm/wasm.cljs index 93c26567ab..cf394896ed 100644 --- a/frontend/src/app/render_wasm/wasm.cljs +++ b/frontend/src/app/render_wasm/wasm.cljs @@ -45,7 +45,6 @@ (set! context-initialized? false) (reset! context-lost? false)) - (defonce serializers #js {:blur-type shared/RawBlurType :blend-mode shared/RawBlendMode diff --git a/frontend/src/app/util/code_gen/style_css_formats.cljs b/frontend/src/app/util/code_gen/style_css_formats.cljs index cfeb55f9e1..9a650f7528 100644 --- a/frontend/src/app/util/code_gen/style_css_formats.cljs +++ b/frontend/src/app/util/code_gen/style_css_formats.cljs @@ -38,7 +38,7 @@ :border-color :border-color :box-shadow :shadows :filter :blur - :backdrop-filter :blur + :backdrop-filter :background-blur :gap :size-array :row-gap :size-array :column-gap :size-array @@ -202,5 +202,6 @@ :tracks (format-tracks value) :shadows (format-shadow value options) :blur (format-blur value) + :background-blur (format-blur value) :matrix (format-matrix value) (if (keyword? value) (d/name value) value)))) diff --git a/frontend/src/app/util/code_gen/style_css_values.cljs b/frontend/src/app/util/code_gen/style_css_values.cljs index 044ca763c5..9bd9b787f3 100644 --- a/frontend/src/app/util/code_gen/style_css_values.cljs +++ b/frontend/src/app/util/code_gen/style_css_values.cljs @@ -256,14 +256,12 @@ (defn- get-filter [shape] (when-not (cgc/svg-markup? shape) - (when (= :layer-blur (get-in shape [:blur :type])) - (get-in shape [:blur :value])))) + (get-in shape [:blur :value]))) (defn- get-backdrop-filter [shape] (when-not (cgc/svg-markup? shape) - (when (= :background-blur (get-in shape [:blur :type])) - (get-in shape [:blur :value])))) + (get-in shape [:background-blur :value]))) (defn- get-display [shape] diff --git a/frontend/test/frontend_tests/plugins/context_shapes_test.cljs b/frontend/test/frontend_tests/plugins/context_shapes_test.cljs index 26192557f7..584121eea6 100644 --- a/frontend/test/frontend_tests/plugins/context_shapes_test.cljs +++ b/frontend/test/frontend_tests/plugins/context_shapes_test.cljs @@ -190,7 +190,6 @@ (t/testing " - blur" (set! (.-blur shape) #js {:value 10}) - (t/is (= (-> (. shape -blur) (aget "type")) "layer-blur")) (t/is (= (-> (. shape -blur) (aget "value")) 10)) (t/is (= (-> (. shape -blur) (aget "hidden")) false)) (let [id (-> (. shape -blur) (aget "id") uuid/uuid)] diff --git a/frontend/translations/en.po b/frontend/translations/en.po index a3559f9c1d..9a44f421f6 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -2755,6 +2755,10 @@ msgstr "Bad Gateway" msgid "labels.blur" msgstr "Blur" +#: src/app/main/ui/inspect/styles/style_box.cljs +msgid "labels.blur-effects" +msgstr "Blur effects" + #: src/app/main/data/common.cljs:119, src/app/main/ui/dashboard/change_owner.cljs:67, src/app/main/ui/dashboard/change_owner.cljs:174, src/app/main/ui/dashboard/import.cljs:543, src/app/main/ui/dashboard/team.cljs:866, src/app/main/ui/dashboard/team.cljs:1345, src/app/main/ui/delete_shared.cljs:36, src/app/main/ui/exports/assets.cljs:163, src/app/main/ui/exports/files.cljs:167, src/app/main/ui/settings/integrations.cljs:228, src/app/main/ui/viewer/share_link.cljs:204, src/app/main/ui/workspace/sidebar/assets/groups.cljs:178, src/app/main/ui/workspace/tokens/export/modal.cljs:43, src/app/main/ui/workspace/tokens/import/modal.cljs:268, src/app/main/ui/workspace/tokens/import_from_library.cljs:88, src/app/main/ui/workspace/tokens/management/forms/generic_form.cljs:364, src/app/main/ui/workspace/tokens/management/forms/rename_node_modal.cljs:73, src/app/main/ui/workspace/tokens/settings/menu.cljs:104, src/app/main/ui/workspace/tokens/themes/create_modal.cljs:242 msgid "labels.cancel" msgstr "Cancel" @@ -7177,10 +7181,30 @@ msgstr "Layer blur" msgid "workspace.options.blur-options.remove-blur" msgstr "Remove blur" +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-options.toggle-more-options" +msgstr "Show/hide more options" + +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-options.disabled-blur-label" +msgstr "Background blur is only supported in the new render. Switch to the new render in the Preferences menu to use this effect" + #: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs:113, src/app/main/ui/workspace/sidebar/options/menus/blur.cljs:138 msgid "workspace.options.blur-options.title" msgstr "Blur" +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-effects-options.title" +msgstr "Blur effects" + +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-effects-options.title.group" +msgstr "Group blur effects" + +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-effects-options.title.multiple" +msgstr "Selection blur effects" + #: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs:112 msgid "workspace.options.blur-options.title.group" msgstr "Group blur" @@ -7193,6 +7217,10 @@ msgstr "Selection blur" msgid "workspace.options.blur-options.toggle-blur" msgstr "Toggle blur" +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-options.blur-type-select" +msgstr "Blur type select" + #: src/app/main/ui/workspace/sidebar/options/page.cljs:60, src/app/main/ui/workspace/sidebar/options/page.cljs:68 msgid "workspace.options.canvas-background" msgstr "Canvas background" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 35393cf0f7..9270d93d58 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -2685,6 +2685,10 @@ msgstr "Error del servidor (Bad Gateway)" msgid "labels.blur" msgstr "Desenfoque" +#: src/app/main/ui/inspect/styles/style_box.cljs +msgid "labels.blur-effects" +msgstr "Efectos de desenfoque" + #: src/app/main/data/common.cljs:119, src/app/main/ui/dashboard/change_owner.cljs:67, src/app/main/ui/dashboard/change_owner.cljs:174, src/app/main/ui/dashboard/import.cljs:543, src/app/main/ui/dashboard/team.cljs:866, src/app/main/ui/dashboard/team.cljs:1345, src/app/main/ui/delete_shared.cljs:36, src/app/main/ui/exports/assets.cljs:163, src/app/main/ui/exports/files.cljs:167, src/app/main/ui/settings/integrations.cljs:228, src/app/main/ui/viewer/share_link.cljs:204, src/app/main/ui/workspace/sidebar/assets/groups.cljs:178, src/app/main/ui/workspace/tokens/export/modal.cljs:43, src/app/main/ui/workspace/tokens/import/modal.cljs:268, src/app/main/ui/workspace/tokens/import_from_library.cljs:88, src/app/main/ui/workspace/tokens/management/forms/generic_form.cljs:364, src/app/main/ui/workspace/tokens/management/forms/rename_node_modal.cljs:73, src/app/main/ui/workspace/tokens/settings/menu.cljs:104, src/app/main/ui/workspace/tokens/themes/create_modal.cljs:242 msgid "labels.cancel" msgstr "Cancelar" @@ -7004,10 +7008,30 @@ msgstr "Desenfoque de capa" msgid "workspace.options.blur-options.remove-blur" msgstr "Eliminar desenfoque" +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-options.toggle-more-options" +msgstr "Mostrar/ocultar más opciones" + +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-options.disabled-blur-label" +msgstr "El desenfoque de fondo (Background Blur) solo está disponible en el nuevo motor de renderizado. Actívalo desde el menú de Preferencias para utilizar este efecto." + #: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs:113, src/app/main/ui/workspace/sidebar/options/menus/blur.cljs:138 msgid "workspace.options.blur-options.title" msgstr "Desenfoque" +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-effects-options.title" +msgstr "Efectos de desenfoque" + +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-effects-options.title.group" +msgstr "Efectos de desenfoque del grupo" + +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-effects-options.title.multiple" +msgstr "Efectos de desenfoque de la selección" + #: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs:112 msgid "workspace.options.blur-options.title.group" msgstr "Desenfoque del grupo" @@ -7020,6 +7044,10 @@ msgstr "Desenfoque de la selección" msgid "workspace.options.blur-options.toggle-blur" msgstr "Mostrar/ocultar desenfoque" +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-options.blur-type-select" +msgstr "Selector de tipo de desenfoque" + #: src/app/main/ui/workspace/sidebar/options/page.cljs:60, src/app/main/ui/workspace/sidebar/options/page.cljs:68 msgid "workspace.options.canvas-background" msgstr "Color de fondo" diff --git a/plugins/libs/plugin-types/index.d.ts b/plugins/libs/plugin-types/index.d.ts index 18d8eafcfa..c283c6f808 100644 --- a/plugins/libs/plugin-types/index.d.ts +++ b/plugins/libs/plugin-types/index.d.ts @@ -194,11 +194,6 @@ export interface Blur { * The optional unique identifier for the blur effect. */ id?: string; - /** - * The optional type of the blur effect. - * Currently, only 'layer-blur' is supported. - */ - type?: 'layer-blur'; /** * The optional intensity value of the blur effect. */ @@ -3717,6 +3712,13 @@ export interface ShapeBase extends PluginData { */ blur?: Blur; + /** + * The background blur effect applied to the shape. + * Background blur creates a blur effect on the content behind the shape, + * rather than on the shape's own content. + */ + backgroundBlur?: Blur; + /** * The export settings of the shape. */ diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index c524f21001..68b0b96453 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -649,10 +649,7 @@ impl RenderState { { return; } - let blur = match shape - .blur - .filter(|b| !b.hidden && b.blur_type == BlurType::BackgroundBlur) - { + let blur = match shape.visible_background_blur() { Some(blur) => blur, None => return, }; @@ -1208,6 +1205,7 @@ impl RenderState { && parent_shadows.is_none() && !shape.needs_layer() && shape.blur.is_none() + && shape.background_blur.is_none() && !has_inherited_blur && shape.shadows.is_empty() && shape.transform.is_identity() @@ -1317,20 +1315,9 @@ impl RenderState { // We don't want to change the value in the global state let mut shape: Cow = Cow::Borrowed(shape); - // Remove background blur from the shape so it doesn't get processed - // as a layer blur. The actual rendering is done before the save_layer - // in render_background_blur() so it's independent of shape opacity. - if !skip_effects - && apply_to_current_surface - && fills_surface_id == SurfaceId::Fills - && !matches!(shape.shape_type, Type::Text(_)) - && !matches!(shape.shape_type, Type::SVGRaw(_)) - && shape - .blur - .is_some_and(|b| !b.hidden && b.blur_type == BlurType::BackgroundBlur) - { - shape.to_mut().set_blur(None); - } + // Background blur is stored separately (shape.background_blur) and is + // rendered before the save_layer in render_background_blur(), so here + // shape.blur only ever holds a layer blur. let frame_has_blur = Self::frame_clip_layer_blur(&shape).is_some(); let shape_has_blur = shape.blur.is_some(); @@ -2838,6 +2825,7 @@ impl RenderState { plain_shape_mut.clear_shadows(); plain_shape_mut.blur = None; + plain_shape_mut.background_blur = None; // Shadow rendering uses a single render_shape call with no render_shape_exit, // so strokes must be drawn here. Disable clip_content to avoid skip_strokes @@ -3641,10 +3629,8 @@ impl RenderState { // assigned to this tile) because the blur snapshots Current // which must contain the shapes behind it. let tile_has_bg_blur = ids.iter().any(|id| { - tree.get(id).is_some_and(|s| { - s.blur - .is_some_and(|b| !b.hidden && b.blur_type == BlurType::BackgroundBlur) - }) + tree.get(id) + .is_some_and(|s| s.visible_background_blur().is_some()) }); // We only need first level shapes, in the same order as the parent node. diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index d651e3f457..95c704ceff 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -188,6 +188,7 @@ pub struct Shape { pub blend_mode: BlendMode, pub vertical_align: VerticalAlign, pub blur: Option, + pub background_blur: Option, pub opacity: f32, pub hidden: bool, pub svg: Option, @@ -291,6 +292,7 @@ impl Shape { opacity: 1., hidden: false, blur: None, + background_blur: None, svg: None, svg_attrs: None, shadows: Vec::with_capacity(1), @@ -314,6 +316,10 @@ impl Shape { blur.scale_content(value); } + if let Some(background_blur) = self.background_blur.as_mut() { + background_blur.scale_content(value); + } + self.layout_item .iter_mut() .for_each(|i| i.scale_content(value)); @@ -631,6 +637,15 @@ impl Shape { self.blur = blur; } + pub fn set_background_blur(&mut self, blur: Option) { + self.invalidate_extrect(); + self.background_blur = blur; + } + + pub fn visible_background_blur(&self) -> Option { + self.background_blur.filter(|blur| !blur.hidden) + } + pub fn add_child(&mut self, id: Uuid) { self.children.push(id); } @@ -1421,6 +1436,7 @@ impl Shape { } self.blur.is_none() + && self.background_blur.is_none() && self.shadows.is_empty() && (self.opacity - 1.0).abs() <= 1e-4 && self.blend_mode().0 == skia::BlendMode::SrcOver @@ -1665,7 +1681,7 @@ impl Shape { return false; } - if self.blur.is_some() { + if self.blur.is_some() || self.background_blur.is_some() { return false; } @@ -1805,6 +1821,38 @@ mod tests { ) } + #[test] + fn layer_blur_and_background_blur_can_coexist() { + let mut shape = any_shape(); + + let layer_blur = Blur::new(BlurType::LayerBlur, false, 4.0); + let background_blur = Blur::new(BlurType::BackgroundBlur, false, 8.0); + + shape.set_blur(Some(layer_blur)); + shape.set_background_blur(Some(background_blur)); + + assert_eq!(shape.blur, Some(layer_blur)); + assert_eq!(shape.background_blur, Some(background_blur)); + assert_eq!(shape.visible_background_blur(), Some(background_blur)); + + // Clearing one type must not affect the other. + shape.set_blur(None); + assert_eq!(shape.blur, None); + assert_eq!(shape.background_blur, Some(background_blur)); + + shape.set_blur(Some(layer_blur)); + shape.set_background_blur(None); + assert_eq!(shape.blur, Some(layer_blur)); + assert_eq!(shape.background_blur, None); + } + + #[test] + fn hidden_background_blur_is_not_visible() { + let mut shape = any_shape(); + shape.set_background_blur(Some(Blur::new(BlurType::BackgroundBlur, true, 8.0))); + assert_eq!(shape.visible_background_blur(), None); + } + #[test] fn test_set_corners() { let mut shape = any_shape(); diff --git a/render-wasm/src/wasm/blurs.rs b/render-wasm/src/wasm/blurs.rs index 022439ab44..1fcd8448bd 100644 --- a/render-wasm/src/wasm/blurs.rs +++ b/render-wasm/src/wasm/blurs.rs @@ -29,14 +29,21 @@ impl From for BlurType { #[no_mangle] pub extern "C" fn set_shape_blur(blur_type: u8, hidden: bool, value: f32) { with_current_shape_mut!(state, |shape: &mut Shape| { - let blur_type = RawBlurType::from(blur_type); - shape.set_blur(Some(Blur::new(blur_type.into(), hidden, value))); + let blur_type: BlurType = RawBlurType::from(blur_type).into(); + let blur = Some(Blur::new(blur_type, hidden, value)); + match blur_type { + BlurType::LayerBlur => shape.set_blur(blur), + BlurType::BackgroundBlur => shape.set_background_blur(blur), + } }); } #[no_mangle] -pub extern "C" fn clear_shape_blur() { +pub extern "C" fn clear_shape_blur(blur_type: u8) { with_current_shape_mut!(state, |shape: &mut Shape| { - shape.set_blur(None); + match RawBlurType::from(blur_type).into() { + BlurType::LayerBlur => shape.set_blur(None), + BlurType::BackgroundBlur => shape.set_background_blur(None), + } }); }