diff --git a/browser/base/content/browser.xhtml b/browser/base/content/browser.xhtml index 351b28291bf1..e31e82a8f8ed 100644 --- a/browser/base/content/browser.xhtml +++ b/browser/base/content/browser.xhtml @@ -100,6 +100,7 @@ + diff --git a/browser/components/tabbrowser/SmartTabGrouping.sys.mjs b/browser/components/tabbrowser/SmartTabGrouping.sys.mjs index da8c91bd330f..f8a7421c6b60 100644 --- a/browser/components/tabbrowser/SmartTabGrouping.sys.mjs +++ b/browser/components/tabbrowser/SmartTabGrouping.sys.mjs @@ -252,7 +252,7 @@ export class SmartTabGroupingManager { * This function will terminate a grouping or label generation in progress * It is currently not implemented. */ - terminateProcesses() { + terminateProcess() { // TODO - teminate AI processes, This method will be // called when tab grouping panel is closed. } @@ -382,13 +382,11 @@ export class SmartTabGroupingManager { ) { this.embeddingEngine = await this._createMLEngine(this.config.embedding); } - const result = []; - for (let runArg of inputData.inputArgs) { - const request = { args: [[runArg]], options: inputData.runOptions }; - let embeddingsPerTab = await this.embeddingEngine.run(request); - result.push(Object.values(embeddingsPerTab.ort_tensor.cpuData)); - } - return result; + const request = { + args: [inputData.inputArgs], + options: inputData.runOptions, + }; + return await this.embeddingEngine.run(request); } /** diff --git a/browser/components/tabbrowser/content/tabbrowser.js b/browser/components/tabbrowser/content/tabbrowser.js index 49e8180ac1e4..5d136fa732dc 100644 --- a/browser/components/tabbrowser/content/tabbrowser.js +++ b/browser/components/tabbrowser/content/tabbrowser.js @@ -99,6 +99,7 @@ this.tabContainer = document.getElementById("tabbrowser-tabs"); this.tabGroupMenu = document.getElementById("tab-group-editor"); this.tabbox = document.getElementById("tabbrowser-tabbox"); + this.tabGroupNameField = document.getElementById("tab-group-name"); this.tabpanels = document.getElementById("tabbrowser-tabpanels"); this.verticalPinnedTabsContainer = document.getElementById( "vertical-pinned-tabs-container" @@ -2948,6 +2949,13 @@ return null; } + gBrowser.getGroupTitleForTabs(tabs).then(newLabel => { + group.label = newLabel; + if (this.tabGroupMenu.panel.state !== "closed") { + this.tabGroupNameField.value = newLabel; + } + }); + group.dispatchEvent( new CustomEvent("TabGroupCreate", { bubbles: true, diff --git a/browser/components/tabbrowser/content/tabgroup-menu.js b/browser/components/tabbrowser/content/tabgroup-menu.js index 7c435ddb5144..570244e0d3bc 100644 --- a/browser/components/tabbrowser/content/tabgroup-menu.js +++ b/browser/components/tabbrowser/content/tabgroup-menu.js @@ -30,15 +30,8 @@ "gray", "red", ]; - static markup = ` - + static headerSection = /*html*/ ` + `; - - - - - - - - - - - - - - - - - - - + static editActions = /*html*/ ` - + @@ -129,7 +85,163 @@ data-l10n-id="tab-group-editor-action-delete"> + `; + static suggestionsHeader = /*html*/ ` + + `; + + static suggestionsSection = /*html*/ ` + + `; + + static suggestionsButton = /*html*/ ` + + `; + + static loadingSection = /*html*/ ` + + `; + + static defaultActions = /*html*/ ` + + + + + + + `; + + static loadingActions = /*html*/ ` + + `; + + static markup = /*html*/ ` + + + + ${this.headerSection} + + + + + + + + + + + + + + ${this.editActions} + + + + ${this.suggestionsHeader} + ${this.loadingSection} + ${this.loadingActions} + ${this.suggestionsSection} + `; @@ -158,6 +270,7 @@ ERROR: 11, }; + #tabGroupMain; #activeGroup; #cancelButton; #createButton; @@ -167,7 +280,25 @@ #panel; #swatches; #swatchesContainer; + #defaultActions; #suggestionState = MozTabbrowserTabGroupMenu.State.CREATE_STANDARD_INITIAL; + #suggestionsHeading; + #suggestionsHeader; + #suggestionsContainer; + #suggestions; + #suggestionButton; + #cancelSuggestionsButton; + #createSuggestionsButton; + #suggestionsLoading; + #selectSuggestionsToggle; + #suggestionsMessage; + #suggestionsDisclaimer; + #selectedSuggestedTabs = []; + #suggestedTabs = []; + #suggestionsLoadActions; + #suggestionsLoadCancel; + #suggestionsSeparator; + #smartTabGroupingManager; constructor() { super(); @@ -195,6 +326,11 @@ this.#swatchesContainer = this.querySelector( ".tab-group-editor-swatches" ); + + this.#defaultActions = this.querySelector("#tab-group-default-actions"); + this.#tabGroupMain = this.querySelector("#tab-group-main"); + this.#initSuggestions(); + this.#populateSwatches(); this.#cancelButton.addEventListener("click", () => { @@ -211,6 +347,22 @@ } }); + /** + * Check if the smart suggest button should be shown + * If there are no ungrouped tabs, the button should be hidden + */ + this.canShowAIUserInterface = () => { + const { tabs } = gBrowser; + let show = false; + tabs.forEach(tab => { + if (tab.group === null) { + show = true; + } + }); + + return show; + }; + document .getElementById("tabGroupEditor_addNewTabInGroup") .addEventListener("command", () => { @@ -247,6 +399,78 @@ this.#swatchesContainer.addEventListener("change", this); } + #initSuggestions() { + const AI_ICON = "chrome://global/skin/icons/highlights.svg"; + const { SmartTabGroupingManager } = ChromeUtils.importESModule( + "resource:///modules/SmartTabGrouping.sys.mjs" + ); + this.#smartTabGroupingManager = new SmartTabGroupingManager(); + + // Init Suggestion Button + this.#suggestionButton = this.querySelector( + "#tab-group-suggestion-button" + ); + this.#suggestionButton.iconSrc = AI_ICON; + this.#suggestionButton.addEventListener("click", () => { + this.#handleSmartSuggest(); + }); + + // Init Suggestions UI + this.#suggestionsHeading = this.querySelector( + "#tab-group-suggestions-heading" + ); + this.#suggestionsHeader = this.querySelector( + "#tab-group-suggestions-header" + ); + this.#suggestionsContainer = this.querySelector( + "#tab-group-suggestions-container" + ); + this.#suggestions = this.querySelector("#tab-group-suggestions"); + this.#selectSuggestionsToggle = this.querySelector( + "#tab-group-select-toggle" + ); + this.#selectSuggestionsToggle.addEventListener("click", () => { + this.#handleSelectToggle(); + }); + this.#suggestionsMessage = this.querySelector( + "#tab-group-suggestions-message" + ); + this.#suggestionsMessage.iconSrc = AI_ICON; + this.#suggestionsDisclaimer = this.querySelector( + "#tab-group-suggestions-disclaimer" + ); + this.#createSuggestionsButton = this.querySelector( + "#tab-group-create-suggestions-button" + ); + this.#createSuggestionsButton.addEventListener("click", () => { + this.activeGroup.addTabs(this.#selectedSuggestedTabs); + this.close(true); + }); + this.#cancelSuggestionsButton = this.querySelector( + "#tab-group-cancel-suggestions-button" + ); + this.#cancelSuggestionsButton.addEventListener("click", () => { + this.close(); + }); + this.#suggestionsSeparator = this.querySelector( + "#tab-group-suggestions-separator" + ); + + // Init Loading UI + this.#suggestionsLoading = this.querySelector( + "#tab-group-suggestions-loading" + ); + this.#suggestionsLoadActions = this.querySelector( + "#tab-group-suggestions-load-actions" + ); + this.#suggestionsLoadCancel = this.querySelector( + "#tab-group-suggestions-load-cancel" + ); + this.#suggestionsLoadCancel.addEventListener("click", () => { + this.#handleLoadSuggestionsCancel(); + }); + } + #populateSwatches() { this.#clearSwatches(); for (let colorCode of MozTabbrowserTabGroupMenu.COLORS) { @@ -348,11 +572,10 @@ openCreateModal(group) { this.activeGroup = group; this.createMode = true; - this.suggestionState = - MozTabbrowserTabGroupMenu.State.CREATE_STANDARD_INITIAL; - if (lazy.smartTabGroupsEnabled) { - //TODO: set appropriate state - } + this.suggestionState = lazy.smartTabGroupsEnabled + ? MozTabbrowserTabGroupMenu.State.CREATE_AI_INITIAL + : MozTabbrowserTabGroupMenu.State.CREATE_STANDARD_INITIAL; + this.#panel.openPopup(group.firstChild, { position: this.#panelPosition, }); @@ -361,11 +584,10 @@ openEditModal(group) { this.activeGroup = group; this.createMode = false; - this.suggestionState = - MozTabbrowserTabGroupMenu.State.EDIT_STANDARD_INITIAL; - if (lazy.smartTabGroupsEnabled) { - //TODO: set appropriate state - } + this.suggestionState = lazy.smartTabGroupsEnabled + ? MozTabbrowserTabGroupMenu.State.EDIT_AI_INITIAL + : MozTabbrowserTabGroupMenu.State.EDIT_STANDARD_INITIAL; + this.#panel.openPopup(group.firstChild, { position: this.#panelPosition, }); @@ -419,6 +641,7 @@ } } this.activeGroup = null; + this.#smartTabGroupingManager.terminateProcess(); } on_keypress(event) { @@ -487,8 +710,192 @@ this.#renderSuggestionState(); } + #handleLoadSuggestionsCancel() { + // TODO look into actually canceling any processes + this.suggestionState = this.createMode + ? MozTabbrowserTabGroupMenu.State.CREATE_AI_INITIAL + : MozTabbrowserTabGroupMenu.State.EDIT_AI_INITIAL; + } + + #handleSelectToggle() { + const currentState = + this.#selectSuggestionsToggle.getAttribute("data-state"); + const isDeselect = currentState === "deselect"; + + isDeselect ? this.#handleDeselectAll() : this.#handleSelectAll(); + const newState = isDeselect ? "select" : "deselect"; + this.#setSelectToggleState(newState); + } + + #handleSelectAll() { + document + .querySelectorAll(".tab-group-suggestion-checkbox") + .forEach(checkbox => { + checkbox.checked = true; + }); + // Reset selected tabs to all suggested tabs + this.#selectedSuggestedTabs = this.#suggestedTabs; + } + + #handleDeselectAll() { + document + .querySelectorAll(".tab-group-suggestion-checkbox") + .forEach(checkbox => { + checkbox.checked = false; + }); + this.#selectedSuggestedTabs = []; + } + + async #handleSmartSuggest() { + // Loading + this.suggestionState = MozTabbrowserTabGroupMenu.State.LOADING; + const tabs = await this.#smartTabGroupingManager.smartTabGroupingForGroup( + this.activeGroup, + gBrowser.tabs + ); + + if (!tabs.length) { + // No un-grouped tabs found + this.suggestionState = this.#createMode + ? MozTabbrowserTabGroupMenu.State.CREATE_AI_WITH_NO_SUGGESTIONS + : MozTabbrowserTabGroupMenu.State.EDIT_AI_WITH_NO_SUGGESTIONS; + return; + } + + this.#selectedSuggestedTabs = tabs; + this.#suggestedTabs = tabs; + tabs.forEach((tab, index) => { + this.#createRow(tab, index); + }); + + this.suggestionState = this.#createMode + ? MozTabbrowserTabGroupMenu.State.CREATE_AI_WITH_SUGGESTIONS + : MozTabbrowserTabGroupMenu.State.EDIT_AI_WITH_SUGGESTIONS; + } + + #createRow(tab, index) { + // Create Row + let row = document.createXULElement("toolbaritem"); + row.setAttribute("context", "tabContextMenu"); + row.setAttribute("id", `tab-bar-${index}`); + + // Create Checkbox + let checkbox = document.createXULElement("checkbox"); + checkbox.value = tab; + checkbox.setAttribute("checked", true); + checkbox.classList.add("tab-group-suggestion-checkbox"); + checkbox.addEventListener("CheckboxStateChange", e => { + const isChecked = e.target.checked; + const currentTab = e.target.value; + + if (isChecked) { + this.#selectedSuggestedTabs.push(currentTab); + } else { + this.#selectedSuggestedTabs = this.#selectedSuggestedTabs.filter( + t => t != currentTab + ); + } + }); + + row.appendChild(checkbox); + + // Create Row Label + let label = document.createXULElement("toolbarbutton"); + label.classList.add( + "all-tabs-button", + "subviewbutton", + "subviewbutton-iconic", + "tab-group-suggestion-label" + ); + label.setAttribute("flex", "1"); + label.setAttribute("crop", "end"); + label.label = tab.label; + label.image = tab.image; + label.disabled = true; + row.appendChild(label); + + // Apply Row to Suggestions + this.#suggestions.appendChild(row); + } + + /** + * Set the state of the select toggle button + * @param {string} "select" | "deselect" + */ + #setSelectToggleState(state) { + this.#selectSuggestionsToggle.setAttribute("data-state", state); + this.#selectSuggestionsToggle.setAttribute( + "data-l10n-id", + `tab-group-editor-${state}-suggestions` + ); + } + + /** + * Element visibility utility function. + * Toggles the `hidden` attribute of a DOM element. + * + * @param {HTMLElement|XULElement} element - The DOM element to show/hide. + * @param {boolean} shouldShow - Whether the element should be shown (true) or hidden (false). + */ + #setElementVisibility(element, shouldShow) { + element.hidden = !shouldShow; + } + + #showDefaultTabGroupActions(value) { + this.#setElementVisibility(this.#defaultActions, value); + } + + #showSmartSuggestionsContainer(value) { + this.#setElementVisibility(this.#suggestionsContainer, value); + } + + #showSuggestionButton(value) { + this.#setElementVisibility(this.#suggestionButton, value); + } + + #showSuggestionMessage(value) { + this.#setElementVisibility(this.#suggestionsMessage, value); + } + + #showSuggestionsDisclaimer(value) { + this.#setElementVisibility(this.#suggestionsDisclaimer, value); + } + + #showSuggestionsSeparator(value) { + this.#setElementVisibility(this.#suggestionsSeparator, value); + } + + #setLoadingState(value) { + this.#setElementVisibility(this.#suggestionsLoadActions, value); + this.#setElementVisibility(this.#suggestionsLoading, value); + } + + #setSuggestionsButtonCreateModeState(value) { + const translationString = value + ? "tab-group-editor-smart-suggest-button-create" + : "tab-group-editor-smart-suggest-button-edit"; + + this.#suggestionButton.setAttribute("data-l10n-id", translationString); + } + + /** + * Unique state setter for a "3rd" panel state while in Edit Mode + * that just shows suggestions and hides the majority of the panel + * @param {boolean} value + */ + #setEditModeSuggestionState(value) { + this.#setElementVisibility(this.#suggestionsHeader, !value); + this.#setElementVisibility(this.#tabGroupMain, !value); + this.#setElementVisibility(this.#suggestionsHeading, value); + } + #resetCommonUI() { - // TODO - has commun UI reset logic + this.#setLoadingState(false); + this.#setEditModeSuggestionState(false); + this.#suggestedTabs = []; + this.#selectedSuggestedTabs = []; + this.#suggestions.innerHTML = ""; + this.#showSmartSuggestionsContainer(false); } #renderSuggestionState() { @@ -496,64 +903,111 @@ // CREATE STANDARD INITIAL case MozTabbrowserTabGroupMenu.State.CREATE_STANDARD_INITIAL: this.#resetCommonUI(); - //TODO + this.#showDefaultTabGroupActions(true); + this.#showSuggestionButton(false); + this.#showSuggestionMessage(false); + this.#showSuggestionsDisclaimer(false); + this.#showSuggestionsSeparator(false); break; //CREATE AI INITIAL case MozTabbrowserTabGroupMenu.State.CREATE_AI_INITIAL: this.#resetCommonUI(); - //TODO + this.#showSuggestionButton(true); + this.#showDefaultTabGroupActions(true); + this.#showSuggestionMessage(false); + this.#setSelectToggleState("deselect"); + this.#showSuggestionsDisclaimer(true); + this.#setSuggestionsButtonCreateModeState(true); + this.#showSuggestionsSeparator(true); break; // CREATE AI INITIAL SUGGESTIONS DISABLED case MozTabbrowserTabGroupMenu.State .CREATE_AI_INITIAL_SUGGESTIONS_DISABLED: this.#resetCommonUI(); - //TODO + this.#showSuggestionButton(false); + this.#showSuggestionsDisclaimer(false); + this.#showSuggestionMessage(true); + this.#showDefaultTabGroupActions(true); + this.#showSuggestionsSeparator(true); break; // CREATE AI WITH SUGGESTIONS case MozTabbrowserTabGroupMenu.State.CREATE_AI_WITH_SUGGESTIONS: - //TODO + this.#setLoadingState(false); + this.#showSmartSuggestionsContainer(true); + this.#showSuggestionButton(false); + this.#showSuggestionsSeparator(true); + this.$showDefaultTabGroupActions(false); break; // CREATE AI WITH NO SUGGESTIONS case MozTabbrowserTabGroupMenu.State.CREATE_AI_WITH_NO_SUGGESTIONS: - //TODO + this.#setLoadingState(false); + this.#showSuggestionMessage(true); + this.#showDefaultTabGroupActions(true); + this.#showSuggestionButton(false); + this.#showSuggestionsSeparator(true); break; // EDIT STANDARD INITIAL case MozTabbrowserTabGroupMenu.State.EDIT_STANDARD_INITIAL: this.#resetCommonUI(); - //TODO + this.#showSuggestionButton(false); + this.#showSuggestionMessage(false); + this.#showDefaultTabGroupActions(false); + this.#showSuggestionsDisclaimer(false); + this.#showSuggestionsSeparator(false); break; // EDIT AI INITIAL case MozTabbrowserTabGroupMenu.State.EDIT_AI_INITIAL: this.#resetCommonUI(); - //TODO + this.#showSuggestionMessage(false); + this.#setSelectToggleState("deselect"); + this.#showSuggestionButton(true); + this.#showDefaultTabGroupActions(false); + this.#showSuggestionsDisclaimer(false); + this.#setSuggestionsButtonCreateModeState(false); + this.#showSuggestionsSeparator(true); break; // EDIT AI INITIAL SUGGESTIONS DISABLED case MozTabbrowserTabGroupMenu.State .EDIT_AI_INITIAL_SUGGESTIONS_DISABLED: this.#resetCommonUI(); - //TODO + this.#showSuggestionMessage(true); + this.#showSuggestionButton(false); + this.#showDefaultTabGroupActions(false); + this.#showSuggestionsDisclaimer(false); + this.#showSuggestionsSeparator(true); break; // EDIT AI WITH SUGGESTIONS case MozTabbrowserTabGroupMenu.State.EDIT_AI_WITH_SUGGESTIONS: - //TODO + this.#setLoadingState(false); + this.#showSmartSuggestionsContainer(true); + this.#setEditModeSuggestionState(true); + this.#showSuggestionsSeparator(false); break; // EDIT AI WITH NO SUGGESTIONS case MozTabbrowserTabGroupMenu.State.EDIT_AI_WITH_NO_SUGGESTIONS: - //TODO + this.#setLoadingState(false); + this.#showSuggestionMessage(true); + this.#showSuggestionsSeparator(true); break; // LOADING case MozTabbrowserTabGroupMenu.State.LOADING: - //TODO + this.#showDefaultTabGroupActions(false); + this.#showSuggestionButton(false); + this.#showSuggestionsDisclaimer(false); + this.#showSuggestionMessage(false); + this.#setLoadingState(true); + this.#showSuggestionsSeparator(true); + this.#showDefaultTabGroupActions(false); break; // ERROR diff --git a/browser/locales-preview/smartTabGroups.ftl b/browser/locales-preview/smartTabGroups.ftl new file mode 100644 index 000000000000..da4e202612f4 --- /dev/null +++ b/browser/locales-preview/smartTabGroups.ftl @@ -0,0 +1,28 @@ +# 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/. + +tab-group-editor-title-suggest = Suggested tabs + +tab-group-editor-smart-suggest-button-edit= + .label = Suggest more tabs for group + +# Button appearing during creation of new tab group. +# Clicking will suggest tabs to group together in the tab group. +tab-group-editor-smart-suggest-button-create= + .label = Suggest tabs to group + +tab-group-editor-suggestions-header = Suggested Tabs + +tab-group-suggestions-loading-header = Suggested Tabs + +tab-group-editor-deselect-suggestions = + .label = Deselect all + +tab-group-editor-select-suggestions = + .label = Select all + +tab-group-editor-no-tabs-found = + .label = As you open similar tabs, { -brand-short-name } will suggest them for this group. + +tab-group-suggestions-disclaimer = { -brand-short-name } uses AI to suggest tabs and group names. Some suggestions may be inaccurate. diff --git a/browser/locales/jar.mn b/browser/locales/jar.mn index 4eebca6ddd1e..7de950268c81 100644 --- a/browser/locales/jar.mn +++ b/browser/locales/jar.mn @@ -17,6 +17,7 @@ preview/credentialChooser.ftl (../../toolkit/components/credentialmanagement/credentialChooser.ftl) browser (%browser/**/*.ftl) preview/backupSettings.ftl (../locales-preview/backupSettings.ftl) + preview/smartTabGroups.ftl (../locales-preview/smartTabGroups.ftl) preview/tabUnload.ftl (../locales-preview/tabUnload.ftl) @AB_CD@.jar: diff --git a/browser/themes/shared/tabbrowser/tabs.css b/browser/themes/shared/tabbrowser/tabs.css index a68a2ba03193..4d828b4bb6c5 100644 --- a/browser/themes/shared/tabbrowser/tabs.css +++ b/browser/themes/shared/tabbrowser/tabs.css @@ -6,6 +6,8 @@ --tabstrip-inner-border: 1px solid color-mix(in srgb, currentColor 25%, transparent); --tabstrip-min-height: calc(var(--tab-min-height) + 2 * var(--tab-block-margin)); --tab-min-height: 36px; + --tab-group-suggestions-loading-animation-color-1: color-mix(in srgb, currentColor 5%, transparent); + --tab-group-suggestions-loading-animation-color-2: color-mix(in srgb, currentColor 35%, transparent); &[uidensity=compact] { --tab-min-height: 29px; } @@ -1690,3 +1692,110 @@ toolbar:not(#TabsToolbar) #firefox-view-button { fill: transparent; stroke: light-dark(var(--tab-group-color), var(--tab-group-color-invert)); } + +.tab-group-suggestion-label { + --text-color-disabled: var(--panel-color); + --button-opacity-disabled: 1; +} + +#tab-group-suggestions-heading, +#tab-group-main { + flex-direction: column; +} + +#tab-group-default-actions, +#tab-group-suggestions-load-actions, +#tab-group-suggestion-button, +#tab-group-suggestions-message { + display: none; +} + +#tab-group-suggestions-heading:not([hidden]), +#tab-group-main:not([hidden]), +#tab-group-suggestions-separator:not([hidden]), +#tab-group-suggestions-load-actions:not([hidden]), +#tab-group-suggestions-loading:not([hidden]), +#tab-group-default-actions:not([hidden]), +#tab-group-suggestion-button:not([hidden]), +#tab-group-suggestions-message:not([hidden]) { + display: flex; +} + +#tab-group-suggestions-container:not([hidden]) { + display: block; +} + +#tab-group-suggestions-disclaimer > a { + display: inline; + color: inherit; +} + +#tab-group-suggestions-disclaimer:not([hidden]) { + display: initial; +} + +#tab-group-select-toggle { + position: relative; + margin-top: 0.5em; +} + +#tab-group-suggestion-button { + color: var(--color-accent-primary); + --button-font-weight: var(--font-weight-normal); +} + +#tab-group-suggestions-loading { + gap: 4px; + flex-direction: column; + margin: 0 8px 8px; +} + +#tab-group-suggestions-message { + font-size: 0.85em; + --button-font-weight: var(--font-weight-normal); +} + +.tab-group-suggestions-loading-header, +#tab-group-suggestions-header { + margin: 8px 0; +} + +@keyframes tab-group-loading-block-animation { + 0% { + background-position: 200% 0; + } + + 50% { + background-position: 0 0; + } + + 100% { + background-position: -200% 0; + } +} + +.tab-group-suggestions-loading-block:nth-of-type(2) { + animation-delay: 0.1s; +} +.tab-group-suggestions-loading-block:nth-of-type(3) { + animation-delay: 0.2s; +} +.tab-group-suggestions-loading-block:nth-of-type(4) { + animation-delay: 0.3s; +} + +.tab-group-suggestions-loading-block { + animation: tab-group-loading-block-animation 3s infinite; + background: linear-gradient(100deg, + color-mix(in srgb, var(--tab-group-suggestions-loading-animation-color-2), transparent 60%) 30%, + var(--tab-group-suggestions-loading-animation-color-1) 50%, + color-mix(in srgb, var(--tab-group-suggestions-loading-animation-color-2), transparent 60%) 70%); + background-size: 200% 100%; + border-radius: var(--border-radius-small); + height: 1.5em; + margin: 0; + margin-bottom: 0.5em; + padding: 0; + white-space: nowrap; + width: 100%; +}