Compare commits
2 Commits
d30fd8d30b
...
12ed7e1b9f
Author | SHA1 | Date | |
---|---|---|---|
12ed7e1b9f | |||
e6bc95b7e6 |
32
App.tsx
32
App.tsx
@@ -15,13 +15,14 @@ import { baseWallpapers } from './components/utils/baseWallpapers';
|
|||||||
const defaultConfig = {
|
const defaultConfig = {
|
||||||
title: 'Vision Start',
|
title: 'Vision Start',
|
||||||
subtitle: 'Your personal portal to the web.',
|
subtitle: 'Your personal portal to the web.',
|
||||||
backgroundUrl: '/waves.jpg',
|
backgroundUrl: 'https://i.imgur.com/C6ynAtX.jpeg',
|
||||||
wallpaperBlur: 0,
|
wallpaperBlur: 0,
|
||||||
wallpaperBrightness: 100,
|
wallpaperBrightness: 100,
|
||||||
wallpaperOpacity: 100,
|
wallpaperOpacity: 100,
|
||||||
titleSize: 'medium',
|
titleSize: 'medium',
|
||||||
subtitleSize: 'medium',
|
subtitleSize: 'medium',
|
||||||
alignment: 'middle',
|
alignment: 'middle',
|
||||||
|
horizontalAlignment: 'middle',
|
||||||
clock: {
|
clock: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
size: 'medium',
|
size: 'medium',
|
||||||
@@ -235,16 +236,16 @@ const App: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getTileSizeClass = (size: string) => {
|
const getHorizontalAlignmentClass = (alignment: string) => {
|
||||||
switch (size) {
|
switch (alignment) {
|
||||||
case 'small':
|
case 'left':
|
||||||
return 'w-28 h-28';
|
return 'justify-start';
|
||||||
case 'medium':
|
case 'middle':
|
||||||
return 'w-32 h-32';
|
return 'justify-center';
|
||||||
case 'large':
|
case 'right':
|
||||||
return 'w-36 h-36';
|
return 'justify-end';
|
||||||
default:
|
default:
|
||||||
return 'w-32 h-32';
|
return 'justify-center';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -291,8 +292,7 @@ const App: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className={`flex flex-col ${config.alignment === 'bottom' ? 'mt-auto' : ''} items-center`}>
|
<div className={`flex flex-col ${config.alignment === 'bottom' ? 'mt-auto' : ''} items-center`}>
|
||||||
{config.title || config.subtitle &&
|
{(config.title || config.subtitle) && (
|
||||||
(
|
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<h1
|
<h1
|
||||||
className={`${getTitleSizeClass(config.titleSize)} font-extrabold text-white tracking-tighter mb-3 mt-4`}
|
className={`${getTitleSizeClass(config.titleSize)} font-extrabold text-white tracking-tighter mb-3 mt-4`}
|
||||||
@@ -313,8 +313,8 @@ const App: React.FC = () => {
|
|||||||
<div className="flex flex-col gap-8 w-full mt-16">
|
<div className="flex flex-col gap-8 w-full mt-16">
|
||||||
{categories.map((category) => (
|
{categories.map((category) => (
|
||||||
<div key={category.id} className="w-full">
|
<div key={category.id} className="w-full">
|
||||||
<div className="flex justify-center items-center mb-4">
|
<div className={`flex ${getHorizontalAlignmentClass(config.horizontalAlignment)} items-center mb-4 w-full ${config.horizontalAlignment !== 'middle' ? 'px-8' : ''}`}>
|
||||||
<h2 className="text-2xl font-bold text-white text-center">{category.name}</h2>
|
<h2 className={`text-2xl font-bold text-white ${config.horizontalAlignment === 'left' ? 'text-left' : config.horizontalAlignment === 'right' ? 'text-right' : 'text-center'} ${config.horizontalAlignment !== 'middle' ? 'w-full' : ''}`}>{category.name}</h2>
|
||||||
{isEditing && (
|
{isEditing && (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -327,7 +327,7 @@ const App: React.FC = () => {
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap justify-center gap-6">
|
<div className={`flex flex-wrap ${getHorizontalAlignmentClass(config.horizontalAlignment)} gap-6`}>
|
||||||
{category.websites.map((website) => (
|
{category.websites.map((website) => (
|
||||||
<WebsiteTile
|
<WebsiteTile
|
||||||
key={website.id}
|
key={website.id}
|
||||||
@@ -335,7 +335,7 @@ const App: React.FC = () => {
|
|||||||
isEditing={isEditing}
|
isEditing={isEditing}
|
||||||
onEdit={setEditingWebsite}
|
onEdit={setEditingWebsite}
|
||||||
onMove={handleMoveWebsite}
|
onMove={handleMoveWebsite}
|
||||||
className={getTileSizeClass(config.tileSize)}
|
tileSize={config.tileSize}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{isEditing && (
|
{isEditing && (
|
||||||
|
@@ -20,6 +20,7 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
|
|||||||
subtitleSize: currentConfig.subtitleSize || 'medium',
|
subtitleSize: currentConfig.subtitleSize || 'medium',
|
||||||
alignment: currentConfig.alignment || 'middle',
|
alignment: currentConfig.alignment || 'middle',
|
||||||
tileSize: currentConfig.tileSize || 'medium',
|
tileSize: currentConfig.tileSize || 'medium',
|
||||||
|
horizontalAlignment: currentConfig.horizontalAlignment || 'middle',
|
||||||
wallpaperBlur: currentConfig.wallpaperBlur || 0,
|
wallpaperBlur: currentConfig.wallpaperBlur || 0,
|
||||||
wallpaperBrightness: currentConfig.wallpaperBrightness || 100,
|
wallpaperBrightness: currentConfig.wallpaperBrightness || 100,
|
||||||
wallpaperOpacity: currentConfig.wallpaperOpacity || 100,
|
wallpaperOpacity: currentConfig.wallpaperOpacity || 100,
|
||||||
@@ -328,6 +329,19 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<label className="text-slate-300 text-sm font-semibold">Horizontal Alignment</label>
|
||||||
|
<Dropdown
|
||||||
|
name="horizontalAlignment"
|
||||||
|
value={config.horizontalAlignment}
|
||||||
|
onChange={handleChange}
|
||||||
|
options={[
|
||||||
|
{ value: 'left', label: 'Left' },
|
||||||
|
{ value: 'middle', label: 'Middle' },
|
||||||
|
{ value: 'right', label: 'Right' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Website } from '../types';
|
import { Website } from '../types';
|
||||||
import { icons, ArrowLeft, ArrowRight, Pencil } from 'lucide-react';
|
import { icons, ArrowLeft, ArrowRight, Pencil } from 'lucide-react';
|
||||||
|
|
||||||
@@ -7,30 +7,85 @@ interface WebsiteTileProps {
|
|||||||
isEditing: boolean;
|
isEditing: boolean;
|
||||||
onEdit: (website: Website) => void;
|
onEdit: (website: Website) => void;
|
||||||
onMove: (website: Website, direction: 'left' | 'right') => void;
|
onMove: (website: Website, direction: 'left' | 'right') => void;
|
||||||
className?: string;
|
tileSize?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const WebsiteTile: React.FC<WebsiteTileProps> = ({ website, isEditing, onEdit, onMove, className }) => {
|
const getTileSizeClass = (size: string | undefined) => {
|
||||||
|
switch (size) {
|
||||||
|
case 'small':
|
||||||
|
return 'w-28 h-28';
|
||||||
|
case 'medium':
|
||||||
|
return 'w-32 h-32';
|
||||||
|
case 'large':
|
||||||
|
return 'w-36 h-36';
|
||||||
|
default:
|
||||||
|
return 'w-32 h-32';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getIconSize = (size: string | undefined) => {
|
||||||
|
switch (size) {
|
||||||
|
case 'small':
|
||||||
|
return 8;
|
||||||
|
case 'medium':
|
||||||
|
return 10;
|
||||||
|
case 'large':
|
||||||
|
return 12;
|
||||||
|
default:
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const WebsiteTile: React.FC<WebsiteTileProps> = ({ website, isEditing, onEdit, onMove, tileSize }) => {
|
||||||
const LucideIcon = icons[website.icon as keyof typeof icons];
|
const LucideIcon = icons[website.icon as keyof typeof icons];
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
const handleClick = (e: React.MouseEvent) => {
|
||||||
|
if (isEditing) {
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
// Simulate loading time (dev purpose)
|
||||||
|
// e.preventDefault();
|
||||||
|
// setTimeout(() => {
|
||||||
|
// setIsLoading(false);
|
||||||
|
// }, 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}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`relative ${className} transition-all duration-300 ease-in-out`}>
|
<div className={`relative ${getTileSizeClass(tileSize)} transition-all duration-300 ease-in-out`}>
|
||||||
<a
|
<a
|
||||||
href={isEditing ? undefined : website.url}
|
href={isEditing ? undefined : website.url}
|
||||||
target="_self"
|
target="_self"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
|
onClick={handleClick}
|
||||||
className="group flex flex-col items-center justify-center p-4 bg-black/25 backdrop-blur-md border border-white/10 rounded-2xl w-full h-full transform transition-all duration-300 ease-in-out hover:scale-105 hover:bg-white/25 shadow-lg focus:outline-none focus:ring-2 focus:ring-cyan-400 focus:ring-opacity-75"
|
className="group flex flex-col items-center justify-center p-4 bg-black/25 backdrop-blur-md border border-white/10 rounded-2xl w-full h-full transform transition-all duration-300 ease-in-out hover:scale-105 hover:bg-white/25 shadow-lg focus:outline-none focus:ring-2 focus:ring-cyan-400 focus:ring-opacity-75"
|
||||||
>
|
>
|
||||||
<div className="mb-2 transition-transform duration-300 group-hover:-translate-y-1">
|
{isLoading && (
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center mb-6">
|
||||||
|
<svg className="animate-spin h-10 w-10 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||||
|
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className={`flex items-center transition-all duration-300 ease-in ${isLoading ? 'mt-18' : 'flex-col'} ${isLoading ? 'gap-2' : ''}`}>
|
||||||
|
<div className={`transition-all duration-300 ease-in ${isLoading ? iconSizeLoadingClass : iconSizeClass}`}>
|
||||||
{LucideIcon ? (
|
{LucideIcon ? (
|
||||||
<LucideIcon className="h-10 w-10 text-white" />
|
<LucideIcon className={`text-white ${isLoading ? iconSizeLoadingClass : iconSizeClass}`} />
|
||||||
) : (
|
) : (
|
||||||
<img src={website.icon} alt={`${website.name} icon`} className="h-10 w-10 object-contain" />
|
<img src={website.icon} alt={`${website.name} icon`} className="object-contain" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<span className="text-slate-100 font-medium text-base tracking-wide text-center">
|
<span className={`text-slate-100 font-medium text-base tracking-wide text-center transition-all duration-300 ease-in ${isLoading ? 'text-sm' : ''}`}>
|
||||||
{website.name}
|
{website.name}
|
||||||
</span>
|
</span>
|
||||||
|
</div>
|
||||||
</a>
|
</a>
|
||||||
{isEditing && (
|
{isEditing && (
|
||||||
<div className="absolute bottom-2 left-0 right-0 flex justify-center gap-2">
|
<div className="absolute bottom-2 left-0 right-0 flex justify-center gap-2">
|
||||||
|
@@ -20,6 +20,6 @@ export const baseWallpapers: Wallpaper[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Waves',
|
name: 'Waves',
|
||||||
url: 'waves.jpg',
|
url: '/waves.jpg',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
15
index.html
15
index.html
@@ -4,19 +4,8 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Vision Start</title>
|
<title>Vision Start</title>
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap" rel="stylesheet">
|
|
||||||
<script type="importmap">
|
|
||||||
{
|
|
||||||
"imports": {
|
|
||||||
"react": "https://esm.sh/react@^19.1.0",
|
|
||||||
"react-dom/": "https://esm.sh/react-dom@^19.1.0/",
|
|
||||||
"react/": "https://esm.sh/react@^19.1.0/"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<link rel="stylesheet" href="/index.css">
|
<link rel="stylesheet" href="/index.css">
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-black">
|
<body class="bg-black">
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
|
import './index.css';
|
||||||
|
|
||||||
const rootElement = document.getElementById('root');
|
const rootElement = document.getElementById('root');
|
||||||
if (!rootElement) {
|
if (!rootElement) {
|
||||||
|
15
manifest.json
Normal file
15
manifest.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"manifest_version": 3,
|
||||||
|
"name": "Vision Startpage",
|
||||||
|
"version": "1.0",
|
||||||
|
"description": "A beautiful and customizable startpage for your browser.",
|
||||||
|
"chrome_url_overrides": {
|
||||||
|
"newtab": "index.html"
|
||||||
|
},
|
||||||
|
"permissions": [
|
||||||
|
"storage"
|
||||||
|
],
|
||||||
|
"content_security_policy": {
|
||||||
|
"extension_pages": "script-src 'self'; object-src 'self';"
|
||||||
|
}
|
||||||
|
}
|
1422
package-lock.json
generated
1422
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -10,13 +10,19 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hello-pangea/dnd": "^18.0.1",
|
"@hello-pangea/dnd": "^18.0.1",
|
||||||
|
"@tailwindcss/vite": "^4.1.11",
|
||||||
"lucide-react": "^0.525.0",
|
"lucide-react": "^0.525.0",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0"
|
"react-dom": "^19.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@tailwindcss/postcss": "^4.1.11",
|
||||||
"@types/node": "^22.14.0",
|
"@types/node": "^22.14.0",
|
||||||
"@types/react": "^19.1.8",
|
"@types/react": "^19.1.8",
|
||||||
|
"@vitejs/plugin-react": "^4.7.0",
|
||||||
|
"autoprefixer": "^10.4.21",
|
||||||
|
"postcss": "^8.5.6",
|
||||||
|
"tailwindcss": "^4.1.11",
|
||||||
"typescript": "~5.7.2",
|
"typescript": "~5.7.2",
|
||||||
"vite": "^6.2.0"
|
"vite": "^6.2.0"
|
||||||
}
|
}
|
||||||
|
6
postcss.config.cjs
Normal file
6
postcss.config.cjs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
'@tailwindcss/postcss': {}, // ← use the new package name
|
||||||
|
autoprefixer: {},
|
||||||
|
}
|
||||||
|
}
|
11
tailwind.config.js
Normal file
11
tailwind.config.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
content: [
|
||||||
|
"./index.html",
|
||||||
|
"./**/*.{js,ts,jsx,tsx}",
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
}
|
@@ -1,14 +1,27 @@
|
|||||||
import path from 'path';
|
|
||||||
import { defineConfig, loadEnv } from 'vite';
|
|
||||||
|
|
||||||
export default defineConfig(({ mode }) => {
|
import { defineConfig } from 'vite'
|
||||||
const env = loadEnv(mode, '.', '');
|
import react from '@vitejs/plugin-react'
|
||||||
return {
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
define: { },
|
import { resolve } from 'path'
|
||||||
resolve: {
|
|
||||||
alias: {
|
// https://vitejs.dev/config/
|
||||||
'@': path.resolve(__dirname, '.'),
|
export default defineConfig({
|
||||||
|
plugins: [react(), tailwindcss()],
|
||||||
|
build: {
|
||||||
|
rollupOptions: {
|
||||||
|
input: {
|
||||||
|
index: resolve(__dirname, 'index.html'),
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
entryFileNames: 'assets/[name].js',
|
||||||
|
chunkFileNames: 'assets/[name].js',
|
||||||
|
assetFileNames: 'assets/[name].[ext]'
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
// Ensure CSS is extracted to a separate file
|
||||||
});
|
cssCodeSplit: false,
|
||||||
|
},
|
||||||
|
// Base path for Chrome extension
|
||||||
|
base: './',
|
||||||
|
|
||||||
|
})
|
Reference in New Issue
Block a user