diff --git a/lighthouse-core/formatters/partials/accessibility.html b/lighthouse-core/formatters/partials/accessibility.html index c255d1a19e38..579cbff038ce 100644 --- a/lighthouse-core/formatters/partials/accessibility.html +++ b/lighthouse-core/formatters/partials/accessibility.html @@ -1,31 +1,15 @@ - -
-
- - {{ this.help }} - - -
-
+
+ + {{#if (gt this.nodes.length 1)}} + {{this.nodes.length}} elements fail this test + {{else}} + {{this.nodes.length}} element fails this test + {{/if}} + learn more + + +
diff --git a/lighthouse-core/formatters/partials/critical-request-chains.html b/lighthouse-core/formatters/partials/critical-request-chains.html index 0e678bb5d0b0..d476301e9ea5 100644 --- a/lighthouse-core/formatters/partials/critical-request-chains.html +++ b/lighthouse-core/formatters/partials/critical-request-chains.html @@ -102,11 +102,11 @@ {{/each}} {{/inline}} -
-
Longest request chain (shorter is better): {{longestChain this}}
-
Longest chain duration (shorter is better): {{formatTime (longestDuration this)}}ms
-
Longest chain transfer size (smaller is better): {{formatTransferSize (longestChainTransferSize this)}}KB
-
+
-
+ + diff --git a/lighthouse-core/formatters/partials/speedline.html b/lighthouse-core/formatters/partials/speedline.html index 92e7a1cc0722..e36e5b418649 100644 --- a/lighthouse-core/formatters/partials/speedline.html +++ b/lighthouse-core/formatters/partials/speedline.html @@ -4,9 +4,7 @@ } -
-
-
First Visual Change: {{this.first}}ms
-
Last Visual Change: {{this.complete}}ms
-
-
+ diff --git a/lighthouse-core/formatters/partials/url-list.html b/lighthouse-core/formatters/partials/url-list.html index 91c80a438c7c..3f667334d30a 100644 --- a/lighthouse-core/formatters/partials/url-list.html +++ b/lighthouse-core/formatters/partials/url-list.html @@ -1,13 +1,7 @@ -
-
- URLs - {{#each this}} -
- {{this.url}} - {{#if this.label}} - ({{this.label}}) - {{/if}} - {{#if this.code}} -
{{this.code}}
- {{/if}} -
- {{/each}} -
-
+
+ URLs + +
diff --git a/lighthouse-core/formatters/partials/user-timings.html b/lighthouse-core/formatters/partials/user-timings.html index b4e72d7d074e..27c975abdedd 100644 --- a/lighthouse-core/formatters/partials/user-timings.html +++ b/lighthouse-core/formatters/partials/user-timings.html @@ -1,23 +1,17 @@ -
-
- {{#each this}} -
- {{#if this.isMark}} - Mark: {{ decimal this.startTime }}ms - {{ this.name }} - {{else}} - Measure {{ decimal this.duration }}ms - {{ this.name }} - {{/if}} -
- {{/each}} -
-
+ diff --git a/lighthouse-core/report/report-generator.js b/lighthouse-core/report/report-generator.js index 4f31f2c5bc78..fa90a748c3ce 100644 --- a/lighthouse-core/report/report-generator.js +++ b/lighthouse-core/report/report-generator.js @@ -93,9 +93,8 @@ class ReportGenerator { // for color styling. Handlebars.registerHelper('getItemRating', getItemRating); - Handlebars.registerHelper('showHelpText', value => { - return getItemRating(value) === RATINGS.GOOD.label ? 'hidden' : ''; - }); + Handlebars.registerHelper('shouldShowHelpText', + value => (getItemRating(value) !== RATINGS.GOOD.label)); // Convert numbers to fixed point decimals Handlebars.registerHelper('decimal', number => { @@ -104,6 +103,27 @@ class ReportGenerator { } return number; }); + + // value is boolean? + Handlebars.registerHelper('is-bool', value => (typeof value === 'boolean')); + + // a > b + Handlebars.registerHelper('gt', (a, b) => (a > b)); + + // !value + Handlebars.registerHelper('not', value => !value); + + // arg1 && arg2 && ... && argn + Handlebars.registerHelper('and', () => { + let arg = false; + for (let i = 0, n = arguments.length - 1; i < n; i++) { + arg = arguments[i]; + if (!arg) { + break; + } + } + return arg; + }); } /** @@ -222,6 +242,7 @@ class ReportGenerator { lighthouseVersion: results.lighthouseVersion, generatedTime: this._formatTime(results.generatedTime), css: this.getReportCSS(inline), + reportContext: 'extension', // devtools, extension, cli script: this.getReportJS(inline), aggregations: results.aggregations, auditsByCategory: this._createPWAAuditsByCategory(results.aggregations) diff --git a/lighthouse-core/report/scripts/lighthouse-report.js b/lighthouse-core/report/scripts/lighthouse-report.js index bef1b7e70419..648642492103 100644 --- a/lighthouse-core/report/scripts/lighthouse-report.js +++ b/lighthouse-core/report/scripts/lighthouse-report.js @@ -22,12 +22,11 @@ /* Using ES5 to broaden support */ function LighthouseReport() { this.printButton = document.querySelector('.js-print'); - this.checkboxToggleView = document.querySelector('.js-toggle-view'); this.viewUserFeature = document.querySelector('.js-report-by-user-feature'); this.viewTechnology = document.querySelector('.js-report-by-technology'); this.reportItems = document.querySelectorAll('.report-section__item'); + this.helpToggles = document.querySelectorAll('.subitem__help-toggle'); - this.updateView = this.updateView.bind(this); this.toggleHelpText = this.toggleHelpText.bind(this); this.addEventListeners(); @@ -39,29 +38,14 @@ LighthouseReport.prototype = { window.print(); }, - updateView: function() { - if (this.checkboxToggleView.checked) { - this.viewUserFeature.setAttribute('hidden', 'hidden'); - this.viewTechnology.removeAttribute('hidden'); - } else { - this.viewUserFeature.removeAttribute('hidden'); - this.viewTechnology.setAttribute('hidden', 'hidden'); - } - }, - toggleHelpText: function(e) { - if (e.target.classList.contains('report-section__item-help-toggle')) { - const el = e.currentTarget.querySelector('.report-section__item-helptext'); - if (el) { - el.hidden = !el.hidden; - } - } + const item = e.currentTarget.closest('.subitem'); + item.classList.toggle('--show-help'); }, addEventListeners: function() { this.printButton.addEventListener('click', this.onPrint); - this.checkboxToggleView.addEventListener('change', this.updateView); - [...this.reportItems].forEach(node => { + [...this.helpToggles].forEach(node => { node.addEventListener('click', this.toggleHelpText); }); } diff --git a/lighthouse-core/report/styles/report.css b/lighthouse-core/report/styles/report.css index f52309e6186c..304c0cf9f29f 100644 --- a/lighthouse-core/report/styles/report.css +++ b/lighthouse-core/report/styles/report.css @@ -14,50 +14,86 @@ * limitations under the License. */ -@font-face { - font-family: 'Roboto'; - font-style: normal; - font-weight: 100; - src: local('Roboto Thin'), local('Roboto-Thin') format('woff'); -} -@font-face { - font-family: 'Roboto'; - font-style: normal; - font-weight: 400; - src: local('Roboto'), local('Roboto-Regular') format('woff'); -} -@font-face { - font-family: 'Roboto'; - font-style: normal; - font-weight: 500; - src: local('Roboto Medium'), local('Roboto-Medium') format('woff'); -} - * { box-sizing: border-box; } -html, body { +span, div, p, section, header, h1, h2, li, ul { + margin: 0; padding: 0; + line-height: inherit; +} + + +:root { + --text-font-family: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; + --text-color: #212121; + --secondary-text-color: #757575; + --accent-color: #719EA8; + --poor-color: #eb211e; + --good-color: #1ac123; + --average-color: #ffae00; + --unknown-color: #b3b3b3; + --gutter-gap: 12px; + --gutter-width: 40px; + --body-font-size: 14px; + --body-line-height: 20px; + --subitem-font-size: 14px; + --subitem-line-height: 20px; + --subheading-font-size: 16px; + --subheading-line-height: 24px; + --subheading-color: var(--accent-color); + --heading-font-size: 24px; + --heading-line-height: 32px; + --subitem-indent: 24px; + --max-line-length: none; +} + +:root[data-report-context="devtools"] { + --text-font-family: '.SFNSDisplay-Regular', 'Helvetica Neue', 'Lucida Grande', sans-serif; + --text-color: #222; + --secondary-text-color: #606060; + --accent-color: #3879d9; + --body-font-size: 13px; + --body-line-height: 17px; + --subitem-font-size: 14px; + --subitem-line-height: 18px; + --subheading-font-size: 16px; + --subheading-line-height: 20px; + --subheading-color: inherit; + --heading-font-size: 20px; + --heading-line-height: 24px; + --subitem-indent: 24px; + --max-line-length: calc(60 * var(--body-font-size)); +} + +html { + font-family: var(--text-font-family); + font-size: var(--body-font-size); + line-height: 1; margin: 0; - background: #F0F0F0; - color: #444; - font-family: Arial, sans-serif; - font-size: 15px; + padding: 0; } -body { - min-width: 845px; +html, body { + height: 100%; } a { - color: #57A0A8; + color: #15c; +} + +body { + display: flex; + flex-direction: column; + align-items: stretch; + margin: 0; + background: #f5f5f5; } .report { width: 100%; margin: 0 auto; - border-radius: 0 0 4px 4px; max-width: 1280px; background: #FFF; box-shadow: 0 0 6px 0 rgba(0,0,0,0.26); @@ -69,25 +105,7 @@ a { background: url('data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjNDQ0IiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxwYXRoIGQ9Ik0xOSA4SDVjLTEuNjYgMC0zIDEuMzQtMyAzdjZoNHY0aDEydi00aDR2LTZjMC0xLjY2LTEuMzQtMy0zLTN6bS0zIDExSDh2LTVoOHY1em0zLTdjLS41NSAwLTEtLjQ1LTEtMXMuNDUtMSAxLTEgMSAuNDUgMSAxLS40NSAxLTEgMXptLTEtOUg2djRoMTJWM3oiLz4KICAgIDxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz4KPC9zdmc+Cg=='); border: none; cursor: pointer; -} - -.toggle { - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; - font-size: 13px; - margin-right: 26px; -} - -.toggle__label { - padding: 0 10px 0 20px; -} - -.toggle__title { - margin: 0; - font-weight: bold; - font-size: 13px; + flex: 0 0 auto; } .score-container__overall-score { @@ -123,7 +141,6 @@ a { } .report-body__content { - padding: 0 4% 0 4%; margin-left: 22%; position: relative; } @@ -157,7 +174,7 @@ a { height: 115px; line-height: 54px; color: #FFF; - font-family: 'Roboto', Arial, sans-serif; + font-family: var(--text-font-family); font-size: 18px; position: relative; display: flex; @@ -178,7 +195,7 @@ a { } .menu__header-title { - font-family: 'Roboto', Arial, sans-serif; + font-family: var(--text-font-family); font-size: 22px; font-weight: 400; color: #fff; @@ -190,7 +207,7 @@ a { .menu__header-version { opacity: 0.4; color: #fff; - font-family: 'Roboto', Arial, sans-serif; + font-family: var(--text-font-family); font-size: 14px; line-height: 1.5; } @@ -258,14 +275,22 @@ a { color: #FFF; } + +.report-body__metadata { + flex: 1 1 0; + margin-right: 40px; + white-space: nowrap; +} + .report-body__url { - font-family: 'Roboto', Arial, sans-serif; + font-family: var(--text-font-family); white-space: nowrap; font-size: 13px; - font-style: italic; font-weight: 400; - color: #aaa; - padding: 20px 0 10px; + color: #757575; + line-height: 20px; + overflow: hidden; + text-overflow: ellipsis; } .report-body__url a { @@ -289,16 +314,17 @@ a { height: 58px; border-bottom: 1px solid #EBEBEB; background: #FAFAFA; + margin-left: 22%; display: flex; flex-direction: row; align-items: center; justify-content: flex-end; - padding: 0 4%; + padding: 0 var(--heading-line-height); } .report-section__title { -webkit-font-smoothing: antialiased; - font-family: 'Roboto', Arial, sans-serif; + font-family: var(--text-font-family); font-size: 28px; font-weight: 500; color: #49525F; @@ -322,7 +348,7 @@ a { .report-section__subtitle { -webkit-font-smoothing: antialiased; - font-family: 'Roboto', Arial, sans-serif; + font-family: var(--text-font-family); font-size: 18px; font-weight: 500; color: #719EA8; @@ -427,112 +453,271 @@ a { .footer { margin-top: 40px; + margin-left: 22%; height: 130px; line-height: 90px; text-align: center; font-size: 12px; border-top: 1px solid #EBEBEB; color: #999; - margin-left: 22%; } -.toggle__label { - width: 120px; - height: 24px; +.coming-soon, .coming-soon * { + color: #AAA; +} + +.coming-soon .report-section__item-value { + font-size: 70%; +} + +.devtabs { + flex: 0 1 auto; + background: right 0 / auto 27px no-repeat url(tabs_right.png), + 0 0 / auto 27px no-repeat url(tabs_left.png), + 0 0 / auto 27px repeat-x url(tabs_center.png); + height: 27px; +} + +.aggregations__header { +} + +.aggregations__header > h1 { + font-size: var(--heading-font-size); + font-weight: normal; + line-height: var(--heading-line-height); +} + +.aggregations { position: relative; - margin-left: 20px; + padding: var(--heading-line-height); + padding-left: calc(var(--heading-line-height) + var(--gutter-width) + var(--gutter-gap)); } -.toggle-view { +.aggregations:not(:first-child) { + border-top: 1px solid #ccc; +} + +.aggregations__desc { + font-size: var(--body-font-size); + line-height: var(--body-line-height); + margin-top: calc(var(--body-line-height) / 2); +} + +.section-result { position: absolute; - left: 50%; - top: 40%; - transform: translate(-50%, -50%); - z-index: 0; + top: var(--heading-line-height); + left: var(--heading-line-height); + width: var(--gutter-width); + + display: flex; + flex-direction: column; + align-items: flex-end; +} + +.section-result__score { + display: flex; + flex-direction: column; + align-items: stretch; + background: #000; + color: #fff; + text-align: center; + padding: 4px 8px; + border-radius: 2px; + margin-top: calc((var(--heading-line-height) - var(--heading-font-size) - 6px) / 2); +} + +.section-result__score.--good { background-color: var(--good-color); } +.section-result__score.--poor { background-color: var(--poor-color); } +.section-result__score.--average { background-color: var(--average-color); } + +.section-result__points { + font-size: var(--heading-font-size); +} + +.section-result__divider { + display: none; +} + +.section-result__total { + font-size: var(--body-font-size); + margin-top: 2px; + border-top: 1px solid #fff; + padding-top: 4px; +} + +.aggregation__header { + max-width: var(--max-line-length); } -.toggle__display { +.aggregation__header > h2 { + font-size: var(--subheading-font-size); + font-weight: normal; + line-height: var(--subheading-line-height); + color: var(--subheading-color); +} + +.aggregation { + margin-top: var(--subheading-line-height); + max-width: var(--max-line-length); +} + +.aggregation__desc { + font-size: var(--body-font-size); + line-height: var(--body-line-height); + margin-top: calc(var(--body-line-height) / 2); +} + +.subitems { + list-style: none; + margin-top: var(--subitem-line-height); +} + +.subitem { + position: relative; + font-size: var(--subitem-font-size); + padding-left: calc(var(--subitem-indent) + var(--gutter-width) + var(--gutter-gap)); + margin-top: calc(var(--subitem-line-height) / 2); +} + +.subitem.--coming-soon { + color: var(--secondary-text-color); +} + +.subitem strong { + font-weight: bold; +} + +.subitem small { + font-size: var(--body-font-size); +} + +.subitem__desc { + line-height: var(--subitem-line-height); +} + +.subitem-result { position: absolute; top: 0; - left: 0; - right: 0; - bottom: 0; - z-index: 1; + left: var(--subitem-indent); + width: var(--gutter-width); + display: flex; + flex-direction: column; + align-items: flex-end; } -.toggle__display::before, -.toggle__display::after, -.toggle__focus-ring { +.subitem-result__good, .subitem-result__poor, .subitem-result__unknown { + position: relative; display: block; - height: 100%; - width: 100%; + overflow: hidden; + margin-top: calc((var(--subitem-line-height) - 16px) / 2); + width: 16px; + height: 16px; + border-radius: 50%; + color: transparent; + background-color: #000; +} + +.subitem-result__good::after, .subitem-result__poor::after, .subitem-result__unknown::after { + content: ''; position: absolute; - top: 0; left: 0; - text-align: center; - line-height: 24px; - font-size: 13px; - color: #333; - transition: opacity 0.2s cubic-bezier(0,0,0.3,1); + right: 0; + top: 0; + bottom: 0; + -webkit-mask: center center / 12px 12px no-repeat; + background-color: #fff; +} + +.subitem-result__good { background-color: var(--good-color); } +.subitem-result__good::after { -webkit-mask-image: url('data:image/svg+xml;utf8,good'); } +.subitem-result__poor { background-color: var(--poor-color); } +.subitem-result__poor::after { -webkit-mask-image: url('data:image/svg+xml;utf8,poor'); } +.subitem-result__unknown { background-color: var(--unknown-color); } +.subitem-result__unknown::after { -webkit-mask-image: url('data:image/svg+xml;utf8,neutral'); } + +.subitem-result__points { + margin-top: calc((var(--subitem-line-height) - var(--subitem-font-size) - 4px) / 2); + background: #000; + padding: 2px 4px; + border-radius: 1px; + color: #fff; + border-radius: 2px; } -.toggle__display::before { - content: attr(data-on); - opacity: 0; - background: #57A0A8; - box-shadow: inset 0 1px 2px rgba(0,0,0,0.5); - border-radius: 12px; - color: #FFF; -} +.subitem-result__points.--good { background-color: var(--good-color); } +.subitem-result__points.--poor { background-color: var(--poor-color); } +.subitem-result__points.--average { background-color: var(--average-color); } -.toggle__display::after { - content: attr(data-off); - opacity: 1; - background: #E7E7E8; - box-shadow: inset 0 1px 2px rgba(0,0,0,0.5); - border-radius: 12px; +.subitem__details { + list-style: none; + margin: 0; + padding: 0; + margin-left: var(--subitem-indent); } -.toggle__focus-ring { - border-radius: 12px; +.subitem__detail { + font-size: var(--body-font-size); + line-height: var(--body-line-height); + margin-top: calc(var(--body-line-height) / 2); } -.toggle__slider { - position: absolute; - left: 4px; - top: 4px; - border-radius: 50%; - background: #FFF; +.subitem__help-toggle { + -webkit-appearance: none; + position: relative; + display: inline-block; width: 16px; height: 16px; - box-shadow: 0 1px 2px rgba(0,0,0,0.5); - transition: transform 0.1s cubic-bezier(0,0,0.3,1); - z-index: 2; + border-radius: 50%; + border: 1px solid #ccc; + vertical-align: middle; + margin-left: .5em; + outline: 0; } -.toggle-view:focus ~ .toggle__focus-ring, -.toggle__label:active .toggle__focus-ring { - box-shadow: 0 0 5px 2px #83BFFC; +.subitem__help-toggle::after { + content: ''; + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + -webkit-mask: 1px 1px / 12px 12px no-repeat; + -webkit-mask-image: url('data:image/svg+xml;utf8,help'); + background-color: var(--secondary-text-color); + cursor: pointer; } -.toggle-view:checked ~ .toggle__display::before { - opacity: 1; +.subitem__help-toggle:hover { + border-color: var(--secondary-text-color); } -.toggle-view:checked ~ .toggle__display::after { - opacity: 0; +.subitem__help-toggle:checked { + background-color: var(--accent-color); + border-color: var(--accent-color); } -.toggle-view:checked ~ .toggle__display .toggle__slider { - transform: translateX(96px); +.subitem__help-toggle:checked::after { + background-color: #fff; } -.coming-soon, .coming-soon * { - color: #AAA; +.subitem__help { + display: none; + font-size: var(--body-font-size); + line-height: var(--body-line-height); + margin-top: calc(var(--body-line-height) / 2); + margin-left: var(--subitem-indent); } -.coming-soon .report-section__item-value { - font-size: 70%; +.subitem.--show-help .subitem__help { + display: block; +} + +.subitem__debug { + font-size: var(--body-font-size); + line-height: var(--body-line-height); + margin-top: calc(var(--body-line-height) / 2); + margin-left: var(--subitem-indent); + color: var(--poor-color); } @media print { @@ -545,3 +730,25 @@ a { display: none; } } + +:root[data-report-context="devtools"] .report { + margin: 0; + box-shadow: none; + max-width: none; +} + +:root[data-report-context="devtools"] .report-body__menu-container { + display: none; +} + +:root[data-report-context="devtools"] .report-body__header { + display: none; +} + +:root[data-report-context="devtools"] .report-body__content { + margin-left: 0; +} + +:root[data-report-context="devtools"] .footer { + display: none; +} diff --git a/lighthouse-core/report/templates/report.html b/lighthouse-core/report/templates/report.html index c3c2107943ec..53a642bfff25 100644 --- a/lighthouse-core/report/templates/report.html +++ b/lighthouse-core/report/templates/report.html @@ -16,7 +16,7 @@ --> - + @@ -30,18 +30,10 @@
-
-

View:

- - + -
@@ -63,73 +55,97 @@

Lighthouse

-
Results for {{ url }}
- +
{{#each aggregations}} -
-

- {{ this.name }} - {{#if this.scored}} - - {{ getTotalScore this }} - / 100 +
+ +

{{ this.description }}

+ {{#if this.scored}} +
+ + {{ getTotalScore this }} + / + 100 - {{/if}} -

-

{{ this.description }}

+
+ {{/if}}
{{#each this.score as |aggregation|}} -
+
{{#if aggregation.name }} -

{{ aggregation.name }}

+
+

{{ aggregation.name }}

+
{{/if}} {{#if aggregation.description }} -

{{{ aggregation.description }}}

+

{{{ aggregation.description }}}

{{/if}} -
    +
      {{#each aggregation.subItems as |subItem| }} -
    • -
      - - {{#unless ../../scored }} - - {{ subItem.category }}: - - {{/unless}} +
    • - {{ subItem.description }} - {{#if subItem.optimalValue }} - - (target: {{ subItem.optimalValue }}) - - {{/if}} - {{#if subItem.comingSoon}} - (Coming soon) - {{/if}} +

      + {{#unless ../../scored }} + {{ subItem.category }}: + {{/unless}} + + {{ subItem.description }} - {{#if subItem.helpText }} - ? - {{/if}} - + {{~#if (and subItem.displayValue (not (is-bool subItem.displayValue))) ~}} + : {{ subItem.displayValue }} + {{/if}} - {{#if subItem.displayValue }} - ({{ subItem.displayValue }})  - {{/if }} - {{{ getItemValue subItem.score }}} -

+ {{#if subItem.optimalValue }} + (target: {{ subItem.optimalValue }}) + {{/if}} + + {{#if subItem.comingSoon}} + (Coming soon) + {{/if}} + + {{#if subItem.helpText }} + + {{/if}} +

{{#if subItem.helpText }} -
+
{{{ subItem.helpText }}}
{{/if}} + {{#if subItem.debugString }} +
+ {{{ subItem.debugString }}} +
+ {{/if}} + +
+ {{#if subItem.comingSoon}} + N/A + {{else}} + {{#if (is-bool subItem.score)}} + {{#if subItem.score}} + Pass + {{else}} + Fail + {{/if}} + {{else}} + + {{{ getItemValue subItem.score }}} + + {{/if}} + {{/if}} +
+ {{#if subItem.extendedInfo.value}} -
{{> (lookup . 'name') subItem.extendedInfo.value }}
+ {{> (lookup . 'name') subItem.extendedInfo.value }} {{/if}} {{/each}} @@ -137,44 +153,9 @@

{{ aggregation.name }}

{{/each}} - - {{#if this.categorizable}} - - {{/if}} - + {{/each}} +