[gnome] Add new extensions

This commit is contained in:
2026-02-11 16:14:31 -05:00
parent 5b86d17bab
commit 2c2be21fef
25 changed files with 4047 additions and 0 deletions

View File

@ -0,0 +1,18 @@
{
"_generated": "Generated by SweetTooth, do not edit",
"creator": "comitanigiacomo",
"description": "Supercharge your GNOME workflow with this sleek top-bar dock:\n\n ~ 1-Click Access - Launch apps/websites instantly\n ~ Full Customization - Resize icons, adjust spacing, toggle labels\n ~ Live Activity Indicators - Color-coded dots for running apps\n ~ Smart Search - Find+pin apps directly from the menu\n ~ 10 Keyboard Shortcuts - Instant launches with custom keybinds\n ~ Web Links Support - Pin frequently visited URLs\n ~ Auto-Start Essentials - Launch critical apps on login\n ~ Flexible Positioning - Left/Center/Right panel placement\n ~ Press-to-Reorder - Intuitive visual organization\n ~ Smart Limiter - Focus on what matters (max 10 items)",
"name": "quick-launch",
"settings-schema": "org.gnome.shell.extensions.app-pinner",
"shell-version": [
"45",
"46",
"47",
"48",
"49"
],
"stylesheet": "stylesheet.css",
"url": "https://github.com/comitanigiacomo/quicklaunch",
"uuid": "quicklaunch@comitanigiacomo.github.com",
"version": 15
}

View File

