<# if ( 'undefined' !== typeof FusionApp ) { #>
<# } else { #>
<# } #>
/*global fusionredux_change, wp, fusionredux, libFilter */ /** * Media Uploader * Dependencies : jquery, wp media uploader * Feature added by : Smartik - http://smartik.ws/ * Date : 05.28.2013 */ (function($){ "use strict"; fusionredux.field_objects = fusionredux.field_objects || {}; fusionredux.field_objects.media = fusionredux.field_objects.media || {}; var isFiltered; $( document ).ready( function() { } ); fusionredux.field_objects.media.init = function( selector ) { if ( !selector ) { selector = $( document ).find( ".fusionredux-group-tab:visible" ).find( '.fusionredux-container-media:visible' ); } $( selector ).each( function() { var el = $( this ); var parent = el; if ( !el.hasClass( 'fusionredux-field-container' ) ) { parent = el.parents( '.fusionredux-field-container:first' ); } if ( parent.is( ":hidden" ) ) { // Skip hidden fields return; } if ( parent.hasClass( 'fusionredux-field-init' ) ) { parent.removeClass( 'fusionredux-field-init' ); } else { return; } isFiltered = false; // Remove the image button el.find( '.remove-image, .remove-file' ).unbind( 'click' ).on( 'click', function() { fusionredux.field_objects.media.removeFile( $( this ).parents( 'fieldset.fusionredux-field:first' ) ); } ); // Upload media button el.find( '.media_upload_button' ).unbind().on( 'click', function( event ) { fusionredux.field_objects.media.addFile( event, $( this ).parents( 'fieldset.fusionredux-field:first' ) ); } ); } ); }; // Add a file via the wp.media function fusionredux.field_objects.media.addFile = function( event, selector ) { event.preventDefault(); var frame; var jQueryel = $( this ); var libFilter; // If the media frame already exists, reopen it. if ( frame ) { frame.open(); return; } // Get library filter data var filter = $( selector ).find('.library-filter').data('lib-filter'); // Must exist to do decoding if (filter !== undefined) { if (filter !== ''){ libFilter = []; isFiltered = true; filter = decodeURIComponent(filter); filter = JSON.parse(filter); $.each(filter, function(index, value) { libFilter.push(value); }); } } // Create the media frame. frame = wp.media( { multiple: false, library: { type: libFilter //Only allow images }, // Set the title of the modal. title: jQueryel.data( 'choose' ), // Customize the submit button. button: { // Set the text of the button. text: jQueryel.data( 'update' ) // Tell the button not to close the modal, since we're // going to refresh the page when the image is selected. } } ); // When an image is selected, run a callback. frame.on( 'select', function() { // Grab the selected attachment. var attachment = frame.state().get( 'selection' ).first(); frame.close(); var data = $( selector ).find('.data').data(); if ( typeof fusionredux.field_objects.media === 'undefined' || typeof fusionredux.field_objects.media === undefined ) { fusionredux.field_objects.media = {}; } if ( data === undefined || data.mode === 'undefined' ) { data = {}; data.mode = "image"; } if (isFiltered === true) { data.mode = 0; } if (data.mode === 0) { } else { if ( data.mode !== false) { if (attachment.attributes.type !== data.mode) { if (attachment.attributes.subtype !== data.mode ) { return; } } } } selector.find( '.upload' ).val( attachment.attributes.url ); selector.find( '.upload-id' ).val( attachment.attributes.id ); selector.find( '.upload-height' ).val( attachment.attributes.height ); selector.find( '.upload-width' ).val( attachment.attributes.width ); fusionredux_change( $( selector ).find( '.upload-id' ) ); var thumbSrc = attachment.attributes.url; if ( typeof attachment.attributes.sizes !== 'undefined' && typeof attachment.attributes.sizes.thumbnail !== 'undefined' ) { thumbSrc = attachment.attributes.sizes.thumbnail.url; } else if ( typeof attachment.attributes.sizes !== 'undefined' ) { var height = attachment.attributes.height; for ( var key in attachment.attributes.sizes ) { var object = attachment.attributes.sizes[key]; if ( object.height < height ) { height = object.height; thumbSrc = object.url; } } } else { thumbSrc = attachment.attributes.icon; } selector.find( '.upload-thumbnail' ).val( thumbSrc ); if ( !selector.find( '.upload' ).hasClass( 'noPreview' ) ) { selector.find( '.screenshot' ).empty().hide().append( '' ).slideDown( 'fast' ); } //selector.find('.media_upload_button').unbind(); selector.find( '.remove-image' ).removeClass( 'hide' );//show "Remove" button selector.find( '.fusionredux-background-properties' ).slideDown(); } ); // Finally, open the modal. frame.open(); }; // Function to remove the image on click. Still requires a save fusionredux.field_objects.media.removeFile = function( selector ) { // This shouldn't have been run... if ( !selector.find( '.remove-image' ).addClass( 'hide' ) ) { return; } selector.find( '.remove-image' ).addClass( 'hide' );//hide "Remove" button selector.find( '.upload' ).val( '' ); selector.find( '.upload-id' ).val( '' ); selector.find( '.upload-height' ).val( '' ); selector.find( '.upload-width' ).val( '' ); selector.find( '.upload-thumbnail' ).val( '' ); fusionredux_change( $( selector ).find( '.upload-id' ) ); selector.find( '.fusionredux-background-properties' ).hide(); var screenshot = selector.find( '.screenshot' ); // Hide the screenshot screenshot.slideUp(); selector.find( '.remove-file' ).unbind(); // We don't display the upload button if .upload-notice is present // This means the user doesn't have the WordPress 3.5 Media Library Support if ( selector.find( '.section-upload .upload-notice' ).length > 0 ) { selector.find( '.media_upload_button' ).remove(); } }; })( jQuery ); /*! elementor-pro - v3.18.0 - 06-12-2023 */ "use strict"; (self["webpackChunkelementor_pro"] = self["webpackChunkelementor_pro"] || []).push([["carousel"],{ /***/ "../modules/carousel/assets/js/frontend/handlers/base.js": /*!***************************************************************!*\ !*** ../modules/carousel/assets/js/frontend/handlers/base.js ***! \***************************************************************/ /***/ ((__unused_webpack_module, exports) => { Object.defineProperty(exports, "__esModule", ({ value: true })); exports["default"] = void 0; class CarouselBase extends elementorModules.frontend.handlers.SwiperBase { getDefaultSettings() { return { selectors: { swiperContainer: '.elementor-main-swiper', swiperSlide: '.swiper-slide' }, slidesPerView: { widescreen: 3, desktop: 3, laptop: 3, tablet_extra: 3, tablet: 2, mobile_extra: 2, mobile: 1 } }; } getDefaultElements() { const selectors = this.getSettings('selectors'), elements = { $swiperContainer: this.$element.find(selectors.swiperContainer) }; elements.$slides = elements.$swiperContainer.find(selectors.swiperSlide); return elements; } getEffect() { return this.getElementSettings('effect'); } getDeviceSlidesPerView(device) { const slidesPerViewKey = 'slides_per_view' + ('desktop' === device ? '' : '_' + device); return Math.min(this.getSlidesCount(), +this.getElementSettings(slidesPerViewKey) || this.getSettings('slidesPerView')[device]); } getSlidesPerView(device) { if ('slide' === this.getEffect()) { return this.getDeviceSlidesPerView(device); } return 1; } getDeviceSlidesToScroll(device) { const slidesToScrollKey = 'slides_to_scroll' + ('desktop' === device ? '' : '_' + device); return Math.min(this.getSlidesCount(), +this.getElementSettings(slidesToScrollKey) || 1); } getSlidesToScroll(device) { if ('slide' === this.getEffect()) { return this.getDeviceSlidesToScroll(device); } return 1; } getSpaceBetween(device) { let propertyName = 'space_between'; if (device && 'desktop' !== device) { propertyName += '_' + device; } return this.getElementSettings(propertyName).size || 0; } getSwiperOptions() { const elementSettings = this.getElementSettings(); const swiperOptions = { grabCursor: true, initialSlide: this.getInitialSlide(), slidesPerView: this.getSlidesPerView('desktop'), slidesPerGroup: this.getSlidesToScroll('desktop'), spaceBetween: this.getSpaceBetween(), loop: 'yes' === elementSettings.loop, speed: elementSettings.speed, effect: this.getEffect(), preventClicksPropagation: false, slideToClickedSlide: true, handleElementorBreakpoints: true }; if ('yes' === elementSettings.lazyload) { swiperOptions.lazy = { loadPrevNext: true, loadPrevNextAmount: 1 }; } if (elementSettings.show_arrows) { swiperOptions.navigation = { prevEl: '.elementor-swiper-button-prev', nextEl: '.elementor-swiper-button-next' }; } if (elementSettings.pagination) { swiperOptions.pagination = { el: '.swiper-pagination', type: elementSettings.pagination, clickable: true }; } if ('cube' !== this.getEffect()) { const breakpointsSettings = {}, breakpoints = elementorFrontend.config.responsive.activeBreakpoints; Object.keys(breakpoints).forEach(breakpointName => { breakpointsSettings[breakpoints[breakpointName].value] = { slidesPerView: this.getSlidesPerView(breakpointName), slidesPerGroup: this.getSlidesToScroll(breakpointName), spaceBetween: this.getSpaceBetween(breakpointName) }; }); swiperOptions.breakpoints = breakpointsSettings; } if (!this.isEdit && elementSettings.autoplay) { swiperOptions.autoplay = { delay: elementSettings.autoplay_speed, disableOnInteraction: !!elementSettings.pause_on_interaction }; } return swiperOptions; } getDeviceBreakpointValue(device) { if (!this.breakpointsDictionary) { const breakpoints = elementorFrontend.config.responsive.activeBreakpoints; this.breakpointsDictionary = {}; Object.keys(breakpoints).forEach(breakpointName => { this.breakpointsDictionary[breakpointName] = breakpoints[breakpointName].value; }); } return this.breakpointsDictionary[device]; } updateSpaceBetween(propertyName) { const deviceMatch = propertyName.match('space_between_(.*)'), device = deviceMatch ? deviceMatch[1] : 'desktop', newSpaceBetween = this.getSpaceBetween(device); if ('desktop' !== device) { this.swiper.params.breakpoints[this.getDeviceBreakpointValue(device)].spaceBetween = newSpaceBetween; } else { this.swiper.params.spaceBetween = newSpaceBetween; } this.swiper.params.spaceBetween = newSpaceBetween; this.swiper.update(); } async onInit() { elementorModules.frontend.handlers.Base.prototype.onInit.apply(this, arguments); if (1 >= this.getSlidesCount()) { return; } const Swiper = elementorFrontend.utils.swiper; this.swiper = await new Swiper(this.elements.$swiperContainer, this.getSwiperOptions()); const elementSettings = this.getElementSettings(); if ('yes' === elementSettings.pause_on_hover) { this.togglePauseOnHover(true); } // Expose the swiper instance in the frontend this.elements.$swiperContainer.data('swiper', this.swiper); } getChangeableProperties() { return { autoplay: 'autoplay', pause_on_hover: 'pauseOnHover', pause_on_interaction: 'disableOnInteraction', autoplay_speed: 'delay', speed: 'speed', width: 'width' }; } updateSwiperOption(propertyName) { if (0 === propertyName.indexOf('width')) { this.swiper.update(); return; } const elementSettings = this.getElementSettings(), newSettingValue = elementSettings[propertyName], changeableProperties = this.getChangeableProperties(); let propertyToUpdate = changeableProperties[propertyName], valueToUpdate = newSettingValue; // Handle special cases where the value to update is not the value that the Swiper library accepts switch (propertyName) { case 'autoplay': if (newSettingValue) { valueToUpdate = { delay: elementSettings.autoplay_speed, disableOnInteraction: 'yes' === elementSettings.pause_on_interaction }; } else { valueToUpdate = false; } break; case 'autoplay_speed': propertyToUpdate = 'autoplay'; valueToUpdate = { delay: newSettingValue, disableOnInteraction: 'yes' === elementSettings.pause_on_interaction }; break; case 'pause_on_hover': this.togglePauseOnHover('yes' === newSettingValue); break; case 'pause_on_interaction': valueToUpdate = 'yes' === newSettingValue; break; } // 'pause_on_hover' is implemented by the handler with event listeners, not the Swiper library if ('pause_on_hover' !== propertyName) { this.swiper.params[propertyToUpdate] = valueToUpdate; } this.swiper.update(); } onElementChange(propertyName) { if (1 >= this.getSlidesCount()) { return; } if (0 === propertyName.indexOf('width')) { this.swiper.update(); // If there is another thumbs slider, like in the Media Carousel widget. if (this.thumbsSwiper) { this.thumbsSwiper.update(); } return; } // This is for handling the responsive control 'space_between'. // Responsive controls require a separate way of handling, and some currently don't work // (Swiper bug, currently exists in v5.3.6) TODO: update Swiper when bug is fixed and handle responsive controls if (0 === propertyName.indexOf('space_between')) { this.updateSpaceBetween(propertyName); return; } const changeableProperties = this.getChangeableProperties(); if (Object.prototype.hasOwnProperty.call(changeableProperties, propertyName)) { this.updateSwiperOption(propertyName); } } onEditSettingsChange(propertyName) { if (1 >= this.getSlidesCount()) { return; } if ('activeItemIndex' === propertyName) { this.swiper.slideToLoop(this.getEditSettings('activeItemIndex') - 1); } } } exports["default"] = CarouselBase; /***/ }), /***/ "../modules/carousel/assets/js/frontend/handlers/testimonial-carousel.js": /*!*******************************************************************************!*\ !*** ../modules/carousel/assets/js/frontend/handlers/testimonial-carousel.js ***! \*******************************************************************************/ /***/ ((__unused_webpack_module, exports, __webpack_require__) => { var _interopRequireDefault = __webpack_require__(/*! @babel/runtime/helpers/interopRequireDefault */ "../node_modules/@babel/runtime/helpers/interopRequireDefault.js"); Object.defineProperty(exports, "__esModule", ({ value: true })); exports["default"] = void 0; var _base = _interopRequireDefault(__webpack_require__(/*! ./base */ "../modules/carousel/assets/js/frontend/handlers/base.js")); class TestimonialCarousel extends _base.default { getDefaultSettings() { const defaultSettings = super.getDefaultSettings(); defaultSettings.slidesPerView = { desktop: 1 }; Object.keys(elementorFrontend.config.responsive.activeBreakpoints).forEach(breakpointName => { defaultSettings.slidesPerView[breakpointName] = 1; }); if (defaultSettings.loop) { defaultSettings.loopedSlides = this.getSlidesCount(); } return defaultSettings; } getEffect() { return 'slide'; } } exports["default"] = TestimonialCarousel; /***/ }) }]); //# sourceMappingURL=carousel.998a291abf70435fd698.bundle.js.map Why 'Cheap Quotes' Could End Up Costing More

Why ‘Cheap Quotes’ Could End Up Costing You More in Your Home Renovation

When that surprisingly low quote lands in your inbox, the initial reaction is often relief. In a world where renovation costs seem to constantly climb, finding a builder who promises to deliver your dream kitchen for significantly less than others feels like striking gold. Unfortunately, what glitters isn’t always gold—especially in home renovations.

The Hidden Mathematics of Low Bids

Experienced builders price projects based on material costs, labor hours, overhead expenses, and a reasonable profit margin. When a quote comes in substantially below others, something has to give. The mathematics simply don’t allow for quality work at drastically reduced prices.
What typically happens is the builder has either underestimated the scope (intentionally or not), plans to use inferior materials, or intends to recover costs through “extras” once the project is underway. None of these scenarios ends well for homeowners.

The Quality Compromise

Materials represent a substantial portion of renovation costs. When builders cut corners to meet unrealistic budgets, they often substitute specified products with cheaper alternatives. The difference might not be immediately visible, but will become apparent in premature wear, performance issues, or early failure.
Consider cabinetry as an example. High-quality cabinets use solid wood, dovetail joints, and quality hardware that will last decades. Budget alternatives might look similar initially but use particleboard that warps when exposed to moisture and hardware that fails after minimal use. The replacement costs quickly erase any initial savings.

The Schedule Slide

Builders who bid too low often find themselves financially stretched, forcing them to juggle multiple projects simultaneously. Your renovation becomes one of many competing priorities, leading to a “start-and-stop” approach where progress happens in sporadic bursts.
This schedule stretching not only delays completion but creates additional costs for homeowners. Extended rental accommodations, storage fees, and the stress of living in a construction zone all represent real expenses not reflected in the original quote.

The Change Order Game

Perhaps the most common way underbidding builders recover costs is through aggressive change orders. Once demolition begins and you’re committed to the project, every discovered issue or requested modification becomes an opportunity to charge premium rates. What seemed like a reasonably priced project gradually balloons as these additional costs accumulate. Many homeowners report final costs 50-100% above the original “bargain” quote by project completion.

The Disappearing Act

The worst-case scenario occurs when builders realize they’ve underbid so severely that completing the project would result in financial loss. In such cases, some simply abandon the project, leaving homeowners with partially completed work and the daunting task of finding a replacement builder—who will likely charge premium rates to fix someone else’s mistakes.

The Value Alternative

Rather than seeking the lowest bid, savvy homeowners look for the best value. This means evaluating quotes based on what’s included, the builder’s reputation, their communication style, and their commitment to quality. A comprehensive quote that accounts for all aspects of your project—including contingencies for common surprises—demonstrates thoroughness and experience. While it might carry a higher initial price, it typically represents a more accurate picture of the true project cost.

The Partnership Perspective

Quality builders view their relationship with clients as a partnership rather than a transaction. They’ll walk you through their quote, explaining allowances, potential variables, and how they’ve prepared for typical challenges.This transparency builds trust and leads to a collaborative approach that ultimately delivers better results for both parties. Remember, renovation is both a significant investment and a relationship that will last months—choose your partner wisely.

The allure of saving money is powerful, especially with substantial investments like home renovations. However, the wisest approach focuses on value rather than initial price. By understanding what drives cost differences between quotes and recognizing the warning signs of underbidding, you protect yourself from the false economy of the too-good-to-be-true price that ultimately costs far more than you bargained for.

Thinking about transforming your home?

Get your free personalised AI renovation plan in 60 seconds.

Get My Free Plan →

Free — no obligation — plan ready in 60 seconds

Scroll to Top