feat: initial commit
26
.gitignore
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
._.DS_Store
|
42
index.html
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>✦ Eclair ✦</title>
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="app">
|
||||||
|
<canvas class="background" id="bg-hash"></canvas>
|
||||||
|
<div class="background bg-no-repeat bg-cover bg-center z-[1]" id="bg-main" style="opacity: 0;"></div>
|
||||||
|
|
||||||
|
<form action="https://www.google.com/search" method="get" class="search-bar" style="opacity: 0;">
|
||||||
|
<img src="/images/google_logo.svg" class="w-8 h-8 ml-1.5 md:ml-2" alt="google" />
|
||||||
|
<input name="q" placeholder="Search" autofocus />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="quick-launch" style="opacity: 0;">
|
||||||
|
<button type="button" title="Open Launcher" class="open-launcher"><img src="/images/chevron-up.svg"
|
||||||
|
class="transition-transform" alt="open launcher" /></button>
|
||||||
|
<div class="items"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="launcher" style="opacity: 0;">
|
||||||
|
<div class="sheet">
|
||||||
|
<div class="title flex items-center gap-4 ml-4 md:ml-0" role="button">
|
||||||
|
<img src="/images/layout-panel-left.svg" class="w-6 h-6" alt="launcher" />
|
||||||
|
<p class="text-2xl font-medium">Apps</p>
|
||||||
|
</div>
|
||||||
|
<div class="items mt-4"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
22
package.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "launcher",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc && vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"autoprefixer": "^10.4.20",
|
||||||
|
"postcss": "^8.4.47",
|
||||||
|
"tailwindcss": "^3.4.14",
|
||||||
|
"typescript": "~5.6.2",
|
||||||
|
"vite": "^5.4.10",
|
||||||
|
"vite-plugin-pwa": "^0.20.5"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"blurhash": "^2.0.5"
|
||||||
|
}
|
||||||
|
}
|
4239
pnpm-lock.yaml
generated
Normal file
6
postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
BIN
public/android-chrome-192x192.png
Executable file
After Width: | Height: | Size: 79 KiB |
BIN
public/android-chrome-512x512.png
Executable file
After Width: | Height: | Size: 443 KiB |
BIN
public/apple-touch-icon.png
Executable file
After Width: | Height: | Size: 70 KiB |
BIN
public/favicon-16x16.png
Executable file
After Width: | Height: | Size: 960 B |
BIN
public/favicon-32x32.png
Executable file
After Width: | Height: | Size: 3.1 KiB |
BIN
public/favicon.ico
Executable file
After Width: | Height: | Size: 15 KiB |
BIN
public/images/._layout-panel-left.svg
Executable file
BIN
public/images/1226538.webp
Normal file
After Width: | Height: | Size: 116 KiB |
1
public/images/chevron-up.svg
Executable file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-up"><path d="m18 15-6-6-6 6"/></svg>
|
After Width: | Height: | Size: 238 B |
1
public/images/google_logo.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#4285F4"/><path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"/><path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05"/><path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"/><path d="M1 1h22v22H1z" fill="none"/></svg>
|
After Width: | Height: | Size: 742 B |
1
public/images/layout-panel-left.svg
Executable file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-layout-panel-left"><rect width="7" height="18" x="3" y="3" rx="1"/><rect width="7" height="7" x="14" y="3" rx="1"/><rect width="7" height="7" x="14" y="14" rx="1"/></svg>
|
After Width: | Height: | Size: 372 B |
19
public/site.webmanifest
Executable file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "Eclair Launcher",
|
||||||
|
"short_name": "Launcher",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/android-chrome-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/android-chrome-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"theme_color": "#ffffff",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"display": "standalone"
|
||||||
|
}
|
56
src/background.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import * as blurhash from "blurhash";
|
||||||
|
|
||||||
|
const bgHash =
|
||||||
|
"|ZCshDM^M_axogogWCWAWA-@WAWBj]axaxj[j]ay9boGobWCR%ayt7ofj[4,xZoeWCaxj[ofj]j[V=ofaxa}ofj[WCWVfP$|WEj^ofofayWBaya{s+R*ogs;axWBayj[j[RiWUj]j[WVj[j[axayRioKflfSa{axaxfPa#";
|
||||||
|
const bgUrl = "/images/1226538.webp";
|
||||||
|
|
||||||
|
const onLoaded = () => {
|
||||||
|
const searchBar = document.querySelector(".search-bar") as HTMLDivElement;
|
||||||
|
if (searchBar) {
|
||||||
|
searchBar.classList.add("loaded");
|
||||||
|
searchBar.style.opacity = "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
const quickLaunch = document.querySelector(".quick-launch") as HTMLDivElement;
|
||||||
|
if (quickLaunch) {
|
||||||
|
quickLaunch.classList.add("loaded");
|
||||||
|
quickLaunch.style.opacity = "1";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const initBackground = () => {
|
||||||
|
const hashCanvas = document.getElementById("bg-hash") as HTMLCanvasElement;
|
||||||
|
if (!hashCanvas) {
|
||||||
|
throw new Error("Could not find canvas");
|
||||||
|
}
|
||||||
|
|
||||||
|
hashCanvas.width = 32;
|
||||||
|
hashCanvas.height = 18;
|
||||||
|
|
||||||
|
const pixels = blurhash.decode(bgHash, 32, 18);
|
||||||
|
const ctx = hashCanvas.getContext("2d");
|
||||||
|
if (!ctx) {
|
||||||
|
throw new Error("Could not get 2d context");
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageData = ctx.createImageData(hashCanvas.width, hashCanvas.height);
|
||||||
|
imageData.data.set(pixels);
|
||||||
|
ctx.putImageData(imageData, 0, 0);
|
||||||
|
|
||||||
|
// Load background image
|
||||||
|
const bg = document.getElementById("bg-main") as HTMLDivElement;
|
||||||
|
const img = new Image();
|
||||||
|
img.src = bgUrl;
|
||||||
|
img.onload = () => {
|
||||||
|
bg.style.backgroundImage = `url(${bgUrl})`;
|
||||||
|
bg.style.opacity = "1";
|
||||||
|
bg.classList.add("loaded");
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
// remove hashCanvas from dom
|
||||||
|
hashCanvas.remove();
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
onLoaded();
|
||||||
|
};
|
||||||
|
};
|
209
src/launcher.ts
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
export const launcherItems: LauncherItem[] = [
|
||||||
|
{
|
||||||
|
name: "Jellyfin",
|
||||||
|
href: "http://10.0.0.101:8096/web",
|
||||||
|
icon: "https://github.com/walkxcode/dashboard-icons/raw/be82e22c418f5980ee2a13064d50f1483df39c8c/svg/jellyfin.svg",
|
||||||
|
pinned: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Musicfin",
|
||||||
|
href: "http://armbian:8096/web",
|
||||||
|
icon: "https://github.com/walkxcode/dashboard-icons/raw/be82e22c418f5980ee2a13064d50f1483df39c8c/svg/jellyfin.svg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Youtube2MP3",
|
||||||
|
href: "http://yt2mp3.home.ip",
|
||||||
|
icon: "https://git.rul.sh/khairul169/Go-YT2MP3/raw/branch/main/ui/public/android-chrome-512x512.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Memos",
|
||||||
|
href: "https://memos.rul.sh",
|
||||||
|
icon: "https://memos.rul.sh/apple-touch-icon.png",
|
||||||
|
pinned: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Flatnotes",
|
||||||
|
href: "http://100.64.0.3:8124",
|
||||||
|
icon: "https://github.com/dullage/flatnotes/blob/develop/client/public/android-chrome-192x192.png?raw=true",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Gitea",
|
||||||
|
href: "https://git.rul.sh",
|
||||||
|
icon: "https://github.com/walkxcode/dashboard-icons/raw/be82e22c418f5980ee2a13064d50f1483df39c8c/svg/gitea.svg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Gotify",
|
||||||
|
href: "https://gotify.rul.sh",
|
||||||
|
icon: "https://github.com/walkxcode/dashboard-icons/raw/be82e22c418f5980ee2a13064d50f1483df39c8c/svg/gotify.svg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "AriaNg",
|
||||||
|
href: "http://ariang.home.ip/",
|
||||||
|
icon: "https://raw.githubusercontent.com/mayswind/AriaNg/master/src/tileicon.png",
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "File Browser",
|
||||||
|
href: "http://10.0.0.101:9005/files",
|
||||||
|
icon: "https://github.com/walkxcode/dashboard-icons/raw/be82e22c418f5980ee2a13064d50f1483df39c8c/svg/filebrowser.svg",
|
||||||
|
pinned: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "File Browser (Armbian)",
|
||||||
|
href: "https://fm.rul.sh",
|
||||||
|
icon: "https://github.com/walkxcode/dashboard-icons/raw/be82e22c418f5980ee2a13064d50f1483df39c8c/svg/filebrowser.svg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Change Detection",
|
||||||
|
href: "https://chgdetect.rul.sh/",
|
||||||
|
icon: "https://raw.githubusercontent.com/dgtlmoon/changedetection.io/master/changedetectionio/static/images/avatar-256x256.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Vaultwarden",
|
||||||
|
href: "https://vw.rul.sh",
|
||||||
|
icon: "https://github.com/walkxcode/dashboard-icons/raw/be82e22c418f5980ee2a13064d50f1483df39c8c/svg/vaultwarden.svg",
|
||||||
|
pinned: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Garage WebUI",
|
||||||
|
href: "http://10.0.0.101:3909/buckets",
|
||||||
|
icon: "https://garagehq.deuxfleurs.fr/icons/apple-touch-icon.png",
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Pi-hole",
|
||||||
|
href: "http://pi.hole/admin",
|
||||||
|
icon: "https://github.com/walkxcode/dashboard-icons/raw/be82e22c418f5980ee2a13064d50f1483df39c8c/svg/pi-hole.svg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Headscale",
|
||||||
|
href: "https://headscale.rul.sh/web/devices.html",
|
||||||
|
icon: "https://raw.githubusercontent.com/juanfont/headscale/refs/heads/main/docs/logo/headscale3-dots.svg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Nginx Proxy Manager",
|
||||||
|
href: "http://nginx-pm.home.ip/",
|
||||||
|
icon: "https://github.com/walkxcode/dashboard-icons/raw/be82e22c418f5980ee2a13064d50f1483df39c8c/svg/nginx-proxy-manager.svg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Nginx UI",
|
||||||
|
href: "https://nginx-ui.rul.sh/",
|
||||||
|
icon: "https://nginxui.com/assets/icon.svg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pgAdmin",
|
||||||
|
href: "http://pgadmin.home.ip/",
|
||||||
|
icon: "https://github.com/walkxcode/dashboard-icons/raw/be82e22c418f5980ee2a13064d50f1483df39c8c/svg/pgadmin.svg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "phpMyAdmin",
|
||||||
|
href: "http://pma.home.ip/",
|
||||||
|
icon: "https://github.com/walkxcode/dashboard-icons/raw/be82e22c418f5980ee2a13064d50f1483df39c8c/svg/phpmyadmin.svg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Mongo Express",
|
||||||
|
href: "http://home:27018/",
|
||||||
|
icon: "https://github.com/walkxcode/dashboard-icons/raw/be82e22c418f5980ee2a13064d50f1483df39c8c/svg/mongodb.svg",
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Hoppscotch",
|
||||||
|
href: "https://hopp.rul.sh/",
|
||||||
|
icon: "https://github.com/walkxcode/dashboard-icons/raw/be82e22c418f5980ee2a13064d50f1483df39c8c/svg/hoppscotch.svg",
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "aaPanel",
|
||||||
|
href: "https://10.0.0.102:29760/590d1b7a",
|
||||||
|
icon: "https://www.aapanel.com/static/images/bt_logo.png",
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ProxmoxVE",
|
||||||
|
href: "https://pve.rul.sh/",
|
||||||
|
icon: "https://www.proxmox.com/apple-touch-icon.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "LXConsole",
|
||||||
|
href: "https://lxc.rul.sh/",
|
||||||
|
icon: "https://github.com/PenningLabs/lxconsole/raw/refs/heads/main/lxconsole/static/assets/img/logo-light.svg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Incus UI",
|
||||||
|
href: "https://164.152.166.61:8443/",
|
||||||
|
icon: "https://linuxcontainers.org/static/img/containers.small.png",
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Incus UI (Armbian)",
|
||||||
|
href: "https://armbian:8443",
|
||||||
|
icon: "https://linuxcontainers.org/static/img/containers.small.png",
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
type LauncherItem = {
|
||||||
|
name: string;
|
||||||
|
href: string;
|
||||||
|
icon: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
pinned?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const openBtn = document.querySelector(".open-launcher") as HTMLButtonElement;
|
||||||
|
const launcher = document.querySelector(".launcher") as HTMLDivElement;
|
||||||
|
const title = launcher.querySelector(".title") as HTMLButtonElement;
|
||||||
|
const itemContainer = launcher.querySelector(".items") as HTMLDivElement;
|
||||||
|
|
||||||
|
let isLauncherOpen = false;
|
||||||
|
|
||||||
|
export const openLauncher = () => {
|
||||||
|
launcher.classList.add("open");
|
||||||
|
launcher.style.opacity = "1";
|
||||||
|
isLauncherOpen = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const closeLauncher = () => {
|
||||||
|
launcher.classList.remove("open");
|
||||||
|
launcher.style.opacity = "0";
|
||||||
|
isLauncherOpen = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onScroll = (e: WheelEvent) => {
|
||||||
|
if (e.deltaY > 10 && !isLauncherOpen) {
|
||||||
|
openLauncher();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const curScroll = launcher.scrollTop;
|
||||||
|
if (e.deltaY < -50 && isLauncherOpen && curScroll <= 1) {
|
||||||
|
closeLauncher();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const initLauncher = () => {
|
||||||
|
itemContainer.innerHTML = launcherItems
|
||||||
|
.filter((item) => item.disabled !== true)
|
||||||
|
.map((item) => {
|
||||||
|
return `
|
||||||
|
<a href="${item.href}" class="item" rel="noopener noreferrer" title="${item.name}">
|
||||||
|
<div class="image">
|
||||||
|
<img src="${item.icon}" alt="${item.name}" />
|
||||||
|
</div>
|
||||||
|
<span class="title">${item.name}</span>
|
||||||
|
</a>
|
||||||
|
`;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
title.addEventListener("click", closeLauncher);
|
||||||
|
openBtn.addEventListener("click", openLauncher);
|
||||||
|
document.addEventListener("wheel", onScroll);
|
||||||
|
|
||||||
|
launcher.addEventListener("click", (e) => {
|
||||||
|
if (e.target === launcher) {
|
||||||
|
closeLauncher();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
12
src/main.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { initBackground } from "./background";
|
||||||
|
import { initLauncher } from "./launcher";
|
||||||
|
import { initQuickLaunch } from "./quick-launch";
|
||||||
|
import "./style.css";
|
||||||
|
|
||||||
|
const onReady = () => {
|
||||||
|
initBackground();
|
||||||
|
initQuickLaunch();
|
||||||
|
initLauncher();
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", onReady);
|
21
src/quick-launch.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { launcherItems } from "./launcher";
|
||||||
|
|
||||||
|
export const initQuickLaunch = () => {
|
||||||
|
const container = document.querySelector(".quick-launch .items");
|
||||||
|
if (!container) {
|
||||||
|
throw new Error("Could not find quick-launch container");
|
||||||
|
}
|
||||||
|
|
||||||
|
const pinnedItems = launcherItems.filter((item) => item.pinned);
|
||||||
|
|
||||||
|
container.innerHTML = pinnedItems
|
||||||
|
.filter((item) => item.disabled !== true)
|
||||||
|
.map((item) => {
|
||||||
|
return `
|
||||||
|
<a href="${item.href}" class="item" rel="noopener noreferrer" title="${item.name}">
|
||||||
|
<img src="${item.icon}" alt="${item.name}" />
|
||||||
|
</a>
|
||||||
|
`;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
};
|
101
src/style.css
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
#app {
|
||||||
|
@apply w-full min-h-screen max-h-dvh relative bg-gray-900 overflow-hidden p-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.background {
|
||||||
|
@apply absolute left-0 top-0 w-full h-full scale-100 transition-[opacity,_transform] duration-1000 ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.background.loaded {
|
||||||
|
@apply scale-105;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-bar {
|
||||||
|
@apply bg-white/80 backdrop-blur-md shadow-lg rounded-full absolute top-[5%] md:top-[10%] left-1/2 -translate-x-1/2 z-[2] flex items-center overflow-hidden w-[90%] max-w-[500px];
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-bar input {
|
||||||
|
@apply bg-transparent outline-none p-2 md:p-3 flex-1 w-full min-w-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-bar.loaded {
|
||||||
|
@apply transition-opacity duration-500 ease-in-out delay-300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-launch {
|
||||||
|
@apply absolute bottom-[3%] md:bottom-[10%] left-1/2 -translate-x-1/2 z-[3] w-full flex flex-col items-center justify-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-launch .open-launcher {
|
||||||
|
@apply rounded-full size-16 mb-2 transition-[background,_transform] ease-in-out duration-700 flex items-center justify-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-launch .open-launcher:hover {
|
||||||
|
@apply bg-white/10 -translate-y-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-launch .open-launcher:hover img {
|
||||||
|
@apply scale-y-110;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-launch .items {
|
||||||
|
@apply bg-white/10 backdrop-blur-[3px] shadow-lg p-4 rounded-xl flex items-center gap-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-launch .item {
|
||||||
|
@apply bg-white rounded-xl p-2 aspect-square overflow-hidden block transition-transform flex-shrink-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-launch .item:hover {
|
||||||
|
@apply scale-110;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-launch .item img {
|
||||||
|
@apply size-12 rounded-lg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-launch.loaded {
|
||||||
|
@apply transition-opacity duration-500 ease-in-out delay-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.launcher {
|
||||||
|
@apply w-full h-screen max-h-dvh overflow-y-auto absolute translate-y-[10%] transition-all duration-300 ease-in-out z-10 pointer-events-none flex flex-col p-4 md:p-8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.launcher.open {
|
||||||
|
@apply translate-y-0 pointer-events-auto backdrop-blur-lg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.launcher .sheet {
|
||||||
|
@apply bg-white/50 backdrop-blur-sm rounded-2xl p-4 md:p-8 w-full max-w-2xl m-auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.launcher .items {
|
||||||
|
@apply grid grid-cols-3 lg:grid-cols-4 gap-y-4 gap-2 md:gap-4 md:gap-y-6 lg:gap-6 lg:gap-y-10 p-2 md:p-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.launcher .item {
|
||||||
|
@apply flex flex-col items-center transition-transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.launcher .item:hover {
|
||||||
|
@apply scale-110;
|
||||||
|
}
|
||||||
|
|
||||||
|
.launcher .item .image {
|
||||||
|
@apply bg-white rounded-xl p-2 aspect-square overflow-hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.launcher .item .image img {
|
||||||
|
@apply size-12 md:size-14 rounded-lg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.launcher .item span {
|
||||||
|
@apply text-center text-sm md:text-base leading-4 mt-3 block;
|
||||||
|
}
|
1
src/vite-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
8
tailwind.config.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
};
|
24
tsconfig.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
30
vite.config.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { defineConfig } from "vite";
|
||||||
|
import { VitePWA } from "vite-plugin-pwa";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
VitePWA({
|
||||||
|
registerType: "autoUpdate",
|
||||||
|
manifest: {
|
||||||
|
name: "Eclair Launcher",
|
||||||
|
short_name: "Launcher",
|
||||||
|
start_url: "https://home.rul.sh",
|
||||||
|
icons: [
|
||||||
|
{
|
||||||
|
src: "/android-chrome-192x192.png",
|
||||||
|
sizes: "192x192",
|
||||||
|
type: "image/png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/android-chrome-512x512.png",
|
||||||
|
sizes: "512x512",
|
||||||
|
type: "image/png",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
theme_color: "#ffffff",
|
||||||
|
background_color: "#ffffff",
|
||||||
|
display: "standalone",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|