feat: refined UI components and updated app data

This commit is contained in:
NIJAT
2025-12-29 12:36:28 +04:00
parent f83293afe2
commit 00d521750e
13 changed files with 1098 additions and 885 deletions

View File

@@ -418,6 +418,7 @@ html {
.app-item {
opacity: 0;
transform: translateY(-20px);
will-change: transform, opacity;
}
.header-animate {
@@ -475,6 +476,18 @@ html {
}
}
@keyframes footerSlideUp {
0% {
opacity: 0;
transform: translateY(100%);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideUp {
0% {
opacity: 0;
@@ -487,6 +500,40 @@ html {
}
}
@keyframes modalSlideIn {
0% {
opacity: 0;
transform: translate(-50%, -48%) scale(0.96);
}
100% {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
}
@keyframes modalSlideOut {
0% {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
100% {
opacity: 0;
transform: translate(-50%, -48%) scale(0.96);
}
}
@keyframes fadeOut {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@keyframes slideDown {
0% {
opacity: 1;
@@ -554,6 +601,51 @@ html {
}
}
/* Slide in from right edge */
@keyframes slideInFromRight {
0% {
opacity: 0;
transform: translateX(100px);
}
70% {
transform: translateX(-5px);
}
100% {
opacity: 1;
transform: translateX(0);
}
}
@keyframes slideInFromRightSecond {
0% {
opacity: 0;
transform: translateX(80px);
}
70% {
transform: translateX(-3px);
}
100% {
opacity: 1;
transform: translateX(0);
}
}
@keyframes slideOutToRight {
0% {
opacity: 1;
transform: translateX(0);
}
100% {
opacity: 0;
transform: translateX(50px);
}
}
@keyframes tooltipSlideUp {
0% {
opacity: 0;
@@ -618,4 +710,40 @@ body.theme-flash::after {
pointer-events: none;
background: var(--text-primary);
animation: themeFlash 0.15s ease-out forwards;
}
/* ===== SKELETON LOADING ANIMATION ===== */
@keyframes skeletonPulse {
0%,
100% {
opacity: 0.4;
}
50% {
opacity: 0.7;
}
}
.skeleton-pulse {
animation: skeletonPulse 1.5s ease-in-out infinite;
will-change: opacity;
}
/* Staggered skeleton items for wave effect */
.skeleton-item {
animation: skeletonPulse 1.5s ease-in-out infinite;
will-change: opacity;
}
/* Force GPU acceleration for animated elements */
.category-header,
.app-item,
.header-animate,
.header-controls {
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-perspective: 1000px;
perspective: 1000px;
}

View File

@@ -64,11 +64,10 @@ export const AppItem = memo(function AppItem({
onTooltipLeave,
onFocus,
}: AppItemProps) {
// Build unavailable tooltip text
// Build unavailable tooltip text (just the reason, no description)
const getUnavailableText = () => {
if (app.unavailableReason) return app.unavailableReason;
const distroName = distros.find(d => d.id === selectedDistro)?.name || '';
return `Not available in ${distroName} repos`;
return app.unavailableReason || `Not available in ${distroName} repos`;
};
const isAur = selectedDistro === 'arch' && app.targets?.arch && isAurPackage(app.targets.arch);
@@ -98,10 +97,11 @@ export const AppItem = memo(function AppItem({
}
}}
onMouseEnter={(e) => {
if (isAvailable) onTooltipEnter(app.description, e);
// Show description tooltip for all apps (available and unavailable)
onTooltipEnter(app.description, e);
}}
onMouseLeave={() => {
if (isAvailable) onTooltipLeave();
onTooltipLeave();
}}
>
<div className={`w-4 h-4 rounded border-2 flex items-center justify-center flex-shrink-0 transition-all duration-150

View File

@@ -58,7 +58,7 @@ function CategorySectionComponent({
const hasAnimated = useRef(false);
const prevAppCount = useRef(categoryApps.length);
// Initial entrance animation
// Initial entrance animation - GPU optimized
useLayoutEffect(() => {
if (!sectionRef.current || hasAnimated.current) return;
hasAnimated.current = true;
@@ -67,27 +67,33 @@ function CategorySectionComponent({
const header = section.querySelector('.category-header');
const items = section.querySelectorAll('.app-item');
// Initial state
gsap.set(header, { clipPath: 'inset(0 100% 0 0)' });
gsap.set(items, { y: -20, opacity: 0 });
// Use requestAnimationFrame for smoother initial setup
requestAnimationFrame(() => {
// Initial state with GPU-accelerated transforms
gsap.set(header, { clipPath: 'inset(0 100% 0 0)' });
gsap.set(items, { y: -15, opacity: 0, force3D: true });
// Animate with staggered delay based on category index
const delay = categoryIndex * 0.08;
// Staggered delay based on category index (reduced for faster feel)
const delay = categoryIndex * 0.05;
gsap.to(header, {
clipPath: 'inset(0 0% 0 0)',
duration: 0.9,
ease: 'power3.out',
delay: delay + 0.1
});
// Animate header with clip-path reveal
gsap.to(header, {
clipPath: 'inset(0 0% 0 0)',
duration: 0.6,
ease: 'power2.out',
delay: delay + 0.05
});
gsap.to(items, {
y: 0,
opacity: 1,
duration: 0.8,
stagger: 0.04,
ease: 'expo.out',
delay: delay + 0.2
// Animate items with GPU-accelerated transforms
gsap.to(items, {
y: 0,
opacity: 1,
duration: 0.5,
stagger: 0.025,
ease: 'power2.out',
delay: delay + 0.1,
force3D: true
});
});
}, [categoryIndex]);

View File

@@ -1,7 +1,5 @@
'use client';
import { Package, Download, Terminal } from 'lucide-react';
interface AurDrawerSettingsProps {
aurAppNames: string[];
hasYayInstalled: boolean;
@@ -11,7 +9,7 @@ interface AurDrawerSettingsProps {
}
/**
* AurDrawerSettings - Settings panel for AUR configuration inside the drawer
* AurDrawerSettings - Compact UI with smooth button animations
*/
export function AurDrawerSettings({
aurAppNames,
@@ -21,95 +19,81 @@ export function AurDrawerSettings({
setSelectedHelper,
}: AurDrawerSettingsProps) {
return (
<div className="mb-6 rounded-xl border border-[#1793d1]/20 bg-[#1793d1]/5 overflow-hidden">
{/* Header */}
<div className="px-5 py-4 border-b border-[#1793d1]/10 flex items-start gap-3">
<div className="p-2 rounded-lg bg-[#1793d1]/10 text-[#1793d1] shrink-0">
<Package className="w-5 h-5" />
</div>
<div>
<h4 className="text-sm font-semibold text-[var(--text-primary)]">
AUR Packages Detected
</h4>
<p className="text-xs text-[var(--text-muted)] mt-1 leading-relaxed">
These apps require an AUR helper: <span className="text-[var(--text-primary)] opacity-80">{aurAppNames.join(', ')}</span>
</p>
</div>
<div className="mb-4 rounded-xl bg-[var(--bg-tertiary)] border border-[var(--border-primary)]/40 overflow-hidden">
{/* Header with all apps listed */}
<div className="px-4 py-3 border-b border-[var(--border-primary)]/30">
<p className="text-xs text-[var(--text-muted)] leading-relaxed">
<span className="font-medium text-[var(--text-primary)]">AUR packages: </span>
{aurAppNames.join(', ')}
</p>
</div>
{/* Controls Grid */}
<div className="p-5 grid grid-cols-1 md:grid-cols-2 gap-6">
{/* 1. Installation Mode */}
<div>
<label className="text-xs font-medium text-[var(--text-muted)] uppercase tracking-wider mb-2.5 flex items-center gap-2">
<Download className="w-3.5 h-3.5" />
Installation Logic
</label>
<div className="flex bg-[var(--bg-tertiary)] p-1 rounded-lg border border-[var(--border-primary)]/50">
<button
onClick={() => setHasYayInstalled(false)}
className={`flex-1 py-2 px-3 text-xs font-medium rounded-md transition-all ${!hasYayInstalled
? 'bg-[var(--bg-secondary)] text-[var(--text-primary)] shadow-sm border border-[var(--border-primary)]/50'
: 'text-[var(--text-muted)] hover:text-[var(--text-secondary)]'
}`}
>
Install New
</button>
<button
onClick={() => setHasYayInstalled(true)}
className={`flex-1 py-2 px-3 text-xs font-medium rounded-md transition-all ${hasYayInstalled
? 'bg-[var(--bg-secondary)] text-[var(--text-primary)] shadow-sm border border-[var(--border-primary)]/50'
: 'text-[var(--text-muted)] hover:text-[var(--text-secondary)]'
}`}
>
I Have One
</button>
</div>
<p className="text-[10px] text-[var(--text-muted)] mt-2 opacity-70 px-1">
{hasYayInstalled
? "Script will use your existing helper"
: "Script will install the helper first"}
</p>
</div>
{/* 2. Helper Selection */}
<div>
<label className="text-xs font-medium text-[var(--text-muted)] uppercase tracking-wider mb-2.5 flex items-center gap-2">
<Terminal className="w-3.5 h-3.5" />
Preferred Helper
</label>
<div className="grid grid-cols-2 gap-2">
{/* Controls with animated buttons */}
<div className="px-4 py-3 flex flex-col sm:flex-row sm:items-center gap-4 text-xs">
{/* Helper selection */}
<div className="flex items-center gap-3">
<span className="text-[var(--text-secondary)] font-medium">AUR helper:</span>
<div className="flex bg-[var(--bg-secondary)] rounded-lg p-1 border border-[var(--border-primary)]/30">
<button
onClick={() => setSelectedHelper('yay')}
className={`relative px-3 py-2 rounded-lg text-left border transition-all ${selectedHelper === 'yay'
? 'bg-[#1793d1]/10 border-[#1793d1]/30 text-[#1793d1]'
: 'bg-[var(--bg-tertiary)] border-[var(--border-primary)]/50 text-[var(--text-muted)] hover:bg-[var(--bg-hover)]'
className={`relative px-3 py-1.5 rounded-md font-medium transition-all duration-200 ease-out ${selectedHelper === 'yay'
? 'bg-[var(--text-primary)] text-[var(--bg-primary)] shadow-sm'
: 'text-[var(--text-muted)] hover:text-[var(--text-primary)]'
}`}
style={{
transform: selectedHelper === 'yay' ? 'scale(1)' : 'scale(0.98)',
}}
>
<span className="block text-xs font-bold">yay</span>
<span className="block text-[10px] opacity-70 mt-0.5">Go-based</span>
{selectedHelper === 'yay' && (
<div className="absolute top-2 right-2 w-1.5 h-1.5 rounded-full bg-[#1793d1]" />
)}
yay <span className="opacity-60 font-normal">(Go)</span>
</button>
<button
onClick={() => setSelectedHelper('paru')}
className={`relative px-3 py-2 rounded-lg text-left border transition-all ${selectedHelper === 'paru'
? 'bg-[#1793d1]/10 border-[#1793d1]/30 text-[#1793d1]'
: 'bg-[var(--bg-tertiary)] border-[var(--border-primary)]/50 text-[var(--text-muted)] hover:bg-[var(--bg-hover)]'
className={`relative px-3 py-1.5 rounded-md font-medium transition-all duration-200 ease-out ${selectedHelper === 'paru'
? 'bg-[var(--text-primary)] text-[var(--bg-primary)] shadow-sm'
: 'text-[var(--text-muted)] hover:text-[var(--text-primary)]'
}`}
style={{
transform: selectedHelper === 'paru' ? 'scale(1)' : 'scale(0.98)',
}}
>
<span className="block text-xs font-bold">paru</span>
<span className="block text-[10px] opacity-70 mt-0.5">Rust-based</span>
{selectedHelper === 'paru' && (
<div className="absolute top-2 right-2 w-1.5 h-1.5 rounded-full bg-[#1793d1]" />
)}
paru <span className="opacity-60 font-normal">(Rust)</span>
</button>
</div>
</div>
{/* Divider */}
<div className="w-px h-5 bg-[var(--border-primary)]/40 hidden sm:block" />
{/* Installation mode */}
<div className="flex items-center gap-3">
<span className="text-[var(--text-secondary)] font-medium">Already installed?</span>
<div className="flex bg-[var(--bg-secondary)] rounded-lg p-1 border border-[var(--border-primary)]/30">
<button
onClick={() => setHasYayInstalled(true)}
className={`relative px-3 py-1.5 rounded-md font-medium transition-all duration-200 ease-out ${hasYayInstalled
? 'bg-[var(--text-primary)] text-[var(--bg-primary)] shadow-sm'
: 'text-[var(--text-muted)] hover:text-[var(--text-primary)]'
}`}
style={{
transform: hasYayInstalled ? 'scale(1)' : 'scale(0.98)',
}}
>
Yes, use it
</button>
<button
onClick={() => setHasYayInstalled(false)}
className={`relative px-3 py-1.5 rounded-md font-medium transition-all duration-200 ease-out ${!hasYayInstalled
? 'bg-[var(--text-primary)] text-[var(--bg-primary)] shadow-sm'
: 'text-[var(--text-muted)] hover:text-[var(--text-primary)]'
}`}
style={{
transform: !hasYayInstalled ? 'scale(1)' : 'scale(0.98)',
}}
>
No, install it
</button>
</div>
</div>
</div>
</div>
);

View File

@@ -1,6 +1,6 @@
'use client';
import { useState, useEffect } from 'react';
import { useState, useEffect, useRef } from 'react';
import { X } from 'lucide-react';
interface AurFloatingCardProps {
@@ -31,19 +31,19 @@ export function AurFloatingCard({
const [hasAnswered, setHasAnswered] = useState<boolean | null>(null);
// Track if user has selected a helper (completed flow)
const [helperChosen, setHelperChosen] = useState(false);
// Track if user has interacted (dismissed or selected) to prevent nagging
const [userInteracted, setUserInteracted] = useState(false);
// Track if user has interacted (dismissed or selected) to prevent nagging - use ref to persist
const userInteractedRef = useRef(false);
// Reset when new AUR packages appear, BUT ONLY if user hasn't interacted yet
useEffect(() => {
if (show && aurAppNames.length > 0 && !userInteracted) {
if (show && aurAppNames.length > 0 && !userInteractedRef.current) {
setDismissed(false);
setIsExiting(false);
setShowConfirmation(false);
setHelperChosen(false);
setHasAnswered(null);
}
}, [aurAppNames.length, show, userInteracted]);
}, [aurAppNames.length, show]);
if (!show || dismissed) return null;
@@ -55,7 +55,7 @@ export function AurFloatingCard({
const handleHelperSelect = (helper: 'yay' | 'paru') => {
setSelectedHelper(helper);
setHelperChosen(true);
setUserInteracted(true); // Don't ask again
userInteractedRef.current = true; // Don't ask again
// Start exit animation after a brief moment
setTimeout(() => {
@@ -67,7 +67,7 @@ export function AurFloatingCard({
};
const handleDismiss = () => {
setUserInteracted(true); // Don't ask again
userInteractedRef.current = true; // Don't ask again
setIsExiting(true);
setTimeout(() => {
setDismissed(true);
@@ -87,11 +87,11 @@ export function AurFloatingCard({
}, 3000);
return (
<div className="fixed bottom-24 right-4 z-30">
<div className="fixed top-4 right-4 z-30">
<p
className="text-[13px] text-[var(--text-muted)]"
style={{
animation: 'cardSlideIn 0.3s cubic-bezier(0.34, 1.56, 0.64, 1) forwards'
animation: 'slideInFromRight 0.3s cubic-bezier(0.34, 1.56, 0.64, 1) forwards'
}}
>
You can change this later in preview tab
@@ -103,17 +103,17 @@ export function AurFloatingCard({
// Hide cards while exiting
if (isExiting && helperChosen) {
return (
<div className="fixed bottom-24 right-4 z-30 flex flex-col gap-3 items-end">
<div className="fixed top-4 right-4 z-30 flex flex-col gap-3 items-end">
<div
className="w-72 bg-[var(--bg-secondary)] backdrop-blur-xl border border-[var(--border-primary)]/60 rounded-2xl shadow-xl shadow-black/10 overflow-hidden"
style={{ animation: 'cardSlideOut 0.25s ease-out forwards' }}
style={{ animation: 'slideOutToRight 0.25s ease-out forwards' }}
>
<div className="p-4" />
</div>
{hasAnswered !== null && (
<div
className="w-72 bg-[var(--bg-secondary)] backdrop-blur-xl border border-[var(--border-primary)]/60 rounded-2xl shadow-xl shadow-black/10 overflow-hidden"
style={{ animation: 'cardSlideOut 0.2s ease-out forwards' }}
style={{ animation: 'slideOutToRight 0.2s ease-out forwards' }}
>
<div className="p-4" />
</div>
@@ -123,7 +123,7 @@ export function AurFloatingCard({
}
return (
<div className="fixed bottom-24 right-4 z-30 flex flex-col gap-3 items-end">
<div className="fixed top-4 right-4 z-30 flex flex-col gap-3 items-end">
{/* Card 1: Do you have an AUR helper? */}
<div
className={`
@@ -135,8 +135,8 @@ export function AurFloatingCard({
`}
style={{
animation: isExiting
? 'cardSlideOut 0.2s ease-out forwards'
: 'cardSlideIn 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) forwards'
? 'slideOutToRight 0.2s ease-out forwards'
: 'slideInFromRight 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) forwards'
}}
>
{/* Header */}
@@ -207,8 +207,8 @@ export function AurFloatingCard({
`}
style={{
animation: isExiting
? 'cardSlideOut 0.15s ease-out forwards'
: 'cardSlideInSecond 0.35s cubic-bezier(0.34, 1.56, 0.64, 1) 0.05s forwards',
? 'slideOutToRight 0.15s ease-out forwards'
: 'slideInFromRightSecond 0.35s cubic-bezier(0.34, 1.56, 0.64, 1) 0.05s forwards',
opacity: 0
}}
>

View File

@@ -166,8 +166,8 @@ export function CommandFooter({
};
return (
<div className="fixed bottom-0 left-0 right-0 p-3" style={{ zIndex: 10 }}>
{/* AUR Floating Card - appears when AUR packages selected */}
<>
{/* AUR Floating Card - appears when AUR packages selected (outside animated container) */}
<AurFloatingCard
show={showAur}
aurAppNames={aurAppNames}
@@ -177,241 +177,233 @@ export function CommandFooter({
setSelectedHelper={setSelectedHelper}
/>
{/* Footer container with strong outer glow */}
<div className="relative w-[85%] mx-auto">
{/* Outer glow - large spread */}
<div
className="absolute -inset-12 rounded-3xl pointer-events-none"
style={{
background: 'var(--bg-primary)',
filter: 'blur(40px)',
zIndex: -1
}}
/>
{/* Middle glow layer */}
<div
className="absolute -inset-8 rounded-3xl pointer-events-none"
style={{
background: 'var(--bg-primary)',
filter: 'blur(30px)',
zIndex: -1
}}
/>
{/* Inner glow - sharp */}
<div
className="absolute -inset-4 rounded-2xl pointer-events-none"
style={{
background: 'var(--bg-primary)',
filter: 'blur(20px)',
zIndex: -1
}}
/>
{/* Bars container */}
<div className="relative flex flex-col gap-1.5">
{/* Shortcuts Bar with Search (nvim-style) */}
<ShortcutsBar
searchQuery={searchQuery}
onSearchChange={onSearchChange}
searchInputRef={searchInputRef}
selectedCount={selectedCount}
distroName={distroDisplayName}
showAur={showAur}
selectedHelper={selectedHelper}
setSelectedHelper={setSelectedHelper}
{/* Slide-up Drawer - outside animated container for proper positioning */}
{drawerOpen && (
<>
{/* Backdrop */}
<div
className="fixed inset-0 bg-black/30 backdrop-blur-sm z-40"
onClick={closeDrawer}
aria-hidden="true"
style={{ animation: drawerClosing ? 'fadeOut 0.3s ease-out forwards' : 'fadeIn 0.3s ease-out' }}
/>
{/* Command Bar - Bufferline style tabs */}
<div className="bg-[var(--bg-tertiary)] font-mono text-xs rounded-lg overflow-hidden border border-[var(--border-primary)]/40 shadow-2xl">
<div className="flex items-stretch">
{/* Tab: Expand/Preview (opens drawer) */}
{/* Drawer */}
<div
role="dialog"
aria-modal="true"
aria-labelledby="drawer-title"
className="fixed z-50 bg-[var(--bg-secondary)] border border-[var(--border-primary)] shadow-2xl
bottom-0 left-0 right-0 rounded-t-2xl
md:bottom-auto md:top-1/2 md:left-1/2 md:-translate-x-1/2 md:-translate-y-1/2 md:rounded-2xl md:max-w-2xl md:w-[90vw]"
style={{
animation: drawerClosing
? 'slideDown 0.3s cubic-bezier(0.32, 0, 0.67, 0) forwards'
: 'slideUp 0.4s cubic-bezier(0.16, 1, 0.3, 1)',
maxHeight: '80vh'
}}
>
{/* Drawer Handle - mobile only */}
<div className="flex justify-center pt-3 pb-2 md:hidden">
<button
onClick={() => selectedCount > 0 && setDrawerOpen(true)}
disabled={selectedCount === 0}
className={`flex items-center gap-2 px-4 py-3 border-r border-[var(--border-primary)]/30 transition-all shrink-0 bg-indigo-500/10 text-indigo-400 hover:bg-indigo-500/20 hover:text-indigo-300 ${selectedCount === 0 ? 'opacity-50 cursor-not-allowed' : ''}`}
title="Toggle Preview (Tab)"
>
<ChevronUp className="w-3.5 h-3.5 shrink-0" />
<span className="font-bold whitespace-nowrap">PREVIEW</span>
{selectedCount > 0 && (
<span className="text-[10px] opacity-60 ml-0.5 whitespace-nowrap">[{selectedCount}]</span>
)}
</button>
className="w-12 h-1.5 bg-[var(--text-muted)]/40 rounded-full cursor-pointer hover:bg-[var(--text-muted)] transition-colors"
onClick={closeDrawer}
aria-label="Close drawer"
/>
</div>
{/* Command text - fills available space, centered both ways */}
<div
className="flex-1 min-w-0 flex items-center justify-center px-4 py-4 overflow-hidden bg-[var(--bg-secondary)] cursor-pointer hover:bg-[var(--bg-hover)] transition-colors group"
onClick={() => selectedCount > 0 && setDrawerOpen(true)}
>
<code className={`whitespace-nowrap overflow-x-auto command-scroll leading-none ${selectedCount > 0 ? 'text-[var(--text-primary)]' : 'text-[var(--text-muted)]'}`}>
{command}
</code>
{/* Drawer Header */}
<div className="flex items-center justify-between px-4 sm:px-6 pb-3 md:pt-4 border-b border-[var(--border-primary)]">
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-emerald-500/20 flex items-center justify-center">
<span className="text-emerald-500 font-bold text-sm">$</span>
</div>
<div>
<h3 id="drawer-title" className="text-sm font-semibold text-[var(--text-primary)]">Terminal Command</h3>
<p className="text-xs text-[var(--text-muted)]">{selectedCount} app{selectedCount !== 1 ? 's' : ''} Press Esc to close</p>
</div>
</div>
<button
onClick={closeDrawer}
className="w-8 h-8 flex items-center justify-center rounded-full hover:bg-[var(--bg-hover)] text-[var(--text-muted)] hover:text-[var(--text-primary)] transition-colors"
aria-label="Close drawer"
>
<X className="w-5 h-5" />
</button>
</div>
{/* Tab: Download */}
{/* Command Content */}
<div className="p-4 sm:p-6 overflow-y-auto" style={{ maxHeight: 'calc(80vh - 200px)' }}>
{/* AUR Settings (if AUR packages selected) */}
{showAur && (
<AurDrawerSettings
aurAppNames={aurAppNames}
hasYayInstalled={hasYayInstalled}
setHasYayInstalled={setHasYayInstalled}
selectedHelper={selectedHelper}
setSelectedHelper={setSelectedHelper}
/>
)}
<div className="bg-[#1a1a1a] rounded-xl overflow-hidden border border-[var(--border-primary)]">
<div className="flex items-center justify-between px-4 py-2 bg-[#252525] border-b border-[var(--border-primary)]">
<div className="flex items-center gap-2">
<div className="w-3 h-3 rounded-full bg-red-500/80" />
<div className="w-3 h-3 rounded-full bg-yellow-500/80" />
<div className="w-3 h-3 rounded-full bg-green-500/80" />
<span className="ml-2 text-xs text-[var(--text-muted)]">bash</span>
</div>
<div className="hidden md:flex items-center gap-2">
<button
onClick={handleDownload}
className="h-7 px-3 flex items-center gap-1.5 rounded-md bg-[var(--bg-tertiary)]/50 text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-tertiary)] transition-colors text-xs font-medium"
>
<Download className="w-3.5 h-3.5" />
Download
</button>
<button
onClick={() => { handleCopy(); setTimeout(closeDrawer, 3000); }}
className={`h-7 px-3 flex items-center gap-1.5 rounded-md text-xs font-medium transition-all ${copied
? 'bg-emerald-600 text-white'
: 'bg-emerald-600/20 text-emerald-400 hover:bg-emerald-600 hover:text-white'
}`}
>
{copied ? <Check className="w-3.5 h-3.5" /> : <Copy className="w-3.5 h-3.5" />}
{copied ? 'Copied!' : 'Copy'}
</button>
</div>
</div>
<div className="p-4 font-mono text-sm overflow-x-auto">
<div className="flex gap-2">
<span className="text-emerald-400 select-none shrink-0">$</span>
<code className="text-gray-300 break-all whitespace-pre-wrap" style={{ lineHeight: '1.6' }}>
{command}
</code>
</div>
</div>
</div>
</div>
{/* Mobile Actions */}
<div className="md:hidden flex flex-col items-stretch gap-3 px-4 py-4 border-t border-[var(--border-primary)]">
<button
onClick={handleDownload}
disabled={selectedCount === 0}
onMouseEnter={() => selectedCount > 0 && setShowDownloadTooltip(true)}
onMouseLeave={() => setShowDownloadTooltip(false)}
className={`flex items-center gap-1.5 px-3 py-3 border-l border-[var(--border-primary)]/30 transition-colors ${selectedCount > 0
? 'text-[var(--text-secondary)] hover:bg-[var(--bg-secondary)] hover:text-[var(--text-primary)]'
: 'text-[var(--text-muted)] opacity-50 cursor-not-allowed'
}`}
title="Download Script (d)"
className="flex-1 h-14 flex items-center justify-center gap-2 rounded-xl bg-[var(--bg-tertiary)] text-[var(--text-secondary)] hover:bg-[var(--bg-hover)] transition-colors font-medium text-base"
>
<Download className="w-3 h-3 shrink-0" />
<span className="hidden sm:inline whitespace-nowrap">Download</span>
<Download className="w-5 h-5" />
Download Script
</button>
{/* Tab: Copy (highlighted) */}
<button
onClick={handleCopy}
disabled={selectedCount === 0}
className={`flex items-center gap-1.5 px-3 py-3 border-l border-[var(--border-primary)]/30 transition-colors ${selectedCount > 0
? (copied
? 'bg-emerald-600 text-white'
: 'bg-[var(--text-primary)] text-[var(--bg-primary)] hover:opacity-90')
: 'text-[var(--text-muted)] opacity-50 cursor-not-allowed'
onClick={() => { handleCopy(); setTimeout(closeDrawer, 3000); }}
className={`flex-1 h-14 flex items-center justify-center gap-2 rounded-xl font-medium text-base transition-all ${copied
? 'bg-emerald-600 text-white'
: 'bg-[var(--text-primary)] text-[var(--bg-primary)] hover:opacity-90'
}`}
title="Copy Command (y)"
>
{copied ? <Check className="w-3 h-3 shrink-0" /> : <Copy className="w-3 h-3 shrink-0" />}
<span className="hidden sm:inline whitespace-nowrap">{copied ? 'Copied!' : 'Copy'}</span>
{copied ? <Check className="w-5 h-5" /> : <Copy className="w-5 h-5" />}
{copied ? 'Copied!' : 'Copy Command'}
</button>
</div>
</div>
</>
)}
{/* Slide-up Drawer */}
{drawerOpen && (
<>
{/* Backdrop */}
<div
className="fixed inset-0 bg-black/30 backdrop-blur-sm z-40"
onClick={closeDrawer}
aria-hidden="true"
style={{ animation: drawerClosing ? 'fadeOut 0.3s ease-out forwards' : 'fadeIn 0.3s ease-out' }}
/>
{/* Drawer */}
<div
role="dialog"
aria-modal="true"
aria-labelledby="drawer-title"
className="fixed z-50 bg-[var(--bg-secondary)] border border-[var(--border-primary)] shadow-2xl
bottom-0 left-0 right-0 rounded-t-2xl
md:bottom-auto md:top-1/2 md:left-1/2 md:-translate-x-1/2 md:-translate-y-1/2 md:rounded-2xl md:max-w-2xl md:w-[90vw]"
style={{
animation: drawerClosing
? 'slideDown 0.3s cubic-bezier(0.32, 0, 0.67, 0) forwards'
: 'slideUp 0.4s cubic-bezier(0.16, 1, 0.3, 1)',
maxHeight: '80vh'
}}
>
{/* Drawer Handle - mobile only */}
<div className="flex justify-center pt-3 pb-2 md:hidden">
<button
className="w-12 h-1.5 bg-[var(--text-muted)]/40 rounded-full cursor-pointer hover:bg-[var(--text-muted)] transition-colors"
onClick={closeDrawer}
aria-label="Close drawer"
/>
</div>
{/* Animated footer container */}
<div
className="fixed bottom-0 left-0 right-0 p-3"
style={{
zIndex: 10,
animation: 'footerSlideUp 0.5s cubic-bezier(0.16, 1, 0.3, 1) 0.3s both'
}}
>
{/* Footer container with glow */}
<div className="relative w-[85%] mx-auto">
{/* Soft glow behind bars */}
<div
className="absolute -inset-12 pointer-events-none"
style={{
background: 'var(--bg-primary)',
filter: 'blur(40px)',
opacity: 1,
zIndex: -1
}}
/>
{/* Drawer Header */}
<div className="flex items-center justify-between px-4 sm:px-6 pb-3 md:pt-4 border-b border-[var(--border-primary)]">
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-emerald-500/20 flex items-center justify-center">
<span className="text-emerald-500 font-bold text-sm">$</span>
</div>
<div>
<h3 id="drawer-title" className="text-sm font-semibold text-[var(--text-primary)]">Terminal Command</h3>
<p className="text-xs text-[var(--text-muted)]">{selectedCount} app{selectedCount !== 1 ? 's' : ''} Press Esc to close</p>
</div>
</div>
<button
onClick={closeDrawer}
className="w-8 h-8 flex items-center justify-center rounded-full hover:bg-[var(--bg-hover)] text-[var(--text-muted)] hover:text-[var(--text-primary)] transition-colors"
aria-label="Close drawer"
>
<X className="w-5 h-5" />
</button>
</div>
{/* Bars container */}
<div className="relative flex flex-col gap-1.5">
{/* Shortcuts Bar with Search (nvim-style) */}
<ShortcutsBar
searchQuery={searchQuery}
onSearchChange={onSearchChange}
searchInputRef={searchInputRef}
selectedCount={selectedCount}
distroName={distroDisplayName}
showAur={showAur}
selectedHelper={selectedHelper}
setSelectedHelper={setSelectedHelper}
/>
{/* Command Content */}
<div className="p-4 sm:p-6 overflow-y-auto" style={{ maxHeight: 'calc(80vh - 200px)' }}>
{/* AUR Settings (if AUR packages selected) */}
{showAur && (
<AurDrawerSettings
aurAppNames={aurAppNames}
hasYayInstalled={hasYayInstalled}
setHasYayInstalled={setHasYayInstalled}
selectedHelper={selectedHelper}
setSelectedHelper={setSelectedHelper}
/>
{/* Command Bar - Bufferline style tabs */}
<div className="bg-[var(--bg-tertiary)] font-mono text-xs rounded-lg overflow-hidden border border-[var(--border-primary)]/40 shadow-2xl">
<div className="flex items-stretch">
{/* Tab: Expand/Preview (opens drawer) */}
<button
onClick={() => selectedCount > 0 && setDrawerOpen(true)}
disabled={selectedCount === 0}
className={`flex items-center gap-2 px-4 py-3 border-r border-[var(--border-primary)]/30 transition-all shrink-0 bg-indigo-500/10 text-indigo-400 hover:bg-indigo-500/20 hover:text-indigo-300 ${selectedCount === 0 ? 'opacity-50 cursor-not-allowed' : ''}`}
title="Toggle Preview (Tab)"
>
<ChevronUp className="w-3.5 h-3.5 shrink-0" />
<span className="font-bold whitespace-nowrap">PREVIEW</span>
{selectedCount > 0 && (
<span className="text-[10px] opacity-60 ml-0.5 whitespace-nowrap">[{selectedCount}]</span>
)}
<div className="bg-[#1a1a1a] rounded-xl overflow-hidden border border-[var(--border-primary)]">
<div className="flex items-center justify-between px-4 py-2 bg-[#252525] border-b border-[var(--border-primary)]">
<div className="flex items-center gap-2">
<div className="w-3 h-3 rounded-full bg-red-500/80" />
<div className="w-3 h-3 rounded-full bg-yellow-500/80" />
<div className="w-3 h-3 rounded-full bg-green-500/80" />
<span className="ml-2 text-xs text-[var(--text-muted)]">bash</span>
</div>
<div className="hidden md:flex items-center gap-2">
<button
onClick={handleDownload}
className="h-7 px-3 flex items-center gap-1.5 rounded-md bg-[var(--bg-tertiary)]/50 text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-tertiary)] transition-colors text-xs font-medium"
>
<Download className="w-3.5 h-3.5" />
Download
</button>
<button
onClick={() => { handleCopy(); setTimeout(closeDrawer, 3000); }}
className={`h-7 px-3 flex items-center gap-1.5 rounded-md text-xs font-medium transition-all ${copied
? 'bg-emerald-600 text-white'
: 'bg-emerald-600/20 text-emerald-400 hover:bg-emerald-600 hover:text-white'
}`}
>
{copied ? <Check className="w-3.5 h-3.5" /> : <Copy className="w-3.5 h-3.5" />}
{copied ? 'Copied!' : 'Copy'}
</button>
</div>
</div>
<div className="p-4 font-mono text-sm overflow-x-auto">
<div className="flex gap-2">
<span className="text-emerald-400 select-none shrink-0">$</span>
<code className="text-gray-300 break-all whitespace-pre-wrap" style={{ lineHeight: '1.6' }}>
{command}
</code>
</div>
</div>
</div>
</button>
{/* Command text - fills available space, centered both ways */}
<div
className="flex-1 min-w-0 flex items-center justify-center px-4 py-4 overflow-hidden bg-[var(--bg-secondary)] cursor-pointer hover:bg-[var(--bg-hover)] transition-colors group"
onClick={() => selectedCount > 0 && setDrawerOpen(true)}
>
<code className={`whitespace-nowrap overflow-x-auto command-scroll leading-none ${selectedCount > 0 ? 'text-[var(--text-primary)]' : 'text-[var(--text-muted)]'}`}>
{command}
</code>
</div>
{/* Mobile Actions */}
<div className="md:hidden flex flex-col items-stretch gap-3 px-4 py-4 border-t border-[var(--border-primary)]">
<button
onClick={handleDownload}
className="flex-1 h-14 flex items-center justify-center gap-2 rounded-xl bg-[var(--bg-tertiary)] text-[var(--text-secondary)] hover:bg-[var(--bg-hover)] transition-colors font-medium text-base"
>
<Download className="w-5 h-5" />
Download Script
</button>
<button
onClick={() => { handleCopy(); setTimeout(closeDrawer, 3000); }}
className={`flex-1 h-14 flex items-center justify-center gap-2 rounded-xl font-medium text-base transition-all ${copied
{/* Tab: Download */}
<button
onClick={handleDownload}
disabled={selectedCount === 0}
onMouseEnter={() => selectedCount > 0 && setShowDownloadTooltip(true)}
onMouseLeave={() => setShowDownloadTooltip(false)}
className={`flex items-center gap-1.5 px-3 py-3 border-l border-[var(--border-primary)]/30 transition-colors ${selectedCount > 0
? 'text-[var(--text-secondary)] hover:bg-[var(--bg-secondary)] hover:text-[var(--text-primary)]'
: 'text-[var(--text-muted)] opacity-50 cursor-not-allowed'
}`}
title="Download Script (d)"
>
<Download className="w-3 h-3 shrink-0" />
<span className="hidden sm:inline whitespace-nowrap">Download</span>
</button>
{/* Tab: Copy (highlighted) */}
<button
onClick={handleCopy}
disabled={selectedCount === 0}
className={`flex items-center gap-1.5 px-3 py-3 border-l border-[var(--border-primary)]/30 transition-colors ${selectedCount > 0
? (copied
? 'bg-emerald-600 text-white'
: 'bg-[var(--text-primary)] text-[var(--bg-primary)] hover:opacity-90'
}`}
>
{copied ? <Check className="w-5 h-5" /> : <Copy className="w-5 h-5" />}
{copied ? 'Copied!' : 'Copy Command'}
</button>
</div>
: 'bg-[var(--text-primary)] text-[var(--bg-primary)] hover:opacity-90')
: 'text-[var(--text-muted)] opacity-50 cursor-not-allowed'
}`}
title="Copy Command (y)"
>
{copied ? <Check className="w-3 h-3 shrink-0" /> : <Copy className="w-3 h-3 shrink-0" />}
<span className="hidden sm:inline whitespace-nowrap">{copied ? 'Copied!' : 'Copy'}</span>
</button>
</div>
</>
)}
</div>
</div>
</div>
</div>
</div>
</>
);
}

View File

@@ -17,6 +17,11 @@ interface ShortcutsBarProps {
/**
* ShortcutsBar - Neovim-style statusline with search on left, shortcuts on right
* Uses theme-aware colors for dark/light mode compatibility
*
* Enhancements:
* - Cleaner grouping of shortcuts
* - Better visual hierarchy
* - Softer separators
*/
export const ShortcutsBar = forwardRef<HTMLInputElement, ShortcutsBarProps>(
function ShortcutsBar({
@@ -37,13 +42,6 @@ export const ShortcutsBar = forwardRef<HTMLInputElement, ShortcutsBarProps>(
}
};
const helperShortcuts = showAur ? [
{ key: '1', label: 'yay' },
{ key: '2', label: 'paru' },
] : [];
return (
<div className="bg-[var(--bg-tertiary)] border border-[var(--border-primary)] font-mono text-xs rounded-lg overflow-hidden">
<div className="flex items-stretch justify-between">
@@ -94,67 +92,46 @@ export const ShortcutsBar = forwardRef<HTMLInputElement, ShortcutsBarProps>(
<div className="flex items-stretch border-r border-[var(--border-primary)]/30">
<button
onClick={() => setSelectedHelper('yay')}
className={`px-3 flex items-center gap-2 text-[10px] font-medium transition-colors border-r border-[var(--border-primary)]/30 whitespace-nowrap ${selectedHelper === 'yay' ? 'bg-[var(--text-primary)] text-[var(--bg-primary)] font-bold' : 'text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-secondary)]'}`}
className={`px-3 flex items-center gap-1.5 text-[10px] font-medium transition-colors border-r border-[var(--border-primary)]/30 whitespace-nowrap ${selectedHelper === 'yay' ? 'bg-[var(--text-primary)] text-[var(--bg-primary)] font-bold' : 'text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-secondary)]'}`}
>
<span className="font-mono opacity-70">1</span>
<span className="font-mono opacity-50">1</span>
yay
</button>
<button
onClick={() => setSelectedHelper('paru')}
className={`px-3 flex items-center gap-2 text-[10px] font-medium transition-colors whitespace-nowrap ${selectedHelper === 'paru' ? 'bg-[var(--text-primary)] text-[var(--bg-primary)] font-bold' : 'text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-secondary)]'}`}
className={`px-3 flex items-center gap-1.5 text-[10px] font-medium transition-colors whitespace-nowrap ${selectedHelper === 'paru' ? 'bg-[var(--text-primary)] text-[var(--bg-primary)] font-bold' : 'text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-secondary)]'}`}
>
<span className="font-mono opacity-70">2</span>
<span className="font-mono opacity-50">2</span>
paru
</button>
</div>
)}
</div>
{/* RIGHT SECTION - Shortcuts */}
{/* RIGHT SECTION - Compact Shortcuts */}
<div className="flex items-stretch">
<div className="hidden sm:flex items-center gap-4 px-4 py-1 text-[var(--text-muted)] text-[11px] font-medium border-l border-[var(--border-primary)]/30">
{/* Navigation Group */}
<div className="hidden lg:flex items-center gap-1.5 transition-opacity hover:opacity-100">
<span className="font-mono text-[10px] tracking-widest text-[var(--text-muted)]">NAV</span>
<div className="flex items-center gap-1 font-mono text-[var(--text-primary)]">
<span></span>
<span className="opacity-50">/</span>
<span>hjkl</span>
</div>
</div>
{/* Separator */}
<div className="w-px h-3 bg-[var(--border-primary)]/40 hidden lg:block"></div>
{/* Actions Group */}
<div className="flex items-center gap-4">
{[...helperShortcuts,
{ key: '/', label: 'Search' },
{ key: 'Space', label: 'Toggle' },
{ key: 'y', label: 'Copy' },
{ key: 'd', label: 'Download' },
{ key: 'c', label: 'Clear' },
{ key: 't', label: 'Theme' }
].map(({ key, label }) => (
<div key={key} className="flex items-center gap-1.5 group cursor-help transition-colors hover:text-[var(--text-primary)]">
<span className={`font-mono font-bold transition-colors ${showAur && (key === '1' || key === '2') ? 'text-[#1793d1]' : 'text-[var(--text-primary)] group-hover:text-[#1793d1]'}`}>
{key}
</span>
<span className="opacity-60 group-hover:opacity-100 transition-opacity">{label}</span>
</div>
))}
<div className="flex items-center gap-4 border-l border-[var(--border-primary)]/40 pl-4">
<div className="flex items-center gap-1.5 transition-colors hover:text-[var(--text-primary)]">
<span className="font-mono font-bold text-[var(--text-primary)]">Esc</span>
<span className="opacity-60">Back</span>
</div>
<div className="flex items-center gap-1.5 transition-colors hover:text-[var(--text-primary)]">
<span className="font-mono font-bold text-[var(--text-primary)]">Tab</span>
<span className="opacity-60">Preview</span>
</div>
</div>
</div>
<div className="hidden sm:flex items-center gap-3 px-3 py-1 text-[var(--text-muted)] text-[10px] border-l border-[var(--border-primary)]/30">
{/* Navigation */}
<span className="hidden lg:inline"><b className="text-[var(--text-secondary)]"> </b>/<b className="text-[var(--text-secondary)]"> hjkl</b> Navigation</span>
<span className="hidden lg:inline opacity-30">·</span>
{/* Actions */}
<span><b className="text-[var(--text-secondary)]">/</b> search</span>
<span className="opacity-30">·</span>
<span><b className="text-[var(--text-secondary)]">Space</b> toggle</span>
<span className="opacity-30">·</span>
<span><b className="text-[var(--text-secondary)]">y</b> copy</span>
<span className="opacity-30">·</span>
<span><b className="text-[var(--text-secondary)]">d</b> download</span>
<span className="opacity-30">·</span>
<span><b className="text-[var(--text-secondary)]">c</b> clear</span>
<span className="opacity-30">·</span>
<span><b className="text-[var(--text-secondary)]">t</b> theme</span>
<span className="opacity-30">·</span>
<span><b className="text-[var(--text-secondary)]">Tab</b> preview</span>
<span className="opacity-30">·</span>
<span><b className="text-[var(--text-secondary)]">Esc</b> back</span>
<span className="opacity-30">·</span>
<span><b className="text-[var(--text-secondary)]">?</b> help</span>
</div>
{/* End badge - like nvim line:col */}

View File

@@ -3,79 +3,166 @@
/**
* LoadingSkeleton - Placeholder UI while localStorage hydrates
*
* Shows animated skeleton blocks mimicking the app grid layout
* to prevent layout shift and provide visual feedback during loading.
* Shows animated skeleton blocks mimicking the app grid layout.
* Uses INLINE keyframe animation to ensure buttery smooth animation
* from the very first render (before CSS is fully loaded/parsed).
*/
export function LoadingSkeleton() {
return (
<div className="min-h-screen bg-[var(--bg-primary)] text-[var(--text-primary)]">
{/* Header Skeleton */}
<header className="pt-8 sm:pt-12 pb-8 sm:pb-10 px-4 sm:px-6">
<div className="max-w-6xl mx-auto">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div className="flex items-start gap-4">
{/* Logo placeholder */}
<div className="w-16 h-16 sm:w-[72px] sm:h-[72px] rounded-xl bg-[var(--bg-tertiary)] animate-pulse" />
<div className="flex flex-col gap-2">
<div className="h-6 w-32 bg-[var(--bg-tertiary)] rounded animate-pulse" />
<div className="h-3 w-48 bg-[var(--bg-tertiary)] rounded animate-pulse" />
<div className="h-3 w-24 bg-[var(--bg-tertiary)] rounded animate-pulse" />
<>
{/* Inline keyframes for immediate animation - no external CSS needed */}
<style jsx global>{`
@keyframes skeletonShimmer {
0%, 100% { opacity: 0.4; }
50% { opacity: 0.7; }
}
.sk-pulse {
animation: skeletonShimmer 1.5s ease-in-out infinite;
will-change: opacity;
}
`}</style>
<div className="min-h-screen bg-[var(--bg-primary)] text-[var(--text-primary)]">
{/* Header Skeleton */}
<header className="pt-8 sm:pt-12 pb-8 sm:pb-10 px-4 sm:px-6">
<div className="max-w-7xl mx-auto">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div className="flex items-start gap-4">
{/* Logo placeholder */}
<div
className="w-16 h-16 sm:w-[72px] sm:h-[72px] rounded-xl bg-[var(--bg-tertiary)] sk-pulse"
/>
<div className="flex flex-col gap-2">
<div className="h-6 w-32 bg-[var(--bg-tertiary)] rounded sk-pulse" />
<div
className="h-3 w-48 bg-[var(--bg-tertiary)] rounded sk-pulse"
style={{ animationDelay: '0.1s' }}
/>
<div
className="h-3 w-36 bg-[var(--bg-tertiary)] rounded sk-pulse"
style={{ animationDelay: '0.2s' }}
/>
</div>
</div>
<div className="flex items-center gap-3">
<div
className="h-6 w-16 bg-[var(--bg-tertiary)] rounded sk-pulse"
style={{ animationDelay: '0.1s' }}
/>
<div
className="h-6 w-16 bg-[var(--bg-tertiary)] rounded sk-pulse"
style={{ animationDelay: '0.15s' }}
/>
<div className="w-px h-6 bg-[var(--border-primary)]" />
<div
className="h-6 w-12 bg-[var(--bg-tertiary)] rounded-full sk-pulse"
style={{ animationDelay: '0.2s' }}
/>
<div
className="h-10 w-28 bg-[var(--bg-tertiary)] rounded-2xl sk-pulse"
style={{ animationDelay: '0.25s' }}
/>
</div>
</div>
<div className="flex items-center gap-3">
<div className="h-8 w-20 bg-[var(--bg-tertiary)] rounded-lg animate-pulse" />
<div className="h-10 w-28 bg-[var(--bg-tertiary)] rounded-2xl animate-pulse" />
</div>
</div>
</div>
</header>
</header>
{/* Grid Skeleton */}
<main className="px-4 sm:px-6 pb-24">
<div className="max-w-6xl mx-auto">
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-x-4 sm:gap-x-8">
{[...Array(5)].map((_, colIdx) => (
<div key={colIdx} className="space-y-5">
{[...Array(3)].map((_, catIdx) => (
<div key={catIdx} className="mb-5">
{/* Category header skeleton */}
<div className="flex items-center gap-2 mb-3 pb-1.5 border-b border-[var(--border-primary)]">
<div className="w-3 h-3 bg-[var(--bg-tertiary)] rounded animate-pulse" />
<div className="h-3 w-20 bg-[var(--bg-tertiary)] rounded animate-pulse" />
</div>
{/* App items skeleton */}
{[...Array(4 + catIdx)].map((_, appIdx) => (
<div
key={appIdx}
className="flex items-center gap-2.5 py-1.5 px-2"
style={{ animationDelay: `${(colIdx * 3 + catIdx) * 50 + appIdx * 20}ms` }}
>
<div className="w-4 h-4 rounded border-2 border-[var(--bg-tertiary)] animate-pulse" />
<div className="w-5 h-5 rounded bg-[var(--bg-tertiary)] animate-pulse" />
{/* Grid Skeleton */}
<main className="px-4 sm:px-6 pb-40">
<div className="max-w-7xl mx-auto">
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-x-4 sm:gap-x-8">
{[...Array(5)].map((_, colIdx) => (
<div key={colIdx} className="space-y-5">
{[...Array(3)].map((_, catIdx) => (
<div key={catIdx} className="mb-5">
{/* Category header skeleton */}
<div className="flex items-center gap-2 mb-3 pb-1.5 border-b border-[var(--border-primary)]">
<div
className="h-4 bg-[var(--bg-tertiary)] rounded animate-pulse"
style={{ width: `${70 + (appIdx % 3) * 10}%` }}
className="w-3 h-3 bg-[var(--bg-tertiary)] rounded sk-pulse"
style={{ animationDelay: `${colIdx * 0.08}s` }}
/>
<div
className="h-3 w-20 bg-[var(--bg-tertiary)] rounded sk-pulse"
style={{ animationDelay: `${colIdx * 0.08 + 0.03}s` }}
/>
</div>
))}
</div>
))}
</div>
))}
{/* App items skeleton */}
{[...Array(3 + catIdx)].map((_, appIdx) => (
<div
key={appIdx}
className="flex items-center gap-2.5 py-1.5 px-2"
>
<div
className="w-4 h-4 rounded border-2 border-[var(--bg-tertiary)] sk-pulse"
style={{ animationDelay: `${colIdx * 0.08 + appIdx * 0.02}s` }}
/>
<div
className="w-5 h-5 rounded bg-[var(--bg-tertiary)] sk-pulse"
style={{ animationDelay: `${colIdx * 0.08 + appIdx * 0.02 + 0.01}s` }}
/>
<div
className="h-4 bg-[var(--bg-tertiary)] rounded sk-pulse"
style={{
width: `${60 + (appIdx % 4) * 10}%`,
animationDelay: `${colIdx * 0.08 + appIdx * 0.02 + 0.02}s`
}}
/>
</div>
))}
</div>
))}
</div>
))}
</div>
</div>
</div>
</main>
</main>
{/* Footer Skeleton */}
<div className="fixed bottom-0 left-0 right-0 h-16 bg-[var(--bg-secondary)] border-t border-[var(--border-primary)]">
<div className="max-w-6xl mx-auto h-full flex items-center justify-between px-4 sm:px-6">
<div className="h-10 flex-1 mr-4 bg-[var(--bg-tertiary)] rounded-lg animate-pulse" />
<div className="flex gap-2">
<div className="w-10 h-10 bg-[var(--bg-tertiary)] rounded-lg animate-pulse" />
<div className="w-10 h-10 bg-[var(--bg-tertiary)] rounded-lg animate-pulse" />
{/* Footer Skeleton - Matches new nvim-style footer */}
<div className="fixed bottom-0 left-0 right-0 p-3">
<div className="relative w-[85%] mx-auto flex flex-col gap-1.5">
{/* ShortcutsBar skeleton */}
<div className="bg-[var(--bg-tertiary)] border border-[var(--border-primary)] rounded-lg overflow-hidden">
<div className="flex items-center justify-between h-8">
<div className="flex items-center">
<div className="w-20 h-full bg-[var(--text-primary)]/20 sk-pulse" />
<div className="flex items-center gap-2 px-3">
<div className="w-3 h-3 bg-[var(--bg-secondary)] rounded sk-pulse" />
<div className="w-20 h-3 bg-[var(--bg-secondary)] rounded sk-pulse" />
</div>
</div>
<div className="flex items-center gap-4 px-4">
<div className="w-32 h-3 bg-[var(--bg-secondary)] rounded sk-pulse hidden sm:block" />
<div className="w-12 h-full bg-[var(--text-primary)]/20 sk-pulse" />
</div>
</div>
</div>
{/* Command Bar skeleton */}
<div className="bg-[var(--bg-tertiary)] border border-[var(--border-primary)]/40 rounded-lg overflow-hidden">
<div className="flex items-center h-11">
<div
className="w-24 h-full bg-indigo-500/10 sk-pulse flex-shrink-0"
style={{ animationDelay: '0.1s' }}
/>
<div className="flex-1 flex items-center justify-center px-4 bg-[var(--bg-secondary)]">
<div
className="w-48 h-4 bg-[var(--bg-tertiary)] rounded sk-pulse"
style={{ animationDelay: '0.15s' }}
/>
</div>
<div
className="w-20 h-full bg-[var(--bg-tertiary)] sk-pulse flex-shrink-0"
style={{ animationDelay: '0.2s' }}
/>
<div
className="w-16 h-full bg-[var(--text-primary)]/20 sk-pulse flex-shrink-0"
style={{ animationDelay: '0.25s' }}
/>
</div>
</div>
</div>
</div>
</div>
</div>
</>
);
}

View File

@@ -1,114 +1,104 @@
'use client';
/**
* Tooltip - Positioned tooltip with markdown-like formatting
*
* Features:
* - Supports inline code, bold text, and links
* - Slide-up animation
* - Arrow pointer
* - Hover persistence (tooltip stays visible when hovered)
*
* @param tooltip - Tooltip data (text, position, key)
* @param onEnter - Callback when mouse enters tooltip
* @param onLeave - Callback when mouse leaves tooltip
*
* @example
* <Tooltip
* tooltip={{ text: "Hello **world**", x: 100, y: 200, key: 1 }}
* onEnter={() => {}}
* onLeave={() => {}}
* />
*/
/**
* Renders a single line with inline formatting
*/
function renderLine(text: string) {
// Split by code, links, and bold
const parts = text.split(/(`[^`]+`|\[.*?\]\(.*?\)|\*\*.*?\*\*)/);
return parts.map((part, i) => {
// Check for inline code
const codeMatch = part.match(/^`([^`]+)`$/);
if (codeMatch) {
return (
<code key={i} className="bg-[var(--bg-primary)] px-1.5 py-0.5 rounded text-[var(--accent)] font-mono text-[10px] select-all break-all">
{codeMatch[1]}
</code>
);
}
// Check for bold
const boldMatch = part.match(/^\*\*(.*?)\*\*$/);
if (boldMatch) {
return <strong key={i} className="font-semibold text-[var(--text-primary)]">{boldMatch[1]}</strong>;
}
// Check for links
const linkMatch = part.match(/\[(.*?)\]\((.*?)\)/);
if (linkMatch) {
return (
<a key={i} href={linkMatch[2]} target="_blank" rel="noopener noreferrer"
className="text-[var(--accent)] underline hover:opacity-80">
{linkMatch[1]}
</a>
);
}
return <span key={i}>{part}</span>;
});
}
/**
* Renders tooltip content with newline support
*/
function renderTooltipContent(text: string) {
// First handle escaped newlines
const lines = text.split(/\\n/);
return lines.map((line, lineIdx) => (
<span key={lineIdx}>
{lineIdx > 0 && <br />}
{renderLine(line)}
</span>
));
}
import React from 'react';
export interface TooltipData {
text: string;
x: number;
y: number;
width?: number;
key?: number;
width: number;
key: number;
}
export function Tooltip({
tooltip,
onEnter,
onLeave
}: {
interface TooltipProps {
tooltip: TooltipData | null;
onEnter: () => void;
onLeave: () => void;
}) {
onEnter?: () => void;
onLeave?: () => void;
}
/**
* Tooltip - Global tooltip component with refined styling and animation
*
* Features:
* - Fixed positioning based on element coordinates
* - "Warm paper" aesthetic styling
* - Smooth entry animation
* - Markdown text rendering (bold, code, links)
* - Max width with wrapping
*/
export function Tooltip({ tooltip, onEnter, onLeave }: TooltipProps) {
if (!tooltip) return null;
// Center horizontally relative to the element
const left = tooltip.x;
const top = tooltip.y;
// Helper to render markdown content
const renderContent = (text: string) => {
// Split by **bold**, `code`, or [link](url)
return text.split(/(\*\*.*?\*\*|`.*?`|\[.*?\]\(.*?\))/g).map((part, i) => {
// Bold
if (part.startsWith('**') && part.endsWith('**')) {
return (
<strong key={i} className="font-bold text-[var(--accent)]">
{part.slice(2, -2)}
</strong>
);
}
// Code
if (part.startsWith('`') && part.endsWith('`')) {
return (
<code key={i} className="bg-[var(--bg-secondary)] px-1 rounded font-mono text-[var(--accent)] text-[10px]">
{part.slice(1, -1)}
</code>
);
}
// Link
if (part.startsWith('[') && part.includes('](') && part.endsWith(')')) {
const match = part.match(/\[(.*?)\]\((.*?)\)/);
if (match) {
return (
<a
key={i}
href={match[2]}
target="_blank"
rel="noopener noreferrer"
className="text-[var(--accent)] underline decoration-[var(--accent)]/50 hover:decoration-[var(--accent)] font-semibold transition-all hover:text-emerald-500"
onClick={(e) => e.stopPropagation()} // Prevent triggering parent clicks
>
{match[1]}
</a>
);
}
}
return <span key={i}>{part}</span>;
});
};
return (
<div
key={tooltip.key}
role="tooltip"
className="fixed z-50 pointer-events-auto"
style={{
left: left,
top: top,
transform: 'translate(-50%, -100%)',
// Using the specific key ensures fresh animation on new tooltip
animation: 'tooltipSlideUp 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards',
maxWidth: '400px', // Limit width
width: 'max-content'
}}
onMouseEnter={onEnter}
onMouseLeave={onLeave}
className="fixed px-3 py-2.5 bg-[var(--bg-tertiary)] text-[var(--text-secondary)] text-xs rounded-lg shadow-xl border border-[var(--border-secondary)] max-w-[320px] leading-relaxed"
style={{
left: tooltip.x,
top: tooltip.y,
transform: 'translate(-50%, -100%)',
zIndex: 99999,
animation: 'tooltipSlideUp 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards',
}}>
{renderTooltipContent(tooltip.text)}
{/* Arrow pointer */}
<div
className="absolute left-1/2 -translate-x-1/2 w-3 h-3 bg-[var(--bg-tertiary)] border-r border-b border-[var(--border-secondary)] rotate-45"
style={{ bottom: '-7px' }}
/>
>
<div className="relative mb-2 px-3 py-2 rounded-lg bg-[var(--bg-tertiary)] text-[var(--text-primary)] text-xs font-medium shadow-xl border border-[var(--border-primary)]/40 backdrop-blur-sm whitespace-normal break-words leading-relaxed">
{renderContent(tooltip.text)}
{/* Arrow pointer */}
<div
className="absolute -bottom-1 left-1/2 -translate-x-1/2 w-2 h-2 bg-[var(--bg-tertiary)] border-b border-r border-[var(--border-primary)]/40 rotate-45"
/>
</div>
</div>
);
}

View File

@@ -6,234 +6,286 @@ import { HelpCircle, X } from 'lucide-react';
import { analytics } from '@/lib/analytics';
/**
* HowItWorks - Interactive help popup with quick start guide
* HowItWorks - Interactive help modal with quick start guide
*
* Displays a popup with:
* Displays a centered modal with backdrop blur, containing:
* - Quick start steps for using TuxMate
* - Info about unavailable apps
* - Arch/AUR specific info
* - Keyboard shortcuts
* - Pro tips
*
* @example
* <HowItWorks />
*/
export function HowItWorks() {
const [isOpen, setIsOpen] = useState(false);
const [isClosing, setIsClosing] = useState(false);
const [mounted, setMounted] = useState(false);
const triggerRef = useRef<HTMLButtonElement>(null);
const popupRef = useRef<HTMLDivElement>(null);
useEffect(() => {
setMounted(true);
}, []);
// Close on click outside
// Lock body scroll when modal is open
useEffect(() => {
if (!isOpen) return;
const handleClickOutside = (e: MouseEvent) => {
if (
triggerRef.current && !triggerRef.current.contains(e.target as Node) &&
popupRef.current && !popupRef.current.contains(e.target as Node)
) {
setIsOpen(false);
if (isOpen) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = '';
}
return () => {
document.body.style.overflow = '';
};
}, [isOpen]);
// Global keyboard shortcut: ? to toggle modal
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
// Ignore if typing in input
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
if (e.key === '?' || (e.shiftKey && e.key === '/')) {
e.preventDefault();
if (isOpen) {
handleClose();
} else {
handleOpen();
}
}
// Close on Escape
if (e.key === 'Escape' && isOpen) {
handleClose();
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, [isOpen]);
// Close on Escape
useEffect(() => {
if (!isOpen) return;
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape') setIsOpen(false);
};
document.addEventListener('keydown', handleEscape);
return () => document.removeEventListener('keydown', handleEscape);
}, [isOpen]);
const getPopupPosition = () => {
if (!triggerRef.current) return { top: 0, left: 0 };
const rect = triggerRef.current.getBoundingClientRect();
return {
top: rect.bottom + 12,
left: Math.max(8, Math.min(rect.left, window.innerWidth - 420)),
};
const handleOpen = () => {
setIsClosing(false);
setIsOpen(true);
analytics.helpOpened();
};
const pos = isOpen ? getPopupPosition() : { top: 0, left: 0 };
const handleClose = () => {
setIsClosing(true);
analytics.helpClosed();
// Wait for exit animation to finish
setTimeout(() => {
setIsOpen(false);
setIsClosing(false);
}, 200);
};
const popup = isOpen && mounted ? (
<div
ref={popupRef}
className="how-it-works-popup bg-[var(--bg-secondary)] backdrop-blur-xl border border-[var(--border-primary)] shadow-2xl"
style={{
position: 'fixed',
top: pos.top,
left: pos.left,
zIndex: 99999,
borderRadius: '16px',
width: '400px',
maxWidth: 'calc(100vw - 16px)',
maxHeight: 'min(70vh, 600px)',
display: 'flex',
flexDirection: 'column',
animation: 'popupSlideIn 0.3s cubic-bezier(0.16, 1, 0.3, 1)',
overflow: 'hidden',
}}
>
{/* Header - fixed */}
<div className="flex items-center justify-between gap-2 p-4 pb-3 border-b border-[var(--border-primary)] shrink-0">
<div className="flex items-center gap-2">
<div className="w-8 h-8 rounded-full bg-[var(--accent)]/20 flex items-center justify-center">
<HelpCircle className="w-4 h-4 text-[var(--accent)]" />
const modal = (
<>
{/* Backdrop with blur */}
<div
className="fixed inset-0 bg-black/30 backdrop-blur-sm z-[99998]"
onClick={handleClose}
style={{
animation: isClosing
? 'fadeOut 0.2s ease-out forwards'
: 'fadeIn 0.25s ease-out'
}}
/>
{/* Modal */}
<div
role="dialog"
aria-modal="true"
aria-labelledby="how-it-works-title"
className="fixed bg-[var(--bg-secondary)] border border-[var(--border-primary)] shadow-2xl z-[99999]"
style={{
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
borderRadius: '20px',
width: '440px',
maxWidth: 'calc(100vw - 32px)',
maxHeight: 'min(80vh, 650px)',
display: 'flex',
flexDirection: 'column',
animation: isClosing
? 'modalSlideOut 0.2s ease-out forwards'
: 'modalSlideIn 0.3s cubic-bezier(0.16, 1, 0.3, 1)',
overflow: 'hidden',
}}
>
{/* Header */}
<div className="flex items-center justify-between gap-3 px-5 py-4 border-b border-[var(--border-primary)] shrink-0">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-[var(--accent)]/15 flex items-center justify-center">
<HelpCircle className="w-5 h-5 text-[var(--accent)]" />
</div>
<div>
<h3 id="how-it-works-title" className="text-lg font-semibold text-[var(--text-primary)]">How TuxMate Works</h3>
<p className="text-xs text-[var(--text-muted)]">Quick guide &amp; keyboard shortcuts</p>
</div>
</div>
<button
onClick={handleClose}
className="w-8 h-8 flex items-center justify-center rounded-lg hover:bg-[var(--bg-hover)] text-[var(--text-muted)] hover:text-[var(--text-primary)] transition-colors"
>
<X className="w-5 h-5" />
</button>
</div>
{/* Scrollable content */}
<div className="flex-1 overflow-y-auto p-5 space-y-5" style={{ scrollbarGutter: 'stable' }}>
{/* Quick Start Steps */}
<div>
<h3 className="text-base font-semibold text-[var(--text-primary)]">How TuxMate Works</h3>
<p className="text-xs text-[var(--text-muted)]">Quick guide &amp; tips</p>
</div>
</div>
<button
onClick={() => setIsOpen(false)}
className="w-7 h-7 flex items-center justify-center rounded-full hover:bg-[var(--bg-hover)] text-[var(--text-muted)] hover:text-[var(--text-primary)] transition-colors"
>
<X className="w-4 h-4" />
</button>
</div>
{/* Scrollable content */}
<div className="flex-1 overflow-y-auto p-4 space-y-5" style={{ scrollbarGutter: 'stable' }}>
{/* Quick Start Steps */}
<div>
<h4 className="text-xs font-semibold text-[var(--text-muted)] uppercase tracking-wider mb-3">Quick Start</h4>
<div className="space-y-3">
<div className="flex gap-3">
<div className="w-5 h-5 rounded-full bg-[var(--accent)]/20 flex items-center justify-center text-[10px] font-bold text-[var(--accent)] shrink-0">1</div>
<p className="text-sm text-[var(--text-secondary)]">Select your distro from the dropdown</p>
</div>
<div className="flex gap-3">
<div className="w-5 h-5 rounded-full bg-[var(--accent)]/20 flex items-center justify-center text-[10px] font-bold text-[var(--accent)] shrink-0">2</div>
<p className="text-sm text-[var(--text-secondary)]">Check the apps you want to install</p>
</div>
<div className="flex gap-3">
<div className="w-5 h-5 rounded-full bg-[var(--accent)]/20 flex items-center justify-center text-[10px] font-bold text-[var(--accent)] shrink-0">3</div>
<p className="text-sm text-[var(--text-secondary)]">Copy the command or download the script</p>
</div>
<div className="flex gap-3">
<div className="w-5 h-5 rounded-full bg-[var(--accent)]/20 flex items-center justify-center text-[10px] font-bold text-[var(--accent)] shrink-0">4</div>
<p className="text-sm text-[var(--text-secondary)]">Paste in terminal (<code className="text-xs bg-[var(--bg-tertiary)] px-1 py-0.5 rounded">Ctrl+Shift+V</code>) and run</p>
<h4 className="text-xs font-semibold text-[var(--text-muted)] uppercase tracking-wider mb-3">Quick Start</h4>
<div className="space-y-3">
<div className="flex gap-3">
<div className="w-5 h-5 rounded-full bg-[var(--accent)]/20 flex items-center justify-center text-[10px] font-bold text-[var(--accent)] shrink-0">1</div>
<p className="text-sm text-[var(--text-secondary)]">Select your distro from the dropdown</p>
</div>
<div className="flex gap-3">
<div className="w-5 h-5 rounded-full bg-[var(--accent)]/20 flex items-center justify-center text-[10px] font-bold text-[var(--accent)] shrink-0">2</div>
<p className="text-sm text-[var(--text-secondary)]">Check the apps you want to install</p>
</div>
<div className="flex gap-3">
<div className="w-5 h-5 rounded-full bg-[var(--accent)]/20 flex items-center justify-center text-[10px] font-bold text-[var(--accent)] shrink-0">3</div>
<p className="text-sm text-[var(--text-secondary)]">Copy the command or download the script</p>
</div>
<div className="flex gap-3">
<div className="w-5 h-5 rounded-full bg-[var(--accent)]/20 flex items-center justify-center text-[10px] font-bold text-[var(--accent)] shrink-0">4</div>
<p className="text-sm text-[var(--text-secondary)]">Paste in terminal (<code className="text-xs bg-[var(--bg-tertiary)] px-1 py-0.5 rounded">Ctrl+Shift+V</code>) and run</p>
</div>
</div>
</div>
</div>
{/* Unavailable Apps */}
<div className="pt-3 border-t border-[var(--border-primary)]">
<h4 className="text-xs font-semibold text-[var(--text-muted)] uppercase tracking-wider mb-3">App Not Available?</h4>
<div className="space-y-2.5 text-xs text-[var(--text-muted)] leading-relaxed">
<p>Greyed-out apps aren&apos;t in your distro&apos;s repos. Here&apos;s what you can do:</p>
<ul className="space-y-2 ml-2">
{/* Unavailable Apps */}
<div className="pt-3 border-t border-[var(--border-primary)]">
<h4 className="text-xs font-semibold text-[var(--text-muted)] uppercase tracking-wider mb-3">App Not Available?</h4>
<div className="space-y-2.5 text-xs text-[var(--text-muted)] leading-relaxed">
<p>Greyed-out apps aren&apos;t in your distro&apos;s repos. Here&apos;s what you can do:</p>
<ul className="space-y-2 ml-2">
<li className="flex gap-2">
<span className="text-[var(--accent)]"></span>
<span><strong className="text-[var(--text-secondary)]">Use Flatpak/Snap:</strong> Switch to Flatpak or Snap in the distro selector for universal packages</span>
</li>
<li className="flex gap-2">
<span className="text-[var(--accent)]"></span>
<span><strong className="text-[var(--text-secondary)]">Download from website:</strong> Visit the app&apos;s official site and grab the <code className="bg-[var(--bg-tertiary)] px-1 rounded">.deb</code>, <code className="bg-[var(--bg-tertiary)] px-1 rounded">.rpm</code>, or <code className="bg-[var(--bg-tertiary)] px-1 rounded">.AppImage</code></span>
</li>
<li className="flex gap-2">
<span className="text-[var(--accent)]"></span>
<span><strong className="text-[var(--text-secondary)]">Hover the icon:</strong> Some unavailable apps show links to alternative download methods</span>
</li>
</ul>
</div>
</div>
{/* Arch & AUR */}
<div className="pt-3 border-t border-[var(--border-primary)]">
<h4 className="text-xs font-semibold text-[var(--text-muted)] uppercase tracking-wider mb-3">Arch Linux &amp; AUR</h4>
<p className="text-xs text-[var(--text-muted)] leading-relaxed">
Some Arch packages are in the <strong className="text-[var(--text-secondary)]">AUR</strong> (Arch User Repository).
TuxMate uses <code className="bg-[var(--bg-tertiary)] px-1 rounded">yay</code> or <code className="bg-[var(--bg-tertiary)] px-1 rounded">paru</code> to install these.
When selecting AUR packages, a popup will ask which helper you have. You can switch between helpers anytime using <kbd className="px-1 py-0.5 bg-[var(--bg-tertiary)] rounded text-[10px]">1</kbd> (yay) or <kbd className="px-1 py-0.5 bg-[var(--bg-tertiary)] rounded text-[10px]">2</kbd> (paru).
</p>
</div>
{/* Keyboard Shortcuts */}
<div className="pt-3 border-t border-[var(--border-primary)]">
<h4 className="text-xs font-semibold text-[var(--text-muted)] uppercase tracking-wider mb-3">Keyboard Shortcuts</h4>
<div className="grid grid-cols-2 gap-2 text-xs">
<div className="flex items-center gap-2">
<kbd className="px-1.5 py-0.5 bg-[var(--bg-tertiary)] text-[var(--text-secondary)] rounded text-[10px] font-mono"></kbd>
<span className="text-[var(--text-muted)]">Navigate</span>
</div>
<div className="flex items-center gap-2">
<kbd className="px-1.5 py-0.5 bg-[var(--bg-tertiary)] text-[var(--text-secondary)] rounded text-[10px] font-mono">hjkl</kbd>
<span className="text-[var(--text-muted)]">Vim navigation</span>
</div>
<div className="flex items-center gap-2">
<kbd className="px-1.5 py-0.5 bg-[var(--bg-tertiary)] text-[var(--text-secondary)] rounded text-[10px] font-mono">Space</kbd>
<span className="text-[var(--text-muted)]">Toggle selection</span>
</div>
<div className="flex items-center gap-2">
<kbd className="px-1.5 py-0.5 bg-[var(--bg-tertiary)] text-[var(--text-secondary)] rounded text-[10px] font-mono">/</kbd>
<span className="text-[var(--text-muted)]">Search apps</span>
</div>
<div className="flex items-center gap-2">
<kbd className="px-1.5 py-0.5 bg-[var(--bg-tertiary)] text-[var(--text-secondary)] rounded text-[10px] font-mono">y</kbd>
<span className="text-[var(--text-muted)]">Copy command</span>
</div>
<div className="flex items-center gap-2">
<kbd className="px-1.5 py-0.5 bg-[var(--bg-tertiary)] text-[var(--text-secondary)] rounded text-[10px] font-mono">d</kbd>
<span className="text-[var(--text-muted)]">Download script</span>
</div>
<div className="flex items-center gap-2">
<kbd className="px-1.5 py-0.5 bg-[var(--bg-tertiary)] text-[var(--text-secondary)] rounded text-[10px] font-mono">c</kbd>
<span className="text-[var(--text-muted)]">Clear selection</span>
</div>
<div className="flex items-center gap-2">
<kbd className="px-1.5 py-0.5 bg-[var(--bg-tertiary)] text-[var(--text-secondary)] rounded text-[10px] font-mono">t</kbd>
<span className="text-[var(--text-muted)]">Toggle theme</span>
</div>
<div className="flex items-center gap-2">
<kbd className="px-1.5 py-0.5 bg-[var(--bg-tertiary)] text-[var(--text-secondary)] rounded text-[10px] font-mono">Tab</kbd>
<span className="text-[var(--text-muted)]">Open preview</span>
</div>
<div className="flex items-center gap-2">
<kbd className="px-1.5 py-0.5 bg-[var(--bg-tertiary)] text-[var(--text-secondary)] rounded text-[10px] font-mono">Esc</kbd>
<span className="text-[var(--text-muted)]">Close popups</span>
</div>
<div className="flex items-center gap-2">
<kbd className="px-1.5 py-0.5 bg-[var(--bg-tertiary)] text-[var(--text-secondary)] rounded text-[10px] font-mono">?</kbd>
<span className="text-[var(--text-muted)]">This help</span>
</div>
</div>
</div>
{/* Pro Tips */}
<div className="pt-3 border-t border-[var(--border-primary)]">
<h4 className="text-xs font-semibold text-[var(--text-muted)] uppercase tracking-wider mb-3">Pro Tips</h4>
<ul className="space-y-2 text-xs text-[var(--text-muted)] leading-relaxed">
<li className="flex gap-2">
<span className="text-[var(--accent)]"></span>
<span><strong className="text-[var(--text-secondary)]">Use Flatpak/Snap:</strong> Switch to Flatpak or Snap in the distro selector for universal packages</span>
<span className="text-emerald-500">💡</span>
<span>The <strong className="text-[var(--text-secondary)]">download button</strong> gives you a full shell script with progress tracking, error handling, and a summary</span>
</li>
<li className="flex gap-2">
<span className="text-[var(--accent)]"></span>
<span><strong className="text-[var(--text-secondary)]">Download from website:</strong> Visit the app&apos;s official site and grab the <code className="bg-[var(--bg-tertiary)] px-1 rounded">.deb</code>, <code className="bg-[var(--bg-tertiary)] px-1 rounded">.rpm</code>, or <code className="bg-[var(--bg-tertiary)] px-1 rounded">.AppImage</code></span>
<span className="text-emerald-500">💡</span>
<span>
<strong className="text-[var(--text-secondary)]">Running the script:</strong>{' '}
<code className="bg-[var(--bg-tertiary)] px-1 rounded">chmod +x tuxmate-*.sh && ./tuxmate-*.sh</code> or{' '}
<code className="bg-[var(--bg-tertiary)] px-1 rounded">bash tuxmate-*.sh</code>
</span>
</li>
<li className="flex gap-2">
<span className="text-[var(--accent)]"></span>
<span><strong className="text-[var(--text-secondary)]">Check GitHub Releases:</strong> Many apps publish packages on their GitHub releases page</span>
<span className="text-emerald-500">💡</span>
<span>Your selections are <strong className="text-[var(--text-secondary)]">saved automatically</strong> come back anytime to modify your setup</span>
</li>
<li className="flex gap-2">
<span className="text-[var(--accent)]"></span>
<span><strong className="text-[var(--text-secondary)]">Hover the icon:</strong> Some unavailable apps show links to alternative download methods</span>
<span className="text-emerald-500">💡</span>
<span>Running <code className="bg-[var(--bg-tertiary)] px-1 rounded">.deb</code> files: <code className="bg-[var(--bg-tertiary)] px-1 rounded">sudo dpkg -i file.deb</code></span>
</li>
<li className="flex gap-2">
<span className="text-emerald-500">💡</span>
<span>Running <code className="bg-[var(--bg-tertiary)] px-1 rounded">.rpm</code> files: <code className="bg-[var(--bg-tertiary)] px-1 rounded">sudo dnf install ./file.rpm</code></span>
</li>
</ul>
</div>
</div>
{/* Arch & AUR */}
<div className="pt-3 border-t border-[var(--border-primary)]">
<h4 className="text-xs font-semibold text-[var(--text-muted)] uppercase tracking-wider mb-3">Arch Linux &amp; AUR</h4>
<p className="text-xs text-[var(--text-muted)] leading-relaxed">
Some Arch packages are in the <strong className="text-[var(--text-secondary)]">AUR</strong> (Arch User Repository).
TuxMate uses <code className="bg-[var(--bg-tertiary)] px-1 rounded">yay</code> to install these.
If you don&apos;t have yay, check &quot;I have yay installed&quot; to skip auto-installation, or leave it unchecked to install yay first.
</p>
</div>
{/* Keyboard Shortcuts */}
<div className="pt-3 border-t border-[var(--border-primary)]">
<h4 className="text-xs font-semibold text-[var(--text-muted)] uppercase tracking-wider mb-3">Keyboard Shortcuts</h4>
<div className="grid grid-cols-2 gap-2 text-xs">
<div className="flex items-center gap-2">
<kbd className="px-1.5 py-0.5 bg-[var(--bg-tertiary)] text-[var(--text-secondary)] rounded text-[10px] font-mono"></kbd>
<span className="text-[var(--text-muted)]">Navigate apps</span>
</div>
<div className="flex items-center gap-2">
<kbd className="px-1.5 py-0.5 bg-[var(--bg-tertiary)] text-[var(--text-secondary)] rounded text-[10px] font-mono">Space</kbd>
<span className="text-[var(--text-muted)]">Toggle selection</span>
</div>
<div className="flex items-center gap-2">
<kbd className="px-1.5 py-0.5 bg-[var(--bg-tertiary)] text-[var(--text-secondary)] rounded text-[10px] font-mono">Enter</kbd>
<span className="text-[var(--text-muted)]">Expand/collapse</span>
</div>
<div className="flex items-center gap-2">
<kbd className="px-1.5 py-0.5 bg-[var(--bg-tertiary)] text-[var(--text-secondary)] rounded text-[10px] font-mono">Esc</kbd>
<span className="text-[var(--text-muted)]">Close popups</span>
</div>
</div>
</div>
{/* Pro Tips */}
<div className="pt-3 border-t border-[var(--border-primary)]">
<h4 className="text-xs font-semibold text-[var(--text-muted)] uppercase tracking-wider mb-3">Pro Tips</h4>
<ul className="space-y-2 text-xs text-[var(--text-muted)] leading-relaxed">
<li className="flex gap-2">
<span className="text-emerald-500">💡</span>
<span>The <strong className="text-[var(--text-secondary)]">download button</strong> gives you a full shell script with progress tracking, error handling, and a summary</span>
</li>
<li className="flex gap-2">
<span className="text-emerald-500">💡</span>
<span>Your selections are <strong className="text-[var(--text-secondary)]">saved automatically</strong> come back anytime to modify your setup</span>
</li>
<li className="flex gap-2">
<span className="text-emerald-500">💡</span>
<span>Running <code className="bg-[var(--bg-tertiary)] px-1 rounded">.deb</code> files: <code className="bg-[var(--bg-tertiary)] px-1 rounded">sudo dpkg -i file.deb</code> or double-click in your file manager</span>
</li>
<li className="flex gap-2">
<span className="text-emerald-500">💡</span>
<span>Running <code className="bg-[var(--bg-tertiary)] px-1 rounded">.rpm</code> files: <code className="bg-[var(--bg-tertiary)] px-1 rounded">sudo dnf install ./file.rpm</code> or <code className="bg-[var(--bg-tertiary)] px-1 rounded">sudo zypper install ./file.rpm</code></span>
</li>
</ul>
</div>
</div>
{/* Arrow pointer */}
<div
className="absolute w-3 h-3 bg-[var(--bg-secondary)] border-l border-t border-[var(--border-primary)] rotate-45"
style={{ top: '-7px', left: '24px' }}
/>
</div>
) : null;
</>
);
return (
<>
<button
ref={triggerRef}
onClick={() => {
const wasOpen = isOpen;
setIsOpen(!isOpen);
if (!wasOpen) analytics.helpOpened();
else analytics.helpClosed();
}}
onClick={handleOpen}
className={`flex items-center gap-1.5 text-sm transition-all duration-200 hover:scale-105 ${isOpen ? 'text-[var(--text-primary)]' : 'text-[var(--text-muted)] hover:text-[var(--text-secondary)]'}`}
>
<HelpCircle className="w-4 h-4" />
<span className="hidden sm:inline whitespace-nowrap">How it works?</span>
</button>
{mounted && typeof document !== 'undefined' && createPortal(popup, document.body)}
{isOpen && mounted && typeof document !== 'undefined' && createPortal(modal, document.body)}
</>
);
}

View File

@@ -62,12 +62,12 @@ export function useDelayedTooltip(delay = 600) {
const hide = useCallback(() => {
if (timerRef.current) clearTimeout(timerRef.current);
// Delay hide to allow moving to tooltip
// Delay hide to allow moving to tooltip (increased to 500ms for better usability)
timerRef.current = setTimeout(() => {
if (!isHoveringTooltip.current) {
setTooltip(null);
}
}, 100);
}, 500);
}, []);
const onTooltipEnter = useCallback(() => {

View File

@@ -22,9 +22,9 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [hydrated, setHydrated] = useState(false)
useEffect(() => {
// On mount, sync with localStorage (which should match DOM already)
// On mount, sync with localStorage and mark as hydrated
const saved = localStorage.getItem('theme') as Theme | null
if (saved) {
if (saved && saved !== theme) {
setTheme(saved)
document.documentElement.classList.toggle('light', saved === 'light')
}
@@ -42,10 +42,10 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) {
}, [])
return (
<ThemeContext.Provider value= {{ theme, toggle }
}>
{ children }
</ThemeContext.Provider>
<ThemeContext.Provider value={{ theme, toggle }
}>
{children}
</ThemeContext.Provider>
)
}

View File

@@ -43,11 +43,8 @@ const icon = (set: string, name: string, color?: string) =>
`https://api.iconify.design/${set}/${name}.svg${color ? `?color=${encodeURIComponent(color)}` : ''}`;
const si = (name: string, color?: string) => icon('simple-icons', name, color); // Simple Icons (brands)
const lo = (name: string) => icon('logos', name); // Logos (already colorful)
const dev = (name: string) => icon('devicon', name); // Devicon (dev tools)
const sk = (name: string) => icon('skill-icons', name); // Skill Icons (colorful)
const vs = (name: string) => icon('vscode-icons', name); // VS Code Icons (colorful file types)
const mdi = (name: string, color?: string) => icon('mdi', name, color); // Material Design Icons
const def = si('linux', '#FCC624'); // Default fallback (Tux yellow)
export const distros: Distro[] = [
{ id: 'ubuntu', name: 'Ubuntu', iconUrl: si('ubuntu', '#E95420'), color: '#E95420', installPrefix: 'sudo apt install -y' },
@@ -62,193 +59,193 @@ export const distros: Distro[] = [
export const apps: AppData[] = [
// WEB BROWSERS
{ id: 'firefox', name: 'Firefox', description: 'Privacy browser by Mozilla', category: 'Web Browsers', iconUrl: si('firefox', '#FF7139'), targets: { ubuntu: 'firefox', debian: 'firefox-esr', arch: 'firefox', fedora: 'firefox', opensuse: 'MozillaFirefox', nix: 'firefox', flatpak: 'org.mozilla.firefox', snap: 'firefox' } },
{ id: 'chromium', name: 'Chromium', description: 'Open-source Chrome', category: 'Web Browsers', iconUrl: 'https://upload.wikimedia.org/wikipedia/commons/2/28/Chromium_Logo.svg', targets: { ubuntu: 'chromium-browser', debian: 'chromium', arch: 'chromium', fedora: 'chromium', opensuse: 'chromium', nix: 'chromium', flatpak: 'org.chromium.Chromium', snap: 'chromium' } },
{ id: 'brave', name: 'Brave', description: 'Privacy browser with ad blocker', category: 'Web Browsers', iconUrl: si('brave', '#FB542B'), targets: { arch: 'brave-bin', opensuse: 'brave-browser', nix: 'brave', flatpak: 'com.brave.Browser', snap: 'brave' }, unavailableReason: 'Not in official repos. Use Flatpak/Snap or follow instructions at [brave.com/linux](https://brave.com/linux/).' },
{ id: 'librewolf', name: 'LibreWolf', description: 'Privacy-hardened Firefox', category: 'Web Browsers', iconUrl: si('firefoxbrowser', '#0588D1'), targets: { arch: 'librewolf-bin', opensuse: 'librewolf', nix: 'librewolf', flatpak: 'io.gitlab.librewolf-community' }, unavailableReason: 'Not available in official repos. Use [Flatpak](https://flathub.org/en/apps/io.gitlab.librewolf-community) or follow instructions at [librewolf.net/installation](https://librewolf.net/installation/).' },
{ id: 'tor', name: 'Tor Browser', description: 'Anonymous browsing', category: 'Web Browsers', iconUrl: si('torbrowser', '#7D4698'), targets: { ubuntu: 'torbrowser-launcher', debian: 'torbrowser-launcher', arch: 'torbrowser-launcher', fedora: 'torbrowser-launcher', opensuse: 'torbrowser-launcher', nix: 'tor-browser-bundle-bin', flatpak: 'org.torproject.torbrowser-launcher' }, unavailableReason: 'Not available as Snap. Download from [torproject.org](https://www.torproject.org/download/).' },
{ id: 'chrome', name: 'Google Chrome', description: 'Popular web browser', category: 'Web Browsers', iconUrl: lo('chrome'), targets: { ubuntu: 'google-chrome-stable', debian: 'google-chrome-stable', arch: 'google-chrome', fedora: 'google-chrome-stable', opensuse: 'google-chrome-stable', nix: 'google-chrome', flatpak: 'com.google.Chrome' }, unavailableReason: 'Not available as Snap. Download from [google.com/chrome](https://www.google.com/chrome/) or use Flatpak.' },
{ id: 'firefox', name: 'Firefox', description: 'Privacy-focused open-source browser by Mozilla', category: 'Web Browsers', iconUrl: si('firefox', '#FF7139'), targets: { ubuntu: 'firefox', debian: 'firefox-esr', arch: 'firefox', fedora: 'firefox', opensuse: 'MozillaFirefox', nix: 'firefox', flatpak: 'org.mozilla.firefox', snap: 'firefox' } },
{ id: 'chromium', name: 'Chromium', description: 'Open-source browser that powers Google Chrome', category: 'Web Browsers', iconUrl: 'https://upload.wikimedia.org/wikipedia/commons/2/28/Chromium_Logo.svg', targets: { ubuntu: 'chromium-browser', debian: 'chromium', arch: 'chromium', fedora: 'chromium', opensuse: 'chromium', nix: 'chromium', flatpak: 'org.chromium.Chromium', snap: 'chromium' } },
{ id: 'brave', name: 'Brave', description: 'Privacy browser with built-in ad/tracker blocker', category: 'Web Browsers', iconUrl: si('brave', '#FB542B'), targets: { arch: 'brave-bin', opensuse: 'brave-browser', nix: 'brave', flatpak: 'com.brave.Browser', snap: 'brave' }, unavailableReason: 'Not in official repos. Use Flatpak/Snap or follow instructions at [brave.com/linux](https://brave.com/linux/).' },
{ id: 'librewolf', name: 'LibreWolf', description: 'Privacy-hardened Firefox fork with telemetry removed', category: 'Web Browsers', iconUrl: si('firefoxbrowser', '#0588D1'), targets: { arch: 'librewolf-bin', opensuse: 'librewolf', nix: 'librewolf', flatpak: 'io.gitlab.librewolf-community' }, unavailableReason: 'Not available in official repos. Use [Flatpak](https://flathub.org/en/apps/io.gitlab.librewolf-community) or follow instructions at [librewolf.net/installation](https://librewolf.net/installation/).' },
{ id: 'tor', name: 'Tor Browser', description: 'Anonymous browsing via the Tor network', category: 'Web Browsers', iconUrl: si('torbrowser', '#7D4698'), targets: { ubuntu: 'torbrowser-launcher', debian: 'torbrowser-launcher', arch: 'torbrowser-launcher', fedora: 'torbrowser-launcher', opensuse: 'torbrowser-launcher', nix: 'tor-browser-bundle-bin', flatpak: 'org.torproject.torbrowser-launcher' }, unavailableReason: 'Not available as Snap. Download from [torproject.org](https://www.torproject.org/download/).' },
{ id: 'chrome', name: 'Google Chrome', description: 'Most popular web browser by Google', category: 'Web Browsers', iconUrl: lo('chrome'), targets: { ubuntu: 'google-chrome-stable', debian: 'google-chrome-stable', arch: 'google-chrome', fedora: 'google-chrome-stable', opensuse: 'google-chrome-stable', nix: 'google-chrome', flatpak: 'com.google.Chrome' }, unavailableReason: 'Not available as Snap. Download from [google.com/chrome](https://www.google.com/chrome/) or use Flatpak.' },
{ id: 'zen', name: 'Zen Browser', description: 'Privacy-focused Firefox fork', category: 'Web Browsers', iconUrl: si('zenbrowser', '#FF7139'), targets: { arch: 'zen-browser-bin', flatpak: 'app.zen_browser.zen' }, unavailableReason: 'Not in most official repos. Use [Flatpak](https://flathub.org/apps/app.zen_browser.zen) or download from [zen-browser.app](https://zen-browser.app).' },
{ id: 'helium', name: 'Helium', description: 'Privacy ungoogled-chromium', category: 'Web Browsers', iconUrl: si('googlechrome', '#00D4FF'), targets: { arch: 'helium-browser-bin' }, unavailableReason: 'Only available as AppImage or via AUR. Download from [helium.computer](https://helium.computer/).' },
{ id: 'vivaldi', name: 'Vivaldi', description: 'Power user browser', category: 'Web Browsers', iconUrl: si('vivaldi', '#EF3939'), targets: { arch: 'vivaldi', nix: 'vivaldi', flatpak: 'com.vivaldi.Vivaldi', snap: 'vivaldi' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/apps/com.vivaldi.Vivaldi)/[Snap](https://snapcraft.io/vivaldi) or download from [vivaldi.com](https://vivaldi.com/download/).' },
{ id: 'helium', name: 'Helium', description: 'Privacy-focused ungoogled-chromium browser', category: 'Web Browsers', iconUrl: si('googlechrome', '#00D4FF'), targets: { arch: 'helium-browser-bin' }, unavailableReason: 'Only available as AppImage or via AUR. Download from [helium.computer](https://helium.computer/).' },
{ id: 'vivaldi', name: 'Vivaldi', description: 'Highly customizable browser for power users', category: 'Web Browsers', iconUrl: si('vivaldi', '#EF3939'), targets: { arch: 'vivaldi', nix: 'vivaldi', flatpak: 'com.vivaldi.Vivaldi', snap: 'vivaldi' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/apps/com.vivaldi.Vivaldi)/[Snap](https://snapcraft.io/vivaldi) or download from [vivaldi.com](https://vivaldi.com/download/).' },
// COMMUNICATION
{ id: 'discord', name: 'Discord', description: 'Voice and text chat', category: 'Communication', iconUrl: si('discord', '#5865F2'), targets: { arch: 'discord', opensuse: 'discord', nix: 'discord', flatpak: 'com.discordapp.Discord', snap: 'discord' }, unavailableReason: 'Not in official repos. Install via [Flatpak](https://flathub.org/en/apps/com.discordapp.Discord) or download from [discord.com/download](https://discord.com/download).' },
{ id: 'vesktop', name: 'Vesktop', description: 'Discord with Vencord', category: 'Communication', iconUrl: si('discord', '#5865F2'), targets: { arch: 'vesktop-bin', nix: 'vesktop', flatpak: 'dev.vencord.Vesktop' }, unavailableReason: 'Not available in official repos. Check it on [Flatpak](https://flathub.org/en/apps/dev.vencord.Vesktop) or Download from [vesktop.dev/install/linux](https://vesktop.dev/install/linux/).' },
{ id: 'telegram', name: 'Telegram', description: 'Cloud messaging', category: 'Communication', iconUrl: si('telegram', '#26A5E4'), targets: { ubuntu: 'telegram-desktop', debian: 'telegram-desktop', arch: 'telegram-desktop', fedora: 'telegram-desktop', opensuse: 'telegram-desktop', nix: 'telegram-desktop', flatpak: 'org.telegram.desktop', snap: 'telegram-desktop' } },
{ id: 'signal', name: 'Signal', description: 'Encrypted messaging', category: 'Communication', iconUrl: si('signal', '#3A76F0'), targets: { arch: 'signal-desktop', opensuse: 'signal-desktop', nix: 'signal-desktop', flatpak: 'org.signal.Signal', snap: 'signal-desktop' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/en/apps/org.signal.Signal)/[Snap](https://snapcraft.io/signal-desktop) or follow instructions at [signal.org/download/linux](https://signal.org/download/linux/).' },
{ id: 'slack', name: 'Slack', description: 'Team collaboration', category: 'Communication', iconUrl: si('slack', '#4A154B'), targets: { arch: 'slack-desktop', nix: 'slack', flatpak: 'com.slack.Slack', snap: 'slack' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/en/apps/com.slack.Slack)/[Snap](https://snapcraft.io/slack) or download from [slack.com/downloads/linux](https://slack.com/downloads/linux).' },
{ id: 'zoom', name: 'Zoom', description: 'Video conferencing', category: 'Communication', iconUrl: si('zoom', '#0B5CFF'), targets: { arch: 'zoom', nix: 'zoom-us', flatpak: 'us.zoom.Zoom', snap: 'zoom-client' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/en/apps/us.zoom.Zoom)/[Snap](https://snapcraft.io/zoom-client) or download from [zoom.us/download/linux](https://zoom.us/download/linux).' },
{ id: 'thunderbird', name: 'Thunderbird', description: 'Email client', category: 'Communication', iconUrl: si('thunderbird', '#0A84FF'), targets: { ubuntu: 'thunderbird', debian: 'thunderbird', arch: 'thunderbird', fedora: 'thunderbird', opensuse: 'MozillaThunderbird', nix: 'thunderbird', flatpak: 'org.mozilla.Thunderbird', snap: 'thunderbird' } },
{ id: 'element', name: 'Element', description: 'Matrix client', category: 'Communication', iconUrl: si('element', '#0DBD8B'), targets: { arch: 'element-desktop', opensuse: 'element-desktop', nix: 'element-desktop', flatpak: 'im.riot.Riot', snap: 'element-desktop' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/en/apps/im.riot.Riot)/[Snap](https://snapcraft.io/element-desktop) or follow instructions at [element.io/download#linux](https://element.io/en/download#linux).' },
{ id: 'discord', name: 'Discord', description: 'Popular voice, video, and text chat platform', category: 'Communication', iconUrl: si('discord', '#5865F2'), targets: { arch: 'discord', opensuse: 'discord', nix: 'discord', flatpak: 'com.discordapp.Discord', snap: 'discord' }, unavailableReason: 'Not in official repos. Install via [Flatpak](https://flathub.org/en/apps/com.discordapp.Discord) or download from [discord.com/download](https://discord.com/download).' },
{ id: 'vesktop', name: 'Vesktop', description: 'Discord client with Vencord mods built-in', category: 'Communication', iconUrl: si('discord', '#5865F2'), targets: { arch: 'vesktop-bin', nix: 'vesktop', flatpak: 'dev.vencord.Vesktop' }, unavailableReason: 'Not available in official repos. Check it on [Flatpak](https://flathub.org/en/apps/dev.vencord.Vesktop) or Download from [vesktop.dev/install/linux](https://vesktop.dev/install/linux/).' },
{ id: 'telegram', name: 'Telegram', description: 'Fast cloud-based messaging with file sharing', category: 'Communication', iconUrl: si('telegram', '#26A5E4'), targets: { ubuntu: 'telegram-desktop', debian: 'telegram-desktop', arch: 'telegram-desktop', fedora: 'telegram-desktop', opensuse: 'telegram-desktop', nix: 'telegram-desktop', flatpak: 'org.telegram.desktop', snap: 'telegram-desktop' } },
{ id: 'signal', name: 'Signal', description: 'Secure end-to-end encrypted messaging app', category: 'Communication', iconUrl: si('signal', '#3A76F0'), targets: { arch: 'signal-desktop', opensuse: 'signal-desktop', nix: 'signal-desktop', flatpak: 'org.signal.Signal', snap: 'signal-desktop' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/en/apps/org.signal.Signal)/[Snap](https://snapcraft.io/signal-desktop) or follow instructions at [signal.org/download/linux](https://signal.org/download/linux/).' },
{ id: 'slack', name: 'Slack', description: 'Team collaboration and business messaging', category: 'Communication', iconUrl: si('slack', '#4A154B'), targets: { arch: 'slack-desktop', nix: 'slack', flatpak: 'com.slack.Slack', snap: 'slack' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/en/apps/com.slack.Slack)/[Snap](https://snapcraft.io/slack) or download from [slack.com/downloads/linux](https://slack.com/downloads/linux).' },
{ id: 'zoom', name: 'Zoom', description: 'Popular video conferencing and meetings', category: 'Communication', iconUrl: si('zoom', '#0B5CFF'), targets: { arch: 'zoom', nix: 'zoom-us', flatpak: 'us.zoom.Zoom', snap: 'zoom-client' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/en/apps/us.zoom.Zoom)/[Snap](https://snapcraft.io/zoom-client) or download from [zoom.us/download/linux](https://zoom.us/download/linux).' },
{ id: 'thunderbird', name: 'Thunderbird', description: 'Free email client by Mozilla with calendar', category: 'Communication', iconUrl: si('thunderbird', '#0A84FF'), targets: { ubuntu: 'thunderbird', debian: 'thunderbird', arch: 'thunderbird', fedora: 'thunderbird', opensuse: 'MozillaThunderbird', nix: 'thunderbird', flatpak: 'org.mozilla.Thunderbird', snap: 'thunderbird' } },
{ id: 'element', name: 'Element', description: 'Decentralized Matrix chat client with E2E encryption', category: 'Communication', iconUrl: si('element', '#0DBD8B'), targets: { arch: 'element-desktop', opensuse: 'element-desktop', nix: 'element-desktop', flatpak: 'im.riot.Riot', snap: 'element-desktop' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/en/apps/im.riot.Riot)/[Snap](https://snapcraft.io/element-desktop) or follow instructions at [element.io/download#linux](https://element.io/en/download#linux).' },
// DEV LANGUAGES
{ id: 'python3', name: 'Python 3', description: 'Python language', category: 'Dev: Languages', iconUrl: si('python', '#3776AB'), targets: { ubuntu: 'python3', debian: 'python3', arch: 'python', fedora: 'python3', opensuse: 'python3', nix: 'python3' }, unavailableReason: 'Python 3 is a system package and not available via Flatpak or Snap.' },
{ id: 'nodejs', name: 'Node.js', description: 'JavaScript runtime', category: 'Dev: Languages', iconUrl: si('nodedotjs', '#5FA04E'), targets: { ubuntu: 'nodejs', debian: 'nodejs', arch: 'nodejs', fedora: 'nodejs', opensuse: 'nodejs', nix: 'nodejs', snap: 'node --classic' }, unavailableReason: 'Node.js is not available as a Flatpak or Snap.' },
{ id: 'go', name: 'Go', description: 'Go language', category: 'Dev: Languages', iconUrl: si('go', '#00ADD8'), targets: { ubuntu: 'golang', debian: 'golang', arch: 'go', fedora: 'golang', opensuse: 'go', nix: 'go', snap: 'go --classic' }, unavailableReason: 'Go is not available as a Flatpak package.' },
{ id: 'rust', name: 'Rust', description: 'Rust via rustup', category: 'Dev: Languages', iconUrl: si('rust', '#F74C00'), targets: { arch: 'rustup', fedora: 'rustup', opensuse: 'rustup', nix: 'rustup', snap: 'rustup --classic' }, unavailableReason: 'Install via [rustup.rs](https://rustup.rs) on Ubuntu and Debian.' },
{ id: 'ruby', name: 'Ruby', description: 'Ruby language', category: 'Dev: Languages', iconUrl: si('ruby', '#CC342D'), targets: { ubuntu: 'ruby', debian: 'ruby', arch: 'ruby', fedora: 'ruby', opensuse: 'ruby', nix: 'ruby', snap: 'ruby --classic' }, unavailableReason: 'Ruby is a system package and not available via Flatpak or Snap.' },
{ id: 'php', name: 'PHP', description: 'PHP language', category: 'Dev: Languages', iconUrl: si('php', '#777BB4'), targets: { ubuntu: 'php', debian: 'php', arch: 'php', fedora: 'php', opensuse: 'php8', nix: 'php' }, unavailableReason: 'PHP is a system package and not available via Flatpak or Snap.' },
{ id: 'openjdk', name: 'OpenJDK', description: 'Java Development Kit', category: 'Dev: Languages', iconUrl: si('openjdk', '#437291'), targets: { ubuntu: 'openjdk-21-jdk', debian: 'openjdk-17-jdk', arch: 'jdk-openjdk', fedora: 'java-21-openjdk', opensuse: 'java-21-openjdk', nix: 'openjdk' }, unavailableReason: 'OpenJDK is a system package and not available via Flatpak or Snap.' },
{ id: 'deno', name: 'Deno', description: 'Modern JS runtime', category: 'Dev: Languages', iconUrl: si('deno', '#70FFAF'), targets: { arch: 'deno', opensuse: 'deno', nix: 'deno' }, unavailableReason: 'Install via `curl -fsSL https://deno.land/install.sh | sh` on other distros.' },
{ id: 'bun', name: 'Bun', description: 'Fast JS runtime', category: 'Dev: Languages', iconUrl: icon('logos', 'bun'), targets: { arch: 'bun-bin', nix: 'bun' }, unavailableReason: 'Install via `curl -fsSL https://bun.sh/install.sh | bash` on other distros.' },
{ id: 'python3', name: 'Python 3', description: 'Popular beginner-friendly programming language', category: 'Dev: Languages', iconUrl: si('python', '#3776AB'), targets: { ubuntu: 'python3', debian: 'python3', arch: 'python', fedora: 'python3', opensuse: 'python3', nix: 'python3' }, unavailableReason: 'Python 3 is a system package and not available via Flatpak or Snap.' },
{ id: 'nodejs', name: 'Node.js', description: 'Server-side JavaScript runtime environment', category: 'Dev: Languages', iconUrl: si('nodedotjs', '#5FA04E'), targets: { ubuntu: 'nodejs', debian: 'nodejs', arch: 'nodejs', fedora: 'nodejs', opensuse: 'nodejs', nix: 'nodejs', snap: 'node --classic' }, unavailableReason: 'Node.js is not available as a Flatpak or Snap.' },
{ id: 'go', name: 'Go', description: 'Fast compiled programming language by Google', category: 'Dev: Languages', iconUrl: si('go', '#00ADD8'), targets: { ubuntu: 'golang', debian: 'golang', arch: 'go', fedora: 'golang', opensuse: 'go', nix: 'go', snap: 'go --classic' }, unavailableReason: 'Go is not available as a Flatpak package.' },
{ id: 'rust', name: 'Rust', description: 'Memory-safe systems programming language', category: 'Dev: Languages', iconUrl: si('rust', '#F74C00'), targets: { arch: 'rustup', fedora: 'rustup', opensuse: 'rustup', nix: 'rustup', snap: 'rustup --classic' }, unavailableReason: 'Install via [rustup.rs](https://rustup.rs) on Ubuntu and Debian.' },
{ id: 'ruby', name: 'Ruby', description: 'Dynamic language known for elegant syntax', category: 'Dev: Languages', iconUrl: si('ruby', '#CC342D'), targets: { ubuntu: 'ruby', debian: 'ruby', arch: 'ruby', fedora: 'ruby', opensuse: 'ruby', nix: 'ruby', snap: 'ruby --classic' }, unavailableReason: 'Ruby is a system package and not available via Flatpak or Snap.' },
{ id: 'php', name: 'PHP', description: 'Popular web server-side scripting language', category: 'Dev: Languages', iconUrl: si('php', '#777BB4'), targets: { ubuntu: 'php', debian: 'php', arch: 'php', fedora: 'php', opensuse: 'php8', nix: 'php' }, unavailableReason: 'PHP is a system package and not available via Flatpak or Snap.' },
{ id: 'openjdk', name: 'OpenJDK', description: 'Open-source Java Development Kit', category: 'Dev: Languages', iconUrl: si('openjdk', '#437291'), targets: { ubuntu: 'openjdk-21-jdk', debian: 'openjdk-17-jdk', arch: 'jdk-openjdk', fedora: 'java-21-openjdk', opensuse: 'java-21-openjdk', nix: 'openjdk' }, unavailableReason: 'OpenJDK is a system package and not available via Flatpak or Snap.' },
{ id: 'deno', name: 'Deno', description: 'Secure TypeScript/JavaScript runtime by Node creator', category: 'Dev: Languages', iconUrl: si('deno', '#70FFAF'), targets: { arch: 'deno', opensuse: 'deno', nix: 'deno' }, unavailableReason: 'Install via `curl -fsSL https://deno.land/install.sh | sh` on other distros.' },
{ id: 'bun', name: 'Bun', description: 'Ultra-fast JavaScript runtime and bundler', category: 'Dev: Languages', iconUrl: icon('logos', 'bun'), targets: { arch: 'bun-bin', nix: 'bun' }, unavailableReason: 'Install via `curl -fsSL https://bun.sh/install.sh | bash` on other distros.' },
// DEV EDITORS
{ id: 'vscode', name: 'VS Code', description: 'Popular code editor', category: 'Dev: Editors', iconUrl: lo('visual-studio-code'), targets: { arch: 'code', nix: 'vscode', flatpak: 'com.visualstudio.code', snap: 'code --classic' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/en/apps/com.visualstudio.code)/[Snap](https://snapcraft.io/code) or download from [code.visualstudio.com](https://code.visualstudio.com/Download).' },
{ id: 'vscodium', name: 'VSCodium', description: 'VS Code without telemetry', category: 'Dev: Editors', iconUrl: si('vscodium', '#2F80ED'), targets: { arch: 'vscodium-bin', nix: 'vscodium', flatpak: 'com.vscodium.codium', snap: 'codium --classic' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/en/apps/com.vscodium.codium)/[Snap](https://snapcraft.io/codium) or follow instructions at [vscodium.com](https://vscodium.com/#install).' },
{ id: 'neovim', name: 'Neovim', description: 'Vim-based editor', category: 'Dev: Editors', iconUrl: si('neovim', '#57A143'), targets: { ubuntu: 'neovim', debian: 'neovim', arch: 'neovim', fedora: 'neovim', opensuse: 'neovim', nix: 'neovim', flatpak: 'com.neovim.Neovim', snap: 'nvim --classic' } },
{ id: 'helix', name: 'Helix', description: 'Post-modern editor', category: 'Dev: Editors', iconUrl: mdi('dna', '#4E2F7F'), targets: { ubuntu: 'helix', arch: 'helix', fedora: 'helix', opensuse: 'helix', nix: 'helix', flatpak: 'com.helix-editor.Helix', snap: 'helix --classic' } },
{ id: "micro", name: "Micro", description: "Intuitive text editor", category: "Dev: Editors", iconUrl: si("microeditor", "#2E3192"), targets: { arch: "micro", ubuntu: "micro", debian: "micro", fedora: "micro", opensuse: "micro-editor", nix: "micro-editor", flatpak: "io.github.zyedidia.micro", snap: "micro --classic" } },
{ id: 'zed', name: 'Zed', description: 'Fast collaborative editor', category: 'Dev: Editors', iconUrl: si('zedindustries', '#084CCF'), targets: { arch: 'zed', fedora: 'zed', nix: 'zed-editor', flatpak: 'dev.zed.Zed' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/en/apps/dev.zed.Zed) or see [zed.dev/docs/linux](https://zed.dev/docs/linux#other-ways-to-install-zed-on-linux) for other methods.' },
{ id: 'sublime', name: 'Sublime Text', description: 'Fast text editor', category: 'Dev: Editors', iconUrl: si('sublimetext', '#FF9800'), targets: { arch: 'sublime-text-4', nix: 'sublime-text', flatpak: 'com.sublimetext.three', snap: 'sublime-text --classic' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/en/apps/com.sublimetext.three)/[Snap](https://snapcraft.io/sublime-text) or follow instructions at [sublimetext.com/docs/linux_repositories](https://www.sublimetext.com/docs/linux_repositories.html).' },
{ id: 'arduino', name: 'Arduino IDE', description: 'Arduino development', category: 'Dev: Editors', iconUrl: si('arduino', '#00878F'), targets: { ubuntu: 'arduino', debian: 'arduino', arch: 'arduino', fedora: 'arduino', nix: 'arduino-ide', flatpak: 'cc.arduino.IDE2', snap: 'arduino' }, unavailableReason: 'Arduino IDE is not in official openSUSE repos. Use [Flatpak](https://flathub.org/en/apps/cc.arduino.IDE2) or [Snap](https://snapcraft.io/arduino) instead.' },
{ id: 'cursor', name: 'Cursor', description: 'AI code editor', category: 'Dev: Editors', iconUrl: si('cursor', '#232020ff'), targets: { arch: 'cursor-bin', nix: 'code-cursor' }, unavailableReason: 'Only available via [AUR](https://aur.archlinux.org/packages/cursor-bin) or Nix. Download from [cursor.com/download](https://cursor.com/download).' },
{ id: 'vscode', name: 'VS Code', description: 'Most popular extensible code editor by Microsoft', category: 'Dev: Editors', iconUrl: lo('visual-studio-code'), targets: { arch: 'code', nix: 'vscode', flatpak: 'com.visualstudio.code', snap: 'code --classic' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/en/apps/com.visualstudio.code)/[Snap](https://snapcraft.io/code) or download from [code.visualstudio.com](https://code.visualstudio.com/Download).' },
{ id: 'vscodium', name: 'VSCodium', description: 'Community-built VS Code without Microsoft telemetry', category: 'Dev: Editors', iconUrl: si('vscodium', '#2F80ED'), targets: { arch: 'vscodium-bin', nix: 'vscodium', flatpak: 'com.vscodium.codium', snap: 'codium --classic' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/en/apps/com.vscodium.codium)/[Snap](https://snapcraft.io/codium) or follow instructions at [vscodium.com](https://vscodium.com/#install).' },
{ id: 'neovim', name: 'Neovim', description: 'Modernized Vim with better extensibility', category: 'Dev: Editors', iconUrl: si('neovim', '#57A143'), targets: { ubuntu: 'neovim', debian: 'neovim', arch: 'neovim', fedora: 'neovim', opensuse: 'neovim', nix: 'neovim', flatpak: 'com.neovim.Neovim', snap: 'nvim --classic' } },
{ id: 'helix', name: 'Helix', description: 'Modal editor with LSP and tree-sitter built-in', category: 'Dev: Editors', iconUrl: mdi('dna', '#4E2F7F'), targets: { ubuntu: 'helix', arch: 'helix', fedora: 'helix', opensuse: 'helix', nix: 'helix', flatpak: 'com.helix-editor.Helix', snap: 'helix --classic' } },
{ id: "micro", name: "Micro", description: "Easy-to-use terminal text editor like nano", category: "Dev: Editors", iconUrl: si("microeditor", "#2E3192"), targets: { arch: "micro", ubuntu: "micro", debian: "micro", fedora: "micro", opensuse: "micro-editor", nix: "micro-editor", flatpak: "io.github.zyedidia.micro", snap: "micro --classic" } },
{ id: 'zed', name: 'Zed', description: 'High-performance editor with real-time collaboration', category: 'Dev: Editors', iconUrl: si('zedindustries', '#084CCF'), targets: { arch: 'zed', fedora: 'zed', nix: 'zed-editor', flatpak: 'dev.zed.Zed' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/en/apps/dev.zed.Zed) or see [zed.dev/docs/linux](https://zed.dev/docs/linux#other-ways-to-install-zed-on-linux) for other methods.' },
{ id: 'sublime', name: 'Sublime Text', description: 'Lightning-fast proprietary text editor', category: 'Dev: Editors', iconUrl: si('sublimetext', '#FF9800'), targets: { arch: 'sublime-text-4', nix: 'sublime-text', flatpak: 'com.sublimetext.three', snap: 'sublime-text --classic' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/en/apps/com.sublimetext.three)/[Snap](https://snapcraft.io/sublime-text) or follow instructions at [sublimetext.com/docs/linux_repositories](https://www.sublimetext.com/docs/linux_repositories.html).' },
{ id: 'arduino', name: 'Arduino IDE', description: 'IDE for Arduino microcontroller development', category: 'Dev: Editors', iconUrl: si('arduino', '#00878F'), targets: { ubuntu: 'arduino', debian: 'arduino', arch: 'arduino', fedora: 'arduino', nix: 'arduino-ide', flatpak: 'cc.arduino.IDE2', snap: 'arduino' }, unavailableReason: 'Arduino IDE is not in official openSUSE repos. Use [Flatpak](https://flathub.org/en/apps/cc.arduino.IDE2) or [Snap](https://snapcraft.io/arduino) instead.' },
{ id: 'cursor', name: 'Cursor', description: 'AI-powered code editor based on VS Code', category: 'Dev: Editors', iconUrl: si('cursor', '#232020ff'), targets: { arch: 'cursor-bin', nix: 'code-cursor' }, unavailableReason: 'Only available via [AUR](https://aur.archlinux.org/packages/cursor-bin) or Nix. Download from [cursor.com/download](https://cursor.com/download).' },
// DEV TOOLS
{ id: 'git', name: 'Git', description: 'Version control', category: 'Dev: Tools', iconUrl: si('git', '#F05032'), targets: { ubuntu: 'git', debian: 'git', arch: 'git', fedora: 'git', opensuse: 'git', nix: 'git' }, unavailableReason: 'Git is a system package and not available via Flatpak or Snap.' },
{ id: 'gitlfs', name: 'Git LFS', description: 'Large file storage', category: 'Dev: Tools', iconUrl: si('git', '#F05032'), targets: { ubuntu: 'git-lfs', debian: 'git-lfs', arch: 'git-lfs', fedora: 'git-lfs', opensuse: 'git-lfs', nix: 'git-lfs' }, unavailableReason: 'Git LFS is a system package and not available via Flatpak or Snap.' },
{ id: 'lazygit', name: 'LazyGit', description: 'Terminal Git UI', category: 'Dev: Tools', iconUrl: si('git', '#F05032'), targets: { ubuntu: 'lazygit', debian: 'lazygit', arch: 'lazygit', fedora: 'lazygit', opensuse: 'lazygit', nix: 'lazygit' }, unavailableReason: 'LazyGit is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'docker', name: 'Docker', description: 'Container runtime', category: 'Dev: Tools', iconUrl: si('docker', '#2496ED'), targets: { ubuntu: 'docker.io', debian: 'docker.io', arch: 'docker', fedora: 'docker', opensuse: 'docker', nix: 'docker', snap: 'docker' }, unavailableReason: 'Docker is a system service and not available as a Flatpak.' },
{ id: 'podman', name: 'Podman', description: 'Daemonless containers', category: 'Dev: Tools', iconUrl: si('podman', '#892CA0'), targets: { ubuntu: 'podman', debian: 'podman', arch: 'podman', fedora: 'podman', opensuse: 'podman', nix: 'podman' }, unavailableReason: 'Podman is a system package and not available via Flatpak or Snap.' },
{ id: 'kubectl', name: 'kubectl', description: 'Kubernetes CLI', category: 'Dev: Tools', iconUrl: si('kubernetes', '#326CE5'), targets: { arch: 'kubectl', fedora: 'kubernetes-client', opensuse: 'kubectl', nix: 'kubectl', snap: 'kubectl --classic' }, unavailableReason: 'kubectl is not in official Ubuntu or Debian repos. Use Snap or install via [kubernetes.io](https://kubernetes.io/docs/tasks/tools/).' },
{ id: 'vagrant', name: 'Vagrant', description: 'Dev environment manager', category: 'Dev: Tools', iconUrl: si('vagrant', '#1868F2'), targets: { ubuntu: 'vagrant', debian: 'vagrant', arch: 'vagrant', fedora: 'vagrant', opensuse: 'vagrant', nix: 'vagrant' }, unavailableReason: 'Vagrant is a system package and not available via Flatpak or Snap.' },
{ id: 'virtualbox', name: 'VirtualBox', description: 'Virtual machines', category: 'Dev: Tools', iconUrl: si('virtualbox', '#183A61'), targets: { ubuntu: 'virtualbox', debian: 'virtualbox', arch: 'virtualbox', fedora: 'VirtualBox', opensuse: 'virtualbox', nix: 'virtualbox' }, unavailableReason: 'VirtualBox requires kernel modules and is not available via Flatpak or Snap.' },
{ id: 'gnomeboxes', name: 'GNOME Boxes', description: 'Simple VM manager', category: 'Dev: Tools', iconUrl: si('gnome', '#4A86CF'), targets: { ubuntu: 'gnome-boxes', debian: 'gnome-boxes', arch: 'gnome-boxes', fedora: 'gnome-boxes', opensuse: 'gnome-boxes', nix: 'gnome-boxes', flatpak: 'org.gnome.Boxes' }, unavailableReason: 'GNOME Boxes is not available as a Snap package.' },
{ id: 'dbeaver', name: 'DBeaver', description: 'Database tool', category: 'Dev: Tools', iconUrl: si('dbeaver', '#382923'), targets: { arch: 'dbeaver', nix: 'dbeaver-bin', flatpak: 'io.dbeaver.DBeaverCommunity' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/en/apps/io.dbeaver.DBeaverCommunity) or download from [dbeaver.io/download](https://dbeaver.io/download/).' },
{ id: 'meld', name: 'Meld', description: 'Visual diff tool', category: 'Dev: Tools', iconUrl: 'https://meldmerge.org/images/meld.svg', targets: { ubuntu: 'meld', debian: 'meld', arch: 'meld', fedora: 'meld', opensuse: 'meld', nix: 'meld', flatpak: 'org.gnome.meld' }, unavailableReason: 'Meld is not available as a Snap package.' },
{ id: 'wireshark', name: 'Wireshark', description: 'Network analyzer', category: 'Dev: Tools', iconUrl: si('wireshark', '#1679A7'), targets: { ubuntu: 'wireshark', debian: 'wireshark', arch: 'wireshark-qt', fedora: 'wireshark', opensuse: 'wireshark', nix: 'wireshark', flatpak: 'org.wireshark.Wireshark' }, unavailableReason: 'Wireshark is not available as a Snap package.' },
{ id: 'postman', name: 'Postman', description: 'API development', category: 'Dev: Tools', iconUrl: si('postman', '#FF6C37'), targets: { arch: 'postman-bin', nix: 'postman', flatpak: 'com.getpostman.Postman', snap: 'postman' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/en/apps/com.getpostman.Postman)/[Snap](https://snapcraft.io/postman) or download from [postman.com/downloads](https://www.postman.com/downloads/).' },
{ id: 'bruno', name: 'Bruno', description: 'API client (offline)', category: 'Dev: Tools', iconUrl: mdi('api', '#F4A62A'), targets: { arch: 'bruno-bin', nix: 'bruno', flatpak: 'com.usebruno.Bruno', snap: 'bruno' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/apps/com.usebruno.Bruno)/[Snap](https://snapcraft.io/bruno) or download from [usebruno.com](https://www.usebruno.com/downloads).' },
{ id: 'hoppscotch', name: 'Hoppscotch', description: 'API development', category: 'Dev: Tools', iconUrl: si('hoppscotch', '#47C0A7'), targets: { arch: 'hoppscotch-bin', nix: 'hoppscotch' }, unavailableReason: 'Use [AUR](https://aur.archlinux.org/packages/hoppscotch-bin) or download from [hoppscotch.io](https://hoppscotch.io/download).' },
{ id: 'virtmanager', name: 'Virt Manager', description: 'KVM/QEMU manager', category: 'Dev: Tools', iconUrl: si('qemu', '#FF6600'), targets: { ubuntu: 'virt-manager', debian: 'virt-manager', arch: 'virt-manager', fedora: 'virt-manager', opensuse: 'virt-manager', nix: 'virt-manager' }, unavailableReason: 'Virt Manager requires system access and is not available via Flatpak or Snap.' },
{ id: 'imhex', name: 'ImHex', description: 'Hex editor', category: 'Dev: Tools', iconUrl: mdi('hexadecimal', '#4FC1E8'), targets: { arch: 'imhex-bin', fedora: 'imhex', nix: 'imhex', flatpak: 'net.werwolv.ImHex' }, unavailableReason: 'Not in most repos. Use [Flatpak](https://flathub.org/apps/net.werwolv.ImHex) or download from [imhex.werwolv.net](https://imhex.werwolv.net/).' },
{ id: 'git', name: 'Git', description: 'Industry-standard distributed version control', category: 'Dev: Tools', iconUrl: si('git', '#F05032'), targets: { ubuntu: 'git', debian: 'git', arch: 'git', fedora: 'git', opensuse: 'git', nix: 'git' }, unavailableReason: 'Git is a system package and not available via Flatpak or Snap.' },
{ id: 'gitlfs', name: 'Git LFS', description: 'Git extension for versioning large files', category: 'Dev: Tools', iconUrl: si('git', '#F05032'), targets: { ubuntu: 'git-lfs', debian: 'git-lfs', arch: 'git-lfs', fedora: 'git-lfs', opensuse: 'git-lfs', nix: 'git-lfs' }, unavailableReason: 'Git LFS is a system package and not available via Flatpak or Snap.' },
{ id: 'lazygit', name: 'LazyGit', description: 'Simple terminal UI for git commands', category: 'Dev: Tools', iconUrl: si('git', '#F05032'), targets: { ubuntu: 'lazygit', debian: 'lazygit', arch: 'lazygit', fedora: 'lazygit', opensuse: 'lazygit', nix: 'lazygit' }, unavailableReason: 'LazyGit is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'docker', name: 'Docker', description: 'Most popular container platform for app deployment', category: 'Dev: Tools', iconUrl: si('docker', '#2496ED'), targets: { ubuntu: 'docker.io', debian: 'docker.io', arch: 'docker', fedora: 'docker', opensuse: 'docker', nix: 'docker', snap: 'docker' }, unavailableReason: 'Docker is a system service and not available as a Flatpak.' },
{ id: 'podman', name: 'Podman', description: 'Rootless container engine, Docker alternative', category: 'Dev: Tools', iconUrl: si('podman', '#892CA0'), targets: { ubuntu: 'podman', debian: 'podman', arch: 'podman', fedora: 'podman', opensuse: 'podman', nix: 'podman' }, unavailableReason: 'Podman is a system package and not available via Flatpak or Snap.' },
{ id: 'kubectl', name: 'kubectl', description: 'Command-line tool for Kubernetes clusters', category: 'Dev: Tools', iconUrl: si('kubernetes', '#326CE5'), targets: { arch: 'kubectl', fedora: 'kubernetes-client', opensuse: 'kubectl', nix: 'kubectl', snap: 'kubectl --classic' }, unavailableReason: 'kubectl is not in official Ubuntu or Debian repos. Use Snap or install via [kubernetes.io](https://kubernetes.io/docs/tasks/tools/).' },
{ id: 'vagrant', name: 'Vagrant', description: 'Build and manage portable dev environments', category: 'Dev: Tools', iconUrl: si('vagrant', '#1868F2'), targets: { ubuntu: 'vagrant', debian: 'vagrant', arch: 'vagrant', fedora: 'vagrant', opensuse: 'vagrant', nix: 'vagrant' }, unavailableReason: 'Vagrant is a system package and not available via Flatpak or Snap.' },
{ id: 'virtualbox', name: 'VirtualBox', description: 'Free cross-platform virtual machine manager', category: 'Dev: Tools', iconUrl: si('virtualbox', '#183A61'), targets: { ubuntu: 'virtualbox', debian: 'virtualbox', arch: 'virtualbox', fedora: 'VirtualBox', opensuse: 'virtualbox', nix: 'virtualbox' }, unavailableReason: 'VirtualBox requires kernel modules and is not available via Flatpak or Snap.' },
{ id: 'gnomeboxes', name: 'GNOME Boxes', description: 'Simple virtual machine app for GNOME', category: 'Dev: Tools', iconUrl: si('gnome', '#4A86CF'), targets: { ubuntu: 'gnome-boxes', debian: 'gnome-boxes', arch: 'gnome-boxes', fedora: 'gnome-boxes', opensuse: 'gnome-boxes', nix: 'gnome-boxes', flatpak: 'org.gnome.Boxes' }, unavailableReason: 'GNOME Boxes is not available as a Snap package.' },
{ id: 'dbeaver', name: 'DBeaver', description: 'Universal database management tool', category: 'Dev: Tools', iconUrl: si('dbeaver', '#382923'), targets: { arch: 'dbeaver', nix: 'dbeaver-bin', flatpak: 'io.dbeaver.DBeaverCommunity' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/en/apps/io.dbeaver.DBeaverCommunity) or download from [dbeaver.io/download](https://dbeaver.io/download/).' },
{ id: 'meld', name: 'Meld', description: 'Visual diff and merge tool for files', category: 'Dev: Tools', iconUrl: 'https://meldmerge.org/images/meld.svg', targets: { ubuntu: 'meld', debian: 'meld', arch: 'meld', fedora: 'meld', opensuse: 'meld', nix: 'meld', flatpak: 'org.gnome.meld' }, unavailableReason: 'Meld is not available as a Snap package.' },
{ id: 'wireshark', name: 'Wireshark', description: 'Network protocol analyzer and packet capture', category: 'Dev: Tools', iconUrl: si('wireshark', '#1679A7'), targets: { ubuntu: 'wireshark', debian: 'wireshark', arch: 'wireshark-qt', fedora: 'wireshark', opensuse: 'wireshark', nix: 'wireshark', flatpak: 'org.wireshark.Wireshark' }, unavailableReason: 'Wireshark is not available as a Snap package.' },
{ id: 'postman', name: 'Postman', description: 'Popular API testing and development platform', category: 'Dev: Tools', iconUrl: si('postman', '#FF6C37'), targets: { arch: 'postman-bin', nix: 'postman', flatpak: 'com.getpostman.Postman', snap: 'postman' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/en/apps/com.getpostman.Postman)/[Snap](https://snapcraft.io/postman) or download from [postman.com/downloads](https://www.postman.com/downloads/).' },
{ id: 'bruno', name: 'Bruno', description: 'Offline-first open-source API client', category: 'Dev: Tools', iconUrl: mdi('api', '#F4A62A'), targets: { arch: 'bruno-bin', nix: 'bruno', flatpak: 'com.usebruno.Bruno', snap: 'bruno' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/apps/com.usebruno.Bruno)/[Snap](https://snapcraft.io/bruno) or download from [usebruno.com](https://www.usebruno.com/downloads).' },
{ id: 'hoppscotch', name: 'Hoppscotch', description: 'Open-source API development ecosystem', category: 'Dev: Tools', iconUrl: si('hoppscotch', '#47C0A7'), targets: { arch: 'hoppscotch-bin', nix: 'hoppscotch' }, unavailableReason: 'Use [AUR](https://aur.archlinux.org/packages/hoppscotch-bin) or download from [hoppscotch.io](https://hoppscotch.io/download).' },
{ id: 'virtmanager', name: 'Virt Manager', description: 'Desktop app for managing KVM virtual machines', category: 'Dev: Tools', iconUrl: si('qemu', '#FF6600'), targets: { ubuntu: 'virt-manager', debian: 'virt-manager', arch: 'virt-manager', fedora: 'virt-manager', opensuse: 'virt-manager', nix: 'virt-manager' }, unavailableReason: 'Virt Manager requires system access and is not available via Flatpak or Snap.' },
{ id: 'imhex', name: 'ImHex', description: 'Feature-rich hex editor for reverse engineering', category: 'Dev: Tools', iconUrl: mdi('hexadecimal', '#4FC1E8'), targets: { arch: 'imhex-bin', fedora: 'imhex', nix: 'imhex', flatpak: 'net.werwolv.ImHex' }, unavailableReason: 'Not in most repos. Use [Flatpak](https://flathub.org/apps/net.werwolv.ImHex) or download from [imhex.werwolv.net](https://imhex.werwolv.net/).' },
// TERMINAL
{ id: 'zsh', name: 'Zsh', description: 'Extended shell', category: 'Terminal', iconUrl: si('zsh', '#F15A24'), targets: { ubuntu: 'zsh', debian: 'zsh', arch: 'zsh', fedora: 'zsh', opensuse: 'zsh', nix: 'zsh' }, unavailableReason: 'Zsh is a system shell and not available via Flatpak or Snap.' },
{ id: 'ohmyzsh', name: 'Oh My Zsh', description: 'Zsh framework', category: 'Terminal', iconUrl: si('zsh', '#F15A24'), targets: {}, unavailableReason: 'Install via `sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"`. See [ohmyzsh wiki](https://github.com/ohmyzsh/ohmyzsh/wiki/Installing-ZSH).' },
{ id: 'fish', name: 'Fish', description: 'User-friendly shell', category: 'Terminal', iconUrl: vs('file-type-shell'), targets: { ubuntu: 'fish', debian: 'fish', arch: 'fish', fedora: 'fish', opensuse: 'fish', nix: 'fish' }, unavailableReason: 'Fish is a system shell and not available via Flatpak or Snap.' },
{ id: 'starship', name: 'Starship', description: 'Shell prompt', category: 'Terminal', iconUrl: si('starship', '#DD0B78'), targets: { ubuntu: 'starship', debian: 'starship', arch: 'starship', opensuse: 'starship', nix: 'starship' }, unavailableReason: 'Not in Fedora repos. Install via `curl -sS https://starship.rs/install.sh | sh` or see [starship.rs](https://starship.rs/).' },
{ id: 'alacritty', name: 'Alacritty', description: 'GPU terminal', category: 'Terminal', iconUrl: si('alacritty', '#F46D01'), targets: { ubuntu: 'alacritty', debian: 'alacritty', arch: 'alacritty', fedora: 'alacritty', opensuse: 'alacritty', nix: 'alacritty', snap: 'alacritty --classic' }, unavailableReason: 'Alacritty is not available as a Flatpak package.' },
{ id: 'kitty', name: 'Kitty', description: 'Feature-rich terminal', category: 'Terminal', iconUrl: mdi('cat', '#ea5e5e'), targets: { ubuntu: 'kitty', debian: 'kitty', arch: 'kitty', fedora: 'kitty', opensuse: 'kitty', nix: 'kitty' }, unavailableReason: 'Kitty is not available via Flatpak or Snap.' },
{ id: 'wezterm', name: 'WezTerm', description: 'GPU terminal', category: 'Terminal', iconUrl: si('wezterm', '#4E49EE'), targets: { arch: 'wezterm', opensuse: 'wezterm', nix: 'wezterm', flatpak: 'org.wezfurlong.wezterm' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/en/apps/org.wezfurlong.wezterm) or follow instructions at [wezfurlong.org/wezterm/install](https://wezfurlong.org/wezterm/install/linux.html).' },
{ id: 'foot', name: 'Foot', description: 'Wayland terminal', category: 'Terminal', iconUrl: si('wayland', '#FFBC00'), targets: { ubuntu: 'foot', debian: 'foot', arch: 'foot', fedora: 'foot', opensuse: 'foot', nix: 'foot' }, unavailableReason: 'Foot is a Wayland-only terminal and not available via Flatpak or Snap.' },
{ id: 'ghostty', name: 'Ghostty', description: 'Fast GPU terminal', category: 'Terminal', iconUrl: mdi('ghost-outline', '#6E56CF'), targets: { arch: 'ghostty', nix: 'ghostty' }, unavailableReason: 'Not in most repos. See [official binaries](https://ghostty.org/docs/install/binary#linux-(official)) or build from source.' },
{ id: 'zellij', name: 'Zellij', description: 'Terminal multiplexer', category: 'Terminal', iconUrl: mdi('view-split-vertical', '#A48CF4'), targets: { ubuntu: 'zellij', arch: 'zellij', fedora: 'zellij', opensuse: 'zellij', nix: 'zellij' }, unavailableReason: 'Not in Debian repos. Install via `cargo install zellij` or see [zellij.dev](https://zellij.dev/documentation/installation.html).' },
{ id: 'zsh', name: 'Zsh', description: 'Powerful shell with advanced completion features', category: 'Terminal', iconUrl: si('zsh', '#F15A24'), targets: { ubuntu: 'zsh', debian: 'zsh', arch: 'zsh', fedora: 'zsh', opensuse: 'zsh', nix: 'zsh' }, unavailableReason: 'Zsh is a system shell and not available via Flatpak or Snap.' },
{ id: 'ohmyzsh', name: 'Oh My Zsh', description: 'Zsh configuration framework with plugins/themes', category: 'Terminal', iconUrl: si('zsh', '#F15A24'), targets: {}, unavailableReason: 'Install via `sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"`. See [ohmyzsh wiki](https://github.com/ohmyzsh/ohmyzsh/wiki/Installing-ZSH).' },
{ id: 'fish', name: 'Fish', description: 'Smart and user-friendly interactive shell', category: 'Terminal', iconUrl: vs('file-type-shell'), targets: { ubuntu: 'fish', debian: 'fish', arch: 'fish', fedora: 'fish', opensuse: 'fish', nix: 'fish' }, unavailableReason: 'Fish is a system shell and not available via Flatpak or Snap.' },
{ id: 'starship', name: 'Starship', description: 'Cross-shell customizable command prompt', category: 'Terminal', iconUrl: si('starship', '#DD0B78'), targets: { ubuntu: 'starship', debian: 'starship', arch: 'starship', opensuse: 'starship', nix: 'starship' }, unavailableReason: 'Not in Fedora repos. Install via `curl -sS https://starship.rs/install.sh | sh` or see [starship.rs](https://starship.rs/).' },
{ id: 'alacritty', name: 'Alacritty', description: 'Blazing fast GPU-accelerated terminal emulator', category: 'Terminal', iconUrl: si('alacritty', '#F46D01'), targets: { ubuntu: 'alacritty', debian: 'alacritty', arch: 'alacritty', fedora: 'alacritty', opensuse: 'alacritty', nix: 'alacritty', snap: 'alacritty --classic' }, unavailableReason: 'Alacritty is not available as a Flatpak package.' },
{ id: 'kitty', name: 'Kitty', description: 'Fast GPU-based terminal with advanced features', category: 'Terminal', iconUrl: mdi('cat', '#ea5e5e'), targets: { ubuntu: 'kitty', debian: 'kitty', arch: 'kitty', fedora: 'kitty', opensuse: 'kitty', nix: 'kitty' }, unavailableReason: 'Kitty is not available via Flatpak or Snap.' },
{ id: 'wezterm', name: 'WezTerm', description: 'GPU-accelerated terminal with Lua configuration', category: 'Terminal', iconUrl: si('wezterm', '#4E49EE'), targets: { arch: 'wezterm', opensuse: 'wezterm', nix: 'wezterm', flatpak: 'org.wezfurlong.wezterm' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/en/apps/org.wezfurlong.wezterm) or follow instructions at [wezfurlong.org/wezterm/install](https://wezfurlong.org/wezterm/install/linux.html).' },
{ id: 'foot', name: 'Foot', description: 'Lightweight Wayland-native terminal emulator', category: 'Terminal', iconUrl: si('wayland', '#FFBC00'), targets: { ubuntu: 'foot', debian: 'foot', arch: 'foot', fedora: 'foot', opensuse: 'foot', nix: 'foot' }, unavailableReason: 'Foot is a Wayland-only terminal and not available via Flatpak or Snap.' },
{ id: 'ghostty', name: 'Ghostty', description: 'Native GPU-accelerated terminal by Mitchell Hashimoto', category: 'Terminal', iconUrl: mdi('ghost-outline', '#6E56CF'), targets: { arch: 'ghostty', nix: 'ghostty' }, unavailableReason: 'Not in most repos. See [official binaries](https://ghostty.org/docs/install/binary#linux-(official)) or build from source.' },
{ id: 'zellij', name: 'Zellij', description: 'Modern terminal multiplexer with layout system', category: 'Terminal', iconUrl: mdi('view-split-vertical', '#A48CF4'), targets: { ubuntu: 'zellij', arch: 'zellij', fedora: 'zellij', opensuse: 'zellij', nix: 'zellij' }, unavailableReason: 'Not in Debian repos. Install via `cargo install zellij` or see [zellij.dev](https://zellij.dev/documentation/installation.html).' },
// CLI TOOLS
{ id: 'btop', name: 'btop', description: 'Resource monitor', category: 'CLI Tools', iconUrl: mdi('monitor-dashboard', '#FF6B6B'), targets: { ubuntu: 'btop', debian: 'btop', arch: 'btop', fedora: 'btop', opensuse: 'btop', nix: 'btop' }, unavailableReason: 'btop is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'htop', name: 'htop', description: 'Process viewer', category: 'CLI Tools', iconUrl: mdi('chart-bar', '#4CE026'), targets: { ubuntu: 'htop', debian: 'htop', arch: 'htop', fedora: 'htop', opensuse: 'htop', nix: 'htop', snap: 'htop' }, unavailableReason: 'htop is a CLI tool and not available as a Flatpak.' },
{ id: 'fastfetch', name: 'fastfetch', description: 'System info', category: 'CLI Tools', iconUrl: mdi('console', '#57F287'), targets: { arch: 'fastfetch', fedora: 'fastfetch', opensuse: 'fastfetch', nix: 'fastfetch' }, unavailableReason: 'Not in Ubuntu/Debian repos. Install from [github.com/fastfetch-cli/fastfetch](https://github.com/fastfetch-cli/fastfetch/releases).' },
{ id: 'neofetch', name: 'neofetch', description: 'System info', category: 'CLI Tools', iconUrl: si('linux', '#FCC624'), targets: { ubuntu: 'neofetch', debian: 'neofetch', arch: 'neofetch', fedora: 'neofetch', opensuse: 'neofetch', nix: 'neofetch' }, unavailableReason: 'neofetch is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'eza', name: 'eza', description: 'Modern ls', category: 'CLI Tools', iconUrl: mdi('format-list-bulleted', '#F9E64F'), targets: { ubuntu: 'eza', debian: 'eza', arch: 'eza', fedora: 'eza', opensuse: 'eza', nix: 'eza' }, unavailableReason: 'eza is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'bat', name: 'bat', description: 'Cat with syntax', category: 'CLI Tools', iconUrl: mdi('file-code-outline', '#A6E22E'), targets: { ubuntu: 'bat', debian: 'bat', arch: 'bat', fedora: 'bat', opensuse: 'bat', nix: 'bat' }, unavailableReason: 'bat is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'fzf', name: 'fzf', description: 'Fuzzy finder', category: 'CLI Tools', iconUrl: mdi('magnify', '#FF0055'), targets: { ubuntu: 'fzf', debian: 'fzf', arch: 'fzf', fedora: 'fzf', opensuse: 'fzf', nix: 'fzf' }, unavailableReason: 'fzf is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'ripgrep', name: 'ripgrep', description: 'Fast grep', category: 'CLI Tools', iconUrl: mdi('text-search', '#C0C0C0'), targets: { ubuntu: 'ripgrep', debian: 'ripgrep', arch: 'ripgrep', fedora: 'ripgrep', opensuse: 'ripgrep', nix: 'ripgrep' }, unavailableReason: 'ripgrep is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'zoxide', name: 'zoxide', description: 'Smarter cd', category: 'CLI Tools', iconUrl: mdi('folder-move-outline', '#FF9F43'), targets: { ubuntu: 'zoxide', debian: 'zoxide', arch: 'zoxide', fedora: 'zoxide', opensuse: 'zoxide', nix: 'zoxide' }, unavailableReason: 'zoxide is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'tldr', name: 'tldr', description: 'Simple man pages', category: 'CLI Tools', iconUrl: mdi('book-open-page-variant-outline', '#2D9CDB'), targets: { ubuntu: 'tldr', debian: 'tldr', arch: 'tldr', fedora: 'tldr', nix: 'tldr' }, unavailableReason: 'tldr is a CLI tool and not available on openSUSE, Flatpak, or Snap.' },
{ id: 'wget', name: 'wget', description: 'Network downloader', category: 'CLI Tools', iconUrl: mdi('download', '#3FA75E'), targets: { ubuntu: 'wget', debian: 'wget', arch: 'wget', fedora: 'wget', opensuse: 'wget', nix: 'wget' }, unavailableReason: 'wget is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'curl', name: 'curl', description: 'Data transfer', category: 'CLI Tools', iconUrl: si('curl', '#073551'), targets: { ubuntu: 'curl', debian: 'curl', arch: 'curl', fedora: 'curl', opensuse: 'curl', nix: 'curl' }, unavailableReason: 'curl is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'aria2', name: 'aria2', description: 'Download accelerator', category: 'CLI Tools', iconUrl: mdi('download-multiple', '#F94144'), targets: { ubuntu: 'aria2', debian: 'aria2', arch: 'aria2', fedora: 'aria2', opensuse: 'aria2', nix: 'aria2' }, unavailableReason: 'aria2 is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'yazi', name: 'yazi', description: 'Terminal file manager', category: 'CLI Tools', iconUrl: mdi('folder-table-outline', '#F3B329'), targets: { arch: 'yazi', opensuse: 'yazi', nix: 'yazi' }, unavailableReason: 'Not in most repos. Install via [cargo](https://crates.io/crates/yazi) or download from [github.com/sxyazi/yazi](https://github.com/sxyazi/yazi/releases).' },
{ id: 'ranger', name: 'ranger', description: 'Terminal file manager', category: 'CLI Tools', iconUrl: mdi('folder-key-outline', '#FFFFFF'), targets: { ubuntu: 'ranger', debian: 'ranger', arch: 'ranger', fedora: 'ranger', opensuse: 'ranger', nix: 'ranger' }, unavailableReason: 'ranger is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'ncdu', name: 'ncdu', description: 'Disk usage', category: 'CLI Tools', iconUrl: mdi('chart-arc', '#00ADEE'), targets: { ubuntu: 'ncdu', debian: 'ncdu', arch: 'ncdu', fedora: 'ncdu', opensuse: 'ncdu', nix: 'ncdu' }, unavailableReason: 'ncdu is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'fd', name: 'fd', description: 'Modern find', category: 'CLI Tools', iconUrl: mdi('file-search-outline', '#56BE89'), targets: { ubuntu: 'fd-find', debian: 'fd-find', arch: 'fd', fedora: 'fd-find', opensuse: 'fd', nix: 'fd' }, unavailableReason: 'fd is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'tmux', name: 'tmux', description: 'Terminal multiplexer', category: 'CLI Tools', iconUrl: si('tmux', '#1BB91F'), targets: { ubuntu: 'tmux', debian: 'tmux', arch: 'tmux', fedora: 'tmux', opensuse: 'tmux', nix: 'tmux' }, unavailableReason: 'tmux is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'superfile', name: 'Superfile', description: 'Terminal file manager', category: 'CLI Tools', iconUrl: mdi('folder-multiple', '#FFD93D'), targets: { arch: 'superfile', nix: 'superfile' }, unavailableReason: 'Install via `go install` or see [superfile.dev](https://superfile.dev/getting-started/installation/).' },
{ id: 'rsync', name: 'rsync', description: 'File sync tool', category: 'CLI Tools', iconUrl: mdi('sync', '#2ECC71'), targets: { ubuntu: 'rsync', debian: 'rsync', arch: 'rsync', fedora: 'rsync', opensuse: 'rsync', nix: 'rsync' }, unavailableReason: 'rsync is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'btop', name: 'btop', description: 'Beautiful terminal resource monitor with graphs', category: 'CLI Tools', iconUrl: mdi('monitor-dashboard', '#FF6B6B'), targets: { ubuntu: 'btop', debian: 'btop', arch: 'btop', fedora: 'btop', opensuse: 'btop', nix: 'btop' }, unavailableReason: 'btop is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'htop', name: 'htop', description: 'Interactive process viewer and system monitor', category: 'CLI Tools', iconUrl: mdi('chart-bar', '#4CE026'), targets: { ubuntu: 'htop', debian: 'htop', arch: 'htop', fedora: 'htop', opensuse: 'htop', nix: 'htop', snap: 'htop' }, unavailableReason: 'htop is a CLI tool and not available as a Flatpak.' },
{ id: 'fastfetch', name: 'fastfetch', description: 'Fast neofetch-like system info tool', category: 'CLI Tools', iconUrl: mdi('console', '#57F287'), targets: { arch: 'fastfetch', fedora: 'fastfetch', opensuse: 'fastfetch', nix: 'fastfetch' }, unavailableReason: 'Not in Ubuntu/Debian repos. Install from [github.com/fastfetch-cli/fastfetch](https://github.com/fastfetch-cli/fastfetch/releases).' },
{ id: 'neofetch', name: 'neofetch', description: 'Display system info with ASCII distro logo', category: 'CLI Tools', iconUrl: si('linux', '#FCC624'), targets: { ubuntu: 'neofetch', debian: 'neofetch', arch: 'neofetch', opensuse: 'neofetch', nix: 'neofetch' }, unavailableReason: 'Archived project; removed from Fedora. CLI-only (no Flatpak/Snap).' },
{ id: 'eza', name: 'eza', description: 'Modern ls replacement with colors and icons', category: 'CLI Tools', iconUrl: mdi('format-list-bulleted', '#F9E64F'), targets: { ubuntu: 'eza', debian: 'eza', arch: 'eza', opensuse: 'eza', nix: 'eza' }, unavailableReason: 'Unmaintained on Fedora; removed from repos. CLI-only (no Flatpak/Snap).' },
{ id: 'bat', name: 'bat', description: 'Cat clone with syntax highlighting and git integration', category: 'CLI Tools', iconUrl: mdi('file-code-outline', '#A6E22E'), targets: { ubuntu: 'bat', debian: 'bat', arch: 'bat', fedora: 'bat', opensuse: 'bat', nix: 'bat' }, unavailableReason: 'bat is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'fzf', name: 'fzf', description: 'Lightning-fast command-line fuzzy finder', category: 'CLI Tools', iconUrl: mdi('magnify', '#FF0055'), targets: { ubuntu: 'fzf', debian: 'fzf', arch: 'fzf', fedora: 'fzf', opensuse: 'fzf', nix: 'fzf' }, unavailableReason: 'fzf is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'ripgrep', name: 'ripgrep', description: 'Ultra-fast recursive search respecting .gitignore', category: 'CLI Tools', iconUrl: mdi('text-search', '#C0C0C0'), targets: { ubuntu: 'ripgrep', debian: 'ripgrep', arch: 'ripgrep', fedora: 'ripgrep', opensuse: 'ripgrep', nix: 'ripgrep' }, unavailableReason: 'ripgrep is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'zoxide', name: 'zoxide', description: 'Smart cd that learns your habits', category: 'CLI Tools', iconUrl: mdi('folder-move-outline', '#FF9F43'), targets: { ubuntu: 'zoxide', debian: 'zoxide', arch: 'zoxide', fedora: 'zoxide', opensuse: 'zoxide', nix: 'zoxide' }, unavailableReason: 'zoxide is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'tldr', name: 'tldr', description: 'Simplified community-driven man pages', category: 'CLI Tools', iconUrl: mdi('book-open-page-variant-outline', '#2D9CDB'), targets: { ubuntu: 'tldr', debian: 'tldr', arch: 'tldr', fedora: 'tldr', nix: 'tldr' }, unavailableReason: 'tldr is a CLI tool and not available on openSUSE, Flatpak, or Snap.' },
{ id: 'wget', name: 'wget', description: 'Command-line file downloader for HTTP/FTP', category: 'CLI Tools', iconUrl: mdi('download', '#3FA75E'), targets: { ubuntu: 'wget', debian: 'wget', arch: 'wget', fedora: 'wget', opensuse: 'wget', nix: 'wget' }, unavailableReason: 'wget is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'curl', name: 'curl', description: 'Command-line tool for transferring data via URLs', category: 'CLI Tools', iconUrl: si('curl', '#073551'), targets: { ubuntu: 'curl', debian: 'curl', arch: 'curl', fedora: 'curl', opensuse: 'curl', nix: 'curl' }, unavailableReason: 'curl is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'aria2', name: 'aria2', description: 'Multi-protocol, multi-source download accelerator', category: 'CLI Tools', iconUrl: mdi('download-multiple', '#F94144'), targets: { ubuntu: 'aria2', debian: 'aria2', arch: 'aria2', fedora: 'aria2', opensuse: 'aria2', nix: 'aria2' }, unavailableReason: 'aria2 is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'yazi', name: 'yazi', description: 'Blazing fast terminal file manager in Rust', category: 'CLI Tools', iconUrl: mdi('folder-table-outline', '#F3B329'), targets: { arch: 'yazi', opensuse: 'yazi', nix: 'yazi' }, unavailableReason: 'Not in most repos. Install via [cargo](https://crates.io/crates/yazi) or download from [github.com/sxyazi/yazi](https://github.com/sxyazi/yazi/releases).' },
{ id: 'ranger', name: 'ranger', description: 'Vim-inspired terminal file manager with previews', category: 'CLI Tools', iconUrl: mdi('folder-key-outline', '#FFFFFF'), targets: { ubuntu: 'ranger', debian: 'ranger', arch: 'ranger', fedora: 'ranger', opensuse: 'ranger', nix: 'ranger' }, unavailableReason: 'ranger is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'ncdu', name: 'ncdu', description: 'NCurses-based disk usage analyzer', category: 'CLI Tools', iconUrl: mdi('chart-arc', '#00ADEE'), targets: { ubuntu: 'ncdu', debian: 'ncdu', arch: 'ncdu', fedora: 'ncdu', opensuse: 'ncdu', nix: 'ncdu' }, unavailableReason: 'ncdu is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'fd', name: 'fd', description: 'Simple, fast alternative to find command', category: 'CLI Tools', iconUrl: mdi('file-search-outline', '#56BE89'), targets: { ubuntu: 'fd-find', debian: 'fd-find', arch: 'fd', fedora: 'fd-find', opensuse: 'fd', nix: 'fd' }, unavailableReason: 'fd is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'tmux', name: 'tmux', description: 'Terminal session manager and multiplexer', category: 'CLI Tools', iconUrl: si('tmux', '#1BB91F'), targets: { ubuntu: 'tmux', debian: 'tmux', arch: 'tmux', fedora: 'tmux', opensuse: 'tmux', nix: 'tmux' }, unavailableReason: 'tmux is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'superfile', name: 'Superfile', description: 'Modern terminal file manager with TUI', category: 'CLI Tools', iconUrl: mdi('folder-multiple', '#FFD93D'), targets: { arch: 'superfile', nix: 'superfile' }, unavailableReason: 'Install via `go install` or see [superfile.dev](https://superfile.dev/getting-started/installation/).' },
{ id: 'rsync', name: 'rsync', description: 'Fast incremental file transfer and sync tool', category: 'CLI Tools', iconUrl: mdi('sync', '#2ECC71'), targets: { ubuntu: 'rsync', debian: 'rsync', arch: 'rsync', fedora: 'rsync', opensuse: 'rsync', nix: 'rsync' }, unavailableReason: 'rsync is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'uv', name: 'uv', description: 'Fast Python package manager', category: 'Dev: Languages', iconUrl: si('astral', '#5C4EE5'), targets: { arch: 'uv', nix: 'uv' }, unavailableReason: 'Install via `curl -LsSf https://astral.sh/uv/install.sh | sh`. See [installation guide](https://docs.astral.sh/uv/getting-started/installation/).' },
// MEDIA
{ id: 'vlc', name: 'VLC', description: 'Plays any format', category: 'Media', iconUrl: si('vlcmediaplayer', '#FF8800'), targets: { ubuntu: 'vlc', debian: 'vlc', arch: 'vlc', fedora: 'vlc', opensuse: 'vlc', nix: 'vlc', flatpak: 'org.videolan.VLC', snap: 'vlc' } },
{ id: 'mpv', name: 'mpv', description: 'Minimal player', category: 'Media', iconUrl: si('mpv', '#691F69'), targets: { ubuntu: 'mpv', debian: 'mpv', arch: 'mpv', fedora: 'mpv', opensuse: 'mpv', nix: 'mpv', flatpak: 'io.mpv.Mpv', snap: 'mpv' } },
{ id: 'celluloid', name: 'Celluloid', description: 'GTK mpv frontend', category: 'Media', iconUrl: si('gnome', '#4A86CF'), targets: { ubuntu: 'celluloid', debian: 'celluloid', arch: 'celluloid', fedora: 'celluloid', opensuse: 'celluloid', nix: 'celluloid', flatpak: 'io.github.celluloid_player.Celluloid' }, unavailableReason: 'Celluloid is not available as a Snap package.' },
{ id: 'strawberry', name: 'Strawberry', description: 'Music player', category: 'Media', iconUrl: si('musicbrainz', '#BA478F'), targets: { ubuntu: 'strawberry', debian: 'strawberry', arch: 'strawberry', fedora: 'strawberry', opensuse: 'strawberry', nix: 'strawberry', flatpak: 'org.strawberrymusicplayer.strawberry' }, unavailableReason: 'Strawberry is not available as a Snap package.' },
{ id: 'spotify', name: 'Spotify', description: 'Music streaming', category: 'Media', iconUrl: si('spotify', '#1DB954'), targets: { arch: 'spotify', nix: 'spotify', flatpak: 'com.spotify.Client', snap: 'spotify' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/apps/details/com.spotify.Client) or [Snap](https://snapcraft.io/spotify) or follow instructions at [spotify.com/download/linux](https://www.spotify.com/download/linux/).' },
{ id: 'audacity', name: 'Audacity', description: 'Audio editor', category: 'Media', iconUrl: si('audacity', '#0000CC'), targets: { ubuntu: 'audacity', debian: 'audacity', arch: 'audacity', fedora: 'audacity', opensuse: 'audacity', nix: 'audacity', flatpak: 'org.audacityteam.Audacity', snap: 'audacity' } },
{ id: 'kdenlive', name: 'Kdenlive', description: 'Video editor', category: 'Media', iconUrl: si('kdenlive', '#527EB2'), targets: { ubuntu: 'kdenlive', debian: 'kdenlive', arch: 'kdenlive', fedora: 'kdenlive', opensuse: 'kdenlive', nix: 'kdenlive', flatpak: 'org.kde.kdenlive', snap: 'kdenlive' } },
{ id: 'obs', name: 'OBS Studio', description: 'Streaming', category: 'Media', iconUrl: si('obsstudio', '#A3A3A3'), targets: { ubuntu: 'obs-studio', debian: 'obs-studio', arch: 'obs-studio', fedora: 'obs-studio', opensuse: 'obs-studio', nix: 'obs-studio', flatpak: 'com.obsproject.Studio', snap: 'obs-studio' } },
{ id: 'ffmpeg', name: 'FFmpeg', description: 'Media converter', category: 'Media', iconUrl: si('ffmpeg', '#007808'), targets: { ubuntu: 'ffmpeg', debian: 'ffmpeg', arch: 'ffmpeg', fedora: 'ffmpeg', opensuse: 'ffmpeg', nix: 'ffmpeg' }, unavailableReason: 'FFmpeg is a system library and not available via Flatpak or Snap.' },
{ id: 'handbrake', name: 'HandBrake', description: 'Video transcoder', category: 'Media', iconUrl: mdi('video-vintage', '#F83262'), targets: { ubuntu: 'handbrake', debian: 'handbrake', arch: 'handbrake', opensuse: 'handbrake', nix: 'handbrake', flatpak: 'fr.handbrake.ghb', snap: 'handbrake-jz' }, unavailableReason: 'HandBrake is not in official Fedora repos. Use [Flatpak](https://flathub.org/apps/details/fr.handbrake.ghb) or [Snap](https://snapcraft.io/handbrake-jz) instead.' },
{ id: 'stremio', name: 'Stremio', description: 'Media center', category: 'Media', iconUrl: si('stremio', '#8A5AAB'), targets: { arch: 'stremio', flatpak: 'com.stremio.Stremio' }, unavailableReason: 'Only available via [AUR](https://aur.archlinux.org/packages/stremio) or [Flatpak](https://flathub.org/apps/details/com.stremio.Stremio), see [stremio.com/downloads](https://www.stremio.com/downloads) for more info.' },
{ id: 'kodi', name: 'Kodi', description: 'Home theater', category: 'Media', iconUrl: si('kodi', '#17B2E7'), targets: { ubuntu: 'kodi', debian: 'kodi', arch: 'kodi', fedora: 'kodi', opensuse: 'kodi', nix: 'kodi', flatpak: 'tv.kodi.Kodi', snap: 'kodi' } },
{ id: 'haruna', name: 'Haruna', description: 'Qt/QML Video Player', category: 'Media', iconUrl: si('hevy', '#A3A3A3'), targets: { ubuntu: 'haruna', debian: 'haruna', arch: 'haruna', fedora: 'haruna', opensuse: 'haruna', nix: 'haruna', flatpak: 'org.kde.haruna', snap: 'haruna' } },
{ id: 'shortwave', name: 'Shortwave', description: 'Internet radio', category: 'Media', iconUrl: si('playerfm', '#478ECC'), targets: { ubuntu: 'shortwave', debian: 'shortwave', arch: 'shortwave', fedora: 'shortwave', opensuse: 'shortwave', nix: 'shortwave', flatpak: 'de.haeckerfelix.Shortwave', snap: 'shortwave' } },
{ id: 'vlc', name: 'VLC', description: 'Universal media player that plays any format', category: 'Media', iconUrl: si('vlcmediaplayer', '#FF8800'), targets: { ubuntu: 'vlc', debian: 'vlc', arch: 'vlc', fedora: 'vlc', opensuse: 'vlc', nix: 'vlc', flatpak: 'org.videolan.VLC', snap: 'vlc' } },
{ id: 'mpv', name: 'mpv', description: 'Lightweight and highly configurable media player', category: 'Media', iconUrl: si('mpv', '#691F69'), targets: { ubuntu: 'mpv', debian: 'mpv', arch: 'mpv', fedora: 'mpv', opensuse: 'mpv', nix: 'mpv', flatpak: 'io.mpv.Mpv', snap: 'mpv' } },
{ id: 'celluloid', name: 'Celluloid', description: 'Simple GTK frontend for the mpv player', category: 'Media', iconUrl: si('gnome', '#4A86CF'), targets: { ubuntu: 'celluloid', debian: 'celluloid', arch: 'celluloid', fedora: 'celluloid', opensuse: 'celluloid', nix: 'celluloid', flatpak: 'io.github.celluloid_player.Celluloid' }, unavailableReason: 'Celluloid is not available as a Snap package.' },
{ id: 'strawberry', name: 'Strawberry', description: 'Music player for local audio collection', category: 'Media', iconUrl: si('musicbrainz', '#BA478F'), targets: { ubuntu: 'strawberry', debian: 'strawberry', arch: 'strawberry', fedora: 'strawberry', opensuse: 'strawberry', nix: 'strawberry', flatpak: 'org.strawberrymusicplayer.strawberry' }, unavailableReason: 'Strawberry is not available as a Snap package.' },
{ id: 'spotify', name: 'Spotify', description: 'Popular music streaming service', category: 'Media', iconUrl: si('spotify', '#1DB954'), targets: { arch: 'spotify', nix: 'spotify', flatpak: 'com.spotify.Client', snap: 'spotify' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/apps/details/com.spotify.Client) or [Snap](https://snapcraft.io/spotify) or follow instructions at [spotify.com/download/linux](https://www.spotify.com/download/linux/).' },
{ id: 'audacity', name: 'Audacity', description: 'Free open-source audio editor and recorder', category: 'Media', iconUrl: si('audacity', '#0000CC'), targets: { ubuntu: 'audacity', debian: 'audacity', arch: 'audacity', fedora: 'audacity', opensuse: 'audacity', nix: 'audacity', flatpak: 'org.audacityteam.Audacity', snap: 'audacity' } },
{ id: 'kdenlive', name: 'Kdenlive', description: 'Powerful open-source video editor by KDE', category: 'Media', iconUrl: si('kdenlive', '#527EB2'), targets: { ubuntu: 'kdenlive', debian: 'kdenlive', arch: 'kdenlive', fedora: 'kdenlive', opensuse: 'kdenlive', nix: 'kdenlive', flatpak: 'org.kde.kdenlive', snap: 'kdenlive' } },
{ id: 'obs', name: 'OBS Studio', description: 'Industry-standard streaming and recording software', category: 'Media', iconUrl: si('obsstudio', '#A3A3A3'), targets: { ubuntu: 'obs-studio', debian: 'obs-studio', arch: 'obs-studio', fedora: 'obs-studio', opensuse: 'obs-studio', nix: 'obs-studio', flatpak: 'com.obsproject.Studio', snap: 'obs-studio' } },
{ id: 'ffmpeg', name: 'FFmpeg', description: 'Swiss army knife of video/audio processing', category: 'Media', iconUrl: si('ffmpeg', '#007808'), targets: { ubuntu: 'ffmpeg', debian: 'ffmpeg', arch: 'ffmpeg', fedora: 'ffmpeg', opensuse: 'ffmpeg', nix: 'ffmpeg' }, unavailableReason: 'FFmpeg is a system library and not available via Flatpak or Snap.' },
{ id: 'handbrake', name: 'HandBrake', description: 'Open-source video transcoder for any format', category: 'Media', iconUrl: mdi('video-vintage', '#F83262'), targets: { ubuntu: 'handbrake', debian: 'handbrake', arch: 'handbrake', opensuse: 'handbrake', nix: 'handbrake', flatpak: 'fr.handbrake.ghb', snap: 'handbrake-jz' }, unavailableReason: 'HandBrake is not in official Fedora repos. Use [Flatpak](https://flathub.org/apps/details/fr.handbrake.ghb) or [Snap](https://snapcraft.io/handbrake-jz) instead.' },
{ id: 'stremio', name: 'Stremio', description: 'Modern media center with streaming addons', category: 'Media', iconUrl: si('stremio', '#8A5AAB'), targets: { arch: 'stremio', flatpak: 'com.stremio.Stremio' }, unavailableReason: 'Only available via [AUR](https://aur.archlinux.org/packages/stremio) or [Flatpak](https://flathub.org/apps/details/com.stremio.Stremio), see [stremio.com/downloads](https://www.stremio.com/downloads) for more info.' },
{ id: 'kodi', name: 'Kodi', description: 'Open-source home theater and media center', category: 'Media', iconUrl: si('kodi', '#17B2E7'), targets: { ubuntu: 'kodi', debian: 'kodi', arch: 'kodi', fedora: 'kodi', opensuse: 'kodi', nix: 'kodi', flatpak: 'tv.kodi.Kodi', snap: 'kodi' } },
{ id: 'haruna', name: 'Haruna', description: 'Modern Qt/QML video player powered by mpv', category: 'Media', iconUrl: si('hevy', '#A3A3A3'), targets: { ubuntu: 'haruna', debian: 'haruna', arch: 'haruna', fedora: 'haruna', opensuse: 'haruna', nix: 'haruna', flatpak: 'org.kde.haruna', snap: 'haruna' } },
{ id: 'shortwave', name: 'Shortwave', description: 'Listen to internet radio stations worldwide', category: 'Media', iconUrl: si('playerfm', '#478ECC'), targets: { ubuntu: 'shortwave', debian: 'shortwave', arch: 'shortwave', fedora: 'shortwave', opensuse: 'shortwave', nix: 'shortwave', flatpak: 'de.haeckerfelix.Shortwave', snap: 'shortwave' } },
// CREATIVE
{ id: 'blender', name: 'Blender', description: '3D modeling', category: 'Creative', iconUrl: si('blender', '#E87D0D'), targets: { ubuntu: 'blender', debian: 'blender', arch: 'blender', fedora: 'blender', opensuse: 'blender', nix: 'blender', flatpak: 'org.blender.Blender', snap: 'blender --classic' } },
{ id: 'gimp', name: 'GIMP', description: 'Image editor', category: 'Creative', iconUrl: si('gimp', '#5C5543'), targets: { ubuntu: 'gimp', debian: 'gimp', arch: 'gimp', fedora: 'gimp', opensuse: 'gimp', nix: 'gimp', flatpak: 'org.gimp.GIMP', snap: 'gimp' } },
{ id: 'inkscape', name: 'Inkscape', description: 'Vector graphics', category: 'Creative', iconUrl: 'https://media.inkscape.org/static/images/inkscape-logo.svg', targets: { ubuntu: 'inkscape', debian: 'inkscape', arch: 'inkscape', fedora: 'inkscape', opensuse: 'inkscape', nix: 'inkscape', flatpak: 'org.inkscape.Inkscape', snap: 'inkscape' } },
{ id: 'krita', name: 'Krita', description: 'Digital painting', category: 'Creative', iconUrl: si('krita', '#337FCC'), targets: { ubuntu: 'krita', debian: 'krita', arch: 'krita', fedora: 'krita', opensuse: 'krita', nix: 'krita', flatpak: 'org.kde.krita', snap: 'krita' } },
{ id: 'darktable', name: 'Darktable', description: 'Photo workflow', category: 'Creative', iconUrl: 'https://www.svgrepo.com/show/378112/darktable.svg', targets: { ubuntu: 'darktable', debian: 'darktable', arch: 'darktable', fedora: 'darktable', opensuse: 'darktable', nix: 'darktable', flatpak: 'org.darktable.Darktable', snap: 'darktable' } },
{ id: 'freecad', name: 'FreeCAD', description: '3D CAD', category: 'Creative', iconUrl: si('freecad', '#CB333B'), targets: { ubuntu: 'freecad', debian: 'freecad', arch: 'freecad', fedora: 'freecad', opensuse: 'freecad', nix: 'freecad', flatpak: 'org.freecad.FreeCAD', snap: 'freecad' } },
{ id: 'kicad', name: 'KiCad', description: 'PCB design', category: 'Creative', iconUrl: si('kicad', '#314CB6'), targets: { ubuntu: 'kicad', debian: 'kicad', arch: 'kicad', fedora: 'kicad', opensuse: 'kicad', nix: 'kicad', flatpak: 'org.kicad.KiCad', snap: 'kicad' } },
{ id: 'cura', name: 'UltiMaker Cura', description: '3D printing slicer', category: 'Creative', iconUrl: 'https://dl.flathub.org/media/com/ultimaker/cura.desktop/9eeed6dfd5a5ec2c8e5c8917012db5ad/icons/128x128/com.ultimaker.cura.desktop.png', targets: { ubuntu: 'cura', debian: 'cura', arch: 'cura', fedora: 'cura', opensuse: 'cura', nix: 'cura', flatpak: 'com.ultimaker.cura', snap: 'cura-slicer' } },
{ id: 'orcaslicer', name: 'OrcaSlicer', description: 'G-code 3D slicer', category: 'Creative', iconUrl: mdi('printer-3d-nozzle', '#00AE42'), targets: { arch: 'orcaslicer-bin', nix: 'orcaslicer', flatpak: 'net.orcaslicer.OrcaSlicer' }, unavailableReason: 'Use [Flatpak](https://flathub.org/apps/net.orcaslicer.OrcaSlicer) or AppImage from [GitHub](https://github.com/SoftFever/OrcaSlicer/releases).' },
{ id: 'blender', name: 'Blender', description: 'Industry-grade 3D creation suite', category: 'Creative', iconUrl: si('blender', '#E87D0D'), targets: { ubuntu: 'blender', debian: 'blender', arch: 'blender', fedora: 'blender', opensuse: 'blender', nix: 'blender', flatpak: 'org.blender.Blender', snap: 'blender --classic' } },
{ id: 'gimp', name: 'GIMP', description: 'Powerful free image editor, Photoshop alternative', category: 'Creative', iconUrl: si('gimp', '#5C5543'), targets: { ubuntu: 'gimp', debian: 'gimp', arch: 'gimp', fedora: 'gimp', opensuse: 'gimp', nix: 'gimp', flatpak: 'org.gimp.GIMP', snap: 'gimp' } },
{ id: 'inkscape', name: 'Inkscape', description: 'Professional vector graphics editor', category: 'Creative', iconUrl: 'https://media.inkscape.org/static/images/inkscape-logo.svg', targets: { ubuntu: 'inkscape', debian: 'inkscape', arch: 'inkscape', fedora: 'inkscape', opensuse: 'inkscape', nix: 'inkscape', flatpak: 'org.inkscape.Inkscape', snap: 'inkscape' } },
{ id: 'krita', name: 'Krita', description: 'Professional digital painting application', category: 'Creative', iconUrl: si('krita', '#337FCC'), targets: { ubuntu: 'krita', debian: 'krita', arch: 'krita', fedora: 'krita', opensuse: 'krita', nix: 'krita', flatpak: 'org.kde.krita', snap: 'krita' } },
{ id: 'darktable', name: 'Darktable', description: 'Professional photography workflow application', category: 'Creative', iconUrl: 'https://www.svgrepo.com/show/378112/darktable.svg', targets: { ubuntu: 'darktable', debian: 'darktable', arch: 'darktable', fedora: 'darktable', opensuse: 'darktable', nix: 'darktable', flatpak: 'org.darktable.Darktable', snap: 'darktable' } },
{ id: 'freecad', name: 'FreeCAD', description: 'Open-source parametric 3D CAD modeler', category: 'Creative', iconUrl: si('freecad', '#CB333B'), targets: { ubuntu: 'freecad', debian: 'freecad', arch: 'freecad', fedora: 'freecad', opensuse: 'freecad', nix: 'freecad', flatpak: 'org.freecad.FreeCAD', snap: 'freecad' } },
{ id: 'kicad', name: 'KiCad', description: 'Professional PCB and schematic design suite', category: 'Creative', iconUrl: si('kicad', '#314CB6'), targets: { ubuntu: 'kicad', debian: 'kicad', arch: 'kicad', fedora: 'kicad', opensuse: 'kicad', nix: 'kicad', flatpak: 'org.kicad.KiCad', snap: 'kicad' } },
{ id: 'cura', name: 'UltiMaker Cura', description: 'Popular 3D printer slicing application', category: 'Creative', iconUrl: 'https://dl.flathub.org/media/com/ultimaker/cura.desktop/9eeed6dfd5a5ec2c8e5c8917012db5ad/icons/128x128/com.ultimaker.cura.desktop.png', targets: { ubuntu: 'cura', debian: 'cura', arch: 'cura', fedora: 'cura', opensuse: 'cura', nix: 'cura', flatpak: 'com.ultimaker.cura', snap: 'cura-slicer' } },
{ id: 'orcaslicer', name: 'OrcaSlicer', description: 'Advanced 3D printer slicer based on BambuStudio', category: 'Creative', iconUrl: mdi('printer-3d-nozzle', '#00AE42'), targets: { arch: 'orcaslicer-bin', nix: 'orcaslicer', flatpak: 'net.orcaslicer.OrcaSlicer' }, unavailableReason: 'Use [Flatpak](https://flathub.org/apps/net.orcaslicer.OrcaSlicer) or AppImage from [GitHub](https://github.com/SoftFever/OrcaSlicer/releases).' },
// GAMING
{ id: 'steam', name: 'Steam', description: 'Gaming platform', category: 'Gaming', iconUrl: si('steam', '#00ADEE'), targets: { ubuntu: 'steam', debian: 'steam-installer', arch: 'steam', fedora: 'steam', opensuse: 'steam', nix: 'steam', flatpak: 'com.valvesoftware.Steam', snap: 'steam' } },
{ id: 'lutris', name: 'Lutris', description: 'Game manager', category: 'Gaming', iconUrl: si('lutris', '#FF8F00'), targets: { ubuntu: 'lutris', debian: 'lutris', arch: 'lutris', fedora: 'lutris', opensuse: 'lutris', nix: 'lutris', flatpak: 'net.lutris.Lutris' }, unavailableReason: 'Lutris is not available as a Snap package.' },
{ id: 'heroic', name: 'Heroic', description: 'Epic/GOG launcher', category: 'Gaming', iconUrl: si('heroicgameslauncher', '#7B62E8'), targets: { arch: 'heroic-games-launcher-bin', nix: 'heroic', flatpak: 'com.heroicgameslauncher.hgl' }, unavailableReason: 'Only available via [AUR](https://aur.archlinux.org/packages/heroic-games-launcher-bin) or [Flatpak](https://flathub.org/apps/details/com.heroicgameslauncher.hgl). see [heroicgameslauncher.com](https://heroicgameslauncher.com/downloads) for more info.' },
{ id: 'prism', name: 'Prism Launcher', description: 'Minecraft launcher', category: 'Gaming', iconUrl: 'https://raw.githubusercontent.com/PrismLauncher/PrismLauncher/develop/program_info/org.prismlauncher.PrismLauncher.logo.svg', targets: { arch: 'prismlauncher', nix: 'prismlauncher', flatpak: 'org.prismlauncher.PrismLauncher' }, unavailableReason: 'Only available via [AUR](https://aur.archlinux.org/packages/prismlauncher) or [Flatpak](https://flathub.org/apps/details/org.prismlauncher.PrismLauncher). see [prismlauncher.org](https://prismlauncher.org/download/) for more info.' },
{ id: 'retroarch', name: 'RetroArch', description: 'Emulator frontend', category: 'Gaming', iconUrl: si('retroarch', '#A0A0A0'), targets: { ubuntu: 'retroarch', debian: 'retroarch', arch: 'retroarch', fedora: 'retroarch', opensuse: 'retroarch', nix: 'retroarch', flatpak: 'org.libretro.RetroArch', snap: 'retroarch' } },
{ id: 'mangohud', name: 'MangoHud', description: 'Performance overlay', category: 'Gaming', iconUrl: si('gamejolt', '#FF9900'), targets: { ubuntu: 'mangohud', debian: 'mangohud', arch: 'mangohud', fedora: 'mangohud', opensuse: 'mangohud', nix: 'mangohud' }, unavailableReason: 'MangoHud is a system overlay and not available via Flatpak or Snap.' },
{ id: 'gamemode', name: 'GameMode', description: 'Gaming optimizer', category: 'Gaming', iconUrl: 'https://www.svgrepo.com/show/411187/game.svg', targets: { ubuntu: 'gamemode', debian: 'gamemode', arch: 'gamemode', fedora: 'gamemode', opensuse: 'gamemode', nix: 'gamemode' }, unavailableReason: 'GameMode is a system service and not available via Flatpak or Snap.' },
{ id: 'protonup', name: 'ProtonUp-Qt', description: 'Proton-GE manager', category: 'Gaming', iconUrl: si('protondb', '#8B0000'), targets: { arch: 'protonup-qt-bin', nix: 'protonup-qt', flatpak: 'net.davidotek.pupgui2' }, unavailableReason: 'Only available via [AUR](https://aur.archlinux.org/packages/protonup-qt-bin) or [Flatpak](https://flathub.org/apps/net.davidotek.pupgui2).' },
{ id: 'antimicrox', name: 'AntiMicroX', description: 'Gamepad key mapper', category: 'Gaming', iconUrl: mdi('controller', '#007BFF'), targets: { ubuntu: 'antimicrox', debian: 'antimicrox', arch: 'antimicrox', fedora: 'antimicrox', opensuse: 'antimicrox', nix: 'antimicrox', flatpak: 'io.github.antimicrox.antimicrox' }, unavailableReason: 'Not available as a Snap package.' },
{ id: 'steam', name: 'Steam', description: 'Largest PC gaming platform and store', category: 'Gaming', iconUrl: si('steam', '#00ADEE'), targets: { ubuntu: 'steam', debian: 'steam-installer', arch: 'steam', fedora: 'steam', opensuse: 'steam', nix: 'steam', flatpak: 'com.valvesoftware.Steam', snap: 'steam' } },
{ id: 'lutris', name: 'Lutris', description: 'Open gaming platform for all your games', category: 'Gaming', iconUrl: si('lutris', '#FF8F00'), targets: { ubuntu: 'lutris', debian: 'lutris', arch: 'lutris', fedora: 'lutris', opensuse: 'lutris', nix: 'lutris', flatpak: 'net.lutris.Lutris' }, unavailableReason: 'Lutris is not available as a Snap package.' },
{ id: 'heroic', name: 'Heroic', description: 'Open-source Epic Games and GOG launcher', category: 'Gaming', iconUrl: si('heroicgameslauncher', '#7B62E8'), targets: { arch: 'heroic-games-launcher-bin', nix: 'heroic', flatpak: 'com.heroicgameslauncher.hgl' }, unavailableReason: 'Only available via [AUR](https://aur.archlinux.org/packages/heroic-games-launcher-bin) or [Flatpak](https://flathub.org/apps/details/com.heroicgameslauncher.hgl). see [heroicgameslauncher.com](https://heroicgameslauncher.com/downloads) for more info.' },
{ id: 'prism', name: 'Prism Launcher', description: 'Open-source Minecraft launcher with mod support', category: 'Gaming', iconUrl: 'https://raw.githubusercontent.com/PrismLauncher/PrismLauncher/develop/program_info/org.prismlauncher.PrismLauncher.logo.svg', targets: { arch: 'prismlauncher', nix: 'prismlauncher', flatpak: 'org.prismlauncher.PrismLauncher' }, unavailableReason: 'Only available via [AUR](https://aur.archlinux.org/packages/prismlauncher) or [Flatpak](https://flathub.org/apps/details/org.prismlauncher.PrismLauncher). see [prismlauncher.org](https://prismlauncher.org/download/) for more info.' },
{ id: 'retroarch', name: 'RetroArch', description: 'All-in-one emulator frontend for retro gaming', category: 'Gaming', iconUrl: si('retroarch', '#A0A0A0'), targets: { ubuntu: 'retroarch', debian: 'retroarch', arch: 'retroarch', fedora: 'retroarch', opensuse: 'retroarch', nix: 'retroarch', flatpak: 'org.libretro.RetroArch', snap: 'retroarch' } },
{ id: 'mangohud', name: 'MangoHud', description: 'Vulkan/OpenGL overlay for monitoring FPS and hardware', category: 'Gaming', iconUrl: si('gamejolt', '#FF9900'), targets: { ubuntu: 'mangohud', debian: 'mangohud', arch: 'mangohud', fedora: 'mangohud', opensuse: 'mangohud', nix: 'mangohud' }, unavailableReason: 'MangoHud is a system overlay and not available via Flatpak or Snap.' },
{ id: 'gamemode', name: 'GameMode', description: 'Optimizes Linux system performance for gaming', category: 'Gaming', iconUrl: 'https://www.svgrepo.com/show/411187/game.svg', targets: { ubuntu: 'gamemode', debian: 'gamemode', arch: 'gamemode', fedora: 'gamemode', opensuse: 'gamemode', nix: 'gamemode' }, unavailableReason: 'GameMode is a system service and not available via Flatpak or Snap.' },
{ id: 'protonup', name: 'ProtonUp-Qt', description: 'Install and manage Proton-GE for Steam games', category: 'Gaming', iconUrl: si('protondb', '#8B0000'), targets: { arch: 'protonup-qt-bin', nix: 'protonup-qt', flatpak: 'net.davidotek.pupgui2' }, unavailableReason: 'Only available via [AUR](https://aur.archlinux.org/packages/protonup-qt-bin) or [Flatpak](https://flathub.org/apps/net.davidotek.pupgui2).' },
{ id: 'antimicrox', name: 'AntiMicroX', description: 'Map gamepad buttons to keyboard/mouse actions', category: 'Gaming', iconUrl: mdi('controller', '#007BFF'), targets: { ubuntu: 'antimicrox', debian: 'antimicrox', arch: 'antimicrox', fedora: 'antimicrox', opensuse: 'antimicrox', nix: 'antimicrox', flatpak: 'io.github.antimicrox.antimicrox' }, unavailableReason: 'Not available as a Snap package.' },
// OFFICE
{ id: 'libreoffice', name: 'LibreOffice', description: 'Office suite', category: 'Office', iconUrl: si('libreoffice', '#0369A1'), targets: { ubuntu: 'libreoffice', debian: 'libreoffice', arch: 'libreoffice-fresh', fedora: 'libreoffice', opensuse: 'libreoffice', nix: 'libreoffice', flatpak: 'org.libreoffice.LibreOffice', snap: 'libreoffice' } },
{ id: 'onlyoffice', name: 'OnlyOffice', description: 'Office suite', category: 'Office', iconUrl: si('onlyoffice', '#FF6F3D'), targets: { arch: 'onlyoffice-bin', nix: 'onlyoffice-bin', flatpak: 'org.onlyoffice.desktopeditors', snap: 'onlyoffice-desktopeditors' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/apps/details/org.onlyoffice.desktopeditors) or [Snap](https://snapcraft.io/onlyoffice-desktopeditors) or download from [onlyoffice.com/desktop](https://www.onlyoffice.com/desktop.aspx).' },
{ id: 'obsidian', name: 'Obsidian', description: 'Markdown notes', category: 'Office', iconUrl: si('obsidian', '#7C3AED'), targets: { arch: 'obsidian', nix: 'obsidian', flatpak: 'md.obsidian.Obsidian', snap: 'obsidian --classic' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/apps/details/md.obsidian.Obsidian) or [Snap](https://snapcraft.io/obsidian) or download from [obsidian.md/download](https://obsidian.md/download).' },
{ id: 'logseq', name: 'Logseq', description: 'Knowledge management', category: 'Office', iconUrl: si('logseq', '#06D5D5'), targets: { arch: 'logseq-desktop-bin', nix: 'logseq', flatpak: 'com.logseq.Logseq', snap: 'logseq' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/apps/details/com.logseq.Logseq) or [Snap](https://snapcraft.io/logseq) or download from [logseq.com](https://logseq.com/downloads).' },
{ id: 'joplin', name: 'Joplin', description: 'Note-taking', category: 'Office', iconUrl: si('joplin', '#1471B7'), targets: { arch: 'joplin-appimage', nix: 'joplin-desktop', flatpak: 'net.cozic.joplin_desktop', snap: 'joplin-desktop' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/apps/details/net.cozic.joplin_desktop) or [Snap](https://snapcraft.io/joplin-desktop) or install from [joplinapp.org](https://joplinapp.org/help/install/).' },
{ id: 'okular', name: 'Okular', description: 'PDF viewer', category: 'Office', iconUrl: si('kde', '#338BDB'), targets: { ubuntu: 'okular', debian: 'okular', arch: 'okular', fedora: 'okular', opensuse: 'okular', nix: 'okular', flatpak: 'org.kde.okular', snap: 'okular' } },
{ id: 'zathura', name: 'Zathura', description: 'Minimal PDF viewer', category: 'Office', iconUrl: 'https://raw.githubusercontent.com/TrixieUA/MoreWaita-copr-trixieua/e5bed029d63d4c14f1aba811152f3f0bf473a4bc/scalable/apps/zathura.svg', targets: { ubuntu: 'zathura', debian: 'zathura', arch: 'zathura', fedora: 'zathura', opensuse: 'zathura', nix: 'zathura' }, unavailableReason: 'Zathura is not available via Flatpak or Snap.' },
{ id: 'calibre', name: 'Calibre', description: 'E-book manager', category: 'Office', iconUrl: si('calibreweb', '#ECA315'), targets: { ubuntu: 'calibre', debian: 'calibre', arch: 'calibre', fedora: 'calibre', opensuse: 'calibre', nix: 'calibre', flatpak: 'com.calibre_ebook.calibre' }, unavailableReason: 'Calibre is not available as a Snap package.' },
{ id: 'xournalpp', name: 'Xournal++', description: 'PDF annotation', category: 'Office', iconUrl: vs('file-type-pdf2'), targets: { ubuntu: 'xournalpp', debian: 'xournalpp', arch: 'xournalpp', fedora: 'xournalpp', opensuse: 'xournalpp', nix: 'xournalpp', flatpak: 'com.github.xournalpp.xournalpp' }, unavailableReason: 'Xournal++ is not available as a Snap package.' },
{ id: 'zotero', name: 'Zotero', description: 'Research organizer', category: 'Office', iconUrl: si('zotero', '#CC2936'), targets: { nix: 'zotero', flatpak: 'org.zotero.Zotero', snap: 'zotero-snap' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/apps/org.zotero.Zotero)/[Snap](https://snapcraft.io/zotero-snap) or download from [zotero.org](https://www.zotero.org/download/).' },
{ id: 'libreoffice', name: 'LibreOffice', description: 'Full-featured open-source office suite', category: 'Office', iconUrl: si('libreoffice', '#0369A1'), targets: { ubuntu: 'libreoffice', debian: 'libreoffice', arch: 'libreoffice-fresh', fedora: 'libreoffice', opensuse: 'libreoffice', nix: 'libreoffice', flatpak: 'org.libreoffice.LibreOffice', snap: 'libreoffice' } },
{ id: 'onlyoffice', name: 'OnlyOffice', description: 'Modern office suite with MS Office compatibility', category: 'Office', iconUrl: si('onlyoffice', '#FF6F3D'), targets: { arch: 'onlyoffice-bin', nix: 'onlyoffice-bin', flatpak: 'org.onlyoffice.desktopeditors', snap: 'onlyoffice-desktopeditors' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/apps/details/org.onlyoffice.desktopeditors) or [Snap](https://snapcraft.io/onlyoffice-desktopeditors) or download from [onlyoffice.com/desktop](https://www.onlyoffice.com/desktop.aspx).' },
{ id: 'obsidian', name: 'Obsidian', description: 'Popular Markdown-based knowledge management app', category: 'Office', iconUrl: si('obsidian', '#7C3AED'), targets: { arch: 'obsidian', nix: 'obsidian', flatpak: 'md.obsidian.Obsidian', snap: 'obsidian --classic' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/apps/details/md.obsidian.Obsidian) or [Snap](https://snapcraft.io/obsidian) or download from [obsidian.md/download](https://obsidian.md/download).' },
{ id: 'logseq', name: 'Logseq', description: 'Open-source outliner for knowledge management', category: 'Office', iconUrl: si('logseq', '#06D5D5'), targets: { arch: 'logseq-desktop-bin', nix: 'logseq', flatpak: 'com.logseq.Logseq', snap: 'logseq' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/apps/details/com.logseq.Logseq) or [Snap](https://snapcraft.io/logseq) or download from [logseq.com](https://logseq.com/downloads).' },
{ id: 'joplin', name: 'Joplin', description: 'Open-source note-taking with sync and encryption', category: 'Office', iconUrl: si('joplin', '#1471B7'), targets: { arch: 'joplin-appimage', nix: 'joplin-desktop', flatpak: 'net.cozic.joplin_desktop', snap: 'joplin-desktop' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/apps/details/net.cozic.joplin_desktop) or [Snap](https://snapcraft.io/joplin-desktop) or install from [joplinapp.org](https://joplinapp.org/help/install/).' },
{ id: 'okular', name: 'Okular', description: 'Universal document viewer by KDE', category: 'Office', iconUrl: si('kde', '#338BDB'), targets: { ubuntu: 'okular', debian: 'okular', arch: 'okular', fedora: 'okular', opensuse: 'okular', nix: 'okular', flatpak: 'org.kde.okular', snap: 'okular' } },
{ id: 'zathura', name: 'Zathura', description: 'Vim-style minimal PDF/document viewer', category: 'Office', iconUrl: 'https://raw.githubusercontent.com/TrixieUA/MoreWaita-copr-trixieua/e5bed029d63d4c14f1aba811152f3f0bf473a4bc/scalable/apps/zathura.svg', targets: { ubuntu: 'zathura', debian: 'zathura', arch: 'zathura', fedora: 'zathura', opensuse: 'zathura', nix: 'zathura' }, unavailableReason: 'Zathura is not available via Flatpak or Snap.' },
{ id: 'calibre', name: 'Calibre', description: 'Complete e-book library management solution', category: 'Office', iconUrl: si('calibreweb', '#ECA315'), targets: { ubuntu: 'calibre', debian: 'calibre', arch: 'calibre', fedora: 'calibre', opensuse: 'calibre', nix: 'calibre', flatpak: 'com.calibre_ebook.calibre' }, unavailableReason: 'Calibre is not available as a Snap package.' },
{ id: 'xournalpp', name: 'Xournal++', description: 'Handwriting and PDF annotation app', category: 'Office', iconUrl: vs('file-type-pdf2'), targets: { ubuntu: 'xournalpp', debian: 'xournalpp', arch: 'xournalpp', fedora: 'xournalpp', opensuse: 'xournalpp', nix: 'xournalpp', flatpak: 'com.github.xournalpp.xournalpp' }, unavailableReason: 'Xournal++ is not available as a Snap package.' },
{ id: 'zotero', name: 'Zotero', description: 'Research reference manager and citation tool', category: 'Office', iconUrl: si('zotero', '#CC2936'), targets: { arch: 'zotero-bin', nix: 'zotero', flatpak: 'org.zotero.Zotero', snap: 'zotero-snap' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/apps/org.zotero.Zotero)/[Snap](https://snapcraft.io/zotero-snap) or download from [zotero.org](https://www.zotero.org/download/).' },
// VPN & NETWORK
{ id: 'protonvpn', name: 'Proton VPN', description: 'Privacy VPN', category: 'VPN & Network', iconUrl: si('protonvpn', '#6D4AFF'), targets: { arch: 'proton-vpn-gtk-app', nix: 'protonvpn-gui', flatpak: 'com.protonvpn.www' }, unavailableReason: 'Not in official repos. Use Flatpak or follow instructions at [protonvpn.com/support/linux-vpn-setup](https://protonvpn.com/support/linux-vpn-setup/).' },
{ id: 'mullvad', name: 'Mullvad VPN', description: 'Privacy VPN', category: 'VPN & Network', iconUrl: si('mullvad', '#44AD4D'), targets: { arch: 'mullvad-vpn-bin', nix: 'mullvad-vpn' }, unavailableReason: 'Not in official repos. Use [AUR](https://aur.archlinux.org/packages/mullvad-vpn-bin) or see [official install guide](https://mullvad.net/en/help/install-mullvad-app-linux).' },
{ id: 'tailscale', name: 'Tailscale', description: 'Mesh VPN', category: 'VPN & Network', iconUrl: si('tailscale', '#797878'), targets: { ubuntu: 'tailscale', arch: 'tailscale', fedora: 'tailscale', opensuse: 'tailscale', nix: 'tailscale' }, unavailableReason: 'Not in Debian repos. Follow instructions at [tailscale.com/download/linux](https://tailscale.com/download/linux).' },
{ id: 'wireguard', name: 'WireGuard', description: 'Modern VPN', category: 'VPN & Network', iconUrl: si('wireguard', '#88171A'), targets: { ubuntu: 'wireguard', debian: 'wireguard', arch: 'wireguard-tools', fedora: 'wireguard-tools', opensuse: 'wireguard-tools', nix: 'wireguard-tools' }, unavailableReason: 'WireGuard is a kernel module and not available via Flatpak or Snap.' },
{ id: 'openvpn', name: 'OpenVPN', description: 'VPN client', category: 'VPN & Network', iconUrl: si('openvpn', '#EA7E20'), targets: { ubuntu: 'openvpn', debian: 'openvpn', arch: 'openvpn', fedora: 'openvpn', opensuse: 'openvpn', nix: 'openvpn' }, unavailableReason: 'OpenVPN is a system package and not available via Flatpak or Snap.' },
{ id: 'nmap', name: 'Nmap', description: 'Network scanner', category: 'VPN & Network', iconUrl: 'https://raw.githubusercontent.com/bwks/vendor-icons-svg/702f2ac88acc71759ce623bc5000a596195e9db3/nmap-logo.svg', targets: { ubuntu: 'nmap', debian: 'nmap', arch: 'nmap', fedora: 'nmap', opensuse: 'nmap', nix: 'nmap' }, unavailableReason: 'Nmap is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'openssh', name: 'OpenSSH', description: 'SSH client', category: 'VPN & Network', iconUrl: mdi('ssh', '#F5A623'), targets: { ubuntu: 'openssh-client', debian: 'openssh-client', arch: 'openssh', fedora: 'openssh-clients', opensuse: 'openssh', nix: 'openssh' }, unavailableReason: 'OpenSSH is a system package and not available via Flatpak or Snap.' },
{ id: 'protonvpn', name: 'Proton VPN', description: 'Secure VPN by the makers of ProtonMail', category: 'VPN & Network', iconUrl: si('protonvpn', '#6D4AFF'), targets: { arch: 'proton-vpn-gtk-app', nix: 'protonvpn-gui', flatpak: 'com.protonvpn.www' }, unavailableReason: 'Not in official repos. Use Flatpak or follow instructions at [protonvpn.com/support/linux-vpn-setup](https://protonvpn.com/support/linux-vpn-setup/).' },
{ id: 'mullvad', name: 'Mullvad VPN', description: 'Privacy-focused VPN with no-logging policy', category: 'VPN & Network', iconUrl: si('mullvad', '#44AD4D'), targets: { arch: 'mullvad-vpn-bin', nix: 'mullvad-vpn' }, unavailableReason: 'Not in official repos. Use [AUR](https://aur.archlinux.org/packages/mullvad-vpn-bin) or see [official install guide](https://mullvad.net/en/help/install-mullvad-app-linux).' },
{ id: 'tailscale', name: 'Tailscale', description: 'Zero-config mesh VPN using WireGuard', category: 'VPN & Network', iconUrl: si('tailscale', '#797878'), targets: { ubuntu: 'tailscale', arch: 'tailscale', fedora: 'tailscale', opensuse: 'tailscale', nix: 'tailscale' }, unavailableReason: 'Not in Debian repos. Follow instructions at [tailscale.com/download/linux](https://tailscale.com/download/linux).' },
{ id: 'wireguard', name: 'WireGuard', description: 'Fast, modern, secure VPN tunnel protocol', category: 'VPN & Network', iconUrl: si('wireguard', '#88171A'), targets: { ubuntu: 'wireguard', debian: 'wireguard', arch: 'wireguard-tools', fedora: 'wireguard-tools', opensuse: 'wireguard-tools', nix: 'wireguard-tools' }, unavailableReason: 'WireGuard is a kernel module and not available via Flatpak or Snap.' },
{ id: 'openvpn', name: 'OpenVPN', description: 'Full-featured SSL VPN solution', category: 'VPN & Network', iconUrl: si('openvpn', '#EA7E20'), targets: { ubuntu: 'openvpn', debian: 'openvpn', arch: 'openvpn', fedora: 'openvpn', opensuse: 'openvpn', nix: 'openvpn' }, unavailableReason: 'OpenVPN is a system package and not available via Flatpak or Snap.' },
{ id: 'nmap', name: 'Nmap', description: 'Network discovery and security auditing tool', category: 'VPN & Network', iconUrl: 'https://raw.githubusercontent.com/bwks/vendor-icons-svg/702f2ac88acc71759ce623bc5000a596195e9db3/nmap-logo.svg', targets: { ubuntu: 'nmap', debian: 'nmap', arch: 'nmap', fedora: 'nmap', opensuse: 'nmap', nix: 'nmap' }, unavailableReason: 'Nmap is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'openssh', name: 'OpenSSH', description: 'Secure remote login and file transfer via SSH', category: 'VPN & Network', iconUrl: mdi('ssh', '#F5A623'), targets: { ubuntu: 'openssh-client', debian: 'openssh-client', arch: 'openssh', fedora: 'openssh-clients', opensuse: 'openssh', nix: 'openssh' }, unavailableReason: 'OpenSSH is a system package and not available via Flatpak or Snap.' },
// SECURITY
{ id: 'bitwarden', name: 'Bitwarden', description: 'Password manager', category: 'Security', iconUrl: si('bitwarden', '#175DDC'), targets: { arch: 'bitwarden', nix: 'bitwarden', flatpak: 'com.bitwarden.desktop', snap: 'bitwarden' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/apps/details/com.bitwarden.desktop) or [Snap](https://snapcraft.io/bitwarden) or download from [bitwarden.com/download](https://bitwarden.com/download/).' },
{ id: 'keepassxc', name: 'KeePassXC', description: 'Offline passwords', category: 'Security', iconUrl: si('keepassxc', '#65B726'), targets: { ubuntu: 'keepassxc', debian: 'keepassxc', arch: 'keepassxc', fedora: 'keepassxc', opensuse: 'keepassxc', nix: 'keepassxc', flatpak: 'org.keepassxc.KeePassXC', snap: 'keepassxc' } },
{ id: 'veracrypt', name: 'VeraCrypt', description: 'Disk encryption', category: 'Security', iconUrl: 'https://raw.githubusercontent.com/PapirusDevelopmentTeam/papirus-icon-theme/3390a630b535d1c1ccca04881b3959e262264116/Papirus/64x64/apps/veracrypt.svg', targets: { ubuntu: 'veracrypt', arch: 'veracrypt', fedora: 'veracrypt', opensuse: 'veracrypt', nix: 'veracrypt' }, unavailableReason: 'Not in Debian repos. Download from [veracrypt.fr/en/Downloads](https://veracrypt.fr/en/Downloads.html).' },
{ id: 'gnupg', name: 'GnuPG', description: 'Encryption tool', category: 'Security', iconUrl: si('gnuprivacyguard', '#0093DD'), targets: { ubuntu: 'gnupg', debian: 'gnupg', arch: 'gnupg', fedora: 'gnupg2', opensuse: 'gnupg', nix: 'gnupg' }, unavailableReason: 'GnuPG is a system package and not available via Flatpak or Snap.' },
{ id: 'firejail', name: 'Firejail', description: 'Sandboxing', category: 'Security', iconUrl: 'https://linux.fi/w/images/1/1f/Firejail-logo.png', targets: { ubuntu: 'firejail', debian: 'firejail', arch: 'firejail', fedora: 'firejail', opensuse: 'firejail', nix: 'firejail' }, unavailableReason: 'Firejail is a system security tool and not available via Flatpak or Snap.' },
{ id: 'clamav', name: 'ClamAV', description: 'Antivirus', category: 'Security', iconUrl: 'https://raw.githubusercontent.com/ivangabriele/clamav-desktop/f60bfafdd23bb455f0468abe5f877d2b76eddfba/assets/icons/icon.svg', targets: { ubuntu: 'clamav', debian: 'clamav', arch: 'clamav', fedora: 'clamav', opensuse: 'clamav', nix: 'clamav' }, unavailableReason: 'ClamAV is a system package and not available via Flatpak or Snap.' },
{ id: 'bitwarden', name: 'Bitwarden', description: 'Open-source password manager with cloud sync', category: 'Security', iconUrl: si('bitwarden', '#175DDC'), targets: { arch: 'bitwarden', nix: 'bitwarden', flatpak: 'com.bitwarden.desktop', snap: 'bitwarden' }, unavailableReason: 'Not in official repos. Use [Flatpak](https://flathub.org/apps/details/com.bitwarden.desktop) or [Snap](https://snapcraft.io/bitwarden) or download from [bitwarden.com/download](https://bitwarden.com/download/).' },
{ id: 'keepassxc', name: 'KeePassXC', description: 'Secure offline-first password manager', category: 'Security', iconUrl: si('keepassxc', '#65B726'), targets: { ubuntu: 'keepassxc', debian: 'keepassxc', arch: 'keepassxc', fedora: 'keepassxc', opensuse: 'keepassxc', nix: 'keepassxc', flatpak: 'org.keepassxc.KeePassXC', snap: 'keepassxc' } },
{ id: 'veracrypt', name: 'VeraCrypt', description: 'Free disk encryption based on TrueCrypt', category: 'Security', iconUrl: 'https://raw.githubusercontent.com/PapirusDevelopmentTeam/papirus-icon-theme/3390a630b535d1c1ccca04881b3959e262264116/Papirus/64x64/apps/veracrypt.svg', targets: { ubuntu: 'veracrypt', arch: 'veracrypt', fedora: 'veracrypt', opensuse: 'veracrypt', nix: 'veracrypt' }, unavailableReason: 'Not in Debian repos. Download from [veracrypt.fr/en/Downloads](https://veracrypt.fr/en/Downloads.html).' },
{ id: 'gnupg', name: 'GnuPG', description: 'GNU Privacy Guard for encryption and signing', category: 'Security', iconUrl: si('gnuprivacyguard', '#0093DD'), targets: { ubuntu: 'gnupg', debian: 'gnupg', arch: 'gnupg', fedora: 'gnupg2', opensuse: 'gnupg', nix: 'gnupg' }, unavailableReason: 'GnuPG is a system package and not available via Flatpak or Snap.' },
{ id: 'firejail', name: 'Firejail', description: 'SUID sandbox for restricting app environments', category: 'Security', iconUrl: 'https://linux.fi/w/images/1/1f/Firejail-logo.png', targets: { ubuntu: 'firejail', debian: 'firejail', arch: 'firejail', fedora: 'firejail', opensuse: 'firejail', nix: 'firejail' }, unavailableReason: 'Firejail is a system security tool and not available via Flatpak or Snap.' },
{ id: 'clamav', name: 'ClamAV', description: 'Open-source antivirus engine for malware detection', category: 'Security', iconUrl: 'https://raw.githubusercontent.com/ivangabriele/clamav-desktop/f60bfafdd23bb455f0468abe5f877d2b76eddfba/assets/icons/icon.svg', targets: { ubuntu: 'clamav', debian: 'clamav', arch: 'clamav', fedora: 'clamav', opensuse: 'clamav', nix: 'clamav' }, unavailableReason: 'ClamAV is a system package and not available via Flatpak or Snap.' },
// FILE SHARING
{ id: 'syncthing', name: 'Syncthing', description: 'P2P file sync', category: 'File Sharing', iconUrl: si('syncthing', '#0CB7E4'), targets: { ubuntu: 'syncthing', debian: 'syncthing', arch: 'syncthing', fedora: 'syncthing', opensuse: 'syncthing', nix: 'syncthing', flatpak: 'me.kozec.syncthingtk' }, unavailableReason: 'Syncthing GTK is available on [Flatpak](https://flathub.org/apps/me.kozec.syncthingtk). Not available as Snap.' },
{ id: 'qbittorrent', name: 'qBittorrent', description: 'Torrent client', category: 'File Sharing', iconUrl: si('qbittorrent', '#2F67BA'), targets: { ubuntu: 'qbittorrent', debian: 'qbittorrent', arch: 'qbittorrent', fedora: 'qbittorrent', opensuse: 'qbittorrent', nix: 'qbittorrent', flatpak: 'org.qbittorrent.qBittorrent', snap: 'qbittorrent-desktop-tak' } },
{ id: 'transmission', name: 'Transmission', description: 'Torrent client', category: 'File Sharing', iconUrl: si('transmission', '#D70000'), targets: { ubuntu: 'transmission', debian: 'transmission', arch: 'transmission-gtk', fedora: 'transmission', opensuse: 'transmission', nix: 'transmission_4-gtk', flatpak: 'com.transmissionbt.Transmission' }, unavailableReason: 'Transmission is not available as a Snap package. Use [Flatpak](https://flathub.org/apps/com.transmissionbt.Transmission).' },
{ id: 'localsend', name: 'LocalSend', description: 'Local file sharing', category: 'File Sharing', iconUrl: si('localsend', '#FCA73C'), targets: { arch: 'localsend-bin', nix: 'localsend', flatpak: 'org.localsend.localsend_app' }, unavailableReason: 'Only available via [AUR](https://aur.archlinux.org/packages/localsend-bin) or [Flatpak](https://flathub.org/apps/org.localsend.localsend_app). Download from [localsend.org](https://localsend.org/download).' },
{ id: 'filezilla', name: 'FileZilla', description: 'FTP client', category: 'File Sharing', iconUrl: si('filezilla', '#BF0000'), targets: { ubuntu: 'filezilla', debian: 'filezilla', arch: 'filezilla', fedora: 'filezilla', opensuse: 'filezilla', nix: 'filezilla', flatpak: 'org.filezillaproject.Filezilla' }, unavailableReason: 'FileZilla is not available as a Snap package.' },
{ id: 'nextcloud', name: 'Nextcloud', description: 'Cloud sync client', category: 'File Sharing', iconUrl: si('nextcloud', '#0082C9'), targets: { ubuntu: 'nextcloud-desktop', debian: 'nextcloud-desktop', arch: 'nextcloud-client', fedora: 'nextcloud-client', opensuse: 'nextcloud-desktop', nix: 'nextcloud-client', flatpak: 'com.nextcloud.desktopclient.nextcloud', snap: 'nextcloud-desktop-client' } },
{ id: 'dropbox', name: 'Dropbox', description: 'Cloud storage', category: 'File Sharing', iconUrl: si('dropbox', '#0061FF'), targets: { arch: 'dropbox', nix: 'dropbox', flatpak: 'com.dropbox.Client' }, unavailableReason: 'Only available via [AUR](https://aur.archlinux.org/packages/dropbox) or [Flatpak](https://flathub.org/apps/com.dropbox.Client). See [dropbox.com/install-linux](https://www.dropbox.com/install-linux) for other methods.' },
{ id: 'abdownloadmanager', name: 'AB Download Manager', description: 'Download accelerator', category: 'File Sharing', iconUrl: mdi('download-box', '#4CAF50'), targets: { arch: 'ab-download-manager-bin' }, unavailableReason: 'Only available via [AUR](https://aur.archlinux.org/packages/ab-download-manager-bin). Download from [abdownloadmanager.com](https://abdownloadmanager.com/).' },
{ id: 'fdm', name: 'Free Download Manager', description: 'Download accelerator', category: 'File Sharing', iconUrl: mdi('progress-download', '#3481FE'), targets: { arch: 'freedownloadmanager-bin' }, unavailableReason: 'Not in official repos. See [freedownloadmanager.org](https://www.freedownloadmanager.org/) for other methods.' },
{ id: 'syncthing', name: 'Syncthing', description: 'Decentralized peer-to-peer file synchronization', category: 'File Sharing', iconUrl: si('syncthing', '#0CB7E4'), targets: { ubuntu: 'syncthing', debian: 'syncthing', arch: 'syncthing', fedora: 'syncthing', opensuse: 'syncthing', nix: 'syncthing', flatpak: 'me.kozec.syncthingtk' }, unavailableReason: 'Syncthing GTK is available on [Flatpak](https://flathub.org/apps/me.kozec.syncthingtk). Not available as Snap.' },
{ id: 'qbittorrent', name: 'qBittorrent', description: 'Open-source BitTorrent client, uTorrent alternative', category: 'File Sharing', iconUrl: si('qbittorrent', '#2F67BA'), targets: { ubuntu: 'qbittorrent', debian: 'qbittorrent', arch: 'qbittorrent', fedora: 'qbittorrent', opensuse: 'qbittorrent', nix: 'qbittorrent', flatpak: 'org.qbittorrent.qBittorrent', snap: 'qbittorrent-desktop-tak' } },
{ id: 'transmission', name: 'Transmission', description: 'Lightweight BitTorrent client', category: 'File Sharing', iconUrl: si('transmission', '#D70000'), targets: { ubuntu: 'transmission', debian: 'transmission', arch: 'transmission-gtk', fedora: 'transmission', opensuse: 'transmission', nix: 'transmission_4-gtk', flatpak: 'com.transmissionbt.Transmission' }, unavailableReason: 'Transmission is not available as a Snap package. Use [Flatpak](https://flathub.org/apps/com.transmissionbt.Transmission).' },
{ id: 'localsend', name: 'LocalSend', description: 'Cross-platform AirDrop alternative for local sharing', category: 'File Sharing', iconUrl: si('localsend', '#FCA73C'), targets: { arch: 'localsend-bin', nix: 'localsend', flatpak: 'org.localsend.localsend_app' }, unavailableReason: 'Only available via [AUR](https://aur.archlinux.org/packages/localsend-bin) or [Flatpak](https://flathub.org/apps/org.localsend.localsend_app). Download from [localsend.org](https://localsend.org/download).' },
{ id: 'filezilla', name: 'FileZilla', description: 'Cross-platform FTP, FTPS and SFTP client', category: 'File Sharing', iconUrl: si('filezilla', '#BF0000'), targets: { ubuntu: 'filezilla', debian: 'filezilla', arch: 'filezilla', fedora: 'filezilla', opensuse: 'filezilla', nix: 'filezilla', flatpak: 'org.filezillaproject.Filezilla' }, unavailableReason: 'FileZilla is not available as a Snap package.' },
{ id: 'nextcloud', name: 'Nextcloud', description: 'Self-hosted cloud storage sync client', category: 'File Sharing', iconUrl: si('nextcloud', '#0082C9'), targets: { ubuntu: 'nextcloud-desktop', debian: 'nextcloud-desktop', arch: 'nextcloud-client', fedora: 'nextcloud-client', opensuse: 'nextcloud-desktop', nix: 'nextcloud-client', flatpak: 'com.nextcloud.desktopclient.nextcloud', snap: 'nextcloud-desktop-client' } },
{ id: 'dropbox', name: 'Dropbox', description: 'Popular cloud file storage and sync service', category: 'File Sharing', iconUrl: si('dropbox', '#0061FF'), targets: { arch: 'dropbox', nix: 'dropbox', flatpak: 'com.dropbox.Client' }, unavailableReason: 'Only available via [AUR](https://aur.archlinux.org/packages/dropbox) or [Flatpak](https://flathub.org/apps/com.dropbox.Client). See [dropbox.com/install-linux](https://www.dropbox.com/install-linux) for other methods.' },
{ id: 'abdownloadmanager', name: 'AB Download Manager', description: 'Modern download manager with browser integration', category: 'File Sharing', iconUrl: mdi('download-box', '#4CAF50'), targets: { arch: 'ab-download-manager-bin' }, unavailableReason: 'Only available via [AUR](https://aur.archlinux.org/packages/ab-download-manager-bin). Download from [abdownloadmanager.com](https://abdownloadmanager.com/).' },
{ id: 'fdm', name: 'Free Download Manager', description: 'Feature-rich download manager with BitTorrent', category: 'File Sharing', iconUrl: mdi('progress-download', '#3481FE'), targets: { arch: 'freedownloadmanager-bin' }, unavailableReason: 'Not in official repos. See [freedownloadmanager.org](https://www.freedownloadmanager.org/) for other methods.' },
// SYSTEM
{ id: 'gparted', name: 'GParted', description: 'Partition editor', category: 'System', iconUrl: si('gnome', '#E95420'), targets: { ubuntu: 'gparted', debian: 'gparted', arch: 'gparted', fedora: 'gparted', opensuse: 'gparted', nix: 'gparted' }, unavailableReason: 'GParted requires root access and is not available via Flatpak or Snap.' },
{ id: 'kdeconnect', name: 'KDE Connect', description: 'Phone integration', category: 'System', iconUrl: si('kde', '#338BDB'), targets: { ubuntu: 'kdeconnect', debian: 'kdeconnect', arch: 'kdeconnect', fedora: 'kdeconnectd', opensuse: 'kdeconnect-kde', nix: 'kdeconnect' }, unavailableReason: 'KDE Connect is not available via Flatpak or Snap.' },
{ id: 'timeshift', name: 'Timeshift', description: 'System restore', category: 'System', iconUrl: mdi('backup-restore', '#D9534F'), targets: { ubuntu: 'timeshift', debian: 'timeshift', arch: 'timeshift', fedora: 'timeshift', opensuse: 'timeshift', nix: 'timeshift' }, unavailableReason: 'Requires root filesystem access; not available as Flatpak or Snap.' },
{ id: 'bleachbit', name: 'BleachBit', description: 'System cleaner', category: 'System', iconUrl: 'https://raw.githubusercontent.com/chocolatey-community/chocolatey-packages/782707302851e7935c4a5a3e48e27140c774fa78/icons/bleachbit.svg', targets: { ubuntu: 'bleachbit', debian: 'bleachbit', arch: 'bleachbit', fedora: 'bleachbit', opensuse: 'bleachbit', nix: 'bleachbit', flatpak: 'org.bleachbit.BleachBit' }, unavailableReason: 'BleachBit is not available as a Snap package.' },
{ id: 'flameshot', name: 'Flameshot', description: 'Screenshot tool', category: 'System', iconUrl: 'https://raw.githubusercontent.com/flameshot-org/flameshot/master/data/img/app/flameshot.svg', targets: { ubuntu: 'flameshot', debian: 'flameshot', arch: 'flameshot', fedora: 'flameshot', opensuse: 'flameshot', nix: 'flameshot', flatpak: 'org.flameshot.Flameshot' }, unavailableReason: 'Flameshot is not available as a Snap package.' },
{ id: 'gnometweaks', name: 'GNOME Tweaks', description: 'GNOME settings', category: 'System', iconUrl: si('gnome', '#4A86CF'), targets: { ubuntu: 'gnome-tweaks', debian: 'gnome-tweaks', arch: 'gnome-tweaks', fedora: 'gnome-tweaks', opensuse: 'gnome-tweaks', nix: 'gnome-tweaks' }, unavailableReason: 'GNOME Tweaks is not available via Flatpak or Snap.' },
{ id: 'dconf', name: 'dconf Editor', description: 'GNOME config', category: 'System', iconUrl: si('gnome', '#4A86CF'), targets: { ubuntu: 'dconf-editor', debian: 'dconf-editor', arch: 'dconf-editor', fedora: 'dconf-editor', opensuse: 'dconf-editor', nix: 'dconf-editor' }, unavailableReason: 'dconf Editor is not available via Flatpak or Snap.' },
{ id: 'borgbackup', name: 'BorgBackup', description: 'Dedup backup', category: 'System', iconUrl: si('borgbackup', '#00B041'), targets: { ubuntu: 'borgbackup', debian: 'borgbackup', arch: 'borg', fedora: 'borgbackup', opensuse: 'borgbackup', nix: 'borgbackup' }, unavailableReason: 'BorgBackup is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'restic', name: 'Restic', description: 'Fast backup', category: 'System', iconUrl: mdi('cloud-sync', '#00B4AB'), targets: { ubuntu: 'restic', debian: 'restic', arch: 'restic', fedora: 'restic', opensuse: 'restic', nix: 'restic' }, unavailableReason: 'Restic is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'flatpaksupport', name: 'Flatpak', description: 'App sandboxing', category: 'System', iconUrl: si('flatpak', '#4A90D9'), targets: { ubuntu: 'flatpak', debian: 'flatpak', arch: 'flatpak', fedora: 'flatpak', opensuse: 'flatpak', nix: 'flatpak' }, unavailableReason: 'Flatpak is a system package manager and not available via Flatpak or Snap.' },
{ id: 'filelight', name: 'Filelight', description: 'Disk usage visualizer', category: 'System', iconUrl: si('kde', '#338BDB'), targets: { ubuntu: 'filelight', debian: 'filelight', arch: 'filelight', fedora: 'filelight', opensuse: 'filelight', nix: 'filelight', flatpak: 'org.kde.filelight', snap: 'filelight' } },
{ id: 'conky', name: 'Conky', description: 'System monitor', category: 'System', iconUrl: mdi('monitor-dashboard', '#FFFFFF'), targets: { ubuntu: 'conky-all', debian: 'conky-all', arch: 'conky', fedora: 'conky', opensuse: 'conky', nix: 'conky' }, unavailableReason: 'Conky is a system tool and not available via Flatpak or Snap.' },
{ id: 'gparted', name: 'GParted', description: 'GNOME partition editor for disk management', category: 'System', iconUrl: si('gnome', '#E95420'), targets: { ubuntu: 'gparted', debian: 'gparted', arch: 'gparted', fedora: 'gparted', opensuse: 'gparted', nix: 'gparted' }, unavailableReason: 'GParted requires root access and is not available via Flatpak or Snap.' },
{ id: 'kdeconnect', name: 'KDE Connect', description: 'Connect your phone to your Linux desktop', category: 'System', iconUrl: si('kde', '#338BDB'), targets: { ubuntu: 'kdeconnect', debian: 'kdeconnect', arch: 'kdeconnect', fedora: 'kdeconnectd', opensuse: 'kdeconnect-kde', nix: 'kdeconnect' }, unavailableReason: 'KDE Connect is not available via Flatpak or Snap.' },
{ id: 'timeshift', name: 'Timeshift', description: 'System snapshot and restore tool like Time Machine', category: 'System', iconUrl: mdi('backup-restore', '#D9534F'), targets: { ubuntu: 'timeshift', debian: 'timeshift', arch: 'timeshift', fedora: 'timeshift', opensuse: 'timeshift', nix: 'timeshift' }, unavailableReason: 'Requires root filesystem access; not available as Flatpak or Snap.' },
{ id: 'bleachbit', name: 'BleachBit', description: 'Free disk space and maintain privacy', category: 'System', iconUrl: 'https://raw.githubusercontent.com/chocolatey-community/chocolatey-packages/782707302851e7935c4a5a3e48e27140c774fa78/icons/bleachbit.svg', targets: { ubuntu: 'bleachbit', debian: 'bleachbit', arch: 'bleachbit', fedora: 'bleachbit', opensuse: 'bleachbit', nix: 'bleachbit', flatpak: 'org.bleachbit.BleachBit' }, unavailableReason: 'BleachBit is not available as a Snap package.' },
{ id: 'flameshot', name: 'Flameshot', description: 'Powerful screenshot tool with annotation', category: 'System', iconUrl: 'https://raw.githubusercontent.com/flameshot-org/flameshot/master/data/img/app/flameshot.svg', targets: { ubuntu: 'flameshot', debian: 'flameshot', arch: 'flameshot', fedora: 'flameshot', opensuse: 'flameshot', nix: 'flameshot', flatpak: 'org.flameshot.Flameshot' }, unavailableReason: 'Flameshot is not available as a Snap package.' },
{ id: 'gnometweaks', name: 'GNOME Tweaks', description: 'Advanced settings and customization for GNOME', category: 'System', iconUrl: si('gnome', '#4A86CF'), targets: { ubuntu: 'gnome-tweaks', debian: 'gnome-tweaks', arch: 'gnome-tweaks', fedora: 'gnome-tweaks', opensuse: 'gnome-tweaks', nix: 'gnome-tweaks' }, unavailableReason: 'GNOME Tweaks is not available via Flatpak or Snap.' },
{ id: 'dconf', name: 'dconf Editor', description: 'Low-level configuration editor for GNOME', category: 'System', iconUrl: si('gnome', '#4A86CF'), targets: { ubuntu: 'dconf-editor', debian: 'dconf-editor', arch: 'dconf-editor', fedora: 'dconf-editor', opensuse: 'dconf-editor', nix: 'dconf-editor' }, unavailableReason: 'dconf Editor is not available via Flatpak or Snap.' },
{ id: 'borgbackup', name: 'BorgBackup', description: 'Deduplicating backup program with compression', category: 'System', iconUrl: si('borgbackup', '#00B041'), targets: { ubuntu: 'borgbackup', debian: 'borgbackup', arch: 'borg', fedora: 'borgbackup', opensuse: 'borgbackup', nix: 'borgbackup' }, unavailableReason: 'BorgBackup is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'restic', name: 'Restic', description: 'Fast, secure, efficient backup program', category: 'System', iconUrl: mdi('cloud-sync', '#00B4AB'), targets: { ubuntu: 'restic', debian: 'restic', arch: 'restic', fedora: 'restic', opensuse: 'restic', nix: 'restic' }, unavailableReason: 'Restic is a CLI tool and not available via Flatpak or Snap.' },
{ id: 'flatpaksupport', name: 'Flatpak', description: 'Universal app packaging and sandboxing framework', category: 'System', iconUrl: si('flatpak', '#4A90D9'), targets: { ubuntu: 'flatpak', debian: 'flatpak', arch: 'flatpak', fedora: 'flatpak', opensuse: 'flatpak', nix: 'flatpak' }, unavailableReason: 'Flatpak is a system package manager and not available via Flatpak or Snap.' },
{ id: 'filelight', name: 'Filelight', description: 'Interactive disk usage visualization by KDE', category: 'System', iconUrl: si('kde', '#338BDB'), targets: { ubuntu: 'filelight', debian: 'filelight', arch: 'filelight', fedora: 'filelight', opensuse: 'filelight', nix: 'filelight', flatpak: 'org.kde.filelight', snap: 'filelight' } },
{ id: 'conky', name: 'Conky', description: 'Highly configurable desktop system monitor', category: 'System', iconUrl: mdi('monitor-dashboard', '#FFFFFF'), targets: { ubuntu: 'conky-all', debian: 'conky-all', arch: 'conky', fedora: 'conky', opensuse: 'conky', nix: 'conky' }, unavailableReason: 'Conky is a system tool and not available via Flatpak or Snap.' },
];
// Categories in display order