Block Editor: Reflect inherited Global Styles values in block inspector controls - #77894
Conversation
|
Thanks for the early feedback @ramonjd, appreciate it 🙇
This is on the cards to investigate but ensuring we can accurately trace where the inheritance comes from and then also pass that data along to the tooltip being injected into the DOM was another. So I left that to follow-up iterations that I'm in the process of exploring. |
There was a problem hiding this comment.
I was playing around with this and noticed how, the underlined/purple text now appears everywhere. It's the default state to have your style inherited from somewhere.
What's good about the bullet icon for overridden styles
is that the default shows nothing special, and it's the override that changes up the UI. This inverts that. Now the default behaviour looks special and the override looks less special. You could just have a tooltip and no visual style change, but then it becomes very hard to discover.
What I'm imagining is that if the hit box for the tooltip was much bigger, like the entire panel was a hitbox, then any hover over the panel would show the detailed info.
This works with a mouse which isn't ideal. The panels which have a click which expands another side panel are better for showing extra info.
I'm sure you have plenty of UI proposals to choose from :)
| helper returns `{ className, title }` to spread onto the wrapping | ||
| `ToolsPanelItem` (or another wrapping element). The class hook drives the | ||
| label-level visual treatment described above; the `title` attribute carries | ||
| the i18n string `__( 'Inherited from Global Styles' )` for native browser |
There was a problem hiding this comment.
The tooltip says things other than "Inherited from Global Styles" too, so I think just needs updated to "e.g. Inherited from ..."
There was a problem hiding this comment.
Thanks, I missed going back over all this when I iterated on the PoC adding the style breadcrumbs to the tooltip.
|
Thanks so much for the thoughtful review and feedbacjk @p-jackson 🙇
For the controls highlighted, you're right there are values inherited, in this case the theme's theme.json file acting as the base for Global Styles. So the purple and underlined labels are "correct" in that regard. Whether it is "too much", that is a question I might direct to @jameskoster and his expertise. For now it is inline with the original design issue and recent feedback.
Initially, I looked at something like this too but wrapping more than the label meant that the tooltip there swallowed all tooltips on subcomponents which are as or more important to end users. So I deliberately kept the scope for this narrow. |
|
Perhaps the underline could be more subtle ( |
9bfb3d7 to
9313738
Compare
|
Size Change: +11.6 kB (+0.15%) Total Size: 7.74 MB 📦 View Changed
|
|
Quick update: I'll be away for a little while but I'll address any feedback collected over the next two weeks as well as the failing tests when i'm back. It is worth keeping in mind this is a proof of concept at this stage and the code and approach hasn't yet been polished to a production ready state. That said, please don't be shy in leaving feedback, especially on the design and UX side of things. |
|
I think I'd lean toward omitting the purple colorisation (we can easily add it back later if the underline proves inadequate). Not a strong feeling though. |
|
Thanks for the continued design direction here. I'll address the current feedback in the next couple of days once I wrangle the new wave of conflicts after other recent global styles projects landed. |
9313738 to
68fda90
Compare
|
It's been a while coming but I think I have this successfully rebased onto trunk. The state-based / responsive styling work that landed on trunk reshaped basically every panel and hook this PR touches, so just about all of the old code was in conflict. As this PR stands now, the inherited global styles display has been relayed and updated on top of trunk and the state/responsive styles feature. A few notes:
Unit and e2es are green locally but treat this as an updated PoC. With as much conflict as the previous working version had, this needs proper manual testing to confirm the inherited value UX actually makes sense across every panel and every state. I'll continue to give this some testing and polish before flagging for review in the near future. |
68fda90 to
1f96f54
Compare
|
Flaky tests detected in 8cb4172. 🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/29720648963
|
1f96f54 to
93fe5df
Compare
|
At the very least, let's try this Joen's suggestion as soon as possible. We still have some time until Beta 3. |
|
There was a conflict while trying to cherry-pick the commit to the wp/7.1 branch. Please resolve the conflict manually and create a PR to the wp/7.1 branch. PRs to wp/7.1 are similar to PRs to trunk, but you should base your PR on the wp/7.1 branch instead of trunk. |
|
Manual backport PR for |
|
This PR was backported to |
I tried to implement this new behaviour. #80506 Concurrently, I plan to highlight this feature in the Gutenberg 23.6 release post. The content of the article is written under the assumption that #80506 will be backported to the Gutenberg 23.6 release. |
|
Following up on yesterday's mockup with an update on the style inheritance symbolism. We have good tech landed, and a good PR followup ready for impriving it further. That PR uses a diamond symbolism for "inherited" and a dot for "local". It can work and move forward. The following meditation looks a bit further, and is based on partial discussions I have had with @fcoveram about how to clarify and elevate this further. The goal is to create a shared language to help delineate local/inherited, but also source/instance. The idea: embrace the diamond as the symbol for both:
This idea is to be simple in the symbolism:
This is the distillation of the visual language, and it should translate well to the style context:
I'm deliberately being minimalist here, because I know that Francisco has instincts that can elevate this further. So I'm carving out a space for him to expand upon this. That's the main reason I'm using shape only for now, because I know there are incoming thoughts on enhancing with colour as well. It's a minimalist starting point: one shape, filled or not. But the perspective of the symbolism can still be considered, and its vernacular where it can get applied:
The vernacular would differentiate the concepts: local/inherited for styles and source/instance for patterns, but the relationship is the same: filled means authored here, outline means reflected from elsewhere. No action item on patterns today, but the goal is to create a shared language with potent silhouette. One that remains the same whether styled or unstyled, and yet is clearly distinguishable between states. That's the main reason for replacing the dot: inheritance doesn't have to stop here: if a font size is set on a group, a paragraph inside will inherit it. A unified symbol with filled/unfilled state differentiation, ◆/◇, retains the singular language. To say again, this deliberately does not preclude further improvements, including adding colour. or otherwise. My hope is also it's a small fix we can still ship in 7.1. Some details:
SVG if helpful: To note, I don't think of that SVG as something to add the the icon library, but rather a local component that lives with this tech. Let me know what you think. |
Design feedback on #77894 asks for one silhouette across the whole inheritance language rather than two unrelated shapes: a diamond, filled when the value is defined at the level you are interacting with, outline when it comes from somewhere else. The same pairing is intended to carry over to synced patterns (source vs. instance) later. Render the diamond as a local SVG child of the indicator button and drop the ::before pseudo-elements that drew the purple rhombus and the blue reset dot. Because fill alone now separates the two states, the symbol uses a single neutral color and no longer tints itself with --wp-block-synced-color. The now-unstyled has-local-override class goes with it. Co-Authored-By: Claude <noreply@anthropic.com>
This PR should at least ship with Gutenberg 23.6. However, looking at #80506, I believe there's still room for discussion on how the UI should be handled. For now, I will not highlight this feature in the Gutenberg 23.6 release post. |
|
Looking back at the comments on #80506, I'm starting to think that merging this PR might have been premature, considering its impact on the design system and the accessibility issues that need to be addressed. @annezazu @joedolson, I understand that due to time zone differences, you may not be able to check this comment immediately. However, I'm considering removing all UI related to global style inheritance, leaving only internal APIs, for the Beta 3 |
…ector controls (#80495) * Global Styles: resolve per-level heading element styles in inspector controls Completes the heading-family portion of the element inheritance layer tracked in #80438 (follow-up to #77894). The block inspector now reflects styles set on a specific heading level (`styles.elements.h1`-`h6`) in addition to the shared `heading` element. Previously the resolver folded in only `styles.elements.heading` for `core/heading`, so anything written by the per-level tabs in Styles > Typography > Headings was invisible to inspector controls. Because `fontSize` is deliberately disabled at the "All headings" level, every Global Styles heading font size lands in `elements.hN` and never reached a control at all. - Engine: replace the hardcoded `BLOCK_TO_ROOT_ELEMENT` map with an optional, caller-supplied `elements` list on the resolve context, ordered low to high precedence so `h2` wins over `heading`. This keeps the engine agnostic of block-to-element mappings and removes core block names from the package. The element list joins the memo cache key so two heading levels no longer share an entry. - block-editor: map block name + `level` to element keys for the heading family (`core/heading`, `site-title`, `post-title`, `query-title`, `comments-title`, `term-name`, `site-tagline`). A level of 0 (a block rendered as `<p>`) folds no heading layer. `clientId` is read from the block edit context, so `useResolvedStyle`'s signature is unchanged and no new store subscription is added. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Update comment * Try to fix tests * Handle accordion heading * Add a test for the 0 heading level case, tidy up changelog --------- Co-authored-by: andrewserong <andrewserong@git.wordpress.org> Co-authored-by: ramonjd <ramonopoly@git.wordpress.org> Co-authored-by: getdave <get_dave@git.wordpress.org>
…ector controls (#80495) * Global Styles: resolve per-level heading element styles in inspector controls Completes the heading-family portion of the element inheritance layer tracked in #80438 (follow-up to #77894). The block inspector now reflects styles set on a specific heading level (`styles.elements.h1`-`h6`) in addition to the shared `heading` element. Previously the resolver folded in only `styles.elements.heading` for `core/heading`, so anything written by the per-level tabs in Styles > Typography > Headings was invisible to inspector controls. Because `fontSize` is deliberately disabled at the "All headings" level, every Global Styles heading font size lands in `elements.hN` and never reached a control at all. - Engine: replace the hardcoded `BLOCK_TO_ROOT_ELEMENT` map with an optional, caller-supplied `elements` list on the resolve context, ordered low to high precedence so `h2` wins over `heading`. This keeps the engine agnostic of block-to-element mappings and removes core block names from the package. The element list joins the memo cache key so two heading levels no longer share an entry. - block-editor: map block name + `level` to element keys for the heading family (`core/heading`, `site-title`, `post-title`, `query-title`, `comments-title`, `term-name`, `site-tagline`). A level of 0 (a block rendered as `<p>`) folds no heading layer. `clientId` is read from the block edit context, so `useResolvedStyle`'s signature is unchanged and no new store subscription is added. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Update comment * Try to fix tests * Handle accordion heading * Add a test for the 0 heading level case, tidy up changelog --------- Co-authored-by: andrewserong <andrewserong@git.wordpress.org> Co-authored-by: ramonjd <ramonopoly@git.wordpress.org> Co-authored-by: getdave <get_dave@git.wordpress.org>
Thanks @t-hamano We are at a limited capacity to work on this, so if you feel the risks are too great then I'd support rolling back the UI. But I'd want us to do it for the right reasons. Are we rolling it back to prevent shipping a worse user experience or because of some future risk to the design system? E.g., as far as I can see #80506 affects panels not the underlying components. I'm not arguing for or against any design and will defer to the release folks here. If we need more time so that the design system can adapt to the UX, then that's great. Whether it needs to block this feature is another question. Are the accessibility issues inextricably linked or can they be separated out for now? |
|
I plan to submit a PR soon, but since there is limited time before the synchronization between Core and Gutenberg, I would like to re-evaluate the strategy.
Please let me know your opinion! |
This seems very reasonable to me. Thanks again @t-hamano Please let us know how we can help. |
+1 — IMO whatever helps make the release process more streamlined for folks! Whether that's adding the guard now and removing if we decide to include for 7.1, or skip the guard to make Beta 3 easier to release and then potentially punt in later phases, I don't mind personally. |
|
Thank you all for your feedback. The PR is ready. #80555 |
…lugin (#80555) * Global Styles: Gate the inherited value treatment on the plugin flag The inherited Global Styles treatment added in #77894 and #80495 lives in `block-editor`, which is synced to WordPress Core. Keep it to the Gutenberg plugin so Core builds keep showing locally-set values alone. `useResolvedStyle` resolves nothing when the flag is off, so each panel's `inheritedValue = value` default restores the pre-feature wiring, and the block-supports panels default `showInheritanceLabelIndicators` to the same flag, switching off every affordance routed through `getInheritanceProps`. Both are required: gating only the value leaves `inheritedValue === value`, which reads as a local override on every locally-set control and renders a reset dot. The typography panel's link-color sync reads `inheritedValue` outside that prop, and collapsing it onto `value` would write a link color where trunk wrote none, so it falls back to the pre-feature comparison when the flag is off. Co-Authored-By: Claude <noreply@anthropic.com> * Global Styles: Note why the inheritance gate returns early Co-Authored-By: Claude <noreply@anthropic.com> * Global Styles: Add the PR number to the changelog entry Co-Authored-By: Claude <noreply@anthropic.com> * Global Styles: Restore comment on inheritance className gating The comment documented a bug found in the original implementation: the layout className must always be passed through, and only the inheritance treatment is gated on `showInheritanceLabelIndicators`. Dropping it loses that context since the code itself is unchanged. Co-Authored-By: Claude <noreply@anthropic.com> * Global Styles: Cover the Core path of ENABLE_GLOBAL_STYLES_INHERITANCE The unit test environment sets `IS_GUTENBERG_PLUGIN` to true, so every existing test exercises the plugin branch only. Nothing guarded the Core behaviour the flag is meant to preserve, which is the behaviour that ships to WordPress. Add two files that mock the constant to false. `jest.mock` is file-scoped, so they cannot live alongside the plugin-path tests. - `useResolvedStyle` resolves nothing, so the panels fall back to their `inheritedValue = value` default and Core shows locally-set values only. - `TypographyPanel` applies no inherited treatment and no reset dot by default, and its link colour sync falls back to comparing the local pair, which is what Core did before the inheritance treatment landed. Co-Authored-By: Claude <noreply@anthropic.com> * Global Styles: Compare inherited values in the Core link colour sync The Core fallback compared `value`, but the check this replaced compared `inheritedValue`. The two are only the same in the block inspector, where `useResolvedStyle` resolves nothing and the prop falls back to `value`. In Global Styles they diverge: `value` is the user config and `inheritedValue` is the merged config. Comparing `value` there meant a theme defining a mismatched text and link colour pair would still sync the link colour, because both sides of the comparison were undefined until the user set something. Restore the original comparison, which is correct on both paths. Co-Authored-By: Claude <noreply@anthropic.com> * Global Styles: Guard the Core link colour sync against reading the user config The existing link colour tests all render without an `inheritedValue`, the block inspector shape where the prop falls back to `value`. On that path the two configs are identical, so none of them notice if the comparison switches from `inheritedValue` to `value`. Add a case with the divergent Global Styles shape: an empty user config and a merged config whose text and link colours differ. Reading `value` there finds undefined on both sides and syncs, which the assertion catches. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: t-hamano <wildworks@git.wordpress.org> Co-authored-by: ramonjd <ramonopoly@git.wordpress.org> Co-authored-by: andrewserong <andrewserong@git.wordpress.org>
…lugin (#80555) * Global Styles: Gate the inherited value treatment on the plugin flag The inherited Global Styles treatment added in #77894 and #80495 lives in `block-editor`, which is synced to WordPress Core. Keep it to the Gutenberg plugin so Core builds keep showing locally-set values alone. `useResolvedStyle` resolves nothing when the flag is off, so each panel's `inheritedValue = value` default restores the pre-feature wiring, and the block-supports panels default `showInheritanceLabelIndicators` to the same flag, switching off every affordance routed through `getInheritanceProps`. Both are required: gating only the value leaves `inheritedValue === value`, which reads as a local override on every locally-set control and renders a reset dot. The typography panel's link-color sync reads `inheritedValue` outside that prop, and collapsing it onto `value` would write a link color where trunk wrote none, so it falls back to the pre-feature comparison when the flag is off. Co-Authored-By: Claude <noreply@anthropic.com> * Global Styles: Note why the inheritance gate returns early Co-Authored-By: Claude <noreply@anthropic.com> * Global Styles: Add the PR number to the changelog entry Co-Authored-By: Claude <noreply@anthropic.com> * Global Styles: Restore comment on inheritance className gating The comment documented a bug found in the original implementation: the layout className must always be passed through, and only the inheritance treatment is gated on `showInheritanceLabelIndicators`. Dropping it loses that context since the code itself is unchanged. Co-Authored-By: Claude <noreply@anthropic.com> * Global Styles: Cover the Core path of ENABLE_GLOBAL_STYLES_INHERITANCE The unit test environment sets `IS_GUTENBERG_PLUGIN` to true, so every existing test exercises the plugin branch only. Nothing guarded the Core behaviour the flag is meant to preserve, which is the behaviour that ships to WordPress. Add two files that mock the constant to false. `jest.mock` is file-scoped, so they cannot live alongside the plugin-path tests. - `useResolvedStyle` resolves nothing, so the panels fall back to their `inheritedValue = value` default and Core shows locally-set values only. - `TypographyPanel` applies no inherited treatment and no reset dot by default, and its link colour sync falls back to comparing the local pair, which is what Core did before the inheritance treatment landed. Co-Authored-By: Claude <noreply@anthropic.com> * Global Styles: Compare inherited values in the Core link colour sync The Core fallback compared `value`, but the check this replaced compared `inheritedValue`. The two are only the same in the block inspector, where `useResolvedStyle` resolves nothing and the prop falls back to `value`. In Global Styles they diverge: `value` is the user config and `inheritedValue` is the merged config. Comparing `value` there meant a theme defining a mismatched text and link colour pair would still sync the link colour, because both sides of the comparison were undefined until the user set something. Restore the original comparison, which is correct on both paths. Co-Authored-By: Claude <noreply@anthropic.com> * Global Styles: Guard the Core link colour sync against reading the user config The existing link colour tests all render without an `inheritedValue`, the block inspector shape where the prop falls back to `value`. On that path the two configs are identical, so none of them notice if the comparison switches from `inheritedValue` to `value`. Add a case with the divergent Global Styles shape: an empty user config and a merged config whose text and link colours differ. Reading `value` there finds undefined on both sides and syncs, which the assertion catches. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: t-hamano <wildworks@git.wordpress.org> Co-authored-by: ramonjd <ramonopoly@git.wordpress.org> Co-authored-by: andrewserong <andrewserong@git.wordpress.org>
…ector controls (#80495) * Global Styles: resolve per-level heading element styles in inspector controls Completes the heading-family portion of the element inheritance layer tracked in #80438 (follow-up to #77894). The block inspector now reflects styles set on a specific heading level (`styles.elements.h1`-`h6`) in addition to the shared `heading` element. Previously the resolver folded in only `styles.elements.heading` for `core/heading`, so anything written by the per-level tabs in Styles > Typography > Headings was invisible to inspector controls. Because `fontSize` is deliberately disabled at the "All headings" level, every Global Styles heading font size lands in `elements.hN` and never reached a control at all. - Engine: replace the hardcoded `BLOCK_TO_ROOT_ELEMENT` map with an optional, caller-supplied `elements` list on the resolve context, ordered low to high precedence so `h2` wins over `heading`. This keeps the engine agnostic of block-to-element mappings and removes core block names from the package. The element list joins the memo cache key so two heading levels no longer share an entry. - block-editor: map block name + `level` to element keys for the heading family (`core/heading`, `site-title`, `post-title`, `query-title`, `comments-title`, `term-name`, `site-tagline`). A level of 0 (a block rendered as `<p>`) folds no heading layer. `clientId` is read from the block edit context, so `useResolvedStyle`'s signature is unchanged and no new store subscription is added. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Update comment * Try to fix tests * Handle accordion heading * Add a test for the 0 heading level case, tidy up changelog --------- Co-authored-by: andrewserong <andrewserong@git.wordpress.org> Co-authored-by: ramonjd <ramonopoly@git.wordpress.org> Co-authored-by: getdave <get_dave@git.wordpress.org>





Closes: #77595
What?
Inspector controls now reflect the Global Styles values a block inherits, so the panels line up closer to what you actually see on the block in the canvas.
Inherited global styles values only come from the merged Global Styles data:
There are no computed styles involved, so custom CSS, enqueued stylesheets, and ancestor variations still can't be reflected even though they paint the canvas.
Scoped to an MVP
To land the core of this in 7.1 without first having to extend heavily-used
@wordpress/components(likeBaseControl) to accept injected label nodes and settle the related design and portal questions that come with it. Two things are deliberately out of scope here and moved to follow-ups:BaseControlnorInputControlexposes one today. Rather than push that public-API change through now, this PR keeps only the dotted underline at rest and moves the portaled adornment + path tooltip to a follow-up, where the approach and design can be settled properly.Why?
Right now a control only reflects what's set on the block, never what it inherits.
A Paragraph block renders with the theme's colour, size, and padding on the canvas while the controls sit empty, reading as "unset" when they're anything but. You can't tell a local override apart from an inherited value.
It's a long-standing rough edge (#37752, #43082, #49278), tracked under #77595.
How?
buildInheritedValue, merges the value a block inherits in precedence order; theme.json/user defaults ‹ block type ‹ applied block style variation. It's pure: all value resolution ({ ref }envelopes and theme-file image links) happens here.useInheritedValue, resolve the cascade for the selected block, and pass the result to the shared inspector panels as a prop.block-editor.0is preserved, inherited values aren't saved until a user picks them, and reset stays a single undo.Architecture
The flow is a straight line from the store to the control:
useInheritedValue, they read the merged Global Styles data (and theme-file links) from the store in a singleuseSelect, then callbuild InheritedValueto resolve the Root ‹ Block ‹ Variation cascade for the selected block, dropping root-level values that don't actually cascade to it (e.g. a root background).So store access lives only in the hooks, cascade resolution lives only in the pure function, and the panels stay presentational. No data dependency and liftable out of
block-editor.APIs
@wordpress/components(e.g.BaseControl) to wrap control labels in tooltips. This PR doesn't need that, and the follow-up avoids it too by portaling the adornment instead. Neither branch touches@wordpress/components.useInheritedValueandbuildInheritedValueare internal toblock-editor, not re-exported from the package entry orprivateApis.showInheritanceLabelIndicators, which flags whether the inheritance labels and indicators should be shown. TheinheritedValueprop is pre-existing.Notes
Testing Instructions
Inherited values
Local overrides
Edge cases
0value is treated as a real value, not an empty one.Example theme.json
To help with defining global style values to check different controls the following theme.json might be of use. It is a modified version of the emptytheme theme.json, so applying this there and switching to emptytheme is a quick way to get up and running.
Example theme.json
{ "$schema": "../../schemas/json/theme.json", "version": 3, "settings": { "appearanceTools": true, "typography": { "writingMode": true, "fontFamilies": [ { "slug": "georgia", "name": "Georgia", "fontFamily": "Georgia, 'Times New Roman', serif" }, { "slug": "system", "name": "System", "fontFamily": "system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif" } ] }, "layout": { "contentSize": "840px", "wideSize": "1100px" } }, "styles": { "blocks": { "core/paragraph": { "color": { "text": "#1e1e1e", "background": "#f6f7f7", "gradient": "linear-gradient(135deg, #fdfbfb 0%, #ebedee 100%)" }, "typography": { "fontFamily": "var:preset|font-family|georgia", "fontSize": "1.125rem", "fontStyle": "italic", "fontWeight": "600", "lineHeight": "1.7", "letterSpacing": "0.01em", "textAlign": "justify", "textColumns": "2", "textDecoration": "underline", "textIndent": "1.5em", "textTransform": "capitalize" }, "spacing": { "margin": { "top": "0", "right": "0", "bottom": "1.5rem", "left": "0" }, "padding": { "top": "0.5rem", "right": "1rem", "bottom": "0.5rem", "left": "1rem" } }, "border": { "color": "#e0e0e0", "style": "solid", "width": "1px", "radius": "4px" }, "elements": { "link": { "color": { "text": "#0073aa" }, ":hover": { "color": { "text": "#005177" } } } } }, "core/group": { "background": { "backgroundImage": { "url": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScyMDAnIGhlaWdodD0nMjAwJz48ZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9J2cnIHgxPScwJyB5MT0nMCcgeDI9JzEnIHkyPScxJz48c3RvcCBvZmZzZXQ9JzAnIHN0b3AtY29sb3I9JyNjOWU3ZmYnLz48c3RvcCBvZmZzZXQ9JzAuNScgc3RvcC1jb2xvcj0nI2VhZTRmZicvPjxzdG9wIG9mZnNldD0nMScgc3RvcC1jb2xvcj0nI2ZmZDZlOCcvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHdpZHRoPScyMDAnIGhlaWdodD0nMjAwJyBmaWxsPSd1cmwoI2cpJy8+PGNpcmNsZSBjeD0nNjAnIGN5PSc3MCcgcj0nMjgnIGZpbGw9JyNmZmZmZmYnIG9wYWNpdHk9JzAuMzUnLz48Y2lyY2xlIGN4PScxNTAnIGN5PScxNDAnIHI9JzQwJyBmaWxsPScjZmZmZmZmJyBvcGFjaXR5PScwLjI1Jy8+PC9zdmc+" }, "backgroundSize": "cover", "backgroundPosition": "center center", "backgroundRepeat": "no-repeat", "backgroundAttachment": "fixed" }, "color": { "text": "#1e1e1e", "background": "#ffffff", "gradient": "linear-gradient(135deg, #f6f7f7 0%, #e5f0f5 100%)" }, "typography": { "fontFamily": "var:preset|font-family|system", "fontSize": "1rem", "fontStyle": "italic", "fontWeight": "500", "lineHeight": "1.6", "letterSpacing": "0.02em", "textDecoration": "underline", "textTransform": "uppercase" }, "spacing": { "blockGap": "1.5rem", "margin": { "top": "2rem", "bottom": "2rem" }, "padding": { "top": "2rem", "right": "2rem", "bottom": "2rem", "left": "2rem" } }, "dimensions": { "minHeight": "120px", "minWidth": "200px" }, "border": { "color": "#d5d5d5", "style": "solid", "width": "1px", "radius": "8px" }, "elements": { "heading": { "color": { "text": "#0a1e2e" }, "typography": { "fontWeight": "700", "lineHeight": "1.2" } }, "button": { "color": { "text": "#ffffff", "background": "#0073aa" }, "border": { "radius": "4px" }, ":hover": { "color": { "background": "#005177" } } }, "link": { "color": { "text": "#0073aa" }, ":hover": { "color": { "text": "#005177" } } } } } } }, "customTemplates": [ { "name": "custom-template", "title": "Custom", "postTypes": [ "post" ] } ], "patterns": [ "short-text-surrounded-by-round-images", "partner-logos" ] }