Compare commits
	
		
			4 Commits
		
	
	
		
			05263d0d3a
			...
			test-tag
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| af7b778561 | |||
| 2849ed3bb2 | |||
| f1c1b0c6c6 | |||
| ffdaf06d55 | 
@@ -1,26 +1,45 @@
 | 
			
		||||
name: Check scripts syntax
 | 
			
		||||
on: [push]
 | 
			
		||||
name: Build and Release
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    branches:
 | 
			
		||||
      - main
 | 
			
		||||
  tag:
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  build:
 | 
			
		||||
    if: gitea.event_name == 'push'
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out repository code
 | 
			
		||||
        uses: actions/checkout@v4
 | 
			
		||||
      - name: List files in the repository
 | 
			
		||||
        run: |
 | 
			
		||||
          ls ${{ gitea.workspace }}
 | 
			
		||||
      - name: Install JS dependencies
 | 
			
		||||
        run: |
 | 
			
		||||
          npm install
 | 
			
		||||
      - name: Run scripts
 | 
			
		||||
        run: |
 | 
			
		||||
          bash download-icons.sh
 | 
			
		||||
        run: npm install
 | 
			
		||||
      - name: Run build
 | 
			
		||||
        run: |
 | 
			
		||||
          npm run build
 | 
			
		||||
        run: npm run build
 | 
			
		||||
 | 
			
		||||
  release:
 | 
			
		||||
    if: gitea.event_name == 'tag'
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out repository code
 | 
			
		||||
        uses: actions/checkout@v4
 | 
			
		||||
      - name: Setup zip
 | 
			
		||||
        run: sudo apt-get install zip -y
 | 
			
		||||
      - name: Install JS dependencies
 | 
			
		||||
        run: npm install
 | 
			
		||||
      - name: Run build
 | 
			
		||||
        run: npm run build
 | 
			
		||||
      - name: Prepare release
 | 
			
		||||
        run: | 
 | 
			
		||||
          bash scripts/prepare_release.sh
 | 
			
		||||
          mv dist vision-start/
 | 
			
		||||
          mv manifest.json vision-start/
 | 
			
		||||
      - name: Create zip archive
 | 
			
		||||
        run: zip -r vision-start-${{ gitea.ref_name }}.zip vision-start
 | 
			
		||||
      - name: Release zip
 | 
			
		||||
        uses: akkuman/gitea-release-action@v1
 | 
			
		||||
        with:
 | 
			
		||||
          files: |-
 | 
			
		||||
            dist/**
 | 
			
		||||
          name: ${{ gitea.ref_name }}
 | 
			
		||||
          tag_name: ${{ gitea.ref_name }}
 | 
			
		||||
          files: vision-start-${{ gitea.ref_name }}.zip
 | 
			
		||||
							
								
								
									
										30
									
								
								App.tsx
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								App.tsx
									
									
									
									
									
								
							@@ -94,7 +94,8 @@ const App: React.FC = () => {
 | 
			
		||||
    const updateWallpaper = () => {
 | 
			
		||||
      const availableWallpapers = allWallpapers.filter(w => config.backgroundUrls.includes(w.url || w.base64));
 | 
			
		||||
      if (availableWallpapers.length > 0) {
 | 
			
		||||
        const currentIndex = availableWallpapers.findIndex(w => (w.url || w.base64) === wallpaperState.current);
 | 
			
		||||
        const currentWallpaperFromState = allWallpapers.find(w => w.name === wallpaperState.current);
 | 
			
		||||
        const currentIndex = currentWallpaperFromState ? availableWallpapers.findIndex(w => w.name === currentWallpaperFromState.name) : -1;
 | 
			
		||||
        const nextIndex = (currentIndex + 1) % availableWallpapers.length;
 | 
			
		||||
        const newWallpaper = availableWallpapers[nextIndex];
 | 
			
		||||
        const newWallpaperUrl = newWallpaper.url || newWallpaper.base64;
 | 
			
		||||
@@ -102,24 +103,20 @@ const App: React.FC = () => {
 | 
			
		||||
        localStorage.setItem('wallpaperState', JSON.stringify({ current: newWallpaper.name, lastChanged: new Date().toISOString() }));
 | 
			
		||||
      } else {
 | 
			
		||||
        setCurrentWallpaper('');
 | 
			
		||||
        localStorage.removeItem('wallpaperState');
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if (Date.now() - lastChanged > frequency) {
 | 
			
		||||
    const currentWallpaperDetails = allWallpapers.find(w => w.name === wallpaperState.current);
 | 
			
		||||
    const isCurrentWallpaperValid = currentWallpaperDetails && config.backgroundUrls.includes(currentWallpaperDetails.url || currentWallpaperDetails.base64 || '');
 | 
			
		||||
 | 
			
		||||
    if (!isCurrentWallpaperValid || Date.now() - lastChanged > frequency) {
 | 
			
		||||
      updateWallpaper();
 | 
			
		||||
    } else if (currentWallpaperDetails) {
 | 
			
		||||
      setCurrentWallpaper(currentWallpaperDetails.url || currentWallpaperDetails.base64 || '');
 | 
			
		||||
    } else {
 | 
			
		||||
      const currentWallpaperName = wallpaperState.current;
 | 
			
		||||
      const wallpaper = allWallpapers.find(w => w.name === currentWallpaperName);
 | 
			
		||||
      if (wallpaper) {
 | 
			
		||||
        setCurrentWallpaper(wallpaper.url || wallpaper.base64 || '');
 | 
			
		||||
      } else {
 | 
			
		||||
        const firstWallpaperUrl = config.backgroundUrls[0] || '';
 | 
			
		||||
        const firstWallpaper = allWallpapers.find(w => (w.url || w.base64) === firstWallpaperUrl);
 | 
			
		||||
        setCurrentWallpaper(firstWallpaperUrl);
 | 
			
		||||
        if (firstWallpaper) {
 | 
			
		||||
          localStorage.setItem('wallpaperState', JSON.stringify({ current: firstWallpaper.name, lastChanged: new Date().toISOString() }));
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      // Fallback for when there's no valid wallpaper state
 | 
			
		||||
      updateWallpaper();
 | 
			
		||||
    }
 | 
			
		||||
  }, [config.backgroundUrls, config.wallpaperFrequency, allWallpapers]);
 | 
			
		||||
 | 
			
		||||
@@ -133,6 +130,10 @@ const App: React.FC = () => {
 | 
			
		||||
    setIsConfigModalOpen(false);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleWallpaperChange = (newConfig: Partial<Config>) => {
 | 
			
		||||
    setConfig(prev => ({ ...prev, ...newConfig }));
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleSaveWebsite = (website: Partial<Website>) => {
 | 
			
		||||
    if (editingWebsite) {
 | 
			
		||||
      const newCategories = categories.map(category => ({
 | 
			
		||||
@@ -341,6 +342,7 @@ const App: React.FC = () => {
 | 
			
		||||
          currentConfig={config}
 | 
			
		||||
          onClose={() => setIsConfigModalOpen(false)} 
 | 
			
		||||
          onSave={handleSaveConfig} 
 | 
			
		||||
          onWallpaperChange={handleWallpaperChange}
 | 
			
		||||
        />
 | 
			
		||||
      )}
 | 
			
		||||
    </main>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										90
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										90
									
								
								README.md
									
									
									
									
									
								
							@@ -1,22 +1,84 @@
 | 
			
		||||
# Vision Start
 | 
			
		||||
#### Small startpage 
 | 
			
		||||
#### A glassmorphism-looking like, modern and customizable startpage built with React.
 | 
			
		||||
 | 
			
		||||
## Predefined themes
 | 
			
		||||
## Screenshots
 | 
			
		||||
 | 
			
		||||
1. Abstract
 | 
			
		||||
2. Aurora (Vista vibes)
 | 
			
		||||
3. Mountain
 | 
			
		||||

 | 
			
		||||

 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
## Run Locally
 | 
			
		||||
## Backgrounds
 | 
			
		||||
 | 
			
		||||
**Prerequisites:**  Node.js
 | 
			
		||||
It comes with a selection of some nice pre-defined backgrounds. You can also upload up to one image to it.
 | 
			
		||||
 | 
			
		||||
1. Install dependencies:
 | 
			
		||||
   `npm install`
 | 
			
		||||
2. Run the app:
 | 
			
		||||
   `npm run dev`
 | 
			
		||||
* **Abstract**
 | 
			
		||||
* **Abstract Red**
 | 
			
		||||
* **Beach**
 | 
			
		||||
* **Dark**
 | 
			
		||||
* **Mountain**
 | 
			
		||||
* **Waves**
 | 
			
		||||
 | 
			
		||||
## to-do
 | 
			
		||||
* [] Multiple wallpapers
 | 
			
		||||
## Features
 | 
			
		||||
 | 
			
		||||
* **Customizable Website Tiles:** Add, edit, and organize your favorite websites for quick access.
 | 
			
		||||
* **Elegant Clock:** A clock because all startpages have one.
 | 
			
		||||
* **Server Status Widgets:** Monitor the status of services directly from the startpage.
 | 
			
		||||
* **Glassmorphism UI:** A modern and stylish interface with a frosted glass effect.
 | 
			
		||||
* **Icon Library:** It uses the [Dashboard Icon library](https://dashboardicons.com/) for a better look and feel. It also supports auto-fetch for some websites.
 | 
			
		||||
* **Future**: a long to do list :(
 | 
			
		||||
 | 
			
		||||
## Running Locally
 | 
			
		||||
 | 
			
		||||
**Prerequisites:** Node.js
 | 
			
		||||
 | 
			
		||||
1.  Clone the repository:
 | 
			
		||||
```bash
 | 
			
		||||
git clone https://gitea.com/ivan/vision-start.git
 | 
			
		||||
cd vision-start
 | 
			
		||||
```
 | 
			
		||||
2.  Install dependencies:
 | 
			
		||||
```bash
 | 
			
		||||
npm install
 | 
			
		||||
```
 | 
			
		||||
3.  Run the development server:
 | 
			
		||||
```bash
 | 
			
		||||
npm run dev
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## To-do
 | 
			
		||||
 | 
			
		||||
* [x] Multiple Wallpapers
 | 
			
		||||
* [x] Remake icons
 | 
			
		||||
* [] Increase offline compatibility
 | 
			
		||||
* [] Increase offline compatibility (might not be possible)
 | 
			
		||||
  * Use chrome.storage.local for user wallpapers -- this one is
 | 
			
		||||
  * Use chrome.storage.local for some logos -- a bit hard
 | 
			
		||||
    * Some logos have CORS enabled, we can add `"<all_urls>"` to the manifest.json file and cache them on storage local
 | 
			
		||||
* Dynamic Weather Widget
 | 
			
		||||
  * A box with information about the current weather, with manual entry on the location
 | 
			
		||||
  * Display current temperature, weather condition (e.g., "Sunny," "Cloudy"), and a corresponding icon
 | 
			
		||||
  * Optionally, show a 3-day forecast when clicked or hovered
 | 
			
		||||
* Search Bar Widget
 | 
			
		||||
  * Positioned to the right or left side of the clock, display a nice search bar
 | 
			
		||||
  * Behaviour:
 | 
			
		||||
    * When not in focus, it could be highly transparent with just a faint border and a search icon.
 | 
			
		||||
    * When clicked, it would smoothly expand and become slightly more opaque, with a soft glow around the border (similar to the existing ones)
 | 
			
		||||
  * Config to allow changing the default search engine
 | 
			
		||||
* Draggable & Resizable Grid System
 | 
			
		||||
  * Allow users to drag and drop all widgets (Clock, Website Tiles, Weather, Title, etc.) into any position on a grid
 | 
			
		||||
* Notes / Scratchpad Widget
 | 
			
		||||
  * A simple text area that saves its content to local storage automatically.
 | 
			
		||||
  * Maybe some extra formatting (bold, italic, increase font size, etc).
 | 
			
		||||
* Theme-ing
 | 
			
		||||
  * A Light/Dark Mode toggle
 | 
			
		||||
  * Custom Accent Colors
 | 
			
		||||
    * Selection of 6-8 accent colors that are guaranteed to look good with both Light and Dark themes
 | 
			
		||||
    * Define CSS variables for the accent color
 | 
			
		||||
  * Dynamic Wallpaper-Based Theming
 | 
			
		||||
    * Automatically adapt the UI's accent color to match the current wallpaper
 | 
			
		||||
  * Minimal feel toggle
 | 
			
		||||
    * Disable title & subtitle and search widget
 | 
			
		||||
    * Tiles become small stylish lines
 | 
			
		||||
 | 
			
		||||
From a technical side:
 | 
			
		||||
* Refactor everything :(
 | 
			
		||||
* Add small nginx demo (with docker)
 | 
			
		||||
 
 | 
			
		||||
@@ -10,9 +10,10 @@ interface ConfigurationModalProps {
 | 
			
		||||
  onClose: () => void;
 | 
			
		||||
  onSave: (config: any) => void;
 | 
			
		||||
  currentConfig: any;
 | 
			
		||||
  onWallpaperChange: (newConfig: Partial<any>) => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave, currentConfig }) => {
 | 
			
		||||
const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave, currentConfig, onWallpaperChange }) => {
 | 
			
		||||
  const [config, setConfig] = useState({
 | 
			
		||||
    ...currentConfig,
 | 
			
		||||
    titleSize: currentConfig.titleSize || 'medium',
 | 
			
		||||
@@ -47,6 +48,7 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
 | 
			
		||||
  const [userWallpapers, setUserWallpapers] = useState<Wallpaper[]>([]);
 | 
			
		||||
  const menuRef = useRef<HTMLDivElement>(null);
 | 
			
		||||
  const fileInputRef = useRef<HTMLInputElement>(null);
 | 
			
		||||
  const isSaving = useRef(false);
 | 
			
		||||
  const [isVisible, setIsVisible] = useState(false);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
@@ -64,6 +66,14 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
 | 
			
		||||
    return () => clearTimeout(timer);
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    return () => {
 | 
			
		||||
      if (!isSaving.current) {
 | 
			
		||||
        onWallpaperChange({ backgroundUrls: currentConfig.backgroundUrls });
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const handleClose = () => {
 | 
			
		||||
    setIsVisible(false);
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
@@ -90,6 +100,10 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    onWallpaperChange({ backgroundUrls: config.backgroundUrls });
 | 
			
		||||
  }, [config.backgroundUrls]);
 | 
			
		||||
 | 
			
		||||
  const handleClockToggleChange = (checked: boolean) => {
 | 
			
		||||
    setConfig({ ...config, clock: { ...config.clock, enabled: checked } });
 | 
			
		||||
  };
 | 
			
		||||
@@ -201,7 +215,10 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
 | 
			
		||||
    localStorage.setItem('userWallpapers', JSON.stringify(updatedUserWallpapers));
 | 
			
		||||
 | 
			
		||||
    const newBackgroundUrls = config.backgroundUrls.filter((url: string) => url !== wallpaperIdentifier);
 | 
			
		||||
    setConfig({ ...config, backgroundUrls: newBackgroundUrls });
 | 
			
		||||
    
 | 
			
		||||
    const newConfig = { ...config, backgroundUrls: newBackgroundUrls };
 | 
			
		||||
    setConfig(newConfig);
 | 
			
		||||
    onWallpaperChange({ backgroundUrls: newBackgroundUrls });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const allWallpapers = [...baseWallpapers, ...userWallpapers];
 | 
			
		||||
@@ -629,7 +646,7 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
 | 
			
		||||
        </div>
 | 
			
		||||
        <div className="p-8 border-t border-white/10">
 | 
			
		||||
            <div className="flex justify-end gap-4">
 | 
			
		||||
                <button onClick={() => onSave(config)} className="bg-green-500 hover:bg-green-400 text-white font-bold py-2 px-6 rounded-lg">
 | 
			
		||||
                <button onClick={() => { isSaving.current = true; onSave(config); }} className="bg-green-500 hover:bg-green-400 text-white font-bold py-2 px-6 rounded-lg">
 | 
			
		||||
                    Save & Close
 | 
			
		||||
                </button>
 | 
			
		||||
                <button onClick={handleClose} className="bg-gray-600 hover:bg-gray-500 text-white font-bold py-2 px-6 rounded-lg">
 | 
			
		||||
 
 | 
			
		||||
@@ -23,21 +23,37 @@ const getTileSizeClass = (size: string | undefined) => {
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getIconSize = (size: string | undefined) => {
 | 
			
		||||
 | 
			
		||||
// Returns normal icon size in px
 | 
			
		||||
const getIconPixelSize = (size: string | undefined): number => {
 | 
			
		||||
  switch (size) {
 | 
			
		||||
    case 'small':
 | 
			
		||||
      return 8;
 | 
			
		||||
      return 32;
 | 
			
		||||
    case 'medium':
 | 
			
		||||
      return 10;
 | 
			
		||||
      return 40;
 | 
			
		||||
    case 'large':
 | 
			
		||||
      return 12;
 | 
			
		||||
      return 48;
 | 
			
		||||
    default:
 | 
			
		||||
      return 10;
 | 
			
		||||
      return 40;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Returns loading icon size in px
 | 
			
		||||
const getIconLoadingPixelSize = (size: string | undefined): number => {
 | 
			
		||||
  switch (size) {
 | 
			
		||||
    case 'small':
 | 
			
		||||
      return 24;
 | 
			
		||||
    case 'medium':
 | 
			
		||||
      return 32;
 | 
			
		||||
    case 'large':
 | 
			
		||||
      return 40;
 | 
			
		||||
    default:
 | 
			
		||||
      return 32;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const WebsiteTile: React.FC<WebsiteTileProps> = ({ website, isEditing, onEdit, onMove, tileSize }) => {
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  const [isLoading, setIsLoading] = useState(false);
 | 
			
		||||
 | 
			
		||||
  const handleClick = (e: React.MouseEvent) => {
 | 
			
		||||
@@ -46,7 +62,7 @@ const WebsiteTile: React.FC<WebsiteTileProps> = ({ website, isEditing, onEdit, o
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    setIsLoading(true);
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Simulate loading time (dev purpose)
 | 
			
		||||
    // e.preventDefault();
 | 
			
		||||
    // setTimeout(() => {
 | 
			
		||||
@@ -54,8 +70,8 @@ const WebsiteTile: React.FC<WebsiteTileProps> = ({ website, isEditing, onEdit, o
 | 
			
		||||
    // }, 3500); // Small delay to show spinner before navigation
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const iconSizeClass = `w-${getIconSize(tileSize)} h-${getIconSize(tileSize)}`;
 | 
			
		||||
  const iconSizeLoadingClass = `w-${getIconSize(tileSize) - 4} h-${getIconSize(tileSize) - 4}`;
 | 
			
		||||
  const iconSizeClass = `w-[${getIconPixelSize(tileSize)}px] h-[${getIconPixelSize(tileSize)}px]`;
 | 
			
		||||
  const iconSizeLoadingClass = `w-[${getIconLoadingPixelSize(tileSize)}px] h-[${getIconLoadingPixelSize(tileSize)}px]`;
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={`relative ${getTileSizeClass(tileSize)} transition-all duration-300 ease-in-out`}>
 | 
			
		||||
@@ -86,14 +102,14 @@ const WebsiteTile: React.FC<WebsiteTileProps> = ({ website, isEditing, onEdit, o
 | 
			
		||||
      {isEditing && (
 | 
			
		||||
        <div className="absolute bottom-2 left-0 right-0 flex justify-center gap-2">
 | 
			
		||||
          <button onClick={() => onMove(website, 'left')} className="text-white/50 hover:text-white transition-colors"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-arrow-left" viewBox="0 0 16 16">
 | 
			
		||||
  <path fill-rule="evenodd" d="M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8z"/>
 | 
			
		||||
</svg></button>
 | 
			
		||||
            <path fill-rule="evenodd" d="M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8z" />
 | 
			
		||||
          </svg></button>
 | 
			
		||||
          <button onClick={() => onEdit(website)} className="text-white/50 hover:text-white transition-colors"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-pencil" viewBox="0 0 16 16">
 | 
			
		||||
  <path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/>
 | 
			
		||||
</svg></button>
 | 
			
		||||
            <path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z" />
 | 
			
		||||
          </svg></button>
 | 
			
		||||
          <button onClick={() => onMove(website, 'right')} className="text-white/50 hover:text-white transition-colors"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-arrow-right" viewBox="0 0 16 16">
 | 
			
		||||
  <path fill-rule="evenodd" d="M1 8a.5.5 0 0 1 .5-.5h11.793l-3.147-3.146a.5.5 0 0 1 .708-.708l4 4a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708L13.293 8.5H1.5A.5.5 0 0 1 1 8z"/>
 | 
			
		||||
</svg></button>
 | 
			
		||||
            <path fill-rule="evenodd" d="M1 8a.5.5 0 0 1 .5-.5h11.793l-3.147-3.146a.5.5 0 0 1 .708-.708l4 4a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708L13.293 8.5H1.5A.5.5 0 0 1 1 8z" />
 | 
			
		||||
          </svg></button>
 | 
			
		||||
        </div>
 | 
			
		||||
      )}
 | 
			
		||||
    </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -41,7 +41,7 @@ const CategoryGroup: React.FC<CategoryGroupProps> = ({
 | 
			
		||||
            className={`ml-2 text-white/50 hover:text-white transition-all duration-300 ease-in-out transform ${isEditing ? 'scale-100 opacity-100' : 'scale-0 opacity-0'}`}
 | 
			
		||||
          >
 | 
			
		||||
            <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" className="bi bi-pencil" viewBox="0 0 16 16">
 | 
			
		||||
              <path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207zM1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/>
 | 
			
		||||
              <path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z" />
 | 
			
		||||
            </svg>
 | 
			
		||||
          </button>
 | 
			
		||||
        )}
 | 
			
		||||
@@ -63,8 +63,8 @@ const CategoryGroup: React.FC<CategoryGroupProps> = ({
 | 
			
		||||
            className={`text-white/50 hover:text-white transition-all duration-300 ease-in-out transform ${isEditing ? 'scale-100 opacity-100' : 'scale-0 opacity-0'}`}
 | 
			
		||||
          >
 | 
			
		||||
            <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="currentColor" className="bi bi-plus-circle" viewBox="0 0 16 16">
 | 
			
		||||
              <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
 | 
			
		||||
              <path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"/>
 | 
			
		||||
              <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
 | 
			
		||||
              <path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z" />
 | 
			
		||||
            </svg>
 | 
			
		||||
          </button>
 | 
			
		||||
        )}
 | 
			
		||||
 
 | 
			
		||||
@@ -11,9 +11,10 @@ const ConfigurationButton: React.FC<ConfigurationButtonProps> = ({ onClick }) =>
 | 
			
		||||
        onClick={onClick}
 | 
			
		||||
        className="bg-black/25 backdrop-blur-md border border-white/10 rounded-xl p-3 text-white flex items-center gap-2 hover:bg-white/25 transition-colors"
 | 
			
		||||
      >
 | 
			
		||||
        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-gear-wide" viewBox="0 0 16 16">
 | 
			
		||||
          <path d="M8.932.727c-.243-.97-1.62-.97-1.864 0l-.071.286a.96.96 0 0 1-1.622.434l-.205-.211c-.695-.719-1.888-.03-1.613.931l.08.284a.96.96 0 0 1-1.186 1.187l-.284-.081c-.96-.275-1.65.918-.931 1.613l.211.205a.96.96 0 0 1-.434 1.622l-.286.071c-.97.243-.97 1.62 0 1.864l.286.071a.96.96 0 0 1 .434 1.622l-.211.205c-.719.695-.03 1.888.931 1.613l.284-.08a.96.96 0 0 1 1.187 1.187l-.081.283c-.275.96.918 1.65 1.613.931l.205-.211a.96.96 0 0 1 1.622.434l.071.286c.243.97 1.62.97 1.864 0l.071-.286a.96.96 0 0 1 1.622-.434l.205.211c.695.719 1.888.03 1.613-.931l-.08-.284a.96.96 0 0 1 1.187-1.187l.283.081c.96.275 1.65-.918-.931-1.613l-.211-.205a.96.96 0 0 1 .434-1.622l.286-.071c.97-.243.97-1.62 0-1.864l-.286-.071a.96.96 0 0 1-.434-1.622l.211-.205c.719-.695.03-1.888-.931-1.613l-.284.08a.96.96 0 0 1-1.187-1.186l.081-.284c.275-.96-.918-1.65-1.613-.931l-.205.211a.96.96 0 0 1-1.622-.434zM8 12.997a4.998 4.998 0 1 1 0-9.995 4.998 4.998 0 0 1 0 9.996z"/>
 | 
			
		||||
        </svg>
 | 
			
		||||
          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor">
 | 
			
		||||
            <circle cx="12" cy="12" r="3" stroke="currentColor" strokeWidth="2" fill="none"/>
 | 
			
		||||
            <path stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09c.7 0 1.31-.4 1.51-1a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06c.51.51 1.31.61 1.82.33.51-.28 1-.81 1-1.51V3a2 2 0 1 1 4 0v.09c0 .7.49 1.23 1 1.51.51.28 1.31.18 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82c.2.6.81 1 1.51 1H21a2 2 0 1 1 0 4h-.09c-.7 0-1.31.4-1.51 1z"/>
 | 
			
		||||
          </svg>
 | 
			
		||||
      </button>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
 
 | 
			
		||||
@@ -14,12 +14,16 @@ export const baseWallpapers: Wallpaper[] = [
 | 
			
		||||
    name: 'Beach',
 | 
			
		||||
    url: 'https://wallpapershome.com/images/pages/pic_h/615.jpg'
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: 'Dark',
 | 
			
		||||
    url: 'https://i.imgur.com/qHlRO0s.jpeg'
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: 'Mountain',
 | 
			
		||||
    url: 'https://i.imgur.com/yHfOZUd.jpeg'
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: 'Waves',
 | 
			
		||||
    url: '/waves.jpg',
 | 
			
		||||
    url: 'https://i.imgur.com/E8uxZ7R.png',
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								screenshots/configuration-abstract-red.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								screenshots/configuration-abstract-red.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 744 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								screenshots/dark-page.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								screenshots/dark-page.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 1.3 MiB  | 
							
								
								
									
										
											BIN
										
									
								
								screenshots/editing-abstract-red.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								screenshots/editing-abstract-red.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 3.5 MiB  | 
		Reference in New Issue
	
	Block a user