From dfd98f49e7f2ab5c94dcae3af2d14750d301fa0a Mon Sep 17 00:00:00 2001 From: Samuel Degueldre Date: Thu, 24 Oct 2019 13:15:55 +0000 Subject: [PATCH] [IMP] web_editor: rework background-sizing option Previously, background size and position for snippets was configured in a modal, the user could choose a "focus point", which is rather unintuitive. They could also choose to use the "contain" background mode, with or without repeat, or a third option, where they could customize everything, from the background size in both directions to whether to repeat the background in only one or in both directions. This commit streamlines the background-sizing options, only the "cover" and "repeat pattern" options have been kept, as they cover almost every use case. Their configuration has been moved into the options menu for consistency with the rest of the editor, and the background's position is now chosen by clicking on the corresponding menu option, and dragging the background to the desired place. Part of https://github.com/odoo/odoo/pull/38959 maintask-2066614 task-2088304 --- .../static/src/js/editor/snippets.options.js | 406 +++++++++++------- .../static/src/scss/wysiwyg_snippets.scss | 23 + addons/web_editor/static/src/xml/editor.xml | 104 +---- .../static/src/js/editor/snippets.options.js | 2 +- addons/website/static/src/scss/website.scss | 11 +- .../static/src/scss/website.wysiwyg.scss | 158 ------- addons/website/views/snippets.xml | 15 +- 7 files changed, 294 insertions(+), 425 deletions(-) diff --git a/addons/web_editor/static/src/js/editor/snippets.options.js b/addons/web_editor/static/src/js/editor/snippets.options.js index 1237c041bf1..49867305fa7 100644 --- a/addons/web_editor/static/src/js/editor/snippets.options.js +++ b/addons/web_editor/static/src/js/editor/snippets.options.js @@ -3,7 +3,6 @@ odoo.define('web_editor.snippets.options', function (require) { var core = require('web.core'); var ColorPaletteWidget = require('web_editor.ColorPalette').ColorPaletteWidget; -var Dialog = require('web.Dialog'); var Widget = require('web.Widget'); var weWidgets = require('wysiwyg.widgets'); @@ -1520,9 +1519,9 @@ registry.background = SnippetOption.extend({ }); /** - * Handles the edition of snippet's background image position. + * Handles the edition of snippets' background image position. */ -registry['background_position'] = SnippetOption.extend({ +registry.BackgroundPosition = SnippetOption.extend({ xmlDependencies: ['/web_editor/static/src/xml/editor.xml'], /** @@ -1530,16 +1529,36 @@ registry['background_position'] = SnippetOption.extend({ */ start: function () { this._super.apply(this, arguments); - var self = this; - this.$target.on('snippet-option-change', function () { - self.onFocus(); + + this._initOverlay(); + this.img = document.createElement('img'); + this.img.src = this._getSrcFromCssValue(); + + this.$target.on('snippet-option-change', () => { + // Hides option if the bg image is removed in favor of a bg color + this._updateUI(); + // this.img is used to compute dragging speed + this.img.src = this._getSrcFromCssValue(); }); + + // Resize overlay content on window resize because background images + // change size, and on carousel slide because they sometimes take up + // more space and move elements around them. + $(window).on('resize.bgposition', () => this._dimensionOverlay()); + }, + /** + * @override + */ + destroy: function () { + this._toggleBgOverlay(false); + $(window).off('.bgposition'); + this._super.apply(this, arguments); }, /** * @override */ onFocus: function () { - this.$el.toggleClass('d-none', this.$target.css('background-image') === 'none'); + this._updateUI(); }, //-------------------------------------------------------------------------- @@ -1547,77 +1566,31 @@ registry['background_position'] = SnippetOption.extend({ //-------------------------------------------------------------------------- /** - * Opens a Dialog to edit the snippet's backgroung image position. + * Sets the background type (cover/repeat pattern). * - * @see this.selectClass for parameters + * @see this.selectClass for params */ - backgroundPosition: function (previewMode, value, $opt) { - var self = this; + backgroundType: function (previewMode, value, $opt) { + this.$target.toggleClass('o_bg_img_opt_repeat', value === 'repeat-pattern'); + this.$target.css('background-position', ''); + this.$target.css('background-size', ''); + }, + /** + * Saves current background position and enables overlay. + * + * @see this.selectClass for params + */ + backgroundPositionOverlay: function (previewMode, value, $opt) { + const position = this.$target.css('background-position').split(' ').map(v => parseInt(v)); + // Convert % values to pixels (because mouse movement is in pixels) + const delta = this._getBackgroundDelta(); + this.originalPosition = { + left: position[0] / 100 * delta.x || 0, + top: position[1] / 100 * delta.y || 0, + }; + this.currentPosition = _.clone(this.originalPosition); - this.previousState = [this.$target.attr('class'), this.$target.css('background-size'), this.$target.css('background-position')]; - - this.bgPos = self.$target.css('background-position').split(' '); - this.bgSize = self.$target.css('background-size').split(' '); - - this.modal = new Dialog(null, { - title: _t("Background Image Sizing"), - $content: $(qweb.render('web_editor.dialog.background_position')), - buttons: [ - {text: _t("Ok"), classes: 'btn-primary', close: true, click: _.bind(this._saveChanges, this)}, - {text: _t("Discard"), close: true, click: _.bind(this._discardChanges, this)}, - ], - }).open(); - - this.modal.opened().then(function () { - // Fetch data form $target - var value = ((self.$target.hasClass('o_bg_img_opt_contain')) ? 'contain' : ((self.$target.hasClass('o_bg_img_opt_custom')) ? 'custom' : 'cover')); - self.modal.$('> label > input[value=' + value + ']').prop('checked', true); - - if (self.$target.hasClass('o_bg_img_opt_repeat')) { - self.modal.$('#o_bg_img_opt_contain_repeat').prop('checked', true); - self.modal.$('#o_bg_img_opt_custom_repeat').val('o_bg_img_opt_repeat'); - } else if (self.$target.hasClass('o_bg_img_opt_repeat_x')) { - self.modal.$('#o_bg_img_opt_custom_repeat').val('o_bg_img_opt_repeat_x'); - } else if (self.$target.hasClass('o_bg_img_opt_repeat_y')) { - self.modal.$('#o_bg_img_opt_custom_repeat').val('o_bg_img_opt_repeat_y'); - } - - if (self.bgPos.length > 1) { - self.bgPos = { - x: self.bgPos[0], - y: self.bgPos[1], - }; - self.modal.$('#o_bg_img_opt_custom_pos_x').val(self.bgPos.x.replace('%', '')); - self.modal.$('#o_bg_img_opt_custom_pos_y').val(self.bgPos.y.replace('%', '')); - } - if (self.bgSize.length > 1) { - self.modal.$('#o_bg_img_opt_custom_size_x').val(self.bgSize[0].replace('%', '')); - self.modal.$('#o_bg_img_opt_custom_size_y').val(self.bgSize[1].replace('%', '')); - } - - // Focus Point - self.$focus = self.modal.$('.o_focus_point'); - self._updatePosInformation(); - - var imgURL = /\(['"]?([^'"]+)['"]?\)/g.exec(self.$target.css('background-image')); - imgURL = (imgURL && imgURL[1]) || ''; - var $img = $('', {class: 'img img-fluid', src: imgURL}); - $img.on('load', function () { - self._bindImageEvents($img); - }); - $img.prependTo(self.modal.$('.o_bg_img_opt_object')); - - // Bind events - self.modal.$el.on('change', '> label > input', function (e) { - self.modal.$('> .o_bg_img_opt').addClass('o_hidden') - .filter('[data-value=' + e.target.value + ']') - .removeClass('o_hidden'); - }); - self.modal.$el.on('change', 'input, select', function (e) { - self._saveChanges(); - }); - self.modal.$('> label > input:checked').trigger('change'); - }); + this._toggleBgOverlay(true); }, //-------------------------------------------------------------------------- @@ -1625,116 +1598,219 @@ registry['background_position'] = SnippetOption.extend({ //-------------------------------------------------------------------------- /** - * Bind events on the given image so that the users can adapt the focus - * point. + * Initializes the overlay, binds events to the buttons, inserts it in + * the DOM. * * @private - * @param {jQuery} $img */ - _bindImageEvents: function ($img) { - var self = this; + _initOverlay: function () { + this.$backgroundOverlay = $(qweb.render('web_editor.background_position_overlay')); + this.$overlayContent = this.$backgroundOverlay.find('.o_we_overlay_content'); + this.$overlayBackground = this.$overlayContent.find('.o_overlay_background'); - var mousedown = false; - $img.on('mousedown', function (e) { - mousedown = true; + this.$backgroundOverlay.on('click', '.o_btn_apply', () => { + this.$target.css('background-position', this.$bgDragger.css('background-position')); + this._toggleBgOverlay(false); }); - $img.on('mousemove', function (e) { - if (mousedown) { - _update(e); + this.$backgroundOverlay.on('click', '.o_btn_discard', () => { + this._toggleBgOverlay(false); + }); + + this.$backgroundOverlay.insertAfter(this.$overlay); + }, + /** + * Sets the overlay in the right place so that the draggable background + * renders over the target, and size the background item like the target. + * + * @private + */ + _dimensionOverlay: function () { + if (!this.$backgroundOverlay.is('.oe_active')) { + return; + } + // TODO: change #wrapwrap after web_editor rework. + const $wrapwrap = $('#wrapwrap'); + const targetOffset = this.$target.offset(); + + this.$backgroundOverlay.css({ + width: $wrapwrap.innerWidth(), + height: $wrapwrap.innerHeight(), + }); + + this.$overlayContent.offset(targetOffset); + + this.$bgDragger.css({ + width: `${this.$target.innerWidth()}px`, + height: `${this.$target.innerHeight()}px`, + }); + }, + /** + * Toggles the overlay's display and renders a background clone inside of it. + * + * @private + * @param {boolean} activate toggle the overlay on (true) or off (false) + */ + _toggleBgOverlay: function (activate) { + if (this.$backgroundOverlay.is('.oe_active') === activate) { + return; + } + + if (!activate) { + this.$backgroundOverlay.removeClass('oe_active'); + this.trigger_up('unblock_preview_overlays'); + this.trigger_up('activate_snippet', {$snippet: this.$target}); + + $(document).off('click.bgposition'); + return; + } + + this.trigger_up('hide_overlay'); + this.trigger_up('activate_snippet', { + $snippet: this.$target, + previewMode: true, + }); + this.trigger_up('block_preview_overlays'); + + // Create empty clone of $target with same display size, make it draggable and give it a tooltip. + this.$bgDragger = this.$target.clone().empty(); + this.$bgDragger.on('mousedown', this._onDragBackgroundStart.bind(this)); + this.$bgDragger.tooltip({ + title: 'Click and drag the background to adjust its position!', + trigger: 'manual', + container: this.$backgroundOverlay + }); + + // Replace content of overlayBackground, activate the overlay and give it the right dimensions. + this.$overlayBackground.empty().append(this.$bgDragger); + this.$backgroundOverlay.addClass('oe_active'); + this._dimensionOverlay(); + this.$bgDragger.tooltip('show'); + + // Needs to be deferred or the click event that activated the overlay deactivates it as well. + // This is caused by the click event which we are currently handling bubbling up to the document. + window.setTimeout(() => $(document).on('click.bgposition', this._onDocumentClicked.bind(this)), 0); + }, + /** + * Disables background position if no background image, disables size inputs + * in cover mode, and activates the proper select option. + * + * @override + */ + _setActive: function () { + this.$el.toggleClass('d-none', this.$target.css('background-image') === 'none'); + this.$el.find('we-input').toggleClass('d-none', this.$target.css('background-repeat') !== 'repeat'); + this.$el.find('[data-background-type]').removeClass('active') + .filter(`[data-background-type=${this.$target.css('background-repeat') === 'repeat' ? 'repeat-pattern' : 'cover'}]`).addClass('active'); + + this._super.apply(this, arguments); + }, + /** + * Returns the src value from a css value related to a background image + * (e.g. "url('blabla')" => "blabla" / "none" => ""). + * + * @private + * @param {string} value + * @returns {string} + */ + _getSrcFromCssValue: function (value) { + if (value === undefined) { + value = this.$target.css('background-image'); + } + var srcValueWrapper = /url\(['"]*|['"]*\)|^none$/g; + return value && value.replace(srcValueWrapper, '') || ''; + }, + /** + * Returns the difference between the target's size and the background's + * rendered size. Background position values in % are a percentage of this. + * + * @private + */ + _getBackgroundDelta: function () { + const bgSize = this.$target.css('background-size'); + if (bgSize !== 'cover') { + let [width, height] = bgSize.split(' '); + if (width === 'auto' && (height === 'auto' || !height)) { + return { + x: this.$target.outerWidth() - this.img.naturalWidth, + y: this.$target.outerHeight() - this.img.naturalHeight, + }; } - }); - $img.on('mouseup', function (e) { - self.$focus.addClass('o_with_transition'); - _update(e); - setTimeout(function () { - self.$focus.removeClass('o_with_transition'); - }, 200); - mousedown = false; - }); - - function _update(e) { - var posX = e.pageX - $(e.target).offset().left; - var posY = e.pageY - $(e.target).offset().top; - self.bgPos = { - x: clipValue(posX / $img.width() * 100).toFixed(2) + '%', - y: clipValue(posY / $img.height() * 100).toFixed(2) + '%', + // At least one of width or height is not auto, so we can use it to calculate the other if it's not set + [width, height] = [parseInt(width), parseInt(height)]; + return { + x: this.$target.outerWidth() - (width || (height * this.img.naturalWidth / this.img.naturalHeight)), + y: this.$target.outerHeight() - (height || (width * this.img.naturalHeight / this.img.naturalWidth)), }; - self._updatePosInformation(); - self._saveChanges(); + } - function clipValue(value) { - return Math.max(0, Math.min(value, 100)); - } - } + const renderRatio = Math.max( + this.$target.outerWidth() / this.img.naturalWidth, + this.$target.outerHeight() / this.img.naturalHeight + ); + + return { + x: this.$target.outerWidth() - Math.round(renderRatio * this.img.naturalWidth), + y: this.$target.outerHeight() - Math.round(renderRatio * this.img.naturalHeight), + }; }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + /** - * Removes all option-related classes and style on the target element. + * Drags the overlay's background image, copied to target on "Apply". * * @private */ - _clean: function () { - this.$target.removeClass('o_bg_img_opt_contain o_bg_img_opt_custom o_bg_img_opt_repeat o_bg_img_opt_repeat_x o_bg_img_opt_repeat_y') - .css({ - 'background-size': '', - 'background-position': '', - }); - }, - /** - * Restores the target style before last edition made with the option. - * - * @private - */ - _discardChanges: function () { - this._clean(); - if (this.previousState) { - this.$target.addClass(this.previousState[0]).css({ - 'background-size': this.previousState[1], - 'background-position': this.previousState[2], - }); - } - }, - /** - * Updates the visual representation of the chosen background position. - * - * @private - */ - _updatePosInformation: function () { - this.modal.$('.o_bg_img_opt_ui_info .o_x').text(this.bgPos.x); - this.modal.$('.o_bg_img_opt_ui_info .o_y').text(this.bgPos.y); - this.$focus.css({ - left: this.bgPos.x, - top: this.bgPos.y, + _onDragBackgroundStart: function (ev) { + ev.preventDefault(); + this.$bgDragger.addClass('o_we_grabbing'); + const $document = $(this.ownerDocument); + $document.on('mousemove.bgposition', this._onDragBackgroundMove.bind(this)); + $document.one('mouseup', () => { + this.$bgDragger.removeClass('o_we_grabbing'); + $document.off('mousemove.bgposition'); }); }, /** - * Updates the target element to match the chosen options. + * Drags the overlay's background image, copied to target on "Apply". * * @private */ - _saveChanges: function () { - this._clean(); + _onDragBackgroundMove: function (ev) { + ev.preventDefault(); - var bgImgSize = this.modal.$('> :not(label):not(.o_hidden)').data('value') || 'cover'; - switch (bgImgSize) { - case 'cover': - this.$target.css('background-position', this.bgPos.x + ' ' + this.bgPos.y); - break; - case 'contain': - this.$target.addClass('o_bg_img_opt_contain'); - this.$target.toggleClass('o_bg_img_opt_repeat', this.modal.$('#o_bg_img_opt_contain_repeat').prop('checked')); - break; - case 'custom': - this.$target.addClass('o_bg_img_opt_custom'); - var sizeX = this.modal.$('#o_bg_img_opt_custom_size_x').val(); - var sizeY = this.modal.$('#o_bg_img_opt_custom_size_y').val(); - var posX = this.modal.$('#o_bg_img_opt_custom_pos_x').val(); - var posY = this.modal.$('#o_bg_img_opt_custom_pos_y').val(); - this.$target.addClass(this.modal.$('#o_bg_img_opt_custom_repeat').val()) - .css({ - 'background-size': (sizeX ? sizeX + '%' : 'auto') + ' ' + (sizeY ? sizeY + '%' : 'auto'), - 'background-position': (posX ? posX + '%' : 'auto') + ' ' + (posY ? posY + '%' : 'auto'), - }); - break; + const delta = this._getBackgroundDelta(); + this.currentPosition.left = clamp(this.currentPosition.left + ev.originalEvent.movementX, [0, delta.x]); + this.currentPosition.top = clamp(this.currentPosition.top + ev.originalEvent.movementY, [0, delta.y]); + + const percentPosition = { + left: this.currentPosition.left / delta.x * 100, + top: this.currentPosition.top / delta.y * 100, + }; + // In cover mode, one delta will be 0 and dividing by it will yield Infinity. + // Defaulting to originalPosition in that case (can't be dragged) + percentPosition.left = isFinite(percentPosition.left) ? percentPosition.left : this.originalPosition.left; + percentPosition.top = isFinite(percentPosition.top) ? percentPosition.top : this.originalPosition.top; + + this.$bgDragger.css('background-position', `${percentPosition.left}% ${percentPosition.top}%`); + + function clamp(val, bounds) { + // We sort the bounds because when one dimension of the rendered background is + // larger than the container, delta is negative, and we want to use it as lower bound + bounds = bounds.sort(); + return Math.max(bounds[0], Math.min(val, bounds[1])); + } + }, + /** + * Deactivates the overlay if the user clicks outside of it. + * + * @private + */ + _onDocumentClicked: function (ev) { + if (!ev.target.closest('.o_we_background_position_overlay')) { + this._toggleBgOverlay(false); } }, }); diff --git a/addons/web_editor/static/src/scss/wysiwyg_snippets.scss b/addons/web_editor/static/src/scss/wysiwyg_snippets.scss index 5ddba517474..98c3d56b22e 100644 --- a/addons/web_editor/static/src/scss/wysiwyg_snippets.scss +++ b/addons/web_editor/static/src/scss/wysiwyg_snippets.scss @@ -836,6 +836,29 @@ body.editor_enable.editor_has_snippets { } } } + + // Background position overlay + &.o_we_background_position_overlay { + background-color: rgba(0,0,0,.7); + z-index: auto; + + .o_we_overlay_content { + cursor: grab; + + .o_we_grabbing { + cursor: grabbing; + } + } + + .o_overlay_background > * { + display: block !important; + top: 0 !important; + right: 0 !important; + bottom: 0 !important; + left: 0 !important; + transform: none !important; + } + } } .contact_menu { diff --git a/addons/web_editor/static/src/xml/editor.xml b/addons/web_editor/static/src/xml/editor.xml index ca653c5d7ea..b1d7128e2c1 100644 --- a/addons/web_editor/static/src/xml/editor.xml +++ b/addons/web_editor/static/src/xml/editor.xml @@ -13,102 +13,16 @@ - -
- - - - -
-
-
Assign a focal point that will always be visible
-
- - - - - -
X: Y:
+ + +
+
+
+
+ +
-
-
-
- -
-
- -
-
-
-
-
-
-
- Background size -

Sets the width and height of the background image in percent of the parent element.

-
- -
-
- -
-
%
-
-
-
-
-
- -
-
- -
-
%
-
-
-
-
-
-
- Background position -

Set the starting position of the background image.

-
- -
-
- -
-
%
-
-
-
-
-
- -
-
- -
-
%
-
-
-
-
-
-
- Background repeat -

Define if/how the background image will be repeated

- -
-
-
+
diff --git a/addons/website/static/src/js/editor/snippets.options.js b/addons/website/static/src/js/editor/snippets.options.js index c1af7852fd2..bf7960cc13d 100644 --- a/addons/website/static/src/js/editor/snippets.options.js +++ b/addons/website/static/src/js/editor/snippets.options.js @@ -674,7 +674,7 @@ options.registry.parallax = options.Class.extend({ */ onFocus: function () { this.trigger_up('option_update', { - optionNames: ['background', 'background_position'], + optionNames: ['background', 'BackgroundPosition'], name: 'target', data: this.$target.find('> .s_parallax_bg'), }); diff --git a/addons/website/static/src/scss/website.scss b/addons/website/static/src/scss/website.scss index 850f180f903..6e29659612c 100644 --- a/addons/website/static/src/scss/website.scss +++ b/addons/website/static/src/scss/website.scss @@ -239,6 +239,14 @@ font[class*='bg-'] { .oe_img_bg { background-size: cover; background-repeat: no-repeat; + + &.o_bg_img_opt_repeat { + background-size: auto; + background-repeat: repeat; + } + + // Compatibility <= 13.0, TODO remove? + // ----------------------------------- &.o_bg_img_opt_contain { background-size: contain; background-position: center center; @@ -246,9 +254,6 @@ font[class*='bg-'] { &.o_bg_img_opt_custom { background-size: auto; } - &.o_bg_img_opt_repeat { - background-repeat: repeat; - } &.o_bg_img_opt_repeat_x { background-repeat: repeat-x; } diff --git a/addons/website/static/src/scss/website.wysiwyg.scss b/addons/website/static/src/scss/website.wysiwyg.scss index 3206e4b027a..4e6fa2bc0c9 100644 --- a/addons/website/static/src/scss/website.wysiwyg.scss +++ b/addons/website/static/src/scss/website.wysiwyg.scss @@ -159,161 +159,3 @@ html[lang] > body.editor_enable [data-oe-translation-state] { opacity: 0; } } - -// MODALS -body .modal { - - // BACKGROUND IMAGE OPTIONS - .o_bg_img_opt_modal .o_bg_img_opt { - margin: 15px 0; - - .help-control { - @include o-position-absolute(15px, 15px); - } - - .help { - margin: 15px 0; - - .simulator { - position: relative; - float: left; - margin-right: 15px; - width: 120px; - height: 100px; - - .bg { - @include o-position-absolute(0, 0, 0, 0); - border: 1px dotted #888787; - background-image: url(/web/image); - background-position: center center; - background-size: cover; - } - - .el { - @include o-position-absolute(10px, 0, 10px, 0); - border: 1px solid #222; - } - - &.contain_bg { - .bg { - @include o-position-absolute(0, 12px, 20px, 12px); - } - - .el { - @include o-position-absolute(0, 0, 20px, 0); - } - } - } - - +* { - clear: left; - } - } - - .o_bg_img_opt_cover_edition { - @include o-we-preview-box; - padding: 0 25px 25px 25px; - - h6 { - color: white; - font-weight: bold; - } - - .o_bg_img_opt_object { - position: relative; - background: white; - min-height: 10px; - @include o-we-preview-content; - - >img { - cursor: crosshair; - border-top: 1px solid #5A5A5A; - border-bottom: 1px solid #111; - } - - &:hover .o_focus_point:before { - opacity: 0.5; - } - } - - .o_bg_img_opt_ui_info { - animation: fadeInOut 2s ease forwards; - @include o-position-absolute($bottom: 1px, $right: 0); - display: block; - padding: 3px 13px; - background-color: rgba(255, 255, 255, 0.8); - color: #333; - text-align: center; - font-weight: bold; - pointer-events: none; - - span { - font-weight: normal; - } - - .o_x { - margin-right: 10px - } - } - - .grid { - @include o-position-absolute($top: 33.33%, $left: 0); - display: block; - width: 100%; - height: 1px; - background: fade-out(lighten($o-brand-primary, 30%), 0.5); - pointer-events: none; - - &.grid-2 { - top: 66.66% - } - - &.grid-3 { - top: 0; - left: 33.33%; - width: 1px; - height: 100% - } - - &.grid-4 { - top: 0; - left: 66.66%; - width: 1px; - height: 100% - } - } - - .o_focus_point { - @include o-position-absolute($top: 0, $left: 0); - width: 30px; - height: 30px; - border: 2px solid white; - border-radius: 100%; - margin-top: -15px; - margin-left: -15px; - box-shadow: 0 0 1px #333; - pointer-events: none; - - &.o_with_transition { - transition: all 0.2s ease 0s; - } - - &:before { - pointer-events: none; - content: ""; - display: block; - width: 100px; - height: 100px; - margin-top: -37px; - margin-left: -37px; - border: 1px solid #EAEAEA; - background: rgba(247, 76, 76, 0); - border-radius: 100em; - opacity: 1; - box-shadow: 0 0 0 100em rgba(0, 0, 0, 0.33); - transition: opacity 0.2s ease 0s; - } - } - } - } -} diff --git a/addons/website/views/snippets.xml b/addons/website/views/snippets.xml index 16b218b6366..103dd62cc4a 100644 --- a/addons/website/views/snippets.xml +++ b/addons/website/views/snippets.xml @@ -1226,11 +1226,20 @@ data-selector="section, .parallax, .carousel-item" data-exclude=".s_hr, .s_image_gallery, .o_gallery .carousel-item"/> - -
+
- Background Image Sizing + + + Cover + Repeat pattern + + + + +