Back to Top
A native button that takes visitors back to the top. Its progress ring follows the button’s shape, it can appear only on the way up, it stays clear of footers and cookie bars, and it moves keyboard focus to the start of the page. Everything is set in the Etch props panel.
Paste it on the Etch canvas with Ctrl+V or ⌘V, then click Save.
{
"type": "block",
"gutenbergBlock": {
"blockName": "etch/component",
"attrs": {
"metadata": {
"name": "ED Back to Top"
},
"ref": 106,
"attributes": {}
},
"innerBlocks": [],
"innerHTML": "\n\n",
"innerContent": [
"\n",
"\n"
]
},
"timestamp": "2026-10-19T18:01:53.611Z",
"styles": {
"ntb250h": {
"type": "class",
"selector": ".ed-back-to-top",
"collection": "default",
"css": "/* Styling guide: delete the comment marks around one line to set that token for every button. */\n /* An uncommented line also wins over the same token on a parent class or :root. For one section or breakpoint only, leave it commented and set the token there. */\n\n /* Placement. Display none hides the button, e.g. inside a media query. */\n /* --ed-back-to-top-display: inline-flex; */\n /* --ed-back-to-top-offset-inline: var(--space-s, 1rem); */\n /* --ed-back-to-top-offset-block: var(--space-s, 1rem); */\n /* --ed-back-to-top-z-index: 50; */\n\n /* Size and surface. Size is the minimum width and height, never below 44px. A backdrop filter such as blur(8px) needs a translucent background. */\n /* --ed-back-to-top-size: 60px; */\n /* --ed-back-to-top-padding: 0.75rem; */\n /* --ed-back-to-top-gap: var(--space-xs, 0.5rem); */\n /* --ed-back-to-top-bg: var(--bg-ultra-light, #ffffff); */\n /* --ed-back-to-top-color: var(--text-dark, #17212b); */\n /* --ed-back-to-top-border-width: 1px; */\n /* --ed-back-to-top-border-style: solid; */\n /* --ed-back-to-top-border-color: var(--neutral-light, #cbd5e1); */\n /* --ed-back-to-top-radius: 999px; */\n /* --ed-back-to-top-shadow: 0 3px 16px rgb(0 0 0 / 0.08); */\n /* --ed-back-to-top-backdrop-filter: none; */\n\n /* Text */\n /* --ed-back-to-top-font-family: inherit; */\n /* --ed-back-to-top-text-size: var(--text-m, 1rem); */\n /* --ed-back-to-top-font-weight: inherit; */\n /* --ed-back-to-top-line-height: 1.2; */\n /* --ed-back-to-top-letter-spacing: normal; */\n /* --ed-back-to-top-text-transform: none; */\n\n /* Icon. Thickness is the chevron and arrow stroke on a 24-unit grid. */\n /* --ed-back-to-top-icon-size: 1.25rem; */\n /* --ed-back-to-top-icon-color: currentColor; */\n /* --ed-back-to-top-icon-thickness: 2; */\n\n /* Progress ring. It follows the border radius corner by corner; inset is the gap between the border and the ring. */\n /* --ed-back-to-top-ring-width: 2px; */\n /* --ed-back-to-top-ring-inset: 0px; */\n /* --ed-back-to-top-track-color: var(--neutral-light, #cbd5e1); */\n /* --ed-back-to-top-progress-color: var(--primary, #245be0); */\n\n /* Hover, press and focus. Hover colors follow the base colors above; active transform takes e.g. scale(0.95). */\n /* --ed-back-to-top-hover-bg: color-mix(in srgb, var(--ed-back-to-top-bg, var(--bg-ultra-light, #ffffff)) 90%, var(--ed-back-to-top-color, var(--text-dark, #17212b))); */\n /* --ed-back-to-top-hover-color: var(--ed-back-to-top-color, var(--text-dark, #17212b)); */\n /* --ed-back-to-top-hover-border-color: var(--ed-back-to-top-border-color, var(--neutral-light, #cbd5e1)); */\n /* --ed-back-to-top-hover-shadow: var(--ed-back-to-top-shadow, 0 3px 16px rgb(0 0 0 / 0.08)); */\n /* --ed-back-to-top-hover-progress-color: var(--ed-back-to-top-progress-color, var(--primary, #245be0)); */\n /* --ed-back-to-top-active-transform: none; */\n /* --ed-back-to-top-focus-color: var(--focus-color, var(--primary, #245be0)); */\n /* --ed-back-to-top-focus-width: 3px; */\n /* --ed-back-to-top-focus-offset: 4px; */\n\n /* Reveal. Hidden offset is the Slide distance, hidden scale the Scale start and hidden transform the Custom transform start. */\n /* --ed-back-to-top-transition: var(--transition, 0.2s ease); */\n /* --ed-back-to-top-hidden-offset: 10px; */\n /* --ed-back-to-top-hidden-scale: 0.8; */\n /* --ed-back-to-top-hidden-transform: translateY(10px); */\n\n --_ed-back-to-top-bg: var(--ed-back-to-top-bg, var(--bg-ultra-light, #ffffff));\n --_ed-back-to-top-color: var(--ed-back-to-top-color, var(--text-dark, #17212b));\n --_ed-back-to-top-border-color: var(--ed-back-to-top-border-color, var(--neutral-light, #cbd5e1));\n --_ed-back-to-top-shadow: var(--ed-back-to-top-shadow, 0 3px 16px rgb(0 0 0 / 0.08));\n --_ed-back-to-top-progress: var(--ed-back-to-top-progress-color, var(--primary, #245be0));\n --_ed-back-to-top-inline-offset: var(--ed-back-to-top-offset-inline, var(--space-s, 1rem));\n --_ed-back-to-top-transition: var(--ed-back-to-top-transition, var(--transition, 0.2s ease));\n position: fixed;\n inset-inline-end: calc(var(--_ed-back-to-top-inline-offset) + env(safe-area-inset-right, 0px));\n inset-block-end: calc(var(--ed-back-to-top-offset-block, var(--space-s, 1rem)) + env(safe-area-inset-bottom, 0px) + var(--_ed-back-to-top-lift, 0px));\n z-index: var(--ed-back-to-top-z-index, 50);\n display: var(--ed-back-to-top-display, inline-flex);\n align-items: center;\n justify-content: center;\n gap: var(--ed-back-to-top-gap, var(--space-xs, 0.5rem));\n box-sizing: border-box;\n min-inline-size: max(44px, var(--ed-back-to-top-size, 60px));\n min-block-size: max(44px, var(--ed-back-to-top-size, 60px));\n max-inline-size: calc(100vw - 2 * var(--_ed-back-to-top-inline-offset));\n margin: 0;\n padding: var(--ed-back-to-top-padding, 0.75rem);\n border: var(--ed-back-to-top-border-width, 1px) var(--ed-back-to-top-border-style, solid) var(--_ed-back-to-top-border-color);\n border-radius: var(--ed-back-to-top-radius, 999px);\n background: var(--_ed-back-to-top-bg);\n color: var(--_ed-back-to-top-color);\n box-shadow: var(--_ed-back-to-top-shadow);\n backdrop-filter: var(--ed-back-to-top-backdrop-filter, none);\n font: inherit;\n font-family: var(--ed-back-to-top-font-family, inherit);\n font-size: var(--ed-back-to-top-text-size, var(--text-m, 1rem));\n font-weight: var(--ed-back-to-top-font-weight, inherit);\n line-height: var(--ed-back-to-top-line-height, 1.2);\n letter-spacing: var(--ed-back-to-top-letter-spacing, normal);\n text-transform: var(--ed-back-to-top-text-transform, none);\n /* Automatic.css styles every plain button through its --btn-* variables, with a rule that outranks this class, so\n the text values go into those variables too (the ACSS way to restyle one button). Family and weight have no\n fallback: left unset, the variable is invalid and the button inherits them, as without ACSS. */\n --btn-font-family: var(--ed-back-to-top-font-family);\n --btn-font-weight: var(--ed-back-to-top-font-weight);\n --btn-line-height: var(--ed-back-to-top-line-height, 1.2);\n --btn-letter-spacing: var(--ed-back-to-top-letter-spacing, normal);\n --btn-text-transform: var(--ed-back-to-top-text-transform, none);\n text-align: center;\n overflow-wrap: anywhere;\n cursor: pointer;\n opacity: 0;\n visibility: hidden;\n pointer-events: none;\n transform: translateY(var(--ed-back-to-top-hidden-offset, 10px));\n /* visibility is transitioned too, so the button stays painted while it hides. */\n transition: opacity var(--_ed-back-to-top-transition), transform var(--_ed-back-to-top-transition), visibility var(--_ed-back-to-top-transition), background-color var(--_ed-back-to-top-transition), color var(--_ed-back-to-top-transition), border-color var(--_ed-back-to-top-transition), box-shadow var(--_ed-back-to-top-transition);\n\n &:dir(rtl) { inset-inline-end: calc(var(--_ed-back-to-top-inline-offset) + env(safe-area-inset-left, 0px)); }\n &[data-ed-btt-position=\"bottom-start\"] { inset-inline-end: auto; inset-inline-start: calc(var(--_ed-back-to-top-inline-offset) + env(safe-area-inset-left, 0px)); }\n &[data-ed-btt-position=\"bottom-start\"]:dir(rtl) { inset-inline-start: calc(var(--_ed-back-to-top-inline-offset) + env(safe-area-inset-right, 0px)); }\n &[data-ed-btt-position=\"bottom-center\"] { inset-inline: 0; margin-inline: auto; inline-size: fit-content; }\n &[data-ed-btt-position=\"inline\"] { position: relative; inset: auto; z-index: auto; max-inline-size: 100%; }\n &[data-ed-btt-layout=\"row-reverse\"] { flex-direction: row-reverse; }\n &[data-ed-btt-layout=\"column\"] { flex-direction: column; }\n\n &[data-ed-btt-state=\"visible\"] { opacity: 1; visibility: visible; transform: none; pointer-events: auto; }\n /* Auto-hide: invisible and out of the pointer's way, but still reachable by screen readers and Tab. */\n &[data-ed-btt-state=\"idle\"] { visibility: visible; }\n &[data-ed-btt-reveal=\"fade\"] { transform: none; }\n &[data-ed-btt-reveal=\"scale\"]:not([data-ed-btt-state=\"visible\"]) { transform: scale(var(--ed-back-to-top-hidden-scale, 0.8)); }\n &[data-ed-btt-reveal=\"custom\"]:not([data-ed-btt-state=\"visible\"]) { transform: var(--ed-back-to-top-hidden-transform, translateY(10px)); }\n &[data-ed-btt-reveal=\"none\"] { transform: none; transition: none; }\n\n @media (hover: hover) {\n &:hover {\n background: var(--ed-back-to-top-hover-bg, color-mix(in srgb, var(--_ed-back-to-top-bg) 90%, var(--_ed-back-to-top-color)));\n color: var(--ed-back-to-top-hover-color, var(--_ed-back-to-top-color));\n border-color: var(--ed-back-to-top-hover-border-color, var(--_ed-back-to-top-border-color));\n box-shadow: var(--ed-back-to-top-hover-shadow, var(--_ed-back-to-top-shadow));\n --_ed-back-to-top-progress: var(--ed-back-to-top-hover-progress-color, var(--ed-back-to-top-progress-color, var(--primary, #245be0)));\n }\n }\n &[data-ed-btt-state=\"visible\"]:active { transform: var(--ed-back-to-top-active-transform, none); }\n &:focus-visible { outline: var(--ed-back-to-top-focus-width, 3px) solid var(--ed-back-to-top-focus-color, var(--focus-color, var(--primary, #245be0))); outline-offset: var(--ed-back-to-top-focus-offset, 4px); }\n\n & .ed-back-to-top__progress {\n position: absolute;\n inset-block-start: var(--ed-back-to-top-ring-inset, 0px);\n inset-inline-start: var(--ed-back-to-top-ring-inset, 0px);\n inline-size: calc(100% - 2 * var(--ed-back-to-top-ring-inset, 0px));\n block-size: calc(100% - 2 * var(--ed-back-to-top-ring-inset, 0px));\n overflow: visible;\n pointer-events: none;\n }\n &[data-ed-btt-button-type=\"regular\"] .ed-back-to-top__progress { display: none; }\n & .ed-back-to-top__track, & .ed-back-to-top__value { fill: none; stroke-width: var(--ed-back-to-top-ring-width, 2px); }\n & .ed-back-to-top__track { stroke: var(--ed-back-to-top-track-color, var(--neutral-light, #cbd5e1)); }\n & .ed-back-to-top__value { stroke: var(--_ed-back-to-top-progress); stroke-linecap: round; transition: stroke var(--_ed-back-to-top-transition); }\n &[data-ed-btt-line-cap=\"butt\"] .ed-back-to-top__value { stroke-linecap: butt; }\n & .ed-back-to-top__content { display: contents; }\n & [data-ed-btt-fallback][hidden] { display: none; }\n & .ed-back-to-top__icon {\n display: inline-flex;\n flex: 0 0 auto;\n align-items: center;\n justify-content: center;\n inline-size: var(--ed-back-to-top-icon-size, 1.25rem);\n block-size: var(--ed-back-to-top-icon-size, 1.25rem);\n color: var(--ed-back-to-top-icon-color, currentColor);\n }\n /* Every SVG in the icon box fills it: built-in icons and custom files, which may keep their own class. */\n & .ed-back-to-top__icon > svg { display: block; flex: none; inline-size: 100%; block-size: 100%; overflow: visible; }\n & .ed-back-to-top__chevron, & .ed-back-to-top__arrow {\n fill: none;\n stroke: currentColor;\n stroke-width: var(--ed-back-to-top-icon-thickness, 2);\n stroke-linecap: round;\n stroke-linejoin: round;\n }\n /* Custom SVG with \"Use text color\": Etch turns its colors into currentColor. Icons without their own\n fill (e.g. Font Awesome) would default to black; outline icons keep their fill=\"none\". */\n & .ed-back-to-top__icon > svg[data-ed-btt-colors=\"text\"]:not([fill]) { fill: currentColor; }\n & .ed-back-to-top__text { min-inline-size: 0; }\n /* Custom slot content shrinks to fit the button and images scale down. Low specificity, so your own sizes can override it. */\n & :where(.ed-back-to-top__content > :not(.ed-back-to-top__icon, .ed-back-to-top__text)) { min-inline-size: 0; max-inline-size: 100%; }\n & :where(.ed-back-to-top__content) :where(img, video, canvas) { max-inline-size: 100%; block-size: auto; }\n\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n transform: none;\n &[data-ed-btt-reveal], &[data-ed-btt-state=\"visible\"]:active { transform: none; }\n & .ed-back-to-top__value { transition: none; }\n }\n @media (forced-colors: active) {\n border-color: ButtonText;\n background: ButtonFace;\n color: ButtonText;\n box-shadow: none;\n & .ed-back-to-top__track { stroke: GrayText; }\n & .ed-back-to-top__value { stroke: ButtonText; }\n &:focus-visible { outline-color: Highlight; }\n }\n @media print { display: none; }",
"readonly": false
},
"g5ioi34": {
"type": "class",
"selector": ".ed-back-to-top__progress",
"collection": "default",
"css": "",
"readonly": false
},
"qcrehvc": {
"type": "class",
"selector": ".ed-back-to-top__track",
"collection": "default",
"css": "",
"readonly": false
},
"7o0or4q": {
"type": "class",
"selector": ".ed-back-to-top__value",
"collection": "default",
"css": "",
"readonly": false
},
"db25vfg": {
"type": "class",
"selector": ".ed-back-to-top__content",
"collection": "default",
"css": "",
"readonly": false
},
"po9ac6t": {
"type": "class",
"selector": ".ed-back-to-top__icon",
"collection": "default",
"css": "",
"readonly": false
},
"om75mg2": {
"type": "class",
"selector": ".ed-back-to-top__arrow",
"collection": "default",
"css": "",
"readonly": false
},
"ucirgcb": {
"type": "class",
"selector": ".ed-back-to-top__custom-icon",
"collection": "default",
"css": "",
"readonly": false
},
"9hn50ch": {
"type": "class",
"selector": ".ed-back-to-top__chevron",
"collection": "default",
"css": "",
"readonly": false
},
"yro05ei": {
"type": "class",
"selector": ".ed-back-to-top__text",
"collection": "default",
"css": "",
"readonly": false
}
},
"components": {
"106": {
"id": 106,
"legacyId": "",
"name": "ED Back to Top",
"key": "EdBackToTop",
"blocks": [
{
"blockName": "etch/element",
"attrs": {
"metadata": {
"name": "Back to Top"
},
"script": {
"code": "aWYoZ2xvYmFsVGhpc1tTeW1ib2wuZm9yKCJlZC5iYWNrLXRvLXRvcC52MiIpXSl7Z2xvYmFsVGhpc1tTeW1ib2wuZm9yKCJlZC5iYWNrLXRvLXRvcC52MiIpXS5zY2FuKCk7fWVsc2V7Ci8qISBFdGNoRGV2ZWxvcGVyIEJhY2sgdG8gVG9wIDEuMC4wLWJldGEuNQpDb3B5cmlnaHQgMjAyNiBUb20gQ2htaWVsLCBodHRwczovL2V0Y2hkZXZlbG9wZXIuY29tClNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBGU0wtMS4xLU1JVCAoaHR0cHM6Ly9ldGNoZGV2ZWxvcGVyLmNvbS9saWNlbnNlLykgKi8KInVzZSBzdHJpY3QiOygoKT0+e3ZhciBUdD0iQmFjayB0byB0b3AiO2Z1bmN0aW9uIFUodCxlLHIsaSl7bGV0IGE9U3RyaW5nKHQ/PyIiKS50cmltKCk7aWYoIS9eKD86XGQrKD86XC5cZCspP3xcLlxkKykkLy50ZXN0KGEpKXJldHVybiBlO2xldCBsPU51bWJlcihhKTtyZXR1cm4gTnVtYmVyLmlzRmluaXRlKGwpP01hdGgubWluKGksTWF0aC5tYXgocixsKSk6ZX12YXIgYXQ9dD0+VSh0LDEwMCwwLDFlNSksc3Q9dD0+e2xldCBlPVUodCwwLDAsNWUzKTtyZXR1cm4gZSYmTWF0aC5tYXgoMTAwLGUpfSxjdD10PT57bGV0IGU9VSh0LDAsMCw2ZTQpO3JldHVybiBlJiZNYXRoLm1heCg1MDAsZSl9LGx0PXQ9PnQ8LjU/NCp0KnQqdDoxLSgtMip0KzIpKiozLzI7ZnVuY3Rpb24gZHQodCxlLHI9VHQpe2xldCBpPWM9PlN0cmluZyhjPz8iIikucmVwbGFjZSgvXHMrL2csIiAiKS50cmltKCksYT1pKHQpLGw9aShlKTtyZXR1cm4gbD9hJiZhLnRvTG9jYWxlTG93ZXJDYXNlKCkuaW5jbHVkZXMobC50b0xvY2FsZUxvd2VyQ2FzZSgpKT9hOm51bGw6YXx8cn12YXIgJD10PT5OdW1iZXIuaXNGaW5pdGUodCk/TWF0aC5tYXgoMCx0KTowO2Z1bmN0aW9uIHV0KHQsZSxyKXtsZXRbaSxhLGwsY109dCxkPShnLHMpPT5zPmc/Zy9zOjEsbT1NYXRoLm1pbihkKGUsaS54K2EueCksZChlLGMueCtsLngpLGQocixpLnkrYy55KSxkKHIsYS55K2wueSkpO3JldHVybiB0Lm1hcCgoe3g6Zyx5OnN9KT0+KHt4OmcqbSx5OnMqbX0pKX1mdW5jdGlvbiBCdCh0KXtsZXQgZT1TdHJpbmcodD8/IiIpLnRyaW0oKSxyPTA7Zm9yKGxldCBpPTA7aTxlLmxlbmd0aDtpKyspaWYoZVtpXT09PSIoIilyKys7ZWxzZSBpZihlW2ldPT09IikiKXItLTtlbHNlIGlmKCFyJiYvXHMvLnRlc3QoZVtpXSkpcmV0dXJuW2Uuc2xpY2UoMCxpKSxlLnNsaWNlKGkpLnRyaW0oKV07cmV0dXJuW2UsZV19ZnVuY3Rpb24gZnQodCxlLHIsaT0oKT0+MCl7ZT0kKGUpLHI9JChyKTtsZXQgYT0obCxjLGQpPT57bGV0IG09L14oWystXT8oPzpcZCsoPzpcLlxkKyk/fFwuXGQrKSg/OmVbKy1dP1xkKyk/KShweHwlKT8kL2kuZXhlYyhsKTtyZXR1cm4gJChtP051bWJlcihtWzFdKSoobVsyXT09PSIlIj9jLzEwMDoxKTppKGwsYyxkKSl9O3JldHVybiB1dChBcnJheS5mcm9tKHtsZW5ndGg6NH0sKGwsYyk9PntsZXRbZCxtXT1CdCh0W2NdKTtyZXR1cm57eDphKGQsZSwieCIpLHk6YShtLHIsInkiKX19KSxlLHIpfWZ1bmN0aW9uIGoodCxlLHIsaSl7dD0kKHQpLGU9JChlKSxpPU1hdGgubWluKCQoaSksdCxlKTtsZXQgYT1pLzIsbD1NYXRoLm1heCgwLHQtaSksYz1NYXRoLm1heCgwLGUtaSksW2QsbSxnLHNdPXV0KEFycmF5LmZyb20oe2xlbmd0aDo0fSwoeSxIKT0+e2xldFtPLFddPXJbSF0/P1tdO3JldHVybnt4Ok1hdGgubWF4KDAsJChPKS1hKSx5Ok1hdGgubWF4KDAsJChXKS1hKX19KSxsLGMpLHU9eT0+U3RyaW5nKE51bWJlci5pc0Zpbml0ZSh5KjEwMCk/TWF0aC5yb3VuZCh5KjEwMCkvMTAwOnkpLHc9YSxGPWEsXz1hK2wsVD1hK2MsQj13K2QueCsobC1kLngtbS54KS8yLFM9KHksSCxPKT0+YEEke3UoeS54KX0gJHt1KHkueSl9IDAgMCAxICR7dShIKX0gJHt1KE8pfWA7cmV0dXJuYE0ke3UoQil9ICR7dShGKX1IJHt1KF8tbS54KX0ke1MobSxfLEYrbS55KX1WJHt1KFQtZy55KX0ke1MoZyxfLWcueCxUKX1IJHt1KHcrcy54KX0ke1Mocyx3LFQtcy55KX1WJHt1KEYrZC55KX0ke1MoZCx3K2QueCxGKX1IJHt1KEIpfWB9dmFyIFB0PWooNjAsNjAsW1szMCwzMF0sWzMwLDMwXSxbMzAsMzBdLFszMCwzMF1dLDIpO2Z1bmN0aW9uIG10KHQpe2xldCBlPXQub3duZXJEb2N1bWVudCxyLGksYSxsLGM7ZnVuY3Rpb24gZChtLGcscyl7bGV0IHU9SlNPTi5zdHJpbmdpZnkoW20sZyxzXSk7aWYodT09PWwpcmV0dXJuIGM7bGV0IHc9bmV3IE1hcDtyZXR1cm4gYz1mdChtLGcscywoRixfLFQpPT57bGV0IEI9VCtGO2lmKHcuaGFzKEIpKXJldHVybiB3LmdldChCKTtyfHwocj1lLmNyZWF0ZUVsZW1lbnQoImVkLWJhY2stdG8tdG9wLW1lYXN1cmUiKSxyLnNldEF0dHJpYnV0ZSgiYXJpYS1oaWRkZW4iLCJ0cnVlIiksci5pbmVydD0hMCxyLnN0eWxlLmNzc1RleHQ9ImFsbDppbml0aWFsO3Bvc2l0aW9uOmZpeGVkO3RvcDowO2xlZnQ6MDt3aWR0aDowO2hlaWdodDowO292ZXJmbG93OmhpZGRlbjtjb250YWluOnN0cmljdDt2aXNpYmlsaXR5OmhpZGRlbjtwb2ludGVyLWV2ZW50czpub25lOyIsaT1lLmNyZWF0ZUVsZW1lbnQoImRpdiIpLGE9ZS5jcmVhdGVFbGVtZW50KCJkaXYiKSxpLnN0eWxlLmNzc1RleHQ9InBvc2l0aW9uOmFic29sdXRlO2xlZnQ6MDt0b3A6MDsiLGEuc3R5bGUuY3NzVGV4dD0icG9zaXRpb246YWJzb2x1dGU7bGVmdDowO3RvcDowO21hcmdpbjowO3BhZGRpbmc6MDtib3JkZXI6MDtib3gtc2l6aW5nOmNvbnRlbnQtYm94OyIsaS5hcHBlbmQoYSksci5hdHRhY2hTaGFkb3coe21vZGU6ImNsb3NlZCJ9KS5hcHBlbmQoaSksZS5kb2N1bWVudEVsZW1lbnQuYXBwZW5kKHIpKSxpLnN0eWxlLndpZHRoPWcrInB4IixpLnN0eWxlLmhlaWdodD1zKyJweCI7bGV0IFM9VD09PSJ4Ij8id2lkdGgiOiJoZWlnaHQiO2Euc3R5bGVbU109IjBweCIsYS5zdHlsZVtTXT1GO2xldCB5PXBhcnNlRmxvYXQoZS5kZWZhdWx0Vmlldy5nZXRDb21wdXRlZFN0eWxlKGEpW1NdKTtyZXR1cm4gdy5zZXQoQix5KSx5fSksbD11LGN9cmV0dXJuIGQuZGlzcG9zZT0oKT0+e3I/LnJlbW92ZSgpLHI9aT1hPW51bGwsbD1jPXZvaWQgMH0sZH12YXIgUT0iYnV0dG9uLmVkLWJhY2stdG8tdG9wIixEPW5ldyBNYXAsRz1tYXRjaE1lZGlhKCIocHJlZmVycy1yZWR1Y2VkLW1vdGlvbjogcmVkdWNlKSIpLGd0PW51bGw7Zm9yKGxldCB0IG9mWyJwb2ludGVyZG93biIsInBvaW50ZXJtb3ZlIl0pd2luZG93LmFkZEV2ZW50TGlzdGVuZXIodCxlPT57Z3Q9ZS5wb2ludGVyVHlwZX0se2NhcHR1cmU6ITAscGFzc2l2ZTohMH0pO3ZhciBDdD1bImRhdGEtZWQtYnR0LWxhYmVsIiwiZGF0YS1lZC1idHQtdmlzaWJsZS1hZnRlciIsImRhdGEtZWQtYnR0LW9ubHktc2Nyb2xsaW5nLXVwIiwiZGF0YS1lZC1idHQtcHJldmlldyIsImRhdGEtZWQtYnR0LWZvY3VzLXRhcmdldCIsImRhdGEtZWQtYnR0LXNjcm9sbC1iZWhhdmlvciIsImRhdGEtZWQtYnR0LXNjcm9sbC1kdXJhdGlvbiIsImRhdGEtZWQtYnR0LXNjcm9sbC1jb250YWluZXIiLCJkYXRhLWVkLWJ0dC1hdm9pZC1zZWxlY3RvciIsImRhdGEtZWQtYnR0LWF2b2lkLW1vZGUiLCJkYXRhLWVkLWJ0dC1pZGxlLWhpZGUiLCJkYXRhLWVkLWJ0dC1wb3NpdGlvbiIsImRhdGEtZWQtYnR0LWJ1dHRvbi10eXBlIiwiZGF0YS1lZC1idHQtY29udGVudC1tb2RlIiwiZGF0YS1lZC1idHQtbGF5b3V0Iiwic3R5bGUiLCJjbGFzcyJdLGt0PVsid2hlZWwiLCJ0b3VjaHN0YXJ0IiwicG9pbnRlcmRvd24iLCJrZXlkb3duIl0sRnQ9MixSdD1bIlRvcExlZnQiLCJUb3BSaWdodCIsIkJvdHRvbVJpZ2h0IiwiQm90dG9tTGVmdCJdO2Z1bmN0aW9uIEx0KCl7dHJ5e3JldHVybiEhKHdpbmRvdy5ldGNofHx3aW5kb3cucGFyZW50IT09d2luZG93JiZ3aW5kb3cucGFyZW50LmV0Y2gpfWNhdGNoe3JldHVybiExfX12YXIgYnQ9THQoKSx2dD10PT50Py5ub2RlVHlwZT09PTE7ZnVuY3Rpb24gWih0LGUpe2lmKCF0Py50cmltKCkpcmV0dXJuIGU/W106bnVsbDt0cnl7cmV0dXJuIGU/Wy4uLmRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwodCldOmRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IodCl9Y2F0Y2h7cmV0dXJuIGU/W106bnVsbH19ZnVuY3Rpb24gcHQodCl7cmV0dXJuIHQuY2hlY2tWaXNpYmlsaXR5P3QuY2hlY2tWaXNpYmlsaXR5KHt2aXNpYmlsaXR5UHJvcGVydHk6ITAsY2hlY2tWaXNpYmlsaXR5Q1NTOiEwfSk6dC5nZXRDbGllbnRSZWN0cygpLmxlbmd0aD4wJiZnZXRDb21wdXRlZFN0eWxlKHQpLnZpc2liaWxpdHk9PT0idmlzaWJsZSJ9dmFyIGh0PSh0LGUpPT50Lmxlbmd0aD09PWUubGVuZ3RoJiZ0LmV2ZXJ5KChyLGkpPT5yPT09ZVtpXSk7ZnVuY3Rpb24geXQodCl7bGV0IGU9Wih0LmRhdGFzZXQuZWRCdHRTY3JvbGxDb250YWluZXIpO2lmKHZ0KGUpJiZlIT09ZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50KXJldHVybiBlO2xldCByPWRvY3VtZW50LmRvY3VtZW50RWxlbWVudCxpPWRvY3VtZW50LmJvZHk7cmV0dXJuIHIuc2Nyb2xsSGVpZ2h0LWlubmVySGVpZ2h0PjAmJmdldENvbXB1dGVkU3R5bGUocikub3ZlcmZsb3dZIT09ImhpZGRlbiI/d2luZG93OmkmJi9hdXRvfHNjcm9sbHxvdmVybGF5Ly50ZXN0KGdldENvbXB1dGVkU3R5bGUoaSkub3ZlcmZsb3dZKSYmaS5zY3JvbGxIZWlnaHQtaS5jbGllbnRIZWlnaHQ+MT9pOndpbmRvd31mdW5jdGlvbiBQKHQpe3JldHVybiB0PT09d2luZG93P3t5OnNjcm9sbFksbWF4Ok1hdGgubWF4KDAsZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LnNjcm9sbEhlaWdodC1pbm5lckhlaWdodCl9Ont5OnQuc2Nyb2xsVG9wLG1heDpNYXRoLm1heCgwLHQuc2Nyb2xsSGVpZ2h0LXQuY2xpZW50SGVpZ2h0KX19ZnVuY3Rpb24gVih0LGUscil7dC5zY3JvbGxUbyh7dG9wOmUsbGVmdDp0PT09d2luZG93P3Njcm9sbFg6dC5zY3JvbGxMZWZ0LGJlaGF2aW9yOnJ9KX12YXIgSz1uZXcgV2Vha01hcDtmdW5jdGlvbiBOdCh0KXtsZXQgZT10PT09d2luZG93P2RvY3VtZW50LmRvY3VtZW50RWxlbWVudDp0LHI9ZS5zdHlsZSxpPUsuZ2V0KGUpO2l8fChpPXtjb3VudDowLGhhZFN0eWxlOmUuaGFzQXR0cmlidXRlKCJzdHlsZSIpLHZhbHVlOnIuZ2V0UHJvcGVydHlWYWx1ZSgib3ZlcmZsb3ctYW5jaG9yIikscHJpb3JpdHk6ci5nZXRQcm9wZXJ0eVByaW9yaXR5KCJvdmVyZmxvdy1hbmNob3IiKX0sSy5zZXQoZSxpKSxyLnNldFByb3BlcnR5KCJvdmVyZmxvdy1hbmNob3IiLCJub25lIiwiaW1wb3J0YW50IikpLGkuY291bnQrKztsZXQgYT0hMDtyZXR1cm4oKT0+e2EmJihhPSExLCEtLWkuY291bnQmJihLLmRlbGV0ZShlKSxpLnZhbHVlP3Iuc2V0UHJvcGVydHkoIm92ZXJmbG93LWFuY2hvciIsaS52YWx1ZSxpLnByaW9yaXR5KTpyLnJlbW92ZVByb3BlcnR5KCJvdmVyZmxvdy1hbmNob3IiKSwhaS5oYWRTdHlsZSYmIWUuZ2V0QXR0cmlidXRlKCJzdHlsZSIpJiZlLnJlbW92ZUF0dHJpYnV0ZSgic3R5bGUiKSkpfX1mdW5jdGlvbiBfdCh0KXtsZXQgZT1kb2N1bWVudC5jcmVhdGVSYW5nZSgpLHI9ZG9jdW1lbnQuY3JlYXRlVHJlZVdhbGtlcih0LE5vZGVGaWx0ZXIuU0hPV19URVhULHthY2NlcHROb2RlOmE9PntsZXQgbD1hLnBhcmVudEVsZW1lbnQ7aWYoIWEubm9kZVZhbHVlLnRyaW0oKXx8IWx8fGwuY2xvc2VzdCgnW2hpZGRlbl0sIFthcmlhLWhpZGRlbj0idHJ1ZSIgaV0sIFtpbmVydF0sIHN2ZycpfHxnZXRDb21wdXRlZFN0eWxlKGwpLnZpc2liaWxpdHkhPT0idmlzaWJsZSIpcmV0dXJuIE5vZGVGaWx0ZXIuRklMVEVSX1JFSkVDVDtmb3IobGV0IGM9bDtjO2M9Yy5wYXJlbnRFbGVtZW50KXtpZihnZXRDb21wdXRlZFN0eWxlKGMpLmNvbnRlbnRWaXNpYmlsaXR5PT09ImhpZGRlbiIpcmV0dXJuIE5vZGVGaWx0ZXIuRklMVEVSX1JFSkVDVDtpZihjPT09dClicmVha31yZXR1cm4gZS5zZWxlY3ROb2RlQ29udGVudHMoYSksZS5nZXRDbGllbnRSZWN0cygpLmxlbmd0aD9Ob2RlRmlsdGVyLkZJTFRFUl9BQ0NFUFQ6Tm9kZUZpbHRlci5GSUxURVJfUkVKRUNUfX0pLGk9IiI7Zm9yKDtyLm5leHROb2RlKCk7KWkrPXIuY3VycmVudE5vZGUubm9kZVZhbHVlKyIgIjtyZXR1cm4gaX1mdW5jdGlvbiAkdCh0KXtsZXQgZT1uZXcgQWJvcnRDb250cm9sbGVyLHI9bXQodCksaT0obixvLGYsaD17fSk9Pm4uYWRkRXZlbnRMaXN0ZW5lcihvLGYsey4uLmgsc2lnbmFsOmUuc2lnbmFsfSksYT1uPT5bLi4udC5xdWVyeVNlbGVjdG9yQWxsKG4pXS5maWx0ZXIobz0+by5jbG9zZXN0KFEpPT09dCksbD10LmdldEF0dHJpYnV0ZSgiYXJpYS1sYWJlbCIpPz8iIixjPTAsZD0wLG09MCxnPSExLHM9bnVsbCx1PW51bGwsdz0wLEY9cGVyZm9ybWFuY2Uubm93KCksXz0wLFQ9W10sQj0hMSxTPShuLG8pPT57dC5nZXRBdHRyaWJ1dGUobikhPT1vJiZ0LnNldEF0dHJpYnV0ZShuLG8pfTtmdW5jdGlvbiB5KCl7aWYoIXMpcmV0dXJuO2xldCBuPXM7aWYocz1udWxsLGNhbmNlbEFuaW1hdGlvbkZyYW1lKG4uZnJhbWUpLGNhbmNlbEFuaW1hdGlvbkZyYW1lKG4uYW5pbWF0aW9uKSxuLnJlc3RvcmU/LigpLG4ubmF0aXZlKXtsZXR7eTpvLG1heDpmfT1QKG4udGFyZ2V0KTtWKG4udGFyZ2V0LG8+MD9NYXRoLm1heCgwLG8tMSk6TWF0aC5taW4oZiwxKSwiaW5zdGFudCIpLFYobi50YXJnZXQsbywiaW5zdGFudCIpfX1sZXQgSD0oKT0+dT09PXdpbmRvdz9kb2N1bWVudC5ib2R5OnU7ZnVuY3Rpb24gTygpe3ouZGlzY29ubmVjdCgpO2xldCBuPUgoKTtpZihuKXt6Lm9ic2VydmUobik7Zm9yKGxldCBvIG9mIG4uY2hpbGRyZW4pei5vYnNlcnZlKG8pfX1mdW5jdGlvbiBXKG4pe24hPT11JiYoeSgpLHU9bixPKCksSi5kaXNjb25uZWN0KCksSCgpJiZKLm9ic2VydmUoSCgpLHtjaGlsZExpc3Q6ITB9KSxtPVAodSkueSxkPTAsZz0hMSl9ZnVuY3Rpb24geHQoKXtsZXQgbj1hKCIuZWQtYmFjay10by10b3BfX3Byb2dyZXNzIilbMF07aWYoIW58fHQuZGF0YXNldC5lZEJ0dEJ1dHRvblR5cGU9PT0icmVndWxhciIpcmV0dXJuO2xldCBvPWdldENvbXB1dGVkU3R5bGUodCksZj1hKCIuZWQtYmFjay10by10b3BfX3RyYWNrLCAuZWQtYmFjay10by10b3BfX3ZhbHVlIiksaD1wYXJzZUZsb2F0KGdldENvbXB1dGVkU3R5bGUobikudG9wKXx8MCxDPU1hdGgubWF4KDAsdC5jbGllbnRXaWR0aC0yKmgpLEE9TWF0aC5tYXgoMCx0LmNsaWVudEhlaWdodC0yKmgpO2lmKCFDfHwhQXx8IWYubGVuZ3RoKXJldHVybjtsZXQgUj1FPT5wYXJzZUZsb2F0KG9bYGJvcmRlciR7RX1XaWR0aGBdKXx8MCxwPXIoUnQubWFwKEU9Pm9bYGJvcmRlciR7RX1SYWRpdXNgXSksdC5vZmZzZXRXaWR0aCx0Lm9mZnNldEhlaWdodCkubWFwKCh7eDpFLHk6cX0sTik9PltFLVIoTj09PTB8fE49PT0zPyJMZWZ0IjoiUmlnaHQiKS1oLHEtUihOPDI/IlRvcCI6IkJvdHRvbSIpLWhdKSxrPXBhcnNlRmxvYXQoZ2V0Q29tcHV0ZWRTdHlsZShmWzBdKS5zdHJva2VXaWR0aCl8fDAseD1qKEMsQSxwLGspLEw9YDAgMCAke0N9ICR7QX1gO24uZ2V0QXR0cmlidXRlKCJ2aWV3Qm94IikhPT1MJiZuLnNldEF0dHJpYnV0ZSgidmlld0JveCIsTCksZi5mb3JFYWNoKEU9PntFLmdldEF0dHJpYnV0ZSgiZCIpIT09eCYmRS5zZXRBdHRyaWJ1dGUoImQiLHgpfSl9bGV0IGV0PSgpPT5aKHQuZGF0YXNldC5lZEJ0dEF2b2lkU2VsZWN0b3IsITApLmZpbHRlcihuPT4hbi5jb250YWlucyh0KSYmIXQuY29udGFpbnMobikpO2Z1bmN0aW9uIHd0KG4pe2lmKCFodChuLFQpKXtUPW4sWC5kaXNjb25uZWN0KCksWS5kaXNjb25uZWN0KCk7Zm9yKGxldCBvIG9mIG4pWC5vYnNlcnZlKG8pLFkub2JzZXJ2ZShvLHthdHRyaWJ1dGVzOiEwLGF0dHJpYnV0ZUZpbHRlcjpbInN0eWxlIiwiY2xhc3MiLCJoaWRkZW4iXX0pfX1mdW5jdGlvbiBBdChuKXtsZXQgbz10LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLGY9dC5vZmZzZXRIZWlnaHQsaD1NYXRoLm1heCgwLChwYXJzZUZsb2F0KGdldENvbXB1dGVkU3R5bGUodCkuYm90dG9tKXx8MCktdyksQz1pbm5lckhlaWdodC1oLEE9Qy1mLFI9bi5maWx0ZXIocHQpLm1hcChwPT5wLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpKS5maWx0ZXIocD0+KHAud2lkdGh8fHAuaGVpZ2h0KSYmcC5yaWdodD5vLmxlZnQmJnAubGVmdDxvLnJpZ2h0JiZwLnRvcDxpbm5lckhlaWdodCksdj0wO2ZvcihsZXQgcD0wO3A8PVIubGVuZ3RoO3ArKyl7bGV0IGs9Ui5yZWR1Y2UoKHgsTCk9PkwuYm90dG9tPkEtdj9NYXRoLm1heCh4LEMraC1MLnRvcCk6eCx2KTtpZihrPT09dilicmVhazt2PWt9cmV0dXJuIHY/dC5kYXRhc2V0LmVkQnR0QXZvaWRNb2RlPT09ImhpZGUifHxBLXY8aD8xLzA6djowfWZ1bmN0aW9uIG50KCl7Yz0wLGNsZWFyVGltZW91dChfKSxXKHl0KHQpKTtsZXR7eTpuLG1heDpvfT1QKHUpLGY9TWF0aC5taW4obyxNYXRoLm1heCgwLG4pKSxoPWYtbTtoJiZNYXRoLnNpZ24oaCkhPT1NYXRoLnNpZ24oZCkmJihkPTApLGQrPWgsTWF0aC5hYnMoZCk+PTQmJihnPWQ8MCxkPTApLG09ZixmPD0wJiYoZz0hMSk7bGV0IEM9YnQ/WyJvZmYiLCJoaWRkZW4iXS5pbmNsdWRlcyh0LmRhdGFzZXQuZWRCdHRQcmV2aWV3KT90LmRhdGFzZXQuZWRCdHRQcmV2aWV3OiJ2aXNpYmxlIjoib2ZmIixBPXQ9PT1kb2N1bWVudC5hY3RpdmVFbGVtZW50LFI9Z3QhPT0idG91Y2giJiZ0Lm1hdGNoZXMoIjpob3ZlciIpLHY9bz4wJiZmPmF0KHQuZGF0YXNldC5lZEJ0dFZpc2libGVBZnRlcikmJih0LmRhdGFzZXQuZWRCdHRPbmx5U2Nyb2xsaW5nVXAhPT0idHJ1ZSJ8fGcpLHA9MDtCPXYmJnQuZGF0YXNldC5lZEJ0dFBvc2l0aW9uIT09ImlubGluZSImJiEhdC5kYXRhc2V0LmVkQnR0QXZvaWRTZWxlY3Rvcj8udHJpbSgpO2xldCBrPUI/ZXQoKTpbXTt3dChrKSxrLmxlbmd0aCYmKHA9QXQoaykscD09PTEvMCYmKHA9MCx2PSExKSk7bGV0IHg9dj8idmlzaWJsZSI6ImhpZGRlbiIsTD1jdCh0LmRhdGFzZXQuZWRCdHRJZGxlSGlkZSk7aWYodiYmTCYmIVImJiFBKXtsZXQgTT1MLShwZXJmb3JtYW5jZS5ub3coKS1GKTtNPjA/Xz1zZXRUaW1lb3V0KGIsTSsxNik6eD0iaWRsZSJ9QSYmKHg9InZpc2libGUiKSxDIT09Im9mZiImJih4PUMpLHAhPT13JiYodz1wLHc/dC5zdHlsZS5zZXRQcm9wZXJ0eSgiLS1fZWQtYmFjay10by10b3AtbGlmdCIsdysicHgiKTp0LnN0eWxlLnJlbW92ZVByb3BlcnR5KCItLV9lZC1iYWNrLXRvLXRvcC1saWZ0IikpLFMoImRhdGEtZWQtYnR0LXN0YXRlIix4KSx0LmRpc2FibGVkPXg9PT0iaGlkZGVuIixTKCJhcmlhLWhpZGRlbiIsU3RyaW5nKHg9PT0iaGlkZGVuIikpO2xldCBFPWEoIi5lZC1iYWNrLXRvLXRvcF9fY29udGVudCIpWzBdLHE9dC5kYXRhc2V0LmVkQnR0Q29udGVudE1vZGU9PT0iY3VzdG9tIiYmRSYmWy4uLkUuY2hpbGROb2Rlc10uc29tZShNPT5NLm5vZGVUeXBlPT09Tm9kZS5FTEVNRU5UX05PREU/IU0uaGFzQXR0cmlidXRlKCJkYXRhLWVkLWJ0dC1mYWxsYmFjayIpOk0ubm9kZVR5cGU9PT1Ob2RlLlRFWFRfTk9ERSYmTS50ZXh0Q29udGVudC50cmltKCkpO2EoIltkYXRhLWVkLWJ0dC1mYWxsYmFja10iKS5mb3JFYWNoKE09PntNLmhpZGRlbiE9PSEhcSYmKE0uaGlkZGVuPSEhcSl9KTtsZXQgTj1kdCh0Lmhhc0F0dHJpYnV0ZSgiZGF0YS1lZC1idHQtbGFiZWwiKT90LmRhdGFzZXQuZWRCdHRMYWJlbDpsLF90KHQpKTtOPT09bnVsbD90LnJlbW92ZUF0dHJpYnV0ZSgiYXJpYS1sYWJlbCIpOlMoImFyaWEtbGFiZWwiLE4pLHh0KCk7bGV0IHJ0PVN0cmluZyhNYXRoLnJvdW5kKCgxMDAtKG8/Zi9vKjEwMDowKSkqMTAwKS8xMDApO2EoIi5lZC1iYWNrLXRvLXRvcF9fdmFsdWUiKS5mb3JFYWNoKE09PntNLmdldEF0dHJpYnV0ZSgic3Ryb2tlLWRhc2hvZmZzZXQiKSE9PXJ0JiZNLnNldEF0dHJpYnV0ZSgic3Ryb2tlLWRhc2hvZmZzZXQiLHJ0KX0pfWZ1bmN0aW9uIGIoKXtjfHwoYz1yZXF1ZXN0QW5pbWF0aW9uRnJhbWUobnQpKX1mdW5jdGlvbiBFdCgpe2xldCBuPW5ldyBTZXQoW1oodC5kYXRhc2V0LmVkQnR0Rm9jdXNUYXJnZXQpLC4uLmRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoIm1haW4gaDEiKSwuLi5kb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKCJoMSIpLGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoIm1haW4iKSxkb2N1bWVudC5ib2R5XSk7Zm9yKGxldCBvIG9mIG4pe2lmKCF2dChvKXx8dHlwZW9mIG8uZm9jdXMhPSJmdW5jdGlvbiJ8fHQuY29udGFpbnMobyl8fG8uY2xvc2VzdCgnW2luZXJ0XSwgW2FyaWEtaGlkZGVuPSJ0cnVlIiBpXScpfHwhcHQobykpY29udGludWU7bGV0IGY9by5nZXRBdHRyaWJ1dGUoInRhYmluZGV4IiksaD0oKT0+e28uZ2V0QXR0cmlidXRlKCJ0YWJpbmRleCIpPT09Ii0xIiYmKGY9PT1udWxsP28ucmVtb3ZlQXR0cmlidXRlKCJ0YWJpbmRleCIpOm8uc2V0QXR0cmlidXRlKCJ0YWJpbmRleCIsZikpfTtpZihvLnNldEF0dHJpYnV0ZSgidGFiaW5kZXgiLCItMSIpLG8uZm9jdXMoe3ByZXZlbnRTY3JvbGw6ITB9KSxkb2N1bWVudC5hY3RpdmVFbGVtZW50PT09byl7by5hZGRFdmVudExpc3RlbmVyKCJibHVyIiwoKT0+c2V0VGltZW91dChoLDApLHtvbmNlOiEwfSk7cmV0dXJufWgoKX19aSh0LCJjbGljayIsKCk9PntpZih0LmRpc2FibGVkfHxidClyZXR1cm47eSgpO2xldCBuPXV8fHl0KHQpLG89dC5kYXRhc2V0LmVkQnR0U2Nyb2xsQmVoYXZpb3IhPT0iaW5zdGFudCImJiFHLm1hdGNoZXMsZj1vP3N0KHQuZGF0YXNldC5lZEJ0dFNjcm9sbER1cmF0aW9uKTowLGg9ZG9jdW1lbnQuYWN0aXZlRWxlbWVudCxDPW5ldyBDdXN0b21FdmVudCgiZWQtYmFjay10by10b3A6YmVmb3JlIix7YnViYmxlczohMCxjYW5jZWxhYmxlOiEwLGRldGFpbDp7YmVoYXZpb3I6bz8ic21vb3RoIjoiaW5zdGFudCIsZHVyYXRpb246Zixjb250YWluZXI6bj09PXdpbmRvdz9udWxsOm59fSksQT0hdC5kaXNwYXRjaEV2ZW50KEMpO2lmKHM9e2ZyYW1lOjAsYW5pbWF0aW9uOjAsYW5pbWF0aW5nOiExLHRhcmdldDpuLG5hdGl2ZTohQSYmbyYmIWYsc3RhcnQ6cGVyZm9ybWFuY2Uubm93KCksbGltaXQ6TWF0aC5tYXgoNWUzLGYrMWUzKX0sIUEmJmYpe2xldCB2PVAobikueSxwPXBlcmZvcm1hbmNlLm5vdygpLGs9djtzLmFuaW1hdGluZz1zLmVhc2VkPSEwLHMucmVzdG9yZT1OdChuKTtsZXQgeD1MPT57aWYoIXMpcmV0dXJuO2xldHt5OkUsbWF4OnF9PVAobik7aWYoTWF0aC5hYnMoRS1NYXRoLm1pbihrLHEpKT5GdCl7eSgpO3JldHVybn1sZXQgTj1NYXRoLm1pbigxLChMLXApL2YpO1Yobix2KigxLWx0KE4pKSwiaW5zdGFudCIpLGs9UChuKS55LE48MT9zLmFuaW1hdGlvbj1yZXF1ZXN0QW5pbWF0aW9uRnJhbWUoeCk6KHMuYW5pbWF0aW5nPSExLHMucmVzdG9yZSgpKX07cy5hbmltYXRpb249cmVxdWVzdEFuaW1hdGlvbkZyYW1lKHgpfWVsc2UgQXx8VihuLDAsbz8ic21vb3RoIjoiaW5zdGFudCIpO2xldCBSPSgpPT57cyYmKCFzLmFuaW1hdGluZyYmTWF0aC5hYnMoUChuKS55KTw9MT8ocz1udWxsLChkb2N1bWVudC5hY3RpdmVFbGVtZW50PT09aHx8ZG9jdW1lbnQuYWN0aXZlRWxlbWVudD09PXQpJiZFdCgpLHQuZGlzcGF0Y2hFdmVudChuZXcgQ3VzdG9tRXZlbnQoImVkLWJhY2stdG8tdG9wOmNvbXBsZXRlIix7YnViYmxlczohMH0pKSxiKCkpOnMuZWFzZWQmJiFzLmFuaW1hdGluZ3x8cGVyZm9ybWFuY2Uubm93KCktcy5zdGFydD5zLmxpbWl0P3koKTpzLmZyYW1lPXJlcXVlc3RBbmltYXRpb25GcmFtZShSKSl9O3MuZnJhbWU9cmVxdWVzdEFuaW1hdGlvbkZyYW1lKFIpfSk7Zm9yKGxldCBuIG9mIGt0KWkod2luZG93LG4seSx7cGFzc2l2ZTohMCxjYXB0dXJlOiEwfSk7aSh0LCJmb2N1cyIsYiksaSh0LCJibHVyIixiKSxpKHQsInBvaW50ZXJsZWF2ZSIsYiksaShkb2N1bWVudCwic2Nyb2xsIixuPT57KG4udGFyZ2V0PT09ZG9jdW1lbnR8fG4udGFyZ2V0PT09dSkmJihGPXBlcmZvcm1hbmNlLm5vdygpKSwobi50YXJnZXQ9PT1kb2N1bWVudHx8bi50YXJnZXQ9PT11fHx0LmRhdGFzZXQuZWRCdHRBdm9pZFNlbGVjdG9yPy50cmltKCkpJiZiKCl9LHtwYXNzaXZlOiEwLGNhcHR1cmU6ITB9KSxpKHdpbmRvdywicmVzaXplIixiLHtwYXNzaXZlOiEwfSksaSh3aW5kb3csInBhZ2VzaG93IixiKSxpKHdpbmRvdywiZWQtYmFjay10by10b3A6cmVmcmVzaCIsYix7Y2FwdHVyZTohMH0pO2xldCBNdD1uPT57QiYmbi50YXJnZXQ/Lm5vZGVUeXBlJiZULnNvbWUobz0+bi50YXJnZXQuY29udGFpbnMobykpJiZiKCl9O2ZvcihsZXQgbiBvZlsidHJhbnNpdGlvbmVuZCIsImFuaW1hdGlvbmVuZCJdKWkoZG9jdW1lbnQsbixNdCx7cGFzc2l2ZTohMCxjYXB0dXJlOiEwfSk7bGV0IFN0PW49PntbInZpc2liaWxpdHkiLCJkaXNwbGF5IiwiY29udGVudC12aXNpYmlsaXR5Il0uaW5jbHVkZXMobi5wcm9wZXJ0eU5hbWUpJiZiKCl9O2ZvcihsZXQgbiBvZlsidHJhbnNpdGlvbmVuZCIsInRyYW5zaXRpb25jYW5jZWwiXSlpKHQsbixTdCx7cGFzc2l2ZTohMH0pO2koRywiY2hhbmdlIiwoKT0+e3MmJkcubWF0Y2hlcyYmKGNhbmNlbEFuaW1hdGlvbkZyYW1lKHMuYW5pbWF0aW9uKSxzLnJlc3RvcmU/LigpLHMuYW5pbWF0aW5nPSExLHMubmF0aXZlPSExLFYocy50YXJnZXQsMCwiaW5zdGFudCIpKSxiKCl9KTtsZXQgST1uZXcgUmVzaXplT2JzZXJ2ZXIoYik7SS5vYnNlcnZlKGRvY3VtZW50LmRvY3VtZW50RWxlbWVudCksZG9jdW1lbnQuYm9keSYmSS5vYnNlcnZlKGRvY3VtZW50LmJvZHkpLEkub2JzZXJ2ZSh0KTtsZXQgej1uZXcgUmVzaXplT2JzZXJ2ZXIoYiksSj1uZXcgTXV0YXRpb25PYnNlcnZlcigoKT0+e08oKSxiKCl9KSxYPW5ldyBSZXNpemVPYnNlcnZlcihiKSxZPW5ldyBNdXRhdGlvbk9ic2VydmVyKGIpLGl0PW5ldyBNdXRhdGlvbk9ic2VydmVyKGIpO2l0Lm9ic2VydmUodCx7YXR0cmlidXRlczohMCxhdHRyaWJ1dGVGaWx0ZXI6Q3R9KTtsZXQgb3Q9bmV3IE11dGF0aW9uT2JzZXJ2ZXIoYik7cmV0dXJuIG90Lm9ic2VydmUodCx7c3VidHJlZTohMCxjaGlsZExpc3Q6ITAsY2hhcmFjdGVyRGF0YTohMCxhdHRyaWJ1dGVzOiEwLGF0dHJpYnV0ZUZpbHRlcjpbInN0eWxlIiwiY2xhc3MiLCJoaWRkZW4iLCJhcmlhLWhpZGRlbiIsImluZXJ0Il19KSxudCgpLHttdXRhdGVkKCl7QiYmIWh0KGV0KCksVCkmJmIoKX0sZGlzcG9zZSgpe3koKSxjYW5jZWxBbmltYXRpb25GcmFtZShjKSxjbGVhclRpbWVvdXQoXyksci5kaXNwb3NlKCksZS5hYm9ydCgpO2ZvcihsZXQgbiBvZltJLHosSixYLFksaXQsb3RdKW4uZGlzY29ubmVjdCgpO3Quc3R5bGUucmVtb3ZlUHJvcGVydHkoIi0tX2VkLWJhY2stdG8tdG9wLWxpZnQiKSx0LmRpc2FibGVkPSEwLHQuZGF0YXNldC5lZEJ0dFN0YXRlPSJoaWRkZW4iLHQuc2V0QXR0cmlidXRlKCJhcmlhLWhpZGRlbiIsInRydWUiKX19fWZ1bmN0aW9uIHR0KCl7Zm9yKGxldFt0LGVdb2YgRCkoIXQuaXNDb25uZWN0ZWR8fCF0Lm1hdGNoZXMoUSkpJiYoZS5kaXNwb3NlKCksRC5kZWxldGUodCkpO2RvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoUSkuZm9yRWFjaCh0PT57RC5oYXModCl8fEQuc2V0KHQsJHQodCkpfSl9dmFyIEh0PW5ldyBNdXRhdGlvbk9ic2VydmVyKHQ9PntpZih0LnNvbWUoZT0+ZS5hZGRlZE5vZGVzLmxlbmd0aHx8ZS5yZW1vdmVkTm9kZXMubGVuZ3RoKSl7dHQoKTtmb3IobGV0IGUgb2YgRC52YWx1ZXMoKSllLm11dGF0ZWQoKX19KTtnbG9iYWxUaGlzW1N5bWJvbC5mb3IoImVkLmJhY2stdG8tdG9wLnYyIildPXtzY2FuOnR0fTtIdC5vYnNlcnZlKGRvY3VtZW50LmRvY3VtZW50RWxlbWVudCx7Y2hpbGRMaXN0OiEwLHN1YnRyZWU6ITB9KTt0dCgpO30pKCk7Cn0K",
"id": "vgzfhcp"
},
"tag": "button",
"attributes": {
"type": "button",
"aria-label": "{props.button.ariaLabel}",
"data-ed-btt-label": "{props.button.ariaLabel}",
"aria-hidden": "true",
"disabled": "",
"data-ed-btt-state": "hidden",
"data-ed-btt-preview": "{props.preview}",
"data-ed-btt-button-type": "{props.button.buttonType}",
"data-ed-btt-content-mode": "{props.button.contentMode}",
"data-ed-btt-icon": "{props.button.icon}",
"data-ed-btt-layout": "{props.button.layout}",
"data-ed-btt-line-cap": "{props.button.lineCap}",
"data-ed-btt-position": "{props.visibility.position}",
"data-ed-btt-visible-after": "{props.visibility.visibleAfter}",
"data-ed-btt-only-scrolling-up": "{props.visibility.onlyScrollingUp}",
"data-ed-btt-idle-hide": "{props.visibility.idleHide}",
"data-ed-btt-reveal": "{props.visibility.reveal}",
"data-ed-btt-avoid-selector": "{props.overlap.avoidSelector}",
"data-ed-btt-avoid-mode": "{props.overlap.avoidMode}",
"data-ed-btt-scroll-behavior": "{props.scrolling.scrollBehavior}",
"data-ed-btt-scroll-duration": "{props.scrolling.scrollDuration}",
"data-ed-btt-scroll-container": "{props.scrolling.scrollContainer}",
"data-ed-btt-focus-target": "{props.scrolling.focusTarget}",
"class": "ed-back-to-top"
},
"styles": [
"ntb250h"
]
},
"innerBlocks": [
{
"blockName": "etch/condition",
"attrs": {
"condition": {
"leftHand": "props.button.buttonType",
"operator": "!=",
"rightHand": "\"regular\""
},
"conditionString": "props.button.buttonType != \"regular\""
},
"innerBlocks": [
{
"blockName": "etch/element",
"attrs": {
"metadata": {
"name": "Progress"
},
"tag": "svg",
"attributes": {
"viewBox": "0 0 60 60",
"aria-hidden": "true",
"focusable": "false",
"class": "ed-back-to-top__progress"
},
"styles": [
"g5ioi34"
]
},
"innerBlocks": [
{
"blockName": "etch/element",
"attrs": {
"metadata": {
"name": "Track"
},
"tag": "path",
"attributes": {
"d": "M30 1H30A29 29 0 0 1 59 30V30A29 29 0 0 1 30 59H30A29 29 0 0 1 1 30V30A29 29 0 0 1 30 1H30",
"class": "ed-back-to-top__track"
},
"styles": [
"qcrehvc"
]
},
"innerBlocks": [],
"innerHTML": "\n\n",
"innerContent": [
"\n",
"\n"
]
},
{
"blockName": "etch/element",
"attrs": {
"metadata": {
"name": "Value"
},
"tag": "path",
"attributes": {
"d": "M30 1H30A29 29 0 0 1 59 30V30A29 29 0 0 1 30 59H30A29 29 0 0 1 1 30V30A29 29 0 0 1 30 1H30",
"pathLength": "100",
"stroke-dasharray": "100",
"stroke-dashoffset": "100",
"class": "ed-back-to-top__value"
},
"styles": [
"7o0or4q"
]
},
"innerBlocks": [],
"innerHTML": "\n\n",
"innerContent": [
"\n",
"\n"
]
}
],
"innerHTML": "\n\n\n\n",
"innerContent": [
"\n",
null,
"\n\n",
null,
"\n"
]
}
],
"innerHTML": "\n\n",
"innerContent": [
"\n",
null,
"\n"
]
},
{
"blockName": "etch/element",
"attrs": {
"metadata": {
"name": "Content"
},
"tag": "span",
"attributes": {
"class": "ed-back-to-top__content"
},
"styles": [
"db25vfg"
]
},
"innerBlocks": [
{
"blockName": "etch/condition",
"attrs": {
"condition": {
"leftHand": {
"leftHand": "props.button.icon",
"operator": "!=",
"rightHand": "\"none\""
},
"operator": "||",
"rightHand": {
"leftHand": "props.button.buttonText.trim()",
"operator": "==",
"rightHand": "\"\""
}
},
"conditionString": "props.button.icon != \"none\" || props.button.buttonText.trim() == \"\""
},
"innerBlocks": [
{
"blockName": "etch/element",
"attrs": {
"metadata": {
"name": "Icon"
},
"tag": "span",
"attributes": {
"aria-hidden": "true",
"data-ed-btt-fallback": "",
"class": "ed-back-to-top__icon"
},
"styles": [
"po9ac6t"
]
},
"innerBlocks": [
{
"blockName": "etch/condition",
"attrs": {
"condition": {
"leftHand": "props.button.icon",
"operator": "==",
"rightHand": "\"arrow\""
},
"conditionString": "props.button.icon == \"arrow\""
},
"innerBlocks": [
{
"blockName": "etch/element",
"attrs": {
"metadata": {
"name": "Arrow"
},
"tag": "svg",
"attributes": {
"viewBox": "0 0 24 24",
"aria-hidden": "true",
"focusable": "false",
"class": "ed-back-to-top__arrow"
},
"styles": [
"om75mg2"
]
},
"innerBlocks": [
{
"blockName": "etch/element",
"attrs": {
"tag": "path",
"attributes": {
"d": "M12 19V5M5 12l7-7 7 7"
}
},
"innerBlocks": [],
"innerHTML": "\n\n",
"innerContent": [
"\n",
"\n"
]
}
],
"innerHTML": "\n\n",
"innerContent": [
"\n",
null,
"\n"
]
}
],
"innerHTML": "\n\n",
"innerContent": [
"\n",
null,
"\n"
]
},
{
"blockName": "etch/condition",
"attrs": {
"condition": {
"leftHand": {
"leftHand": "props.button.icon",
"operator": "==",
"rightHand": "\"custom\""
},
"operator": "&&",
"rightHand": {
"leftHand": {
"leftHand": "props.button.customIcon",
"operator": "isTruthy",
"rightHand": null
},
"operator": "&&",
"rightHand": {
"leftHand": "props.button.customIconColors",
"operator": "!=",
"rightHand": "\"original\""
}
}
},
"conditionString": "props.button.icon == \"custom\" && props.button.customIcon && props.button.customIconColors != \"original\""
},
"innerBlocks": [
{
"blockName": "etch/svg",
"attrs": {
"metadata": {
"name": "Custom Icon"
},
"tag": "svg",
"attributes": {
"src": "{props.button.customIcon}",
"stripColors": "true",
"data-ed-btt-colors": "text",
"aria-hidden": "true",
"focusable": "false",
"class": "ed-back-to-top__custom-icon"
},
"styles": [
"ucirgcb"
]
},
"innerBlocks": [],
"innerHTML": "\n\n",
"innerContent": [
"\n",
"\n"
]
}
],
"innerHTML": "\n\n",
"innerContent": [
"\n",
null,
"\n"
]
},
{
"blockName": "etch/condition",
"attrs": {
"condition": {
"leftHand": {
"leftHand": "props.button.icon",
"operator": "==",
"rightHand": "\"custom\""
},
"operator": "&&",
"rightHand": {
"leftHand": {
"leftHand": "props.button.customIcon",
"operator": "isTruthy",
"rightHand": null
},
"operator": "&&",
"rightHand": {
"leftHand": "props.button.customIconColors",
"operator": "==",
"rightHand": "\"original\""
}
}
},
"conditionString": "props.button.icon == \"custom\" && props.button.customIcon && props.button.customIconColors == \"original\""
},
"innerBlocks": [
{
"blockName": "etch/svg",
"attrs": {
"metadata": {
"name": "Custom Icon"
},
"tag": "svg",
"attributes": {
"src": "{props.button.customIcon}",
"data-ed-btt-colors": "original",
"aria-hidden": "true",
"focusable": "false",
"class": "ed-back-to-top__custom-icon"
},
"styles": [
"ucirgcb"
]
},
"innerBlocks": [],
"innerHTML": "\n\n",
"innerContent": [
"\n",
"\n"
]
}
],
"innerHTML": "\n\n",
"innerContent": [
"\n",
null,
"\n"
]
},
{
"blockName": "etch/condition",
"attrs": {
"condition": {
"leftHand": {
"leftHand": "props.button.icon",
"operator": "!=",
"rightHand": "\"arrow\""
},
"operator": "&&",
"rightHand": {
"leftHand": {
"leftHand": {
"leftHand": "props.button.icon",
"operator": "==",
"rightHand": "\"custom\""
},
"operator": "&&",
"rightHand": {
"leftHand": "props.button.customIcon",
"operator": "isTruthy",
"rightHand": null
}
},
"operator": "isFalsy",
"rightHand": null
}
},
"conditionString": "props.button.icon != \"arrow\" && !(props.button.icon == \"custom\" && props.button.customIcon)"
},
"innerBlocks": [
{
"blockName": "etch/element",
"attrs": {
"metadata": {
"name": "Chevron"
},
"tag": "svg",
"attributes": {
"viewBox": "0 0 24 24",
"aria-hidden": "true",
"focusable": "false",
"class": "ed-back-to-top__chevron"
},
"styles": [
"9hn50ch"
]
},
"innerBlocks": [
{
"blockName": "etch/element",
"attrs": {
"tag": "path",
"attributes": {
"d": "M6 15l6-6 6 6"
}
},
"innerBlocks": [],
"innerHTML": "\n\n",
"innerContent": [
"\n",
"\n"
]
}
],
"innerHTML": "\n\n",
"innerContent": [
"\n",
null,
"\n"
]
}
],
"innerHTML": "\n\n",
"innerContent": [
"\n",
null,
"\n"
]
}
],
"innerHTML": "\n\n\n\n\n\n\n\n",
"innerContent": [
"\n",
null,
"\n\n",
null,
"\n\n",
null,
"\n\n",
null,
"\n"
]
}
],
"innerHTML": "\n\n",
"innerContent": [
"\n",
null,
"\n"
]
},
{
"blockName": "etch/condition",
"attrs": {
"condition": {
"leftHand": "props.button.buttonText.trim()",
"operator": "!=",
"rightHand": "\"\""
},
"conditionString": "props.button.buttonText.trim() != \"\""
},
"innerBlocks": [
{
"blockName": "etch/element",
"attrs": {
"metadata": {
"name": "Text"
},
"tag": "span",
"attributes": {
"data-ed-btt-fallback": "",
"class": "ed-back-to-top__text"
},
"styles": [
"yro05ei"
]
},
"innerBlocks": [
{
"blockName": "etch/text",
"attrs": {
"metadata": {
"name": "Text"
},
"content": "{props.button.buttonText}"
},
"innerBlocks": [],
"innerHTML": "",
"innerContent": []
}
],
"innerHTML": "\n\n",
"innerContent": [
"\n",
null,
"\n"
]
}
],
"innerHTML": "\n\n",
"innerContent": [
"\n",
null,
"\n"
]
},
{
"blockName": "etch/condition",
"attrs": {
"condition": {
"leftHand": "props.button.contentMode",
"operator": "==",
"rightHand": "\"custom\""
},
"conditionString": "props.button.contentMode == \"custom\""
},
"innerBlocks": [
{
"blockName": "etch/slot-placeholder",
"attrs": {
"name": "content"
},
"innerBlocks": [],
"innerHTML": "\n\n",
"innerContent": [
"\n",
"\n"
]
}
],
"innerHTML": "\n\n",
"innerContent": [
"\n",
null,
"\n"
]
}
],
"innerHTML": "\n\n\n\n\n\n",
"innerContent": [
"\n",
null,
"\n\n",
null,
"\n\n",
null,
"\n"
]
}
],
"innerHTML": "\n\n\n\n",
"innerContent": [
"\n",
null,
"\n\n",
null,
"\n"
]
}
],
"properties": [
{
"name": "Builder preview",
"key": "preview",
"description": "Builder canvas only: Visible keeps the button on screen while you edit, Follow scroll uses the frontend rules. The frontend always follows the scroll rules.",
"type": {
"primitive": "string",
"specialized": "select"
},
"default": "visible",
"selectOptionsString": "Visible : visible\nFollow scroll : off\nHidden : hidden"
},
{
"name": "Button",
"key": "button",
"description": "Button type, content, ARIA label, icon and inner layout.",
"type": {
"primitive": "object",
"specialized": "group"
},
"properties": [
{
"name": "Button type",
"key": "buttonType",
"description": "Progress ring that follows the button shape, or a regular button.",
"type": {
"primitive": "string",
"specialized": "select"
},
"default": "progress",
"selectOptionsString": "Progress circle : progress\nRegular : regular"
},
{
"name": "Content",
"key": "contentMode",
"description": "Custom content uses the content slot; an empty slot falls back to icon/text.",
"type": {
"primitive": "string",
"specialized": "select"
},
"default": "icon-text",
"selectOptionsString": "Icon / text : icon-text\nCustom content : custom"
},
{
"name": "Button text",
"key": "buttonText",
"description": "Visible text. It also becomes the accessible name unless the ARIA label contains it.",
"type": {
"primitive": "string"
},
"default": ""
},
{
"name": "ARIA label",
"key": "ariaLabel",
"description": "Name for icon-only buttons, in your site language. With visible text it is used only when it contains that text.",
"type": {
"primitive": "string"
},
"default": "Back to top"
},
{
"name": "Icon",
"key": "icon",
"description": "Decorative icon. Custom SVG uses the SVG file; without a file it shows the chevron.",
"type": {
"primitive": "string",
"specialized": "select"
},
"default": "chevron",
"selectOptionsString": "Chevron : chevron\nArrow : arrow\nCustom SVG : custom\nNone : none"
},
{
"name": "SVG file",
"key": "customIcon",
"description": "SVG from the Media Library or its URL, used when Icon is Custom SVG. Etch inlines it; it stays decorative.",
"type": {
"primitive": "string",
"specialized": "image"
},
"default": ""
},
{
"name": "SVG colors",
"key": "customIconColors",
"description": "Use text color replaces the SVG colors with currentColor (Etch Strip Colors), so icon color tokens and hover apply. Keep original colors suits multicolor icons.",
"type": {
"primitive": "string",
"specialized": "select"
},
"default": "text",
"selectOptionsString": "Use text color : text\nKeep original colors : original"
},
{
"name": "Inner layout",
"key": "layout",
"description": "Flex direction of icon, text and custom content.",
"type": {
"primitive": "string",
"specialized": "select"
},
"default": "row",
"selectOptionsString": "Row : row\nReversed row : row-reverse\nColumn : column"
},
{
"name": "Ring line cap",
"key": "lineCap",
"description": "Progress ring stroke line cap.",
"type": {
"primitive": "string",
"specialized": "select"
},
"default": "round",
"selectOptionsString": "Round : round\nSquare : butt"
}
]
},
{
"name": "Visibility",
"key": "visibility",
"description": "Where the button sits and when it shows: threshold, scroll direction, auto-hide and reveal animation.",
"type": {
"primitive": "object",
"specialized": "group"
},
"properties": [
{
"name": "Position",
"key": "position",
"description": "Start and end follow the document direction. Inline stays in the page flow.",
"type": {
"primitive": "string",
"specialized": "select"
},
"default": "bottom-end",
"selectOptionsString": "Bottom end : bottom-end\nBottom start : bottom-start\nBottom center : bottom-center\nInline : inline"
},
{
"name": "Show after (px)",
"key": "visibleAfter",
"description": "Decimal pixels 0–100000; invalid input uses 100.",
"type": {
"primitive": "string"
},
"default": "100"
},
{
"name": "Scroll-up only",
"key": "onlyScrollingUp",
"description": "Reveal after upward motion beyond a 4px jitter tolerance.",
"type": {
"primitive": "string",
"specialized": "select"
},
"default": "false",
"selectOptionsString": "No : false\nYes : true"
},
{
"name": "Auto-hide (ms)",
"key": "idleHide",
"description": "Empty or 0 keeps the button visible. 500–60000 hides it after that time without scrolling; hover and focus keep it visible.",
"type": {
"primitive": "string"
},
"default": ""
},
{
"name": "Animation",
"key": "reveal",
"description": "Show and hide animation; tune it with the hidden-offset, hidden-scale and hidden-transform CSS tokens.",
"type": {
"primitive": "string",
"specialized": "select"
},
"default": "slide",
"selectOptionsString": "Slide : slide\nFade : fade\nScale : scale\nNone : none\nCustom transform : custom"
}
]
},
{
"name": "Overlap",
"key": "overlap",
"description": "Elements a fixed button must not cover, and what it does when they overlap it.",
"type": {
"primitive": "object",
"specialized": "group"
},
"properties": [
{
"name": "Avoid elements",
"key": "avoidSelector",
"description": "CSS selector of elements a fixed button must not cover, e.g. footer, .cookie-banner. Empty turns this off.",
"type": {
"primitive": "string"
},
"default": ""
},
{
"name": "On overlap",
"key": "avoidMode",
"description": "Move the button above avoided elements, or hide it while they overlap it.",
"type": {
"primitive": "string",
"specialized": "select"
},
"default": "lift",
"selectOptionsString": "Move above : lift\nHide : hide"
}
]
},
{
"name": "Scrolling",
"key": "scrolling",
"description": "How the return scrolls, which element scrolls and where focus lands.",
"type": {
"primitive": "object",
"specialized": "group"
},
"properties": [
{
"name": "Scroll behavior",
"key": "scrollBehavior",
"description": "Reduced motion always returns immediately.",
"type": {
"primitive": "string",
"specialized": "select"
},
"default": "smooth",
"selectOptionsString": "Smooth : smooth\nInstant : instant"
},
{
"name": "Scroll time (ms)",
"key": "scrollDuration",
"description": "Empty uses native browser smooth scrolling. 100–5000 uses an eased scroll of that duration.",
"type": {
"primitive": "string"
},
"default": ""
},
{
"name": "Scroll container",
"key": "scrollContainer",
"description": "Empty uses the page and detects themes that scroll the body. A selector uses that element instead.",
"type": {
"primitive": "string"
},
"default": ""
},
{
"name": "Focus target",
"key": "focusTarget",
"description": "Optional selector near the scroll start; default main heading, main, then body. For a scroll panel, set a heading inside it.",
"type": {
"primitive": "string"
},
"default": ""
}
]
}
],
"description": "Back to Top 1.0.0-beta.5 — scroll progress, overlap avoidance and accessible return"
}
},
"version": 2.1
}
Stay up to date. Etch won’t update a pasted component for you. Subscribe and get one email when a new version is out.
One corner button per page
Scroll the demo: the corner button fills its ring, rises above the cookie notice and the footer, and brings you back. Three scroll panels have buttons of their own. Switch to Mobile for a phone width.
What it does
One pasted component and no setup: no library, no icon font and no external script. The CSS and the script travel with it. In the builder the button stays on screen, so every setting you change shows at once.
A ring that follows the shape
The progress ring fills as the page scrolls and traces the button’s outline: a circle, a pill with text, a rounded square, even a different radius on each corner.
Shows up when it helps
It appears after the scroll distance you set. It can show only when the visitor scrolls back up, or fade out after a few seconds without scrolling. A focused button always stays visible.
Stays clear of footers and cookie bars
Name the elements it must not cover. It moves above them, or hides while they overlap it, and follows banners that appear, resize or slide in without waiting for a scroll.
Accessible by default
A native button with a 44 px minimum target and a visible focus ring. While hidden, it is disabled and out of the accessibility tree; Auto-hide only fades it out, so Tab and screen readers still reach it. After the return, focus moves to the main heading.
Icon, text or your own content
Chevron, arrow or any SVG from the Media Library, with or without text, in a row or a column. Or fill the content slot with your own text, images and decorative SVG.
Smooth, eased or instant
The browser’s smooth scrolling, or an eased scroll of a set duration. Reduced motion always returns at once, and any wheel, touch, pointer or key input stops a return.
Works with any scroll setup
The page, themes that scroll the body, or a panel with its own scrollbar. Events let a smooth-scroll library such as Lenis take over the return.
41 styling variables
Size, colors, border, radius, shadow, ring, hover, focus and the animation, set on any parent class. ACSS 4 tokens with plain fallbacks, so it works without Automatic.css too.
Add it in four steps
No plugin and no build step. The component brings its own CSS and script and loads nothing from a CDN. It works with or without Automatic.css 4.
Shows without its styles? It was pasted but not saved, and pasting again won’t repair it. Delete ED Back to Top from your components, paste it again and click Save.
- Copy the componentUse Copy component at the top of this page, or download the .json file.
- Paste it once per pageSelect a footer template or the end of the page, so every page gets one button, then press Ctrl+V or ⌘V and click Save. Etch adds the component to your list right away but stores its CSS only when you save.
- Name it in your languageIn the Button group, set ARIA label in your site language, such as Powrót na górę. It names the button when it shows only an icon.
- Save and scrollIn the builder the button stays on screen, so every setting you change shows at once. On the page it appears after 100 px of scrolling.
21 settings in the Etch props panel
Builder preview comes first, then four groups as in the Etch props panel, where each setting has help text too. Nothing is required: the defaults give a round progress button in the bottom corner.
Before you place it
Things that are easy to miss when the button goes onto a real site.
- One button per page. Paste it into a footer template or at the end of the page. Two fixed buttons in the same corner would sit on top of each other.
- Keep it out of transformed parents. A parent with
transform,filterorcontainchanges how a fixed button is placed, so don’t put it inside an animated wrapper. - Scroll-up only hides it from some screen reader users. Someone who reads without scrolling reaches the button only while it shows, so leave it off when the button should always be there after the threshold. Auto-hide only fades the button out and keeps it in the reading order.
- Custom content stays noninteractive. The slot sits inside a native button: text, images and decorative SVG only, no links, buttons or form fields. Give an image-only slot
alt=""and set ARIA label. - Smooth-scroll libraries can take over. With Lenis or GSAP ScrollSmoother, cancel
ed-back-to-top:beforeand start your own scroll. The button still moves focus and fires complete. - Tell it about late changes. After adding a scroll container later, or changing its variables on a parent, dispatch
ed-back-to-top:refreshon document. - Not tested yet: NVDA and VoiceOver on iOS. It was checked in Chrome, Firefox, Safari and Microsoft Edge, with VoiceOver on macOS, TalkBack on an Android emulator and Safari in the iOS Simulator.
Free to use. Not for resale.
Licensed under FSL-1.1-MIT, the Functional Source License with an MIT future license. Each version becomes MIT-licensed two years after its release.
- Use it on unlimited personal, client and commercial websites
- Modify and adapt it however you like
- Use it in paid design and development work for your clients
- Sell it, or a modified version, as a component: in a library, bundle, template kit, plugin or marketplace listing
- Offer it in another commercial product or service that competes with it
Keep the copyright and license notices in the code, and link to this page instead of re-hosting the download, so everyone gets fixes and updates. Read the full license
Third-party code
None. This component is all my own code under FSL-1.1-MIT. It bundles no library and loads no icon font or external script.
How to update
Etch has no update button for pasted components. Pasting a newer version over an installed one changes nothing: Etch keeps the component and the CSS it already has. To update, reinstall in five steps.
- Note what you setWrite down the settings of each button and copy any CSS you changed in the component’s own styles.
- Delete the componentIn Etch, delete ED Back to Top from your components. The button disappears from every page that used it.
- Delete its stylesIn the Style Manager, search for ed-back-to-top and delete the ten styles listed below.
- Paste the new versionCopy or download the new JSON from this page, paste it on the canvas and click Save. Etch creates the component and its CSS fresh.
- Put your buttons backInsert the button wherever it was, usually in a footer template, re-enter its settings and save. Reload any other open builder tab.
Styles to delete in step 3
.ed-back-to-top.ed-back-to-top__progress.ed-back-to-top__track.ed-back-to-top__value.ed-back-to-top__content.ed-back-to-top__icon.ed-back-to-top__chevron.ed-back-to-top__arrow.ed-back-to-top__custom-icon.ed-back-to-top__text
Trade-offs
- Placements are not kept. Etch doesn’t link old buttons to the new component, so you insert each one again and re-enter its settings.
- Edits inside the component’s CSS are replaced. Keep overrides on a parent class with the
--ed-back-to-top-*variables; those survive an update. - Skipping a step leaves a mix. Without step 2, Etch keeps the old component. Without step 3, the new version runs on the old CSS.
Coming from 1.0.0-beta.4? Follow the five steps above. Settings, classes and styling variables are unchanged, so you can re-enter your settings as they were. With Automatic.css, button text now uses the component’s text defaults (see the changelog). Subscribe and you’ll get one email when the next version is out.
What changed
Versions follow semantic versioning. When an update changes how existing options behave, its entry says so.
1.0.0-beta.5Latest
- FixedWith reduced motion on a site that uses Automatic.css, Safari and other WebKit browsers kept the English fallback name “Back to top” on a button with visible text until the next scroll, so voice control could miss it. The name now follows the visible text as soon as it shows.
- FixedOn Automatic.css sites the font family, font weight, line height, letter spacing and text transform variables did nothing, because ACSS styles every plain button with a stronger rule. They now work there too, on the page and in the builder.
- FixedAn empty Builder preview, for example from a site component that passes it down without a value, hid the button in the canvas. It now counts as Visible.
- ChangedOn Automatic.css sites, button text now uses the component’s text defaults, such as line height 1.2, instead of your ACSS button text settings. To keep those, set
--ed-back-to-top-line-height: var(--btn-line-height)and the other text variables from the matching--btn-*variables on:root. - ChangedThe script and the CSS of
.ed-back-to-topchanged, so update the component and its styles together (see How to update). Props, classes and the 41 styling variables are the same. - TestedChrome, Firefox, Microsoft Edge and WebKit on the desktop, with and without reduced motion, and Safari on iPhone in the iOS Simulator with Reduce Motion, on Etch 1.7.1 with Automatic.css. It stays a beta until NVDA and VoiceOver on a real iPhone are tested.
1.0.0-beta.4
- ChangedAuto-hide now only fades the button out. It stays enabled and in the reading order, so Tab and screen readers still reach it, and it shows as soon as it takes focus. Clicks and taps on its spot reach the page, and it still moves above avoided elements.
- FixedWith TalkBack resting on the button, Auto-hide hid it about 2.6 s after a scroll: TalkBack lost its place, read the page from the top, and a double tap did nothing. TalkBack now stays on the faded button, and a double tap returns to the top.
- FixedOn Android phones, one tap on the button stopped Auto-hide until the visitor tapped somewhere else, because Chrome keeps the hover state after a tap. Hover now counts only for a mouse or a pen.
- ChangedAn auto-hidden button has the new state
idle, so site CSS that targets[data-ed-btt-state="hidden"]no longer matches it. Below the threshold, under Scroll-up only and when avoided elements hide it, the button ishiddenas before. - ChangedThe script and the CSS of
.ed-back-to-topchanged, so update the component and its styles together (see How to update). Props, classes and the 41 styling variables are the same. - TestedTalkBack on Android, Safari on iPhone and iPad in the iOS Simulator, VoiceOver with Safari on macOS, and Chrome, Firefox, Microsoft Edge and WebKit on the desktop. It stays a beta until NVDA and VoiceOver on a real iPhone are tested.
1.0.0-beta.3
- ReleasedFirst public version. It stays a beta until NVDA, iOS and Android devices and Microsoft Edge are tested.
Tell me what this component needs
A bug, a missing option, or anything that made the component awkward to use in a real build — it all belongs here. The versions above are the ones it was tested on; if yours differ, say so.