@ -0,0 +1,672 @@
import Gtk from 'gi://Gtk';
import Adw from 'gi://Adw';
import Gio from 'gi://Gio';
import Gdk from 'gi://Gdk';
import GLib from 'gi://GLib';
import { ExtensionPreferences } from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js';
export default class AppPinnerExtensionPreferences extends ExtensionPreferences {
fillPreferencesWindow(window) {
const settings = this.getSettings('org.gnome.shell.extensions.app-pinner');
window.set_search_enabled(true);
// Pagina Aspetto
const appearancePage = new Adw.PreferencesPage({
title: 'Appearance',
icon_name: 'applications-graphics-symbolic'
});
this._buildAppearancePage(appearancePage, settings);
window.add(appearancePage);
// Pagina Scorciatoie
const shortcutsPage = new Adw.PreferencesPage({
title: 'Shortcuts',
icon_name: 'preferences-desktop-keyboard-symbolic'
});
this._buildShortcutsPage(shortcutsPage, settings);
window.add(shortcutsPage);
// Pagina Avvio Automatico
const startupPage = new Adw.PreferencesPage({
title: 'Startup Apps',
icon_name: 'system-run-symbolic'
});
this._buildStartupPage(startupPage, settings);
window.add(startupPage);
// Pagina About
const aboutPage = new Adw.PreferencesPage({
title: 'About',
icon_name: 'dialog-information-symbolic'
});
this._buildAboutPage(aboutPage);
window.add(aboutPage);
}
_buildAppearancePage(page, settings) {
const group = new Adw.PreferencesGroup({
title: 'Panel Customization',
description: 'Adjust how pinned apps appear in your top panel'
});
const iconSizeRow = new Adw.SpinRow({
title: 'Icon Size',
subtitle: 'Recommended between 16-24 pixels for optimal panel integration',
adjustment: new Gtk.Adjustment({
value: settings.get_int('icon-size'),
lower: 16,
upper: 24,
step_increment: 1
})
});
settings.bind('icon-size', iconSizeRow, 'value', Gio.SettingsBindFlags.DEFAULT);
group.add(iconSizeRow);
const positionRow = new Adw.ComboRow({
title: 'Panel Position',
subtitle: 'Choose where to show the pinned apps in your top panel',
model: new Gtk.StringList({ strings: ['left', 'center', 'right'] })
});
positionRow.connect('notify::selected', () => {
const selectedString = positionRow.model.get_string(positionRow.selected);
settings.set_string('position-in-panel', selectedString);
});
const currentPos = settings.get_string('position-in-panel');
positionRow.selected = ['left', 'center', 'right'].indexOf(currentPos);
group.add(positionRow);
const spacingRow = new Adw.SpinRow({
title: 'Icon Spacing',
subtitle: 'Space between app icons in pixels',
adjustment: new Gtk.Adjustment({
value: settings.get_int('spacing'),
lower: 0,
upper: 20,
step_increment: 1
})
});
settings.bind('spacing', spacingRow, 'value', Gio.SettingsBindFlags.DEFAULT);
group.add(spacingRow);
const colorRow = new Adw.ActionRow({
title: 'Indicator Color',
subtitle: 'Choose color for running apps indicator'
});
const colorButton = new Gtk.ColorButton({
valign: Gtk.Align.CENTER,
use_alpha: true
});
const initialColor = new Gdk.RGBA();
initialColor.parse(settings.get_string('indicator-color'));
colorButton.set_rgba(initialColor);
colorButton.connect('color-set', (btn) => {
const color = btn.get_rgba().to_string();
settings.set_string('indicator-color', color);
});
colorRow.add_suffix(colorButton);
colorRow.set_activatable_widget(colorButton);
group.add(colorRow);
const sortToggle = new Adw.SwitchRow({
title: 'Sort Alphabetically',
subtitle: 'Automatically sort apps by name',
active: settings.get_boolean('sort-alphabetically')
});
settings.bind('sort-alphabetically', sortToggle, 'active', Gio.SettingsBindFlags.DEFAULT);
group.add(sortToggle);
const visibilityGroup = new Adw.PreferencesGroup({
title: 'Visibility Options',
description: 'Control what elements are displayed in the panel'
});
const panelToggle = new Adw.SwitchRow({
title: 'Show in Top Panel',
subtitle: 'Display pinned apps directly in the panel',
active: settings.get_boolean('show-in-panel')
});
settings.bind('show-in-panel', panelToggle, 'active', Gio.SettingsBindFlags.DEFAULT);
visibilityGroup.add(panelToggle);
const pinIconToggle = new Adw.SwitchRow({
title: 'Show Pin Icon',
subtitle: 'Display the pin icon when apps are pinned',
active: settings.get_boolean('show-pin-icon')
});
settings.bind('show-pin-icon', pinIconToggle, 'active', Gio.SettingsBindFlags.DEFAULT);
visibilityGroup.add(pinIconToggle);
page.add(visibilityGroup);
page.add(group);
}
_buildShortcutsPage(page, settings) {
const group = new Adw.PreferencesGroup({
title: 'Keyboard Shortcuts',
description: 'Assign custom keyboard shortcuts to launch your pinned apps directly.\nShortcuts are position-based and will follow your current app order.'
});
const listBox = new Gtk.ListBox({
selection_mode: Gtk.SelectionMode.NONE,
css_classes: ['shortcut-list']
});
const updateList = () => {
let child = listBox.get_first_child();
while (child) {
const next = child.get_next_sibling();
listBox.remove(child);
child = next;
}
const pinnedApps = settings.get_strv('pinned-apps');
if (pinnedApps.length === 0) {
const emptyRow = new Adw.ActionRow({
title: 'No Pinned Apps',
subtitle: 'Pin apps from the main menu to enable shortcuts',
css_classes: ['dim-label']
});
listBox.append(emptyRow);
return;
}
pinnedApps.forEach((appId, index) => {
const position = index + 1;
const appName = this._getAppName(appId) || 'Unknown Application';
const row = new Adw.ActionRow({
title: `Position ${position}`,
subtitle: appName,
css_classes: ['shortcut-row']
});
const entry = new Gtk.Entry({
text: this._acceleratorToLabel(settings.get_string(`shortcut-${position}`)),
editable: false,
width_chars: 20,
css_classes: ['shortcut-entry'],
placeholder_text: 'Click to set shortcut',
tooltip_text: `Press any key combination for ${appName}`
});
entry.connect('notify::has-focus', (widget) => {
if (!widget.hasFocus) {
widget.set_selection(0, 0);
widget.set_position(-1);
}
});
const controller = new Gtk.EventControllerKey();
controller.connect('key-pressed', (_, keyval, _keycode, state) => {
if (keyval === Gdk.KEY_Escape) {
entry.grab_focus_away();
return Gdk.EVENT_STOP;
}
const isModifier = [
Gdk.KEY_Control_L, Gdk.KEY_Control_R,
Gdk.KEY_Shift_L, Gdk.KEY_Shift_R,
Gdk.KEY_Alt_L, Gdk.KEY_Alt_R,
Gdk.KEY_Super_L, Gdk.KEY_Super_R
].includes(keyval);
if (isModifier) return Gdk.EVENT_STOP;
const modifiers = state & Gtk.accelerator_get_default_mod_mask();
const accelerator = Gtk.accelerator_name(keyval, modifiers);
entry.text = this._acceleratorToLabel(accelerator);
settings.set_string(`shortcut-${position}`, accelerator);
return Gdk.EVENT_STOP;
});
entry.add_controller(controller);
const clearButton = new Gtk.Button({
icon_name: 'edit-clear-symbolic',
tooltip_text: 'Clear shortcut'
});
clearButton.connect('clicked', () => {
entry.text = 'Disabled';
settings.set_string(`shortcut-${position}`, '');
});
row.add_suffix(entry);
row.add_suffix(clearButton);
listBox.append(row);
});
};
updateList();
settings.connect('changed::pinned-apps', () => {
updateList();
});
const scrolledWindow = new Gtk.ScrolledWindow({
height_request: 300,
child: listBox
});
group.add(scrolledWindow);
page.add(group);
}
_buildStartupPage(page, settings) {
const group = new Adw.PreferencesGroup({
title: 'Launch at Startup',
description: 'Select applications to launch automatically when you log in'
});
const listStore = new Gtk.StringList();
const updateList = () => {
const pinned = settings.get_strv('pinned-apps');
listStore.splice(0, listStore.get_n_items(), pinned);
};
updateList();
settings.connect('changed::pinned-apps', updateList);
const factory = new Gtk.SignalListItemFactory();
factory.connect('setup', (_, listItem) => {
const row = new Adw.ActionRow();
const icon = new Gtk.Image({ icon_size: Gtk.IconSize.LARGE, margin_end: 12 });
row.add_prefix(icon);
const toggle = new Gtk.Switch({
valign: Gtk.Align.CENTER,
halign: Gtk.Align.END
});
row.add_suffix(toggle);
row._icon = icon;
row._toggle = toggle;
listItem.set_child(row);
});
factory.connect('bind', (_, listItem) => {
const appId = listItem.get_item().string;
const row = listItem.get_child();
const toggle = row._toggle;
const startupApps = settings.get_strv('startup-apps');
toggle.set_active(startupApps.includes(appId));
toggle.connect('notify::active', (sw) => {
const newStartup = sw.active
? [...settings.get_strv('startup-apps'), appId]
: settings.get_strv('startup-apps').filter(id => id !== appId);
settings.set_strv('startup-apps', newStartup);
});
const appInfo = this._getAppInfo(appId);
row.set_title(appInfo?.get_name() || appId);
row._icon.set_from_gicon(appInfo?.get_icon());
});
const listView = new Gtk.ListView({
model: new Gtk.SingleSelection({ model: listStore }),
factory: factory
});
const scrolledWindow = new Gtk.ScrolledWindow({
height_request: 300,
child: listView
});
group.add(scrolledWindow);
page.add(group);
}
_buildAboutPage(page) {
const group = new Adw.PreferencesGroup({
title: this.metadata.name,
description: 'A modern application pinner and quick launcher for GNOME Shell'
});
const versionRow = new Adw.ActionRow({
title: 'Version'
});
const versionLabel = new Gtk.Label({
label: `v${this.metadata.version.toString()}`,
css_classes: ['dim-label'],
xalign: 0
});
versionRow.add_suffix(versionLabel);
group.add(versionRow);
const authorRow = new Adw.ActionRow({
title: 'Developer'
});
const githubButton = new Gtk.Button({
label: this.metadata.creator,
css_classes: ['flat', 'suggested-action'],
tooltip_text: 'Open GitHub profile',
margin_top: 6,
margin_bottom: 6
});
githubButton.connect('clicked', () => {
Gio.AppInfo.launch_default_for_uri('https://github.com/comitanigiacomo', null);
});
authorRow.add_suffix(githubButton);
authorRow.activatable_widget = githubButton;
group.add(authorRow);
const repoRow = new Adw.ActionRow({
title: 'Repository'
});
const repoButton = new Gtk.Button({
label: 'Source Code',
css_classes: ['flat', 'suggested-action'],
tooltip_text: 'Open GitHub repository',
margin_top: 6,
margin_bottom: 6
});
repoButton.connect('clicked', () => {
if (this.metadata.url) {
Gio.AppInfo.launch_default_for_uri(this.metadata.url, null);
}
});
repoRow.add_suffix(repoButton);
repoRow.activatable_widget = repoButton;
group.add(repoRow);
const licenseRow = new Adw.ActionRow({
title: 'License'
});
const licenseButton = new Gtk.Button({
label: 'GPLv3',
css_classes: ['flat', 'suggested-action'],
tooltip_text: 'View license details',
margin_top: 6,
margin_bottom: 6
});
licenseButton.connect('clicked', () => {
Gio.AppInfo.launch_default_for_uri('https://github.com/comitanigiacomo/quicklaunch?tab=GPL-3.0-1-ov-file', null);
});
licenseRow.add_suffix(licenseButton);
licenseRow.activatable_widget = licenseButton;
group.add(licenseRow);
const descriptionLabel = new Gtk.Label({
label: `
● 🚀 One-Click Access: Launch apps/links directly from panel\n
● ⌨️ Custom Shortcuts: Configurable keyboard combinations\n
● 🔴 Live Indicators: Real-time running app status\n
● 🌐 Web Links: Open URLs in default browser\n
● ⚡ Auto-Start: Launch critical apps at login\n
● 🔧 Smart Reordering: long-press to organize\n
● 🔍 Instant Search: Find/pin apps from dropdown\n\n
Optimized for power users - Seamless GNOME integration
`
.replace(/ {8}/g, '')
.trim(),
wrap: true,
xalign: 0.5,
halign: Gtk.Align.CENTER,
hexpand: true
});
const descGroup = new Adw.PreferencesGroup();
descGroup.add(descriptionLabel);
page.add(group);
page.add(descGroup);
}
_acceleratorToLabel(accel) {
if (!accel) return 'Disabled';
try {
const [success, keyval, mods] = Gtk.accelerator_parse(accel);
return success ? Gtk.accelerator_get_label(keyval, mods) : 'Invalid';
} catch (e) {
return 'Error';
}
}
_labelToAccelerator(label) {
let [success, keyval, mods] = Gtk.accelerator_parse(label);
if (!success || keyval === 0) {
const custom = this._parseCustomShortcut(label);
[success, keyval, mods] = Gtk.accelerator_parse(custom);
}
if (success && keyval !== 0) {
return Gtk.accelerator_name(keyval, mods);
}
return '';
}
_configureShortcut(window, settings, key, entry) {
const dialog = new Gtk.Dialog({
title: 'Set Shortcut',
transient_for: window,
modal: true,
use_header_bar: true
});
let accelerator = settings.get_string(key);
const label = new Gtk.Label({
label: 'Press desired key combination...\nCurrent: ' + this._acceleratorToLabel(accelerator),
margin: 12
});
const controller = new Gtk.EventControllerKey();
controller.connect('key-pressed', (_, keyval, _keycode, state) => {
try {
const mask = state & Gtk.accelerator_get_default_mod_mask();
const validMods = mask & (
Gdk.ModifierType.SHIFT_MASK |
Gdk.ModifierType.CONTROL_MASK |
Gdk.ModifierType.ALT_MASK |
Gdk.ModifierType.SUPER_MASK
);
if (keyval === Gdk.KEY_Shift_L || keyval === Gdk.KEY_Shift_R ||
keyval === Gdk.KEY_Control_L || keyval === Gdk.KEY_Control_R ||
keyval === Gdk.KEY_Alt_L || keyval === Gdk.KEY_Alt_R ||
keyval === Gdk.KEY_Super_L || keyval === Gdk.KEY_Super_R) {
return Gdk.EVENT_STOP;
}
accelerator = Gtk.accelerator_name(keyval, validMods);
label.label = 'New shortcut: ' + this._acceleratorToLabel(accelerator);
GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
dialog.queue_draw();
return GLib.SOURCE_REMOVE;
});
} catch (e) {
console.error('Error processing key:', e);
}
return Gdk.EVENT_STOP;
});
dialog.add_controller(controller);
const resetButton = new Gtk.Button({
label: 'Reset',
margin_end: 12
});
resetButton.connect('clicked', () => {
accelerator = '';
label.label = 'Shortcut cleared!';
});
dialog.get_content_area().append(label);
dialog.get_header_bar().pack_end(resetButton);
dialog.add_button('Cancel', Gtk.ResponseType.CANCEL);
dialog.add_button('Save', Gtk.ResponseType.OK);
dialog.connect('response', (_, response) => {
if (response === Gtk.ResponseType.OK) {
try {
const [success] = Gtk.accelerator_parse(accelerator);
if (success) {
settings.set_string(key, accelerator);
entry.text = this._acceleratorToLabel(accelerator);
} else {
this._showErrorDialog('Invalid shortcut combination');
}
} catch (e) {
this._showErrorDialog(`Error saving shortcut: ${e.message}`);
}
}
dialog.destroy();
});
dialog.present();
}
_showErrorDialog(message) {
const dialog = new Gtk.MessageDialog({
transient_for: this._window,
modal: true,
message_type: Gtk.MessageType.ERROR,
buttons: Gtk.ButtonsType.OK,
text: message
});
dialog.connect('response', () => dialog.destroy());
dialog.present();
}
_parseCustomShortcut(text) {
try {
const normalized = text
.replace(/</g, '')
.replace(/>/g, '+')
.toLowerCase();
const parts = normalized.split('+');
let mods = 0;
let keyval = 0;
parts.forEach(part => {
const mod = Gtk.accelerator_parse_modifier(part.trim());
if (mod) {
mods |= mod;
} else {
keyval = Gtk.accelerator_parse_key(part.trim(), 0);
}
});
return Gtk.accelerator_name(keyval, mods);
} catch (e) {
return '';
}
}
_validateAccelerator(accel) {
try {
const [success, keyval] = Gtk.accelerator_parse(accel);
return success && keyval !== 0;
} catch (e) {
return false;
}
}
_getAppInfo(appId) {
const variants = [
appId,
`${appId}.desktop`,
`${appId.replace(/-/g, '')}.desktop`,
appId.toLowerCase(),
`${appId.toLowerCase()}.desktop`
];
const searchPaths = [
GLib.get_user_data_dir() + '/applications',
'/usr/share/applications',
'/usr/local/share/applications'
];
for (const variant of variants) {
const appInfo = Gio.DesktopAppInfo.new(variant);
if (appInfo) return appInfo;
for (const path of searchPaths) {
const fullPath = `${path}/${variant}`;
if (GLib.file_test(fullPath, GLib.FileTest.EXISTS)) {
return Gio.DesktopAppInfo.new_from_filename(fullPath);
}
}
}
return null;
}
_getAppDisplayInfo(appId) {
let appInfo = Gio.DesktopAppInfo.new(`${appId}.desktop`);
if (!appInfo) {
const cleanId = appId.replace(/^application:\/\//, '');
appInfo = Gio.DesktopAppInfo.new(`${cleanId}.desktop`);
}
if (!appInfo) {
const shellApp = Shell.AppSystem.get_default().lookup_app(appId);
if (shellApp) {
return {
name: shellApp.get_name(),
icon: shellApp.get_icon()
};
}
}
if (!appInfo) {
const variations = [
appId.toLowerCase(),
appId.replace(/-/g, ''),
`${appId}.desktop`,
`${appId.toLowerCase()}.desktop`
];
for (const variant of variations) {
appInfo = Gio.DesktopAppInfo.new(variant);
if (appInfo) break;
}
}
return {
name: appInfo ? appInfo.get_name() : appId,
icon: appInfo ? appInfo.get_icon() : null
};
}
_getAppName(appId) {
const appInfo = Gio.DesktopAppInfo.new(`${appId}.desktop`);
return appInfo ? appInfo.get_name() : appId;
}
_sanitizeAppId(appId) {
return appId
.replace(/^application:\/\//, '')
.replace(/\.desktop$/i, '')
.replace(/\s+/g, '-')
.toLowerCase();
}
}

View File

@ -0,0 +1,142 @@
<?xml version="1.0" encoding="UTF-8"?>
<schemalist>
<schema id="org.gnome.shell.extensions.app-pinner" path="/org/gnome/shell/extensions/app-pinner/">
<key name="custom-keybindings" type="as">
<default>[]</default>
<summary>Custom keybindings paths</summary>
</key>
<key name="show-pin-icon" type="b">
<default>true</default>
<summary>Show pin icon in panel</summary>
<description>Whether to show the pin icon in the panel when there are pinned items</description>
</key>
<!-- Applicazioni Pinnate -->
<key name="pinned-apps" type="as">
<default>[]</default>
<summary>Pinned Applications</summary>
</key>
<!-- Aspetto -->
<key name="icon-size" type="i">
<default>24</default>
<range min="16" max="48"/>
<summary>Icon size in pixels</summary>
</key>
<key name="show-in-panel" type="b">
<default>true</default>
<summary>Show apps in panel</summary>
<description>Whether to display pinned apps in the top panel</description>
</key>
<key name="always-show-menu" type="b">
<default>false</default>
<summary>Always show menu icon</summary>
<description>Keep the menu icon visible even when panel apps are hidden</description>
</key>
<key name="position-in-panel" type="s">
<default>'right'</default>
<choices>
<choice value='left'/>
<choice value="center"/>
<choice value='right'/>
</choices>
<summary>Panel position</summary>
<description>Posizione nella barra superiore</description>
</key>
<key name="spacing" type="i">
<default>6</default>
<range min="0" max="24"/>
<summary>Spacing between icons</summary>
</key>
<key name="enable-labels" type="b">
<default>false</default>
<summary>Show application labels</summary>
</key>
<!-- Comportamento -->
<key name="launch-animation" type="b">
<default>true</default>
<summary>Enable launch animation</summary>
</key>
<!-- Avanzate -->
<key name="max-apps" type="i">
<default>10</default>
<range min="1" max="20"/>
<summary>Maximum apps to display</summary>
</key>
<key name="default-apps" type="as">
<default>['org.gnome.Calculator.desktop']</default>
<summary>Default suggested apps</summary>
</key>
<!-- Scorciatoie da tastiera -->
<key name="shortcut-1" type="s">
<default>''</default>
<summary>Shortcut for position 1</summary>
</key>
<key name="shortcut-2" type="s">
<default>''</default>
<summary>Shortcut for position 2</summary>
</key>
<key name="shortcut-3" type="s">
<default>''</default>
<summary>Shortcut for position 3</summary>
</key>
<key name="shortcut-4" type="s">
<default>''</default>
<summary>Shortcut for position 4</summary>
</key>
<key name="shortcut-5" type="s">
<default>''</default>
<summary>Shortcut for position 5</summary>
</key>
<key name="shortcut-6" type="s">
<default>''</default>
<summary>Shortcut for position 6</summary>
</key>
<key name="shortcut-7" type="s">
<default>''</default>
<summary>Shortcut for position 7</summary>
</key>
<key name="shortcut-8" type="s">
<default>''</default>
<summary>Shortcut for position 8</summary>
</key>
<key name="shortcut-9" type="s">
<default>''</default>
<summary>Shortcut for position 9</summary>
</key>
<key name="shortcut-10" type="s">
<default>''</default>
<summary>Shortcut for position 10</summary>
</key>
<key type="as" name="startup-apps">
<default>[]</default>
<summary>Startup Applications</summary>
<description>List of application IDs to launch at system startup.</description>
</key>
<key name="indicator-color" type="s">
<default>'#00ff00'</default>
<summary>Indicator color</summary>
<description>Color for running application indicator</description>
</key>
<key name="custom-links" type="as">
<default>[]</default>
<summary>Custom pinned links</summary>
</key>
<key name="sort-alphabetically" type="b">
<default>false</default>
<summary>Sort apps alphabetically</summary>
<description>If enabled, pinned apps will be sorted by name.</description>
</key>
</schema>
</schemalist>

View File

@ -0,0 +1,131 @@
.app-pinner-icons {
height: 100%;
padding: 0 2px;
}
.app-pinner-icon-box {
height: 100%;
padding: 2px 0;
margin: auto 0;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.app-pinner-icon {
display: flex !important;
align-items: center;
justify-content: center;
padding: 0 !important;
margin: auto;
border-radius: 6px;
transition: all 0.2s ease-in-out;
}
.app-pinner-link-icon {
margin: auto;
-st-icon-style: symbolic;
}
.app-pinner-running-indicator {
background-color: #ff0000;
border-radius: 4px;
width: 8px;
height: 8px;
position: absolute;
top: 3px;
right: 3px;
box-shadow: 0 1px 3px rgba(0,0,0,0.3);
z-index: 1000;
}
.app-pinner-label {
max-width: 100px;
text-overflow: ellipsis;
font-size: 0.8em;
margin-top: 2px;
}
.app-pinner-remove-btn {
font-size: 18px;
background-color: transparent;
border-radius: 100px;
min-width: 18px;
min-height: 18px;
padding: 2px;
display: flex;
align-items: right;
justify-content: right;
transition: background-color 0.2s ease-in-out;
}
.app-pinner-remove-btn:hover {
background-color: rgba(255, 0, 0, 0.8);
}
.app-pinner-remove-label {
font-size: 16px;
font-weight: bold;
color: white;
text-align: center;
}
.search-box {
width: 200px;
padding: 8px 0;
display: flex;
align-items: center;
justify-content: center;
}
.app-pinner-link-input {
min-width: 200px;
padding: 6px 12px;
border-radius: 6px;
background-color: rgba(0,0,0,0.3);
color: #ffffff;
border: 1px solid rgba(255,255,255,0.2);
font-size: 13px;
}
.app-pinner-link-input:focus {
border-color: #3584e4;
box-shadow: inset 0 0 0 1px #3584e4;
}
.app-pinner-add-link-btn {
background-color: rgba(78, 166, 78, 0.8);
border-radius: 8px;
min-width: 32px;
min-height: 32px;
padding: 6px;
margin-left: 6px;
transition: all 0.2s ease-in-out;
}
.app-pinner-add-link-btn:hover {
background-color: rgba(98, 196, 98, 0.9);
transform: scale(1.05);
}
.app-pinner-add-link-btn:active {
background-color: rgba(68, 146, 68, 0.9);
}
.app-pinner-icon-container {
position: relative;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
width: 100%;
}
.app-pinner-link-box {
margin: 8px;
padding: 6px;
background-color: rgba(255,255,255,0.05);
border-radius: 8px;
border: 1px solid rgba(255,255,255,0.1);
}