// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. @import 'feature-callout-theme'; /* stylelint-disable max-nesting-depth */ #feature-callout { // See _feature-callout-theme.scss for the theme mixins and // FeatureCallout.sys.mjs for the color values @include light-theme; position: absolute; z-index: 2147483647; outline: none; color: var(--fc-color); accent-color: var(--fc-accent-color); // Make sure HTML content uses non-native theming, even in chrome windows. -moz-theme: non-native; @media (prefers-color-scheme: dark) { @include dark-theme; } @media (prefers-contrast) { @include hcm-theme; } // Account for feature callouts that may be rendered in the chrome but // displayed on top of content. Each context has its own color scheme, so they // may not match. In that case, we use the special media query below. &.simulateContent { color-scheme: env(-moz-content-preferred-color-scheme); // TODO - replace 2 mixins with a single mixin with light-dark() values. @media (-moz-content-prefers-color-scheme: light) { @include light-theme; } @media (-moz-content-prefers-color-scheme: dark) { @include dark-theme; } @media (prefers-contrast) { @include hcm-theme; } } // The desired width of the arrow (the triangle base). --arrow-width: 33.9411px; // The width/height of the square that, rotated 90deg, will become the arrow. --arrow-square-size: calc(var(--arrow-width) / sqrt(2)); // After rotating, the width is no longer the square width. It's now equal to // the diagonal from corner to corner, i.e. √2 * the square width. We need to // account for this extra width in some calculations. --extra-width-from-rotation: calc(var(--arrow-width) - var(--arrow-square-size)); // The height of the arrow, once rotated and cut in half. --arrow-visible-height: calc(var(--arrow-width) / 2); // Half the width/height of the square. Calculations on the arrow itself need // to treat the arrow as a square, since they are operating on the element // _before_ it is rotated. Calculations on other elements (like the panel // margin that needs to make space for the arrow) should use the visible // height that treats it as a triangle. --arrow-visible-size: calc(var(--arrow-square-size) / 2); --arrow-center-inset: calc(50% - var(--arrow-visible-size)); // Move the arrow 1.5px closer to the callout to account for subpixel rounding // differences, which might cause the corners of the arrow (which is actually // a rotated square) to be visible. --arrow-offset: calc(1.5px - var(--arrow-visible-size)); // For positions like top-end, the arrow is 12px away from the corner. --arrow-corner-distance: 12px; --arrow-corner-inset: calc(var(--arrow-corner-distance) + (var(--extra-width-from-rotation) / 2)); --arrow-overlap-magnitude: 5px; @at-root panel#{&} { --panel-color: var(--fc-color); --panel-shadow: none; // Extra space around the panel for the shadow to be drawn in. The panel // content can't overflow the XUL popup frame, so the frame must be extended. --panel-shadow-margin: 6px; // The panel needs more extra space on the side that the arrow is on, since // the arrow is absolute positioned. This adds the visible height of the // arrow to the margin and subtracts 1 since the arrow is inset by 1.5px // (see --arrow-offset). --panel-arrow-space: calc(var(--panel-shadow-margin) + var(--arrow-visible-height) - 1.5px); // The callout starts with its edge aligned with the edge of the anchor. But // we want the arrow to align to the anchor, not the callout edge. So we need // to offset the callout by the arrow size and margin, as well as the margin // of the entire callout (it has margins on all sides to make room for the // shadow when displayed in a panel, which would normally cut off the shadow). --panel-margin-offset: calc(-1 * (var(--panel-shadow-margin) + var(--arrow-corner-distance) + (var(--arrow-width) / 2))); } @at-root panel#{&}::part(content) { width: initial; border: 0; border-radius: 0; padding: 0; margin: var(--panel-shadow-margin); background: none; color: inherit; // stylelint-disable-next-line declaration-no-important overflow: visible !important; } @at-root div#{&} { transition: opacity 0.5s ease; &.hidden { opacity: 0; pointer-events: none; } } .onboardingContainer, .onboardingContainer .outer-wrapper { // Override the element transitions from aboutwelcome.scss --transition: none; // auto height to allow for arrow positioning based on height height: auto; } // use a different approach to flipping to avoid the fuzzy aliasing that // transform causes. &:dir(rtl) { transform: none; direction: ltr; } & .outer-wrapper:dir(rtl) { transform: none; direction: rtl; } .screen { // override transform in about:welcome &:dir(rtl) { transform: none; } &[pos='callout'] { height: fit-content; min-height: unset; overflow: visible; &[layout='inline'] { .section-main { .main-content, .main-content.no-steps { width: 18em; padding-inline: 16px; padding-block: 0; .welcome-text { // Same height as the dismiss button height: 24px; margin-block: 12px; margin-inline: 0; padding: 0; white-space: nowrap; } } .dismiss-button { height: 24px; width: 24px; min-height: 24px; min-width: 24px; margin: 0; top: calc(50% - 12px); inset-inline-end: 12px; } } } .logo-container { display: flex; justify-content: center; .brand-logo { margin: 0; // This may not work for all future messages, so we may want to make // flipping the logo image in RTL mode configurable &:dir(rtl) { transform: rotateY(180deg); } } } .welcome-text { align-items: baseline; text-align: start; margin: 0; padding: 0; gap: 8px; h1, h2 { font-size: 0.813em; margin: 0; color: inherit; } h1 { font-weight: 600; } .inline-icon-container { display: flex; flex-flow: row wrap; align-items: center; .logo-container { height: 16px; width: 16px; margin-inline-end: 6px; box-sizing: border-box; -moz-context-properties: fill; fill: currentColor; img { height: 16px; width: 16px; margin: 0; } } &[alignment='top'], &[alignment='bottom'] { flex-wrap: nowrap; .logo-container { height: 1.5em; // match the title's line-height align-items: center; padding-bottom: 0.15em; box-sizing: border-box; } } &[alignment='top'] { align-items: start; } &[alignment='bottom'] { align-items: end; } } } .multi-select-container { margin: 0; font-size: 0.813em; row-gap: 12px; color: inherit; overflow: visible; #multi-stage-multi-select-label { font-size: inherit; // There's a 12px gap that pushes the .multi-select-container down // away from the .welcome-text. And there's an 8px gap between the h1 // and h2 in the .welcome-text container. So subtract 4px to get the // desired 8px margin, so spacing is the same as for `subtitle`. margin: -4px 0 0; color: inherit; } } .cta-link { background: none; text-decoration: underline; cursor: pointer; border: none; padding: 0; color: var(--fc-link-color); order: -1; margin-inline-end: auto; margin-block: 8px; &:hover { color: var(--fc-link-color-hover); } &:active { color: var(--fc-link-color-active); } } // Secondary section is not included in callouts .section-secondary { display: none; } .section-main { height: fit-content; width: fit-content; .main-content { position: relative; overflow: hidden; border: 1px solid var(--fc-border); box-shadow: 0 2px 6px rgba(0, 0, 0, 15%); border-radius: 4px; padding: var(--callout-padding, 24px); width: 25em; gap: 16px; background-color: var(--fc-background); .main-content-inner { gap: 12px; } .steps { height: auto; position: absolute; // 24px is the callout's bottom padding. The CTAs are 32px tall, and // the steps are 8px tall. So we need to offset the steps by half // the difference in order to center them. 32/2 - 8/2 = 12. bottom: calc(var(--callout-padding, 24px) + 12px); padding-block: 0; .indicator { // using border will show up in Windows High Contrast Mode to improve accessibility. border: 4px solid var(--fc-step-color); &.current { border-color: var(--fc-accent-color); } } &:not(.progress-bar) { flex-flow: row nowrap; gap: 8px; .indicator { margin: 0; } } & .indicator.current, &.progress-bar .indicator.complete { border-color: var(--fc-accent-color); } } } .dismiss-button { font-size: 1em; inset-block: 0 auto; inset-inline: auto 0; margin-block: 16px 0; margin-inline: 0 16px; background-color: var(--fc-background); &[button-size='small'] { height: 24px; width: 24px; min-height: 24px; min-width: 24px; } } } .action-buttons { display: flex; flex-flow: row nowrap; align-items: stretch; justify-content: end; gap: 10px; // The Figma spec wants a 16px gap between major content blocks and the // action buttons. But the action buttons are siblings with the minor // content blocks, which want a 12px gap. So we use a 12px gap and just // add 4px of margin to the action buttons. margin-top: 4px; &[alignment='start'] { justify-content: start; } &[alignment='space-between'] { justify-content: space-between; } .secondary-cta { font-size: inherit; } .primary, .secondary { padding: 4px 16px; margin: 0; font-size: 0.813em; font-weight: 600; line-height: 16px; min-height: 32px; text-decoration: none; cursor: default; } .secondary { background-color: var(--fc-button-background); } .primary { background-color: var(--fc-primary-button-background); } .split-button-container { align-items: stretch; &:not([hidden]) { display: flex; } .primary, .secondary, .additional-cta { &:not(.submenu-button) { border-start-end-radius: 0; border-end-end-radius: 0; margin-inline-end: 0; } &:focus-visible { z-index: 2; } } .submenu-button { border-start-start-radius: 0; border-end-start-radius: 0; margin-inline-start: 1px; padding: 8px; min-width: 30px; box-sizing: border-box; background-image: url('chrome://global/skin/icons/arrow-down.svg'); background-repeat: no-repeat; background-size: 16px; background-position: center; -moz-context-properties: fill; fill: currentColor; } } } .action-buttons .primary, .action-buttons .secondary, .dismiss-button { border-radius: 4px; &:focus-visible { box-shadow: none; outline: 2px solid var(--fc-accent-color); outline-offset: 2px; } &:disabled { opacity: 0.4; cursor: auto; } } .action-buttons .secondary, .dismiss-button { border: 1px solid var(--fc-button-border); color: var(--fc-button-color); &:hover:not(:disabled), &[open] { background-color: var(--fc-button-background-hover); color: var(--fc-button-color-hover); border: 1px solid var(--fc-button-border-hover); &:active { background-color: var(--fc-button-background-active); color: var(--fc-button-color-active); border: 1px solid var(--fc-button-border-active); } } } .action-buttons .primary { border: 1px solid var(--fc-primary-button-border); color: var(--fc-primary-button-color); &:hover:not(:disabled), &[open] { background-color: var(--fc-primary-button-background-hover); color: var(--fc-primary-button-color-hover); border: 1px solid var(--fc-primary-button-border-hover); &:active { background-color: var(--fc-primary-button-background-active); color: var(--fc-primary-button-color-active); border: 1px solid var(--fc-primary-button-border-active); } } } } } @at-root panel#{&}:is([side='top'], [side='bottom']):not([hide-arrow='permanent']) { margin-inline: var(--panel-margin-offset); } @at-root panel#{&}:is([side='left'], [side='right']):not([hide-arrow='permanent']) { margin-block: var(--panel-margin-offset); } @at-root panel#{&}::part(content) { position: relative; } // all visible callout arrow boxes. boxes are for rotating 45 degrees, arrows // are for the actual arrow shape and are children of the boxes. .arrow-box { position: absolute; overflow: visible; transform: rotate(45deg); // keep the border crisp under transformation transform-style: preserve-3d; } &:not([arrow-position]) .arrow-box, &[hide-arrow] .arrow-box { display: none; } // both shadow arrow and background arrow .arrow { width: var(--arrow-square-size); height: var(--arrow-square-size); } // the arrow's shadow box .shadow-arrow-box { z-index: -1; } // the arrow's shadow .shadow-arrow { background: transparent; outline: 1px solid var(--fc-border); box-shadow: 0 2px 6px rgba(0, 0, 0, 15%); } // the 'filled' arrow box .background-arrow-box { z-index: 1; // the background arrow technically can overlap the dismiss button. it // doesn't visibly overlap it because of the clip-path rule below, but it // can still be clicked. so we need to make sure it doesn't block inputs on // the button. the visible part of the arrow can still catch clicks because // we don't add this rule to .shadow-arrow-box. pointer-events: none; } // the 'filled' arrow .background-arrow { background-color: var(--fc-background); clip-path: var(--fc-arrow-clip-path); } // top (center) arrow positioning &[arrow-position='top'] .arrow-box { top: var(--arrow-offset); inset-inline-start: var(--arrow-center-inset); // the callout arrow is actually a diamond (a rotated square), with the // lower half invisible. the part that appears in front of the callout has // only a background, so that where it overlaps the callout's border, the // border is not visible. the part that appears behind the callout has only // a border/shadow, so that it can't be seen overlapping the callout. but // because the background is the same color as the callout, that half of the // diamond would visibly overlap any callout content that happens to be in // the same place. so we clip it to a triangle, with a 2% extension on the // bottom to account for any subpixel rounding differences. --fc-arrow-clip-path: polygon(100% 0, 100% 2%, 2% 100%, 0 100%, 0 0); } @at-root panel#{&}[arrow-position='top']::part(content) { margin-top: var(--panel-arrow-space); } @at-root panel#{&}[arrow-position='top'] { margin-top: calc(-1 * (var(--panel-shadow-margin) + var(--arrow-overlap-magnitude))); } // bottom (center) arrow positioning &[arrow-position='bottom'] .arrow-box { bottom: var(--arrow-offset); inset-inline-start: var(--arrow-center-inset); --fc-arrow-clip-path: polygon(100% 0, 98% 0, 0 98%, 0 100%, 100% 100%); } @at-root panel#{&}[arrow-position='bottom']::part(content) { margin-bottom: var(--panel-arrow-space); } @at-root panel#{&}[arrow-position='bottom'] { margin-bottom: calc(-1 * (var(--panel-shadow-margin) + var(--arrow-overlap-magnitude))); } // end (center) arrow positioning &[arrow-position='inline-end'] .arrow-box { top: var(--arrow-center-inset); inset-inline-end: var(--arrow-offset); --fc-arrow-clip-path: polygon(100% 0, 100% 100%, 98% 100%, 0 2%, 0 0); } @at-root panel#{&}[arrow-position='inline-end']::part(content) { margin-inline-end: var(--panel-arrow-space); } @at-root panel#{&}[arrow-position='inline-end'] { margin-inline-end: calc(-1 * (var(--panel-shadow-margin) + var(--arrow-overlap-magnitude))); } // start (center) arrow positioning &[arrow-position='inline-start'] .arrow-box { top: var(--arrow-center-inset); inset-inline-start: var(--arrow-offset); --fc-arrow-clip-path: polygon(0 100%, 100% 100%, 100% 98%, 2% 0, 0 0); } @at-root panel#{&}[arrow-position='inline-start']::part(content) { margin-inline-start: var(--panel-arrow-space); } @at-root panel#{&}[arrow-position='inline-start'] { margin-inline-start: calc(-1 * (var(--panel-shadow-margin) + var(--arrow-overlap-magnitude))); } // top-end arrow positioning &[arrow-position='top-end'] .arrow-box { top: var(--arrow-offset); inset-inline-end: var(--arrow-corner-inset); --fc-arrow-clip-path: polygon(100% 0, 100% 2%, 2% 100%, 0 100%, 0 0); } @at-root panel#{&}[arrow-position='top-end']::part(content) { margin-top: var(--panel-arrow-space); } @at-root panel#{&}[arrow-position='top-end'] { margin-top: calc(-1 * (var(--panel-shadow-margin) + var(--arrow-overlap-magnitude))); } // top-start arrow positioning &[arrow-position='top-start'] .arrow-box { top: var(--arrow-offset); inset-inline-start: var(--arrow-corner-inset); --fc-arrow-clip-path: polygon(100% 0, 100% 2%, 2% 100%, 0 100%, 0 0); } @at-root panel#{&}[arrow-position='top-start']::part(content) { margin-top: var(--panel-arrow-space); } @at-root panel#{&}[arrow-position='top-start'] { margin-top: calc(-1 * (var(--panel-shadow-margin) + var(--arrow-overlap-magnitude))); } // bottom-end arrow positioning &[arrow-position='bottom-end'] .arrow-box { bottom: var(--arrow-offset); inset-inline-end: var(--arrow-corner-inset); --fc-arrow-clip-path: polygon(100% 0, 98% 0, 0 98%, 0 100%, 100% 100%); } @at-root panel#{&}[arrow-position='bottom-end']::part(content) { margin-bottom: var(--panel-arrow-space); } @at-root panel#{&}[arrow-position='bottom-end'] { margin-bottom: calc(-1 * (var(--panel-shadow-margin) + var(--arrow-overlap-magnitude))); } // bottom-start arrow positioning &[arrow-position='bottom-start'] .arrow-box { bottom: var(--arrow-offset); inset-inline-start: var(--arrow-corner-inset); --fc-arrow-clip-path: polygon(100% 0, 98% 0, 0 98%, 0 100%, 100% 100%); } @at-root panel#{&}[arrow-position='bottom-start']::part(content) { margin-bottom: var(--panel-arrow-space); } @at-root panel#{&}[arrow-position='bottom-start'] { margin-bottom: calc(-1 * (var(--panel-shadow-margin) + var(--arrow-overlap-magnitude))); } // inline-end-top arrow positioning &[arrow-position='inline-end-top'] .arrow-box { top: var(--arrow-corner-inset); inset-inline-end: var(--arrow-offset); --fc-arrow-clip-path: polygon(100% 0, 100% 100%, 98% 100%, 0 2%, 0 0); } @at-root panel#{&}[arrow-position='inline-end-top']::part(content) { margin-inline-end: var(--panel-arrow-space); } @at-root panel#{&}[arrow-position='inline-end-top'] { margin-inline-end: calc(-1 * (var(--panel-shadow-margin) + var(--arrow-overlap-magnitude))); } // inline-end-bottom arrow positioning &[arrow-position='inline-end-bottom'] .arrow-box { bottom: var(--arrow-corner-inset); inset-inline-end: var(--arrow-offset); --fc-arrow-clip-path: polygon(100% 0, 100% 100%, 98% 100%, 0 2%, 0 0); } @at-root panel#{&}[arrow-position='inline-end-bottom']::part(content) { margin-inline-end: var(--panel-arrow-space); } @at-root panel#{&}[arrow-position='inline-end-bottom'] { margin-inline-end: calc(-1 * (var(--panel-shadow-margin) + var(--arrow-overlap-magnitude))); } // inline-start-top arrow positioning &[arrow-position='inline-start-top'] .arrow-box { top: var(--arrow-corner-inset); inset-inline-start: var(--arrow-offset); --fc-arrow-clip-path: polygon(0 100%, 100% 100%, 100% 98%, 2% 0, 0 0); } @at-root panel#{&}[arrow-position='inline-start-top']::part(content) { margin-inline-start: var(--panel-arrow-space); } @at-root panel#{&}[arrow-position='inline-start-top'] { margin-inline-start: calc(-1 * (var(--panel-shadow-margin) + var(--arrow-overlap-magnitude))); } // inline-start-bottom arrow positioning &[arrow-position='inline-start-bottom'] .arrow-box { bottom: var(--arrow-corner-inset); inset-inline-start: var(--arrow-offset); --fc-arrow-clip-path: polygon(0 100%, 100% 100%, 100% 98%, 2% 0, 0 0); } @at-root panel#{&}[arrow-position='inline-start-bottom']::part(content) { margin-inline-start: var(--panel-arrow-space); } @at-root panel#{&}[arrow-position='inline-start-bottom'] { margin-inline-start: calc(-1 * (var(--panel-shadow-margin) + var(--arrow-overlap-magnitude))); } // focus outline for the callout itself &:focus-visible { .screen { &[pos='callout'] { .section-main .main-content { outline: 2px solid var(--fc-accent-color); border-color: transparent; @media (prefers-contrast) { border-color: var(--fc-background); } } } } .shadow-arrow { outline: 2px solid var(--fc-accent-color); } } }