Source code
Revision control
Copy as Markdown
Other Tools
// 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
@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;
-moz-window-shadow: none;
@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;
}
@at-root :root[lwt-newtab-brighttext] #{&}.lwtNewtab {
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.
transform: none;
}
.screen {
// override the RTL 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: start;
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: 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: transparent;
&[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);
}
}
}
.action-checklist {
display: flex;
flex-direction: column;
.action-checklist-subtitle {
text-align: start;
margin-block-end: 8px;
}
.action-checklist-divider {
margin-block: 0;
width: 100%;
color: var(--fc-button-background);
}
.action-checklist-progress-bar {
width: 100%;
height: 6px;
padding-block: 0;
background-color: color-mix(in srgb, var(--fc-color) 25%, transparent);
opacity: 1;
transition: none;
border-radius: 8px;
display: flex;
justify-content: flex-start;
margin-block-end: 16px;
&:dir(rtl) {
justify-content: flex-end;
}
.indicator {
width: var(--action-checklist-progress-bar-progress);
height: 100%;
background-color: var(--fc-accent-color);
border: 0;
border-radius: 8px;
opacity: 1;
transition: var(--progress-bar-transition);
}
}
.action-checklist-items {
display: flex;
flex-direction: column;
button {
&:first-child {
margin-block-start: 0;
}
&:last-child {
margin-block-end: 0;
}
// TODO: This is (very) likely to change
&:not(:disabled):hover {
background-color: var(--fc-button-background-hover);
}
&:disabled {
color: var(--fc-color);
}
.action-checklist-label-container {
display: flex;
align-items: center;
span {
text-align: start;
}
.check-icon-container {
margin-block-start: 2px;
}
.check-empty, .check-filled {
margin-inline-end: 8px;
flex-shrink: 0;
}
.check-empty {
border-radius: 50%;
width: 0.75em;
height: 0.75em;
border: 2px solid color-mix(in srgb, var(--fc-color) 25%, transparent);
}
.check-filled {
background-image: url('chrome://global/skin/icons/check-filled.svg');
fill: var(--fc-icon-success-color);
}
}
.external-link-icon, .check-filled {
border: none;
width: 1em;
height: 1em;
background-repeat: no-repeat;
background-position: center;
background-size: contain;
-moz-context-properties: fill;
}
.external-link-icon-container {
.external-link-icon {
background-image: url('chrome://global/skin/icons/open-in-new.svg');
fill: var(--fc-color);
}
}
background: none;
display: flex;
justify-content: space-between;
align-items: center;
margin-inline: 0;
margin-block: 4px;
border: none;
border-radius: 4px;
padding-block: 8px;
}
}
}
}
}
@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: 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)));
}
}