Update to v2.0

pull/131/head
Tom 2023-11-24 06:59:14 +01:00
parent 1a66e154a1
commit 51f7db16cf
7 changed files with 302 additions and 51 deletions

3
.gitignore vendored
View File

@ -1,2 +1,3 @@
__pycache__
.vscode
.vscode
running

View File

@ -23,6 +23,10 @@
* Assign tags by scanning all installed models for automatic use in image generation.
<h3>Quick Model Info Access 📊</h3>
* A button for each model card in txt2img and img2img to load it into the extension.
<h3>High-speed downloads with Aria2 🚄</h3>
* Maximize your bandwidth for lightning-fast downloads.
@ -86,14 +90,29 @@ These settings can be found under the "Settings" tab in Web-UI and then under th
# Preview 👀
https://github.com/BlafKing/sd-civitai-browser/assets/9644716/ea873c3e-a7e4-44a8-907a-e9bddf13bc55
https://github.com/BlafKing/sd-civitai-browser-plus/assets/9644716/44c5c7a0-4854-4043-bfbb-f32fa9df5a74
(Theme used: [Lobe](https://github.com/canisminor1990/sd-webui-lobe-theme))
# Changelog 📋
<h3>v2.0</h3>
* Feature: New button on each model card in txt2img and img2img to view it in the extension.
<details>
<summary> Preview</summary>
![ezgif-3-b1f0de4dd2](https://github.com/BlafKing/sd-civitai-browser-plus/assets/9644716/536a693a-c30c-438e-a34f-1aec54e4e7ee)
</details>
* Feature: Ability to set [\Model Name] & [\Model Name\Version Name] as default sub folders.
* New setting: Hide sub folders that start with a '.'
* Bug fix: Preview HTML is now emptied when loading a new page.
* Bug fix: Buttons now correctly display when loading new page.
* Bug fix: Fixed compatibility with SD.Next. (again)
* Bug fix: Emptied tags, base model, and filename upon loading new page.
---
<h3>v1.16</h3>
* Feature: Ability to download/update model preview images in Update Models tab.

View File

@ -1,17 +1,23 @@
"use strict";
// Selects a model by pressing on card
function select_model(model_name) {
var civitaiDiv = document.getElementById('civitai_preview_html');
let model_dropdown = gradioApp().querySelector('#eventtext1 textarea');
if (model_dropdown && model_name) {
let randomNumber = Math.floor(Math.random() * 1000);
let paddedNumber = String(randomNumber).padStart(3, '0');
model_dropdown.value = model_name + "." + paddedNumber;
updateInput(model_dropdown);
observer.unobserve(civitaiDiv);
observer.observe(civitaiDiv);
}
function select_model(model_name, bool = false, content_type = null) {
const output = bool ? gradioApp().querySelector('#model_sent textarea') : gradioApp().querySelector('#model_select textarea');
if (output && model_name) {
const randomNumber = Math.floor(Math.random() * 1000);
const paddedNumber = String(randomNumber).padStart(3, '0');
output.value = model_name + "." + paddedNumber;
updateInput(output);
}
if (content_type) {
const outputType = gradioApp().querySelector('#type_sent textarea');
const randomNumber = Math.floor(Math.random() * 1000);
const paddedNumber = String(randomNumber).padStart(3, '0');
outputType.value = content_type + "." + paddedNumber;
updateInput(outputType);
}
}
// Changes the card size
@ -341,9 +347,7 @@ function handleCivitaiDivChanges() {
document.addEventListener("scroll", handleCivitaiDivChanges)
// Create the accordion dropdown inside the settings tab
function createAccordion() {
var containerDiv = document.querySelector("#settings_civitai_browser_plus > div > div");
var subfolders = containerDiv.querySelectorAll("[id$='subfolder']");
function createAccordion(containerDiv, subfolders, name) {
if (containerDiv == null || subfolders.length == 0) {
return;
}
@ -351,7 +355,7 @@ function createAccordion() {
accordionContainer.id = 'settings-accordion';
var toggleButton = document.createElement('button');
toggleButton.id = 'accordionToggle';
toggleButton.innerHTML = 'Default sub folders<div style="transition: transform 0.15s; transform: rotate(90deg)">▼</div>';
toggleButton.innerHTML = name + '<div style="transition: transform 0.15s; transform: rotate(90deg)">▼</div>';
toggleButton.onclick = function () {
accordionDiv.style.display = (accordionDiv.style.display === 'none') ? 'block' : 'none';
toggleButton.lastChild.style.transform = accordionDiv.style.display === 'none' ? 'rotate(90deg)' : 'rotate(0)';
@ -366,6 +370,122 @@ function createAccordion() {
containerDiv.appendChild(accordionContainer);
}
// Adds a button to the cards in txt2img and img2img
function createCardButtons(event) {
const clickedElement = event.target;
const validButtonNames = ['Textual Inversion', 'Hypernetworks', 'Checkpoints', 'Lora'];
const validParentIds = ['txt2img_textual_inversion_cards_html', 'txt2img_hypernetworks_cards_html', 'txt2img_checkpoints_cards_html', 'txt2img_lora_cards_html'];
const hasMatchingButtonName = validButtonNames.some(buttonName =>
clickedElement.innerText.trim() === buttonName
);
const flexboxDivs = document.querySelectorAll('.layoutkit-flexbox');
let isLobeTheme = false;
flexboxDivs.forEach(div => {
const anchorElements = div.querySelectorAll('a');
const hasGitHubLink = Array.from(anchorElements).some(anchor => anchor.href === 'https://github.com/lobehub/sd-webui-lobe-theme/releases');
if (hasGitHubLink) {
isLobeTheme = true;
}
});
if (hasMatchingButtonName || isLobeTheme) {
const checkForCardDivs = setInterval(() => {
const cardDivs = document.querySelectorAll('.card');
if (cardDivs.length > 0) {
clearInterval(checkForCardDivs);
cardDivs.forEach(cardDiv => {
const buttonRow = cardDiv.querySelector('.button-row');
const actions = cardDiv.querySelector('.actions');
const nameSpan = actions.querySelector('.name');
let modelName = nameSpan.textContent.trim();
let currentElement = cardDiv.parentElement;
let content_type = null;
while (currentElement) {
const parentId = currentElement.id;
if (validParentIds.includes(parentId)) {
content_type = parentId;
break;
}
currentElement = currentElement.parentElement;
}
const existingDiv = buttonRow.querySelector('.goto-civitbrowser.card-button');
if (existingDiv) {
return;
}
const metaDataButton = buttonRow.querySelector('.metadata-button.card-button')
const newDiv = document.createElement('div');
newDiv.classList.add('goto-civitbrowser', 'card-button');
newDiv.addEventListener('click', function (event) {
event.stopPropagation();
sendModelToBrowser(modelName, content_type);
});
const svgIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
if (isLobeTheme) {
svgIcon.setAttribute('width', '25');
svgIcon.setAttribute('height', '25');
} else {
if (metaDataButton) {
metaDataButton.style.paddingTop = '5px';
metaDataButton.style.width = '42px';
metaDataButton.style.fontSize = '230%';
}
svgIcon.setAttribute('width', '40');
svgIcon.setAttribute('height', '40');
}
svgIcon.setAttribute('viewBox', '75 0 500 500');
svgIcon.setAttribute('fill', 'white');
svgIcon.innerHTML = `
<path d="M 352.79 218.85 L 319.617 162.309 L 203.704 162.479 L 146.28 259.066 L 203.434 355.786 L 319.373 355.729 L 352.773 299.386 L 411.969 299.471 L 348.861 404.911 L 174.065 404.978 L 87.368 259.217 L 174.013 113.246 L 349.147 113.19 L 411.852 218.782 L 352.79 218.85 Z"/>
<path d="M 304.771 334.364 L 213.9 334.429 L 169.607 259.146 L 214.095 183.864 L 305.132 183.907 L 330.489 227.825 L 311.786 259.115 L 330.315 290.655 Z M 278.045 290.682 L 259.294 259.18 L 278.106 227.488 L 240.603 227.366 L 221.983 259.128 L 240.451 291.026 Z"/>
`;
newDiv.appendChild(svgIcon);
buttonRow.insertBefore(newDiv, buttonRow.firstChild);
});
}
}, 100);
}
}
document.addEventListener('click', createCardButtons);
function sendModelToBrowser(modelName, content_type) {
const tabNav = document.querySelector('.tab-nav');
const buttons = tabNav.querySelectorAll('button');
for (const button of buttons) {
if (button.textContent.includes('Civitai')) {
button.click();
const firstButton = document.querySelector('#tab_civitai_interface > div > div > div > button');
if (firstButton) {
firstButton.click();
}
}
}
select_model(modelName, true, content_type);
}
// Clicks the first item in the browser cards list
function clickFirstFigureInColumn() {
const columnDiv = document.querySelector('.column.civmodellist');
if (columnDiv) {
const firstFigure = columnDiv.querySelector('figure');
if (firstFigure) {
firstFigure.click();
}
}
}
// Runs all functions when the page is fully loaded
function onPageLoad() {
const divElement = document.getElementById('setting_custom_api_key');
@ -375,6 +495,16 @@ function onPageLoad() {
return;
}
var subfolderDiv = document.querySelector("#settings_civitai_browser_plus > div > div");
var subfolders = subfolderDiv.querySelectorAll("[id$='subfolder']");
createAccordion(subfolderDiv, subfolders, "Default sub folders");
var upscalerDiv = document.querySelector("#settings_civitai_browser_plus > div > div > #settings-accordion > div");
var upscalers = upscalerDiv.querySelectorAll("[id$='upscale_subfolder']");
createAccordion(upscalerDiv, upscalers, "Upscalers");
observer.observe(civitaiDiv);
clearInterval(intervalID);
updateSVGIcons();
@ -384,7 +514,6 @@ function onPageLoad() {
setupClickOutsideListener();
createLink(infoElement);
updateBackToTopVisibility([{isIntersecting: false}]);
createAccordion();
}
// Checks every second if the page is fully loaded

View File

@ -462,10 +462,15 @@ def update_next_page(content_type, sort_type, period_type, use_search_term, sear
gr.Slider.update(value=current_page, maximum=total_pages, label=page_string), # Page Count
gr.Button.update(interactive=False), # Save Tags
gr.Button.update(interactive=False), # Save Images
gr.Button.update(interactive=False), # Download Button
gr.Button.update(interactive=False, visible=False if gl.isDownloading else True), # Download Button
gr.Button.update(interactive=False, visible=False), # Delete Button
gr.Textbox.update(interactive=False, value=None), # Install Path
gr.Dropdown.update(choices=[], value="", interactive=False), # Sub Folder List
gr.Dropdown.update(choices=[], value="", interactive=False) # File List
gr.Dropdown.update(choices=[], value="", interactive=False), # File List
gr.HTML.update(value='<div style="min-height: 0px;"></div>'), # Preview HTML
gr.Textbox.update(value=None), # Trained Tags
gr.Textbox.update(value=None), # Base Model
gr.Textbox.update(value=None) # Model Filename
)
def pagecontrol(json_data):
@ -549,10 +554,16 @@ def update_model_list(content_type, sort_type, period_type, use_search_term, sea
gr.Slider.update(value=current_page, maximum=total_pages, label=page_string), # Page Count
gr.Button.update(interactive=False), # Save Tags
gr.Button.update(interactive=False), # Save Images
gr.Button.update(interactive=False), # Download Button
gr.Textbox.update(interactive=False, value=None), # Install Path
gr.Button.update(interactive=False, visible=False if gl.isDownloading else True), # Download Button
gr.Button.update(interactive=False, visible=False), # Delete Button
gr.Textbox.update(interactive=False, value=None, visible=True), # Install Path
gr.Dropdown.update(choices=[], value="", interactive=False), # Sub Folder List
gr.Dropdown.update(choices=[], value="", interactive=False) # File List
gr.Dropdown.update(choices=[], value="", interactive=False), # File List
gr.HTML.update(value='<div style="min-height: 0px;"></div>'), # Preview HTML
gr.Textbox.update(value=None), # Trained Tags
gr.Textbox.update(value=None), # Base Model
gr.Textbox.update(value=None) # Model Filename
)
def update_model_versions(model_name):
@ -817,9 +828,13 @@ def update_model_info(model_name=None, model_version=None):
break
insert_sub = getattr(opts, "insert_sub", True)
dot_subfolders = getattr(opts, "dot_subfolders", True)
try:
sub_folders = ["None"]
for root, dirs, _ in os.walk(model_folder):
if dot_subfolders:
dirs = [d for d in dirs if not d.startswith('.')]
for d in dirs:
sub_folder = os.path.relpath(os.path.join(root, d), model_folder)
if sub_folder:
@ -828,11 +843,13 @@ def update_model_info(model_name=None, model_version=None):
sub_folders.remove("None")
sub_folders = sorted(sub_folders)
sub_folders.insert(0, "None")
sub_opt1 = os.path.join(os.sep, model_name)
sub_opt2 = os.path.join(os.sep, model_name, model_version)
if insert_sub:
model_name = model_name.replace('/', '').replace('\\', '')
model_version = model_version.replace('/', '').replace('\\', '')
sub_folders.insert(1, os.path.join(os.sep, model_name))
sub_folders.insert(2, os.path.join(os.sep, model_name, model_version))
sub_folders.insert(1, sub_opt1)
sub_folders.insert(2, sub_opt2)
list = set()
sub_folders = [x for x in sub_folders if not (x in list or list.add(x))]
@ -840,6 +857,11 @@ def update_model_info(model_name=None, model_version=None):
sub_folders = ["None"]
default_sub = sub_folder_value(content_type, desc)
if default_sub == f"{os.sep}Model Name":
default_sub = sub_opt1
elif default_sub == f"{os.sep}Model Name{os.sep}Version Name":
default_sub = sub_opt2
if folder_location == "None":
folder_location = model_folder
if default_sub != "None":

View File

@ -332,6 +332,48 @@ def gen_sha256(file_path):
return hash_value
def model_from_sent(model_name, content_type, click_first_item):
model_name = re.sub(r'\.\d{3}$', '', model_name)
content_type = re.sub(r'\.\d{3}$', '', content_type)
content_mapping = {
"txt2img_textual_inversion_cards_html": ['TextualInversion'],
"txt2img_hypernetworks_cards_html": ['Hypernetwork'],
"txt2img_checkpoints_cards_html": ['Checkpoint'],
"txt2img_lora_cards_html": ['LORA', 'LoCon']
}
content_type = content_mapping.get(content_type, content_type)
for content_type_item in content_type:
folder = _api.contenttype_folder(content_type_item)
for folder_path, _, files in os.walk(folder):
for file in files:
if file.startswith(model_name) and not file.endswith(".json"):
model_file = os.path.join(folder_path, file)
modelID = get_models(model_file)
gl.json_data = _api.api_to_data(content_type, "Newest", "AllTime", "Model name", None, None, None, f"civitai.com/models/{modelID}")
if gl.json_data == "timeout":
HTML = '<div style="font-size: 24px; text-align: center; margin: 50px !important;">The Civit-API has timed out, please try again.<br>The servers might be too busy or the selected model could not be found.</div>'
number = click_first_item
if gl.json_data != None and gl.json_data != "timeout":
model_dict = {}
for item in gl.json_data['items']:
model_dict[item['name']] = item['name']
HTML = _api.model_list_html(gl.json_data, model_dict)
(hasPrev, hasNext, current_page, total_pages) = _api.pagecontrol(gl.json_data)
page_string = f"Page: {current_page}/{total_pages}"
number = _download.random_number(click_first_item)
return (
gr.HTML.update(HTML), # Card HTML
gr.Button.update(interactive=hasPrev), # Prev Button
gr.Button.update(interactive=hasNext), # Next Button
gr.Slider.update(value=current_page, maximum=total_pages, label=page_string), # Page Slider
gr.Textbox.update(number) # Click first card trigger
)
def is_image_url(url):
image_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp']
parsed = urlparse(url)
@ -845,9 +887,9 @@ def load_to_browser():
global from_ver, from_installed
_ = None
if from_ver:
(lm,lv,lh,pp,np,p,st,si,dm,ip,sf,fl) = _api.update_model_list(_,_,_,_,_,_,_,_,_,_,True)
(lm,lv,lh,pp,np,p,st,si,dm,_dm,ip,sf,fl,ph,tt,bm,mf) = _api.update_model_list(_,_,_,_,_,_,_,_,_,_,True)
if from_installed:
(lm,lv,lh,pp,np,p,st,si,dm,ip,sf,fl) = _api.update_model_list(_,_,_,_,_,_,_,_,_,_,False,True)
(lm,lv,lh,pp,np,p,st,si,dm,_dm,ip,sf,fl,ph,tt,bm,mf) = _api.update_model_list(_,_,_,_,_,_,_,_,_,_,False,True)
gl.file_scan = True
from_ver, from_installed = False, False
@ -858,7 +900,7 @@ def load_to_browser():
gr.Button.update(interactive=True, visible=True),
gr.Button.update(interactive=False, visible=False),
gr.Button.update(interactive=False, visible=False),
lm,lv,lh,pp,np,p,st,si,dm,ip,sf,fl,
lm,lv,lh,pp,np,p,st,si,dm,_dm,ip,sf,fl,ph,tt,bm,mf,
gr.HTML.update(value='<div style="min-height: 0px;"></div>')
)

View File

@ -1,5 +1,5 @@
import gradio as gr
from modules import script_callbacks, shared, ui_settings
from modules import script_callbacks, shared
import os
import json
import fnmatch
@ -70,6 +70,7 @@ def on_ui_tabs():
toggle3 = "toggle3L" if lobe_directory else "toggle3"
refreshbtn = "refreshBtnL" if lobe_directory else "refreshBtn"
filterBox = "filterBoxL" if lobe_directory else "filterBox"
if page_header:
header = "headerL" if lobe_directory else "header"
else:
@ -178,7 +179,10 @@ def on_ui_tabs():
#Invisible triggers/variables
model_id = gr.Textbox(value=None, visible=False)
dl_url = gr.Textbox(value=None, visible=False)
event_text = gr.Textbox(elem_id="eventtext1", visible=False)
model_select = gr.Textbox(elem_id="model_select", visible=False)
model_sent = gr.Textbox(elem_id="model_sent", visible=False)
type_sent = gr.Textbox(elem_id="type_sent", visible=False)
click_first_item = gr.Textbox(elem_id="type_sent", visible=False)
download_start = gr.Textbox(value=None, visible=False)
download_finish = gr.Textbox(value=None, visible=False)
tag_start = gr.Textbox(value=None, visible=False)
@ -211,6 +215,11 @@ def on_ui_tabs():
# Javascript Functions #
click_first_item.change(
fn=None,
_js="() => clickFirstFigureInColumn()"
)
list_models.select(
fn=None,
inputs=[list_models],
@ -311,11 +320,9 @@ def on_ui_tabs():
(html, tags, base_mdl, DwnButton, DelButton, filelist, filename, id, current_sha256, install_path, sub_folder) = _api.update_model_info(model_name,ret_versions['value'])
return gr.Dropdown.update(value=model_name),ret_versions,html,tags,base_mdl,filename,install_path,sub_folder,DwnButton,DelButton,filelist,id,current_sha256,gr.Button.update(interactive=True)
event_text.change(
model_select.change(
fn=update_models_dropdown,
inputs=[
event_text
],
inputs=[model_select],
outputs=[
list_models,
list_versions,
@ -333,6 +340,12 @@ def on_ui_tabs():
save_info
]
)
model_sent.change(
fn=_file.model_from_sent,
inputs=[model_sent, type_sent, click_first_item],
outputs=[list_html, get_prev_page , get_next_page, page_slider, click_first_item]
)
sub_folder.select(
fn=select_subfolder,
@ -526,9 +539,14 @@ def on_ui_tabs():
save_info,
save_images,
download_model,
delete_model,
install_path,
sub_folder,
file_list
file_list,
preview_html,
trained_tags,
base_model,
model_filename
]
page_btn_list = {
@ -732,9 +750,14 @@ def on_ui_tabs():
save_info,
save_images,
download_model,
delete_model,
install_path,
sub_folder,
file_list,
preview_html,
trained_tags,
base_model,
model_filename,
installed_progress
]
)
@ -757,9 +780,14 @@ def on_ui_tabs():
save_info,
save_images,
download_model,
delete_model,
install_path,
sub_folder,
file_list,
preview_html,
trained_tags,
base_model,
model_filename,
version_progress
]
)
@ -767,12 +795,16 @@ def on_ui_tabs():
return (civitai_interface, "Civitai Browser+", "civitai_interface"),
def subfolder_list(folder, desc=None):
insert_sub = getattr(opts, "insert_sub", True)
dot_subfolders = getattr(opts, "dot_subfolders", True)
if folder == None:
return
try:
model_folder = _api.contenttype_folder(folder, desc)
sub_folders = ["None"]
for root, dirs, _ in os.walk(model_folder):
if dot_subfolders:
dirs = [d for d in dirs if not d.startswith('.')]
for d in dirs:
sub_folder = os.path.relpath(os.path.join(root, d), model_folder)
if sub_folder:
@ -781,6 +813,9 @@ def subfolder_list(folder, desc=None):
sub_folders.remove("None")
sub_folders = sorted(sub_folders)
sub_folders.insert(0, "None")
if insert_sub:
sub_folders.insert(1, f"{os.sep}Model Name")
sub_folders.insert(2, f"{os.sep}Model Name{os.sep}Version Name")
list = set()
sub_folders = [x for x in sub_folders if not (x in list or list.add(x))]
@ -805,9 +840,10 @@ def on_ui_settings():
shared.opts.add_option("show_log", shared.OptionInfo(False, "Show Aria2 logs in console", section=section).info("Requires UI reload"))
shared.opts.add_option("split_aria2", shared.OptionInfo(64, "Number of connections to use for downloading a model", gr.Slider, lambda: {"maximum": "64", "minimum": "1", "step": "1"}, section=section).info("Only applies to Aria2"))
shared.opts.add_option("aria2_flags", shared.OptionInfo(r"", "Custom Aria2 command line flags", section=section).info("Requires UI reload"))
shared.opts.add_option("insert_sub", shared.OptionInfo(True, "Insert [/Model Name] & [/Model Name/Version Name] as default sub folder options", section=section))
shared.opts.add_option("insert_sub", shared.OptionInfo(True, f"Insert [{os.sep}Model Name] & [{os.sep}Model Name{os.sep}Version Name] as sub folder options", section=section))
shared.opts.add_option("dot_subfolders", shared.OptionInfo(True, "Hide sub-folders that start with a .", section=section))
shared.opts.add_option("use_LORA", shared.OptionInfo(False, "Treat LoCon's as LORA's", section=section).info("SD-WebUI v1.5 and higher treats LoCON's the same as LORA's, Requires UI reload"))
shared.opts.add_option("unpack_zip", shared.OptionInfo(False, "Automatically unpack .zip after downloading", section=section))
shared.opts.add_option("unpack_zip", shared.OptionInfo(False, "Automatically unpack .zip files after downloading", section=section))
shared.opts.add_option("hide_early_access", shared.OptionInfo(True, "Hide early access models", section=section).info("Early access models are only downloadable for supporter tier members, Requires API key"))
shared.opts.add_option("custom_api_key", shared.OptionInfo(r"", "Personal CivitAI API key", section=section).info("You can create your own API key in your CivitAI account settings, Requires UI reload"))
shared.opts.add_option("page_header", shared.OptionInfo(False, "Page navigation as header", section=section).info("Keeps the page navigation always visible at the top, Requires UI reload"))
@ -816,15 +852,6 @@ def on_ui_settings():
use_LORA = getattr(opts, "use_LORA", False)
def has_folders(input, desc=None):
if input == None:
return False
try:
path = any(entry.is_dir() for entry in os.scandir(_api.contenttype_folder(input, desc)))
except:
return False
return path
# Default sub folders
folders = [
"Checkpoint",
@ -853,7 +880,7 @@ def on_ui_settings():
desc = None
if isinstance(folder, tuple):
folder_name = " - ".join(folder)
setting_name = folder[1]
setting_name = f"{folder[1]}_upscale"
folder = folder[0]
desc = folder[1]
else:
@ -863,8 +890,7 @@ def on_ui_settings():
folder = "LORA"
setting_name = "LORA_LoCon"
if has_folders(folder, desc):
shared.opts.add_option(f"{setting_name}_subfolder", shared.OptionInfo("None", folder_name, gr.Dropdown, make_lambda(folder, desc), section=section))
shared.opts.add_option(f"{setting_name}_subfolder", shared.OptionInfo("None", folder_name, gr.Dropdown, make_lambda(folder, desc), section=section))
script_callbacks.on_ui_tabs(on_ui_tabs)
script_callbacks.on_ui_settings(on_ui_settings)

View File

@ -96,6 +96,10 @@
z-index: 60;
}
.acss-14flpmm .gap:has(#quicksettings):first-child {
gap: var(--layout-gap);
}
#browserTab > div > #header, #browserTab > div > #header_off {
display: flex;
flex-direction: column;
@ -343,6 +347,14 @@
/* End of Custom Accordion */
.goto-civitbrowser.card-button {
filter: drop-shadow(2px 2px 3px black);
}
.goto-civitbrowser.card-button:hover svg {
fill: red !important;
}
/* Custom settings Accordion */
#settings-accordion {
border: 1px solid var(--block-border-color);