This commit is contained in:
2026-03-11 00:06:51 +01:00
commit 2215fd96f9
167 changed files with 2627 additions and 0 deletions

19
.dockerignore Normal file
View File

@@ -0,0 +1,19 @@
node_modules
npm-debug.log
.git
.gitignore
README.md
.env
.env.local
.DS_Store
.vscode
.svelte-kit
build
dist
.next
coverage
.turbo
timelapse
.npmrc
bun.lock.backup
*.log

5
.env.example Normal file
View File

@@ -0,0 +1,5 @@
# Timelapse Configuration
# Path where hourly timelapse images will be stored
# Default: ./timelapse (relative to project root)
# Example: /mnt/storage/timelapse or ./timelapse
TIMELAPSE_OUTPUT_PATH=./timelapse

23
.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
node_modules
# Output
.output
.vercel
.netlify
.wrangler
/.svelte-kit
/build
# OS
.DS_Store
Thumbs.db
# Env
.env
.env.*
!.env.example
!.env.test
# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
engine-strict=true

6
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,6 @@
{
"recommendations": [
"svelte.svelte-vscode",
"bradlc.vscode-tailwindcss"
]
}

5
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,5 @@
{
"files.associations": {
"*.css": "tailwindcss"
}
}

46
Dockerfile Normal file
View File

