diff --git a/components/geochart/geochart.js b/components/geochart/geochart.js index 83ad0f5..6e1b3ec 100644 --- a/components/geochart/geochart.js +++ b/components/geochart/geochart.js @@ -54,7 +54,7 @@ // Convert string to numeric. for (let i = 0; i < csvData.length; ++i) { for (let j = 0; j < csvData[i].length; ++j) { - if ($.isNumeric(csvData[i][j])) { + if (!isNaN(parseFloat(csvData[i][j])) && isFinite(csvData[i][j])) { csvData[i][j] = parseFloat(csvData[i][j]) } } diff --git a/components/header/header.js b/components/header/header.js index b8f1196..534facf 100644 --- a/components/header/header.js +++ b/components/header/header.js @@ -57,22 +57,55 @@ } }; - // Initial the jQuery Fixx plugin. - // once() prevents this listener being attached multiple times... - $(once('convivial_bootstrap_Header--fixx', '.bs-header--sticky ' + stickyElementSelector, context)) - .fixx({ - // Give the placeholder a class so we can specifically target only this one on the page. - placeholderClass: 'bs-header-placeholder', + // Simple vanilla JS sticky header + makeStickyHeader = function (selector, stickyClass = 'sticky-fixed') { + const element = document.querySelector(selector); + if (!element) return; - // Don't prepend the placeholder before the element, append it after. - placeholderPrepend: false, + // Create placeholder + const placeholder = document.createElement('div'); + placeholder.className = 'bs-header-placeholder'; + placeholder.style.display = 'none'; + element.parentNode.insertBefore(placeholder, element.nextSibling); - // Keep the terminology the same. - stateFixedClass: stickyClass, + // Get initial position + const originalTop = element.offsetTop; + let isSticky = false; - // Pixel offset from screen-top of when the element becomes sticky. - startThreshold: 0 - }); + function handleScroll() { + const shouldStick = window.scrollY > originalTop; + + if (shouldStick && !isSticky) { + // Make sticky + isSticky = true; + placeholder.style.height = element.offsetHeight + 'px'; + placeholder.style.display = 'block'; + + element.style.position = 'fixed'; + element.style.top = '0'; + element.style.left = '0'; + element.style.width = '100%'; + element.classList.add(stickyClass); + + } else if (!shouldStick && isSticky) { + // Remove sticky + isSticky = false; + placeholder.style.display = 'none'; + + element.style.position = ''; + element.style.top = ''; + element.style.left = ''; + element.style.width = ''; + element.classList.remove(stickyClass); + } + } + + window.addEventListener('scroll', handleScroll); + } + + $(once('convivial_bootstrap_Header--fixx', '.bs-header--sticky ' + stickyElementSelector, context)).each(function() { + makeStickyHeader('.bs-header--sticky ' + stickyElementSelector, stickyClass); + }); $(once('convivial_bootstrap_Header--window-change', context)) .on('resize scroll', function () { @@ -111,5 +144,6 @@ } }); } + }; })(jQuery, Drupal, once); diff --git a/components/header/jquery.fixx.js b/components/header/jquery.fixx.js deleted file mode 100644 index 493d0bd..0000000 --- a/components/header/jquery.fixx.js +++ /dev/null @@ -1,315 +0,0 @@ -/** - * @file - * Fixx | v1.0 | 05/10/2018 - https://github.com/fiasst - Marc Hudson. - * - * Instructions: - * 1) For the element you want to be fixed, add: - * "#example-element.fixed { position: fixed; }" to your CSS. - * 2) Update #example-element with the element's ID/classname. - * 3) Make sure the .fixed selector matches the "stateFixedClass" - * value shown in this file. - * 4) Add $('#example-element').fixx(); to your custom Javascript - * with any override options you need. -*/ - -(function ($) { - /* - * Fix an element to the top of the screen on page scroll. - * Included: responsive support on window scroll/resize, - * placeholder element to prevent other elements resizing/repositioning - * when element gets 'position: fixed;' style, offset and threshold and - * callbacks. - */ - $.fn.fixx = function (options) { - if (this.length < 1) { - return false; - } - - var element = this, - options = options || {}, - - defaults = { - // The pixel amount to offset the element from the top of the screen. - offset: 0, - - /* - * Provide a function() to update the offset dynamically, - * called on window scroll/resize. - */ - offsetCallback: false, - - /* - * Set the window width in pixels that define each Media Query - * breakpoint in your responsive CSS. - */ - responsiveBreakpoints: { - // Mobile to Tablet-portrait screens. - sm: 768, - // Tablet-landscape and small desktop screens. - md: 992, - // Larger desktop screens and above. - lg: 1200 - }, - - /* - * list the breakpoints that this element should be Fixed for: - * "xs" = Extra-small (mobile only) screens, - * "sm" = Small (mobile and some tablets) screens, - * "md" = Medium (tablet portrait) screens, - * "lg" = Large (tablet landscape/desktop) screens. - */ - fixedBreakpoints: ['xs', 'sm', 'md', 'lg'], - - /* - * Start fixing the element when the screen reaches the top/bottom - * of this element. - */ - startElement: $('body'), - - /* - * Define if the element should start being fixed when the top - * of the screen reaches the "top" or "bottom" of the startElement. - */ - startAt: 'top', - - /* - * Set a pixel threshold from the top of the screen for when - * the element should start being fixed. - */ - startThreshold: 0, - - /* - * Provide a function() to update the threshold dynamically, - * called on window scroll/resize. For example, change the - * threshold from 50 to 0 if the user is scrolling up. - */ - startThresholdCallback: false, - - /* - * If defined, the element will stop being fixed when the top - * of the screen reaches this element. - */ - endElement: null, - - /* - * Define if the element should stop being fixed when the top - * of the screen reaches the "top" or "bottom" of the endElement. - */ - endAt: 'bottom', - - /* - * Same as the startThreshold but for when the element should stop - * being fixed. - */ - endThreshold: 0, - - /* - * Whether to let a placeholder element elmulate the fixed - * element's position to stop other page content from repositioning - * when the element's position changes from static to fixed. - * The placeholder also adjusts the fixed element's width on window - * resize for true responsive support. - */ - placeholder: true, - - // Whether to add the placeholder before the fixed element in the - // markup or after it. There's not many benefits to this but it can - // be very useful in certain situations. - placeholderPrepend: true, - - // Class to specifically target only this placeholder on the page. - placeholderClass: '', - // Class applied to make this element fixed in place. - stateFixedClass: 'fixed', - // Class applied when the element stops being fixed in place. - stateStaticClass: 'static', - /* - * Class applied when the element reaches the endElement and should - * freeze in current position. - */ - stateFreezeClass: 'freeze', - - /* - * There are ocassions where you might not want to assign a 'left' - * CSS property to the fixed element. This gives you the option - * to prevent it being set. - */ - leftPositioning: true - }, - - optionsObj = $.extend(true, defaults, options), - placeholder = $('
').css({ - height: 1, - marginTop: -1 - }), - startElement = $(optionsObj.startElement), - endElement = $(optionsObj.endElement); - - // Add placeholder for position and size support. - if (optionsObj.placeholderPrepend) { - // Add placeholder before element. - element.before(placeholder); - } - else { - // Add placeholder after element. - element.after(placeholder); - } - - /* - * Return a String depending on the window width and the defined - * responsiveBreakpoints. - */ - var viewportWidth = function () { - var width = $(window).width(); - - if (typeof(optionsObj.responsiveBreakpoints) !== 'undefined') { - if (width < optionsObj.responsiveBreakpoints.sm) { - return 'xs'; - } - if (width >= optionsObj.responsiveBreakpoints.lg) { - return 'lg'; - } - if (width >= optionsObj.responsiveBreakpoints.md) { - return 'md'; - } - if (width >= optionsObj.responsiveBreakpoints.sm) { - return 'sm'; - } - } - }; - - var fix = function (element, placeholder) { - var pos = placeholder.offset(), - topPos, - scrollTop = $(window).scrollTop(), - - // Get the current viewport width of the screen as a 2 digit String. - vw = viewportWidth(), - // Check if the element should be fixed for the current breakpoint. - isAcceptedBreakpoint = ($.inArray(vw, optionsObj.fixedBreakpoints) > -1), - - elementHeight = element.outerHeight(true), - - startPointOffset = 0, - endPointOffset = 9999999999; - - if ($.isFunction(optionsObj.offsetCallback)) { - /* - * Get a new offset value on window scroll/resize, - * in case its changed since init. - */ - optionsObj.offset = optionsObj.offsetCallback(); - } - if ($.isFunction(optionsObj.startThresholdCallback)) { - /* - * Get a new threshold value on window scroll/resize, - * in case its changed since init. - */ - optionsObj.startThreshold = optionsObj.startThresholdCallback(); - } - - var startPoint = function () { - // If startElement exists in DOM. - if (startElement.length > 0) { - startPointOffset = startElement.offset().top; - - // If the start point is the bottom of the startElement and not the top: - if (optionsObj.startAt === 'bottom') { - // Add the element height. - startPointOffset += startElement.outerHeight() - optionsObj.offset; - } - } - else { - // Default to when user scrolls past the element. - startPointOffset = pos.top - optionsObj.offset; - } - return startPointOffset + optionsObj.startThreshold; - }, - endPoint = function () { - // If endElement exists in DOM. - if (endElement.length > 0) { - endPointOffset = endElement.offset().top; - - // If the start point is the bottom of the endElement and not the top: - if (optionsObj.endAt === 'bottom') { - // Add the element height. - endPointOffset += endElement.outerHeight(); - } - endPointOffset -= (element.outerHeight(true) + (optionsObj.offset * 2)); - } - return endPointOffset + optionsObj.endThreshold; - }; - - // Add or remove fixed/static class. - if (isAcceptedBreakpoint && scrollTop > startPoint()) { - element.removeClass(optionsObj.stateStaticClass).addClass(optionsObj.stateFixedClass); - - /* - * If user has scrolled past where we want the element to - * freeze in position: - */ - if (scrollTop >= endPoint()) { - // Add classname to freeze element. - element.addClass(optionsObj.stateFreezeClass); - } - else { - /* - * Back within fixed startPoint => endPoint range so un-freeze - * element be removing freeze classname. - */ - element.removeClass(optionsObj.stateFreezeClass); - } - } - else { - // Scroll up past startPoint so remove fixed/freeze classes. - element.addClass(optionsObj.stateStaticClass).removeClass(optionsObj.stateFixedClass + ' ' + optionsObj.stateFreezeClass); - } - - if (element.hasClass(optionsObj.stateFixedClass)) { - // Adjust placeholder height. - if (optionsObj.placeholder) { - placeholder.css('height', elementHeight); - } - - // Set the top position of the element. - if (element.hasClass(optionsObj.stateFreezeClass)) { - // Sit frozen in place as user has scrolled past endPoint. - topPos = endPoint() - (scrollTop - optionsObj.offset); - } - else { - // Sit at top of screen + offset. - topPos = optionsObj.offset; - } - - // Adjust element width and position. - element - .css({ - width: placeholder.width(), - top: topPos, - }); - - if (options.leftPositioning) { - element.css({ - left: pos.left - }) - } - } - else { - element.removeAttr('style'); - - // Remove placeholder height. - if (optionsObj.placeholder) { - placeholder.removeAttr('style'); - } - } - }; - - $(window) - .on('resize scroll', function () { - fix(element, placeholder); - }); - - // Init. - fix(element, placeholder); - }; -})(jQuery); diff --git a/components/slick-carousel/slick-carousel.css b/components/slick-carousel/slick-carousel.css index e26e3e0..2c71df6 100644 --- a/components/slick-carousel/slick-carousel.css +++ b/components/slick-carousel/slick-carousel.css @@ -1 +1 @@ -.slick-slider{position:relative;display:block;box-sizing:border-box;-webkit-touch-callout:none;-webkit-user-select:none;user-select:none;touch-action:pan-y;-webkit-tap-highlight-color:rgba(0,0,0,0)}.slick-list{position:relative;overflow:hidden;display:block;margin:0;padding:0}.slick-list:focus{outline:none}.slick-list.dragging{cursor:pointer;cursor:hand}.slick-slider .slick-track,.slick-slider .slick-list{transform:translate3d(0, 0, 0)}.slick-track{position:relative;left:0;top:0;display:block;margin-left:auto;margin-right:auto}.slick-track:before,.slick-track:after{content:"";display:table}.slick-track:after{clear:both}.slick-loading .slick-track{visibility:hidden}.slick-slide{float:left;height:100%;min-height:1px;display:none}[dir=rtl] .slick-slide{float:right}.slick-slide img{display:block}.slick-slide.slick-loading img{display:none}.slick-slide.dragging img{pointer-events:none}.slick-initialized .slick-slide{display:block}.slick-loading .slick-slide{visibility:hidden}.slick-vertical .slick-slide{display:block;height:auto;border:1px solid rgba(0,0,0,0)}.slick-arrow.slick-hidden{display:none}.slick-loading .slick-list{background:#fff url("../../libraries/slick-carousel/slick/ajax-loader.gif") center center no-repeat}.slick-prev,.slick-next{position:absolute;display:block;height:20px;width:20px;line-height:0px;font-size:0px;cursor:pointer;background:rgba(0,0,0,0);color:rgba(0,0,0,0);top:50%;transform:translate(0, -50%);padding:0;border:none;outline:none}.slick-prev:hover,.slick-prev:focus,.slick-next:hover,.slick-next:focus{outline:none;background:rgba(0,0,0,0);color:rgba(0,0,0,0)}.slick-prev:hover:before,.slick-prev:focus:before,.slick-next:hover:before,.slick-next:focus:before{opacity:1}.slick-prev.slick-disabled:before,.slick-next.slick-disabled:before{opacity:.25}.slick-prev:before,.slick-next:before{font-family:inherit;font-size:20px;line-height:1;color:#fff;opacity:.75;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.slick-prev{left:-25px}[dir=rtl] .slick-prev{left:auto;right:-25px}.slick-prev:before{content:"←"}[dir=rtl] .slick-prev:before{content:"→"}.slick-next{right:-25px}[dir=rtl] .slick-next{left:-25px;right:auto}.slick-next:before{content:"→"}[dir=rtl] .slick-next:before{content:"←"}.slick-dotted.slick-slider{margin-bottom:30px}.slick-dots{position:absolute;bottom:-25px;list-style:none;display:block;text-align:center;padding:0;margin:0;width:100%}.slick-dots li{position:relative;display:inline-block;height:20px;width:20px;margin:0 5px;padding:0;cursor:pointer}.slick-dots li button{border:0;background:rgba(0,0,0,0);display:block;height:20px;width:20px;outline:none;line-height:0px;font-size:0px;color:rgba(0,0,0,0);padding:5px;cursor:pointer}.slick-dots li button:hover,.slick-dots li button:focus{outline:none}.slick-dots li button:hover:before,.slick-dots li button:focus:before{opacity:1}.slick-dots li button:before{position:absolute;top:0;left:0;content:"•";width:20px;height:20px;font-family:inherit;font-size:6px;line-height:20px;text-align:center;color:#000;opacity:.25;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.slick-dots li.slick-active button:before{color:#000;opacity:.75} \ No newline at end of file +.slick-slider{position:relative;display:block;box-sizing:border-box;-webkit-touch-callout:none;-webkit-user-select:none;user-select:none;touch-action:pan-y;-webkit-tap-highlight-color:rgba(0,0,0,0)}.slick-list{position:relative;overflow:hidden;display:block;margin:0;padding:0}.slick-list:focus{outline:none}.slick-list.dragging{cursor:pointer;cursor:hand}.slick-slider .slick-track,.slick-slider .slick-list{transform:translate3d(0, 0, 0)}.slick-track{position:relative;left:0;top:0;display:block;margin-left:auto;margin-right:auto}.slick-track:before,.slick-track:after{content:"";display:table}.slick-track:after{clear:both}.slick-loading .slick-track{visibility:hidden}.slick-slide{float:left;height:100%;min-height:1px;display:none}[dir=rtl] .slick-slide{float:right}.slick-slide img{display:block}.slick-slide.slick-loading img{display:none}.slick-slide.dragging img{pointer-events:none}.slick-initialized .slick-slide{display:block}.slick-loading .slick-slide{visibility:hidden}.slick-vertical .slick-slide{display:block;height:auto;border:1px solid rgba(0,0,0,0)}.slick-arrow.slick-hidden{display:none}.slick-loading .slick-list{background:#fff url(../../libraries/slick-carousel/slick/ajax-loader.gif) center center no-repeat}.slick-prev,.slick-next{position:absolute;display:block;height:20px;width:20px;line-height:0px;font-size:0px;cursor:pointer;background:rgba(0,0,0,0);color:rgba(0,0,0,0);top:50%;transform:translate(0, -50%);padding:0;border:none;outline:none}.slick-prev:hover,.slick-prev:focus,.slick-next:hover,.slick-next:focus{outline:none;background:rgba(0,0,0,0);color:rgba(0,0,0,0)}.slick-prev:hover:before,.slick-prev:focus:before,.slick-next:hover:before,.slick-next:focus:before{opacity:1}.slick-prev.slick-disabled:before,.slick-next.slick-disabled:before{opacity:.25}.slick-prev:before,.slick-next:before{font-family:inherit;font-size:20px;line-height:1;color:#fff;opacity:.75;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.slick-prev{left:-25px}[dir=rtl] .slick-prev{left:auto;right:-25px}.slick-prev:before{content:"←"}[dir=rtl] .slick-prev:before{content:"→"}.slick-next{right:-25px}[dir=rtl] .slick-next{left:-25px;right:auto}.slick-next:before{content:"→"}[dir=rtl] .slick-next:before{content:"←"}.slick-dotted.slick-slider{margin-bottom:30px}.slick-dots{position:absolute;bottom:-25px;list-style:none;display:block;text-align:center;padding:0;margin:0;width:100%}.slick-dots li{position:relative;display:inline-block;height:20px;width:20px;margin:0 5px;padding:0;cursor:pointer}.slick-dots li button{border:0;background:rgba(0,0,0,0);display:block;height:20px;width:20px;outline:none;line-height:0px;font-size:0px;color:rgba(0,0,0,0);padding:5px;cursor:pointer}.slick-dots li button:hover,.slick-dots li button:focus{outline:none}.slick-dots li button:hover:before,.slick-dots li button:focus:before{opacity:1}.slick-dots li button:before{position:absolute;top:0;left:0;content:"•";width:20px;height:20px;font-family:inherit;font-size:6px;line-height:20px;text-align:center;color:#000;opacity:.25;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.slick-dots li.slick-active button:before{color:#000;opacity:.75} \ No newline at end of file diff --git a/components/slider/slider.js b/components/slider/slider.js index 8d4298f..061f6dc 100644 --- a/components/slider/slider.js +++ b/components/slider/slider.js @@ -6,6 +6,8 @@ adaptiveHeight: true, centerPadding: '0px', slidesToShow: 1, + // Temporary fix for the div wrapper + rows: 0 } let halvesSettings = { diff --git a/convivial_bootstrap.libraries.yml b/convivial_bootstrap.libraries.yml index 5225caa..a3002f1 100644 --- a/convivial_bootstrap.libraries.yml +++ b/convivial_bootstrap.libraries.yml @@ -117,7 +117,6 @@ glightbox: header: js: - components/header/jquery.fixx.js: { } components/header/header.js: { } dependencies: - core/jquery @@ -198,7 +197,7 @@ sticky_header_height: slick_carousel: js: - libraries/slick-carousel/slick/slick.min.js: { minified: true } + libraries/slick-carousel/slick/slick.js: { } css: theme: components/slick-carousel/slick-carousel.css: { minified: true } diff --git a/libraries/slick-carousel/slick/ajax-loader.gif b/libraries/slick-carousel/slick/ajax-loader.gif index e0e6e97..d528c21 100644 Binary files a/libraries/slick-carousel/slick/ajax-loader.gif and b/libraries/slick-carousel/slick/ajax-loader.gif differ diff --git a/libraries/slick-carousel/slick/fonts/slick.woff2 b/libraries/slick-carousel/slick/fonts/slick.woff2 new file mode 100644 index 0000000..2b61e5a Binary files /dev/null and b/libraries/slick-carousel/slick/fonts/slick.woff2 differ diff --git a/libraries/slick-carousel/slick/slick-theme.css b/libraries/slick-carousel/slick/slick-theme.css index 1232fca..a6b32f1 100644 --- a/libraries/slick-carousel/slick/slick-theme.css +++ b/libraries/slick-carousel/slick/slick-theme.css @@ -1,4 +1,4 @@ -@charset 'UTF-8'; +@charset "UTF-8"; /* Slider */ .slick-loading .slick-list { @@ -11,9 +11,11 @@ font-family: 'slick'; font-weight: normal; font-style: normal; + font-display: swap; src: url('./fonts/slick.eot'); - src: url('./fonts/slick.eot?#iefix') format('embedded-opentype'), url('./fonts/slick.woff') format('woff'), url('./fonts/slick.ttf') format('truetype'), url('./fonts/slick.svg#slick') format('svg'); + src: url('./fonts/slick.eot?#iefix') format('embedded-opentype'), url('./fonts/slick.woff2') format('woff2'), url('./fonts/slick.woff') format('woff'), url('./fonts/slick.ttf') format('truetype'), url('./fonts/slick.svg#slick') format('svg'); + font-display: swap; } /* Arrows */ .slick-prev, diff --git a/libraries/slick-carousel/slick/slick-theme.less b/libraries/slick-carousel/slick/slick-theme.less index e06fc18..33019f3 100644 --- a/libraries/slick-carousel/slick/slick-theme.less +++ b/libraries/slick-carousel/slick/slick-theme.less @@ -61,7 +61,7 @@ opacity: @slick-opacity-default; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - + & when ( @slick-font-family = 'slick' ) { /* Icons */ @font-face { @@ -69,7 +69,8 @@ font-weight: normal; font-style: normal; src: url('@{slick-font-path}slick.eot'); - src: url('@{slick-font-path}slick.eot?#iefix') format('embedded-opentype'), url('@{slick-font-path}slick.woff') format('woff'), url('@{slick-font-path}slick.ttf') format('truetype'), url('@{slick-font-path}slick.svg#slick') format('svg'); + src: url('@{slick-font-path}slick.eot?#iefix') format('embedded-opentype'), url('@{slick-font-path}slick.woff2') format('woff2'), url('@{slick-font-path}slick.woff') format('woff'), url('@{slick-font-path}slick.ttf') format('truetype'), url('@{slick-font-path}slick.svg#slick') format('svg'); + font-display: swap; } } } diff --git a/libraries/slick-carousel/slick/slick-theme.scss b/libraries/slick-carousel/slick/slick-theme.scss index 7fe63e1..368ba23 100644 --- a/libraries/slick-carousel/slick/slick-theme.scss +++ b/libraries/slick-carousel/slick/slick-theme.scss @@ -26,7 +26,7 @@ $slick-opacity-not-active: 0.25 !default; @return image-url($url); } @else { - @return url($slick-loader-path + $url); + @return url(#{$slick-loader-path}#{$url}); } } @@ -35,7 +35,7 @@ $slick-opacity-not-active: 0.25 !default; @return font-url($url); } @else { - @return url($slick-font-path + $url); + @return url(#{$slick-font-path}#{$url}); } } @@ -52,9 +52,10 @@ $slick-opacity-not-active: 0.25 !default; @font-face { font-family: "slick"; src: slick-font-url("slick.eot"); - src: slick-font-url("slick.eot?#iefix") format("embedded-opentype"), slick-font-url("slick.woff") format("woff"), slick-font-url("slick.ttf") format("truetype"), slick-font-url("slick.svg#slick") format("svg"); + src: slick-font-url("slick.eot?#iefix") format("embedded-opentype"), slick-font-url("slick.woff2") format("woff2"), slick-font-url("slick.woff") format("woff"), slick-font-url("slick.ttf") format("truetype"), slick-font-url("slick.svg#slick") format("svg"); font-weight: normal; font-style: normal; + font-display: swap; } } diff --git a/libraries/slick-carousel/slick/slick.js b/libraries/slick-carousel/slick/slick.js index 7aca613..d904c4c 100644 --- a/libraries/slick-carousel/slick/slick.js +++ b/libraries/slick-carousel/slick/slick.js @@ -52,7 +52,7 @@ centerPadding: '50px', cssEase: 'ease', customPaging: function(slider, i) { - return $('').text(i + 1); }, dots: false, dotsClass: 'slick-dots', @@ -488,10 +488,10 @@ _.$slider.addClass('slick-dotted'); - dot = $('