Style Guide
Client-First is a set of guidelines and strategies created by Finsweet to help you build Webflow websites.
Structure Classes
Defined and flexible core structure we can use on all or most pages.
Headings
HTML tags define default Heading styles. Use Heading classes when the typography style doesn't match the default HTML tag.
Sample text helps you understand how real text may look. Sample text is being used as a placeholder.
Sample text helps you understand how real text may look. Sample text is being used as a placeholder.
Sample text is being used as a placeholder. Sample text helps you understand how real text may look.
Sample text is being used as a placeholder. Sample text helps you understand how real text may look.
Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text that is normally present.
Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text that is normally present.
Sample text is being used as a placeholder. Sample text helps you understand how real text may look. Sample text is being used as a placeholder for real text that is normally present.
Sample text is being used as a placeholder. Sample text helps you understand how real text may look. Sample text is being used as a placeholder for real text that is normally present.
Sample text is being used as a placeholder. Sample text helps you understand how real text may look. Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look.
Sample text is being used as a placeholder. Sample text helps you understand how real text may look. Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look.
Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look. Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look.
Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look. Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look.
Text Classes
Text classes when typography style doesn't match the default HTML tag.
Text Sizes
Sample text is being used as a placeholder for real text that is normally present.
Sample text is being used as a placeholder for real text that is normally present.
Sample text is being used as a placeholder for real text that is normally present.
Sample text is being used as a placeholder for real text that is normally present.
Sample text is being used as a placeholder for real text that is normally present.
Sample text is being used as a placeholder for real text that is normally present on your website.
Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website.
Sample text is being used as a placeholder for real text that is normally present on your website. Sample text helps you understand how real text may look on your website.
Sample text is being used as a placeholder for real text that is normally present on your website. Sample text helps you understand how real text may look on your website.
Text Styles
text-style-strikethrough
text-style-underline
text-style-overline
text-style-italic
text-style-muted
text-style-allcaps
text-style-nowrap
text-style-link
text-style-none
Sample text is being used as a placeholder.
Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text.
Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text.
Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text.
Heading Heading
Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text.
Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text.
Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text text-style-2lines
Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text.
Text Weights
Text Alignments
Colors
Manage recurring text and background colors.
Text Colors
Background Colors
Max widths
Use the max-width CSS property to contain inner content to a maximum width.
Paddings
Utility spacing system - padding classes. [padding-direction] + [padding-size].
Direction Classes
Size Classes
Margins
Utility spacing system - padding classes. [margin-direction] + [margin-size].
Direction Classes
Size Classes
Spacers
Unified spacer system for the project.
Icons
Unify icons sizes. icon-height sets height of icons. icon-1x1 sets both height and width of icons.
Useful utility systems
Utility classes we like to use in most of our projects to build faster.
1<script>
2
3const initDarkMode = (onMatch, onUnmatch) => {
4 const runOnDarkMode = (media) => {
5 if (media.matches) {
6 onMatch && onMatch();
7 } else {
8 onUnmatch && onUnmatch();
9 }
10 };
11
12 const darkModeMedia = window.matchMedia("(prefers-color-scheme: dark)");
13
14 runOnDarkMode(darkModeMedia);
15 darkModeMedia.addEventListener("change", runOnDarkMode);
16};
17
18</script>
19
20<script>
21
22document.addEventListener("DOMContentLoaded", () => {
23 initDarkMode(() => {}, () => {});
24});
25
26</script>
27Webflow elements
Native Webflow elements with Client-First classes applied.
Heading 1
Heading 2
Heading 3
Heading 4
Heading 5
Heading 6
Sample text with a link is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text.
- Sample text is being used as a placeholder for real text that is normally present.
- Sample text is being used as a placeholder for real text that is normally present.
- Sample text is being used as a placeholder for real text that is normally present.
- Sample text is being used as a placeholder for real text that is normally present.
- Sample text is being used as a placeholder for real text that is normally present.
- Sample text is being used as a placeholder for real text that is normally present.
Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text that is normally present.
Heading 1
Heading 2
Heading 3
Heading 4
Heading 5
Heading 6
Sample text with a link is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text.
- Sample text is being used as a placeholder for real text that is normally present.
- Sample text is being used as a placeholder for real text that is normally present.
- Sample text is being used as a placeholder for real text that is normally present.
- Sample text is being used as a placeholder for real text that is normally present.
- Sample text is being used as a placeholder for real text that is normally present.
- Sample text is being used as a placeholder for real text that is normally present.
Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text that is normally present.
Example of a form component using Folders
1<script>
2
3const initFilters = () => {
4 const filterMap = {};
5
6 const setDisplay = () => {
7 const filterTargets = Array.from(
8 document.querySelectorAll("[data-filter_target]")
9 );
10
11 for (const target of filterTargets) {
12 const resolvedFilterMapEntries = Object.entries(filterMap).filter(
13 ([_, values]) => (values.length > 0)
14 );
15
16 const isMatch = resolvedFilterMapEntries.every(([source, values]) => {
17 const matchedFilterTargets = Array.from(
18 document.querySelectorAll(`[data-filter_target="${source}"]`)
19 );
20
21 return matchedFilterTargets.length > 0 &&
22 matchedFilterTargets.some((target) => (
23 values.includes(target.dataset.filter_target_value)
24 ));
25 });
26
27 if (resolvedFilterMapEntries.length > 0 && !isMatch) {
28 target.classList.add("hide");
29 } else {
30 target.classList.remove("hide");
31 }
32 }
33 };
34
35 const filterSources = document.querySelectorAll("[data-filter_source]");
36
37 filterSources.forEach((source) => {
38 if (source.tagName === "INPUT") {
39 if (source.type === "radio" || source.type === "checkbox") {
40 source.addEventListener("change", function () {
41 const { filter_source, filter_source_value } = this.dataset;
42
43 if (this.checked) {
44 filterMap[filter_source] = filterMap[filter_source]
45 ? [...filterMap[filter_source], filter_source_value]
46 : [filter_source_value]
47 } else {
48 filterMap[filter_source] = filterMap[filter_source].filter(
49 (value) => (value !== filter_source_value)
50 );
51 }
52
53 setDisplay();
54 });
55 }
56
57 if (source.type === "text") {
58 source.addEventListener("keyup", function () {
59 const { value, dataset: { filter_source } } = this;
60
61 filterMap[filter_source] = [value];
62 setDisplay();
63 });
64 }
65 }
66
67 if (source.tagName === "SELECT") {
68 source.addEventListener("change", function () {
69 const { value, dataset: { filter_source } } = this;
70
71 filterMap[filter_source] = [value];
72 setDisplay();
73 });
74 }
75 });
76};
77
78const initInputDropdowns = () => {
79 const inputDropdowns = document.querySelectorAll("[data-dropdown='input']");
80
81 if (inputDropdowns.length > 0) {
82 inputDropdowns.forEach((dropdown) => {
83 const { dropdown_close_delay, dropdown_multiple } = dropdown.dataset;
84 const search = dropdown.querySelector("[data-dropdown='search']");
85 const options = dropdown.querySelectorAll("[data-dropdown='option']");
86
87 const valueTarget = dropdown.querySelector(
88 "[data-dropdown='value-target']"
89 );
90
91 if (search) {
92 const fixSpacePropagation = (e) => {
93 if (e.code === "Space") {
94 e.stopPropagation();
95 }
96 };
97
98 search.addEventListener("keydown", fixSpacePropagation);
99 search.addEventListener("keypress", fixSpacePropagation);
100 search.addEventListener("input", fixSpacePropagation);
101
102 search.addEventListener("keyup", function (e) {
103 fixSpacePropagation(e);
104
105 const searchValue = this.value.toLowerCase();
106
107 options.forEach((option) => {
108 const valueSource = option.querySelector(
109 "[data-dropdown='value-source']"
110 );
111
112 const value = valueSource
113 ? valueSource.innerHTML.toLowerCase()
114 : option.innerText.toLowerCase();
115
116 if (value.includes(searchValue)) {
117 option.classList.remove("hide");
118 } else {
119 option.classList.add("hide");
120 }
121 });
122
123 const empty = dropdown.querySelector("[data-dropdown='empty']");
124
125 if (empty) {
126 const hiddenOptions = dropdown.querySelectorAll(
127 ".hide[data-dropdown='option']"
128 );
129
130 if (options.length === hiddenOptions.length) {
131 empty.classList.remove("hide");
132 } else {
133 empty.classList.add("hide");
134 }
135 }
136 });
137 }
138
139 const closeDropdown = () => {
140 const toggle = dropdown.querySelector(".w-dropdown-toggle");
141 const list = dropdown.querySelector(".w-dropdown-list");
142
143 toggle.dispatchEvent(new Event("mousedown"));
144 toggle.dispatchEvent(new Event("mouseup"));
145 toggle.click();
146
147 setTimeout(() => {
148 toggle.classList.remove("w--open");
149 toggle.setAttribute("aria-expanded", "false");
150 list.classList.remove("w--open");
151 }, parseInt(dropdown_close_delay) || 250);
152 };
153
154 options.forEach((option) => {
155 const input = option.querySelector("input");
156
157 if (input) {
158 const defaultValue = valueTarget.innerHTML;
159
160 const valueSource = option.querySelector(
161 "[data-dropdown='value-source']"
162 );
163
164 const value = valueSource
165 ? valueSource.innerHTML
166 : option.innerText;
167
168 input.addEventListener("change", function () {
169 if (dropdown_multiple && valueTarget) {
170 if (this.checked) {
171 valueTarget.innerHTML = valueTarget.innerHTML === defaultValue
172 ? value
173 : `${valueTarget.innerHTML}, ${value}`;
174 } else {
175 const currentValues = valueTarget.innerHTML.split(", ");
176
177 const newValue = currentValues.filter(
178 (currentValue) => (currentValue !== value)
179 ).join(", ")
180
181 valueTarget.innerHTML = newValue === ""
182 ? defaultValue
183 : newValue;
184 }
185 }
186
187 if (!dropdown_multiple) {
188 if (valueTarget) {
189 valueTarget.innerHTML = value;
190 }
191
192 closeDropdown();
193 }
194 });
195 } else {
196 option.addEventListener("click", function () {
197 if (valueTarget) {
198 const valueSource = this.querySelector(
199 "[data-dropdown='value-source']"
200 );
201
202 const value = valueSource
203 ? valueSource.innerHTML
204 : option.innerText;
205
206 valueTarget.value = value;
207 }
208
209 closeDropdown();
210 });
211 }
212 });
213 });
214 }
215};
216
217const initInputTriggers = () => {
218 const triggers = document.querySelectorAll("[data-input_trigger]");
219
220 triggers.forEach((trigger) => {
221 trigger.addEventListener("change", function () {
222 const { type, checked, dataset, dataset: { input_trigger } } = this;
223
224 const target = document.querySelector(
225 `[data-input_target="${input_trigger}"]`
226 );
227
228 const triggerValue = Object.keys(dataset).find((key) => (
229 key.includes("input_trigger_value")
230 ));
231
232 if (target && triggerValue) {
233 if (type === "radio") {
234 target.value = checked ? dataset[triggerValue] : "";
235 }
236
237 if (type === "checkbox") {
238 if (checked) {
239 target.value = target.value === ""
240 ? dataset[sourceValue]
241 : `${target.value}, ${dataset[triggerValue]}`;
242 } else {
243 target.value = target.value === ""
244 ? ""
245 : target.value.split(", ").filter(
246 (value) => (value !== dataset[triggerValue])
247 ).join(", ");
248 }
249 }
250 }
251 });
252 });
253};
254
255const initFormSubmit = (
256 identifier,
257 {
258 url,
259 buildBody,
260 loaderIdentifier,
261 customSuccess,
262 displayApiError,
263 schema,
264 method = "POST",
265 headers = { "Content-Type": "application/json" },
266 formDisplay = "block"
267 }
268) => {
269 const form = document.getElementById(identifier);
270
271 if (form) {
272 const mainSubmitButton = form.querySelector("[type='submit']");
273 const submitButton = form.querySelector("[data-form_button='submit']");
274
275 submitButton.addEventListener("click", async function (e) {
276 e.preventDefault();
277
278 if (schema) {
279 const { elements: fields, parentNode } = form;
280 const validations = [];
281
282 for (const field of fields) {
283 const fieldRules = schema[field.name];
284
285 if (fieldRules) {
286 const fields = Array.from(
287 document.querySelectorAll(`[name="${field.name}"]`)
288 );
289
290 for (const { validate, message } of fieldRules) {
291 const isValid = validate(field, fields);
292
293 validations.push(isValid);
294
295 const errorMessage = document.querySelector(
296 `[data-form_field_error="${field.name}"]`
297 );
298
299 if (errorMessage) {
300 if (isValid) {
301 field.classList.remove("field-error");
302 errorMessage.classList.add("hide");
303 } else {
304 const textBox = errorMessage.querySelector(
305 "div"
306 ) || errorMessage;
307
308 field.classList.add("field-error");
309
310 textBox.innerHTML = typeof message === "string"
311 ? message
312 : message(field, fields);
313
314 errorMessage.classList.remove("hide");
315
316 break;
317 }
318 }
319 }
320 }
321 }
322
323 if (validations.some((validation) => !validation)) {
324 return false;
325 }
326 }
327
328 if (url) {
329 const { elements: fields, parentNode } = form;
330
331 const loadingMessage = loaderIdentifier
332 ? document.getElementById(loaderIdentifier)
333 : parentNode.querySelector("[data-form_message='loading']");
334
335 const successMessage = parentNode.querySelector(".w-form-done");
336 const errorMessage = parentNode.querySelector(".w-form-fail");
337 const errorText = "Oops! Something went wrong while submitting the form.";
338
339 const setDisplay = (element, display = "block", callback = null) => {
340 if (element) {
341 if (callback) {
342 const textBox = element.querySelector("div") || element;
343
344 callback(textBox);
345 }
346
347 element.style.display = display;
348 }
349 };
350
351 try {
352 loadingMessage.classList.remove("hide");
353
354 const body = JSON.stringify(buildBody(fields));
355
356 const response = await fetch(url, {
357 method,
358 headers: { "Content-Type": "application/json", ...headers },
359 body
360 });
361
362 if (!response.ok) {
363 if (response.headers.get("Content-Type") === "application/json") {
364 const data = await response.json();
365
366 throw new Error(data.message || errorText);
367 }
368
369 throw new Error(errorText);
370 }
371
372 const data = response.status === 204 ? null : await response.json();
373
374 setDisplay(form, "none");
375 loadingMessage.classList.add("hide");
376
377 setDisplay(successMessage, "block", (textBox) => {
378 if (customSuccess) {
379 textBox.innerHTML = typeof customSuccess === "string"
380 ? customSuccess
381 : customSuccess(data);
382 }
383 });
384
385 setDisplay(errorMessage, "none");
386 mainSubmitButton.click();
387 } catch (error) {
388 setDisplay(form, formDisplay);
389 loadingMessage.classList.add("hide");
390 setDisplay(successMessage, "none");
391
392 setDisplay(errorMessage, "block", (textBox) => {
393 if (displayApiError) {
394 textBox.innerHTML = error.message || errorText;
395 }
396 });
397
398 throw error;
399 }
400 } else {
401 mainSubmitButton.click();
402 }
403 });
404 }
405};
406
407</script>
408
409<script>
410
411document.addEventListener("DOMContentLoaded", () => {
412 initInputDropdowns();
413
414 initFormSubmit(
415 "wf-form-Default-Form",
416 {
417 url: "<URL>",
418 buildBody: (fields) => {
419 const body = {};
420
421 for (const field of fields) {
422 switch (field.name) {
423 case "First-Name":
424 body.first_name = field.value;
425
426 break;
427 default:
428 }
429 }
430
431 return body;
432 },
433 loaderIdentifier: "loader",
434 customSuccess: null,
435 displayApiError: false,
436 schema: {
437 "First-Name": [
438 {
439 validate: (field) => {
440 return field.value !== "";
441 },
442 message: "First Name is required"
443 }
444 ]
445 },
446 method: "POST",
447 headers: {
448 "Content-Type": "application/json",
449 Authorization: "Bearer abc123"
450 },
451 formDisplay: "block"
452 }
453 );
454});
455
456</script>
457Component Guide
Put all components below in another page when possible.
Button
Global
1<style>
2
3/* Make text look crisper and more legible in all browsers */
4body {
5 -webkit-font-smoothing: antialiased;
6 -moz-osx-font-smoothing: grayscale;
7 text-rendering: optimizeLegibility;
8}
9
10/* Focus state style for keyboard navigation for the focusable elements */
11*[tabindex]:focus-visible,
12input[type="file"]:focus-visible {
13 outline: 0.125rem solid var(--base-color-system--field-state-400, #4d65ff);
14 outline-offset: 0.125rem;
15}
16
17/* Set color style to inherit */
18.inherit-color * {
19 color: inherit;
20}
21
22/* Get rid of top margin on first element in any rich text element */
23.w-richtext > *:first-child {
24 margin-top: 0 !important;
25}
26
27/* Get rid of bottom margin on last element in any rich text element */
28.w-richtext > *:last-child,
29.w-richtext li:last-child {
30 margin-bottom: 0 !important;
31}
32
33/* Make sure containers never lose their center alignment */
34[class^="container-"] {
35 margin-left: auto !important;
36 margin-right: auto !important;
37}
38
39/*
40Make the following elements inherit typography styles from the parent and not have hardcoded values.
41Important: You will not be able to style for example "All Links" in Designer with this CSS applied.
42Uncomment this CSS to use it in the project. Leave this message for future hand-off.
43*/
44/*
45a,
46.w-input,
47.w-select,
48.w-tab-link,
49.w-nav-link,
50.w-dropdown-btn,
51.w-dropdown-toggle,
52.w-dropdown-link {
53 color: inherit;
54 text-decoration: inherit;
55 font-size: inherit;
56}
57*/
58
59/* Apply "..." after 2 lines of text */
60.text-style-2lines {
61 display: -webkit-box;
62 overflow: hidden;
63 -webkit-line-clamp: 2;
64 line-clamp: 2;
65 -webkit-box-orient: vertical;
66}
67
68/* Apply "..." after 3 lines of text */
69.text-style-3lines {
70 display: -webkit-box;
71 overflow: hidden;
72 -webkit-line-clamp: 3;
73 line-clamp: 3;
74 -webkit-box-orient: vertical;
75}
76
77/* These classes are never overwritten */
78.margin-0 {
79 margin: 0rem !important;
80}
81
82.padding-0 {
83 padding: 0rem !important;
84}
85
86.spacing-clean {
87 padding: 0rem !important;
88 margin: 0rem !important;
89}
90
91.margin-top {
92 margin-right: 0rem !important;
93 margin-bottom: 0rem !important;
94 margin-left: 0rem !important;
95}
96
97.padding-top {
98 padding-right: 0rem !important;
99 padding-bottom: 0rem !important;
100 padding-left: 0rem !important;
101}
102
103.margin-right {
104 margin-top: 0rem !important;
105 margin-bottom: 0rem !important;
106 margin-left: 0rem !important;
107}
108
109.padding-right {
110 padding-top: 0rem !important;
111 padding-bottom: 0rem !important;
112 padding-left: 0rem !important;
113}
114
115.margin-bottom {
116 margin-top: 0rem !important;
117 margin-right: 0rem !important;
118 margin-left: 0rem !important;
119}
120
121.padding-bottom {
122 padding-top: 0rem !important;
123 padding-right: 0rem !important;
124 padding-left: 0rem !important;
125}
126
127.margin-left {
128 margin-top: 0rem !important;
129 margin-right: 0rem !important;
130 margin-bottom: 0rem !important;
131}
132
133.padding-left {
134 padding-top: 0rem !important;
135 padding-right: 0rem !important;
136 padding-bottom: 0rem !important;
137}
138
139.margin-horizontal {
140 margin-top: 0rem !important;
141 margin-bottom: 0rem !important;
142}
143
144.padding-horizontal {
145 padding-top: 0rem !important;
146 padding-bottom: 0rem !important;
147}
148
149.margin-vertical {
150 margin-right: 0rem !important;
151 margin-left: 0rem !important;
152}
153
154.padding-vertical {
155 padding-right: 0rem !important;
156 padding-left: 0rem !important;
157}
158
159.hide {
160 display: none !important;
161}
162
163@media only screen and (min-width: 1280px) {
164 .hide-large-screen {
165 display: none !important;
166 }
167}
168
169@media only screen and (min-width: 1440px) {
170 .hide-xlarge-screen {
171 display: none !important;
172 }
173}
174
175@media only screen and (min-width: 1920px) {
176 .hide-xxlarge-screen {
177 display: none !important;
178 }
179}
180
181@media only screen and (max-width: 991px) {
182 .hide-tablet {
183 display: none !important;
184 }
185}
186
187@media only screen and (max-width: 767px) {
188 .hide-mobile-landscape {
189 display: none !important;
190 }
191}
192
193@media only screen and (max-width: 479px) {
194 .hide-mobile-portrait {
195 display: none !important;
196 }
197}
198
199</style>
2001<style>
2
3.w-webflow-badge {
4 display: none !important;
5}
6
7html.wf-design-mode .wf-slot {
8 display: block;
9}
10
11.w-nav:before,
12.w-nav:after,
13.w-container:before,
14.w-container:after {
15 display: none;
16}
17
18@media only screen and (min-width: 1280px) {
19 .nav-expand-large.w-nav[data-collapse="all"] .w-nav-menu {
20 display: block;
21 }
22}
23
24@media only screen and (min-width: 1440px) {
25 .nav-expand-xlarge.w-nav[data-collapse="all"] .w-nav-menu {
26 display: block;
27 }
28}
29
30@media only screen and (min-width: 1920px) {
31 .nav-expand-xxlarge.w-nav[data-collapse="all"] .w-nav-menu {
32 display: block;
33 }
34}
35
36.w-nav-brand,
37.w-nav-link {
38 margin: 0rem;
39 padding: 0rem;
40}
41
42.w-nav-button.w--open {
43 background-color: transparent;
44}
45
46.w-dropdown {
47 z-index: auto;
48}
49
50.w-dropdown-toggle {
51 white-space: normal;
52}
53
54.w-dropdown-list[style*="block"]:not(.w--open):not([style*="will-change"]) {
55 display: none !important;
56}
57
58.w-dropdown-list.w--open {
59 display: block !important;
60}
61
62.w-lightbox-backdrop {
63 z-index: var(--_index---index-lightbox-backdrop, 2000);
64}
65
66address {
67 font-style: normal;
68}
69
70table {
71 table-layout: fixed;
72}
73
74th, td {
75 height: 0rem;
76 text-align: initial;
77}
78
79nav:not(.w-dropdown-list) a,
80footer a,
81.text-style-none a,
82nav:not(.w-dropdown-list) .link,
83footer .link,
84.text-style-none .link {
85 text-decoration: none;
86}
87
88a svg:not(.retain-color) path[fill],
89button svg:not(.retain-color) path[fill] {
90 fill: currentColor;
91}
92
93a svg:not(.retain-color) path[stroke],
94button svg:not(.retain-color) path[stroke] {
95 stroke: currentColor;
96}
97
98picture,
99svg,
100video {
101 display: inline-block;
102 vertical-align: middle;
103 width: auto;
104 height: auto;
105 max-width: 100%;
106}
107
108svg {
109 object-fit: contain;
110}
111
112video {
113 object-fit: cover;
114}
115
116[style*="background-image"][style*="url"] {
117 background-size: cover;
118 background-position: center;
119 background-repeat: no-repeat;
120}
121
122</style>
1231<style>
2
3.text-style-1line * {
4 margin-bottom: 0rem !important;
5 overflow: hidden;
6 white-space: nowrap;
7 text-overflow: ellipsis;
8}
9
10.text-style-1line *:not(*:first-child) {
11 display: none;
12}
13
14.show {
15 display: block !important;
16}
17
18.show-large-screen,
19.show-xlarge-screen,
20.show-xxlarge-screen,
21.show-tablet,
22.show-mobile-landscape,
23.show-mobile-portrait {
24 display: none !important;
25}
26
27@media only screen and (min-width: 1280px) {
28 .show-large-screen {
29 display: block !important;
30 }
31}
32
33@media only screen and (min-width: 1440px) {
34 .show-xlarge-screen {
35 display: block !important;
36 }
37}
38
39@media only screen and (min-width: 1920px) {
40 .show-xxlarge-screen {
41 display: block !important;
42 }
43}
44
45@media only screen and (max-width: 991px) {
46 .show-tablet {
47 display: block !important;
48 }
49}
50
51@media only screen and (max-width: 767px) {
52 .show-mobile-landscape {
53 display: block !important;
54 }
55}
56
57@media only screen and (max-width: 479px) {
58 .show-mobile-portrait {
59 display: block !important;
60 }
61}
62
63html:not(.wf-design-mode) [data-interaction_start="self"],
64html:not(.wf-design-mode) [data-interaction_start="children"] > *,
65html:not(.wf-design-mode) [data-interaction_start="letter"] .gsap_split_letter {
66 opacity: 0;
67}
68
69</style>
701<style>
2
3[class*="icon"] {
4 flex: 0 0 auto;
5}
6
7[class*="icon"] svg {
8 width: 100%;
9 height: 100%;
10}
11
12html.wf-design-mode .icon-rich-text {
13 width: 1em;
14 height: 1em;
15}
16
17.label-required:after {
18 content: " *";
19 color: var(--text-color--text-error-light, #dc3545);
20}
21
22[type="checkbox"]:checked + .form_dropdown-item,
23[type="radio"]:checked + .form_dropdown-item {
24 color: var(--text-color--text-alternate, #fff);
25 background-color: var(--base-color-system--field-state-500, #4d65ff);
26}
27
28[type="checkbox"]:checked + .form_checkbox-icon,
29[type="radio"]:checked + .form_radio-icon {
30 color: var(--text-color--text-alternate, #fff);
31 background-color: var(--base-color-system--field-state-400, #3898ec);
32 border-color: var(--base-color-system--field-state-400, #3898ec);
33}
34
35[type="radio"]:checked + .form_radio-icon {
36 outline-color: var(--base-color-system--field-state-400, #3898ec);
37}
38
39html.wf-design-mode [data-message="loading"].hide,
40html.wf-design-mode [data-field_error].hide {
41 display: block !important;
42}
43
44[class*="generic-image"][class*="w-variant"] *,
45[class*="zoomer"][class*="w-variant"] {
46 width: inherit;
47 height: inherit;
48}
49
50[class*="font-setter"][class*="w-variant"] * {
51 font-family: inherit;
52}
53
54[class*="text-weight-setter"][class*="w-variant"] * {
55 font-weight: inherit;
56}
57
58[class*="text-resizer"][class*="w-variant"] * {
59 font-size: inherit;
60}
61
62[class*="text-colorizer"][class*="w-variant"] * {
63 color: inherit;
64}
65
66.component_grid[data-wf--grid--gutter-size="xsmall"] .component_grid-column {
67 padding: var(--_base---grid-gutter--gutter-xsmall);
68}
69
70.component_grid[data-wf--grid--gutter-size="small"] .component_grid-column {
71 padding: var(--_base---grid-gutter--gutter-small);
72}
73
74.component_grid[data-wf--grid--gutter-size="medium"] .component_grid-column {
75 padding: var(--_base---grid-gutter--gutter-medium);
76}
77
78.component_grid[data-wf--grid--gutter-size="large"] .component_grid-column {
79 padding: var(--_base---grid-gutter--gutter-large);
80}
81
82.component_grid[data-wf--grid--gutter-size="xlarge"] .component_grid-column {
83 padding: var(--_base---grid-gutter--gutter-large);
84}
85
86@media only screen and (max-width: 991px) {
87 .component_grid-column[data-column_width_tablet*="cw"] {
88 flex: 0 0 auto;
89 }
90
91 .component_grid-column[data-column_width_tablet="cw-1"] {
92 width: var(--_base---grid-column-width--column-1);
93 }
94
95 .component_grid-column[data-column_width_tablet="cw-2"] {
96 width: var(--_base---grid-column-width--column-2);
97 }
98
99 .component_grid-column[data-column_width_tablet="cw-3"] {
100 width: var(--_base---grid-column-width--column-3);
101 }
102
103 .component_grid-column[data-column_width_tablet="cw-4"] {
104 width: var(--_base---grid-column-width--column-4);
105 }
106
107 .component_grid-column[data-column_width_tablet="cw-5"] {
108 width: var(--_base---grid-column-width--column-5);
109 }
110
111 .component_grid-column[data-column_width_tablet="cw-6"] {
112 width: var(--_base---grid-column-width--column-6);
113 }
114
115 .component_grid-column[data-column_width_tablet="cw-7"] {
116 width: var(--_base---grid-column-width--column-7);
117 }
118
119 .component_grid-column[data-column_width_tablet="cw-8"] {
120 width: var(--_base---grid-column-width--column-8);
121 }
122
123 .component_grid-column[data-column_width_tablet="cw-9"] {
124 width: var(--_base---grid-column-width--column-9);
125 }
126
127 .component_grid-column[data-column_width_tablet="cw-10"] {
128 width: var(--_base---grid-column-width--column-10);
129 }
130
131 .component_grid-column[data-column_width_tablet="cw-11"] {
132 width: var(--_base---grid-column-width--column-11);
133 }
134
135 .component_grid-column[data-column_width_tablet="cw-12"] {
136 width: var(--_base---grid-column-width--column-12);
137 }
138}
139
140@media only screen and (max-width: 767px) {
141 .component_grid-column[data-column_width_mobile*="cw"] {
142 flex: 0 0 auto;
143 }
144
145 .component_grid-column[data-column_width_mobile="cw-1"] {
146 width: var(--_base---grid-column-width--column-1);
147 }
148
149 .component_grid-column[data-column_width_mobile="cw-2"] {
150 width: var(--_base---grid-column-width--column-2);
151 }
152
153 .component_grid-column[data-column_width_mobile="cw-3"] {
154 width: var(--_base---grid-column-width--column-3);
155 }
156
157 .component_grid-column[data-column_width_mobile="cw-4"] {
158 width: var(--_base---grid-column-width--column-4);
159 }
160
161 .component_grid-column[data-column_width_mobile="cw-5"] {
162 width: var(--_base---grid-column-width--column-5);
163 }
164
165 .component_grid-column[data-column_width_mobile="cw-6"] {
166 width: var(--_base---grid-column-width--column-6);
167 }
168
169 .component_grid-column[data-column_width_mobile="cw-7"] {
170 width: var(--_base---grid-column-width--column-7);
171 }
172
173 .component_grid-column[data-column_width_mobile="cw-8"] {
174 width: var(--_base---grid-column-width--column-8);
175 }
176
177 .component_grid-column[data-column_width_mobile="cw-9"] {
178 width: var(--_base---grid-column-width--column-9);
179 }
180
181 .component_grid-column[data-column_width_mobile="cw-10"] {
182 width: var(--_base---grid-column-width--column-10);
183 }
184
185 .component_grid-column[data-column_width_mobile="cw-11"] {
186 width: var(--_base---grid-column-width--column-11);
187 }
188
189 .component_grid-column[data-column_width_mobile="cw-12"] {
190 width: var(--_base---grid-column-width--column-12);
191 }
192}
193
194.component_grid-column[data-column_order="co-1"] {
195 order: 1;
196}
197
198.component_grid-column[data-column_order="co-2"] {
199 order: 2;
200}
201
202.component_grid-column[data-column_order="co-3"] {
203 order: 3;
204}
205
206.component_grid-column[data-column_order="co-4"] {
207 order: 4;
208}
209
210.component_grid-column[data-column_order="co-5"] {
211 order: 5;
212}
213
214.component_grid-column[data-column_order="co-6"] {
215 order: 6;
216}
217
218.component_grid-column[data-column_order="co-7"] {
219 order: 7;
220}
221
222.component_grid-column[data-column_order="co-8"] {
223 order: 8;
224}
225
226.component_grid-column[data-column_order="co-9"] {
227 order: 9;
228}
229
230.component_grid-column[data-column_order="co-10"] {
231 order: 10;
232}
233
234.component_grid-column[data-column_order="co-11"] {
235 order: 11;
236}
237
238.component_grid-column[data-column_order="co-12"] {
239 order: 12;
240}
241
242@media only screen and (max-width: 991px) {
243 .component_grid-column[data-column_order_tablet="co-1"] {
244 order: 1;
245 }
246
247 .component_grid-column[data-column_order_tablet="co-2"] {
248 order: 2;
249 }
250
251 .component_grid-column[data-column_order_tablet="co-3"] {
252 order: 3;
253 }
254
255 .component_grid-column[data-column_order_tablet="co-4"] {
256 order: 4;
257 }
258
259 .component_grid-column[data-column_order_tablet="co-5"] {
260 order: 5;
261 }
262
263 .component_grid-column[data-column_order_tablet="co-6"] {
264 order: 6;
265 }
266
267 .component_grid-column[data-column_order_tablet="co-7"] {
268 order: 7;
269 }
270
271 .component_grid-column[data-column_order_tablet="co-8"] {
272 order: 8;
273 }
274
275 .component_grid-column[data-column_order_tablet="co-9"] {
276 order: 9;
277 }
278
279 .component_grid-column[data-column_order_tablet="co-10"] {
280 order: 10;
281 }
282
283 .component_grid-column[data-column_order_tablet="co-11"] {
284 order: 11;
285 }
286
287 .component_grid-column[data-column_order_tablet="co-12"] {
288 order: 12;
289 }
290}
291
292@media only screen and (max-width: 767px) {
293 .component_grid-column[data-column_order_mobile="co-1"] {
294 order: 1;
295 }
296
297 .component_grid-column[data-column_order_mobile="co-2"] {
298 order: 2;
299 }
300
301 .component_grid-column[data-column_order_mobile="co-3"] {
302 order: 3;
303 }
304
305 .component_grid-column[data-column_order_mobile="co-4"] {
306 order: 4;
307 }
308
309 .component_grid-column[data-column_order_mobile="co-5"] {
310 order: 5;
311 }
312
313 .component_grid-column[data-column_order_mobile="co-6"] {
314 order: 6;
315 }
316
317 .component_grid-column[data-column_order_mobile="co-7"] {
318 order: 7;
319 }
320
321 .component_grid-column[data-column_order_mobile="co-8"] {
322 order: 8;
323 }
324
325 .component_grid-column[data-column_order_mobile="co-9"] {
326 order: 9;
327 }
328
329 .component_grid-column[data-column_order_mobile="co-10"] {
330 order: 10;
331 }
332
333 .component_grid-column[data-column_order_mobile="co-11"] {
334 order: 11;
335 }
336
337 .component_grid-column[data-column_order_mobile="co-12"] {
338 order: 12;
339 }
340}
341
342</style>
3431<style>
2
3html.wf-design-mode .masonry_item {
4 position: static;
5}
6
7.component_initial div::first-letter {
8 font-size: initial;
9}
10
11.vertical-tab_wrapper:before,
12.vertical-tab_wrapper:after {
13 display: none;
14}
15
16.state-dropdown_toggle.w--open .state-dropdown_icon {
17 transform: rotate(180deg);
18}
19
20::-webkit-scrollbar {
21 width: 0.75rem;
22 height: 0.75rem;
23}
24
25::-webkit-scrollbar-thumb {
26 background-color: var(--background-color--background-secondary, #2d62ff);
27 border-radius: 0.5rem;
28 transition: background-color 0.25s;
29}
30
31::-webkit-scrollbar-thumb:hover {
32 background-color: var(--background-color--background-alternate, #000);
33
34 box-shadow: inset 0 0 1rem var(
35 --background-color--background-secondary,
36 #2d62ff
37 );
38}
39
40::-webkit-scrollbar-track {
41 background-color: var(--background-color--background-accent, #d9e5ff);
42 border-radius: 0.5rem;
43 box-shadow: inset 0 0 0.25rem var(--background-color--background-dark, #222);
44}
45
46.page-wrapper[data-theme="light"] {
47 --background-color--background-primary: var(
48 --base-color-misc-neutral--white,
49 #fff
50 );
51
52 --text-color--text-primary: var(--base-color-misc-neutral--black, #000);
53}
54
55.page-wrapper[data-theme="dark"] {
56 --background-color--background-primary: var(
57 --base-color-misc-neutral--neutral-800,
58 #111
59 );
60
61 --text-color--text-primary: var(--base-color-misc-neutral--white, #fff);
62}
63
64</style>
651<script>
2
3const injectSourceCodes = (sourceCodes) => {
4 if (sourceCodes.length > 0) {
5 sourceCodes.forEach((sourceCode) => {
6 const { type, url, location } = sourceCode;
7 const domTarget = document[location ? location : "head"];
8
9 if (domTarget) {
10 if (type === "script") {
11 const script = document.createElement("script");
12
13 script.setAttribute("src", url);
14 domTarget.appendChild(script);
15 } else {
16 const link = document.createElement("link");
17
18 link.setAttribute("rel", "stylesheet");
19 link.setAttribute("href", url);
20 domTarget.appendChild(link);
21 }
22 }
23 });
24 }
25};
26
27const initStructuredData = () => {
28 const { data_type } = document.body.dataset;
29
30 if (data_type) {
31 const script = document.createElement("script");
32
33 script.type = "application/ld+json";
34
35 const structuredData = {
36 "@context": "https://schema.org",
37 "@type": data_type
38 };
39
40 switch (data_type) {
41 case "WebSite":
42 structuredData.name = "Patrick Samson";
43 structuredData.url = window.location.href.slice(0, -1);
44
45 break;
46 case "WebPage":
47 structuredData.name = document.title;
48 structuredData.url = window.location.href;
49
50 break;
51 case "Article":
52 case "BlogPosting":
53 case "NewsArticle":
54 structuredData.headline = document.querySelector(
55 "[data-article='headline']"
56 ).innerText;
57
58 const date = new Date(
59 document.querySelector("[data-article='date']").innerText
60 );
61
62 const year = date.getFullYear();
63 const month = date.getMonth() + 1;
64 const day = date.getDate();
65
66 const resolvedDate = `${year}-${
67 month >= 10 ? month : `0${month}`
68 }-${day >= 10 ? day : `0${day}`}`
69
70 structuredData.datePublished = resolvedDate;
71
72 structuredData.author = {
73 "@type": "Person",
74 name: document.querySelector("[data-article='author']").innerText
75 };
76
77 break;
78 }
79
80 script.innerHTML = JSON.stringify(structuredData);
81 document.head.appendChild(script);
82 }
83};
84
85const initDarkMode = (onMatch, onUnmatch) => {
86 const runOnDarkMode = (media) => {
87 if (media.matches) {
88 onMatch && onMatch();
89 } else {
90 onUnmatch && onUnmatch();
91 }
92 };
93
94 const darkModeMedia = window.matchMedia("(prefers-color-scheme: dark)");
95
96 runOnDarkMode(darkModeMedia);
97 darkModeMedia.addEventListener("change", runOnDarkMode);
98};
99
100const initAria = () => {
101 const ariaElements = document.querySelectorAll("[data-aria]");
102
103 if (ariaElements.length > 0) {
104 ariaElements.forEach((element) => {
105 const { aria, aria_collapse_target } = element.dataset;
106
107 if (aria === "expansion") {
108 element.addEventListener("click", function (e) {
109 e.preventDefault();
110
111 const expansion = this.getAttribute("aria-expanded") === "true"
112 ? "false"
113 : "true";
114
115 this.setAttribute("aria-expanded", expansion);
116 });
117
118 if (aria_collapse_target) {
119 const collapser = document.querySelector(
120 `[data-aria_collapse_source="${aria_collapse_target}"]`
121 );
122
123 collapser.addEventListener("click", (e) => {
124 e.preventDefault();
125 element.setAttribute("aria-expanded", "false");
126 });
127 }
128 }
129 });
130 }
131};
132
133const initMatchHeight = (delay = 100) => {
134 const elements = Array.from(document.querySelectorAll("[data-match_height]"));
135
136 if (elements.length > 0) {
137 const setHeight = (elementSets, isReset) => {
138 setTimeout(() => {
139 for (const matchElements of elementSets) {
140 let maxHeight = 0;
141
142 matchElements.forEach((element) => {
143 element.style.height = "auto";
144 });
145
146 if (!isReset) {
147 matchElements.forEach((element) => {
148 const { offsetHeight } = element;
149
150 if (offsetHeight > maxHeight) {
151 maxHeight = offsetHeight;
152 }
153 });
154
155 matchElements.forEach((element) => {
156 element.style.height = `${maxHeight}px`;
157 });
158 }
159 }
160 }, delay);
161 };
162
163 const nonBreakpointMap = elements.filter((element) => (
164 !element.dataset.breakpoint
165 )).reduce((init, element) => {
166 const { match_height } = element.dataset;
167
168 if (init[match_height]) {
169 init[match_height].push(element);
170 } else {
171 init[match_height] = [element];
172 }
173
174 return init;
175 }, {});
176
177
178 if (Object.keys(nonBreakpointMap).length > 0) {
179 const nonBreakpointElementSets = Object.values(nonBreakpointMap);
180
181 setHeight(nonBreakpointElementSets);
182
183 window.addEventListener("resize", () => {
184 setNormalHeight(nonBreakpointElementSets);
185 });
186
187 window.addEventListener("orientationchange", () => {
188 setNormalHeight(nonBreakpointElementSets);
189 });
190 }
191
192 const breakpointMap = elements.filter((element) => (
193 element.dataset.breakpoint
194 )).reduce((init, element) => {
195 const { breakpoint, match_height } = element.dataset;
196
197 if (init[breakpoint] && init[breakpoint][match_height]) {
198 init[breakpoint][match_height].push(element);
199 } else {
200 init[breakpoint] = { [match_height]: [element] };
201 }
202
203 return init;
204 }, {});
205
206 if (Object.keys(breakpointMap).length > 0) {
207 Object.entries(breakpointMap).forEach(([breakpoint, elementMap]) => {
208 const resolvedBreakpoint = parseInt(breakpoint);
209
210 const media = window.matchMedia(`only screen and (${
211 resolvedBreakpoint >= 0 ? "min" : "max"
212 }-width: ${
213 resolvedBreakpoint >= 0 ? resolvedBreakpoint : resolvedBreakpoint * -1
214 }px)`);
215
216 const runOnMatch = (media) => {
217 setHeight(Object.values(elementMap), !media.matches);
218 };
219
220 runOnMatch(media);
221
222 window.addEventListener("resize", () => {
223 runOnMatch(media);
224 });
225
226 media.addEventListener("change", runOnMatch);
227 });
228 }
229 }
230};
231
232const initFilters = () => {
233 const filterMap = {};
234
235 const setDisplay = () => {
236 const filterTargets = Array.from(
237 document.querySelectorAll("[data-filter_target]")
238 );
239
240 for (const target of filterTargets) {
241 const resolvedFilterMapEntries = Object.entries(filterMap).filter(
242 ([_, values]) => (values.length > 0)
243 );
244
245 const isMatch = resolvedFilterMapEntries.every(([source, values]) => {
246 const matchedFilterTargets = Array.from(
247 document.querySelectorAll(`[data-filter_target="${source}"]`)
248 );
249
250 return matchedFilterTargets.length > 0 &&
251 matchedFilterTargets.some((target) => (
252 values.includes(target.dataset.filter_target_value)
253 ));
254 });
255
256 if (resolvedFilterMapEntries.length > 0 && !isMatch) {
257 target.classList.add("hide");
258 } else {
259 target.classList.remove("hide");
260 }
261 }
262 };
263
264 const filterSources = document.querySelectorAll("[data-filter_source]");
265
266 filterSources.forEach((source) => {
267 if (source.tagName === "INPUT") {
268 if (source.type === "radio" || source.type === "checkbox") {
269 source.addEventListener("change", function () {
270 const { filter_source, filter_source_value } = this.dataset;
271
272 if (this.checked) {
273 filterMap[filter_source] = filterMap[filter_source]
274 ? [...filterMap[filter_source], filter_source_value]
275 : [filter_source_value]
276 } else {
277 filterMap[filter_source] = filterMap[filter_source].filter(
278 (value) => (value !== filter_source_value)
279 );
280 }
281
282 setDisplay();
283 });
284 }
285
286 if (source.type === "text") {
287 source.addEventListener("keyup", function () {
288 const { value, dataset: { filter_source } } = this;
289
290 filterMap[filter_source] = [value];
291 setDisplay();
292 });
293 }
294 }
295
296 if (source.tagName === "SELECT") {
297 source.addEventListener("change", function () {
298 const { value, dataset: { filter_source } } = this;
299
300 filterMap[filter_source] = [value];
301 setDisplay();
302 });
303 }
304 });
305};
306
307const initInputDropdowns = () => {
308 const inputDropdowns = document.querySelectorAll("[data-dropdown='input']");
309
310 if (inputDropdowns.length > 0) {
311 inputDropdowns.forEach((dropdown) => {
312 const { dropdown_close_delay, dropdown_multiple } = dropdown.dataset;
313 const search = dropdown.querySelector("[data-dropdown='search']");
314 const options = dropdown.querySelectorAll("[data-dropdown='option']");
315
316 const valueTarget = dropdown.querySelector(
317 "[data-dropdown='value-target']"
318 );
319
320 if (search) {
321 const fixSpacePropagation = (e) => {
322 if (e.code === "Space") {
323 e.stopPropagation();
324 }
325 };
326
327 search.addEventListener("keydown", fixSpacePropagation);
328 search.addEventListener("keypress", fixSpacePropagation);
329 search.addEventListener("input", fixSpacePropagation);
330
331 search.addEventListener("keyup", function (e) {
332 fixSpacePropagation(e);
333
334 const searchValue = this.value.toLowerCase();
335
336 options.forEach((option) => {
337 const valueSource = option.querySelector(
338 "[data-dropdown='value-source']"
339 );
340
341 const value = valueSource
342 ? valueSource.innerHTML.toLowerCase()
343 : option.innerText.toLowerCase();
344
345 if (value.includes(searchValue)) {
346 option.classList.remove("hide");
347 } else {
348 option.classList.add("hide");
349 }
350 });
351
352 const empty = dropdown.querySelector("[data-dropdown='empty']");
353
354 if (empty) {
355 const hiddenOptions = dropdown.querySelectorAll(
356 ".hide[data-dropdown='option']"
357 );
358
359 if (options.length === hiddenOptions.length) {
360 empty.classList.remove("hide");
361 } else {
362 empty.classList.add("hide");
363 }
364 }
365 });
366 }
367
368 const closeDropdown = () => {
369 const toggle = dropdown.querySelector(".w-dropdown-toggle");
370 const list = dropdown.querySelector(".w-dropdown-list");
371
372 toggle.dispatchEvent(new Event("mousedown"));
373 toggle.dispatchEvent(new Event("mouseup"));
374 toggle.click();
375
376 setTimeout(() => {
377 toggle.classList.remove("w--open");
378 toggle.setAttribute("aria-expanded", "false");
379 list.classList.remove("w--open");
380 }, parseInt(dropdown_close_delay) || 250);
381 };
382
383 options.forEach((option) => {
384 const input = option.querySelector("input");
385
386 if (input) {
387 const defaultValue = valueTarget.innerHTML;
388
389 const valueSource = option.querySelector(
390 "[data-dropdown='value-source']"
391 );
392
393 const value = valueSource
394 ? valueSource.innerHTML
395 : option.innerText;
396
397 input.addEventListener("change", function () {
398 if (dropdown_multiple && valueTarget) {
399 if (this.checked) {
400 valueTarget.innerHTML = valueTarget.innerHTML === defaultValue
401 ? value
402 : `${valueTarget.innerHTML}, ${value}`;
403 } else {
404 const currentValues = valueTarget.innerHTML.split(", ");
405
406 const newValue = currentValues.filter(
407 (currentValue) => (currentValue !== value)
408 ).join(", ")
409
410 valueTarget.innerHTML = newValue === ""
411 ? defaultValue
412 : newValue;
413 }
414 }
415
416 if (!dropdown_multiple) {
417 if (valueTarget) {
418 valueTarget.innerHTML = value;
419 }
420
421 closeDropdown();
422 }
423 });
424 } else {
425 option.addEventListener("click", function () {
426 if (valueTarget) {
427 const valueSource = this.querySelector(
428 "[data-dropdown='value-source']"
429 );
430
431 const value = valueSource
432 ? valueSource.innerHTML
433 : option.innerText;
434
435 valueTarget.value = value;
436 }
437
438 closeDropdown();
439 });
440 }
441 });
442 });
443 }
444};
445
446const initInputTriggers = () => {
447 const triggers = document.querySelectorAll("[data-input_trigger]");
448
449 triggers.forEach((trigger) => {
450 trigger.addEventListener("change", function () {
451 const { type, checked, dataset, dataset: { input_trigger } } = this;
452
453 const target = document.querySelector(
454 `[data-input_target="${input_trigger}"]`
455 );
456
457 const triggerValue = Object.keys(dataset).find((key) => (
458 key.includes("input_trigger_value")
459 ));
460
461 if (target && triggerValue) {
462 if (type === "radio") {
463 target.value = checked ? dataset[triggerValue] : "";
464 }
465
466 if (type === "checkbox") {
467 if (checked) {
468 target.value = target.value === ""
469 ? dataset[sourceValue]
470 : `${target.value}, ${dataset[triggerValue]}`;
471 } else {
472 target.value = target.value === ""
473 ? ""
474 : target.value.split(", ").filter(
475 (value) => (value !== dataset[triggerValue])
476 ).join(", ");
477 }
478 }
479 }
480 });
481 });
482};
483
484const requestApi = async (
485 url,
486 {
487 loaderIdentifier,
488 callback,
489 body,
490 method = "GET",
491 headers = { "Content-Type": "application/json" }
492 }
493) => {
494 const setLoaderDisplay = (show) => {
495 if (loaderIdentifier) {
496 const loader = document.getElementById(loaderIdentifier);
497
498 if (loader) {
499 if (show) {
500 loader.classList.remove("hide");
501 } else {
502 loader.classList.add("hide");
503 }
504 }
505 }
506 };
507
508 try {
509 if (!headers["Content-Type"]) {
510 headers["Content-Type"] = "application/json";
511 }
512
513 const params = body && headers["Content-Type"] === "application/json"
514 ? { method, headers, body: JSON.stringify(body) }
515 : { method, headers };
516
517 setLoaderDisplay(true);
518
519 const response = await fetch(url, params);
520 const contentType = response.headers.get("Content-Type");
521
522 if (!response.ok) {
523 if (contentType === "application/json") {
524 const data = await response.json();
525
526 throw new Error(data.message || "Error occurred");
527 }
528
529 throw new Error("Error occurred");
530 }
531
532 const data = response.status === 204
533 ? null
534 : contentType === "application/json"
535 ? await response.json()
536 : await response.text();
537
538 if (callback) {
539 callback(data);
540 }
541
542 setLoaderDisplay();
543 } catch (error) {
544 setLoaderDisplay();
545
546 throw error;
547 }
548};
549
550const initFormSubmit = (
551 identifier,
552 {
553 url,
554 buildBody,
555 loaderIdentifier,
556 customSuccess,
557 displayApiError,
558 schema,
559 method = "POST",
560 headers = { "Content-Type": "application/json" },
561 formDisplay = "block"
562 }
563) => {
564 const form = document.getElementById(identifier);
565
566 if (form) {
567 const mainSubmitButton = form.querySelector("[type='submit']");
568 const submitButton = form.querySelector("[data-form_button='submit']");
569
570 submitButton.addEventListener("click", async function (e) {
571 e.preventDefault();
572
573 if (schema) {
574 const { elements: fields, parentNode } = form;
575 const validations = [];
576
577 for (const field of fields) {
578 const fieldRules = schema[field.name];
579
580 if (fieldRules) {
581 const fields = Array.from(
582 document.querySelectorAll(`[name="${field.name}"]`)
583 );
584
585 for (const { validate, message } of fieldRules) {
586 const isValid = validate(field, fields);
587
588 validations.push(isValid);
589
590 const errorMessage = document.querySelector(
591 `[data-form_field_error="${field.name}"]`
592 );
593
594 if (errorMessage) {
595 if (isValid) {
596 field.classList.remove("field-error");
597 errorMessage.classList.add("hide");
598 } else {
599 const textBox = errorMessage.querySelector(
600 "div"
601 ) || errorMessage;
602
603 field.classList.add("field-error");
604
605 textBox.innerHTML = typeof message === "string"
606 ? message
607 : message(field, fields);
608
609 errorMessage.classList.remove("hide");
610
611 break;
612 }
613 }
614 }
615 }
616 }
617
618 if (validations.some((validation) => !validation)) {
619 return false;
620 }
621 }
622
623 if (url) {
624 const { elements: fields, parentNode } = form;
625
626 const loadingMessage = loaderIdentifier
627 ? document.getElementById(loaderIdentifier)
628 : parentNode.querySelector("[data-form_message='loading']");
629
630 const successMessage = parentNode.querySelector(".w-form-done");
631 const errorMessage = parentNode.querySelector(".w-form-fail");
632 const errorText = "Oops! Something went wrong while submitting the form.";
633
634 const setDisplay = (element, display = "block", callback = null) => {
635 if (element) {
636 if (callback) {
637 const textBox = element.querySelector("div") || element;
638
639 callback(textBox);
640 }
641
642 element.style.display = display;
643 }
644 };
645
646 try {
647 loadingMessage.classList.remove("hide");
648
649 const body = JSON.stringify(buildBody(fields));
650
651 const response = await fetch(url, {
652 method,
653 headers: { "Content-Type": "application/json", ...headers },
654 body
655 });
656
657 if (!response.ok) {
658 if (response.headers.get("Content-Type") === "application/json") {
659 const data = await response.json();
660
661 throw new Error(data.message || errorText);
662 }
663
664 throw new Error(errorText);
665 }
666
667 const data = response.status === 204 ? null : await response.json();
668
669 setDisplay(form, "none");
670 loadingMessage.classList.add("hide");
671
672 setDisplay(successMessage, "block", (textBox) => {
673 if (customSuccess) {
674 textBox.innerHTML = typeof customSuccess === "string"
675 ? customSuccess
676 : customSuccess(data);
677 }
678 });
679
680 setDisplay(errorMessage, "none");
681 mainSubmitButton.click();
682 } catch (error) {
683 setDisplay(form, formDisplay);
684 loadingMessage.classList.add("hide");
685 setDisplay(successMessage, "none");
686
687 setDisplay(errorMessage, "block", (textBox) => {
688 if (displayApiError) {
689 textBox.innerHTML = error.message || errorText;
690 }
691 });
692
693 throw error;
694 }
695 } else {
696 mainSubmitButton.click();
697 }
698 });
699 }
700};
701
702const initMediaMatch = (breakpoint, onMatch, onUnmatch) => {
703 const runOnMatch = (media) => {
704 if (media.matches) {
705 onMatch && onMatch();
706 } else {
707 onUnmatch && onUnmatch();
708 }
709 };
710
711 const media = window.matchMedia(
712 `only screen and (${breakpoint >= 0 ? "min" : "max"}-width: ${
713 breakpoint >= 0 ? breakpoint : breakpoint * -1
714 }px)`
715 );
716
717 runOnMatch(media);
718
719 window.addEventListener("resize", () => {
720 runOnMatch(media);
721 });
722
723 media.addEventListener("change", runOnMatch);
724};
725
726const initMasonry = (identifier, configSet) => {
727 const resolveConfig = (config, configIndex) => {
728 config.container = identifier;
729
730 if (config.surroundingGutter === undefined) {
731 config.surroundingGutter = false;
732 }
733
734 if (config.minify === undefined) {
735 config.minify = false;
736 }
737
738 if (config.wedge === undefined) {
739 config.wedge = true;
740 }
741
742 if (configIndex) {
743 const previousConfig = Object.values(configSet).reduce(
744 (init, item, i) => {
745 return i <= configIndex ? { ...init, ...item } : init;
746 },
747 {}
748 );
749
750 return { ...previousConfig, ...config };
751 }
752
753 return config;
754 };
755
756 const handleInitMasonries = () => {
757 const masonries = [];
758
759 if (configSet.baseWidth) {
760 const resolvedConfig = resolveConfig(configSet);
761 const masonry = new MiniMasonry(resolvedConfig);
762
763 masonries.push(masonry);
764 } else {
765 const breakpoints = Object.keys(configSet);
766
767 for (const [index, breakpoint] of breakpoints.entries()) {
768 let masonry = null;
769
770 const runOnMatch = (media) => {
771 if (media.matches && !masonry) {
772 const resolvedConfig = resolveConfig(configSet[breakpoint], index);
773
774 masonry = new MiniMasonry(resolvedConfig);
775 masonries.push(masonry);
776 }
777
778 if (!media.matches && masonry) {
779 masonry.destroy();
780 masonry = null;
781 }
782 };
783
784 const media = window.matchMedia(
785 `only screen and (min-width: ${breakpoint}px)`
786 );
787
788 runOnMatch(media);
789
790 window.addEventListener("resize", () => {
791 runOnMatch(media);
792 });
793
794 media.addEventListener("change", runOnMatch);
795 }
796 }
797
798 return masonries;
799 };
800
801 const { dataset: { breakpoint } } = document.querySelector(identifier);
802 let masonries = null;
803
804 if (breakpoint) {
805 const resolvedBreakpoint = parseInt(breakpoint);
806
807 const initOnMatch = (media) => {
808 if (media.matches && !masonries) {
809 masonries = handleInitMasonries();
810 }
811
812 if (!media.matches && masonries) {
813 masonries.forEach((masonry) => masonry.destroy());
814 masonries = null;
815 }
816 };
817
818 const media = window.matchMedia(
819 `only screen and (${resolvedBreakpoint >= 0 ? "min" : "max"}-width: ${
820 resolvedBreakpoint >= 0 ? resolvedBreakpoint : resolvedBreakpoint * -1
821 }px)`
822 );
823
824 initOnMatch(media);
825
826 window.addEventListener("resize", () => {
827 initOnMatch(media);
828 });
829
830 media.addEventListener("change", initOnMatch);
831 } else {
832 masonries = handleInitMasonries();
833 }
834};
835
836const getVisibility = (element) => {
837 const { top, bottom, left, right } = element.getBoundingClientRect();
838 const { innerWidth, innerHeight } = window;
839
840 if (top >= innerHeight || bottom <= 0 || left >= innerWidth || right <= 0) {
841 return "invisible";
842 }
843
844 if (top < innerHeight && bottom > 0 && left < innerWidth && right > 0) {
845 return "partial";
846 }
847
848 if (top >= 0 && bottom <= innerHeight && left >= 0 && right <= innerWidth) {
849 return "full";
850 }
851
852 return null;
853};
854
855const lazyLoadAssets = () => {
856 const loadAssets = () => {
857 const assets = document.querySelectorAll('[data-loading="lazy"]');
858
859 if (assets.length > 0) {
860 assets.forEach((asset) => {
861 const { top, bottom, left, right } = asset.getBoundingClientRect();
862 const { innerHeight, innerWidth } = window;
863
864 if (
865 top < innerHeight && bottom > 0 && left < innerWidth && right > 0
866 ) {
867 if (asset.tagName === "VIDEO") {
868 for (const source of asset.children) {
869 source.src = source.dataset.src;
870 }
871
872 asset.load();
873 }
874
875 if (asset.tagName === "DIV") {
876 asset.style.backgroundImage = `url(${asset.dataset.src})`;
877 }
878
879 asset.removeAttribute("data-loading");
880 }
881 });
882 } else {
883 window.removeEventListener("scroll", loadAssets);
884 window.removeEventListener("resize", loadAssets);
885 window.removeEventListener("orientationchange", loadAssets);
886 }
887 };
888
889 const assets = document.querySelectorAll('[data-loading="lazy"]');
890
891 if (assets.length > 0) {
892 loadAssets();
893 window.addEventListener("scroll", loadAssets);
894 window.addEventListener("resize", loadAssets);
895 window.addEventListener("orientationchange", loadAssets);
896 }
897};
898
899const injectSvgs = () => {
900 const svgTargets = document.querySelectorAll("[data-svg='target']");
901
902 if (svgTargets.length > 0) {
903 svgTargets.forEach((wrapper) => {
904 wrapper.parentNode.innerHTML = wrapper.innerText;
905 });
906 }
907};
908
909const initResponsiveGsapInteractions = () => {
910 const interactionElements = Array.from(
911 document.body.querySelectorAll("*")
912 ).filter((element) => (
913 Object.keys(element.dataset).some((key) => (
914 key.includes("interaction_")
915 )) && element.dataset.breakpoint
916 ));
917
918 if (interactionElements.length > 0) {
919 const interactionMap = interactionElements.reduce((init, element) => {
920 const { breakpoint } = element.dataset;
921
922 if (init[breakpoint]) {
923 init[breakpoint].push(element);
924 } else {
925 init[breakpoint] = [element];
926 }
927
928 return init;
929 }, {});
930
931 Object.entries(interactionMap).forEach(([breakpoint, elements]) => {
932 const resolvedBreakpoint = parseInt(breakpoint);
933
934 const media = window.matchMedia(`only screen and (${
935 resolvedBreakpoint >= 0 ? "min" : "max"
936 }-width: ${
937 resolvedBreakpoint >= 0 ? resolvedBreakpoint : resolvedBreakpoint * -1
938 }px)`);
939
940 const runOnMatch = (media) => {
941 if (!media.matches) {
942 elements.forEach((element) => {
943 const attribute = Object.keys(element.dataset).find((key) => (
944 key.includes("interaction_")
945 ));
946
947 element.setAttribute(attribute, "none");
948 });
949 }
950 };
951
952 runOnMatch(media);
953
954 window.addEventListener("resize", () => {
955 runOnMatch(media);
956 });
957
958 media.addEventListener("change", runOnMatch);
959 });
960 }
961};
962
963const initLenis = (config) => {
964 const lenis = new Lenis(config);
965
966 if (gsap && ScrollTrigger) {
967 lenis.on("scroll", ScrollTrigger.update);
968
969 gsap.ticker.add((time) => {
970 lenis.raf(time * 1000);
971 });
972
973 gsap.ticker.lagSmoothing(0);
974 }
975
976 window.constants = {
977 ...window.constants,
978 lenis
979 }
980};
981
982const initScrollAnchors = () => {
983 const triggers = Array.from(
984 document.querySelectorAll("[data-scroll_href]")
985 ).filter((trigger) => trigger.dataset.scroll_href);
986
987 if (triggers.length > 0) {
988 const setScrollTriggers = (triggers, scrollBottom) => {
989 triggers.forEach((trigger) => {
990 trigger.addEventListener("click", function (e) {
991 e.preventDefault();
992
993 const { scroll_href, scroll_delay, scroll_duration } = this.dataset;
994
995 const target = document.querySelector(
996 `[data-scroll_id="${scroll_href}"]`
997 );
998
999 if (target) {
1000 const { top } = target.getBoundingClientRect();
1001
1002 setTimeout(() => {
1003 const { lenis } = window.constants || {};
1004
1005 if (lenis) {
1006 lenis.scrollTo((top + lenis.scroll) - (
1007 scrollBottom ? window.innerHeight : 0
1008 ), {
1009 duration: parseInt(scroll_duration) || 1.2
1010 });
1011 } else {
1012 window.scrollTo({
1013 top: (top + window.scrollY) - (
1014 scrollBottom ? window.innerHeight : 0
1015 ),
1016 behavior: "smooth",
1017 });
1018 }
1019 }, parseInt(scroll_delay) || 0);
1020 } else {
1021 setTimeout(() => {
1022 const { lenis } = window.constants || {};
1023
1024 if (lenis) {
1025 lenis.scrollTo((
1026 scroll_href === "top" ? 0 : lenis.limit
1027 ), {
1028 duration: parseInt(scroll_duration) || 1.2
1029 });
1030 } else {
1031 window.scrollTo({
1032 top: (
1033 scroll_href === "top"
1034 ? 0
1035 : document.documentElement.scrollHeight
1036 ),
1037 behavior: "smooth",
1038 });
1039 }
1040 }, parseInt(scroll_delay) || 0);
1041 }
1042 });
1043 });
1044 };
1045
1046 const nonBreakpointTriggers = triggers.filter((trigger) => (
1047 !trigger.dataset.breakpoint
1048 ));
1049
1050 if (nonBreakpointTriggers.length > 0) {
1051 setScrollTriggers(nonBreakpointTriggers);
1052 }
1053
1054 const breakpointMap = triggers.reduce((init, trigger) => {
1055 const { scroll_bottom_breakpoint } = trigger.dataset;
1056
1057 if (init[scroll_bottom_breakpoint]) {
1058 init[scroll_bottom_breakpoint].push(trigger);
1059 } else {
1060 init[scroll_bottom_breakpoint] = [trigger];
1061 }
1062
1063 return init;
1064 }, {});
1065
1066 if (Object.keys(breakpointMap).length > 0) {
1067 Object.entries(breakpointMap).forEach(([breakpoint, triggers]) => {
1068 const resolvedBreakpoint = parseInt(breakpoint);
1069
1070 const media = window.matchMedia(`only screen and (${
1071 resolvedBreakpoint >= 0 ? "min" : "max"
1072 }-width: ${
1073 resolvedBreakpoint >= 0 ? resolvedBreakpoint : resolvedBreakpoint * -1
1074 }px)`);
1075
1076 const runOnMatch = (media) => {
1077 setScrollTriggers(triggers, media.matches);
1078 };
1079
1080 runOnMatch(media);
1081
1082 window.addEventListener("resize", () => {
1083 runOnMatch(media);
1084 });
1085
1086 media.addEventListener("change", runOnMatch);
1087 });
1088 }
1089 }
1090};
1091
1092</script>
10931<script>
2
3const initSlider = (identifier, config, effectConfigSet) => {
4 const sliderElement = document.querySelector(identifier);
5 const { parentNode, dataset: { breakpoint } } = sliderElement;
6
7 if (sliderElement.classList.contains("w-dyn-list")) {
8 const sliderWrapper = sliderElement.querySelector(".swiper-wrapper");
9
10 sliderWrapper.removeAttribute("role");
11 }
12
13 if (config.pagination) {
14 const pagination = parentNode.querySelector(".swiper-pagination");
15
16 if (pagination) {
17 const { id } = pagination;
18
19 const type = typeof config.pagination === "string"
20 ? config.pagination
21 : config.pagination.type;
22
23 const paginationConfig = { el: `#${id}`, type };
24
25 if (type === "bullets") {
26 const bullet = parentNode.querySelector(
27 ".swiper-pagination-bullet"
28 );
29
30 if (bullet) {
31 const { className } = bullet;
32
33 paginationConfig.bulletClass = className.replace(
34 "swiper-pagination-bullet-active",
35 ""
36 );
37
38 paginationConfig.clickable = true;
39 }
40 }
41
42 if (type === "progressbar") {
43 const progressBar = parentNode.querySelector(
44 ".swiper-pagination-progressbar-fill"
45 );
46
47 if (progressBar) {
48 const { className } = progressBar;
49
50 paginationConfig.progressbarFillClass = className;
51 }
52 }
53
54 config.pagination = Object.prototype.toString.call(
55 config.pagination
56 ) === "[object Object]"
57 ? { ...config.pagination, ...paginationConfig }
58 : paginationConfig;
59 }
60 }
61
62 if (config.navigation) {
63 const previousButton = parentNode.querySelector(".swiper-button-prev");
64 const nextButton = parentNode.querySelector(".swiper-button-next");
65
66 if (previousButton && nextButton) {
67 const { id: prevId } = previousButton;
68 const { id: nextId } = nextButton;
69
70 const navigationConfig = {
71 prevEl: `#${prevId}`,
72 nextEl: `#${nextId}`,
73 addIcons: false
74 };
75
76 config.navigation = Object.prototype.toString.call(
77 config.navigation
78 ) === "[object Object]"
79 ? { ...config.navigation, ...navigationConfig }
80 : navigationConfig;
81 }
82 }
83
84 if (config.scrollbar) {
85 const scrollbar = parentNode.querySelector(".swiper-scrollbar");
86
87 if (scrollbar) {
88 const { id } = scrollbar;
89 const scrollbarConfig = { el: `#${id}`, draggable: true };
90
91 config.scrollbar = Object.prototype.toString.call(
92 config.scrollbar
93 ) === "[object Object]"
94 ? { ...config.scrollbar, ...scrollbarConfig }
95 : scrollbarConfig;
96 }
97 }
98
99 if (config.loop) {
100 const currentBeforeInit = config.on && config.on.beforeInit
101 ? config.on.beforeInit
102 : null;
103
104 config.on = {
105 ...config.on,
106 beforeInit: (swiper) => {
107 const { el, wrapperEl } = swiper;
108
109 if (!el.classList.contains("init-slider-loop")) {
110 const slides = Array.from(wrapperEl.children);
111
112 slides.forEach((slide) => {
113 const clone = slide.cloneNode(true);
114
115 wrapperEl.appendChild(clone);
116 });
117
118 const webflow = window.Webflow;
119
120 if (webflow) {
121 webflow.destroy();
122 webflow.ready();
123 webflow.require("ix2") && webflow.require("ix2").init();
124 document.dispatchEvent(new Event("readystatechange"));
125 }
126
127 el.classList.add("init-slider-loop");
128 }
129
130 if (currentBeforeInit) {
131 currentBeforeInit(swiper);
132 }
133 }
134 };
135 }
136
137 const handleInitSliders = () => {
138 const sliders = [];
139
140 if (effectConfigSet) {
141 const breakpoints = Object.entries(effectConfigSet);
142
143 for (const [breakpoint, effectConfig] of breakpoints) {
144 let slider = null;
145
146 const runOnMatch = (media) => {
147 config.init = false;
148
149 const resolvedConfig = media.matches
150 ? { ...config, ...effectConfig }
151 : config;
152
153 if (slider) {
154 slider.destroy();
155 slider = null;
156 }
157
158 slider = new Swiper(identifier, resolvedConfig);
159 slider.init();
160 sliders.push(slider);
161 };
162
163 const media = window.matchMedia(
164 `only screen and (min-width: ${breakpoint}px)`
165 );
166
167 runOnMatch(media);
168
169 window.addEventListener("resize", () => {
170 runOnMatch(media);
171 });
172
173 media.addEventListener("change", runOnMatch);
174 }
175 } else {
176 const slider = new Swiper(identifier, config);
177
178 sliders.push(slider);
179 }
180
181 return sliders;
182 };
183
184 let sliders = null;
185
186 if (breakpoint) {
187 const resolvedBreakpoint = parseInt(breakpoint);
188
189 const initOnMatch = (media) => {
190 if (media.matches && !sliders) {
191 sliders = handleInitSliders();
192 }
193
194 if (!media.matches && sliders) {
195 sliders.forEach((slider) => slider.destroy());
196 sliders = null;
197 }
198 };
199
200 const media = window.matchMedia(
201 `only screen and (${resolvedBreakpoint >= 0 ? "min" : "max"}-width: ${
202 resolvedBreakpoint >= 0 ? resolvedBreakpoint : resolvedBreakpoint * -1
203 }px)`
204 );
205
206 initOnMatch(media);
207
208 window.addEventListener("resize", () => {
209 initOnMatch(media);
210 });
211
212 media.addEventListener("change", initOnMatch);
213 } else {
214 sliders = handleInitSliders();
215 }
216};
217
218const resolveSliderHeight = (swiper) => {
219 const { params, slides, el } = swiper;
220 const { direction, slidesPerView, loop, spaceBetween } = params;
221
222 if (direction && direction === "vertical") {
223 const activeIndex = swiper[loop ? "realIndex" : "activeIndex"];
224 const resolvedSlidesPerView = parseInt(slidesPerView) || 1;
225 const currentSlideLimit = activeIndex + resolvedSlidesPerView;
226 let height = 0;
227
228 for (let i = activeIndex; i < currentSlideLimit; i++) {
229 if (slides[i]) {
230 height += slides[i].offsetHeight;
231 }
232 }
233
234 if (spaceBetween && resolvedSlidesPerView > 1) {
235 height += (spaceBetween * resolvedSlidesPerView);
236 }
237
238 el.style.height = height > 0 ? `${height}px` : "auto";
239 }
240};
241
242const initSliderTransformSwitchers = (swiper) => {
243 const { el, slides, wrapperEl } = swiper;
244
245 if (!el.classList.contains("init-slider-transform-switchers")) {
246 el.classList.add("init-slider-transform-switchers");
247
248 slides.forEach((slide) => {
249 const switchers = slide.querySelectorAll("[data-slider_transform]");
250
251 if (switchers.length > 0) {
252 switchers.forEach((switcher) => {
253 switcher.addEventListener("click", () => {
254 const { slider_transform } = switcher.dataset;
255
256 if (slider_transform === "off") {
257 slide.classList.add("transform-none");
258 wrapperEl.classList.add("transform-none");
259 } else {
260 slide.classList.remove("transform-none");
261 wrapperEl.classList.remove("transform-none");
262 }
263 });
264 });
265 }
266 });
267 }
268};
269
270const forceLastSlideActive = (swiper) => {
271 swiper.snapGrid = [...swiper.slidesGrid];
272};
273
274const toggleSlideOverlays = (swiper) => {
275 const overlays = swiper.el.querySelectorAll("[data-slide='overlay']");
276
277 overlays.forEach((overlay) => {
278 overlay.classList.toggle("hide");
279 });
280};
281
282const resolveSpans = () => {
283 const texts = document.querySelectorAll("[data-span_style]");
284
285 if (texts.length > 0) {
286 texts.forEach((text) => {
287 const { span_style } = text.dataset;
288
289 if (span_style.length > 0) {
290 const spanClasses = span_style.split("|");
291 let index = 0;
292
293 const resolvedText = text.innerHTML.replace(
294 /\|([^|]+?)\|/g,
295 (_, content) => {
296 const spanClass = spanClasses[index];
297
298 if (spanClasses.length > 1) {
299 index++;
300 }
301
302 return spanClass
303 ? `<span class="${spanClass}">${content}</span>`
304 : content;
305 }
306 );
307
308 text.innerHTML = resolvedText;
309 }
310 });
311 }
312};
313
314const openActiveAccordions = () => {
315 const accordionHeaders = document.querySelectorAll(
316 "[data-accordion='active']"
317 );
318
319 if (accordionHeaders.length > 0) {
320 accordionHeaders.forEach((header) => {
321 header.dispatchEvent(new Event("mousedown"));
322 header.dispatchEvent(new Event("mouseup"));
323 header.click();
324 });
325 }
326};
327
328const initForceOpenDropdowns = () => {
329 const dropdowns = document.querySelectorAll("[data-force_class]");
330
331 if (dropdowns.length > 0) {
332 dropdowns.forEach((dropdown) => {
333 const { force_class } = dropdown.dataset;
334 const triggers = dropdown.querySelectorAll("[data-force_trigger]");
335
336 triggers.forEach((trigger) => {
337 trigger.addEventListener("click", function (e) {
338 e.preventDefault();
339
340 if (this.dataset.force_trigger === "open") {
341 dropdown.classList.add(force_class);
342 } else {
343 dropdown.classList.remove(force_class);
344 }
345 });
346 });
347 });
348 }
349};
350
351const initMirrorClick = () => {
352 const triggers = document.querySelectorAll("[data-mirror_trigger]");
353
354 if (triggers.length > 0) {
355 triggers.forEach((trigger) => {
356 trigger.addEventListener("click", function (e) {
357 e.preventDefault();
358
359 const { mirror_trigger, mirror_delay } = this.dataset;
360
361 const targets = document.querySelectorAll(
362 `[data-mirror_target="${mirror_trigger}"]`
363 );
364
365 if (targets.length > 0) {
366 setTimeout(() => {
367 targets.forEach((target) => {
368 if (
369 target.classList.contains("w-dropdown-toggle") &&
370 target.classList.contains("w--open")
371 ) {
372 return false;
373 }
374
375 target.dispatchEvent(new Event("mousedown"));
376 target.dispatchEvent(new Event("mouseup"));
377 target.click();
378 });
379 }, parseInt(mirror_delay) || 0);
380 }
381 });
382 });
383 }
384};
385
386const initCookie = (
387 modalIdentifier,
388 acceptIdentifier,
389 denyIdentifier,
390 expiry = 0
391) => {
392 if (document.cookie.includes("cookie_consent_accepted")) {
393 const modal = document.getElementById(modalIdentifier);
394
395 if (modal) {
396 modal.classList.add("hide");
397 }
398 } else {
399 const cookieButtons = document.querySelectorAll(
400 `#${acceptIdentifier}, #${denyIdentifier}`
401 );
402
403 if (cookieButtons.length > 0) {
404 cookieButtons.forEach((button) => {
405 button.addEventListener("click", function () {
406 let cookie = `cookie_consent_accepted=${
407 this.id === acceptIdentifier
408 };`;
409
410 if (expiry > 0) {
411 const date = new Date();
412
413 date.setDate(date.getDate() + expiry);
414 cookie += ` expires=${date.toUTCString()}`;
415 }
416
417 document.cookie = cookie;
418 });
419 });
420 }
421 }
422};
423
424const initProgressBars = () => {
425 const setValue = async (progressBar, targetValue) => {
426 const sleep = (waitTime) => new Promise(
427 (resolve) => setTimeout(resolve, parseInt(waitTime) || 250)
428 );
429
430 const { progress_transition } = progressBar.dataset;
431
432 const progressFill = progressBar.querySelector(
433 "[data-progress_target='fill']"
434 );
435
436 const progressValue = progressBar.querySelector(
437 "[data-progress_target='value']"
438 );
439
440 let resolvedTargetValue = parseInt(targetValue);
441 let resolvedCurrentValue = parseInt(progressValue.innerHTML);
442
443 progressFill.style.width = `${resolvedTargetValue}%`;
444
445 if (resolvedTargetValue > resolvedCurrentValue) {
446 while (resolvedTargetValue > resolvedCurrentValue) {
447 await sleep(progress_transition);
448 resolvedCurrentValue++;
449 progressValue.innerHTML = resolvedCurrentValue;
450 progressValue.setAttribute("aria-valuenow", resolvedCurrentValue);
451 }
452 } else {
453 while (resolvedTargetValue < resolvedCurrentValue) {
454 await sleep(progress_transition);
455 resolvedCurrentValue--;
456 progressValue.innerHTML = resolvedCurrentValue;
457 progressValue.setAttribute("aria-valuenow", resolvedCurrentValue);
458 }
459 }
460 };
461
462 const triggerSources = document.querySelectorAll(
463 "[data-progress_type='trigger'][data-progress_trigger_source]"
464 );
465
466 if (triggerSources.length > 0) {
467 triggerSources.forEach((source) => {
468 source.addEventListener("click", function () {
469 const { progress_trigger_source, progress_valuemax } = this.dataset;
470
471 const progressBar = document.querySelector(
472 `[data-progress_trigger_target='${progress_trigger_source}']`
473 );
474
475 setValue(progressBar, progress_valuemax);
476 });
477 });
478 }
479
480 const loadSources = document.querySelectorAll("[data-progress_type='load']");
481
482 if (loadSources.length > 0) {
483 const loadProgressBars = () => {
484 const sources = document.querySelectorAll("[data-progress_type='load']");
485
486 if (sources.length > 0) {
487 sources.forEach((source) => {
488 const { top, bottom, left, right } = source.getBoundingClientRect();
489 const { innerWidth, innerHeight } = window;
490
491 if (
492 top < innerHeight && bottom > 0 && left < innerWidth && right > 0
493 ) {
494 const targetValue = source.getAttribute("aria-valuemax");
495
496 setValue(source, targetValue);
497 source.removeAttribute("data-progress_type");
498 } else {
499 window.removeEventListener("scroll", loadProgressBars);
500 window.removeEventListener("resize", loadProgressBars);
501 window.removeEventListener("orientationchange", loadProgressBars);
502 }
503 });
504 }
505 };
506
507 loadProgressBars();
508 window.addEventListener("scroll", loadProgressBars);
509 window.addEventListener("resize", loadProgressBars);
510 window.addEventListener("orientationchange", loadProgressBars);
511 }
512};
513
514const initToc = () => {
515 const tocSources = document.querySelectorAll("[data-toc_source]");
516
517 if (tocSources.length > 0) {
518 tocSources.forEach((tocSource) => {
519 const tocTarget = document.querySelector(
520 `[data-toc_target="${tocSource.dataset.toc_source}"]`
521 );
522
523 const tocClone = tocTarget.firstElementChild.cloneNode(true);
524 let nextLevel = tocClone;
525 let depth = 1;
526
527 while (nextLevel.lastElementChild.querySelector("[data-toc='label']")) {
528 nextLevel = nextLevel.lastElementChild;
529 depth++;
530 }
531
532 tocTarget.innerHTML = "";
533
534 const headings = tocSource.querySelectorAll("h2, h3, h4, h5, h6");
535
536 headings.forEach((heading, i) => {
537 const level = parseInt(heading.tagName.replace("H", ""));
538
539 if (level <= depth) {
540 const headingSlug = `${
541 heading.textContent.toLowerCase().split(" ").join("-")
542 }-${i}`;
543
544 const offsets = document.querySelectorAll("[data-toc='offset']");
545 let anchorOffset = parseInt(tocTarget.dataset.toc_offset) || 16;
546
547 if (offsets.length > 0) {
548 offsets.forEach((offset) => {
549 anchorOffset += offset.offsetHeight;
550 });
551 }
552
553 const headingAnchor = document.createElement("div");
554
555 headingAnchor.style.position = "relative";
556
557 headingAnchor.innerHTML = `<div id="${
558 headingSlug
559 }" style="position: absolute; margin-top: -${
560 anchorOffset
561 }px;"></div>`;
562
563 tocSource.insertBefore(headingAnchor, heading);
564
565 let levelTarget = tocClone;
566
567 for (let l = 2; l < level; l++) {
568 if (levelTarget.querySelector("[data-toc='label']")) {
569 levelTarget = levelTarget.lastElementChild;
570 }
571 }
572
573 const levelTargetClone = levelTarget.cloneNode(true);
574
575 if (
576 level < depth &&
577 levelTargetClone.lastElementChild.querySelector(
578 "[data-toc='label']"
579 )
580 ) {
581 levelTargetClone.lastElementChild.remove();
582 }
583
584 const label = levelTargetClone.querySelector(
585 "[data-toc='label']"
586 );
587
588 if (label) {
589 label.innerHTML = heading.innerHTML;
590 }
591
592 const link = levelTargetClone.querySelector("a");
593
594 if (link) {
595 link.href = `#${headingSlug}`;
596 }
597
598 let parentTarget = tocTarget;
599
600 for (let l = 2; l < level - 1; l++) {
601 if (parentTarget.querySelector("[data-toc='label']")) {
602 parentTarget = parentTarget.lastElementChild;
603 }
604 }
605
606 parentTarget.appendChild(levelTargetClone);
607 }
608 });
609 });
610 }
611};
612
613const initAutoplayTabs = () => {
614 const setActiveTab = (tabLinks, index) => {
615 tabLinks.forEach((tabLink, i) => {
616 const tabPaneId = tabLink.href.split("#")[1];
617 const tabPane = document.getElementById(tabPaneId);
618
619 if (index === i) {
620 tabLink.classList.add("w--current");
621 tabLink.setAttribute("aria-selected", "true");
622 tabLink.removeAttribute("tabindex");
623 tabPane.classList.add("w--tab-active");
624 } else {
625 tabLink.classList.remove("w--current");
626 tabLink.setAttribute("aria-selected", "false");
627 tabLink.setAttribute("tabindex", "-1");
628 tabPane.classList.remove("w--tab-active");
629 }
630 });
631 };
632
633 const resolveIndex = (index, array) => {
634 return index < array.length - 1 ? index + 1 : 0;
635 };
636
637 const tabs = document.querySelectorAll("[data-autoplay_tab]");
638
639 tabs.forEach((tab) => {
640 const tabLinks = tab.querySelectorAll(".w-tab-link");
641 const timeInterval = parseInt(tab.dataset.autoplay_tab) || 1000;
642 let currentIndex = 0;
643
644 let tabInterval = setInterval(() => {
645 setActiveTab(tabLinks, currentIndex);
646 currentIndex = resolveIndex(currentIndex, tabLinks);
647 }, timeInterval);
648
649 tabLinks.forEach((tabLink, i) => {
650 tabLink.addEventListener("click", (e) => {
651 e.preventDefault();
652 clearInterval(tabInterval);
653 setActiveTab(tabLinks, i);
654 currentIndex = resolveIndex(i, tabLinks);
655
656 tabInterval = setInterval(() => {
657 setActiveTab(tabLinks, currentIndex);
658 currentIndex = resolveIndex(currentIndex, tabLinks);
659 }, timeInterval)
660 });
661 });
662 });
663};
664
665const initZoomer = (identifier, config) => {
666 const zoomerContainer = document.querySelector(identifier);
667
668 if (!zoomerContainer.classList.contains("zoomer-initialized")) {
669 new Zoomist(identifier, config);
670 zoomerContainer.classList.add("zoomer-initialized");
671 }
672};
673
674const initTimeToRead = (
675 wordsBefore,
676 wordsAfter = "read",
677 unit = "min",
678 wordsPerMinute = 100
679) => {
680 const timeSources = document.querySelectorAll("[data-time_source]");
681
682 if (timeSources.length > 0) {
683 timeSources.forEach((source) => {
684 const words = source.innerText.split(" ").length;
685 const images = source.querySelectorAll("img").length;
686 const videos = source.querySelectorAll("iframe").length;
687 const { time_video, time_source } = source.dataset;
688
689 const minutes = Math.floor(
690 (words / wordsPerMinute) +
691 ((images * 10) / 60) +
692 (time_video ? parseInt(time_video) : videos * 4)
693 );
694
695 const timeToRead = minutes > 1 ? `${minutes} ${unit}s` : `1 ${unit}`;
696
697 const timeTarget = document.querySelector(
698 `[data-time_target="${time_source}"]`
699 );
700
701 timeTarget.innerHTML = `${wordsBefore ? `${wordsBefore} ` : ""}${
702 timeToRead
703 }${wordsAfter ? ` ${wordsAfter}` : ""}`;
704 });
705 }
706};
707
708const initSocialShare = () => {
709 const urlSharers = document.querySelectorAll("[data-share_url]");
710
711 if(urlSharers.length > 0) {
712 urlSharers.forEach((sharer) => {
713 const { share_url, slug } = sharer.dataset;
714 const { origin, href } = window.location;
715 let socialMediaUrl = null;
716
717 switch (share_url) {
718 case "facebook":
719 socialMediaUrl = "https://www.facebook.com/sharer/sharer.php?u=";
720
721 break;
722 case "twitter":
723 case "x":
724 socialMediaUrl = "https://twitter.com/intent/tweet?url=";
725
726 break;
727 case "linkedin":
728 socialMediaUrl = "https://www.linkedin.com/sharing/share-offsite/?url=";
729
730 break;
731 default:
732 socialMediaUrl = share_url;
733 }
734
735 if (share_url === "clipboard") {
736 sharer.addEventListener("click", function (e) {
737 e.preventDefault();
738
739 const { slug } = this.dataset;
740 const { origin, href } = window.location;
741
742 navigator.clipboard.writeText(`${slug ? `${origin}${slug}` : href}`);
743 });
744 } else {
745 sharer.href = `${slug ? `${socialMediaUrl}${origin}${slug}` : href}`;
746 sharer.target = "_blank";
747 sharer.rel = "noopener noreferrer";
748 }
749 });
750 }
751};
752
753const initInterval = (callback, timeInterval = 250) => {
754 const interval = setInterval(() => {
755 callback(() => clearInterval(interval));
756 }, timeInterval);
757};
758
759const removeInvisibleElements = () => {
760 const invisibleElements = document.querySelectorAll(
761 ".w-condition-invisible"
762 );
763
764 if (invisibleElements.length > 0) {
765 invisibleElements.forEach((element) => {
766 element.remove();
767 });
768 }
769};
770
771const runAfterFinsweet = (attributeModules, callback, onRenderCallback) => {
772 window.fsAttributes = window.fsAttributes || [];
773
774 const attributeFlag = attributeModules.reduce((init, attributeModule) => {
775 init[attributeModule] = false;
776
777 return init;
778 }, {});
779
780 for (const attributeModule of attributeModules) {
781 window.fsAttributes.push([
782 attributeModule,
783 (instances) => {
784 attributeFlag[attributeModule] = true;
785
786 const allModulesRendered = Object.values(
787 attributeFlag
788 ).every((flag) => flag);
789
790 if (allModulesRendered) {
791 if (callback) {
792 callback();
793 }
794
795 if (onRenderCallback) {
796 for (const instance of instances) {
797 if (instance.listInstance) {
798 instance.listInstance.on("renderitems", (renderedItems) => {
799 onRenderCallback(renderedItems);
800 });
801 }
802 }
803 }
804 }
805 }
806 ]);
807 }
808};
809
810const runAfterFinsweetV2 = (callback) => {
811 window.FinsweetAttributes = window.FinsweetAttributes || [];
812 callback(window.FinsweetAttributes);
813};
814
815const resetWebflow = (version, callback) => {
816 const webflow = window.Webflow;
817
818 if (webflow) {
819 webflow.destroy();
820 webflow.ready();
821
822 if (version) {
823 const setVersion = webflow.require(version);
824
825 if (setVersion.init) {
826 setVersion.init();
827 }
828 }
829
830 document.dispatchEvent(new Event("readystatechange"));
831
832 if (callback) {
833 callback();
834 }
835 }
836};
837
838const resetWebflowAfterWized = (
839 onEndRequests,
840 onceRequests,
841 version,
842 callback
843) => {
844 const resetWebflow = () => {
845 const webflow = window.Webflow;
846
847 if (webflow) {
848 webflow.destroy();
849 webflow.ready();
850
851 if (version) {
852 const setVersion = webflow.require(version);
853
854 if (setVersion.init) {
855 setVersion.init();
856 }
857 }
858
859 document.dispatchEvent(new Event("readystatechange"));
860
861 if (callback) {
862 callback();
863 }
864 }
865 };
866
867 window.Wized = window.Wized || [];
868
869 window.Wized.push(async (Wized) => {
870 if (onEndRequests && onEndRequests.length > 0) {
871 Wized.on("requestend", ({ name }) => {
872 if (onEndRequests.some((request) => request === name)) {
873 resetWebflow();
874 }
875 });
876 }
877
878 if (onceRequests && onceRequests.length > 0) {
879 for (const request of onceRequests) {
880 await Wized.requests.waitFor(request);
881 }
882
883 resetWebflow();
884 }
885 });
886};
887
888</script>
889Grid
Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text.
Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text.
Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text.
Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text.
Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text.
1<style>
2
3html.wf-design-mode .masonry_item {
4 position: static;
5}
6
7</style>
8
9<script
10 src="https://cdn.jsdelivr.net/npm/minimasonry@1.3.2/build/minimasonry.min.js"
11></script>
12
13<script>
14
15const initMasonry = (identifier, configSet) => {
16 const resolveConfig = (config, configIndex) => {
17 config.container = identifier;
18
19 if (config.surroundingGutter === undefined) {
20 config.surroundingGutter = false;
21 }
22
23 if (config.minify === undefined) {
24 config.minify = false;
25 }
26
27 if (config.wedge === undefined) {
28 config.wedge = true;
29 }
30
31 if (configIndex) {
32 const previousConfig = Object.values(configSet).reduce(
33 (init, item, i) => {
34 return i <= configIndex ? { ...init, ...item } : init;
35 },
36 {}
37 );
38
39 return { ...previousConfig, ...config };
40 }
41
42 return config;
43 };
44
45 const handleInitMasonries = () => {
46 const masonries = [];
47
48 if (configSet.baseWidth) {
49 const resolvedConfig = resolveConfig(configSet);
50 const masonry = new MiniMasonry(resolvedConfig);
51
52 masonries.push(masonry);
53 } else {
54 const breakpoints = Object.keys(configSet);
55
56 for (const [index, breakpoint] of breakpoints.entries()) {
57 let masonry = null;
58
59 const runOnMatch = (media) => {
60 if (media.matches && !masonry) {
61 const resolvedConfig = resolveConfig(configSet[breakpoint], index);
62
63 masonry = new MiniMasonry(resolvedConfig);
64 masonries.push(masonry);
65 }
66
67 if (!media.matches && masonry) {
68 masonry.destroy();
69 masonry = null;
70 }
71 };
72
73 const media = window.matchMedia(
74 `only screen and (min-width: ${breakpoint}px)`
75 );
76
77 runOnMatch(media);
78
79 window.addEventListener("resize", () => {
80 runOnMatch(media);
81 });
82
83 media.addEventListener("change", runOnMatch);
84 }
85 }
86
87 return masonries;
88 };
89
90 const { dataset: { breakpoint } } = document.querySelector(identifier);
91 let masonries = null;
92
93 if (breakpoint) {
94 const resolvedBreakpoint = parseInt(breakpoint);
95
96 const initOnMatch = (media) => {
97 if (media.matches && !masonries) {
98 masonries = handleInitMasonries();
99 }
100
101 if (!media.matches && masonries) {
102 masonries.forEach((masonry) => masonry.destroy());
103 masonries = null;
104 }
105 };
106
107 const media = window.matchMedia(
108 `only screen and (${resolvedBreakpoint >= 0 ? "min" : "max"}-width: ${
109 resolvedBreakpoint >= 0 ? resolvedBreakpoint : resolvedBreakpoint * -1
110 }px)`
111 );
112
113 initOnMatch(media);
114
115 window.addEventListener("resize", () => {
116 initOnMatch(media);
117 });
118
119 media.addEventListener("change", initOnMatch);
120 } else {
121 masonries = handleInitMasonries();
122 }
123};
124
125</script>
126
127<script>
128
129document.addEventListener("DOMContentLoaded", () => {
130 new MiniMasonry({
131 container: "#masonry",
132 baseWidth: 255,
133 gutterX: 10 || 16,
134 gutterY: 10 || 16,
135 ultimateGutter: 5 || 16,
136 surroundingGutter: true || false,
137 minify: true || false,
138 wedge: false || true
139 });
140
141 initMasonry("#masonry", {
142 /* SINGLE CONFIG */
143 baseWidth: 255,
144 gutterX: 10 || 16,
145 gutterY: 10 || 16,
146 ultimateGutter: 5 || 16,
147 surroundingGutter: true || false,
148 minify: true || false,
149 wedge: false || true,
150 /* SINGLE CONFIG */
151
152 /* BREAKPOINT CONFIG */
153 0: {
154 baseWidth: 255,
155 gutterX: 10 || 16,
156 gutterY: 10 || 16,
157 ultimateGutter: 5 || 16,
158 surroundingGutter: true || false,
159 minify: true || false,
160 wedge: false || true
161 },
162 480: {},
163 768: {},
164 992: {},
165 1280: {},
166 1440: {},
167 1920: {}
168 /* BREAKPOINT CONFIG */
169 });
170});
171
172</script>
173Layout Part
Media
1<script>
2
3const lazyLoadAssets = () => {
4 const loadAssets = () => {
5 const assets = document.querySelectorAll('[data-loading="lazy"]');
6
7 if (assets.length > 0) {
8 assets.forEach((asset) => {
9 const { top, bottom, left, right } = asset.getBoundingClientRect();
10 const { innerHeight, innerWidth } = window;
11
12 if (
13 top < innerHeight && bottom > 0 && left < innerWidth && right > 0
14 ) {
15 if (asset.tagName === "VIDEO") {
16 for (const source of asset.children) {
17 source.src = source.dataset.src;
18 }
19
20 asset.load();
21 }
22
23 if (asset.tagName === "DIV") {
24 asset.style.backgroundImage = `url(${asset.dataset.src})`;
25 }
26
27 asset.removeAttribute("data-loading");
28 }
29 });
30 } else {
31 window.removeEventListener("scroll", loadAssets);
32 window.removeEventListener("resize", loadAssets);
33 window.removeEventListener("orientationchange", loadAssets);
34 }
35 };
36
37 const assets = document.querySelectorAll('[data-loading="lazy"]');
38
39 if (assets.length > 0) {
40 loadAssets();
41 window.addEventListener("scroll", loadAssets);
42 window.addEventListener("resize", loadAssets);
43 window.addEventListener("orientationchange", loadAssets);
44 }
45};
46
47</script>
48
49<script>
50
51document.addEventListener("DOMContentLoaded", () => {
52 lazyLoadAssets();
53});
54
55</script>
561<script>
2
3const injectSvgs = () => {
4 const svgTargets = document.querySelectorAll("[data-svg='target']");
5
6 if (svgTargets.length > 0) {
7 svgTargets.forEach((wrapper) => {
8 wrapper.parentNode.innerHTML = wrapper.innerText;
9 });
10 }
11};
12
13</script>
14
15<script>
16
17document.addEventListener("DOMContentLoaded", () => {
18 injectSvgs();
19});
20
21</script>
221<script>
2
3const lazyLoadAssets = () => {
4 const loadAssets = () => {
5 const assets = document.querySelectorAll('[data-loading="lazy"]');
6
7 if (assets.length > 0) {
8 assets.forEach((asset) => {
9 const { top, bottom, left, right } = asset.getBoundingClientRect();
10 const { innerHeight, innerWidth } = window;
11
12 if (
13 top < innerHeight && bottom > 0 && left < innerWidth && right > 0
14 ) {
15 if (asset.tagName === "VIDEO") {
16 for (const source of asset.children) {
17 source.src = source.dataset.src;
18 }
19
20 asset.load();
21 }
22
23 if (asset.tagName === "DIV") {
24 asset.style.backgroundImage = `url(${asset.dataset.src})`;
25 }
26
27 asset.removeAttribute("data-loading");
28 }
29 });
30 } else {
31 window.removeEventListener("scroll", loadAssets);
32 window.removeEventListener("resize", loadAssets);
33 window.removeEventListener("orientationchange", loadAssets);
34 }
35 };
36
37 const assets = document.querySelectorAll('[data-loading="lazy"]');
38
39 if (assets.length > 0) {
40 loadAssets();
41 window.addEventListener("scroll", loadAssets);
42 window.addEventListener("resize", loadAssets);
43 window.addEventListener("orientationchange", loadAssets);
44 }
45};
46
47</script>
48
49<script>
50
51document.addEventListener("DOMContentLoaded", () => {
52 lazyLoadAssets();
53});
54
55</script>
56Navigation
1<script>
2
3const initLenis = (config) => {
4 const lenis = new Lenis(config);
5
6 if (gsap && ScrollTrigger) {
7 lenis.on("scroll", ScrollTrigger.update);
8
9 gsap.ticker.add((time) => {
10 lenis.raf(time * 1000);
11 });
12
13 gsap.ticker.lagSmoothing(0);
14 }
15
16 window.constants = {
17 ...window.constants,
18 lenis
19 }
20};
21
22const initScrollAnchors = () => {
23 const triggers = Array.from(
24 document.querySelectorAll("[data-scroll_href]")
25 ).filter((trigger) => trigger.dataset.scroll_href);
26
27 if (triggers.length > 0) {
28 const setScrollTriggers = (triggers, scrollBottom) => {
29 triggers.forEach((trigger) => {
30 trigger.addEventListener("click", function (e) {
31 e.preventDefault();
32
33 const { scroll_href, scroll_delay, scroll_duration } = this.dataset;
34
35 const target = document.querySelector(
36 `[data-scroll_id="${scroll_href}"]`
37 );
38
39 if (target) {
40 const { top } = target.getBoundingClientRect();
41
42 setTimeout(() => {
43 const { lenis } = window.constants || {};
44
45 if (lenis) {
46 lenis.scrollTo((top + lenis.scroll) - (
47 scrollBottom ? window.innerHeight : 0
48 ), {
49 duration: parseInt(scroll_duration) || 1.2
50 });
51 } else {
52 window.scrollTo({
53 top: (top + window.scrollY) - (
54 scrollBottom ? window.innerHeight : 0
55 ),
56 behavior: "smooth",
57 });
58 }
59 }, parseInt(scroll_delay) || 0);
60 } else {
61 setTimeout(() => {
62 const { lenis } = window.constants || {};
63
64 if (lenis) {
65 lenis.scrollTo((
66 scroll_href === "top" ? 0 : lenis.limit
67 ), {
68 duration: parseInt(scroll_duration) || 1.2
69 });
70 } else {
71 window.scrollTo({
72 top: (
73 scroll_href === "top"
74 ? 0
75 : document.documentElement.scrollHeight
76 ),
77 behavior: "smooth",
78 });
79 }
80 }, parseInt(scroll_delay) || 0);
81 }
82 });
83 });
84 };
85
86 const nonBreakpointTriggers = triggers.filter((trigger) => (
87 !trigger.dataset.breakpoint
88 ));
89
90 if (nonBreakpointTriggers.length > 0) {
91 setScrollTriggers(nonBreakpointTriggers);
92 }
93
94 const breakpointMap = triggers.reduce((init, trigger) => {
95 const { scroll_bottom_breakpoint } = trigger.dataset;
96
97 if (init[scroll_bottom_breakpoint]) {
98 init[scroll_bottom_breakpoint].push(trigger);
99 } else {
100 init[scroll_bottom_breakpoint] = [trigger];
101 }
102
103 return init;
104 }, {});
105
106 if (Object.keys(breakpointMap).length > 0) {
107 Object.entries(breakpointMap).forEach(([breakpoint, triggers]) => {
108 const resolvedBreakpoint = parseInt(breakpoint);
109
110 const media = window.matchMedia(`only screen and (${
111 resolvedBreakpoint >= 0 ? "min" : "max"
112 }-width: ${
113 resolvedBreakpoint >= 0 ? resolvedBreakpoint : resolvedBreakpoint * -1
114 }px)`);
115
116 const runOnMatch = (media) => {
117 setScrollTriggers(triggers, media.matches);
118 };
119
120 runOnMatch(media);
121
122 window.addEventListener("resize", () => {
123 runOnMatch(media);
124 });
125
126 media.addEventListener("change", runOnMatch);
127 });
128 }
129 }
130};
131
132</script>
133
134<script>
135
136initLenis({});
137
138document.addEventListener("DOMContentLoaded", () => {
139 initScrollAnchors();
140});
141
142</script>
143Section
Coming Soon
We are working hard to bring you something amazing. Stay tuned!
Section Part
Slider
1<head>
2
3<link
4 rel="stylesheet"
5 href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css"
6/>
7
8<style>
9
10:root {
11 --swiper-pagination-bullet-horizontal-gap: 0rem;
12 --swiper-pagination-bullet-vertical-gap: 0rem;
13 --swiper-pagination-color: var(--base-color-misc--blue-500, #007aff);
14}
15
16.swiper-button-prev:after,
17.swiper-button-next:after {
18 display: none;
19}
20
21.transform-none {
22 transform: none !important;
23}
24
25</style>
26
27</head>
28
29<body>
30
31<script
32 src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"
33></script>
34
35<script>
36
37const initSlider = (identifier, config, effectConfigSet) => {
38 const sliderElement = document.querySelector(identifier);
39 const { parentNode, dataset: { breakpoint } } = sliderElement;
40
41 if (sliderElement.classList.contains("w-dyn-list")) {
42 const sliderWrapper = sliderElement.querySelector(".swiper-wrapper");
43
44 sliderWrapper.removeAttribute("role");
45 }
46
47 if (config.pagination) {
48 const pagination = parentNode.querySelector(".swiper-pagination");
49
50 if (pagination) {
51 const { id } = pagination;
52
53 const type = typeof config.pagination === "string"
54 ? config.pagination
55 : config.pagination.type;
56
57 const paginationConfig = { el: `#${id}`, type };
58
59 if (type === "bullets") {
60 const bullet = parentNode.querySelector(
61 ".swiper-pagination-bullet"
62 );
63
64 if (bullet) {
65 const { className } = bullet;
66
67 paginationConfig.bulletClass = className.replace(
68 "swiper-pagination-bullet-active",
69 ""
70 );
71
72 paginationConfig.clickable = true;
73 }
74 }
75
76 if (type === "progressbar") {
77 const progressBar = parentNode.querySelector(
78 ".swiper-pagination-progressbar-fill"
79 );
80
81 if (progressBar) {
82 const { className } = progressBar;
83
84 paginationConfig.progressbarFillClass = className;
85 }
86 }
87
88 config.pagination = Object.prototype.toString.call(
89 config.pagination
90 ) === "[object Object]"
91 ? { ...config.pagination, ...paginationConfig }
92 : paginationConfig;
93 }
94 }
95
96 if (config.navigation) {
97 const previousButton = parentNode.querySelector(".swiper-button-prev");
98 const nextButton = parentNode.querySelector(".swiper-button-next");
99
100 if (previousButton && nextButton) {
101 const { id: prevId } = previousButton;
102 const { id: nextId } = nextButton;
103
104 const navigationConfig = {
105 prevEl: `#${prevId}`,
106 nextEl: `#${nextId}`,
107 addIcons: false
108 };
109
110 config.navigation = Object.prototype.toString.call(
111 config.navigation
112 ) === "[object Object]"
113 ? { ...config.navigation, ...navigationConfig }
114 : navigationConfig;
115 }
116 }
117
118 if (config.scrollbar) {
119 const scrollbar = parentNode.querySelector(".swiper-scrollbar");
120
121 if (scrollbar) {
122 const { id } = scrollbar;
123 const scrollbarConfig = { el: `#${id}`, draggable: true };
124
125 config.scrollbar = Object.prototype.toString.call(
126 config.scrollbar
127 ) === "[object Object]"
128 ? { ...config.scrollbar, ...scrollbarConfig }
129 : scrollbarConfig;
130 }
131 }
132
133 const handleInitSliders = () => {
134 const sliders = [];
135
136 if (effectConfigSet) {
137 const breakpoints = Object.entries(effectConfigSet);
138
139 for (const [breakpoint, effectConfig] of breakpoints) {
140 let slider = null;
141
142 const runOnMatch = (media) => {
143 config.init = false;
144
145 const resolvedConfig = media.matches
146 ? { ...config, ...effectConfig }
147 : config;
148
149 if (slider) {
150 slider.destroy();
151 slider = null;
152 }
153
154 slider = new Swiper(identifier, resolvedConfig);
155 slider.init();
156 sliders.push(slider);
157 };
158
159 const media = window.matchMedia(
160 `only screen and (min-width: ${breakpoint}px)`
161 );
162
163 runOnMatch(media);
164
165 window.addEventListener("resize", () => {
166 runOnMatch(media);
167 });
168
169 media.addEventListener("change", runOnMatch);
170 }
171 } else {
172 const slider = new Swiper(identifier, config);
173
174 sliders.push(slider);
175 }
176
177 return sliders;
178 };
179
180 let sliders = null;
181
182 if (breakpoint) {
183 const resolvedBreakpoint = parseInt(breakpoint);
184
185 const initOnMatch = (media) => {
186 if (media.matches && !sliders) {
187 sliders = handleInitSliders();
188 }
189
190 if (!media.matches && sliders) {
191 sliders.forEach((slider) => slider.destroy());
192 sliders = null;
193 }
194 };
195
196 const media = window.matchMedia(
197 `only screen and (${resolvedBreakpoint >= 0 ? "min" : "max"}-width: ${
198 resolvedBreakpoint >= 0 ? resolvedBreakpoint : resolvedBreakpoint * -1
199 }px)`
200 );
201
202 initOnMatch(media);
203
204 window.addEventListener("resize", () => {
205 initOnMatch(media);
206 });
207
208 media.addEventListener("change", initOnMatch);
209 } else {
210 sliders = handleInitSliders();
211 }
212};
213
214const resolveSliderHeight = (swiper) => {
215 const { params, slides, el } = swiper;
216 const { direction, slidesPerView, loop, spaceBetween } = params;
217
218 if (direction && direction === "vertical") {
219 const activeIndex = swiper[loop ? "realIndex" : "activeIndex"];
220 const resolvedSlidesPerView = parseInt(slidesPerView) || 1;
221 const currentSlideLimit = activeIndex + resolvedSlidesPerView;
222 let height = 0;
223
224 for (let i = activeIndex; i < currentSlideLimit; i++) {
225 if (slides[i]) {
226 height += slides[i].offsetHeight;
227 }
228 }
229
230 if (spaceBetween && resolvedSlidesPerView > 1) {
231 height += (spaceBetween * resolvedSlidesPerView);
232 }
233
234 el.style.height = height > 0 ? `${height}px` : "auto";
235 }
236};
237
238const initSliderTransformSwitchers = (swiper) => {
239 const { el, slides, wrapperEl } = swiper;
240
241 if (!el.classList.contains("init-slider-transform-switchers")) {
242 el.classList.add("init-slider-transform-switchers");
243
244 slides.forEach((slide) => {
245 const switchers = slide.querySelectorAll("[data-slider_transform]");
246
247 if (switchers.length > 0) {
248 switchers.forEach((switcher) => {
249 switcher.addEventListener("click", () => {
250 const { slider_transform } = switcher.dataset;
251
252 if (slider_transform === "off") {
253 slide.classList.add("transform-none");
254 wrapperEl.classList.add("transform-none");
255 } else {
256 slide.classList.remove("transform-none");
257 wrapperEl.classList.remove("transform-none");
258 }
259 });
260 });
261 }
262 });
263 }
264};
265
266const forceLastSlideActive = (swiper) => {
267 swiper.snapGrid = [...swiper.slidesGrid];
268};
269
270const toggleSlideOverlays = (swiper) => {
271 const overlays = swiper.el.querySelectorAll("[data-slide='overlay']");
272
273 overlays.forEach((overlay) => {
274 overlay.classList.toggle("hide");
275 });
276};
277
278</script>
279
280<script>
281
282document.addEventListener("DOMContentLoaded", () => {
283 new Swiper("#slider", {
284 /* ALWAYS */
285 slidesPerView: "auto" || 1 || 1.5 || 2,
286 slidesPerGroup: 1,
287 /*
288 AUTO SLIDE GROUPING PER NAVIGATION REQUIREMENTS
289 slidesPerView: "auto"
290 slidesPerGroup: 1
291 */
292 slidesPerGroupAuto: false || true,
293 spaceBetween: 0 || 16 || 16 * 2,
294 /* ALWAYS */
295
296 /* NAVIGATION */
297 pagination: {
298 el: ".swiper-pagination" || "#slider-pagination",
299 type: "bullets" || "progressbar",
300 clickable: true,
301 bulletClass: "slider-pagination_bullet swiper-pagination-bullet",
302 progressbarFillClass: "slider-pagination_progressbar-fill swiper-pagination-progressbar-fill"
303 },
304 navigation: {
305 prevEl: ".swiper-button-prev" || "#slider-previous-button",
306 nextEl: ".swiper-button-next" || "#slider-next-button",
307 addIcons: false
308 },
309 scrollbar: {
310 el: ".swiper-scrollbar" || "#slider-scrollbar",
311 draggable: true
312 },
313 /* NAVIGATION */
314
315 /* OPTIONAL */
316 init: false,
317 loop: true,
318 loopAdditionalSlides: 0 || 2,
319 grabCursor: true,
320 direction: "vertical",
321 centeredSlides: true,
322 slideToClickedSlide: true,
323 effect: "coverflow",
324 coverflowEffect: {
325 depth: 100 || 300,
326 modifier: 1 || 2,
327 rotate: 50 || 0,
328 scale: 1,
329 stretch: 0,
330 slideShadows: true || false
331 },
332 on: {
333 init: (swiper) => {
334 resolveSliderHeight(swiper);
335 initSliderTransformSwitchers(swiper);
336 },
337 slideChange: resolveSliderHeight,
338 slidesUpdated: resolveSliderHeight,
339 resize: resolveSliderHeight,
340 orientationchange: resolveSliderHeight,
341 reachEnd: forceLastSlideActive,
342 touchStart: toggleSlideOverlays,
343 touchEnd: toggleSlideOverlays
344 },
345 breakpoints: {
346 0: {
347 direction: "horizontal"
348 },
349 480: {},
350 768: {},
351 992: {
352 direction: "vertical",
353 slidesPerView: 2
354 },
355 1280: {},
356 1440: {},
357 1920: {}
358 }
359 /* OPTIONAL */
360 });
361
362 initSlider("#slider", {
363 /* ALWAYS */
364 slidesPerView: "auto" || 1 || 1.5 || 2,
365 slidesPerGroup: 1,
366 /*
367 AUTO SLIDE GROUPING PER NAVIGATION REQUIREMENTS
368 slidesPerView: "auto"
369 slidesPerGroup: 1
370 */
371 slidesPerGroupAuto: false || true,
372 spaceBetween: 0 || 16 || 16 * 2,
373 /* ALWAYS */
374
375 /* NAVIGATION */
376 pagination: "bullets" || "progressbar" || "fraction",
377 navigation: true,
378 scrollbar: true,
379 /* NAVIGATION */
380
381 /* OPTIONAL */
382 init: false,
383 loop: true,
384 loopAdditionalSlides: 0 || 2,
385 grabCursor: true,
386 direction: "vertical",
387 centeredSlides: true,
388 slideToClickedSlide: true,
389 on: {
390 init: (swiper) => {
391 resolveSliderHeight(swiper);
392 initSliderTransformSwitchers(swiper);
393 },
394 slideChange: resolveSliderHeight,
395 slidesUpdated: resolveSliderHeight,
396 resize: resolveSliderHeight,
397 orientationchange: resolveSliderHeight,
398 reachEnd: forceLastSlideActive,
399 touchStart: toggleSlideOverlays,
400 touchEnd: toggleSlideOverlays
401 },
402 breakpoints: {
403 0: {
404 direction: "horizontal"
405 },
406 480: {},
407 768: {},
408 992: {
409 direction: "vertical",
410 slidesPerView: 2
411 },
412 1280: {},
413 1440: {},
414 1920: {}
415 }
416 /* OPTIONAL */
417 }, {
418 992: {
419 effect: "coverflow",
420 coverflowEffect: {
421 depth: 100 || 300,
422 modifier: 1 || 2,
423 rotate: 50 || 0,
424 scale: 1,
425 stretch: 0,
426 slideShadows: true || false
427 }
428 }
429 });
430});
431
432</script>
433
434</body>
4351<!-- Finsweet Attributes -->
2<script async type="module"
3src="https://cdn.jsdelivr.net/npm/@finsweet/attributes@2/attributes.js"
4fs-list
5fs-sliderdots
6></script>
7Typography
1<script>
2
3const resolveSpans = () => {
4 const texts = document.querySelectorAll("[data-span_style]");
5
6 if (texts.length > 0) {
7 texts.forEach((text) => {
8 const { span_style } = text.dataset;
9
10 if (span_style.length > 0) {
11 const spanClasses = span_style.split("|");
12 let index = 0;
13
14 const resolvedText = text.innerHTML.replace(
15 /\|([^|]+?)\|/g,
16 (_, content) => {
17 const spanClass = spanClasses[index];
18
19 if (spanClasses.length > 1) {
20 index++;
21 }
22
23 return spanClass
24 ? `<span class="${spanClass}">${content}</span>`
25 : content;
26 }
27 );
28
29 text.innerHTML = resolvedText;
30 }
31 });
32 }
33};
34
35</script>
36
37<script>
38
39document.addEventListener("DOMContentLoaded", () => {
40 resolveSpans();
41});
42
43</script>
44Heading
Heading
Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text.
Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text.
Utility
Coming Soon
We are working hard to bring you something amazing. Stay tuned!
Coming Soon
We are working hard to bring you something amazing. Stay tuned!
Still in the Works
(Feature Section)
We are working hard to bring you something amazing. Stay tuned!
Thank You
We will be in touch with you soon. Have a great day!
404
Oops! The page you are looking for doesn't exist or has been moved.
Widget
Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text.
Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text.
1<script>
2
3const openActiveAccordions = () => {
4 const accordionHeaders = document.querySelectorAll(
5 "[data-accordion='active']"
6 );
7
8 if (accordionHeaders.length > 0) {
9 accordionHeaders.forEach((header) => {
10 header.dispatchEvent(new Event("mousedown"));
11 header.dispatchEvent(new Event("mouseup"));
12 header.click();
13 });
14 }
15};
16
17</script>
18
19<script>
20
21document.addEventListener("DOMContentLoaded", () => {
22 openActiveAccordions();
23});
24
25</script>
26
1<!-- Finsweet Attributes -->
2<script async type="module"
3src="https://cdn.jsdelivr.net/npm/@finsweet/attributes@2/attributes.js"
4fs-list
5></script>
61<script>
2
3const initForceOpenDropdowns = () => {
4 const dropdowns = document.querySelectorAll("[data-force_class]");
5
6 if (dropdowns.length > 0) {
7 dropdowns.forEach((dropdown) => {
8 const { force_class } = dropdown.dataset;
9 const triggers = dropdown.querySelectorAll("[data-force_trigger]");
10
11 triggers.forEach((trigger) => {
12 trigger.addEventListener("click", function (e) {
13 e.preventDefault();
14
15 if (this.dataset.force_trigger === "open") {
16 dropdown.classList.add(force_class);
17 } else {
18 dropdown.classList.remove(force_class);
19 }
20 });
21 });
22 });
23 }
24};
25
26const initMirrorClick = () => {
27 const triggers = document.querySelectorAll("[data-mirror_trigger]");
28
29 if (triggers.length > 0) {
30 triggers.forEach((trigger) => {
31 trigger.addEventListener("click", function (e) {
32 e.preventDefault();
33
34 const { mirror_trigger, mirror_delay } = this.dataset;
35
36 const targets = document.querySelectorAll(
37 `[data-mirror_target="${mirror_trigger}"]`
38 );
39
40 if (targets.length > 0) {
41 setTimeout(() => {
42 targets.forEach((target) => {
43 if (
44 target.classList.contains("w-dropdown-toggle") &&
45 target.classList.contains("w--open")
46 ) {
47 return false;
48 }
49
50 target.dispatchEvent(new Event("mousedown"));
51 target.dispatchEvent(new Event("mouseup"));
52 target.click();
53 });
54 }, parseInt(mirror_delay) || 0);
55 }
56 });
57 });
58 }
59};
60
61</script>
62
63<script>
64
65document.addEventListener("DOMContentLoaded", () => {
66 initForceOpenDropdowns();
67 initMirrorClick();
68});
69
70</script>
711<script
2 src="https://cdn.jsdelivr.net/npm/gsap@3.12.7/dist/gsap.min.js"
3></script>
4
5<script
6 src="https://cdn.jsdelivr.net/npm/gsap@3.12.7/dist/Draggable.min.js"
7></script>
8
9<script>
10
11gsap.registerPlugin(Draggable);
12
13document.addEventListener("DOMContentLoaded", () => {
14 Draggable.create("#horizontal-scroller-wrapper", {
15 type: "x",
16 bounds: "#horizontal-scroller-container"
17 });
18});
19
20</script>
211<style>
2
3.component_initial div::first-letter {
4 font-size: initial;
5}
6
7</style>
8--00--00--00--00-- Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text.
--11--11--11--11-- Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text.
1<style>
2
3@keyframes marquee-slide-loop {
4 0% {
5 transform: translateX(0%);
6 }
7
8 100% {
9 transform: translateX(-50%);
10 }
11}
12
13.marquee_wrapper {
14 animation: marquee-slide-loop 30s linear 0s infinite;
15}
16
17.marquee_wrapper:hover {
18 animation-play-state: paused;
19}
20
21</style>
22By clicking "Accept", you agree to the storing of cookies on your device to enhance site navigation, analyze site usage, and assist in our marketing efforts. View our Privacy Policy for more information.
1<script>
2
3const initCookie = (
4 modalIdentifier,
5 acceptIdentifier,
6 denyIdentifier,
7 expiry = 1
8) => {
9 if (document.cookie.includes("cookie_consent_accepted")) {
10 const modal = document.getElementById(modalIdentifier);
11
12 if (modal) {
13 modal.classList.add("hide");
14 }
15 } else {
16 const cookieButtons = document.querySelectorAll(
17 `#${acceptIdentifier}, #${denyIdentifier}`
18 );
19
20 if (cookieButtons.length > 0) {
21 cookieButtons.forEach((button) => {
22 button.addEventListener("click", function () {
23 const date = new Date();
24
25 date.setDate(date.getDate() + expiry);
26
27 document.cookie = `cookie_consent_accepted=${
28 this.id === acceptIdentifier
29 }; expires=${date.toUTCString()}`;
30 });
31 });
32 }
33 }
34};
35
36</script>
37
38<script>
39
40document.addEventListener("DOMContentLoaded", () => {
41 initCookie("cookie-modal", "cookie-accept", "cookie-deny", 1);
42});
43
44</script>
451<script
2 src="https://cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js"
3></script>
4
5<script>
6
7document.addEventListener("DOMContentLoaded", () => {
8 new QRCode("qr-code", {
9 text: "https://flow-of-things.webflow.io/",
10 width: 160,
11 height: 160,
12 colorDark: "#000000",
13 colorLight: "#ffffff",
14 correctLevel: QRCode.CorrectLevel.H
15 });
16});
17
18</script>
191<script>
2
3const initProgressBars = () => {
4 const setValue = async (progressBar, targetValue) => {
5 const sleep = (waitTime) => new Promise(
6 (resolve) => setTimeout(resolve, parseInt(waitTime) || 250)
7 );
8
9 const { progress_transition } = progressBar.dataset;
10
11 const progressFill = progressBar.querySelector(
12 "[data-progress_target='fill']"
13 );
14
15 const progressValue = progressBar.querySelector(
16 "[data-progress_target='value']"
17 );
18
19 let resolvedTargetValue = parseInt(targetValue);
20 let resolvedCurrentValue = parseInt(progressValue.innerHTML);
21
22 progressFill.style.width = `${resolvedTargetValue}%`;
23
24 if (resolvedTargetValue > resolvedCurrentValue) {
25 while (resolvedTargetValue > resolvedCurrentValue) {
26 await sleep(progress_transition);
27 resolvedCurrentValue++;
28 progressValue.innerHTML = resolvedCurrentValue;
29 progressValue.setAttribute("aria-valuenow", resolvedCurrentValue);
30 }
31 } else {
32 while (resolvedTargetValue < resolvedCurrentValue) {
33 await sleep(progress_transition);
34 resolvedCurrentValue--;
35 progressValue.innerHTML = resolvedCurrentValue;
36 progressValue.setAttribute("aria-valuenow", resolvedCurrentValue);
37 }
38 }
39 };
40
41 const triggerSources = document.querySelectorAll(
42 "[data-progress_type='trigger'][data-progress_trigger_source]"
43 );
44
45 if (triggerSources.length > 0) {
46 triggerSources.forEach((source) => {
47 source.addEventListener("click", function () {
48 const { progress_trigger_source, progress_valuemax } = this.dataset;
49
50 const progressBar = document.querySelector(
51 `[data-progress_trigger_target='${progress_trigger_source}']`
52 );
53
54 setValue(progressBar, progress_valuemax);
55 });
56 });
57 }
58
59 const loadSources = document.querySelectorAll("[data-progress_type='load']");
60
61 if (loadSources.length > 0) {
62 const loadProgressBars = () => {
63 const sources = document.querySelectorAll("[data-progress_type='load']");
64
65 if (sources.length > 0) {
66 sources.forEach((source) => {
67 const { top, bottom, left, right } = source.getBoundingClientRect();
68 const { innerWidth, innerHeight } = window;
69
70 if (
71 top < innerHeight && bottom > 0 && left < innerWidth && right > 0
72 ) {
73 const targetValue = source.getAttribute("aria-valuemax");
74
75 setValue(source, targetValue);
76 source.removeAttribute("data-progress_type");
77 } else {
78 window.removeEventListener("scroll", loadProgressBars);
79 window.removeEventListener("resize", loadProgressBars);
80 window.removeEventListener("orientationchange", loadProgressBars);
81 }
82 });
83 }
84 };
85
86 loadProgressBars();
87 window.addEventListener("scroll", loadProgressBars);
88 window.addEventListener("resize", loadProgressBars);
89 window.addEventListener("orientationchange", loadProgressBars);
90 }
91};
92
93</script>
94
95<script>
96
97document.addEventListener("DOMContentLoaded", () => {
98 initProgressBars();
99});
100
101</script>
102Heading 1
Heading 2
Heading 3
Heading 4
Heading 5
Heading 6
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Block quote
Ordered list
- Item 1
- Item 2
- Item 3
Unordered list
- Item A
- Item B
- Item C
Bold text
Emphasis
Superscript
Subscript
1<head>
2
3<!-- Finsweet Attributes -->
4<script async type="module"
5src="https://cdn.jsdelivr.net/npm/@finsweet/attributes@2/attributes.js"
6fs-toc
7></script>
8
9<style>
10
11html:not(.wf-design-mode) {
12 scroll-behavior: smooth;
13}
14
15</style>
16
17</head>
18
19<body>
20
21<script>
22
23const initToc = () => {
24 const tocSources = document.querySelectorAll("[data-toc_source]");
25
26 if (tocSources.length > 0) {
27 tocSources.forEach((tocSource) => {
28 const tocTarget = document.querySelector(
29 `[data-toc_target="${tocSource.dataset.toc_source}"]`
30 );
31
32 const tocClone = tocTarget.firstElementChild.cloneNode(true);
33 let nextLevel = tocClone;
34 let depth = 1;
35
36 while (nextLevel.lastElementChild.querySelector("[data-toc='label']")) {
37 nextLevel = nextLevel.lastElementChild;
38 depth++;
39 }
40
41 tocTarget.innerHTML = "";
42
43 const headings = tocSource.querySelectorAll("h2, h3, h4, h5, h6");
44
45 headings.forEach((heading, i) => {
46 const level = parseInt(heading.tagName.replace("H", ""));
47
48 if (level <= depth) {
49 const headingSlug = `${
50 heading.textContent.toLowerCase().split(" ").join("-")
51 }-${i}`;
52
53 const offsets = document.querySelectorAll("[data-toc='offset']");
54 let anchorOffset = parseInt(tocTarget.dataset.toc_offset) || 16;
55
56 if (offsets.length > 0) {
57 offsets.forEach((offset) => {
58 anchorOffset += offset.offsetHeight;
59 });
60 }
61
62 const headingAnchor = document.createElement("div");
63
64 headingAnchor.style.position = "relative";
65
66 headingAnchor.innerHTML = `<div id="${
67 headingSlug
68 }" style="position: absolute; margin-top: -${
69 anchorOffset
70 }px;"></div>`;
71
72 tocSource.insertBefore(headingAnchor, heading);
73
74 let levelTarget = tocClone;
75
76 for (let l = 2; l < level; l++) {
77 if (levelTarget.querySelector("[data-toc='label']")) {
78 levelTarget = levelTarget.lastElementChild;
79 }
80 }
81
82 const levelTargetClone = levelTarget.cloneNode(true);
83
84 if (
85 level < depth &&
86 levelTargetClone.lastElementChild.querySelector(
87 "[data-toc='label']"
88 )
89 ) {
90 levelTargetClone.lastElementChild.remove();
91 }
92
93 const label = levelTargetClone.querySelector(
94 "[data-toc='label']"
95 );
96
97 if (label) {
98 label.innerHTML = heading.innerHTML;
99 }
100
101 const link = levelTargetClone.querySelector("a");
102
103 if (link) {
104 link.href = `#${headingSlug}`;
105 }
106
107 let parentTarget = tocTarget;
108
109 for (let l = 2; l < level - 1; l++) {
110 if (parentTarget.querySelector("[data-toc='label']")) {
111 parentTarget = parentTarget.lastElementChild;
112 }
113 }
114
115 parentTarget.appendChild(levelTargetClone);
116 }
117 });
118 });
119 }
120};
121
122</script>
123
124<script>
125
126document.addEventListener("DOMContentLoaded", () => {
127 initToc();
128});
129
130</script>
131
132</body>
133<head>
<style>
.vertical-tab_wrapper:before,
.vertical-tab_wrapper:after {
display: none;
}
</style>
</head>
<body>
<script
src="https://tools.refokus.com/automatic-tabs/bundle.v1.0.0.js"
></script>
<script>
const initAutoplayTabs = () => {
const setActiveTab = (tabLinks, index) => {
tabLinks.forEach((tabLink, i) => {
const tabPaneId = tabLink.href.split("#")[1];
const tabPane = document.getElementById(tabPaneId);
if (index === i) {
tabLink.classList.add("w--current");
tabLink.setAttribute("aria-selected", "true");
tabLink.removeAttribute("tabindex");
tabPane.classList.add("w--tab-active");
} else {
tabLink.classList.remove("w--current");
tabLink.setAttribute("aria-selected", "false");
tabLink.setAttribute("tabindex", "-1");
tabPane.classList.remove("w--tab-active");
}
});
};
const resolveIndex = (index, array) => {
return index < array.length - 1 ? index + 1 : 0;
};
const tabs = document.querySelectorAll("[data-autoplay_tab]");
tabs.forEach((tab) => {
const tabLinks = tab.querySelectorAll(".w-tab-link");
const timeInterval = parseInt(tab.dataset.autoplay_tab) || 1000;
let currentIndex = 0;
let tabInterval = setInterval(() => {
setActiveTab(tabLinks, currentIndex);
currentIndex = resolveIndex(currentIndex, tabLinks);
}, timeInterval);
tabLinks.forEach((tabLink, i) => {
tabLink.addEventListener("click", (e) => {
e.preventDefault();
clearInterval(tabInterval);
setActiveTab(tabLinks, i);
currentIndex = resolveIndex(i, tabLinks);
tabInterval = setInterval(() => {
setActiveTab(tabLinks, currentIndex);
currentIndex = resolveIndex(currentIndex, tabLinks);
}, timeInterval)
});
});
});
};
</script>
<script>
document.addEventListener("DOMContentLoaded", () => {
initAutoplayTabs();
});
</script>
</body>
<head>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/zoomist@2/zoomist.css"
/>
<style>
:root {
--zoomist-slider-padding-x: 1.25rem;
--zoomist-slider-padding-y: 1.25rem;
--zoomist-slider-bg-color: var(--base-color-misc--blue-400);
--zoomist-slider-track-color: var(--base-color-neutral-misc--neutral-200);
--zoomist-slider-track-color-hover: var(--base-color-neutral-misc--white);
--zoomist-slider-button-color: var(--base-color-neutral-misc--white);
--zoomist-zoomer-button-size: 3rem;
--zoomist-zoomer-button-color: var(--base-color-misc--blue-400);
--zoomist-zoomer-button-color-hover: var(--base-color-misc--blue-500);
--zoomist-zoomer-button-color-disabled: var(
--base-color-neutral-misc--neutral-200
);
--zoomist-zoomer-button-opacity-disabled: 0.5;
--zoomist-zoomer-icon-color: var(--base-color-neutral-misc--white);
--zoomist-zoomer-icon-color-hover: var(--base-color-neutral-misc--white);
--zoomist-zoomer-icon-color-disabled: var(--base-color-misc--blue-600);
}
@media only screen and (max-width: 767px) {
:root {
--zoomist-slider-padding-x: 1rem;
--zoomist-slider-padding-y: 0.75rem;
}
}
</style>
</head>
<body>
<script src="https://cdn.jsdelivr.net/npm/zoomist@2/zoomist.umd.js"></script>
<script>
const initZoomer = (identifier, config) => {
const zoomerContainer = document.querySelector(identifier);
if (!zoomerContainer.classList.contains("zoomer-initialized")) {
new Zoomist(identifier, config);
zoomerContainer.classList.add("zoomer-initialized");
}
};
</script>
<script>
document.addEventListener("DOMContentLoaded", () => {
new Zoomist("#zoomer-container", {
maxScale: 4,
bounds: true,
slider: true,
zoomer: true
});
initZoomer("#zoomer-container", {
maxScale: 4,
bounds: true,
slider: true,
zoomer: true
});
});
</script>
</body>