@@ -0,0 +1,46 @@
# Build stage
FROM oven/bun:latest AS builder
WORKDIR /app
# Install ffmpeg and other system dependencies
RUN apt-get update && apt-get install -y \
ffmpeg \
&& rm -rf /var/lib/apt/lists/*
# Copy package files
COPY package.json bun.lock ./
# Install dependencies with bun
RUN bun install --frozen-lockfile
# Copy source code
COPY . .
# Build the application
RUN bun run build
# Runtime stage
FROM oven/bun:latest
WORKDIR /app
# Install ffmpeg in runtime image as well
RUN apt-get update && apt-get install -y \
ffmpeg \
&& rm -rf /var/lib/apt/lists/*
# Copy package files
COPY package.json bun.lock ./
# Install dependencies (production only)
RUN bun install --frozen-lockfile --production
# Copy built application from builder
COPY --from=builder /app/build ./build
# Expose port 3000
EXPOSE 3000
# Start the application
CMD ["bun", "--bun", "run", "build/index.js"]

223
QUICK_START.md Normal file
View File

@@ -0,0 +1,223 @@
# RTSP to HLS Streaming - Quick Start Guide
## Installation (5 minutes)
### 1. Install FFmpeg
```bash
# macOS
brew install ffmpeg
# Ubuntu/Debian
sudo apt-get install ffmpeg
# Windows (Chocolatey)
choco install ffmpeg
```
### 2. Install Dependencies
```bash
npm install
```
### 3. Start Development Server
```bash
npm run dev
```
Visit `http://localhost:5173`
## Usage
1. Enter a **Stream ID** (e.g., "camera-1")
2. Enter an **RTSP URL** (e.g., "rtsp://192.168.1.100:554/stream")
3. Click **Start Stream**
4. Watch video play in the browser
5. Click **Stop Stream** to terminate
## What Was Set Up
### Backend Components
- **`src/lib/server/streaming.ts`** - FFmpeg process manager
- Spawns FFmpeg to convert RTSP → HLS
- Manages stream lifecycle
- Handles errors and cleanup
- **`src/routes/api/stream/+server.ts`** - REST API
- `/api/stream` POST endpoint
- Actions: start, stop, status, list
### Frontend Components
- **`src/lib/components/RTSPVideoPlayer.svelte`** - Video player
- HLS.js library for playback
- Input forms for Stream ID and RTSP URL
- Stream controls and status display
### Static Files
- **`static/hls/`** - HLS segments (auto-created)
- Stores `.m3u8` playlists
- Stores `.ts` segment files
- Automatically cleaned up by FFmpeg
## How It Works
```
RTSP Stream (Camera)
FFmpeg (spawned process on server)
Converts to HLS segments
HLS.js (browser)
Parses playlist and segments
HTML5 Video Player
Displays video in browser
```
## API Examples
### Start Stream
```bash
curl -X POST http://localhost:5173/api/stream \
-H "Content-Type: application/json" \
-d '{
"action": "start",
"streamId": "camera-1",
"rtspUrl": "rtsp://192.168.1.100:554/stream"
}'
```
### Stop Stream
```bash
curl -X POST http://localhost:5173/api/stream \
-H "Content-Type: application/json" \
-d '{
"action": "stop",
"streamId": "camera-1"
}'
```
### Get Status
```bash
curl -X POST http://localhost:5173/api/stream \
-H "Content-Type: application/json" \
-d '{"action": "status", "streamId": "camera-1"}'
```
### List All Streams
```bash
curl -X POST http://localhost:5173/api/stream \
-H "Content-Type: application/json" \
-d '{"action": "list"}'
```
## Common RTSP URLs
| Camera Brand | URL Pattern |
|---|---|
| Hikvision | `rtsp://admin:password@IP:554/stream` |
| Dahua | `rtsp://admin:password@IP:554/live` |
| Generic | `rtsp://user:password@IP:554/stream` |
| Reolink | `rtsp://user:password@IP:554/h264Preview_01_main` |
| Ubiquiti | `rtsp://user:password@IP:554/live1` |
## Troubleshooting
| Problem | Solution |
|---|---|
| FFmpeg not found | Install FFmpeg and add to PATH |
| Stream won't connect | Verify RTSP URL with VLC first |
| No video playback | Check browser console for HLS.js errors |
| High CPU usage | Use `-preset ultrafast` in streaming.ts |
| High latency | Reduce `-hls_time` from 10 to 2-5 seconds |
## File Structure
```
grown/
├── src/
│ ├── lib/
│ │ ├── server/
│ │ │ └── streaming.ts ← FFmpeg manager
│ │ └── components/
│ │ └── RTSPVideoPlayer.svelte ← Video player UI
│ └── routes/
│ ├── +page.svelte ← Home page
│ └── api/stream/
│ └── +server.ts ← Stream API
├── static/
│ └── hls/ ← HLS segments (auto-created)
├── package.json
├── svelte.config.js
└── vite.config.ts
```
## Configuration
Edit `src/lib/server/streaming.ts` to adjust FFmpeg parameters:
### Lower Latency (for real-time apps)
```typescript
'-hls_time', '2', // 2-second segments
'-hls_list_size', '5', // Keep 5 segments
'-preset', 'ultrafast', // Fastest encoding
```
### Higher Quality
```typescript
'-crf', '23', // Better quality
'-b:v', '2500k', // Higher video bitrate
'-b:a', '192k', // Higher audio bitrate
```
### GPU Acceleration (NVIDIA)
```typescript
'-c:v', 'h264_nvenc', // GPU encoder
'-preset', 'fast', // fast, medium, slow
```
## Testing
### Test with a Public RTSP Stream
```
rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov
```
### Test with VLC Player
```bash
# Verify RTSP URL works before using in the app
vlc rtsp://192.168.1.100:554/stream
```
## Next Steps
1. **Deploy**: Use Docker or your preferred hosting
2. **Secure**: Add authentication to `/api/stream` endpoints
3. **Monitor**: Log stream statistics and errors
4. **Scale**: Implement load balancing for multiple cameras
5. **Optimize**: Adjust FFmpeg parameters for your use case
## Documentation
- Full setup guide: `RTSP_STREAMING_SETUP.md`
- FFmpeg docs: https://ffmpeg.org/documentation.html
- HLS.js docs: https://github.com/video-dev/hls.js/wiki
- SvelteKit docs: https://kit.svelte.dev
## Browser Support
✓ Chrome/Edge (HLS.js)
✓ Firefox (HLS.js)
✓ Safari (Native HLS)
✓ Mobile browsers (Full support)
## Need Help?
1. Check the browser console for errors
2. Check terminal output for FFmpeg logs
3. Verify RTSP URL is correct
4. Ensure FFmpeg is installed and in PATH
5. Check network connectivity to camera
---
**You're all set!** Start the dev server and visit `http://localhost:5173`

42
README.md Normal file
View File

@@ -0,0 +1,42 @@
# sv
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```sh
# create a new project
npx sv create my-app
```
To recreate this project with the same configuration:
```sh
# recreate this project
bun x sv@0.12.5 create --template minimal --types ts --add tailwindcss="plugins:none" --install bun grown
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```sh
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```sh
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.

546
RTSP_STREAMING_SETUP.md Normal file
View File

@@ -0,0 +1,546 @@
# RTSP to HLS Streaming in SvelteKit
This guide walks you through setting up real-time RTSP stream conversion to HLS format in your SvelteKit application.
## What's Included
- **Backend**: FFmpeg-based RTSP to HLS converter running on Node.js
- **Frontend**: SvelteKit component with HLS.js video player
- **API**: RESTful endpoints to control streams
## Prerequisites
### System Requirements
- **Node.js**: 18 or higher
- **FFmpeg**: Must be installed and in your PATH
- **Package Manager**: npm or bun
### Install FFmpeg
#### macOS
```bash
brew install ffmpeg
```
#### Ubuntu/Debian
```bash
sudo apt-get update
sudo apt-get install ffmpeg
```
#### Windows (Chocolatey)
```bash
choco install ffmpeg
```
#### Windows (Scoop)
```bash
scoop install ffmpeg
```
#### Verify Installation
```bash
ffmpeg -version
```
## Installation
### 1. Install Dependencies
```bash
npm install
# or with bun
bun install
```
This installs `hls.js` for browser-based HLS playback.
### 2. Project Structure
The setup creates the following files:
```
src/
├── lib/
│ ├── server/
│ │ └── streaming.ts # FFmpeg stream manager
│ └── components/
│ └── RTSPVideoPlayer.svelte # Video player component
├── routes/
│ ├── +page.svelte # Home page
│ └── api/
│ └── stream/
│ └── +server.ts # Stream API endpoints
└── ...
static/
└── hls/ # HLS playlists & segments (auto-created)
```
### 3. Run Development Server
```bash
npm run dev
```
Open `http://localhost:5173` in your browser.
## Usage
### Web Interface
1. **Stream ID**: Enter a unique identifier (e.g., "camera-1")
2. **RTSP URL**: Enter your camera's RTSP stream URL
3. **Start Stream**: Click to begin conversion and playback
4. **Stop Stream**: Click to terminate the stream
### Common RTSP URLs
**Hikvision Cameras**
```
rtsp://admin:password@192.168.1.100:554/stream
rtsp://admin:password@192.168.1.100:554/Streaming/Channels/101
```
**Dahua Cameras**
```
rtsp://admin:password@192.168.1.100:554/live
```
**Generic IP Cameras**
```
rtsp://user:password@camera-ip:554/stream
rtsp://camera-ip:554/stream1
```
## API Reference
### Start Stream
**Request:**
```bash
curl -X POST http://localhost:5173/api/stream \
-H "Content-Type: application/json" \
-d '{
"action": "start",
"streamId": "camera-1",
"rtspUrl": "rtsp://192.168.1.100:554/stream"
}'
```
**Response:**
```json
{
"playlistUrl": "/hls/camera-1.m3u8"
}
```
### Stop Stream
**Request:**
```bash
curl -X POST http://localhost:5173/api/stream \
-H "Content-Type: application/json" \
-d '{
"action": "stop",
"streamId": "camera-1"
}'
```
**Response:**
```json
{
"success": true
}
```
### Get Stream Status
**Request:**
```bash
curl -X POST http://localhost:5173/api/stream \
-H "Content-Type: application/json" \
-d '{
"action": "status",
"streamId": "camera-1"
}'
```
**Response:**
```json
{
"streamId": "camera-1",
"rtspUrl": "rtsp://192.168.1.100:554/stream",
"startedAt": "2024-01-15T10:30:45.123Z",
"isRunning": true,
"playlistUrl": "/hls/camera-1.m3u8"
}
```
### List All Streams
**Request:**
```bash
curl -X POST http://localhost:5173/api/stream \
-H "Content-Type: application/json" \
-d '{"action": "list"}'
```
**Response:**
```json
{
"streams": [
{
"streamId": "camera-1",
"rtspUrl": "rtsp://192.168.1.100:554/stream",
"startedAt": "2024-01-15T10:30:45.123Z",
"isRunning": true,
"playlistUrl": "/hls/camera-1.m3u8"
}
]
}
```
## Configuration
### FFmpeg Parameters
Edit `src/lib/server/streaming.ts` to adjust encoding parameters:
```typescript
// Current defaults
'-hls_time', '10', // Segment duration (seconds)
'-hls_list_size', '3', // Number of segments to keep
'-preset', 'fast', // Encoding speed
'-b:a', '128k', // Audio bitrate
```
### Low Latency Setup
For real-time applications, modify the FFmpeg arguments:
```typescript
'-hls_time', '2', // 2-second segments
'-hls_list_size', '5', // Keep more segments
'-preset', 'ultrafast', // Fastest encoding
'-flags', '+low_delay', // Low-delay mode
```
### High Quality Setup
```typescript
'-crf', '23', // Quality (0-51, lower=better)
'-b:v', '2500k', // Video bitrate
'-c:a', 'aac',
'-b:a', '192k', // Higher audio quality
```
### GPU Acceleration (NVIDIA)
```typescript
'-c:v', 'h264_nvenc', // NVIDIA encoder
'-preset', 'fast', // fast, medium, slow
```
## Troubleshooting
### "ffmpeg: command not found"
FFmpeg is not installed or not in your system PATH.
**Solution:** Reinstall FFmpeg and ensure it's in your PATH, then restart your terminal.
```bash
# Verify FFmpeg is accessible
which ffmpeg
ffmpeg -version
```
### Stream won't connect
Check the following:
1. **RTSP URL is correct**: Test with VLC player first
2. **Network connectivity**: Ping the camera IP
3. **Firewall rules**: Ensure port 554 (default RTSP) is open
4. **Camera credentials**: Verify username/password in URL
5. **FFmpeg logs**: Check browser console and terminal output
### "Playlist not found" Error
This usually means FFmpeg hasn't created the HLS segments yet.
**Solution:** Increase the wait time in the component:
```typescript
// In RTSPVideoPlayer.svelte, startStream function
await new Promise((resolve) => setTimeout(resolve, 3000)); // Increase from 1000 to 3000
```
### Video won't play in Safari
HLS.js may have issues with some configurations.
**Solution:** Check that the HLS.js library is loaded:
```typescript
// Verify HLS.js is available
if (typeof window !== 'undefined' && !(window as any).HLS) {
console.error('HLS.js not loaded');
}
```
### High CPU Usage
The FFmpeg process is using too many resources.
**Solution:** Use a faster preset or reduce resolution:
```typescript
// Use ultrafast preset
'-preset', 'ultrafast',
// Or reduce resolution
'-vf', 'scale=1280:720',
```
### High Latency / Buffering
Segments are taking too long to generate or playback is laggy.
**Solutions:**
1. Reduce segment duration to 2-5 seconds
2. Enable low-latency mode
3. Check network bandwidth
4. Reduce video resolution
5. Close other CPU-intensive applications
## Browser Support
| Browser | HLS Support | Notes |
|---------|-------------|-------|
| Chrome | ✓ HLS.js | Full support via HLS.js library |
| Firefox | ✓ HLS.js | Full support via HLS.js library |
| Safari | ✓ Native | Native HLS support |
| Edge | ✓ HLS.js | Chromium-based, full support |
| Mobile Chrome | ✓ HLS.js | Full support |
| Mobile Safari | ✓ Native | Native HLS support |
| Opera | ✓ HLS.js | Full support |
## Security Best Practices
### 1. Environment Variables for Credentials
Never hardcode camera credentials in your code.
```typescript
// Load from environment
const rtspUrl = `rtsp://${process.env.CAMERA_USER}:${process.env.CAMERA_PASS}@${process.env.CAMERA_IP}:554/stream`;
```
Create a `.env.local` file:
```
CAMERA_USER=admin
CAMERA_PASS=password
CAMERA_IP=192.168.1.100
```
### 2. Restrict API Access
Implement authentication on the `/api/stream` endpoint:
```typescript
// src/routes/api/stream/+server.ts
export async function POST({ request, locals }) {
// Check authentication
if (!locals.user) {
return json({ error: 'Unauthorized' }, { status: 401 });
}
// Continue with stream logic
}
```
### 3. Network Security
- Use HTTPS in production
- Restrict camera access to internal network only
- Use VPN for remote access
- Implement IP whitelisting
### 4. Process Management
FFmpeg runs with server privileges. Ensure:
- Minimal file system access
- Process limits to prevent DoS
- Regular monitoring and logging
## Performance Optimization
### 1. Adaptive Bitrate
Implement multiple quality levels:
```typescript
// Start multiple streams at different resolutions
const streams = [
{ id: 'high', resolution: '1920:1080', bitrate: '5000k' },
{ id: 'medium', resolution: '1280:720', bitrate: '2500k' },
{ id: 'low', resolution: '640:360', bitrate: '1000k' }
];
```
### 2. Connection Pooling
For multiple concurrent streams, optimize memory usage:
```typescript
// Limit concurrent streams
const MAX_STREAMS = 5;
if (activeStreams.size >= MAX_STREAMS) {
return { error: 'Too many concurrent streams' };
}
```
### 3. Caching
Cache HLS segments on a CDN for better performance.
### 4. Hardware Acceleration
Use GPU encoding when available:
- NVIDIA: `h264_nvenc`
- Intel: `h264_qsv`
- AMD: `h264_amf`
## Production Deployment
### Docker
Create `Dockerfile`:
```dockerfile
FROM node:18-slim
RUN apt-get update && apt-get install -y ffmpeg && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["node", "build/index.js"]
```
Build and run:
```bash
docker build -t rtsp-hls-app .
docker run -p 3000:3000 \
-e CAMERA_USER=admin \
-e CAMERA_PASS=password \
-e CAMERA_IP=192.168.1.100 \
rtsp-hls-app
```
### Docker Compose
```yaml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
CAMERA_USER: admin
CAMERA_PASS: password
CAMERA_IP: 192.168.1.100
volumes:
- ./static/hls:/app/static/hls
restart: unless-stopped
```
### Nginx Reverse Proxy
```nginx
server {
listen 443 ssl http2;
server_name stream.example.com;
ssl_certificate /etc/ssl/certs/cert.pem;
ssl_certificate_key /etc/ssl/private/key.pem;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
location /hls/ {
alias /app/static/hls/;
expires 1h;
add_header Cache-Control "public, max-age=3600";
}
}
```
## Advanced Topics
### Custom Video Filters
Add effects or transformations:
```typescript
'-vf', 'scale=1280:720,fps=30,format=yuv420p'
```
### Audio Processing
```typescript
'-af', 'aresample=44100' // Resample to 44.1kHz
```
### Statistics and Monitoring
Log stream statistics:
```typescript
ffmpegProcess.stdout.on('data', (data) => {
console.log(`Stream stats: ${data}`);
});
```
### Multiple Bitrate HLS (Adaptive)
Generate multiple quality versions:
```typescript
// Create master playlist pointing to multiple variants
const masterPlaylist = `#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=5000000
high.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2500000
medium.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1000000
low.m3u8`;
```
## Support & Resources
- [FFmpeg Documentation](https://ffmpeg.org/documentation.html)
- [HLS.js Documentation](https://github.com/video-dev/hls.js/wiki)
- [SvelteKit Documentation](https://kit.svelte.dev)
- [ONVIF Protocol](https://www.onvif.org/) - For camera device discovery
## License
MIT

401
bun.lock Normal file
View File

@@ -0,0 +1,401 @@
{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "grown",
"dependencies": {
"node-schedule": "^2.1.1",
"onvif": "^0.8.1",
},
"devDependencies": {
"@lucide/svelte": "^0.577.0",
"@sveltejs/adapter-auto": "^7.0.0",
"@sveltejs/kit": "^2.50.2",
"@sveltejs/vite-plugin-svelte": "^6.2.4",
"@tailwindcss/vite": "^4.1.18",
"@types/node": "^25.4.0",
"@types/node-schedule": "^2.1.8",
"clsx": "^2.1.1",
"svelte": "^5.51.0",
"svelte-adapter-bun": "^1.0.1",
"svelte-check": "^4.4.2",
"tailwind-merge": "^3.4.0",
"tailwind-variants": "^3.2.2",
"tailwindcss": "^4.1.18",
"tw-animate-css": "^1.4.0",
"typescript": "^5.9.3",
"vite": "^7.3.1",
},
},
},
"packages": {
"@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
"@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="],
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="],
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="],
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="],
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
"@lucide/svelte": ["@lucide/svelte@0.577.0", "", { "peerDependencies": { "svelte": "^5" } }, "sha512-0P6mkySd2MapIEgq08tADPmcN4DHndC/02PWwaLkOerXlx5Sv9aT4BxyXLIY+eccr0g/nEyCYiJesqS61YdBZQ=="],
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="],
"@oxc-project/types": ["@oxc-project/types@0.115.0", "", {}, "sha512-4n91DKnebUS4yjUHl2g3/b2T+IUdCfmoZGhmwsovZCDaJSs+QkVAM+0AqqTxHSsHfeiMuueT75cZaZcT/m0pSw=="],
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.8", "", { "os": "android", "cpu": "arm64" }, "sha512-5bcmMQDWEfWUq3m79Mcf/kbO6e5Jr6YjKSsA1RnpXR6k73hQ9z1B17+4h93jXpzHvS18p7bQHM1HN/fSd+9zog=="],
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-dcHPd5N4g9w2iiPRJmAvO0fsIWzF2JPr9oSuTjxLL56qu+oML5aMbBMNwWbk58Mt3pc7vYs9CCScwLxdXPdRsg=="],
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-mw0VzDvoj8AuR761QwpdCFN0sc/jspuc7eRYJetpLWd+XyansUrH3C7IgNw6swBOgQT9zBHNKsVCjzpfGJlhUA=="],
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.8", "", { "os": "freebsd", "cpu": "x64" }, "sha512-xNrRa6mQ9NmMIJBdJtPMPG8Mso0OhM526pDzc/EKnRrIrrkHD1E0Z6tONZRmUeJElfsQ6h44lQQCcDilSNIvSQ=="],
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.8", "", { "os": "linux", "cpu": "arm" }, "sha512-WgCKoO6O/rRUwimWfEJDeztwJJmuuX0N2bYLLRxmXDTtCwjToTOqk7Pashl/QpQn3H/jHjx0b5yCMbcTVYVpNg=="],
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-tOHgTOQa8G4Z3ULj4G3NYOGGJEsqPHR91dT72u63OtVsZ7B6wFJKOx+ZKv+pvwzxWz92/I2ycaqi2/Ll4l+rlg=="],
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-oRbxcgDujCi2Yp1GTxoUFsIFlZsuPHU4OV4AzNc3/6aUmR4lfm9FK0uwQu82PJsuUwnF2jFdop3Ep5c1uK7Uxg=="],
"@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.8", "", { "os": "linux", "cpu": "ppc64" }, "sha512-oaLRyUHw8kQE5M89RqrDJZ10GdmGJcMeCo8tvaE4ukOofqgjV84AbqBSH6tTPjeT2BHv+xlKj678GBuIb47lKA=="],
"@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.0-rc.8", "", { "os": "linux", "cpu": "s390x" }, "sha512-1hjSKFrod5MwBBdLOOA0zpUuSfSDkYIY+QqcMcIU1WOtswZtZdUkcFcZza9b2HcAb0bnpmmyo0LZcaxLb2ov1g=="],
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.8", "", { "os": "linux", "cpu": "x64" }, "sha512-a1+F0aV4Wy9tT3o+cHl3XhOy6aFV+B8Ll+/JFj98oGkb6lGk3BNgrxd+80RwYRVd23oLGvj3LwluKYzlv1PEuw=="],
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.8", "", { "os": "linux", "cpu": "x64" }, "sha512-bGyXCFU11seFrf7z8PcHSwGEiFVkZ9vs+auLacVOQrVsI8PFHJzzJROF3P6b0ODDmXr0m6Tj5FlDhcXVk0Jp8w=="],
"@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.8", "", { "os": "none", "cpu": "arm64" }, "sha512-n8d+L2bKgf9G3+AM0bhHFWdlz9vYKNim39ujRTieukdRek0RAo2TfG2uEnV9spa4r4oHUfL9IjcY3M9SlqN1gw=="],
"@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.8", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-4R4iJDIk7BrJdteAbEAICXPoA7vZoY/M0OBfcRlQxzQvUYMcEp2GbC/C8UOgQJhu2TjGTpX1H8vVO1xHWcRqQA=="],
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-3lwnklba9qQOpFnQ7EW+A1m4bZTWXZE4jtehsZ0YOl2ivW1FQqp5gY7X2DLuKITggesyuLwcmqS11fA7NtrmrA=="],
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.8", "", { "os": "win32", "cpu": "x64" }, "sha512-VGjCx9Ha1P/r3tXGDZyG0Fcq7Q0Afnk64aaKzr1m40vbn1FL8R3W0V1ELDvPgzLXaaqK/9PnsqSaLWXfn6JtGQ=="],
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.8", "", {}, "sha512-wzJwL82/arVfeSP3BLr1oTy40XddjtEdrdgtJ4lLRBu06mP3q/8HGM6K0JRlQuTA3XB0pNJx2so/nmpY4xyOew=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.59.0", "", { "os": "android", "cpu": "arm" }, "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.59.0", "", { "os": "android", "cpu": "arm64" }, "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q=="],
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.59.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg=="],
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.59.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.59.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA=="],
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.59.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA=="],
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg=="],
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q=="],
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA=="],
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg=="],
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.59.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg=="],
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.59.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ=="],
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.59.0", "", { "os": "none", "cpu": "arm64" }, "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.59.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.59.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA=="],
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA=="],
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.9", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA=="],
"@sveltejs/adapter-auto": ["@sveltejs/adapter-auto@7.0.1", "", { "peerDependencies": { "@sveltejs/kit": "^2.0.0" } }, "sha512-dvuPm1E7M9NI/+canIQ6KKQDU2AkEefEZ2Dp7cY6uKoPq9Z/PhOXABe526UdW2mN986gjVkuSLkOYIBnS/M2LQ=="],
"@sveltejs/kit": ["@sveltejs/kit@2.53.4", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", "devalue": "^5.6.3", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "set-cookie-parser": "^3.0.0", "sirv": "^3.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": "^5.3.3", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0" }, "optionalPeers": ["@opentelemetry/api", "typescript"], "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-iAIPEahFgDJJyvz8g0jP08KvqnM6JvdW8YfsygZ+pMeMvyM2zssWMltcsotETvjSZ82G3VlitgDtBIvpQSZrTA=="],
"@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@6.2.4", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "deepmerge": "^4.3.1", "magic-string": "^0.30.21", "obug": "^2.1.0", "vitefu": "^1.1.1" }, "peerDependencies": { "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA=="],
"@sveltejs/vite-plugin-svelte-inspector": ["@sveltejs/vite-plugin-svelte-inspector@5.0.2", "", { "dependencies": { "obug": "^2.1.0" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-TZzRTcEtZffICSAoZGkPSl6Etsj2torOVrx6Uw0KpXxrec9Gg6jFWQ60Q3+LmNGfZSxHRCZL7vXVZIWmuV50Ig=="],
"@tailwindcss/node": ["@tailwindcss/node@4.2.1", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.31.1", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.1" } }, "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg=="],
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.1", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.1", "@tailwindcss/oxide-darwin-arm64": "4.2.1", "@tailwindcss/oxide-darwin-x64": "4.2.1", "@tailwindcss/oxide-freebsd-x64": "4.2.1", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", "@tailwindcss/oxide-linux-x64-musl": "4.2.1", "@tailwindcss/oxide-wasm32-wasi": "4.2.1", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" } }, "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw=="],
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.1", "", { "os": "android", "cpu": "arm64" }, "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg=="],
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw=="],
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw=="],
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA=="],
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1", "", { "os": "linux", "cpu": "arm" }, "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw=="],
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ=="],
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ=="],
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.1", "", { "os": "linux", "cpu": "x64" }, "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g=="],
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.1", "", { "os": "linux", "cpu": "x64" }, "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g=="],
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.1", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q=="],
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA=="],
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.1", "", { "os": "win32", "cpu": "x64" }, "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ=="],
"@tailwindcss/vite": ["@tailwindcss/vite@4.2.1", "", { "dependencies": { "@tailwindcss/node": "4.2.1", "@tailwindcss/oxide": "4.2.1", "tailwindcss": "4.2.1" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w=="],
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
"@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
"@types/node": ["@types/node@25.4.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-9wLpoeWuBlcbBpOY3XmzSTG3oscB6xjBEEtn+pYXTfhyXhIxC5FsBer2KTopBlvKEiW9l13po9fq+SJY/5lkhw=="],
"@types/node-schedule": ["@types/node-schedule@2.1.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-k00g6Yj/oUg/CDC+MeLHUzu0+OFxWbIqrFfDiLi6OPKxTujvpv29mHGM8GtKr7B+9Vv92FcK/8mRqi1DK5f3hA=="],
"@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
"acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
"aria-query": ["aria-query@5.3.1", "", {}, "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g=="],
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
"cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="],
"cron-parser": ["cron-parser@4.9.0", "", { "dependencies": { "luxon": "^3.2.1" } }, "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q=="],
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"devalue": ["devalue@5.6.3", "", {}, "sha512-nc7XjUU/2Lb+SvEFVGcWLiKkzfw8+qHI7zn8WYXKkLMgfGSHbgCEaR6bJpev8Cm6Rmrb19Gfd/tZvGqx9is3wg=="],
"enhanced-resolve": ["enhanced-resolve@5.20.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ=="],
"esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="],
"esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="],
"esrap": ["esrap@2.2.3", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-8fOS+GIGCQZl/ZIlhl59htOlms6U8NvX6ZYgYHpRU/b6tVSh3uHkOHZikl3D4cMbYM0JlpBe+p/BkZEi8J9XIQ=="],
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="],
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
"lightningcss": ["lightningcss@1.31.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.31.1", "lightningcss-darwin-arm64": "1.31.1", "lightningcss-darwin-x64": "1.31.1", "lightningcss-freebsd-x64": "1.31.1", "lightningcss-linux-arm-gnueabihf": "1.31.1", "lightningcss-linux-arm64-gnu": "1.31.1", "lightningcss-linux-arm64-musl": "1.31.1", "lightningcss-linux-x64-gnu": "1.31.1", "lightningcss-linux-x64-musl": "1.31.1", "lightningcss-win32-arm64-msvc": "1.31.1", "lightningcss-win32-x64-msvc": "1.31.1" } }, "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ=="],
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.31.1", "", { "os": "android", "cpu": "arm64" }, "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg=="],
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.31.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg=="],
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.31.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA=="],
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.31.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A=="],
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.31.1", "", { "os": "linux", "cpu": "arm" }, "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g=="],
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.31.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg=="],
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.31.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg=="],
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.31.1", "", { "os": "linux", "cpu": "x64" }, "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA=="],
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.31.1", "", { "os": "linux", "cpu": "x64" }, "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA=="],
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.31.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w=="],
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.31.1", "", { "os": "win32", "cpu": "x64" }, "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw=="],
"locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
"long-timeout": ["long-timeout@0.1.1", "", {}, "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w=="],
"luxon": ["luxon@3.7.2", "", {}, "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew=="],
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
"mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"node-schedule": ["node-schedule@2.1.1", "", { "dependencies": { "cron-parser": "^4.2.0", "long-timeout": "0.1.1", "sorted-array-functions": "^1.3.0" } }, "sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ=="],
"obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="],
"onvif": ["onvif@0.8.1", "", { "dependencies": { "xml2js": "^0.6.2" } }, "sha512-D0VSuTQutZWQsaWWvh7acbengYNJew25kXpXc/stJc6/xYx2MwU1tkIZA23bu12hr5MxstSQ55decY7we2/LWw=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
"postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="],
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
"rolldown": ["rolldown@1.0.0-rc.8", "", { "dependencies": { "@oxc-project/types": "=0.115.0", "@rolldown/pluginutils": "1.0.0-rc.8" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.8", "@rolldown/binding-darwin-arm64": "1.0.0-rc.8", "@rolldown/binding-darwin-x64": "1.0.0-rc.8", "@rolldown/binding-freebsd-x64": "1.0.0-rc.8", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.8", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.8", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.8", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.8", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.8", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.8", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.8", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.8", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.8", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.8", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.8" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-RGOL7mz/aoQpy/y+/XS9iePBfeNRDUdozrhCEJxdpJyimW8v6yp4c30q6OviUU5AnUJVLRL9GP//HUs6N3ALrQ=="],
"rollup": ["rollup@4.59.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.59.0", "@rollup/rollup-android-arm64": "4.59.0", "@rollup/rollup-darwin-arm64": "4.59.0", "@rollup/rollup-darwin-x64": "4.59.0", "@rollup/rollup-freebsd-arm64": "4.59.0", "@rollup/rollup-freebsd-x64": "4.59.0", "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", "@rollup/rollup-linux-arm-musleabihf": "4.59.0", "@rollup/rollup-linux-arm64-gnu": "4.59.0", "@rollup/rollup-linux-arm64-musl": "4.59.0", "@rollup/rollup-linux-loong64-gnu": "4.59.0", "@rollup/rollup-linux-loong64-musl": "4.59.0", "@rollup/rollup-linux-ppc64-gnu": "4.59.0", "@rollup/rollup-linux-ppc64-musl": "4.59.0", "@rollup/rollup-linux-riscv64-gnu": "4.59.0", "@rollup/rollup-linux-riscv64-musl": "4.59.0", "@rollup/rollup-linux-s390x-gnu": "4.59.0", "@rollup/rollup-linux-x64-gnu": "4.59.0", "@rollup/rollup-linux-x64-musl": "4.59.0", "@rollup/rollup-openbsd-x64": "4.59.0", "@rollup/rollup-openharmony-arm64": "4.59.0", "@rollup/rollup-win32-arm64-msvc": "4.59.0", "@rollup/rollup-win32-ia32-msvc": "4.59.0", "@rollup/rollup-win32-x64-gnu": "4.59.0", "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg=="],
"sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="],
"sax": ["sax@1.5.0", "", {}, "sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA=="],
"set-cookie-parser": ["set-cookie-parser@3.0.1", "", {}, "sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q=="],
"sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="],
"sorted-array-functions": ["sorted-array-functions@1.3.0", "", {}, "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"svelte": ["svelte@5.53.9", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "@types/trusted-types": "^2.0.7", "acorn": "^8.12.1", "aria-query": "5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.6.3", "esm-env": "^1.2.1", "esrap": "^2.2.2", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-MwDfWsN8qZzeP0jlQsWF4k/4B3csb3IbzCRggF+L/QqY7T8bbKvnChEo1cPZztF51HJQhilDbevWYl2LvXbquA=="],
"svelte-adapter-bun": ["svelte-adapter-bun@1.0.1", "", { "dependencies": { "rolldown": "^1.0.0-beta.38" }, "peerDependencies": { "@sveltejs/kit": "^2.4.0", "typescript": "^5" } }, "sha512-tNOvfm8BGgG+rmEA7hkmqtq07v7zoo4skLQc+hIoQ79J+1fkEMpJEA2RzCIe3aPc8JdrsMJkv3mpiZPMsgahjA=="],
"svelte-check": ["svelte-check@4.4.5", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-1bSwIRCvvmSHrlK52fOlZmVtUZgil43jNL/2H18pRpa+eQjzGt6e3zayxhp1S7GajPFKNM/2PMCG+DZFHlG9fw=="],
"tailwind-merge": ["tailwind-merge@3.5.0", "", {}, "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A=="],
"tailwind-variants": ["tailwind-variants@3.2.2", "", { "peerDependencies": { "tailwind-merge": ">=3.0.0", "tailwindcss": "*" }, "optionalPeers": ["tailwind-merge"] }, "sha512-Mi4kHeMTLvKlM98XPnK+7HoBPmf4gygdFmqQPaDivc3DpYS6aIY6KiG/PgThrGvii5YZJqRsPz0aPyhoFzmZgg=="],
"tailwindcss": ["tailwindcss@4.2.1", "", {}, "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw=="],
"tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
"totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
"vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="],
"vitefu": ["vitefu@1.1.2", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw=="],
"xml2js": ["xml2js@0.6.2", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA=="],
"xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="],
"zimmerframe": ["zimmerframe@1.1.4", "", {}, "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="],
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
}
}

16
components.json Normal file
View File

@@ -0,0 +1,16 @@
{
"$schema": "https://shadcn-svelte.com/schema.json",
"tailwind": {
"css": "src/routes/layout.css",
"baseColor": "zinc"
},
"aliases": {
"components": "$lib/components",
"utils": "$lib/utils",
"ui": "$lib/components/ui",
"hooks": "$lib/hooks",
"lib": "$lib"
},
"typescript": true,
"registry": "https://shadcn-svelte.com/registry"
}

37
package.json Normal file
View File

@@ -0,0 +1,37 @@
{
"name": "grown",
"private": true,
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
},
"devDependencies": {
"@lucide/svelte": "^0.577.0",
"@sveltejs/adapter-auto": "^7.0.0",
"@sveltejs/kit": "^2.50.2",
"@sveltejs/vite-plugin-svelte": "^6.2.4",
"@tailwindcss/vite": "^4.1.18",
"@types/node": "^25.4.0",
"@types/node-schedule": "^2.1.8",
"clsx": "^2.1.1",
"svelte": "^5.51.0",
"svelte-adapter-bun": "^1.0.1",
"svelte-check": "^4.4.2",
"tailwind-merge": "^3.4.0",
"tailwind-variants": "^3.2.2",
"tailwindcss": "^4.1.18",
"tw-animate-css": "^1.4.0",
"typescript": "^5.9.3",
"vite": "^7.3.1"
},
"dependencies": {
"node-schedule": "^2.1.1",
"onvif": "^0.8.1"
}
}

13
src/app.d.ts vendored Normal file
View File

@@ -0,0 +1,13 @@
// See https://svelte.dev/docs/kit/types#app.d.ts
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};

11
src/app.html Normal file
View File

@@ -0,0 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

104
src/hooks.server.ts Normal file
View File

@@ -0,0 +1,104 @@
import schedule from "node-schedule";
import { spawn } from "child_process";
import { existsSync, mkdirSync } from "fs";
import { join } from "path";
import { getAllStreams, getStreamProcess } from "$lib/server/streaming.js";
const TIMELAPSE_OUTPUT_PATH =
process.env.TIMELAPSE_OUTPUT_PATH || "./timelapse";
const CAPTURE_INTERVAL = "0 * * * *"; // Every hour at minute 0
let captureJob: any = null;
let initialized = false;
function ensureOutputDirectory() {
if (!existsSync(TIMELAPSE_OUTPUT_PATH)) {
mkdirSync(TIMELAPSE_OUTPUT_PATH, { recursive: true });
}
}
function captureTimelapseFrame() {
const streams = getAllStreams();
streams.forEach((streamInfo) => {
const stream = getStreamProcess(streamInfo.streamId);
if (!stream || !stream.process || !stream.process.stdout) {
console.error(
`[Timelapse] Stream ${streamInfo.streamId} not available for capture`,
);
return;
}
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
const filename = `${streamInfo.streamId}_${timestamp}.jpg`;
const outputPath = join(TIMELAPSE_OUTPUT_PATH, filename);
const ffmpegArgs = [
"-rtsp_transport",
"tcp",
"-i",
streamInfo.rtspUrl,
"-vframes",
"1",
"-q:v",
"2",
outputPath,
];
const ffmpegProcess = spawn("ffmpeg", ffmpegArgs);
ffmpegProcess.on("error", (err: Error) => {
console.error(
`[Timelapse] FFmpeg error for ${streamInfo.streamId}:`,
err,
);
});
ffmpegProcess.on("close", (code: number | null) => {
if (code === 0) {
console.log(
`[Timelapse] Captured frame for ${streamInfo.streamId}: ${filename}`,
);
} else {
console.error(
`[Timelapse] FFmpeg exited with code ${code} for ${streamInfo.streamId}`,
);
}
});
});
}
function initializeTimelapse() {
if (initialized) {
return;
}
ensureOutputDirectory();
console.log(`[Timelapse] Output directory: ${TIMELAPSE_OUTPUT_PATH}`);
console.log("[Timelapse] Starting hourly capture job...");
captureJob = schedule.scheduleJob(CAPTURE_INTERVAL, () => {
console.log("[Timelapse] Running hourly capture job...");
captureTimelapseFrame();
});
initialized = true;
console.log("[Timelapse] Hourly capture job initialized");
}
export async function handle({ event, resolve }) {
// Initialize timelapse on first request
if (!initialized) {
initializeTimelapse();
}
return resolve(event);
}
// Cleanup on shutdown
process.on("exit", () => {
if (captureJob) {
captureJob.cancel();
console.log("[Timelapse] Capture job cancelled");
}
});

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,357 @@
<script lang="ts">
import { onMount } from "svelte";
import { Play, Pause, Maximize2, Minimize2 } from "@lucide/svelte";
let canvasElement: HTMLCanvasElement | undefined = undefined;
let videoContainer: HTMLDivElement | undefined = undefined;
let isFullscreen = $state(false);
let isPlaying = $state(false);
let isLoading = $state(false);
let error = $state<string | null>(null);
let streamId = $state("camera-1");
let rtspUrl = $state("rtsp://Kamera_1:kamera_1@192.168.175.49/stream1");
let frameCount = $state(0);
let fps = $state(0);
let frameCounterStart = 0;
let streamWidth = 0;
let streamHeight = 0;
let abortController: AbortController | null = null;
async function startStream() {
if (!streamId || !rtspUrl) {
error = "Please enter both Stream ID and RTSP URL";
return;
}
isLoading = true;
error = null;
frameCount = 0;
fps = 0;
try {
const response = await fetch("/api/stream", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
action: "start",
streamId,
rtspUrl,
}),
});
const data = await response.json();
if (!response.ok || data.error) {
throw new Error(data.error || "Failed to start stream");
}
// Wait for FFmpeg to start
await new Promise((resolve) => setTimeout(resolve, 2000));
// Start streaming MJPEG frames
streamMJPEG();
isPlaying = true;
} catch (err) {
error =
err instanceof Error ? err.message : "Unknown error occurred";
console.error("Error starting stream:", err);
} finally {
isLoading = false;
}
}
async function streamMJPEG() {
if (!canvasElement) return;
abortController = new AbortController();
const signal = abortController.signal;
let reader: ReadableStreamDefaultReader<Uint8Array> | null = null;
try {
const response = await fetch(`/api/stream/mjpeg?id=${streamId}`, {
signal,
});
if (!response.ok) {
throw new Error(
`Failed to connect to stream: ${response.status}`,
);
}
if (!response.body) {
throw new Error("Response body is empty");
}
reader = response.body.getReader();
let buffer = new Uint8Array();
frameCounterStart = Date.now();
while (true) {
const { done, value } = await reader.read();
if (done) break;
// Append new data to buffer
const newBuffer = new Uint8Array(buffer.length + value.length);
newBuffer.set(buffer);
newBuffer.set(value, buffer.length);
buffer = newBuffer;
// Find JPEG frames (they start with FFD8 and end with FFD9)
const jpegStart = findJPEGStart(buffer);
if (jpegStart !== -1) {
const jpegEnd = findJPEGEnd(buffer, jpegStart);
if (jpegEnd !== -1) {
const jpegData = buffer.slice(jpegStart, jpegEnd + 2);
displayFrame(jpegData);
buffer = buffer.slice(jpegEnd + 2);
// Update FPS counter
frameCount++;
const now = Date.now();
if (now - frameCounterStart >= 1000) {
fps = frameCount;
frameCount = 0;
frameCounterStart = now;
}
}
}
// Prevent buffer from growing too large
if (buffer.length > 5000000) {
buffer = buffer.slice(-1000000);
}
}
} catch (err) {
if ((err as Error).name !== "AbortError") {
error = err instanceof Error ? err.message : "Stream error";
console.error("Error streaming MJPEG:", err);
}
} finally {
if (reader) {
reader.releaseLock();
}
}
}
function findJPEGStart(buffer: Uint8Array): number {
for (let i = 0; i < buffer.length - 1; i++) {
if (buffer[i] === 0xff && buffer[i + 1] === 0xd8) {
return i;
}
}
return -1;
}
function findJPEGEnd(buffer: Uint8Array, start: number): number {
for (let i = start + 2; i < buffer.length - 1; i++) {
if (buffer[i] === 0xff && buffer[i + 1] === 0xd9) {
return i + 1;
}
}
return -1;
}
function displayFrame(jpegData: Uint8Array) {
if (!canvasElement) return;
const blob = new Blob([jpegData as BlobPart], { type: "image/jpeg" });
const url = URL.createObjectURL(blob);
const img = new Image();
img.onload = () => {
if (!canvasElement) return;
// Store original stream dimensions
if (streamWidth === 0 || streamHeight === 0) {
streamWidth = img.width;
streamHeight = img.height;
}
// Get container dimensions
const container = canvasElement.parentElement;
if (!container) return;
const containerWidth = container.clientWidth;
const containerHeight = container.clientHeight;
// Calculate scale to fit container while maintaining aspect ratio
const scaleW = containerWidth / streamWidth;
const scaleH = containerHeight / streamHeight;
const scale = Math.min(scaleW, scaleH);
const displayWidth = Math.round(streamWidth * scale);
const displayHeight = Math.round(streamHeight * scale);
canvasElement.width = displayWidth;
canvasElement.height = displayHeight;
// Get fresh context after resizing canvas
const ctx = canvasElement.getContext("2d");
if (!ctx) {
console.error("Failed to get canvas context");
URL.revokeObjectURL(url);
return;
}
ctx.drawImage(img, 0, 0, displayWidth, displayHeight);
URL.revokeObjectURL(url);
};
img.onerror = () => {
console.error("Failed to load JPEG frame");
URL.revokeObjectURL(url);
};
img.src = url;
}
async function stopStream() {
isLoading = true;
try {
if (abortController) {
abortController.abort();
abortController = null;
}
const response = await fetch("/api/stream", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
action: "stop",
streamId,
}),
});
if (!response.ok) {
throw new Error("Failed to stop stream");
}
isPlaying = false;
error = null;
frameCount = 0;
fps = 0;
} catch (err) {
error =
err instanceof Error ? err.message : "Failed to stop stream";
console.error("Error stopping stream:", err);
} finally {
isLoading = false;
}
}
function toggleFullscreen() {
if (!videoContainer) return;
if (!document.fullscreenElement) {
videoContainer.requestFullscreen().catch((err) => {
console.error(
`Error attempting to enable fullscreen: ${err.message}`,
);
});
} else {
document.exitFullscreen();
}
}
function togglePlayPause() {
if (isPlaying) {
stopStream();
} else {
startStream();
}
}
onMount(() => {
// Handle page unload/reload
const handleBeforeUnload = () => {
if (isPlaying) {
stopStream();
}
};
const handleFullscreenChange = () => {
isFullscreen = !!document.fullscreenElement;
};
window.addEventListener("beforeunload", handleBeforeUnload);
document.addEventListener("fullscreenchange", handleFullscreenChange);
// Autoplay stream on mount
startStream();
return () => {
window.removeEventListener("beforeunload", handleBeforeUnload);
document.removeEventListener(
"fullscreenchange",
handleFullscreenChange,
);
if (abortController) {
abortController.abort();
}
};
});
</script>
<svelte:head>
<title>Keimli der II.</title>
</svelte:head>
<div class="min-h-screen bg-black py-8 px-4">
<div class="w-full max-w-7xl mx-auto">
<h1 class="text-white text-3xl font-bold mb-6">Keimli der II.</h1>
<div
bind:this={videoContainer}
class="relative bg-black rounded-lg overflow-hidden group"
style="width: 100%; aspect-ratio: 16/9;"
>
<canvas
bind:this={canvasElement}
class="w-full h-full bg-black"
style="display: block; object-fit: contain;"
></canvas>
<!-- Play/Pause button - lower left -->
<button
onclick={togglePlayPause}
class="absolute bottom-4 left-4 bg-black/50 hover:bg-black/80 text-white p-2 rounded opacity-0 group-hover:opacity-100 transition-opacity duration-200"
title={isPlaying ? "Pause stream" : "Play stream"}
>
{#if isPlaying}
<Pause class="w-5 h-5" />
{:else}
<Play class="w-5 h-5" />
{/if}
</button>
<!-- Fullscreen button - lower right -->
<button
onclick={toggleFullscreen}
class="absolute bottom-4 right-4 bg-black/50 hover:bg-black/80 text-white p-2 rounded opacity-0 group-hover:opacity-100 transition-opacity duration-200"
title="Toggle fullscreen"
>
{#if isFullscreen}
<Minimize2 class="w-5 h-5" />
{:else}
<Maximize2 class="w-5 h-5" />
{/if}
</button>
</div>
</div>
</div>
<style>
:global(html, body) {
height: 100%;
margin: 0;
padding: 0;
}
</style>

1
src/lib/index.ts Normal file
View File

@@ -0,0 +1 @@
// place files you want to import through the `$lib` alias in this folder.

138
src/lib/server/streaming.ts Normal file
View File

@@ -0,0 +1,138 @@
import { spawn } from "child_process";
interface StreamProcess {
process: ReturnType<typeof spawn> | null;
rtspUrl: string;
startedAt: Date;
clients: Set<string>;
}
const activeStreams = new Map<string, StreamProcess>();
export function startStream(
streamId: string,
rtspUrl: string,
): { success: boolean; error?: string } {
// Kill existing stream if it's running
if (activeStreams.has(streamId)) {
stopStream(streamId);
}
try {
const args = [
"-rtsp_transport",
"tcp",
"-i",
rtspUrl,
// Scale video to 1920x1080 (Full HD)
"-vf",
"scale=1920:1080",
// Video output - MJPEG format
"-c:v",
"mjpeg",
"-q:v",
"5",
"-f",
"mjpeg",
"-fflags",
"nobuffer",
// Disable audio
"-an",
// Output to stdout
"pipe:1",
];
const ffmpegProcess = spawn("ffmpeg", args);
ffmpegProcess.on("error", (err: Error) => {
console.error(`[${streamId}] FFmpeg error:`, err);
activeStreams.delete(streamId);
});
ffmpegProcess.on("close", (code: number | null) => {
activeStreams.delete(streamId);
});
activeStreams.set(streamId, {
process: ffmpegProcess,
rtspUrl,
startedAt: new Date(),
clients: new Set(),
});
return {
success: true,
};
} catch (error) {
console.error(`[${streamId}] Failed to start stream:`, error);
return {
success: false,
error: `Failed to start stream: ${error instanceof Error ? error.message : "Unknown error"}`,
};
}
}
export function stopStream(streamId: string): void {
const stream = activeStreams.get(streamId);
if (stream && stream.process) {
stream.process.kill("SIGTERM");
stream.clients.clear();
activeStreams.delete(streamId);
}
}
export function getStreamProcess(streamId: string) {
return activeStreams.get(streamId);
}
export function addStreamClient(streamId: string, client: string): boolean {
const stream = activeStreams.get(streamId);
if (!stream) {
return false;
}
stream.clients.add(client);
return true;
}
export function removeStreamClient(streamId: string, client: string): void {
const stream = activeStreams.get(streamId);
if (stream) {
stream.clients.delete(client);
// Auto-stop stream if no clients remain
if (stream.clients.size === 0) {
stopStream(streamId);
}
}
}
export function getStreamStatus(streamId: string) {
const stream = activeStreams.get(streamId);
if (!stream) {
return null;
}
return {
streamId,
rtspUrl: stream.rtspUrl,
startedAt: stream.startedAt,
isRunning: stream.process !== null,
clientCount: stream.clients.size,
};
}
export function getAllStreams() {
return Array.from(activeStreams.entries()).map(([streamId, stream]) => ({
streamId,
rtspUrl: stream.rtspUrl,
startedAt: stream.startedAt,
isRunning: stream.process !== null,
clientCount: stream.clients.size,
}));
}
export function stopAllStreams(): void {
activeStreams.forEach((stream, streamId) => {
stopStream(streamId);
});
}

13
src/lib/utils.ts Normal file
View File

@@ -0,0 +1,13 @@
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type WithoutChild<T> = T extends { child?: any } ? Omit<T, "child"> : T;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type WithoutChildren<T> = T extends { children?: any } ? Omit<T, "children"> : T;
export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>;
export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & { ref?: U | null };

View File

@@ -0,0 +1,9 @@
<script lang="ts">
import './layout.css';
import favicon from '$lib/assets/favicon.svg';
let { children } = $props();
</script>
<svelte:head><link rel="icon" href={favicon} /></svelte:head>
{@render children()}

18
src/routes/+page.svelte Normal file
View File

@@ -0,0 +1,18 @@
<script lang="ts">
import RTSPVideoPlayer from "$lib/components/RTSPVideoPlayer.svelte";
</script>
<svelte:head>
<title>RTSP Stream Viewer</title>
</svelte:head>
<div class="min-h-screen bg-linear-to-br from-gray-900 to-gray-800 py-8">
<RTSPVideoPlayer />
</div>
<style>
:global(body) {
margin: 0;
padding: 0;
}
</style>

View File

@@ -0,0 +1,97 @@
import { json } from "@sveltejs/kit";
import {
startStream,
stopStream,
getStreamStatus,
getAllStreams,
getStreamProcess,
addStreamClient,
removeStreamClient,
} from "$lib/server/streaming.js";
export async function POST({ request }) {
const { action, streamId, rtspUrl } = await request.json();
if (action === "start") {
if (!streamId || !rtspUrl) {
return json({ error: "Missing streamId or rtspUrl" }, { status: 400 });
}
const result = startStream(streamId, rtspUrl);
if (result.error) {
return json({ error: result.error }, { status: 500 });
}
return json({ success: true, streamId });
} else if (action === "stop") {
if (!streamId) {
return json({ error: "Missing streamId" }, { status: 400 });
}
stopStream(streamId);
return json({ success: true });
} else if (action === "status") {
if (!streamId) {
return json({ error: "Missing streamId" }, { status: 400 });
}
const status = getStreamStatus(streamId);
return json(status);
} else if (action === "list") {
const streams = getAllStreams();
return json({ streams });
} else {
return json({ error: "Unknown action" }, { status: 400 });
}
}
export async function GET({ url }) {
const streamId = url.searchParams.get("id");
if (!streamId) {
return new Response("Missing stream ID", { status: 400 });
}
const stream = getStreamProcess(streamId);
if (!stream || !stream.process || !stream.process.stdout) {
return new Response("Stream not found", { status: 404 });
}
// Track client
const clientId = Math.random().toString(36).substring(7);
addStreamClient(streamId, clientId);
// Create readable stream from FFmpeg stdout
const readable = stream.process.stdout;
// Return as multipart/x-mixed-replace for MJPEG
const response = new Response(readable as any, {
headers: {
"Content-Type": "multipart/x-mixed-replace; boundary=FFMPEG",
"Cache-Control": "no-cache, no-store, must-revalidate",
Pragma: "no-cache",
Expires: "0",
Connection: "keep-alive",
},
});
// Track when connection closes
if (response.body) {
const reader = response.body.getReader();
(async () => {
try {
while (true) {
await reader.read();
}
} catch (e) {
// Connection closed
console.error("Error monitoring stream connection:", e);
} finally {
removeStreamClient(streamId, clientId);
reader.releaseLock();
}
})();
}
return response;
}

View File

@@ -0,0 +1,55 @@
import {
getStreamProcess,
addStreamClient,
removeStreamClient,
} from "$lib/server/streaming.js";
export async function GET({ url }) {
const streamId = url.searchParams.get("id");
if (!streamId) {
return new Response("Missing stream ID", { status: 400 });
}
const stream = getStreamProcess(streamId);
if (!stream || !stream.process || !stream.process.stdout) {
return new Response("Stream not found", { status: 404 });
}
const clientId = Math.random().toString(36).substring(7);
addStreamClient(streamId, clientId);
// Create a web ReadableStream from the Node.js stream
const nodeStream = stream.process.stdout;
const webReadableStream = new ReadableStream({
start(controller) {
nodeStream.on("data", (chunk) => {
controller.enqueue(chunk);
});
nodeStream.on("end", () => {
controller.close();
});
nodeStream.on("error", () => {
controller.close();
});
},
cancel() {
removeStreamClient(streamId, clientId);
},
});
// Return the response with proper headers
return new Response(webReadableStream, {
status: 200,
headers: {
"Content-Type": "image/jpeg",
"Cache-Control": "no-cache, no-store, must-revalidate",
Pragma: "no-cache",
Expires: "0",
Connection: "keep-alive",
},
});
}

121
src/routes/layout.css Normal file
View File

@@ -0,0 +1,121 @@
@import "tailwindcss";
@import "tw-animate-css";
@custom-variant dark (&:is(.dark *));
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.141 0.005 285.823);
--card: oklch(1 0 0);
--card-foreground: oklch(0.141 0.005 285.823);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.141 0.005 285.823);
--primary: oklch(0.21 0.006 285.885);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.967 0.001 286.375);
--secondary-foreground: oklch(0.21 0.006 285.885);
--muted: oklch(0.967 0.001 286.375);
--muted-foreground: oklch(0.552 0.016 285.938);
--accent: oklch(0.967 0.001 286.375);
--accent-foreground: oklch(0.21 0.006 285.885);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.92 0.004 286.32);
--input: oklch(0.92 0.004 286.32);
--ring: oklch(0.705 0.015 286.067);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.141 0.005 285.823);
--sidebar-primary: oklch(0.21 0.006 285.885);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.967 0.001 286.375);
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
--sidebar-border: oklch(0.92 0.004 286.32);
--sidebar-ring: oklch(0.705 0.015 286.067);
}
.dark {
--background: oklch(0.141 0.005 285.823);
--foreground: oklch(0.985 0 0);
--card: oklch(0.21 0.006 285.885);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.21 0.006 285.885);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.92 0.004 286.32);
--primary-foreground: oklch(0.21 0.006 285.885);
--secondary: oklch(0.274 0.006 286.033);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.274 0.006 286.033);
--muted-foreground: oklch(0.705 0.015 286.067);
--accent: oklch(0.274 0.006 286.033);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.552 0.016 285.938);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.21 0.006 285.885);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.274 0.006 286.033);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.552 0.016 285.938);
}
@theme inline {
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}

View File

@@ -0,0 +1,7 @@
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:3.150000,
camera-10.ts
#EXT-X-ENDLIST

BIN
src/static/hls/camera-10.ts Normal file

Binary file not shown.

BIN
src/static/hls/camera-11.ts Normal file

Binary file not shown.

BIN
src/static/hls/camera-12.ts Normal file

Binary file not shown.

269
static/hls/camera-1.m3u8 Normal file
View File

@@ -0,0 +1,269 @@
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:12
#EXT-X-MEDIA-SEQUENCE:1773178276
#EXTINF:12.500000,
camera-10.ts
#EXTINF:12.500000,
camera-11.ts
#EXTINF:12.500000,
camera-12.ts
#EXTINF:12.500000,
camera-13.ts
#EXTINF:12.500000,
camera-14.ts
#EXTINF:12.500000,
camera-15.ts
#EXTINF:12.500000,
camera-16.ts
#EXTINF:12.500000,
camera-17.ts
#EXTINF:12.500000,
camera-18.ts
#EXTINF:12.500000,
camera-19.ts
#EXTINF:12.500000,
camera-110.ts
#EXTINF:4.600000,
camera-111.ts
#EXT-X-DISCONTINUITY
#EXTINF:3.000000,
camera-11773178288.ts
#EXTINF:3.000000,
camera-11773178289.ts
#EXTINF:3.000000,
camera-11773178290.ts
#EXTINF:3.000000,
camera-11773178291.ts
#EXTINF:3.000000,
camera-11773178292.ts
#EXTINF:3.000000,
camera-11773178293.ts
#EXTINF:3.000000,
camera-11773178294.ts
#EXTINF:3.000000,
camera-11773178295.ts
#EXTINF:3.000000,
camera-11773178296.ts
#EXTINF:3.000000,
camera-11773178297.ts
#EXTINF:3.000000,
camera-11773178298.ts
#EXTINF:3.000000,
camera-11773178299.ts
#EXTINF:3.000000,
camera-11773178300.ts
#EXTINF:3.000000,
camera-11773178301.ts
#EXTINF:3.000000,
camera-11773178302.ts
#EXTINF:3.000000,
camera-11773178303.ts
#EXTINF:3.000000,
camera-11773178304.ts
#EXTINF:3.000000,
camera-11773178305.ts
#EXTINF:3.000000,
camera-11773178306.ts
#EXTINF:3.000000,
camera-11773178307.ts
#EXTINF:3.000000,
camera-11773178308.ts
#EXTINF:3.000000,
camera-11773178309.ts
#EXTINF:3.000000,
camera-11773178310.ts
#EXTINF:3.000000,
camera-11773178311.ts
#EXTINF:3.000000,
camera-11773178312.ts
#EXTINF:3.000000,
camera-11773178313.ts
#EXTINF:3.000000,
camera-11773178314.ts
#EXTINF:3.000000,
camera-11773178315.ts
#EXTINF:3.000000,
camera-11773178316.ts
#EXTINF:3.000000,
camera-11773178317.ts
#EXTINF:3.000000,
camera-11773178318.ts
#EXTINF:3.000000,
camera-11773178319.ts
#EXTINF:3.000000,
camera-11773178320.ts
#EXTINF:3.000000,
camera-11773178321.ts
#EXTINF:3.000000,
camera-11773178322.ts
#EXTINF:3.000000,
camera-11773178323.ts
#EXTINF:3.000000,
camera-11773178324.ts
#EXTINF:3.000000,
camera-11773178325.ts
#EXTINF:3.000000,
camera-11773178326.ts
#EXTINF:3.000000,
camera-11773178327.ts
#EXTINF:3.000000,
camera-11773178328.ts
#EXTINF:3.000000,
camera-11773178329.ts
#EXTINF:3.000000,
camera-11773178330.ts
#EXTINF:3.000000,
camera-11773178331.ts
#EXTINF:3.000000,
camera-11773178332.ts
#EXTINF:3.000000,
camera-11773178333.ts
#EXTINF:3.000000,
camera-11773178334.ts
#EXTINF:3.000000,
camera-11773178335.ts
#EXTINF:3.000000,
camera-11773178336.ts
#EXTINF:3.000000,
camera-11773178337.ts
#EXTINF:3.000000,
camera-11773178338.ts
#EXTINF:3.000000,
camera-11773178339.ts
#EXTINF:3.000000,
camera-11773178340.ts
#EXTINF:3.000000,
camera-11773178341.ts
#EXTINF:3.000000,
camera-11773178342.ts
#EXTINF:3.000000,
camera-11773178343.ts
#EXTINF:3.000000,
camera-11773178344.ts
#EXTINF:3.000000,
camera-11773178345.ts
#EXTINF:3.000000,
camera-11773178346.ts
#EXTINF:3.000000,
camera-11773178347.ts
#EXTINF:3.000000,
camera-11773178348.ts
#EXTINF:3.000000,
camera-11773178349.ts
#EXTINF:3.000000,
camera-11773178350.ts
#EXTINF:3.000000,
camera-11773178351.ts
#EXTINF:3.000000,
camera-11773178352.ts
#EXTINF:3.000000,
camera-11773178353.ts
#EXTINF:3.000000,
camera-11773178354.ts
#EXTINF:3.000000,
camera-11773178355.ts
#EXTINF:3.000000,
camera-11773178356.ts
#EXTINF:3.000000,
camera-11773178357.ts
#EXTINF:3.000000,
camera-11773178358.ts
#EXTINF:3.000000,
camera-11773178359.ts
#EXTINF:3.000000,
camera-11773178360.ts
#EXTINF:3.000000,
camera-11773178361.ts
#EXTINF:3.000000,
camera-11773178362.ts
#EXTINF:3.000000,
camera-11773178363.ts
#EXTINF:3.000000,
camera-11773178364.ts
#EXTINF:3.000000,
camera-11773178365.ts
#EXTINF:3.000000,
camera-11773178366.ts
#EXTINF:3.000000,
camera-11773178367.ts
#EXTINF:3.000000,
camera-11773178368.ts
#EXTINF:3.000000,
camera-11773178369.ts
#EXTINF:3.000000,
camera-11773178370.ts
#EXTINF:3.000000,
camera-11773178371.ts
#EXTINF:3.000000,
camera-11773178372.ts
#EXTINF:3.000000,
camera-11773178373.ts
#EXTINF:3.000000,
camera-11773178374.ts
#EXTINF:3.000000,
camera-11773178375.ts
#EXTINF:3.000000,
camera-11773178376.ts
#EXTINF:3.000000,
camera-11773178377.ts
#EXTINF:3.000000,
camera-11773178378.ts
#EXTINF:3.000000,
camera-11773178379.ts
#EXTINF:3.000000,
camera-11773178380.ts
#EXTINF:3.000000,
camera-11773178381.ts
#EXTINF:3.000000,
camera-11773178382.ts
#EXTINF:3.000000,
camera-11773178383.ts
#EXTINF:3.000000,
camera-11773178384.ts
#EXTINF:3.000000,
camera-11773178385.ts
#EXTINF:3.000000,
camera-11773178386.ts
#EXTINF:3.000000,
camera-11773178387.ts
#EXTINF:3.000000,
camera-11773178388.ts
#EXTINF:3.000000,
camera-11773178389.ts
#EXTINF:3.000000,
camera-11773178390.ts
#EXTINF:3.000000,
camera-11773178391.ts
#EXTINF:3.000000,
camera-11773178392.ts
#EXTINF:3.000000,
camera-11773178393.ts
#EXTINF:3.000000,
camera-11773178394.ts
#EXTINF:3.000000,
camera-11773178395.ts
#EXTINF:3.000000,
camera-11773178396.ts
#EXTINF:3.000000,
camera-11773178397.ts
#EXTINF:3.000000,
camera-11773178398.ts
#EXTINF:3.000000,
camera-11773178399.ts
#EXTINF:3.000000,
camera-11773178400.ts
#EXTINF:3.000000,
camera-11773178401.ts
#EXTINF:3.000000,
camera-11773178402.ts
#EXTINF:3.000000,
camera-11773178403.ts
#EXTINF:3.000000,
camera-11773178404.ts
#EXTINF:3.000000,
camera-11773178405.ts
#EXTINF:3.000000,
camera-11773178406.ts
#EXTINF:3.000000,
camera-11773178407.ts

BIN
static/hls/camera-10.ts Normal file

Binary file not shown.

BIN
static/hls/camera-11.ts Normal file

Binary file not shown.

BIN
static/hls/camera-110.ts Normal file

Binary file not shown.

BIN
static/hls/camera-111.ts Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More