Compare commits
40 Commits
develop-go
...
develop-up
| Author | SHA1 | Date | |
|---|---|---|---|
| eeac331ea2 | |||
| d473b0f59e | |||
| 051fc27dac | |||
| 3be483cff0 | |||
| 476f40a529 | |||
| dbc8b1a82a | |||
| ab9b4f229d | |||
| b435638774 | |||
| 8515498ade | |||
| 43702e8bf7 | |||
| cc3f62a6a1 | |||
| 15b69773f0 | |||
| a80fa755d4 | |||
| 6a1d8b1aee | |||
| 1f8fdad2da | |||
| 5350f421f9 | |||
| 698abcec22 | |||
| b60f65e4d1 | |||
| e854c68ad0 | |||
| b787cd161a | |||
| bd8b21955e | |||
| 87c99e64cd | |||
| baa8811e9e | |||
| fa88fe26b3 | |||
| 90d8409aa9 | |||
| b4bbacd9f1 | |||
| 8b85736903 | |||
| 3beabcfe7f | |||
| 35117b7be9 | |||
| e3587eff71 | |||
| 57903b80b6 | |||
| 5c0ca0e139 | |||
| 9276603a70 | |||
| dc06412f79 | |||
| edc1a33547 | |||
| 3c24da4af8 | |||
| 3491a0a08e | |||
| 6d04f1cbdc | |||
| bbe15d5f3e | |||
| dba9713d96 |
@@ -3,7 +3,16 @@
|
||||
"allow": [
|
||||
"Bash(bun run build)",
|
||||
"mcp__ide__getDiagnostics",
|
||||
"Bash(bun install:*)"
|
||||
"Bash(bun install:*)",
|
||||
"Bash(bun preview:*)",
|
||||
"Bash(curl:*)",
|
||||
"Bash(python -:*)",
|
||||
"Bash(bun run:*)",
|
||||
"Bash(bunx:*)",
|
||||
"Bash(bun:*)",
|
||||
"Bash(git diff:*)",
|
||||
"Bash(git add:*)",
|
||||
"Bash(git status:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
87
.deploy/stream.ui-production.yaml
Normal file
87
.deploy/stream.ui-production.yaml
Normal file
@@ -0,0 +1,87 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: stream-ui-config
|
||||
namespace: stream-production
|
||||
labels:
|
||||
app: stream-ui
|
||||
data:
|
||||
STREAM_API_GRPC_ADDR: "stream.api-svc:9000"
|
||||
GOOGLE_AUTH_FINALIZE_PATH: "/auth/google/finalize"
|
||||
STREAM_INTERNAL_AUTH_MARKER: "stream_maker_123xxx"
|
||||
STREAM_UI_JWT_SECRET: "xxx_stream_maker_123_xxx"
|
||||
STREAM_UI_REDIS_URL: "redis://:pass123@47.84.62.226:6379/3"
|
||||
FRONTEND_BASE_URL: "https://hlstiktok.com"
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: stream-ui-svc
|
||||
namespace: stream-production
|
||||
labels:
|
||||
app: stream-ui
|
||||
spec:
|
||||
selector:
|
||||
app: stream-ui
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
targetPort: 3000
|
||||
type: NodePort
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: stream-ui-dep
|
||||
namespace: stream-production
|
||||
labels:
|
||||
app: stream-ui
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: stream-ui
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: stream-ui
|
||||
spec:
|
||||
# imagePullSecrets:
|
||||
# - name: registry-production-secret
|
||||
containers:
|
||||
- name: stream-ui
|
||||
image: registry.awing.vn/stream-production/stream-ui:$BUILD_NUMBER
|
||||
ports:
|
||||
- containerPort: 3000
|
||||
env:
|
||||
- name: STREAM_API_GRPC_ADDR
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: stream-ui-config
|
||||
key: STREAM_API_GRPC_ADDR
|
||||
- name: GOOGLE_AUTH_FINALIZE_PATH
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: stream-ui-config
|
||||
key: GOOGLE_AUTH_FINALIZE_PATH
|
||||
- name: STREAM_INTERNAL_AUTH_MARKER
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: stream-ui-config
|
||||
key: STREAM_INTERNAL_AUTH_MARKER
|
||||
- name: STREAM_UI_JWT_SECRET
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: stream-ui-config
|
||||
key: STREAM_UI_JWT_SECRET
|
||||
- name: STREAM_UI_REDIS_URL
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: stream-ui-config
|
||||
key: STREAM_UI_REDIS_URL
|
||||
- name: FRONTEND_BASE_URL
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: stream-ui-config
|
||||
key: FRONTEND_BASE_URL
|
||||
68
.dockerignore
Normal file
68
.dockerignore
Normal file
@@ -0,0 +1,68 @@
|
||||
# Dependencies
|
||||
node_modules
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Build outputs
|
||||
dist
|
||||
build
|
||||
.rsbuild
|
||||
node_modules
|
||||
/node_modules
|
||||
# Environment files
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# IDE and editor files
|
||||
.vscode
|
||||
.idea
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS generated files
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# Docker
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
docker-compose.yml
|
||||
|
||||
# Documentation
|
||||
README.md
|
||||
*.md
|
||||
|
||||
# Test files
|
||||
coverage
|
||||
.coverage
|
||||
.nyc_output
|
||||
test
|
||||
tests
|
||||
__tests__
|
||||
*.test.js
|
||||
*.test.ts
|
||||
*.spec.js
|
||||
*.spec.ts
|
||||
|
||||
# Linting
|
||||
.eslintrc*
|
||||
.prettierrc*
|
||||
.stylelintrc*
|
||||
|
||||
# Other
|
||||
.husky
|
||||
@@ -1,7 +1,7 @@
|
||||
# AGENTS.md
|
||||
|
||||
This file provides guidance for AI coding agents working with the Holistream codebase.
|
||||
|
||||
hallo
|
||||
## Project Overview
|
||||
|
||||
**Holistream** is a Vue 3 streaming application with Server-Side Rendering (SSR) deployed on Cloudflare Workers. It provides video upload, management, and streaming capabilities for content creators.
|
||||
|
||||
219
CLAUDE.md
219
CLAUDE.md
@@ -2,197 +2,82 @@
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
## Project overview
|
||||
|
||||
Holistream is a Vue 3 streaming application with Server-Side Rendering (SSR) deployed on Cloudflare Workers. It provides video upload, management, and streaming capabilities.
|
||||
`stream-ui` is a Vue 3 SSR frontend deployed on Cloudflare Workers. It uses Hono as the Worker server layer and a custom Vite SSR setup rather than Nuxt.
|
||||
|
||||
## Technology Stack
|
||||
## Common commands
|
||||
|
||||
- **Framework**: Vue 3 with JSX/TSX support
|
||||
- **Router**: Vue Router 5 with SSR-aware history
|
||||
- **Server**: Hono framework on Cloudflare Workers
|
||||
- **Build Tool**: Vite 7 with custom SSR plugin
|
||||
- **Styling**: UnoCSS (Tailwind-like utility-first CSS)
|
||||
- **UI Components**: PrimeVue 4 with Aura theme
|
||||
- **State Management**: Pinia + Pinia Colada for server state
|
||||
- **HTTP Client**: Auto-generated from OpenAPI spec via swagger-typescript-api
|
||||
- **Package Manager**: Bun
|
||||
|
||||
## Common Commands
|
||||
Run all commands from `stream-ui/`.
|
||||
|
||||
```bash
|
||||
# Development server with hot reload
|
||||
bun dev
|
||||
# Install dependencies
|
||||
bun install
|
||||
|
||||
# Production build (client + worker)
|
||||
# Start local dev server
|
||||
bun run dev
|
||||
|
||||
# Build client + worker bundles
|
||||
bun run build
|
||||
|
||||
# Preview production build locally
|
||||
bun preview
|
||||
bun run preview
|
||||
|
||||
# Deploy to Cloudflare Workers
|
||||
bun run deploy
|
||||
|
||||
# Generate TypeScript types from Wrangler config
|
||||
# Regenerate Cloudflare binding types from Wrangler config
|
||||
bun run cf-typegen
|
||||
|
||||
# View Cloudflare Worker logs
|
||||
# Tail Cloudflare Worker logs
|
||||
bun run tail
|
||||
```
|
||||
|
||||
**Note**: The project uses Bun as the package manager. If using npm/yarn, replace `bun` with `npm run` or `yarn`.
|
||||
Notes:
|
||||
- This project uses Bun (`bun.lock` is present).
|
||||
- There is currently no configured `test` script.
|
||||
- There is currently no configured `lint` script.
|
||||
|
||||
## Architecture
|
||||
|
||||
### SSR Architecture
|
||||
### SSR entrypoints
|
||||
- `src/index.tsx`: Hono Worker entry; registers middleware, proxy routes, merge/display/manifest routes, then SSR routes
|
||||
- `src/main.ts`: shared app factory for SSR and client hydration
|
||||
- `src/client.ts`: client-side hydration entry
|
||||
- `ssrPlugin.ts`: custom Vite SSR plugin that builds the client first, injects the Vite manifest, and swaps environment-specific modules
|
||||
|
||||
The app uses a custom SSR setup (`ssrPlugin.ts`) that:
|
||||
- Builds the client bundle FIRST, then the Worker bundle
|
||||
- Injects the Vite manifest into the server build for asset rendering
|
||||
- Uses environment-based module resolution for `httpClientAdapter` and `liteMqtt`
|
||||
### Routing and app structure
|
||||
- Routes live in `src/routes/index.ts`.
|
||||
- Routing is SSR-aware: `createMemoryHistory()` on the server and `createWebHistory()` in the browser.
|
||||
- The app is split into:
|
||||
- public pages
|
||||
- auth pages
|
||||
- protected dashboard/settings pages
|
||||
- Current protected areas include `videos`, `notification`, and `settings/*` routes.
|
||||
|
||||
Entry points:
|
||||
- **Server**: `src/index.tsx` - Hono app that renders Vue SSR stream
|
||||
- **Client**: `src/client.ts` - Hydrates the SSR-rendered app
|
||||
### State and hydration
|
||||
- Pinia is used for app state.
|
||||
- `@pinia/colada` is used for server-state/query hydration.
|
||||
- SSR serializes Pinia state into `$p` and query cache into `$colada`; `src/client.ts` restores both during hydration.
|
||||
- `src/stores/auth.ts` owns session state and route guards depend on `auth.user`.
|
||||
|
||||
### Module Aliases
|
||||
### API integration
|
||||
- `src/api/client.ts` is generated by `swagger-typescript-api`; do not hand-edit generated sections.
|
||||
- API access should go through the generated client and `@httpClientAdapter`, not raw `fetch`.
|
||||
- `src/api/httpClientAdapter.server.ts` handles SSR-side API calls by forwarding request headers/cookies and proxying frontend `/r/*` requests to `https://api.pipic.fun`.
|
||||
- `src/api/httpClientAdapter.client.ts` is the browser-side adapter.
|
||||
|
||||
- `@/` → `src/`
|
||||
- `@httpClientAdapter` → `src/api/httpClientAdapter.server.ts` (SSR) or `.client.ts` (browser)
|
||||
- `@liteMqtt` → `src/lib/liteMqtt.server.ts` (SSR) or `.ts` (browser)
|
||||
### Notable flows
|
||||
- `src/stores/auth.ts` initializes the logged-in user from `/me` and opens an MQTT connection after login.
|
||||
- `src/composables/useUploadQueue.ts` implements the custom upload queue:
|
||||
- 90MB chunks
|
||||
- max 3 parallel uploads
|
||||
- max 3 retries
|
||||
- max 5 queued items
|
||||
- Styling uses UnoCSS (`uno.config.ts`).
|
||||
|
||||
### State Management Pattern
|
||||
## Important notes
|
||||
|
||||
Uses **Pinia Colada** for server state with SSR hydration:
|
||||
- Queries are fetched server-side and serialized to `window.__APP_DATA__`
|
||||
- Client hydrates the query cache on startup via `hydrateQueryCache()`
|
||||
- Pinia state is similarly serialized and restored via `PiniaSharedState` plugin
|
||||
|
||||
### API Client Architecture
|
||||
|
||||
The API client (`src/api/client.ts`) is auto-generated from OpenAPI spec:
|
||||
- Uses `customFetch` adapter that differs between client/server
|
||||
- Server adapter (`httpClientAdapter.server.ts`): Forwards cookies via `hono/context-storage`, merges headers, calls `https://api.pipic.fun`
|
||||
- Client adapter (`httpClientAdapter.client.ts`): Standard fetch with `credentials: "include"`
|
||||
- API proxy route: `/r/*` paths proxy to `https://api.pipic.fun` via `apiProxyMiddleware`
|
||||
- Base API URL constant: `baseAPIURL = "https://api.pipic.fun"`
|
||||
|
||||
### Routing Structure
|
||||
|
||||
Routes are defined in `src/routes/index.ts` with three main layouts:
|
||||
1. **Public** (`/`): Landing page, terms, privacy
|
||||
2. **Auth** (`/login`, `/sign-up`, `/forgot`): Authentication pages (redirects if logged in)
|
||||
3. **Dashboard**: Protected routes requiring auth
|
||||
- `/overview` - Main dashboard
|
||||
- `/upload` - Video upload
|
||||
- `/video` - Video list
|
||||
- `/video/:id` - Video detail/edit
|
||||
- `/payments-and-plans` - Billing
|
||||
- `/notification`, `/profile` - User settings
|
||||
|
||||
Route meta supports `@unhead/vue` for SEO: `meta: { head: { title, meta: [...] } }`
|
||||
|
||||
### Styling System (UnoCSS)
|
||||
|
||||
Configuration in `uno.config.ts`:
|
||||
- **Presets**: Wind4 (Tailwind), Typography, Attributify, Bootstrap buttons
|
||||
- **Custom colors**: `primary` (#14a74b), `accent`, `secondary` (#fd7906), `success`, `info`, `warning`, `danger`
|
||||
- **Shortcuts**: `press-animated` for button press effects
|
||||
- **Transformers**: `transformerCompileClass` (prefix: `_`), `transformerVariantGroup`
|
||||
|
||||
Use `cn()` from `src/lib/utils.ts` for conditional class merging (clsx + tailwind-merge).
|
||||
|
||||
### Component Auto-Import
|
||||
|
||||
Components in `src/components/` are auto-imported via `unplugin-vue-components`:
|
||||
- PrimeVue components resolved via `PrimeVueResolver`
|
||||
- Vue/Pinia/Vue Router APIs auto-imported via `unplugin-auto-import`
|
||||
|
||||
### Auth Flow
|
||||
|
||||
- `useAuthStore` manages auth state with cookie-based sessions
|
||||
- `init()` called on every request to fetch current user via `/me` endpoint
|
||||
- `beforeEach` router guard redirects unauthenticated users from protected routes
|
||||
- MQTT client connects on user login for real-time notifications
|
||||
|
||||
### File Upload Architecture
|
||||
|
||||
Upload queue (`src/composables/useUploadQueue.ts`):
|
||||
- Supports both local files and remote URLs
|
||||
- Presigned POST URLs fetched from API
|
||||
- Parallel chunk upload for large files
|
||||
- Progress tracking with speed calculation
|
||||
- **Chunk configuration**: 90MB chunks, max 3 parallel uploads, max 3 retries
|
||||
- **Upload limits**: Max 5 items in queue
|
||||
- Uses `tmpfiles.org` API for chunk uploads, `/merge` endpoint for finalizing
|
||||
- Cancel support via XHR abort tracking
|
||||
|
||||
### Type Safety
|
||||
|
||||
- TypeScript strict mode enabled
|
||||
- `CloudflareBindings` interface for environment variables (generated via `cf-typegen`)
|
||||
- API types auto-generated from backend OpenAPI spec
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Cloudflare Worker bindings (configured in `wrangler.jsonc`):
|
||||
- No explicit secrets in code - use Wrangler secrets management
|
||||
- `compatibility_date`: "2025-08-03"
|
||||
- `compatibility_flags`: ["nodejs_compat"]
|
||||
|
||||
## Important File Locations
|
||||
|
||||
| Purpose | Path |
|
||||
|---------|------|
|
||||
| Server entry | `src/index.tsx` |
|
||||
| Client entry | `src/client.ts` |
|
||||
| App factory | `src/main.ts` |
|
||||
| Router config | `src/routes/index.ts` |
|
||||
| API client | `src/api/client.ts` |
|
||||
| Auth store | `src/stores/auth.ts` |
|
||||
| SSR plugin | `ssrPlugin.ts` |
|
||||
| UnoCSS config | `uno.config.ts` |
|
||||
| Wrangler config | `wrangler.jsonc` |
|
||||
| Vite config | `vite.config.ts` |
|
||||
|
||||
## Server Structure
|
||||
|
||||
Middleware and routes are organized in `src/server/`:
|
||||
|
||||
**Middlewares** (`src/server/middlewares/`):
|
||||
- `setup.ts` - Global middleware: `contextStorage`, CORS, mobile detection via `is-mobile`
|
||||
- `apiProxy.ts` - Proxies `/r/*` requests to external API
|
||||
|
||||
**Routes** (`src/server/routes/`):
|
||||
- `ssr.ts` - Handles SSR rendering and state serialization
|
||||
- `display.ts`, `merge.ts`, `manifest.ts`, `wellKnown.ts` - API endpoints
|
||||
|
||||
## Development Notes
|
||||
|
||||
- Always use `customFetch` from `@httpClientAdapter` for API calls, never raw fetch
|
||||
- The `honoContext` is provided to Vue app for accessing request context in components
|
||||
- MQTT client in `src/lib/liteMqtt.ts` (using `TinyMqttClient`) handles real-time notifications
|
||||
- Icons are custom Vue components in `src/components/icons/`
|
||||
- Upload indicator is a global component showing queue status
|
||||
- Root component uses error boundary wrapper: `withErrorBoundary(RouterView)` in `src/main.ts`
|
||||
- **Testing & Linting**: There are currently no automated test suites (like Vitest) or linting tools (like ESLint/Prettier) configured.
|
||||
|
||||
## Code Organization
|
||||
|
||||
### Component Structure
|
||||
|
||||
- Keep view components small and focused - extract logical sections into child components
|
||||
- Page views should compose child components, not contain all logic inline
|
||||
- Example: `src/routes/settings/Settings.vue` uses child components in `src/routes/settings/components/`
|
||||
- Components that exceed ~200 lines should be considered for refactoring
|
||||
- Use `components/` subfolder pattern for page-specific components: `src/routes/{feature}/components/`
|
||||
|
||||
### Icons
|
||||
|
||||
- **Use custom SVG icon components** from `src/components/icons/` for UI icons (e.g., `Home`, `Video`, `Bell`, `SettingsIcon`)
|
||||
- Custom icons are Vue components with `filled` prop for active/filled state
|
||||
- PrimeIcons (`pi pi-*` class) should **only** be used for:
|
||||
- Button icons in PrimeVue components (e.g., `icon="pi pi-check"`)
|
||||
- Dialog/action icons where no custom SVG exists
|
||||
- **Do NOT use** `<i class="pi pi-*">` for navigation icons, action buttons, or UI elements that have custom SVG equivalents
|
||||
- When adding new icons, create SVG components in `src/components/icons/` following the existing pattern (support `filled` prop)
|
||||
- Prefer the actual current code over older documentation when they conflict.
|
||||
- The previous version of this file contained stale route and dependency details; verify against `src/routes/index.ts` and `package.json` before assuming old pages or libraries still exist.
|
||||
- Any frontend change that affects API contracts should be checked against the backend repo (`../stream.api`) as well.
|
||||
|
||||
39
Dockerfile
Normal file
39
Dockerfile
Normal file
@@ -0,0 +1,39 @@
|
||||
# ---------- Builder stage ----------
|
||||
FROM oven/bun:1.3.10-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy lockfiles & package.json
|
||||
COPY package*.json ./
|
||||
COPY bun.lockb* ./
|
||||
COPY yarn.lock* ./
|
||||
COPY pnpm-lock.yaml* ./
|
||||
|
||||
# Install dependencies (cached)
|
||||
RUN --mount=type=cache,target=/root/.bun bun install
|
||||
|
||||
# Copy source
|
||||
COPY . .
|
||||
|
||||
# Build app (RSBuild output -> dist)
|
||||
RUN bun run build
|
||||
|
||||
|
||||
# ---------- Production stage ----------
|
||||
FROM oven/bun:1.3.10-alpine AS production
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy built files
|
||||
COPY --from=builder /app/dist ./dist
|
||||
|
||||
ENV NODE_ENV=production
|
||||
# Expose port
|
||||
EXPOSE 3000
|
||||
|
||||
# Optional health check
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||
CMD wget -qO- http://localhost:3000/ || exit 1
|
||||
|
||||
# Run Bun with fallback install (auto resolves missing deps)
|
||||
CMD [ "bun", "--bun", "dist" ]
|
||||
605
bun.lock
605
bun.lock
@@ -5,30 +5,43 @@
|
||||
"": {
|
||||
"name": "holistream",
|
||||
"dependencies": {
|
||||
"@pinia/colada": "^0.21.2",
|
||||
"@unhead/vue": "^2.1.2",
|
||||
"@vueuse/core": "^14.2.0",
|
||||
"@bufbuild/protobuf": "^2.11.0",
|
||||
"@grpc/grpc-js": "^1.14.3",
|
||||
"@hattip/adapter-node": "^0.0.49",
|
||||
"@hiogawa/tiny-rpc": "^0.2.3-pre.18",
|
||||
"@hiogawa/utils": "^1.7.0",
|
||||
"@hono/node-server": "^1.19.12",
|
||||
"@hono/zod-validator": "^0.7.6",
|
||||
"@pinia/colada": "^1.1.0",
|
||||
"@tanstack/vue-table": "^8.21.3",
|
||||
"@unhead/vue": "^2.1.12",
|
||||
"@vueuse/core": "^14.2.1",
|
||||
"aws4fetch": "^1.0.20",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"hono": "^4.11.7",
|
||||
"hono": "^4.12.9",
|
||||
"i18next": "^26.0.3",
|
||||
"i18next-http-backend": "^3.0.4",
|
||||
"i18next-vue": "^5.4.0",
|
||||
"is-mobile": "^5.0.0",
|
||||
"pinia": "^3.0.4",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"vue": "^3.5.27",
|
||||
"vue-router": "^5.0.2",
|
||||
"superjson": "^2.2.6",
|
||||
"tailwind-merge": "^3.5.0",
|
||||
"tweetnacl": "^1.0.3",
|
||||
"vue": "^3.5.31",
|
||||
"vue-router": "^5.0.4",
|
||||
"zod": "^4.3.6",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/vite-plugin": "^1.23.0",
|
||||
"@types/node": "^25.2.0",
|
||||
"@vitejs/plugin-vue": "^6.0.4",
|
||||
"@vitejs/plugin-vue-jsx": "^5.1.4",
|
||||
"unocss": "^66.6.0",
|
||||
"@types/bun": "^1.3.11",
|
||||
"@vitejs/plugin-vue": "^6.0.5",
|
||||
"@vitejs/plugin-vue-jsx": "^5.1.5",
|
||||
"estree-walker": "3.0.3",
|
||||
"unocss": "^66.6.7",
|
||||
"unplugin-auto-import": "^21.0.0",
|
||||
"unplugin-vue-components": "^31.0.0",
|
||||
"vite": "^7.3.1",
|
||||
"unplugin-vue-components": "^32.0.0",
|
||||
"vite": "^8.0.3",
|
||||
"vite-ssr-components": "^0.5.2",
|
||||
"wrangler": "^4.62.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -71,9 +84,9 @@
|
||||
|
||||
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
|
||||
|
||||
"@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="],
|
||||
"@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="],
|
||||
|
||||
"@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
|
||||
"@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
|
||||
|
||||
"@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w=="],
|
||||
|
||||
@@ -81,138 +94,48 @@
|
||||
|
||||
"@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw=="],
|
||||
|
||||
"@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="],
|
||||
|
||||
"@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
|
||||
|
||||
"@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="],
|
||||
|
||||
"@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
|
||||
|
||||
"@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.2", "", {}, "sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ=="],
|
||||
"@bufbuild/protobuf": ["@bufbuild/protobuf@2.11.0", "", {}, "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ=="],
|
||||
|
||||
"@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.14.0", "", { "peerDependencies": { "unenv": "2.0.0-rc.24", "workerd": "^1.20260218.0" }, "optionalPeers": ["workerd"] }, "sha512-XKAkWhi1nBdNsSEoNG9nkcbyvfUrSjSf+VYVPfOto3gLTZVc3F4g6RASCMh6IixBKCG2yDgZKQIHGKtjcnLnKg=="],
|
||||
"@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" } }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="],
|
||||
|
||||
"@cloudflare/vite-plugin": ["@cloudflare/vite-plugin@1.25.5", "", { "dependencies": { "@cloudflare/unenv-preset": "2.14.0", "miniflare": "4.20260302.0", "unenv": "2.0.0-rc.24", "wrangler": "4.68.1", "ws": "8.18.0" }, "peerDependencies": { "vite": "^6.1.0 || ^7.0.0" } }, "sha512-dWnJtp/4/m2XQ5Ssnxrh6rb+Jvlkd9pTZhX8MS5sNhdzoULB6vzPkdKaKnaLnYC97iL3j1I2m0gIr15QznKRjA=="],
|
||||
"@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="],
|
||||
|
||||
"@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20260302.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-cGtxPByeVrgoqxbmd8qs631wuGwf8yTm/FY44dEW4HdoXrb5jhlE4oWYHFafedkQCvGjY1Vbs3puAiKnuMxTXQ=="],
|
||||
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="],
|
||||
|
||||
"@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20260302.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-WRGqV6RNXM3xoQblJJw1EHKwx9exyhB18cdnToSCUFPObFhk3fzMLoQh7S+nUHUpto6aUrXPVj6R/4G3UPjCxw=="],
|
||||
"@grpc/grpc-js": ["@grpc/grpc-js@1.14.3", "", { "dependencies": { "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA=="],
|
||||
|
||||
"@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20260302.0", "", { "os": "linux", "cpu": "x64" }, "sha512-gG423mtUjrmlQT+W2+KisLc6qcGcBLR+QcK5x1gje3bu/dF3oNiYuqY7o58A+sQk6IB849UC4UyNclo1RhP2xw=="],
|
||||
"@grpc/proto-loader": ["@grpc/proto-loader@0.8.0", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.5.3", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ=="],
|
||||
|
||||
"@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20260302.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-7M25noGI4WlSBOhrIaY8xZrnn87OQKtJg9YWAO2EFqGjF1Su5QXGaLlQVF4fAKbqTywbHnI8BAuIsIlUSNkhCg=="],
|
||||
"@hattip/adapter-node": ["@hattip/adapter-node@0.0.49", "", { "dependencies": { "@hattip/core": "0.0.49", "@hattip/polyfills": "0.0.49", "@hattip/walk": "0.0.49" } }, "sha512-BE+Y8Q4U0YcH34FZUYU4DssGKOaZLbNL0zK57Z41UZp0m9kS79ZIolBmjjpPhTVpIlRY3Rs+uhXbVXKk7mUcJA=="],
|
||||
|
||||
"@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20260302.0", "", { "os": "win32", "cpu": "x64" }, "sha512-jK1L3ADkiWxFzlqZTq2iHW1Bd2Nzu1fmMWCGZw4sMZ2W1B2WCm2wHwO2SX/py4BgylyEN3wuF+5zagbkNKht9A=="],
|
||||
"@hattip/core": ["@hattip/core@0.0.49", "", {}, "sha512-3/ZJtC17cv8m6Sph8+nw4exUp9yhEf2Shi7HK6AHSUSBtaaQXZ9rJBVxTfZj3PGNOR/P49UBXOym/52WYKFTJQ=="],
|
||||
|
||||
"@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="],
|
||||
"@hattip/headers": ["@hattip/headers@0.0.49", "", { "dependencies": { "@hattip/core": "0.0.49" } }, "sha512-rrB2lEhTf0+MNVt5WdW184Ky706F1Ze9Aazn/R8c+/FMUYF9yjem2CgXp49csPt3dALsecrnAUOHFiV0LrrHXA=="],
|
||||
|
||||
"@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
|
||||
"@hattip/polyfills": ["@hattip/polyfills@0.0.49", "", { "dependencies": { "@hattip/core": "0.0.49", "@whatwg-node/fetch": "^0.9.22", "node-fetch-native": "^1.6.4" } }, "sha512-5g7W5s6Gq+HDxwULGFQ861yAnEx3yd9V8GDwS96HBZ1nM1u93vN+KTuwXvNsV7Z3FJmCrD/pgU8WakvchclYuA=="],
|
||||
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="],
|
||||
"@hattip/walk": ["@hattip/walk@0.0.49", "", { "dependencies": { "@hattip/headers": "0.0.49", "cac": "^6.7.14", "mime-types": "^2.1.35" }, "bin": { "hattip-walk": "cli.js" } }, "sha512-AgJgKLooZyQnzMfoFg5Mo/aHM+HGBC9ExpXIjNqGimYTRgNbL/K7X5EM1kR2JY90BNKk9lo6Usq1T/nWFdT7TQ=="],
|
||||
|
||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="],
|
||||
"@hiogawa/tiny-rpc": ["@hiogawa/tiny-rpc@0.2.3-pre.18", "", {}, "sha512-BiNHrutG9G9yV622QvkxZxF+PhkaH2Aspp4/X1KYTfnaQTcg4fFUTBWf5Kf533swon2SuVJwi6U6H1LQbhVOQQ=="],
|
||||
|
||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="],
|
||||
"@hiogawa/utils": ["@hiogawa/utils@1.7.0", "", {}, "sha512-ghiEFWBR1NENoHn+lSuW7liicTIzVPN+8Srm5UedCTw43gus0mlse6Wp2lz6GmbOXJ/CalMPp/0Tz2X8tajkAg=="],
|
||||
|
||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="],
|
||||
"@hono/node-server": ["@hono/node-server@1.19.12", "", { "peerDependencies": { "hono": "^4" } }, "sha512-txsUW4SQ1iilgE0l9/e9VQWmELXifEFvmdA1j6WFh/aFPj99hIntrSsq/if0UWyGVkmrRPKA1wCeP+UCr1B9Uw=="],
|
||||
|
||||
"@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=="],
|
||||
"@hono/zod-validator": ["@hono/zod-validator@0.7.6", "", { "peerDependencies": { "hono": ">=3.9.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Io1B6d011Gj1KknV4rXYz4le5+5EubcWEU/speUjuw9XMMIaP3n78yXLhjd2A3PXaXaUwEAluOiAyLqhBEJgsw=="],
|
||||
|
||||
"@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="],
|
||||
|
||||
"@iconify/utils": ["@iconify/utils@3.1.0", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "mlly": "^1.8.0" } }, "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw=="],
|
||||
|
||||
"@img/colour": ["@img/colour@1.0.0", "", {}, "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw=="],
|
||||
|
||||
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="],
|
||||
|
||||
"@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="],
|
||||
|
||||
"@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="],
|
||||
|
||||
"@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="],
|
||||
|
||||
"@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="],
|
||||
|
||||
"@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="],
|
||||
|
||||
"@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="],
|
||||
|
||||
"@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="],
|
||||
|
||||
"@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="],
|
||||
|
||||
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="],
|
||||
|
||||
"@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="],
|
||||
|
||||
"@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="],
|
||||
|
||||
"@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="],
|
||||
|
||||
"@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="],
|
||||
|
||||
"@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="],
|
||||
|
||||
"@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="],
|
||||
|
||||
"@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="],
|
||||
|
||||
"@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="],
|
||||
|
||||
"@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="],
|
||||
|
||||
"@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="],
|
||||
|
||||
"@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="],
|
||||
|
||||
"@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="],
|
||||
|
||||
"@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="],
|
||||
|
||||
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="],
|
||||
|
||||
"@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=="],
|
||||
@@ -223,127 +146,173 @@
|
||||
|
||||
"@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=="],
|
||||
|
||||
"@pinia/colada": ["@pinia/colada@0.21.6", "", { "peerDependencies": { "pinia": "^2.2.6 || ^3.0.0", "vue": "^3.5.17" } }, "sha512-DppfAYky3Uavlpdx2iZHgd/+ZVPyBGTR+x+kFfAUz8h9l1DIQgf2cw/QZg0RZ4GAUNnKf6Ue6FzfWttwqhZXUQ=="],
|
||||
"@js-sdsl/ordered-map": ["@js-sdsl/ordered-map@4.4.2", "", {}, "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw=="],
|
||||
|
||||
"@kamilkisiela/fast-url-parser": ["@kamilkisiela/fast-url-parser@1.1.4", "", {}, "sha512-gbkePEBupNydxCelHCESvFSFM8XPh1Zs/OAVRW/rKpEqPAl5PbOM90Si8mv9bvnR53uPD2s/FiRxdvSejpRJew=="],
|
||||
|
||||
"@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-parser/binding-android-arm-eabi": ["@oxc-parser/binding-android-arm-eabi@0.115.0", "", { "os": "android", "cpu": "arm" }, "sha512-VoB2rhgoqgYf64d6Qs5emONQW8ASiTc0xp+aUE4JUhxjX+0pE3gblTYDO0upcN5vt9UlBNmUhAwfSifkfre7nw=="],
|
||||
|
||||
"@oxc-parser/binding-android-arm64": ["@oxc-parser/binding-android-arm64@0.115.0", "", { "os": "android", "cpu": "arm64" }, "sha512-lWRX75u+gqfB4TF3pWCHuvhaeneAmRl2b2qNBcl4S6yJ0HtnT4VXOMEZrq747i4Zby1ZTxj6mtOe678Bg8gRLw=="],
|
||||
|
||||
"@oxc-parser/binding-darwin-arm64": ["@oxc-parser/binding-darwin-arm64@0.115.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ii/oOZjfGY1aszXTy29Z5DRyCEnBOrAXDVCvfdfXFQsOZlbbOa7NMHD7D+06YFe5qdxfmbWAYv4yn6QJi/0d2g=="],
|
||||
|
||||
"@oxc-parser/binding-darwin-x64": ["@oxc-parser/binding-darwin-x64@0.115.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-R/sW/p8l77wglbjpMcF+h/3rWbp9zk1mRP3U14mxTYIC2k3m+aLBpXXgk2zksqf9qKk5mcc4GIYsuCn9l8TgDg=="],
|
||||
|
||||
"@oxc-parser/binding-freebsd-x64": ["@oxc-parser/binding-freebsd-x64@0.115.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-CSJ5ldNm9wIGGkhaIJeGmxRMZbgxThRN+X1ufYQQUNi5jZDV/U3C2QDMywpP93fczNBj961hXtcUPO/oVGq4Pw=="],
|
||||
|
||||
"@oxc-parser/binding-linux-arm-gnueabihf": ["@oxc-parser/binding-linux-arm-gnueabihf@0.115.0", "", { "os": "linux", "cpu": "arm" }, "sha512-uWFwssE5dHfQ8lH+ktrsD9JA49+Qa0gtxZHUs62z1e91NgGz6O7jefHGI6aygNyKNS45pnnBSDSP/zV977MsOQ=="],
|
||||
|
||||
"@oxc-parser/binding-linux-arm-musleabihf": ["@oxc-parser/binding-linux-arm-musleabihf@0.115.0", "", { "os": "linux", "cpu": "arm" }, "sha512-fZbqt8y/sKQ+v6bBCuv/mYYFoC0+fZI3mGDDEemmDOhT78+aUs2+4ZMdbd2btlXmnLaScl37r8IRbhnok5Ka9w=="],
|
||||
|
||||
"@oxc-parser/binding-linux-arm64-gnu": ["@oxc-parser/binding-linux-arm64-gnu@0.115.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-1ej/MjuTY9tJEunU/hUPIFmgH5PqgMQoRjNOvOkibtJ3Zqlw/+Lc+HGHDNET8sjbgIkWzdhX+p4J96A5CPdbag=="],
|
||||
|
||||
"@oxc-parser/binding-linux-arm64-musl": ["@oxc-parser/binding-linux-arm64-musl@0.115.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-HjsZbJPH9mMd4swJRywVMsDZsJX0hyKb1iNHo5ijRl5yhtbO3lj7ImSrrL1oZ1VEg0te4iKmDGGz/6YPLd1G8w=="],
|
||||
|
||||
"@oxc-parser/binding-linux-ppc64-gnu": ["@oxc-parser/binding-linux-ppc64-gnu@0.115.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-zhhePoBrd7kQx3oClX/W6NldsuCbuMqaN9rRsY+6/WoorAb4j490PG/FjqgAXscWp2uSW2WV9L+ksn0wHrvsrg=="],
|
||||
|
||||
"@oxc-parser/binding-linux-riscv64-gnu": ["@oxc-parser/binding-linux-riscv64-gnu@0.115.0", "", { "os": "linux", "cpu": "none" }, "sha512-t/IRojvUE9XrKu+/H1b8YINug+7Q6FLls5rsm2lxB5mnS8GN/eYAYrPgHkcg9/1SueRDSzGpDYu3lGWTObk1zw=="],
|
||||
|
||||
"@oxc-parser/binding-linux-riscv64-musl": ["@oxc-parser/binding-linux-riscv64-musl@0.115.0", "", { "os": "linux", "cpu": "none" }, "sha512-79jBHSSh/YpQRAmvYoaCfpyToRbJ/HBrdB7hxK2ku2JMehjopTVo+xMJss/RV7/ZYqeezgjvKDQzapJbgcjVZA=="],
|
||||
|
||||
"@oxc-parser/binding-linux-s390x-gnu": ["@oxc-parser/binding-linux-s390x-gnu@0.115.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-nA1TpxkhNTIOMMyiSSsa7XIVJVoOU/SsVrHIz3gHvWweB5PHCQfO7w+Lb2EP0lBWokv7HtA/KbF7aLDoXzmuMw=="],
|
||||
|
||||
"@oxc-parser/binding-linux-x64-gnu": ["@oxc-parser/binding-linux-x64-gnu@0.115.0", "", { "os": "linux", "cpu": "x64" }, "sha512-9iVX789DoC3SaOOG+X6NcF/tVChgLp2vcHffzOC2/Z1JTPlz6bMG2ogvcW6/9s0BG2qvhNQImd+gbWYeQbOwVw=="],
|
||||
|
||||
"@oxc-parser/binding-linux-x64-musl": ["@oxc-parser/binding-linux-x64-musl@0.115.0", "", { "os": "linux", "cpu": "x64" }, "sha512-RmQmk+mjCB0nMNfEYhaCxwofLo1Z95ebHw1AGvRiWGCd4zhCNOyskgCbMogIcQzSB3SuEKWgkssyaiQYVAA4hQ=="],
|
||||
|
||||
"@oxc-parser/binding-openharmony-arm64": ["@oxc-parser/binding-openharmony-arm64@0.115.0", "", { "os": "none", "cpu": "arm64" }, "sha512-viigraWWQhhDvX5aGq+wrQq58k00Xq3MHz/0R4AFMxGlZ8ogNonpEfNc73Q5Ly87Z6sU9BvxEdG0dnYTfVnmew=="],
|
||||
|
||||
"@oxc-parser/binding-wasm32-wasi": ["@oxc-parser/binding-wasm32-wasi@0.115.0", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-IzGCrMwXhpb4kTXy/8lnqqqwjI7eOvy+r9AhVw+hsr8t1ecBBEHprcNy0aKatFHN6hsX7UMHHQmBAQjVvL/p1A=="],
|
||||
|
||||
"@oxc-parser/binding-win32-arm64-msvc": ["@oxc-parser/binding-win32-arm64-msvc@0.115.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-/ym+Absk/TLFvbhh3se9XYuI1D7BrUVHw4RaG/2dmWKgBenrZHaJsgnRb7NJtaOyjEOLIPtULx1wDdVL0SX2eg=="],
|
||||
|
||||
"@oxc-parser/binding-win32-ia32-msvc": ["@oxc-parser/binding-win32-ia32-msvc@0.115.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-AQSZjIR+b+Te7uaO/hGTMjT8/oxlYrvKrOTi4KTHF/O6osjHEatUQ3y6ZW2+8+lJxy20zIcGz6iQFmFq/qDKkg=="],
|
||||
|
||||
"@oxc-parser/binding-win32-x64-msvc": ["@oxc-parser/binding-win32-x64-msvc@0.115.0", "", { "os": "win32", "cpu": "x64" }, "sha512-oxUl82N+fIO9jIaXPph8SPPHQXrA08BHokBBJW8ct9F/x6o6bZE6eUAhUtWajbtvFhL8UYcCWRMba+kww6MBlA=="],
|
||||
|
||||
"@oxc-project/types": ["@oxc-project/types@0.122.0", "", {}, "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA=="],
|
||||
|
||||
"@pinia/colada": ["@pinia/colada@1.1.0", "", { "peerDependencies": { "pinia": "^2.2.6 || ^3.0.0", "vue": "^3.5.17" } }, "sha512-GoTOlaDuQuF+9Lj5MbY2oOOZeJSP/I0U9/+Ugo5nfCymlWSyjFFjE7QcHageNL7tQr+tfepb13ks8+qO6uSV/A=="],
|
||||
|
||||
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
|
||||
|
||||
"@poppinss/colors": ["@poppinss/colors@4.1.6", "", { "dependencies": { "kleur": "^4.1.5" } }, "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg=="],
|
||||
"@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="],
|
||||
|
||||
"@poppinss/dumper": ["@poppinss/dumper@0.6.5", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@sindresorhus/is": "^7.0.2", "supports-color": "^10.0.0" } }, "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw=="],
|
||||
"@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="],
|
||||
|
||||
"@poppinss/exception": ["@poppinss/exception@1.2.3", "", {}, "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw=="],
|
||||
"@protobufjs/codegen": ["@protobufjs/codegen@2.0.4", "", {}, "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="],
|
||||
|
||||
"@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.0", "", {}, "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="],
|
||||
|
||||
"@protobufjs/fetch": ["@protobufjs/fetch@1.1.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" } }, "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ=="],
|
||||
|
||||
"@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="],
|
||||
|
||||
"@protobufjs/inquire": ["@protobufjs/inquire@1.1.0", "", {}, "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="],
|
||||
|
||||
"@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="],
|
||||
|
||||
"@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="],
|
||||
|
||||
"@protobufjs/utf8": ["@protobufjs/utf8@1.1.0", "", {}, "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="],
|
||||
|
||||
"@quansync/fs": ["@quansync/fs@1.0.0", "", { "dependencies": { "quansync": "^1.0.0" } }, "sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ=="],
|
||||
|
||||
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.12", "", { "os": "android", "cpu": "arm64" }, "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA=="],
|
||||
|
||||
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg=="],
|
||||
|
||||
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw=="],
|
||||
|
||||
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q=="],
|
||||
|
||||
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12", "", { "os": "linux", "cpu": "arm" }, "sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q=="],
|
||||
|
||||
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg=="],
|
||||
|
||||
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw=="],
|
||||
|
||||
"@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g=="],
|
||||
|
||||
"@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og=="],
|
||||
|
||||
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.12", "", { "os": "linux", "cpu": "x64" }, "sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg=="],
|
||||
|
||||
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.12", "", { "os": "linux", "cpu": "x64" }, "sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig=="],
|
||||
|
||||
"@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.12", "", { "os": "none", "cpu": "arm64" }, "sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA=="],
|
||||
|
||||
"@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.12", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg=="],
|
||||
|
||||
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q=="],
|
||||
|
||||
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.12", "", { "os": "win32", "cpu": "x64" }, "sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw=="],
|
||||
|
||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.2", "", {}, "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw=="],
|
||||
|
||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.59.0", "", { "os": "android", "cpu": "arm" }, "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg=="],
|
||||
"@tanstack/table-core": ["@tanstack/table-core@8.21.3", "", {}, "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg=="],
|
||||
|
||||
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.59.0", "", { "os": "android", "cpu": "arm64" }, "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q=="],
|
||||
"@tanstack/vue-table": ["@tanstack/vue-table@8.21.3", "", { "dependencies": { "@tanstack/table-core": "8.21.3" }, "peerDependencies": { "vue": ">=3.2" } }, "sha512-rusRyd77c5tDPloPskctMyPLFEQUeBzxdQ+2Eow4F7gDPlPOB1UnnhzfpdvqZ8ZyX2rRNGmqNnQWm87OI2OQPw=="],
|
||||
|
||||
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.59.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg=="],
|
||||
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
"@sindresorhus/is": ["@sindresorhus/is@7.2.0", "", {}, "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw=="],
|
||||
|
||||
"@speed-highlight/core": ["@speed-highlight/core@1.2.14", "", {}, "sha512-G4ewlBNhUtlLvrJTb88d2mdy2KRijzs4UhnlrOSRT4bmjh/IqNElZa3zkrZ+TC47TwtlDWzVLFADljF1Ijp5hA=="],
|
||||
"@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||
|
||||
"@types/node": ["@types/node@25.3.1", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-hj9YIJimBCipHVfHKRMnvmHg+wfhKc0o4mTtXh9pKBjC8TLJzz0nzGmLi5UJsYAUgSvXFHgb0V2oY10DUFtImw=="],
|
||||
"@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="],
|
||||
|
||||
"@types/web-bluetooth": ["@types/web-bluetooth@0.0.21", "", {}, "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA=="],
|
||||
|
||||
"@unhead/vue": ["@unhead/vue@2.1.9", "", { "dependencies": { "hookable": "^6.0.1", "unhead": "2.1.9" }, "peerDependencies": { "vue": ">=3.5.18" } }, "sha512-7SqqDEn5zFID1PnEdjLCLa/kOhoAlzol0JdYfVr2Ejek+H4ON4s8iyExv2QQ8bReMosbXQ/Bw41j2CF1NUuGSA=="],
|
||||
"@unhead/vue": ["@unhead/vue@2.1.12", "", { "dependencies": { "hookable": "^6.0.1", "unhead": "2.1.12" }, "peerDependencies": { "vue": ">=3.5.18" } }, "sha512-zEWqg0nZM8acpuTZE40wkeUl8AhIe0tU0OkilVi1D4fmVjACrwoh5HP6aNqJ8kUnKsoy6D+R3Vi/O+fmdNGO7g=="],
|
||||
|
||||
"@unocss/cli": ["@unocss/cli@66.6.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "@unocss/config": "66.6.2", "@unocss/core": "66.6.2", "@unocss/preset-wind3": "66.6.2", "@unocss/preset-wind4": "66.6.2", "@unocss/transformer-directives": "66.6.2", "cac": "^6.7.14", "chokidar": "^5.0.0", "colorette": "^2.0.20", "consola": "^3.4.2", "magic-string": "^0.30.21", "pathe": "^2.0.3", "perfect-debounce": "^2.1.0", "tinyglobby": "^0.2.15", "unplugin-utils": "^0.3.1" }, "bin": { "unocss": "bin/unocss.mjs" } }, "sha512-N7nKnOJ/36FRs3PE7+CFbzg7UBhIsucYYAK5xjJScX0H2q8O6rODaNM5uvc77Qh4q+y1S/Bt5ArOwIewzdpP4w=="],
|
||||
"@unocss/cli": ["@unocss/cli@66.6.7", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "@unocss/config": "66.6.7", "@unocss/core": "66.6.7", "@unocss/preset-wind3": "66.6.7", "@unocss/preset-wind4": "66.6.7", "@unocss/transformer-directives": "66.6.7", "cac": "^6.7.14", "chokidar": "^5.0.0", "colorette": "^2.0.20", "consola": "^3.4.2", "magic-string": "^0.30.21", "pathe": "^2.0.3", "perfect-debounce": "^2.1.0", "tinyglobby": "^0.2.15", "unplugin-utils": "^0.3.1" }, "bin": { "unocss": "bin/unocss.mjs" } }, "sha512-m/yW5HMVyxfAOeyO4OyA4JB9dY+/gTsk25ucI8xVCFVDEENPEGr+vEqTDOA+vfe6pdURtyDYS7OrhikIRU1WNA=="],
|
||||
|
||||
"@unocss/config": ["@unocss/config@66.6.2", "", { "dependencies": { "@unocss/core": "66.6.2", "colorette": "^2.0.20", "consola": "^3.4.2", "unconfig": "^7.5.0" } }, "sha512-qny2bRW1OA+MZbWShVZdBg6fJundm1LqQwCxJnIpeK3McpPHS3pnHBiwD1wfZHY2z5Pe+XgZOZkozNmG/eyyqg=="],
|
||||
"@unocss/config": ["@unocss/config@66.6.7", "", { "dependencies": { "@unocss/core": "66.6.7", "colorette": "^2.0.20", "consola": "^3.4.2", "unconfig": "^7.5.0" } }, "sha512-1uleyRLyJc6PNNc2L3hEaKL89zXwvQAtP36oFySgL47RAxZHPZ4vfqFpbwR0eEN4iSqTS24ZFr7CTRWCaEGjzQ=="],
|
||||
|
||||
"@unocss/core": ["@unocss/core@66.6.2", "", {}, "sha512-IOvN1BLRP0VTjjS5afSxmXhvKRDko2Shisp8spU+A9qiH1tXEFP3phyVevm/SuGwBHO1lC+SJ451/4oFkCAwJA=="],
|
||||
"@unocss/core": ["@unocss/core@66.6.7", "", {}, "sha512-Q8456iWFtdwrUNYKVOQY8ygRggjZOVtLc6Jc8KIkxig7OiNlUWOgXJTfCh4I8g6jBYzC5eHaHFDLgJOmOrxBsg=="],
|
||||
|
||||
"@unocss/extractor-arbitrary-variants": ["@unocss/extractor-arbitrary-variants@66.6.2", "", { "dependencies": { "@unocss/core": "66.6.2" } }, "sha512-D2tK/8QClrVViSuoH5eLjXwlVOK1UgXx7ukz/D260+R6vhCmjv97RXPouZkq40sxGzfxzaQZUyPEjXLjtnO3bw=="],
|
||||
"@unocss/extractor-arbitrary-variants": ["@unocss/extractor-arbitrary-variants@66.6.7", "", { "dependencies": { "@unocss/core": "66.6.7" } }, "sha512-PQiBHK0yUJ0BR+3GYnTPU6va6HVSRPV+O+s1zZmt23TWbyIeucoKCNR47TDtv+Z1xuksY8krIjtDYtufdrVWKw=="],
|
||||
|
||||
"@unocss/inspector": ["@unocss/inspector@66.6.2", "", { "dependencies": { "@unocss/core": "66.6.2", "@unocss/rule-utils": "66.6.2", "colorette": "^2.0.20", "gzip-size": "^6.0.0", "sirv": "^3.0.2" } }, "sha512-q0kktb01dXeeXyNnNwYM1SkSHxrEOQhCZ/YQ5aCdC7BWNGF4yZMK0YrJXmGUTEHN4RhEPLN/rAIsDBsKcoFaAQ=="],
|
||||
"@unocss/inspector": ["@unocss/inspector@66.6.7", "", { "dependencies": { "@unocss/core": "66.6.7", "@unocss/rule-utils": "66.6.7", "colorette": "^2.0.20", "gzip-size": "^6.0.0", "sirv": "^3.0.2" } }, "sha512-4lA70A/wy9dfSDm7rJ5Uq5fKz+/Szm2rUcHjdbLCVNEc6vv2YXeI7aFvP5qDjTp4ClBSF2AMPnF1mtoMQOfDvA=="],
|
||||
|
||||
"@unocss/preset-attributify": ["@unocss/preset-attributify@66.6.2", "", { "dependencies": { "@unocss/core": "66.6.2" } }, "sha512-pRry38qO1kJvj5/cekbDk0QLosty+UFQ3fhNiph88D//jkT5tsUCn77nB/RTSe7oTqw/FqNwxPgbGz/wfNWqZg=="],
|
||||
"@unocss/preset-attributify": ["@unocss/preset-attributify@66.6.7", "", { "dependencies": { "@unocss/core": "66.6.7" } }, "sha512-thtoLQb53+Acy2QJYT6n+YhgNJ5ilhS8k9bqi+UzflbsuK4TJqOuQQjC9fRkULP5QjtNxgqN3d5Up7ms8tBPDA=="],
|
||||
|
||||
"@unocss/preset-icons": ["@unocss/preset-icons@66.6.2", "", { "dependencies": { "@iconify/utils": "^3.1.0", "@unocss/core": "66.6.2", "ofetch": "^1.5.1" } }, "sha512-FjhxvYX+21HefYdMIxJCq8C9v/K7fSlO1DMqDQgtrCp0/WvHyFncHILLOwp064M7m3AqzOVJx7Vw/zCvKy0Jrg=="],
|
||||
"@unocss/preset-icons": ["@unocss/preset-icons@66.6.7", "", { "dependencies": { "@iconify/utils": "^3.1.0", "@unocss/core": "66.6.7", "ofetch": "^1.5.1" } }, "sha512-mGAOyI/qz1pZUV1BcOtWAMm5czdFCjhFCYcDk0KY+Jw37pKRVSQRFeh4gpHuYKmehGv36caLyVrWXpTAwRBdFQ=="],
|
||||
|
||||
"@unocss/preset-mini": ["@unocss/preset-mini@66.6.2", "", { "dependencies": { "@unocss/core": "66.6.2", "@unocss/extractor-arbitrary-variants": "66.6.2", "@unocss/rule-utils": "66.6.2" } }, "sha512-mybpiAq9htF7PWPH1Mnb4y7hrxVwpsBg8VfbjSglY3SfLca8RrJtvBT+DVh7YUDRiYsZGfihRWkfD0AN68gkcA=="],
|
||||
"@unocss/preset-mini": ["@unocss/preset-mini@66.6.7", "", { "dependencies": { "@unocss/core": "66.6.7", "@unocss/extractor-arbitrary-variants": "66.6.7", "@unocss/rule-utils": "66.6.7" } }, "sha512-tf0mqiSEhPQ49WZOqjNhxlbZbNakiBLzCoxfLSzqfIGglOPYShP8mxsdp9Jv0n+Ntn0rHcBiX5KTLfax1/Bd9g=="],
|
||||
|
||||
"@unocss/preset-tagify": ["@unocss/preset-tagify@66.6.2", "", { "dependencies": { "@unocss/core": "66.6.2" } }, "sha512-ybb45So2x87P3bssLRp1uIS+VHAeNSecwkHqiv93PnuBDJ38/9XlqWF98uga2MEfNM3zvMj9plX9MauidxiPrw=="],
|
||||
"@unocss/preset-tagify": ["@unocss/preset-tagify@66.6.7", "", { "dependencies": { "@unocss/core": "66.6.7" } }, "sha512-0WeQf+Dx9Ztv3aewkBKEnAfOauSjvWBlfkpsgLpXcCkyGMnCqq87UrAq3+b76TDJvQc8i2ADlvVGK7V1z0JZQg=="],
|
||||
|
||||
"@unocss/preset-typography": ["@unocss/preset-typography@66.6.2", "", { "dependencies": { "@unocss/core": "66.6.2", "@unocss/rule-utils": "66.6.2" } }, "sha512-1f/ZfeuLQOnO48mRz1+6UdoJxa13ZYcamaLz7ft96n7D1eWvkOUAC/AUUke/kbHh3vvqwRVimC9OpdXxdGFQAQ=="],
|
||||
"@unocss/preset-typography": ["@unocss/preset-typography@66.6.7", "", { "dependencies": { "@unocss/core": "66.6.7", "@unocss/rule-utils": "66.6.7" } }, "sha512-RA7MwPDD5N9xGrbWnguVm5tP+F4/n/9X1rJsq2nBjvvK2dbtIRJZjRFM1vBDsR0GIhtvbHMoTchZaSZed5I+Hw=="],
|
||||
|
||||
"@unocss/preset-uno": ["@unocss/preset-uno@66.6.2", "", { "dependencies": { "@unocss/core": "66.6.2", "@unocss/preset-wind3": "66.6.2" } }, "sha512-Wy3V25ZF29OmVHJk5ghP6HCCRNBJXm0t+bKLKJJknOjD+/D51DZbUsDqZBtTpVtgi/SOPDbw7cX3lY2oqt4Hnw=="],
|
||||
"@unocss/preset-uno": ["@unocss/preset-uno@66.6.7", "", { "dependencies": { "@unocss/core": "66.6.7", "@unocss/preset-wind3": "66.6.7" } }, "sha512-imGCe6Yv2XgrJxP77gV8WZCz0xL99MsGov5rYn64lh2/tcsHF2rUIhTj/Urgxt0kwk8rLFtGbR1JuwPMNL5EDw=="],
|
||||
|
||||
"@unocss/preset-web-fonts": ["@unocss/preset-web-fonts@66.6.2", "", { "dependencies": { "@unocss/core": "66.6.2", "ofetch": "^1.5.1" } }, "sha512-0ckqiE8HkhETeghhxCXVGf96sNPhgBsB5q32iAuMM0HFR4x+ANiLqyfKrm/iqxKUw6rVO4+ItTV0RUWKcZvkXg=="],
|
||||
"@unocss/preset-web-fonts": ["@unocss/preset-web-fonts@66.6.7", "", { "dependencies": { "@unocss/core": "66.6.7", "ofetch": "^1.5.1" } }, "sha512-GLjUoSL/kYt1Yw2zpzixKnxvpHgLHAg0JXiPglct4PZ9YmUzCPbvJ/vVn+0AnB8Fxr29Z8NAFSNoX625ZaRonQ=="],
|
||||
|
||||
"@unocss/preset-wind": ["@unocss/preset-wind@66.6.2", "", { "dependencies": { "@unocss/core": "66.6.2", "@unocss/preset-wind3": "66.6.2" } }, "sha512-G0H4baUizmTByEowqGuYbKpU2TTisDhZ9W7hrIpYFbRkFv0i1kN2mIxCwj/FLmdY/6x8iSRJ7rO8Nez63YYhnw=="],
|
||||
"@unocss/preset-wind": ["@unocss/preset-wind@66.6.7", "", { "dependencies": { "@unocss/core": "66.6.7", "@unocss/preset-wind3": "66.6.7" } }, "sha512-jxtAN96jljd+KglbhPv6Y/ujceI5rVdrLQimj4KUTPoYBPEiWadzsGKN3o8Q07hlPRg+hBlO0r4tGSUWl+/EZQ=="],
|
||||
|
||||
"@unocss/preset-wind3": ["@unocss/preset-wind3@66.6.2", "", { "dependencies": { "@unocss/core": "66.6.2", "@unocss/preset-mini": "66.6.2", "@unocss/rule-utils": "66.6.2" } }, "sha512-UqdU2Obx3wXid9xeBHGY1MWxedXa43MGuP5Z2FA9modcXptReux4Zhy764SeQwx6acOUEql2/CTvOBwelZzheQ=="],
|
||||
"@unocss/preset-wind3": ["@unocss/preset-wind3@66.6.7", "", { "dependencies": { "@unocss/core": "66.6.7", "@unocss/preset-mini": "66.6.7", "@unocss/rule-utils": "66.6.7" } }, "sha512-PKyqeRzlIMd3Irdt6fCKMm73zgwweiXESk5edUK8dVWndvPIcZCOqrEq7yg6Pr/Q8tHdq26viYSkVY3a3t8RSg=="],
|
||||
|
||||
"@unocss/preset-wind4": ["@unocss/preset-wind4@66.6.2", "", { "dependencies": { "@unocss/core": "66.6.2", "@unocss/extractor-arbitrary-variants": "66.6.2", "@unocss/rule-utils": "66.6.2" } }, "sha512-XU+4NN9QIMefawDB9FqOeKONXeGDUJQuQgOeBcpbV/jwOYtyqRrHiqQg++fy1hRbluM+S+KqwRHYjvje8zCTow=="],
|
||||
"@unocss/preset-wind4": ["@unocss/preset-wind4@66.6.7", "", { "dependencies": { "@unocss/core": "66.6.7", "@unocss/extractor-arbitrary-variants": "66.6.7", "@unocss/rule-utils": "66.6.7" } }, "sha512-9grhWeBsFzpv8iER9AFATRaxLyXMCwGQ5HzeI4XZh2ZZ9O6vC7nYfGhns4/I+F/RpFglzU1bjqMWRS/DS8OpGQ=="],
|
||||
|
||||
"@unocss/rule-utils": ["@unocss/rule-utils@66.6.2", "", { "dependencies": { "@unocss/core": "^66.6.2", "magic-string": "^0.30.21" } }, "sha512-cygfCtkeMrqMM6si1cnyOF16sS7M2gCAqgmZybAhGV7tmH7V8Izn52JZiZIrxVRNMz9dWMVWerHEI9nLbFdbrg=="],
|
||||
"@unocss/rule-utils": ["@unocss/rule-utils@66.6.7", "", { "dependencies": { "@unocss/core": "^66.6.7", "magic-string": "^0.30.21" } }, "sha512-4PT/s8yKIShSqP9XPSw4EjbZopcu3wlIB9i3kbGbzQwF91H+0Yy10guK3kHDGtkmWVN6Np6VvaGIj2UcbmaivA=="],
|
||||
|
||||
"@unocss/transformer-attributify-jsx": ["@unocss/transformer-attributify-jsx@66.6.2", "", { "dependencies": { "@babel/parser": "7.27.7", "@babel/traverse": "7.27.7", "@unocss/core": "66.6.2" } }, "sha512-WiAEdEowGjQWu1ayhkGGBNGyw3mZLzZ+V5o3zx5U2GPuqvP67YIUfvY+/gTkCnd4+A8unkb+a1VeVgr4cHUkQw=="],
|
||||
"@unocss/transformer-attributify-jsx": ["@unocss/transformer-attributify-jsx@66.6.7", "", { "dependencies": { "@unocss/core": "66.6.7", "oxc-parser": "^0.115.0", "oxc-walker": "^0.7.0" } }, "sha512-r5bsnaPVe4iySLK5G5rA/QPSKmpPjYT9lixEv+KElvZcqZ+cPpkGoo+E+rnTcapu9KDMOVJItH/4Zy9m4AQ1ZQ=="],
|
||||
|
||||
"@unocss/transformer-compile-class": ["@unocss/transformer-compile-class@66.6.2", "", { "dependencies": { "@unocss/core": "66.6.2" } }, "sha512-L0yaQAmvWkm6LVLXMviqhHIi4c7WQpZFBgJF8jfsALyHihh8K9U9OrRJ81zfLH3Ltw5ZbGzoDE8m/2bB6aRhyw=="],
|
||||
"@unocss/transformer-compile-class": ["@unocss/transformer-compile-class@66.6.7", "", { "dependencies": { "@unocss/core": "66.6.7" } }, "sha512-4uz4jCyq8VUaSPveXhelUWUNaTnetPFvEmXzmbYJ5BygAlUlipNynffUlUusDQmBBRrfZhJNB5J1Zif2Q6oUiA=="],
|
||||
|
||||
"@unocss/transformer-directives": ["@unocss/transformer-directives@66.6.2", "", { "dependencies": { "@unocss/core": "66.6.2", "@unocss/rule-utils": "66.6.2", "css-tree": "^3.1.0" } }, "sha512-gjLDLItTUJ4CV8K2AA0cw381a7rJ3U4kCHQmZmN3+956o2R7cEHSLyEczmMy04Mg2JBomrjIZjo+L66z5rvblQ=="],
|
||||
"@unocss/transformer-directives": ["@unocss/transformer-directives@66.6.7", "", { "dependencies": { "@unocss/core": "66.6.7", "@unocss/rule-utils": "66.6.7", "css-tree": "^3.1.0" } }, "sha512-z3gi8/cD2P0I+c6jOPZUtsPXknHwVNlMIitSh7LhyM6W3EqbqvDcYH2gFeGhdhoYcN2r5OpTBujq34iz4IdUxA=="],
|
||||
|
||||
"@unocss/transformer-variant-group": ["@unocss/transformer-variant-group@66.6.2", "", { "dependencies": { "@unocss/core": "66.6.2" } }, "sha512-Uoo6xthOHJ36NdN4b7s/Y7R3fZOf4JYgKzuldHEyHAo0LL204Ss+Ah0+TEt4v72aq+Z86vrLJPyYCeGNKdr8cA=="],
|
||||
"@unocss/transformer-variant-group": ["@unocss/transformer-variant-group@66.6.7", "", { "dependencies": { "@unocss/core": "66.6.7" } }, "sha512-XouJuQCjYJpvR3sY4QDXnGXxtyJ4qgWFG+S9bAB01TTslhQLvNPE9o2+4gZlltnJLqxiPQWuLeJA1KdPD6ciww=="],
|
||||
|
||||
"@unocss/vite": ["@unocss/vite@66.6.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "@unocss/config": "66.6.2", "@unocss/core": "66.6.2", "@unocss/inspector": "66.6.2", "chokidar": "^5.0.0", "magic-string": "^0.30.21", "pathe": "^2.0.3", "tinyglobby": "^0.2.15", "unplugin-utils": "^0.3.1" }, "peerDependencies": { "vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 || ^8.0.0-0" } }, "sha512-HLmzDvde3BJ2C6iromHVE21lmNm4SmGSMlbSbFuLPOmWV11XhhHBkAOzytSxPBRG0dbuo+InSGUM14Ek2d6UDg=="],
|
||||
"@unocss/vite": ["@unocss/vite@66.6.7", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "@unocss/config": "66.6.7", "@unocss/core": "66.6.7", "@unocss/inspector": "66.6.7", "chokidar": "^5.0.0", "magic-string": "^0.30.21", "pathe": "^2.0.3", "tinyglobby": "^0.2.15", "unplugin-utils": "^0.3.1" }, "peerDependencies": { "vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 || ^8.0.0-0" } }, "sha512-8AHrVzAecnQaPLJv3/mpyFt5j2iL3gEwkZcZ8HzjH5ttK2XON1YE9vgujN5NS/yvZwlJxCMNPxn0S410/Ek61A=="],
|
||||
|
||||
"@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.4", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.2" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "vue": "^3.2.25" } }, "sha512-uM5iXipgYIn13UUQCZNdWkYk+sysBeA97d5mHsAoAt1u/wpN3+zxOmsVJWosuzX+IMGRzeYUNytztrYznboIkQ=="],
|
||||
"@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.5", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.2" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", "vue": "^3.2.25" } }, "sha512-bL3AxKuQySfk1iGcBsQnoRVexTPJq0Z/ixFVM8OhVJAP6ZXXXLtM7NFKWhLl30Kg7uTBqIaPXbh+nuQCuBDedg=="],
|
||||
|
||||
"@vitejs/plugin-vue-jsx": ["@vitejs/plugin-vue-jsx@5.1.4", "", { "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-syntax-typescript": "^7.28.6", "@babel/plugin-transform-typescript": "^7.28.6", "@rolldown/pluginutils": "^1.0.0-rc.2", "@vue/babel-plugin-jsx": "^2.0.1" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "vue": "^3.0.0" } }, "sha512-70LmoVk9riR7qc4W2CpjsbNMWTPnuZb9dpFKX1emru0yP57nsc9k8nhLA6U93ngQapv5VDIUq2JatNfLbBIkrA=="],
|
||||
"@vitejs/plugin-vue-jsx": ["@vitejs/plugin-vue-jsx@5.1.5", "", { "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-syntax-typescript": "^7.28.6", "@babel/plugin-transform-typescript": "^7.28.6", "@rolldown/pluginutils": "^1.0.0-rc.2", "@vue/babel-plugin-jsx": "^2.0.1" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", "vue": "^3.0.0" } }, "sha512-jIAsvHOEtWpslLOI2MeElGFxH7M8pM83BU/Tor4RLyiwH0FM4nUW3xdvbw20EeU9wc5IspQwMq225K3CMnJEpA=="],
|
||||
|
||||
"@vue-macros/common": ["@vue-macros/common@3.1.2", "", { "dependencies": { "@vue/compiler-sfc": "^3.5.22", "ast-kit": "^2.1.2", "local-pkg": "^1.1.2", "magic-string-ast": "^1.0.2", "unplugin-utils": "^0.3.0" }, "peerDependencies": { "vue": "^2.7.0 || ^3.2.25" }, "optionalPeers": ["vue"] }, "sha512-h9t4ArDdniO9ekYHAD95t9AZcAbb19lEGK+26iAjUODOIJKmObDNBSe4+6ELQAA3vtYiFPPBtHh7+cQCKi3Dng=="],
|
||||
|
||||
@@ -353,13 +322,13 @@
|
||||
|
||||
"@vue/babel-plugin-resolve-type": ["@vue/babel-plugin-resolve-type@2.0.1", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/helper-module-imports": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/parser": "^7.28.4", "@vue/compiler-sfc": "^3.5.22" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ybwgIuRGRRBhOU37GImDoWQoz+TlSqap65qVI6iwg/J7FfLTLmMf97TS7xQH9I7Qtr/gp161kYVdhr1ZMraSYQ=="],
|
||||
|
||||
"@vue/compiler-core": ["@vue/compiler-core@3.5.29", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/shared": "3.5.29", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-cuzPhD8fwRHk8IGfmYaR4eEe4cAyJEL66Ove/WZL7yWNL134nqLddSLwNRIsFlnnW1kK+p8Ck3viFnC0chXCXw=="],
|
||||
"@vue/compiler-core": ["@vue/compiler-core@3.5.31", "", { "dependencies": { "@babel/parser": "^7.29.2", "@vue/shared": "3.5.31", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-k/ueL14aNIEy5Onf0OVzR8kiqF/WThgLdFhxwa4e/KF/0qe38IwIdofoSWBTvvxQOesaz6riAFAUaYjoF9fLLQ=="],
|
||||
|
||||
"@vue/compiler-dom": ["@vue/compiler-dom@3.5.29", "", { "dependencies": { "@vue/compiler-core": "3.5.29", "@vue/shared": "3.5.29" } }, "sha512-n0G5o7R3uBVmVxjTIYcz7ovr8sy7QObFG8OQJ3xGCDNhbG60biP/P5KnyY8NLd81OuT1WJflG7N4KWYHaeeaIg=="],
|
||||
"@vue/compiler-dom": ["@vue/compiler-dom@3.5.31", "", { "dependencies": { "@vue/compiler-core": "3.5.31", "@vue/shared": "3.5.31" } }, "sha512-BMY/ozS/xxjYqRFL+tKdRpATJYDTTgWSo0+AJvJNg4ig+Hgb0dOsHPXvloHQ5hmlivUqw1Yt2pPIqp4e0v1GUw=="],
|
||||
|
||||
"@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.29", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/compiler-core": "3.5.29", "@vue/compiler-dom": "3.5.29", "@vue/compiler-ssr": "3.5.29", "@vue/shared": "3.5.29", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.6", "source-map-js": "^1.2.1" } }, "sha512-oJZhN5XJs35Gzr50E82jg2cYdZQ78wEwvRO6Y63TvLVTc+6xICzJHP1UIecdSPPYIbkautNBanDiWYa64QSFIA=="],
|
||||
"@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.31", "", { "dependencies": { "@babel/parser": "^7.29.2", "@vue/compiler-core": "3.5.31", "@vue/compiler-dom": "3.5.31", "@vue/compiler-ssr": "3.5.31", "@vue/shared": "3.5.31", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.8", "source-map-js": "^1.2.1" } }, "sha512-M8wpPgR9UJ8MiRGjppvx9uWJfLV7A/T+/rL8s/y3QG3u0c2/YZgff3d6SuimKRIhcYnWg5fTfDMlz2E6seUW8Q=="],
|
||||
|
||||
"@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.29", "", { "dependencies": { "@vue/compiler-dom": "3.5.29", "@vue/shared": "3.5.29" } }, "sha512-Y/ARJZE6fpjzL5GH/phJmsFwx3g6t2KmHKHx5q+MLl2kencADKIrhH5MLF6HHpRMmlRAYBRSvv347Mepf1zVNw=="],
|
||||
"@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.31", "", { "dependencies": { "@vue/compiler-dom": "3.5.31", "@vue/shared": "3.5.31" } }, "sha512-h0xIMxrt/LHOvJKMri+vdYT92BrK3HFLtDqq9Pr/lVVfE4IyKZKvWf0vJFW10Yr6nX02OR4MkJwI0c1HDa1hog=="],
|
||||
|
||||
"@vue/devtools-api": ["@vue/devtools-api@7.7.9", "", { "dependencies": { "@vue/devtools-kit": "^7.7.9" } }, "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g=="],
|
||||
|
||||
@@ -367,15 +336,15 @@
|
||||
|
||||
"@vue/devtools-shared": ["@vue/devtools-shared@7.7.9", "", { "dependencies": { "rfdc": "^1.4.1" } }, "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA=="],
|
||||
|
||||
"@vue/reactivity": ["@vue/reactivity@3.5.29", "", { "dependencies": { "@vue/shared": "3.5.29" } }, "sha512-zcrANcrRdcLtmGZETBxWqIkoQei8HaFpZWx/GHKxx79JZsiZ8j1du0VUJtu4eJjgFvU/iKL5lRXFXksVmI+5DA=="],
|
||||
"@vue/reactivity": ["@vue/reactivity@3.5.31", "", { "dependencies": { "@vue/shared": "3.5.31" } }, "sha512-DtKXxk9E/KuVvt8VxWu+6Luc9I9ETNcqR1T1oW1gf02nXaZ1kuAx58oVu7uX9XxJR0iJCro6fqBLw9oSBELo5g=="],
|
||||
|
||||
"@vue/runtime-core": ["@vue/runtime-core@3.5.29", "", { "dependencies": { "@vue/reactivity": "3.5.29", "@vue/shared": "3.5.29" } }, "sha512-8DpW2QfdwIWOLqtsNcds4s+QgwSaHSJY/SUe04LptianUQ/0xi6KVsu/pYVh+HO3NTVvVJjIPL2t6GdeKbS4Lg=="],
|
||||
"@vue/runtime-core": ["@vue/runtime-core@3.5.31", "", { "dependencies": { "@vue/reactivity": "3.5.31", "@vue/shared": "3.5.31" } }, "sha512-AZPmIHXEAyhpkmN7aWlqjSfYynmkWlluDNPHMCZKFHH+lLtxP/30UJmoVhXmbDoP1Ng0jG0fyY2zCj1PnSSA6Q=="],
|
||||
|
||||
"@vue/runtime-dom": ["@vue/runtime-dom@3.5.29", "", { "dependencies": { "@vue/reactivity": "3.5.29", "@vue/runtime-core": "3.5.29", "@vue/shared": "3.5.29", "csstype": "^3.2.3" } }, "sha512-AHvvJEtcY9tw/uk+s/YRLSlxxQnqnAkjqvK25ZiM4CllCZWzElRAoQnCM42m9AHRLNJ6oe2kC5DCgD4AUdlvXg=="],
|
||||
"@vue/runtime-dom": ["@vue/runtime-dom@3.5.31", "", { "dependencies": { "@vue/reactivity": "3.5.31", "@vue/runtime-core": "3.5.31", "@vue/shared": "3.5.31", "csstype": "^3.2.3" } }, "sha512-xQJsNRmGPeDCJq/u813tyonNgWBFjzfVkBwDREdEWndBnGdHLHgkwNBQxLtg4zDrzKTEcnikUy1UUNecb3lJ6g=="],
|
||||
|
||||
"@vue/server-renderer": ["@vue/server-renderer@3.5.29", "", { "dependencies": { "@vue/compiler-ssr": "3.5.29", "@vue/shared": "3.5.29" }, "peerDependencies": { "vue": "3.5.29" } }, "sha512-G/1k6WK5MusLlbxSE2YTcqAAezS+VuwHhOvLx2KnQU7G2zCH6KIb+5Wyt6UjMq7a3qPzNEjJXs1hvAxDclQH+g=="],
|
||||
"@vue/server-renderer": ["@vue/server-renderer@3.5.31", "", { "dependencies": { "@vue/compiler-ssr": "3.5.31", "@vue/shared": "3.5.31" }, "peerDependencies": { "vue": "3.5.31" } }, "sha512-GJuwRvMcdZX/CriUnyIIOGkx3rMV3H6sOu0JhdKbduaeCji6zb60iOGMY7tFoN24NfsUYoFBhshZtGxGpxO4iA=="],
|
||||
|
||||
"@vue/shared": ["@vue/shared@3.5.29", "", {}, "sha512-w7SR0A5zyRByL9XUkCfdLs7t9XOHUyJ67qPGQjOou3p6GvBeBW+AVjUUmlxtZ4PIYaRvE+1LmK44O4uajlZwcg=="],
|
||||
"@vue/shared": ["@vue/shared@3.5.31", "", {}, "sha512-nBxuiuS9Lj5bPkPbWogPUnjxxWpkRniX7e5UBQDWl6Fsf4roq9wwV+cR7ezQ4zXswNvPIlsdj1slcLB7XCsRAw=="],
|
||||
|
||||
"@vueuse/core": ["@vueuse/core@14.2.1", "", { "dependencies": { "@types/web-bluetooth": "^0.0.21", "@vueuse/metadata": "14.2.1", "@vueuse/shared": "14.2.1" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-3vwDzV+GDUNpdegRY6kzpLm4Igptq+GA0QkJ3W61Iv27YWwW/ufSlOfgQIpN6FZRMG0mkaz4gglJRtq5SeJyIQ=="],
|
||||
|
||||
@@ -383,30 +352,48 @@
|
||||
|
||||
"@vueuse/shared": ["@vueuse/shared@14.2.1", "", { "peerDependencies": { "vue": "^3.5.0" } }, "sha512-shTJncjV9JTI4oVNyF1FQonetYAiTBd+Qj7cY89SWbXSkx7gyhrgtEdF2ZAVWS1S3SHlaROO6F2IesJxQEkZBw=="],
|
||||
|
||||
"@whatwg-node/fetch": ["@whatwg-node/fetch@0.9.23", "", { "dependencies": { "@whatwg-node/node-fetch": "^0.6.0", "urlpattern-polyfill": "^10.0.0" } }, "sha512-7xlqWel9JsmxahJnYVUj/LLxWcnA93DR4c9xlw3U814jWTiYalryiH1qToik1hOxweKKRLi4haXHM5ycRksPBA=="],
|
||||
|
||||
"@whatwg-node/node-fetch": ["@whatwg-node/node-fetch@0.6.0", "", { "dependencies": { "@kamilkisiela/fast-url-parser": "^1.1.4", "busboy": "^1.6.0", "fast-querystring": "^1.1.1", "tslib": "^2.6.3" } }, "sha512-tcZAhrpx6oVlkEsRngeTEEE7I5/QdLjeEz4IlekabGaESP7+Dkm/6a9KcF1KdCBB7mO9PXtBkwCuTCt8+UPg8Q=="],
|
||||
|
||||
"acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
|
||||
|
||||
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"ast-kit": ["ast-kit@2.2.0", "", { "dependencies": { "@babel/parser": "^7.28.5", "pathe": "^2.0.3" } }, "sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw=="],
|
||||
|
||||
"ast-walker-scope": ["ast-walker-scope@0.8.3", "", { "dependencies": { "@babel/parser": "^7.28.4", "ast-kit": "^2.1.3" } }, "sha512-cbdCP0PGOBq0ASG+sjnKIoYkWMKhhz+F/h9pRexUdX2Hd38+WOlBkRKlqkGOSm0YQpcFMQBJeK4WspUAkwsEdg=="],
|
||||
|
||||
"aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="],
|
||||
|
||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.0", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA=="],
|
||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.10", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-sUoJ3IMxx4AyRqO4MLeHlnGDkyXRoUG0/AI9fjK+vS72ekpV0yWVY7O0BVjmBcRtkNcsAO2QDZ4tdKKGoI6YaQ=="],
|
||||
|
||||
"birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="],
|
||||
|
||||
"blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="],
|
||||
|
||||
"browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="],
|
||||
|
||||
"bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="],
|
||||
|
||||
"busboy": ["busboy@1.6.0", "", { "dependencies": { "streamsearch": "^1.1.0" } }, "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA=="],
|
||||
|
||||
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001774", "", {}, "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA=="],
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001781", "", {}, "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw=="],
|
||||
|
||||
"chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
|
||||
|
||||
"class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="],
|
||||
|
||||
"cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
|
||||
|
||||
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
||||
|
||||
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||
|
||||
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||
|
||||
"colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="],
|
||||
|
||||
"confbox": ["confbox@0.2.4", "", {}, "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ=="],
|
||||
@@ -415,11 +402,11 @@
|
||||
|
||||
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||
|
||||
"cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
|
||||
|
||||
"copy-anything": ["copy-anything@4.0.5", "", { "dependencies": { "is-what": "^5.2.0" } }, "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA=="],
|
||||
|
||||
"css-tree": ["css-tree@3.1.0", "", { "dependencies": { "mdn-data": "2.12.2", "source-map-js": "^1.0.1" } }, "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w=="],
|
||||
"cross-fetch": ["cross-fetch@4.1.0", "", { "dependencies": { "node-fetch": "^2.7.0" } }, "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw=="],
|
||||
|
||||
"css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="],
|
||||
|
||||
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
||||
|
||||
@@ -433,14 +420,12 @@
|
||||
|
||||
"duplexer": ["duplexer@0.1.2", "", {}, "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg=="],
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.302", "", {}, "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg=="],
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.323", "", {}, "sha512-oQm+FxbazvN2WICCbvJgj3IYPKV8awip57+W5VP+Aatk4kFU4pDYCPHZOX22Z27zpw8uttBehEqgK+VTJAYrVw=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
|
||||
|
||||
"error-stack-parser-es": ["error-stack-parser-es@1.0.5", "", {}, "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||
|
||||
"escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
|
||||
@@ -449,19 +434,31 @@
|
||||
|
||||
"exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="],
|
||||
|
||||
"fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="],
|
||||
|
||||
"fast-querystring": ["fast-querystring@1.1.2", "", { "dependencies": { "fast-decode-uri-component": "^1.0.1" } }, "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
||||
|
||||
"globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
|
||||
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
|
||||
|
||||
"gzip-size": ["gzip-size@6.0.0", "", { "dependencies": { "duplexer": "^0.1.2" } }, "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q=="],
|
||||
|
||||
"hono": ["hono@4.12.2", "", {}, "sha512-gJnaDHXKDayjt8ue0n8Gs0A007yKXj4Xzb8+cNjZeYsSzzwKc0Lr+OZgYwVfB0pHfUs17EPoLvrOsEaJ9mj+Tg=="],
|
||||
"hono": ["hono@4.12.9", "", {}, "sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA=="],
|
||||
|
||||
"hookable": ["hookable@6.0.1", "", {}, "sha512-uKGyY8BuzN/a5gvzvA+3FVWo0+wUjgtfSdnmjtrOVwQCZPHpHDH2WRO3VZSOeluYrHoDCiXFffZXs8Dj1ULWtw=="],
|
||||
"hookable": ["hookable@6.1.0", "", {}, "sha512-ZoKZSJgu8voGK2geJS+6YtYjvIzu9AOM/KZXsBxr83uhLL++e9pEv/dlgwgy3dvHg06kTz6JOh1hk3C8Ceiymw=="],
|
||||
|
||||
"i18next": ["i18next@26.0.3", "", { "dependencies": { "@babel/runtime": "^7.29.2" }, "peerDependencies": { "typescript": "^5 || ^6" }, "optionalPeers": ["typescript"] }, "sha512-1571kXINxHKY7LksWp8wP+zP0YqHSSpl/OW0Y0owFEf2H3s8gCAffWaZivcz14rMkOvn3R/psiQxVsR9t2Nafg=="],
|
||||
|
||||
"i18next-http-backend": ["i18next-http-backend@3.0.4", "", { "dependencies": { "cross-fetch": "4.1.0" } }, "sha512-udwrBIE6cNpqn1gRAqRULq3+7MzIIuaiKRWrz++dVz5SqWW2VwXmPJtAgkI0JtMLFaADC9qNmnZAxWAhsxXx2g=="],
|
||||
|
||||
"i18next-vue": ["i18next-vue@5.4.0", "", { "peerDependencies": { "i18next": ">=23", "vue": "^3.4.38" } }, "sha512-GDj0Xvmis5Xgcvo9gMBJMgJCtewYMLZP6gAEPDDGCMjA+QeB4uS4qUf1MK79mkz/FukhaJdC+nlj0y1qk6NO2Q=="],
|
||||
|
||||
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
||||
|
||||
"is-mobile": ["is-mobile@5.0.0", "", {}, "sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ=="],
|
||||
|
||||
@@ -475,23 +472,53 @@
|
||||
|
||||
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
||||
|
||||
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
|
||||
"lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
|
||||
|
||||
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="],
|
||||
|
||||
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="],
|
||||
|
||||
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="],
|
||||
|
||||
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="],
|
||||
|
||||
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="],
|
||||
|
||||
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="],
|
||||
|
||||
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="],
|
||||
|
||||
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="],
|
||||
|
||||
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="],
|
||||
|
||||
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="],
|
||||
|
||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
|
||||
|
||||
"local-pkg": ["local-pkg@1.1.2", "", { "dependencies": { "mlly": "^1.7.4", "pkg-types": "^2.3.0", "quansync": "^0.2.11" } }, "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A=="],
|
||||
|
||||
"lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="],
|
||||
|
||||
"long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="],
|
||||
|
||||
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||
|
||||
"magic-regexp": ["magic-regexp@0.10.0", "", { "dependencies": { "estree-walker": "^3.0.3", "magic-string": "^0.30.12", "mlly": "^1.7.2", "regexp-tree": "^0.1.27", "type-level-regexp": "~0.1.17", "ufo": "^1.5.4", "unplugin": "^2.0.0" } }, "sha512-Uly1Bu4lO1hwHUW0CQeSWuRtzCMNO00CmXtS8N6fyvB3B979GOEEeAkiTUDsmbYLAbvpUS/Kt5c4ibosAzVyVg=="],
|
||||
|
||||
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||
|
||||
"magic-string-ast": ["magic-string-ast@1.0.3", "", { "dependencies": { "magic-string": "^0.30.19" } }, "sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA=="],
|
||||
|
||||
"mdn-data": ["mdn-data@2.12.2", "", {}, "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA=="],
|
||||
"mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="],
|
||||
|
||||
"miniflare": ["miniflare@4.20260302.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "^0.34.5", "undici": "7.18.2", "workerd": "1.20260302.0", "ws": "8.18.0", "youch": "4.1.0-beta.10" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-joGFywlo7HdfHXXGOkc6tDCVkwjEncM0mwEsMOLWcl+vDVJPj9HRV7JtEa0+lCpNOLdYw7mZNHYe12xz9KtJOw=="],
|
||||
"mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="],
|
||||
|
||||
"mlly": ["mlly@1.8.0", "", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="],
|
||||
"mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="],
|
||||
|
||||
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
|
||||
|
||||
@@ -501,17 +528,21 @@
|
||||
|
||||
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||
|
||||
"node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
|
||||
|
||||
"node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="],
|
||||
|
||||
"node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="],
|
||||
"node-releases": ["node-releases@2.0.36", "", {}, "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA=="],
|
||||
|
||||
"obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="],
|
||||
|
||||
"ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="],
|
||||
|
||||
"package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="],
|
||||
"oxc-parser": ["oxc-parser@0.115.0", "", { "dependencies": { "@oxc-project/types": "^0.115.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm-eabi": "0.115.0", "@oxc-parser/binding-android-arm64": "0.115.0", "@oxc-parser/binding-darwin-arm64": "0.115.0", "@oxc-parser/binding-darwin-x64": "0.115.0", "@oxc-parser/binding-freebsd-x64": "0.115.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.115.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.115.0", "@oxc-parser/binding-linux-arm64-gnu": "0.115.0", "@oxc-parser/binding-linux-arm64-musl": "0.115.0", "@oxc-parser/binding-linux-ppc64-gnu": "0.115.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.115.0", "@oxc-parser/binding-linux-riscv64-musl": "0.115.0", "@oxc-parser/binding-linux-s390x-gnu": "0.115.0", "@oxc-parser/binding-linux-x64-gnu": "0.115.0", "@oxc-parser/binding-linux-x64-musl": "0.115.0", "@oxc-parser/binding-openharmony-arm64": "0.115.0", "@oxc-parser/binding-wasm32-wasi": "0.115.0", "@oxc-parser/binding-win32-arm64-msvc": "0.115.0", "@oxc-parser/binding-win32-ia32-msvc": "0.115.0", "@oxc-parser/binding-win32-x64-msvc": "0.115.0" } }, "sha512-2w7Xn3CbS/zwzSY82S5WLemrRu3CT57uF7Lx8llrE/2bul6iMTcJE4Rbls7GDNbLn3ttATI68PfOz2Pt3KZ2cQ=="],
|
||||
|
||||
"path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="],
|
||||
"oxc-walker": ["oxc-walker@0.7.0", "", { "dependencies": { "magic-regexp": "^0.10.0" }, "peerDependencies": { "oxc-parser": ">=0.98.0" } }, "sha512-54B4KUhrzbzc4sKvKwVYm7E2PgeROpGba0/2nlNZMqfDyca+yOor5IMb4WLGBatGDT0nkzYdYuzylg7n3YfB7A=="],
|
||||
|
||||
"package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="],
|
||||
|
||||
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||
|
||||
@@ -519,67 +550,77 @@
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
||||
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||
"picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
|
||||
|
||||
"pinia": ["pinia@3.0.4", "", { "dependencies": { "@vue/devtools-api": "^7.7.7" }, "peerDependencies": { "typescript": ">=4.5.0", "vue": "^3.5.11" }, "optionalPeers": ["typescript"] }, "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw=="],
|
||||
|
||||
"pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="],
|
||||
|
||||
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
||||
"postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="],
|
||||
|
||||
"protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="],
|
||||
|
||||
"quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="],
|
||||
|
||||
"readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
|
||||
|
||||
"regexp-tree": ["regexp-tree@0.1.27", "", { "bin": { "regexp-tree": "bin/regexp-tree" } }, "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA=="],
|
||||
|
||||
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
|
||||
|
||||
"rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
|
||||
|
||||
"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=="],
|
||||
"rolldown": ["rolldown@1.0.0-rc.12", "", { "dependencies": { "@oxc-project/types": "=0.122.0", "@rolldown/pluginutils": "1.0.0-rc.12" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.12", "@rolldown/binding-darwin-arm64": "1.0.0-rc.12", "@rolldown/binding-darwin-x64": "1.0.0-rc.12", "@rolldown/binding-freebsd-x64": "1.0.0-rc.12", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.12", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.12", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.12", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.12", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.12", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.12", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.12", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.12", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.12", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.12", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.12" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A=="],
|
||||
|
||||
"scule": ["scule@1.3.0", "", {}, "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g=="],
|
||||
|
||||
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
|
||||
"speakingurl": ["speakingurl@14.0.1", "", {}, "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ=="],
|
||||
|
||||
"streamsearch": ["streamsearch@1.1.0", "", {}, "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="],
|
||||
|
||||
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"strip-literal": ["strip-literal@3.1.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg=="],
|
||||
|
||||
"superjson": ["superjson@2.2.6", "", { "dependencies": { "copy-anything": "^4" } }, "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA=="],
|
||||
|
||||
"supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
|
||||
|
||||
"tailwind-merge": ["tailwind-merge@3.5.0", "", {}, "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A=="],
|
||||
|
||||
"tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
|
||||
"tinyexec": ["tinyexec@1.0.4", "", {}, "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"tweetnacl": ["tweetnacl@1.0.3", "", {}, "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw=="],
|
||||
|
||||
"type-level-regexp": ["type-level-regexp@0.1.17", "", {}, "sha512-wTk4DH3cxwk196uGLK/E9pE45aLfeKJacKmcEgEOA/q5dnPGNxXt0cfYdFxb57L+sEpf1oJH4Dnx/pnRcku9jg=="],
|
||||
|
||||
"ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="],
|
||||
|
||||
"unconfig": ["unconfig@7.5.0", "", { "dependencies": { "@quansync/fs": "^1.0.0", "defu": "^6.1.4", "jiti": "^2.6.1", "quansync": "^1.0.0", "unconfig-core": "7.5.0" } }, "sha512-oi8Qy2JV4D3UQ0PsopR28CzdQ3S/5A1zwsUwp/rosSbfhJ5z7b90bIyTwi/F7hCLD4SGcZVjDzd4XoUQcEanvA=="],
|
||||
|
||||
"unconfig-core": ["unconfig-core@7.5.0", "", { "dependencies": { "@quansync/fs": "^1.0.0", "quansync": "^1.0.0" } }, "sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w=="],
|
||||
|
||||
"undici": ["undici@7.18.2", "", {}, "sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw=="],
|
||||
|
||||
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
|
||||
|
||||
"unenv": ["unenv@2.0.0-rc.24", "", { "dependencies": { "pathe": "^2.0.3" } }, "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw=="],
|
||||
"unhead": ["unhead@2.1.12", "", { "dependencies": { "hookable": "^6.0.1" } }, "sha512-iTHdWD9ztTunOErtfUFk6Wr11BxvzumcYJ0CzaSCBUOEtg+DUZ9+gnE99i8QkLFT2q1rZD48BYYGXpOZVDLYkA=="],
|
||||
|
||||
"unhead": ["unhead@2.1.9", "", { "dependencies": { "hookable": "^6.0.1" } }, "sha512-4GvP6YeJQzo9J3g9fFZUJOH6jacUp5JgJ0/zC8eZrt8Dwompg9SuOSfrYbZaEzsfMPgQc4fsEjMoY9WzGPOChg=="],
|
||||
"unimport": ["unimport@5.7.0", "", { "dependencies": { "acorn": "^8.16.0", "escape-string-regexp": "^5.0.0", "estree-walker": "^3.0.3", "local-pkg": "^1.1.2", "magic-string": "^0.30.21", "mlly": "^1.8.0", "pathe": "^2.0.3", "picomatch": "^4.0.3", "pkg-types": "^2.3.0", "scule": "^1.3.0", "strip-literal": "^3.1.0", "tinyglobby": "^0.2.15", "unplugin": "^2.3.11", "unplugin-utils": "^0.3.1" } }, "sha512-njnL6sp8lEA8QQbZrt+52p/g4X0rw3bnGGmUcJnt1jeG8+iiqO779aGz0PirCtydAIVcuTBRlJ52F0u46z309Q=="],
|
||||
|
||||
"unimport": ["unimport@5.6.0", "", { "dependencies": { "acorn": "^8.15.0", "escape-string-regexp": "^5.0.0", "estree-walker": "^3.0.3", "local-pkg": "^1.1.2", "magic-string": "^0.30.21", "mlly": "^1.8.0", "pathe": "^2.0.3", "picomatch": "^4.0.3", "pkg-types": "^2.3.0", "scule": "^1.3.0", "strip-literal": "^3.1.0", "tinyglobby": "^0.2.15", "unplugin": "^2.3.11", "unplugin-utils": "^0.3.1" } }, "sha512-8rqAmtJV8o60x46kBAJKtHpJDJWkA2xcBqWKPI14MgUb05o1pnpnCnXSxedUXyeq7p8fR5g3pTo2BaswZ9lD9A=="],
|
||||
|
||||
"unocss": ["unocss@66.6.2", "", { "dependencies": { "@unocss/cli": "66.6.2", "@unocss/core": "66.6.2", "@unocss/preset-attributify": "66.6.2", "@unocss/preset-icons": "66.6.2", "@unocss/preset-mini": "66.6.2", "@unocss/preset-tagify": "66.6.2", "@unocss/preset-typography": "66.6.2", "@unocss/preset-uno": "66.6.2", "@unocss/preset-web-fonts": "66.6.2", "@unocss/preset-wind": "66.6.2", "@unocss/preset-wind3": "66.6.2", "@unocss/preset-wind4": "66.6.2", "@unocss/transformer-attributify-jsx": "66.6.2", "@unocss/transformer-compile-class": "66.6.2", "@unocss/transformer-directives": "66.6.2", "@unocss/transformer-variant-group": "66.6.2", "@unocss/vite": "66.6.2" }, "peerDependencies": { "@unocss/astro": "66.6.2", "@unocss/postcss": "66.6.2", "@unocss/webpack": "66.6.2" }, "optionalPeers": ["@unocss/astro", "@unocss/postcss", "@unocss/webpack"] }, "sha512-ulkfFBFm++/yTdgDn/clpxtm3GxynZi57F4KETQkMQWRXUI7FwqPKGn0xooscvbtldlX67pkovwj/mzkwExitQ=="],
|
||||
"unocss": ["unocss@66.6.7", "", { "dependencies": { "@unocss/cli": "66.6.7", "@unocss/core": "66.6.7", "@unocss/preset-attributify": "66.6.7", "@unocss/preset-icons": "66.6.7", "@unocss/preset-mini": "66.6.7", "@unocss/preset-tagify": "66.6.7", "@unocss/preset-typography": "66.6.7", "@unocss/preset-uno": "66.6.7", "@unocss/preset-web-fonts": "66.6.7", "@unocss/preset-wind": "66.6.7", "@unocss/preset-wind3": "66.6.7", "@unocss/preset-wind4": "66.6.7", "@unocss/transformer-attributify-jsx": "66.6.7", "@unocss/transformer-compile-class": "66.6.7", "@unocss/transformer-directives": "66.6.7", "@unocss/transformer-variant-group": "66.6.7", "@unocss/vite": "66.6.7" }, "peerDependencies": { "@unocss/astro": "66.6.7", "@unocss/postcss": "66.6.7", "@unocss/webpack": "66.6.7" }, "optionalPeers": ["@unocss/astro", "@unocss/postcss", "@unocss/webpack"] }, "sha512-TdZ/JnKhrqkknrMvLl0KOwrGzFThEspFIyYiylFYJki2JkMN/5EJIr+vIZEGRX69hFTjTLi6utIpbipueqzNbw=="],
|
||||
|
||||
"unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="],
|
||||
|
||||
@@ -587,45 +628,49 @@
|
||||
|
||||
"unplugin-utils": ["unplugin-utils@0.3.1", "", { "dependencies": { "pathe": "^2.0.3", "picomatch": "^4.0.3" } }, "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog=="],
|
||||
|
||||
"unplugin-vue-components": ["unplugin-vue-components@31.0.0", "", { "dependencies": { "chokidar": "^5.0.0", "local-pkg": "^1.1.2", "magic-string": "^0.30.21", "mlly": "^1.8.0", "obug": "^2.1.1", "picomatch": "^4.0.3", "tinyglobby": "^0.2.15", "unplugin": "^2.3.11", "unplugin-utils": "^0.3.1" }, "peerDependencies": { "@nuxt/kit": "^3.2.2 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@nuxt/kit"] }, "sha512-4ULwfTZTLuWJ7+S9P7TrcStYLsSRkk6vy2jt/WTfgUEUb0nW9//xxmrfhyHUEVpZ2UKRRwfRb8Yy15PDbVZf+Q=="],
|
||||
"unplugin-vue-components": ["unplugin-vue-components@32.0.0", "", { "dependencies": { "chokidar": "^5.0.0", "local-pkg": "^1.1.2", "magic-string": "^0.30.21", "mlly": "^1.8.2", "obug": "^2.1.1", "picomatch": "^4.0.3", "tinyglobby": "^0.2.15", "unplugin": "^3.0.0", "unplugin-utils": "^0.3.1" }, "peerDependencies": { "@nuxt/kit": "^3.2.2 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@nuxt/kit"] }, "sha512-uLdccgS7mf3pv1bCCP20y/hm+u1eOjAmygVkh+Oa70MPkzgl1eQv1L0CwdHNM3gscO8/GDMGIET98Ja47CBbZg=="],
|
||||
|
||||
"update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
|
||||
|
||||
"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=="],
|
||||
"urlpattern-polyfill": ["urlpattern-polyfill@10.1.0", "", {}, "sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw=="],
|
||||
|
||||
"vite": ["vite@8.0.3", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.12", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.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", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ=="],
|
||||
|
||||
"vite-ssr-components": ["vite-ssr-components@0.5.2", "", { "dependencies": { "@babel/parser": "^7.27.2", "@babel/traverse": "^7.27.1", "picomatch": "^4.0.2" } }, "sha512-1a8YThRwyyu1gGjc1Ral9Q4uS+n0D4GydhbkVd9c1SA1YNgXyrOizttped87C1ItEznQzhiCyQjaOcYnXa0zMA=="],
|
||||
|
||||
"vue": ["vue@3.5.29", "", { "dependencies": { "@vue/compiler-dom": "3.5.29", "@vue/compiler-sfc": "3.5.29", "@vue/runtime-dom": "3.5.29", "@vue/server-renderer": "3.5.29", "@vue/shared": "3.5.29" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA=="],
|
||||
"vue": ["vue@3.5.31", "", { "dependencies": { "@vue/compiler-dom": "3.5.31", "@vue/compiler-sfc": "3.5.31", "@vue/runtime-dom": "3.5.31", "@vue/server-renderer": "3.5.31", "@vue/shared": "3.5.31" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-iV/sU9SzOlmA/0tygSmjkEN6Jbs3nPoIPFhCMLD2STrjgOU8DX7ZtzMhg4ahVwf5Rp9KoFzcXeB1ZrVbLBp5/Q=="],
|
||||
|
||||
"vue-router": ["vue-router@5.0.3", "", { "dependencies": { "@babel/generator": "^7.28.6", "@vue-macros/common": "^3.1.1", "@vue/devtools-api": "^8.0.6", "ast-walker-scope": "^0.8.3", "chokidar": "^5.0.0", "json5": "^2.2.3", "local-pkg": "^1.1.2", "magic-string": "^0.30.21", "mlly": "^1.8.0", "muggle-string": "^0.4.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "scule": "^1.3.0", "tinyglobby": "^0.2.15", "unplugin": "^3.0.0", "unplugin-utils": "^0.3.1", "yaml": "^2.8.2" }, "peerDependencies": { "@pinia/colada": ">=0.21.2", "@vue/compiler-sfc": "^3.5.17", "pinia": "^3.0.4", "vue": "^3.5.0" }, "optionalPeers": ["@pinia/colada", "@vue/compiler-sfc", "pinia"] }, "sha512-nG1c7aAFac7NYj8Hluo68WyWfc41xkEjaR0ViLHCa3oDvTQ/nIuLJlXJX1NUPw/DXzx/8+OKMng045HHQKQKWw=="],
|
||||
"vue-router": ["vue-router@5.0.4", "", { "dependencies": { "@babel/generator": "^7.28.6", "@vue-macros/common": "^3.1.1", "@vue/devtools-api": "^8.0.6", "ast-walker-scope": "^0.8.3", "chokidar": "^5.0.0", "json5": "^2.2.3", "local-pkg": "^1.1.2", "magic-string": "^0.30.21", "mlly": "^1.8.0", "muggle-string": "^0.4.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "scule": "^1.3.0", "tinyglobby": "^0.2.15", "unplugin": "^3.0.0", "unplugin-utils": "^0.3.1", "yaml": "^2.8.2" }, "peerDependencies": { "@pinia/colada": ">=0.21.2", "@vue/compiler-sfc": "^3.5.17", "pinia": "^3.0.4", "vue": "^3.5.0" }, "optionalPeers": ["@pinia/colada", "@vue/compiler-sfc", "pinia"] }, "sha512-lCqDLCI2+fKVRl2OzXuzdSWmxXFLQRxQbmHugnRpTMyYiT+hNaycV0faqG5FBHDXoYrZ6MQcX87BvbY8mQ20Bg=="],
|
||||
|
||||
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
|
||||
|
||||
"webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="],
|
||||
|
||||
"workerd": ["workerd@1.20260302.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260302.0", "@cloudflare/workerd-darwin-arm64": "1.20260302.0", "@cloudflare/workerd-linux-64": "1.20260302.0", "@cloudflare/workerd-linux-arm64": "1.20260302.0", "@cloudflare/workerd-windows-64": "1.20260302.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-FhNdC8cenMDllI6bTktFgxP5Bn5ZEnGtofgKipY6pW9jtq708D1DeGI6vGad78KQLBGaDwFy1eThjCoLYgFfog=="],
|
||||
"whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
|
||||
|
||||
"wrangler": ["wrangler@4.68.1", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.2", "@cloudflare/unenv-preset": "2.14.0", "blake3-wasm": "2.1.5", "esbuild": "0.27.3", "miniflare": "4.20260302.0", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20260302.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20260302.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-G+TI3k/olEGBAVkPtUlhAX/DIbL/190fv3aK+r+45/wPclNEymjxCc35T8QGTDhc2fEMXiw51L5bH9aNsBg+yQ=="],
|
||||
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||
|
||||
"ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="],
|
||||
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
|
||||
|
||||
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||
|
||||
"yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="],
|
||||
"yaml": ["yaml@2.8.3", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg=="],
|
||||
|
||||
"youch": ["youch@4.1.0-beta.10", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@poppinss/dumper": "^0.6.4", "@speed-highlight/core": "^1.2.7", "cookie": "^1.0.2", "youch-core": "^0.3.3" } }, "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ=="],
|
||||
"yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
|
||||
|
||||
"youch-core": ["youch-core@0.3.3", "", { "dependencies": { "@poppinss/exception": "^1.2.2", "error-stack-parser-es": "^1.0.5" } }, "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA=="],
|
||||
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
|
||||
|
||||
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
||||
|
||||
"@cspotcode/source-map-support/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="],
|
||||
|
||||
"@quansync/fs/quansync": ["quansync@1.0.0", "", {}, "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA=="],
|
||||
|
||||
"@unocss/transformer-attributify-jsx/@babel/parser": ["@babel/parser@7.27.7", "", { "dependencies": { "@babel/types": "^7.27.7" }, "bin": "./bin/babel-parser.js" }, "sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q=="],
|
||||
"@vitejs/plugin-vue-jsx/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.11", "", {}, "sha512-xQO9vbwBecJRv9EUcQ/y0dzSTJgA7Q6UVN7xp6B81+tBGSLVAK03yJ9NkJaUA7JFD91kbjxRSC/mDnmvXzbHoQ=="],
|
||||
|
||||
"@unocss/transformer-attributify-jsx/@babel/traverse": ["@babel/traverse@7.27.7", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.27.5", "@babel/parser": "^7.27.7", "@babel/template": "^7.27.2", "@babel/types": "^7.27.7", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-X6ZlfR/O/s5EQ/SnUSLzr+6kGnkg8HXGMzpgsMsrJVcfDtH1vIp6ctCN4eZ1LS5c0+te5Cb6Y514fASjMRJ1nw=="],
|
||||
"@vue-macros/common/@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.30", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/compiler-core": "3.5.30", "@vue/compiler-dom": "3.5.30", "@vue/compiler-ssr": "3.5.30", "@vue/shared": "3.5.30", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.8", "source-map-js": "^1.2.1" } }, "sha512-LqmFPDn89dtU9vI3wHJnwaV6GfTRD87AjWpTWpyrdVOObVtjIuSeZr181z5C4PmVx/V3j2p+0f7edFKGRMpQ5A=="],
|
||||
|
||||
"@vitejs/plugin-vue-jsx/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.5", "", {}, "sha512-RxlLX/DPoarZ9PtxVrQgZhPoor987YtKQqCo5zkjX+0S0yLJ7Vv515Wk6+xtTL67VONKJKxETWZwuZjss2idYw=="],
|
||||
"@vue/babel-plugin-jsx/@vue/shared": ["@vue/shared@3.5.30", "", {}, "sha512-YXgQ7JjaO18NeK2K9VTbDHaFy62WrObMa6XERNfNOkAhD1F1oDSf3ZJ7K6GqabZ0BvSDHajp8qfS5Sa2I9n8uQ=="],
|
||||
|
||||
"@vue/babel-plugin-resolve-type/@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.30", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/compiler-core": "3.5.30", "@vue/compiler-dom": "3.5.30", "@vue/compiler-ssr": "3.5.30", "@vue/shared": "3.5.30", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.8", "source-map-js": "^1.2.1" } }, "sha512-LqmFPDn89dtU9vI3wHJnwaV6GfTRD87AjWpTWpyrdVOObVtjIuSeZr181z5C4PmVx/V3j2p+0f7edFKGRMpQ5A=="],
|
||||
|
||||
"@vue/compiler-core/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||
|
||||
@@ -637,7 +682,9 @@
|
||||
|
||||
"mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
|
||||
|
||||
"sharp/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
||||
"oxc-parser/@oxc-project/types": ["@oxc-project/types@0.115.0", "", {}, "sha512-4n91DKnebUS4yjUHl2g3/b2T+IUdCfmoZGhmwsovZCDaJSs+QkVAM+0AqqTxHSsHfeiMuueT75cZaZcT/m0pSw=="],
|
||||
|
||||
"rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.12", "", {}, "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw=="],
|
||||
|
||||
"strip-literal/js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="],
|
||||
|
||||
@@ -645,17 +692,37 @@
|
||||
|
||||
"unconfig-core/quansync": ["quansync@1.0.0", "", {}, "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA=="],
|
||||
|
||||
"vue-router/@vue/devtools-api": ["@vue/devtools-api@8.0.6", "", { "dependencies": { "@vue/devtools-kit": "^8.0.6" } }, "sha512-+lGBI+WTvJmnU2FZqHhEB8J1DXcvNlDeEalz77iYgOdY1jTj1ipSBaKj3sRhYcy+kqA8v/BSuvOz1XJucfQmUA=="],
|
||||
"unplugin-vue-components/unplugin": ["unplugin@3.0.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg=="],
|
||||
|
||||
"vue-router/@vue/devtools-api": ["@vue/devtools-api@8.1.1", "", { "dependencies": { "@vue/devtools-kit": "^8.1.1" } }, "sha512-bsDMJ07b3GN1puVwJb/fyFnj/U2imyswK5UQVLZwVl7O05jDrt6BHxeG5XffmOOdasOj/bOmIjxJvGPxU7pcqw=="],
|
||||
|
||||
"vue-router/unplugin": ["unplugin@3.0.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg=="],
|
||||
|
||||
"@unocss/transformer-attributify-jsx/@babel/traverse/@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
|
||||
"@vue-macros/common/@vue/compiler-sfc/@vue/compiler-core": ["@vue/compiler-core@3.5.30", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/shared": "3.5.30", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-s3DfdZkcu/qExZ+td75015ljzHc6vE+30cFMGRPROYjqkroYI5NV2X1yAMX9UeyBNWB9MxCfPcsjpLS11nzkkw=="],
|
||||
|
||||
"@vue-macros/common/@vue/compiler-sfc/@vue/compiler-dom": ["@vue/compiler-dom@3.5.30", "", { "dependencies": { "@vue/compiler-core": "3.5.30", "@vue/shared": "3.5.30" } }, "sha512-eCFYESUEVYHhiMuK4SQTldO3RYxyMR/UQL4KdGD1Yrkfdx4m/HYuZ9jSfPdA+nWJY34VWndiYdW/wZXyiPEB9g=="],
|
||||
|
||||
"@vue-macros/common/@vue/compiler-sfc/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.30", "", { "dependencies": { "@vue/compiler-dom": "3.5.30", "@vue/shared": "3.5.30" } }, "sha512-NsYK6OMTnx109PSL2IAyf62JP6EUdk4Dmj6AkWcJGBvN0dQoMYtVekAmdqgTtWQgEJo+Okstbf/1p7qZr5H+bA=="],
|
||||
|
||||
"@vue-macros/common/@vue/compiler-sfc/@vue/shared": ["@vue/shared@3.5.30", "", {}, "sha512-YXgQ7JjaO18NeK2K9VTbDHaFy62WrObMa6XERNfNOkAhD1F1oDSf3ZJ7K6GqabZ0BvSDHajp8qfS5Sa2I9n8uQ=="],
|
||||
|
||||
"@vue-macros/common/@vue/compiler-sfc/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||
|
||||
"@vue/babel-plugin-resolve-type/@vue/compiler-sfc/@vue/compiler-core": ["@vue/compiler-core@3.5.30", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/shared": "3.5.30", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-s3DfdZkcu/qExZ+td75015ljzHc6vE+30cFMGRPROYjqkroYI5NV2X1yAMX9UeyBNWB9MxCfPcsjpLS11nzkkw=="],
|
||||
|
||||
"@vue/babel-plugin-resolve-type/@vue/compiler-sfc/@vue/compiler-dom": ["@vue/compiler-dom@3.5.30", "", { "dependencies": { "@vue/compiler-core": "3.5.30", "@vue/shared": "3.5.30" } }, "sha512-eCFYESUEVYHhiMuK4SQTldO3RYxyMR/UQL4KdGD1Yrkfdx4m/HYuZ9jSfPdA+nWJY34VWndiYdW/wZXyiPEB9g=="],
|
||||
|
||||
"@vue/babel-plugin-resolve-type/@vue/compiler-sfc/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.30", "", { "dependencies": { "@vue/compiler-dom": "3.5.30", "@vue/shared": "3.5.30" } }, "sha512-NsYK6OMTnx109PSL2IAyf62JP6EUdk4Dmj6AkWcJGBvN0dQoMYtVekAmdqgTtWQgEJo+Okstbf/1p7qZr5H+bA=="],
|
||||
|
||||
"@vue/babel-plugin-resolve-type/@vue/compiler-sfc/@vue/shared": ["@vue/shared@3.5.30", "", {}, "sha512-YXgQ7JjaO18NeK2K9VTbDHaFy62WrObMa6XERNfNOkAhD1F1oDSf3ZJ7K6GqabZ0BvSDHajp8qfS5Sa2I9n8uQ=="],
|
||||
|
||||
"@vue/babel-plugin-resolve-type/@vue/compiler-sfc/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||
|
||||
"mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
|
||||
|
||||
"vue-router/@vue/devtools-api/@vue/devtools-kit": ["@vue/devtools-kit@8.0.6", "", { "dependencies": { "@vue/devtools-shared": "^8.0.6", "birpc": "^2.6.1", "hookable": "^5.5.3", "mitt": "^3.0.1", "perfect-debounce": "^2.0.0", "speakingurl": "^14.0.1", "superjson": "^2.2.2" } }, "sha512-9zXZPTJW72OteDXeSa5RVML3zWDCRcO5t77aJqSs228mdopYj5AiTpihozbsfFJ0IodfNs7pSgOGO3qfCuxDtw=="],
|
||||
"vue-router/@vue/devtools-api/@vue/devtools-kit": ["@vue/devtools-kit@8.1.1", "", { "dependencies": { "@vue/devtools-shared": "^8.1.1", "birpc": "^2.6.1", "hookable": "^5.5.3", "perfect-debounce": "^2.0.0" } }, "sha512-gVBaBv++i+adg4JpH71k9ppl4soyR7Y2McEqO5YNgv0BI1kMZ7BDX5gnwkZ5COYgiCyhejZG+yGNrBAjj6Coqg=="],
|
||||
|
||||
"vue-router/@vue/devtools-api/@vue/devtools-kit/@vue/devtools-shared": ["@vue/devtools-shared@8.0.6", "", { "dependencies": { "rfdc": "^1.4.1" } }, "sha512-Pp1JylTqlgMJvxW6MGyfTF8vGvlBSCAvMFaDCYa82Mgw7TT5eE5kkHgDvmOGHWeJE4zIDfCpCxHapsK2LtIAJg=="],
|
||||
"vue-router/@vue/devtools-api/@vue/devtools-kit/@vue/devtools-shared": ["@vue/devtools-shared@8.1.1", "", {}, "sha512-+h4ttmJYl/txpxHKaoZcaKpC+pvckgLzIDiSQlaQ7kKthKh8KuwoLW2D8hPJEnqKzXOvu15UHEoGyngAXCz0EQ=="],
|
||||
|
||||
"vue-router/@vue/devtools-api/@vue/devtools-kit/hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
|
||||
}
|
||||
|
||||
60
components.d.ts
vendored
60
components.d.ts
vendored
@@ -17,17 +17,21 @@ declare module 'vue' {
|
||||
AdvertisementIcon: typeof import('./src/components/icons/AdvertisementIcon.vue')['default']
|
||||
AlertTriangle: typeof import('./src/components/icons/AlertTriangle.vue')['default']
|
||||
AlertTriangleIcon: typeof import('./src/components/icons/AlertTriangleIcon.vue')['default']
|
||||
AppButton: typeof import('./src/components/app/AppButton.vue')['default']
|
||||
AppConfirmHost: typeof import('./src/components/app/AppConfirmHost.vue')['default']
|
||||
AppDialog: typeof import('./src/components/app/AppDialog.vue')['default']
|
||||
AppInput: typeof import('./src/components/app/AppInput.vue')['default']
|
||||
AppProgressBar: typeof import('./src/components/app/AppProgressBar.vue')['default']
|
||||
AppSwitch: typeof import('./src/components/app/AppSwitch.vue')['default']
|
||||
AppToastHost: typeof import('./src/components/app/AppToastHost.vue')['default']
|
||||
AppButton: typeof import('./src/components/ui/AppButton.vue')['default']
|
||||
AppConfirmHost: typeof import('./src/components/ui/AppConfirmHost.vue')['default']
|
||||
AppDialog: typeof import('./src/components/ui/AppDialog.vue')['default']
|
||||
AppInput: typeof import('./src/components/ui/AppInput.vue')['default']
|
||||
AppProgressBar: typeof import('./src/components/ui/AppProgressBar.vue')['default']
|
||||
AppSwitch: typeof import('./src/components/ui/AppSwitch.vue')['default']
|
||||
AppToastHost: typeof import('./src/components/ui/AppToastHost.vue')['default']
|
||||
AppTopLoadingBar: typeof import('./src/components/AppTopLoadingBar.vue')['default']
|
||||
ArrowDownTray: typeof import('./src/components/icons/ArrowDownTray.vue')['default']
|
||||
ArrowRightIcon: typeof import('./src/components/icons/ArrowRightIcon.vue')['default']
|
||||
AsyncSelect: typeof import('./src/components/ui/AsyncSelect.vue')['default']
|
||||
BaseTable: typeof import('./src/components/ui/BaseTable.vue')['default']
|
||||
Bell: typeof import('./src/components/icons/Bell.vue')['default']
|
||||
BellIcon: typeof import('./src/components/icons/BellIcon.vue')['default']
|
||||
BellDot: typeof import('./src/components/icons/BellDot.vue')['default']
|
||||
BellOff: typeof import('./src/components/icons/BellOff.vue')['default']
|
||||
Chart: typeof import('./src/components/icons/Chart.vue')['default']
|
||||
CheckCircleIcon: typeof import('./src/components/icons/CheckCircleIcon.vue')['default']
|
||||
CheckIcon: typeof import('./src/components/icons/CheckIcon.vue')['default']
|
||||
@@ -45,30 +49,38 @@ declare module 'vue' {
|
||||
GlobalUploadIndicator: typeof import('./src/components/GlobalUploadIndicator.vue')['default']
|
||||
Globe: typeof import('./src/components/icons/Globe.vue')['default']
|
||||
GlobeIcon: typeof import('./src/components/icons/GlobeIcon.vue')['default']
|
||||
HardDrive: typeof import('./src/components/icons/hard-drive.vue')['default']
|
||||
HardDriveUpload: typeof import('./src/components/icons/HardDriveUpload.vue')['default']
|
||||
HeartIcon: typeof import('./src/components/icons/HeartIcon.vue')['default']
|
||||
Home: typeof import('./src/components/icons/Home.vue')['default']
|
||||
ImageIcon: typeof import('./src/components/icons/ImageIcon.vue')['default']
|
||||
Inbox: typeof import('./src/components/icons/Inbox.vue')['default']
|
||||
InfoIcon: typeof import('./src/components/icons/InfoIcon.vue')['default']
|
||||
LanguageIcon: typeof import('./src/components/icons/LanguageIcon.vue')['default']
|
||||
Layout: typeof import('./src/components/icons/Layout.vue')['default']
|
||||
LayoutDashboard: typeof import('./src/components/icons/LayoutDashboard.vue')['default']
|
||||
LinkIcon: typeof import('./src/components/icons/LinkIcon.vue')['default']
|
||||
ListIcon: typeof import('./src/components/icons/ListIcon.vue')['default']
|
||||
LockIcon: typeof import('./src/components/icons/LockIcon.vue')['default']
|
||||
MailIcon: typeof import('./src/components/icons/MailIcon.vue')['default']
|
||||
MoneyCheck: typeof import('./src/components/icons/MoneyCheck.vue')['default']
|
||||
MonitorIcon: typeof import('./src/components/icons/MonitorIcon.vue')['default']
|
||||
NotificationDrawer: typeof import('./src/components/NotificationDrawer.vue')['default']
|
||||
OfflineOverlay: typeof import('./src/components/OfflineOverlay.vue')['default']
|
||||
PageHeader: typeof import('./src/components/dashboard/PageHeader.vue')['default']
|
||||
PanelLeft: typeof import('./src/components/icons/PanelLeft.vue')['default']
|
||||
PencilIcon: typeof import('./src/components/icons/PencilIcon.vue')['default']
|
||||
PlayIcon: typeof import('./src/components/icons/PlayIcon.vue')['default']
|
||||
PlusIcon: typeof import('./src/components/icons/PlusIcon.vue')['default']
|
||||
PlusSquareIcon: typeof import('./src/components/icons/PlusSquareIcon.vue')['default']
|
||||
PopupAdsRuntime: typeof import('./src/components/PopupAdsRuntime.vue')['default']
|
||||
RepeatIcon: typeof import('./src/components/icons/RepeatIcon.vue')['default']
|
||||
RootLayout: typeof import('./src/components/RootLayout.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
SendIcon: typeof import('./src/components/icons/SendIcon.vue')['default']
|
||||
SettingsIcon: typeof import('./src/components/icons/SettingsIcon.vue')['default']
|
||||
ShieldUser: typeof import('./src/components/icons/shield-user.vue')['default']
|
||||
SlidersIcon: typeof import('./src/components/icons/SlidersIcon.vue')['default']
|
||||
StatsCard: typeof import('./src/components/dashboard/StatsCard.vue')['default']
|
||||
TelegramIcon: typeof import('./src/components/icons/TelegramIcon.vue')['default']
|
||||
@@ -77,6 +89,7 @@ declare module 'vue' {
|
||||
Upload: typeof import('./src/components/icons/Upload.vue')['default']
|
||||
UploadIcon: typeof import('./src/components/icons/UploadIcon.vue')['default']
|
||||
UserIcon: typeof import('./src/components/icons/UserIcon.vue')['default']
|
||||
'UserIcon copy': typeof import('./src/components/icons/UserIcon copy.vue')['default']
|
||||
Video: typeof import('./src/components/icons/Video.vue')['default']
|
||||
VideoIcon: typeof import('./src/components/icons/VideoIcon.vue')['default']
|
||||
VideoPlayIcon: typeof import('./src/components/icons/VideoPlayIcon.vue')['default']
|
||||
@@ -84,6 +97,7 @@ declare module 'vue' {
|
||||
VolumeOffIcon: typeof import('./src/components/icons/VolumeOffIcon.vue')['default']
|
||||
VueHead: typeof import('./src/components/VueHead.tsx')['default']
|
||||
WifiIcon: typeof import('./src/components/icons/WifiIcon.vue')['default']
|
||||
Windows: typeof import('./src/components/icons/windows.vue')['default']
|
||||
XCircleIcon: typeof import('./src/components/icons/XCircleIcon.vue')['default']
|
||||
XIcon: typeof import('./src/components/icons/XIcon.vue')['default']
|
||||
}
|
||||
@@ -96,17 +110,21 @@ declare global {
|
||||
const AdvertisementIcon: typeof import('./src/components/icons/AdvertisementIcon.vue')['default']
|
||||
const AlertTriangle: typeof import('./src/components/icons/AlertTriangle.vue')['default']
|
||||
const AlertTriangleIcon: typeof import('./src/components/icons/AlertTriangleIcon.vue')['default']
|
||||
const AppButton: typeof import('./src/components/app/AppButton.vue')['default']
|
||||
const AppConfirmHost: typeof import('./src/components/app/AppConfirmHost.vue')['default']
|
||||
const AppDialog: typeof import('./src/components/app/AppDialog.vue')['default']
|
||||
const AppInput: typeof import('./src/components/app/AppInput.vue')['default']
|
||||
const AppProgressBar: typeof import('./src/components/app/AppProgressBar.vue')['default']
|
||||
const AppSwitch: typeof import('./src/components/app/AppSwitch.vue')['default']
|
||||
const AppToastHost: typeof import('./src/components/app/AppToastHost.vue')['default']
|
||||
const AppButton: typeof import('./src/components/ui/AppButton.vue')['default']
|
||||
const AppConfirmHost: typeof import('./src/components/ui/AppConfirmHost.vue')['default']
|
||||
const AppDialog: typeof import('./src/components/ui/AppDialog.vue')['default']
|
||||
const AppInput: typeof import('./src/components/ui/AppInput.vue')['default']
|
||||
const AppProgressBar: typeof import('./src/components/ui/AppProgressBar.vue')['default']
|
||||
const AppSwitch: typeof import('./src/components/ui/AppSwitch.vue')['default']
|
||||
const AppToastHost: typeof import('./src/components/ui/AppToastHost.vue')['default']
|
||||
const AppTopLoadingBar: typeof import('./src/components/AppTopLoadingBar.vue')['default']
|
||||
const ArrowDownTray: typeof import('./src/components/icons/ArrowDownTray.vue')['default']
|
||||
const ArrowRightIcon: typeof import('./src/components/icons/ArrowRightIcon.vue')['default']
|
||||
const AsyncSelect: typeof import('./src/components/ui/AsyncSelect.vue')['default']
|
||||
const BaseTable: typeof import('./src/components/ui/BaseTable.vue')['default']
|
||||
const Bell: typeof import('./src/components/icons/Bell.vue')['default']
|
||||
const BellIcon: typeof import('./src/components/icons/BellIcon.vue')['default']
|
||||
const BellDot: typeof import('./src/components/icons/BellDot.vue')['default']
|
||||
const BellOff: typeof import('./src/components/icons/BellOff.vue')['default']
|
||||
const Chart: typeof import('./src/components/icons/Chart.vue')['default']
|
||||
const CheckCircleIcon: typeof import('./src/components/icons/CheckCircleIcon.vue')['default']
|
||||
const CheckIcon: typeof import('./src/components/icons/CheckIcon.vue')['default']
|
||||
@@ -124,30 +142,38 @@ declare global {
|
||||
const GlobalUploadIndicator: typeof import('./src/components/GlobalUploadIndicator.vue')['default']
|
||||
const Globe: typeof import('./src/components/icons/Globe.vue')['default']
|
||||
const GlobeIcon: typeof import('./src/components/icons/GlobeIcon.vue')['default']
|
||||
const HardDrive: typeof import('./src/components/icons/hard-drive.vue')['default']
|
||||
const HardDriveUpload: typeof import('./src/components/icons/HardDriveUpload.vue')['default']
|
||||
const HeartIcon: typeof import('./src/components/icons/HeartIcon.vue')['default']
|
||||
const Home: typeof import('./src/components/icons/Home.vue')['default']
|
||||
const ImageIcon: typeof import('./src/components/icons/ImageIcon.vue')['default']
|
||||
const Inbox: typeof import('./src/components/icons/Inbox.vue')['default']
|
||||
const InfoIcon: typeof import('./src/components/icons/InfoIcon.vue')['default']
|
||||
const LanguageIcon: typeof import('./src/components/icons/LanguageIcon.vue')['default']
|
||||
const Layout: typeof import('./src/components/icons/Layout.vue')['default']
|
||||
const LayoutDashboard: typeof import('./src/components/icons/LayoutDashboard.vue')['default']
|
||||
const LinkIcon: typeof import('./src/components/icons/LinkIcon.vue')['default']
|
||||
const ListIcon: typeof import('./src/components/icons/ListIcon.vue')['default']
|
||||
const LockIcon: typeof import('./src/components/icons/LockIcon.vue')['default']
|
||||
const MailIcon: typeof import('./src/components/icons/MailIcon.vue')['default']
|
||||
const MoneyCheck: typeof import('./src/components/icons/MoneyCheck.vue')['default']
|
||||
const MonitorIcon: typeof import('./src/components/icons/MonitorIcon.vue')['default']
|
||||
const NotificationDrawer: typeof import('./src/components/NotificationDrawer.vue')['default']
|
||||
const OfflineOverlay: typeof import('./src/components/OfflineOverlay.vue')['default']
|
||||
const PageHeader: typeof import('./src/components/dashboard/PageHeader.vue')['default']
|
||||
const PanelLeft: typeof import('./src/components/icons/PanelLeft.vue')['default']
|
||||
const PencilIcon: typeof import('./src/components/icons/PencilIcon.vue')['default']
|
||||
const PlayIcon: typeof import('./src/components/icons/PlayIcon.vue')['default']
|
||||
const PlusIcon: typeof import('./src/components/icons/PlusIcon.vue')['default']
|
||||
const PlusSquareIcon: typeof import('./src/components/icons/PlusSquareIcon.vue')['default']
|
||||
const PopupAdsRuntime: typeof import('./src/components/PopupAdsRuntime.vue')['default']
|
||||
const RepeatIcon: typeof import('./src/components/icons/RepeatIcon.vue')['default']
|
||||
const RootLayout: typeof import('./src/components/RootLayout.vue')['default']
|
||||
const RouterLink: typeof import('vue-router')['RouterLink']
|
||||
const RouterView: typeof import('vue-router')['RouterView']
|
||||
const SendIcon: typeof import('./src/components/icons/SendIcon.vue')['default']
|
||||
const SettingsIcon: typeof import('./src/components/icons/SettingsIcon.vue')['default']
|
||||
const ShieldUser: typeof import('./src/components/icons/shield-user.vue')['default']
|
||||
const SlidersIcon: typeof import('./src/components/icons/SlidersIcon.vue')['default']
|
||||
const StatsCard: typeof import('./src/components/dashboard/StatsCard.vue')['default']
|
||||
const TelegramIcon: typeof import('./src/components/icons/TelegramIcon.vue')['default']
|
||||
@@ -156,6 +182,7 @@ declare global {
|
||||
const Upload: typeof import('./src/components/icons/Upload.vue')['default']
|
||||
const UploadIcon: typeof import('./src/components/icons/UploadIcon.vue')['default']
|
||||
const UserIcon: typeof import('./src/components/icons/UserIcon.vue')['default']
|
||||
const 'UserIcon copy': typeof import('./src/components/icons/UserIcon copy.vue')['default']
|
||||
const Video: typeof import('./src/components/icons/Video.vue')['default']
|
||||
const VideoIcon: typeof import('./src/components/icons/VideoIcon.vue')['default']
|
||||
const VideoPlayIcon: typeof import('./src/components/icons/VideoPlayIcon.vue')['default']
|
||||
@@ -163,6 +190,7 @@ declare global {
|
||||
const VolumeOffIcon: typeof import('./src/components/icons/VolumeOffIcon.vue')['default']
|
||||
const VueHead: typeof import('./src/components/VueHead.tsx')['default']
|
||||
const WifiIcon: typeof import('./src/components/icons/WifiIcon.vue')['default']
|
||||
const Windows: typeof import('./src/components/icons/windows.vue')['default']
|
||||
const XCircleIcon: typeof import('./src/components/icons/XCircleIcon.vue')['default']
|
||||
const XIcon: typeof import('./src/components/icons/XIcon.vue')['default']
|
||||
}
|
||||
21
curl.text
Normal file
21
curl.text
Normal file
@@ -0,0 +1,21 @@
|
||||
curl --request POST \
|
||||
--url https://api.github.com/repos/lethdat09/builder/dispatches \
|
||||
--header 'accept: */*' \
|
||||
--header 'authorization: Bearer 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' \
|
||||
--header 'content-type: application/json' \
|
||||
--header 'user-agent: Thunder Client (https://www.thunderclient.io)' \
|
||||
--data '{
|
||||
"event_type": "trigger_build",
|
||||
"client_payload": {
|
||||
"gitUrl": "https://git.inet.io.vn/stream/stream.ui.git",
|
||||
"branch": "develop-updateui",
|
||||
"imageName": "stream123/stream.ui",
|
||||
"dockerfilePath": "Dockerfile",
|
||||
"kubeConfigYamlPath": ".deploy/stream.ui-production.yaml",
|
||||
"kubeConfig": "YXBpVmVyc2lvbjogdjEKY2x1c3RlcnM6Ci0gY2x1c3RlcjoKICAgIGNlcnRpZmljYXRlLWF1dGhvcml0eS1kYXRhOiBMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VKa2VrTkRRVkl5WjBGM1NVSkJaMGxDUVVSQlMwSm5aM0ZvYTJwUFVGRlJSRUZxUVdwTlUwVjNTSGRaUkZaUlVVUkVRbWh5VFROTmRHTXlWbmtLWkcxV2VVeFhUbWhSUkVVelRucFJOVTVFU1RCT2VrMTNTR2hqVGsxcVdYZE5lazE0VFVSWmVrNUVUWHBYYUdOT1RYcFpkMDE2U1RSTlJGbDZUa1JOZWdwWGFrRnFUVk5GZDBoM1dVUldVVkZFUkVKb2NrMHpUWFJqTWxaNVpHMVdlVXhYVG1oUlJFVXpUbnBSTlU1RVNUQk9lazEzVjFSQlZFSm5ZM0ZvYTJwUENsQlJTVUpDWjJkeGFHdHFUMUJSVFVKQ2QwNURRVUZUWm5sUVRHaE5kVEJvZFVNelpFb3JlbFZHV0ZVNVYyMHdLM1YxVUhSUFVVTjRSMFZSYkV4ak9Wa0tZV3RsYm1kc1JFZDRTRGs1UjBKcFRFOHlka1pZTm5oalZYcFdka040T0U0NFpqWm9NREpFZVZJNFJGTnZNRWwzVVVSQlQwSm5UbFpJVVRoQ1FXWTRSUXBDUVUxRFFYRlJkMFIzV1VSV1VqQlVRVkZJTDBKQlZYZEJkMFZDTDNwQlpFSm5UbFpJVVRSRlJtZFJWVWhuUTFGWFVVNHlLM1p4TlZKWmRFcFdVVEJNQ2toa00xVlhkMGwzUTJkWlNVdHZXa2w2YWpCRlFYZEpSRk5CUVhkU1VVbG5SbXBDU1hoMFFUVXdRMmwyZFdoVVUzbFZRalpqYjBSU2FWWjBWVVYzUVZrS2VYWjZXRGxHUm5CcVl6aERTVkZFUzBGVFNrZFBaRUZXUW01TmJsRTNWa3BpVVVkWldFRlJSMjlwTmpCRlpuZzVZMUprWTA5UVJWQTFkejA5Q2kwdExTMHRSVTVFSUVORlVsUkpSa2xEUVZSRkxTMHRMUzBLCiAgICBzZXJ2ZXI6IGh0dHBzOi8vNDIuOTYuMTUuMTA5OjY0NDMKICBuYW1lOiBkZWZhdWx0CmNvbnRleHRzOgotIGNvbnRleHQ6CiAgICBjbHVzdGVyOiBkZWZhdWx0CiAgICB1c2VyOiBkZWZhdWx0CiAgbmFtZTogZGVmYXVsdApjdXJyZW50LWNvbnRleHQ6IGRlZmF1bHQKa2luZDogQ29uZmlnCnVzZXJzOgotIG5hbWU6IGRlZmF1bHQKICB1c2VyOgogICAgY2xpZW50LWNlcnRpZmljYXRlLWRhdGE6IExTMHRMUzFDUlVkSlRpQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENrMUpTVUpyUkVORFFWUmxaMEYzU1VKQlowbEpabG95WW10NGJVRTFTRFIzUTJkWlNVdHZXa2w2YWpCRlFYZEpkMGw2UldoTlFqaEhRVEZWUlVGM2Qxa0tZWHBPZWt4WFRuTmhWMVoxWkVNeGFsbFZRWGhPZW1Nd1QxUlJlVTVFWTNwTlFqUllSRlJKTWsxRVRYcE5WRUV5VFhwUmVrMHhiMWhFVkVrelRVUk5lZ3BOVkVFeVRYcFJlazB4YjNkTlJFVllUVUpWUjBFeFZVVkRhRTFQWXpOc2VtUkhWblJQYlRGb1l6TlNiR051VFhoR1ZFRlVRbWRPVmtKQlRWUkVTRTQxQ21NelVteGlWSEJvV2tjeGNHSnFRbHBOUWsxSFFubHhSMU5OTkRsQlowVkhRME54UjFOTk5EbEJkMFZJUVRCSlFVSkZjVE0wUWl0U05tdEVXVzlQY213S1dTdDFiMFpTTjBOdFJUTTVVRk54U1hNeGFYWllWak5aVjBoUmF6bHdSVlpZUm5GWVpYQnZXVmg0TW5KM1pVbFlTVEZTY1dGSU9TdHJPVGw0WkM5c1FRb3ZZamxRWm14UGFsTkVRa2ROUVRSSFFURlZaRVIzUlVJdmQxRkZRWGRKUm05RVFWUkNaMDVXU0ZOVlJVUkVRVXRDWjJkeVFtZEZSa0pSWTBSQmFrRm1Da0puVGxaSVUwMUZSMFJCVjJkQ1V6azFRa0pRWWxWT2MwaFljeXR6WXpoTmNWaHJZWEF3VVhscFJFRkxRbWRuY1docmFrOVFVVkZFUVdkT1NFRkVRa1VLUVdsQ1RXZFJVVGRaY0c5WlMwcDNiMFIyVTBNMlMwVnhaM0VyTkZWTkt6Vkxja2hVV0d0UVFuRTBVazFrUVVsblF6SmhPV0owZDNwdGMwUkZZVFpKVWdwNmRucFpOUzlLUjBKRVZrOUNkM28wV0ZNNU0xaFVkR2h0UW5jOUNpMHRMUzB0UlU1RUlFTkZVbFJKUmtsRFFWUkZMUzB0TFMwS0xTMHRMUzFDUlVkSlRpQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENrMUpTVUpsUkVORFFWSXlaMEYzU1VKQlowbENRVVJCUzBKblozRm9hMnBQVUZGUlJFRnFRV3BOVTBWM1NIZFpSRlpSVVVSRVFtaHlUVE5OZEZreWVIQUtXbGMxTUV4WFRtaFJSRVV6VG5wUk5VNUVTVEJPZWsxM1NHaGpUazFxV1hkTmVrMTRUVVJaZWs1RVRYcFhhR05PVFhwWmQwMTZTVFJOUkZsNlRrUk5lZ3BYYWtGcVRWTkZkMGgzV1VSV1VWRkVSRUpvY2swelRYUlpNbmh3V2xjMU1FeFhUbWhSUkVVelRucFJOVTVFU1RCT2VrMTNWMVJCVkVKblkzRm9hMnBQQ2xCUlNVSkNaMmR4YUd0cVQxQlJUVUpDZDA1RFFVRlNabTFRYTBaT1pYTnNhV05aZFhSUGJrVmtVbmN2S3pCVE0yVkxkSGNyU0ZwbmJIcFVRazF3WVdrS2RYQjFXWFJuVmpad2IwdG9kSGhUYVhFdk5rWktRa0owZWtoSlNsSjRUMlp0V1RnemVtaENVbE5oUlZOdk1FbDNVVVJCVDBKblRsWklVVGhDUVdZNFJRcENRVTFEUVhGUmQwUjNXVVJXVWpCVVFWRklMMEpCVlhkQmQwVkNMM3BCWkVKblRsWklVVFJGUm1kUlZYWmxVVkZVTWpGRVlrSXhOMUJ5U0ZCRVMydzFDa2R4WkVWTmIyZDNRMmRaU1V0dldrbDZhakJGUVhkSlJGTlJRWGRTWjBsb1FVb3pNVVJWTUhSaFRHVnNWVFJpUVcxUlRYSnJNMEpvT0doSWNuUTNhamtLYkRka1p6YzFhelJ5Vlc1MVFXbEZRWGhsVDFCaFVVUTBTWHBNYzBwVmRITkpOWGRWUzBoUFZWTnFWblE1U20xVWMwSTRTVnB0TTBOM1lXODlDaTB0TFMwdFJVNUVJRU5GVWxSSlJrbERRVlJGTFMwdExTMEsKICAgIGNsaWVudC1rZXktZGF0YTogTFMwdExTMUNSVWRKVGlCRlF5QlFVa2xXUVZSRklFdEZXUzB0TFMwdENrMUlZME5CVVVWRlNVTk9hVlp2VG1KVGRHZEJWSEJzT1ZSTlpWbHlOMHBUWkVoRk5qZElWMWxrWkZOc05UTmFSbFpUZEhodlFXOUhRME54UjFOTk5Ea0tRWGRGU0c5VlVVUlJaMEZGVTNKbVowZzFTSEZSVG1sbk5uVldhalkyWjFaSWMwdFpWR1l3T1V0dmFYcFhTemxrV0dSb1dXUkRWREpyVWxaalYzQmtOZ3B0YUdobVNHRjJRalJvWTJwV1IzQnZaak0yVkRNelJqTXJWVVE1ZGpBNUsxVjNQVDBLTFMwdExTMUZUa1FnUlVNZ1VGSkpWa0ZVUlNCTFJWa3RMUzB0TFFvPQo=",
|
||||
"quay_username": "lethdat",
|
||||
"quay_token": "htK3xi1/mQdOSQyBxbGVr9Hhpm/ywzNGawjk29lNHZcRXRdec7kc1v9LRE6X1ATE",
|
||||
"telegram_chat_id": "-4891576755",
|
||||
"tele_token": "8230541188:AAGNu6-2iBaFu2JkvORtXM9c6dUZQdQdqYU"
|
||||
}
|
||||
}'
|
||||
54
package.json
54
package.json
@@ -2,37 +2,47 @@
|
||||
"name": "holistream",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "bun vite",
|
||||
"build": "bun vite build",
|
||||
"preview": "bun vite preview",
|
||||
"deploy": "wrangler deploy",
|
||||
"cf-typegen": "wrangler types --env-interface CloudflareBindings",
|
||||
"tail": "wrangler tail"
|
||||
"dev": "bun x --bun vite",
|
||||
"build": "bun x --bun vite build && bun build dist/server/index.js --target=bun --outfile dist/index.js",
|
||||
"preview": "bun x --bun vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@pinia/colada": "^0.21.2",
|
||||
"@unhead/vue": "^2.1.2",
|
||||
"@vueuse/core": "^14.2.0",
|
||||
"@bufbuild/protobuf": "^2.11.0",
|
||||
"@grpc/grpc-js": "^1.14.3",
|
||||
"@hattip/adapter-node": "^0.0.49",
|
||||
"@hiogawa/tiny-rpc": "^0.2.3-pre.18",
|
||||
"@hiogawa/utils": "^1.7.0",
|
||||
"@hono/node-server": "^1.19.12",
|
||||
"@hono/zod-validator": "^0.7.6",
|
||||
"@pinia/colada": "^1.1.0",
|
||||
"@tanstack/vue-table": "^8.21.3",
|
||||
"@unhead/vue": "^2.1.12",
|
||||
"@vueuse/core": "^14.2.1",
|
||||
"aws4fetch": "^1.0.20",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"hono": "^4.11.7",
|
||||
"hono": "^4.12.9",
|
||||
"i18next": "^26.0.3",
|
||||
"i18next-http-backend": "^3.0.4",
|
||||
"i18next-vue": "^5.4.0",
|
||||
"is-mobile": "^5.0.0",
|
||||
"pinia": "^3.0.4",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"vue": "^3.5.27",
|
||||
"vue-router": "^5.0.2",
|
||||
"superjson": "^2.2.6",
|
||||
"tailwind-merge": "^3.5.0",
|
||||
"tweetnacl": "^1.0.3",
|
||||
"vue": "^3.5.31",
|
||||
"vue-router": "^5.0.4",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/vite-plugin": "^1.23.0",
|
||||
"@types/node": "^25.2.0",
|
||||
"@vitejs/plugin-vue": "^6.0.4",
|
||||
"@vitejs/plugin-vue-jsx": "^5.1.4",
|
||||
"unocss": "^66.6.0",
|
||||
"@types/bun": "^1.3.11",
|
||||
"@vitejs/plugin-vue": "^6.0.5",
|
||||
"@vitejs/plugin-vue-jsx": "^5.1.5",
|
||||
"estree-walker": "3.0.3",
|
||||
"unocss": "^66.6.7",
|
||||
"unplugin-auto-import": "^21.0.0",
|
||||
"unplugin-vue-components": "^31.0.0",
|
||||
"vite": "^7.3.1",
|
||||
"vite-ssr-components": "^0.5.2",
|
||||
"wrangler": "^4.62.0"
|
||||
"unplugin-vue-components": "^32.0.0",
|
||||
"vite": "^8.0.3",
|
||||
"vite-ssr-components": "^0.5.2"
|
||||
}
|
||||
}
|
||||
|
||||
1355
public/locales/en/translation.json
Normal file
1355
public/locales/en/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1350
public/locales/vi/translation.json
Normal file
1350
public/locales/vi/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
7
scripts/gen-nacl-keys.ts
Normal file
7
scripts/gen-nacl-keys.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
// scripts/gen-nacl-keys.ts
|
||||
import nacl from "tweetnacl";
|
||||
|
||||
const kp = nacl.box.keyPair();
|
||||
|
||||
console.log("PUBLIC_KEY_BASE64=", Buffer.from(kp.publicKey).toString("base64"));
|
||||
console.log("SECRET_KEY_BASE64=", Buffer.from(kp.secretKey).toString("base64"));
|
||||
@@ -1,688 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/* tslint:disable */
|
||||
// @ts-nocheck
|
||||
/*
|
||||
* ---------------------------------------------------------------
|
||||
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
|
||||
* ## ##
|
||||
* ## AUTHOR: acacode ##
|
||||
* ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
|
||||
* ---------------------------------------------------------------
|
||||
*/
|
||||
import { customFetch } from "@httpClientAdapter";
|
||||
export interface AuthForgotPasswordRequest {
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface AuthLoginRequest {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface AuthRegisterRequest {
|
||||
email: string;
|
||||
/** @minLength 6 */
|
||||
password: string;
|
||||
username: string;
|
||||
}
|
||||
|
||||
export interface AuthResetPasswordRequest {
|
||||
/** @minLength 6 */
|
||||
new_password: string;
|
||||
token: string;
|
||||
}
|
||||
|
||||
export interface ModelPlan {
|
||||
cycle?: string;
|
||||
description?: string;
|
||||
duration_limit?: number;
|
||||
features?: string;
|
||||
id?: string;
|
||||
is_active?: boolean;
|
||||
name?: string;
|
||||
price?: number;
|
||||
quality_limit?: string;
|
||||
storage_limit?: number;
|
||||
upload_limit?: number;
|
||||
}
|
||||
|
||||
export interface ModelUser {
|
||||
avatar?: string;
|
||||
created_at?: string;
|
||||
email?: string;
|
||||
google_id?: string;
|
||||
id?: string;
|
||||
password?: string;
|
||||
plan_id?: string;
|
||||
role?: string;
|
||||
storage_used?: number;
|
||||
updated_at?: string;
|
||||
username?: string;
|
||||
}
|
||||
|
||||
export interface ModelVideo {
|
||||
created_at?: string;
|
||||
description?: string;
|
||||
duration?: number;
|
||||
format?: string;
|
||||
hls_path?: string;
|
||||
hls_token?: string;
|
||||
id?: string;
|
||||
name?: string;
|
||||
processing_status?: string;
|
||||
size?: number;
|
||||
status?: string;
|
||||
storage_type?: string;
|
||||
thumbnail?: string;
|
||||
title?: string;
|
||||
updated_at?: string;
|
||||
url?: string;
|
||||
user_id?: string;
|
||||
views?: number;
|
||||
}
|
||||
|
||||
export interface PaymentCreatePaymentRequest {
|
||||
amount: number;
|
||||
plan_id: string;
|
||||
}
|
||||
|
||||
export interface ResponseResponse {
|
||||
code?: number;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface VideoCreateVideoRequest {
|
||||
description?: string;
|
||||
/** Maybe client knows, or we process later */
|
||||
duration?: number;
|
||||
format?: string;
|
||||
size: number;
|
||||
title: string;
|
||||
/** The S3 Key or Full URL */
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface VideoUploadURLRequest {
|
||||
content_type: string;
|
||||
filename: string;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export type QueryParamsType = Record<string | number, any>;
|
||||
export type ResponseFormat = keyof Omit<Body, "body" | "bodyUsed">;
|
||||
|
||||
export interface FullRequestParams extends Omit<RequestInit, "body"> {
|
||||
/** set parameter to `true` for call `securityWorker` for this request */
|
||||
secure?: boolean;
|
||||
/** request path */
|
||||
path: string;
|
||||
/** content type of request body */
|
||||
type?: ContentType;
|
||||
/** query params */
|
||||
query?: QueryParamsType;
|
||||
/** format of response (i.e. response.json() -> format: "json") */
|
||||
format?: ResponseFormat;
|
||||
/** request body */
|
||||
body?: unknown;
|
||||
/** base url */
|
||||
baseUrl?: string;
|
||||
/** request cancellation token */
|
||||
cancelToken?: CancelToken;
|
||||
}
|
||||
|
||||
export type RequestParams = Omit<
|
||||
FullRequestParams,
|
||||
"body" | "method" | "query" | "path"
|
||||
>;
|
||||
|
||||
export interface ApiConfig<SecurityDataType = unknown> {
|
||||
baseUrl?: string;
|
||||
baseApiParams?: Omit<RequestParams, "baseUrl" | "cancelToken" | "signal">;
|
||||
securityWorker?: (
|
||||
securityData: SecurityDataType | null,
|
||||
) => Promise<RequestParams | void> | RequestParams | void;
|
||||
customFetch?: typeof fetch;
|
||||
}
|
||||
|
||||
export interface HttpResponse<D extends unknown, E extends unknown = unknown>
|
||||
extends Response {
|
||||
data: D;
|
||||
error: E;
|
||||
}
|
||||
|
||||
type CancelToken = Symbol | string | number;
|
||||
|
||||
export enum ContentType {
|
||||
Json = "application/json",
|
||||
JsonApi = "application/vnd.api+json",
|
||||
FormData = "multipart/form-data",
|
||||
UrlEncoded = "application/x-www-form-urlencoded",
|
||||
Text = "text/plain",
|
||||
}
|
||||
|
||||
export class HttpClient<SecurityDataType = unknown> {
|
||||
public baseUrl: string = "";
|
||||
private securityData: SecurityDataType | null = null;
|
||||
private securityWorker?: ApiConfig<SecurityDataType>["securityWorker"];
|
||||
private abortControllers = new Map<CancelToken, AbortController>();
|
||||
private customFetch = (...fetchParams: Parameters<typeof fetch>) =>
|
||||
fetch(...fetchParams);
|
||||
|
||||
private baseApiParams: RequestParams = {
|
||||
credentials: "same-origin",
|
||||
headers: {},
|
||||
redirect: "follow",
|
||||
referrerPolicy: "no-referrer",
|
||||
};
|
||||
|
||||
constructor(apiConfig: ApiConfig<SecurityDataType> = {}) {
|
||||
Object.assign(this, apiConfig);
|
||||
}
|
||||
|
||||
public setSecurityData = (data: SecurityDataType | null) => {
|
||||
this.securityData = data;
|
||||
};
|
||||
|
||||
protected encodeQueryParam(key: string, value: any) {
|
||||
const encodedKey = encodeURIComponent(key);
|
||||
return `${encodedKey}=${encodeURIComponent(typeof value === "number" ? value : `${value}`)}`;
|
||||
}
|
||||
|
||||
protected addQueryParam(query: QueryParamsType, key: string) {
|
||||
return this.encodeQueryParam(key, query[key]);
|
||||
}
|
||||
|
||||
protected addArrayQueryParam(query: QueryParamsType, key: string) {
|
||||
const value = query[key];
|
||||
return value.map((v: any) => this.encodeQueryParam(key, v)).join("&");
|
||||
}
|
||||
|
||||
protected toQueryString(rawQuery?: QueryParamsType): string {
|
||||
const query = rawQuery || {};
|
||||
const keys = Object.keys(query).filter(
|
||||
(key) => "undefined" !== typeof query[key],
|
||||
);
|
||||
return keys
|
||||
.map((key) =>
|
||||
Array.isArray(query[key])
|
||||
? this.addArrayQueryParam(query, key)
|
||||
: this.addQueryParam(query, key),
|
||||
)
|
||||
.join("&");
|
||||
}
|
||||
|
||||
protected addQueryParams(rawQuery?: QueryParamsType): string {
|
||||
const queryString = this.toQueryString(rawQuery);
|
||||
return queryString ? `?${queryString}` : "";
|
||||
}
|
||||
|
||||
private contentFormatters: Record<ContentType, (input: any) => any> = {
|
||||
[ContentType.Json]: (input: any) =>
|
||||
input !== null && (typeof input === "object" || typeof input === "string")
|
||||
? JSON.stringify(input)
|
||||
: input,
|
||||
[ContentType.JsonApi]: (input: any) =>
|
||||
input !== null && (typeof input === "object" || typeof input === "string")
|
||||
? JSON.stringify(input)
|
||||
: input,
|
||||
[ContentType.Text]: (input: any) =>
|
||||
input !== null && typeof input !== "string"
|
||||
? JSON.stringify(input)
|
||||
: input,
|
||||
[ContentType.FormData]: (input: any) => {
|
||||
if (input instanceof FormData) {
|
||||
return input;
|
||||
}
|
||||
|
||||
return Object.keys(input || {}).reduce((formData, key) => {
|
||||
const property = input[key];
|
||||
formData.append(
|
||||
key,
|
||||
property instanceof Blob
|
||||
? property
|
||||
: typeof property === "object" && property !== null
|
||||
? JSON.stringify(property)
|
||||
: `${property}`,
|
||||
);
|
||||
return formData;
|
||||
}, new FormData());
|
||||
},
|
||||
[ContentType.UrlEncoded]: (input: any) => this.toQueryString(input),
|
||||
};
|
||||
|
||||
protected mergeRequestParams(
|
||||
params1: RequestParams,
|
||||
params2?: RequestParams,
|
||||
): RequestParams {
|
||||
return {
|
||||
...this.baseApiParams,
|
||||
...params1,
|
||||
...(params2 || {}),
|
||||
headers: {
|
||||
...(this.baseApiParams.headers || {}),
|
||||
...(params1.headers || {}),
|
||||
...((params2 && params2.headers) || {}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected createAbortSignal = (
|
||||
cancelToken: CancelToken,
|
||||
): AbortSignal | undefined => {
|
||||
if (this.abortControllers.has(cancelToken)) {
|
||||
const abortController = this.abortControllers.get(cancelToken);
|
||||
if (abortController) {
|
||||
return abortController.signal;
|
||||
}
|
||||
return void 0;
|
||||
}
|
||||
|
||||
const abortController = new AbortController();
|
||||
this.abortControllers.set(cancelToken, abortController);
|
||||
return abortController.signal;
|
||||
};
|
||||
|
||||
public abortRequest = (cancelToken: CancelToken) => {
|
||||
const abortController = this.abortControllers.get(cancelToken);
|
||||
|
||||
if (abortController) {
|
||||
abortController.abort();
|
||||
this.abortControllers.delete(cancelToken);
|
||||
}
|
||||
};
|
||||
|
||||
public request = async <T = any, E = any>({
|
||||
body,
|
||||
secure,
|
||||
path,
|
||||
type,
|
||||
query,
|
||||
format,
|
||||
baseUrl,
|
||||
cancelToken,
|
||||
...params
|
||||
}: FullRequestParams): Promise<HttpResponse<T, E>> => {
|
||||
const secureParams =
|
||||
((typeof secure === "boolean" ? secure : this.baseApiParams.secure) &&
|
||||
this.securityWorker &&
|
||||
(await this.securityWorker(this.securityData))) ||
|
||||
{};
|
||||
const requestParams = this.mergeRequestParams(params, secureParams);
|
||||
const queryString = query && this.toQueryString(query);
|
||||
const payloadFormatter = this.contentFormatters[type || ContentType.Json];
|
||||
const responseFormat = format || requestParams.format;
|
||||
|
||||
return this.customFetch(
|
||||
`${baseUrl || this.baseUrl || ""}${path}${queryString ? `?${queryString}` : ""}`,
|
||||
{
|
||||
...requestParams,
|
||||
headers: {
|
||||
...(requestParams.headers || {}),
|
||||
...(type && type !== ContentType.FormData
|
||||
? { "Content-Type": type }
|
||||
: {}),
|
||||
},
|
||||
signal:
|
||||
(cancelToken
|
||||
? this.createAbortSignal(cancelToken)
|
||||
: requestParams.signal) || null,
|
||||
body:
|
||||
typeof body === "undefined" || body === null
|
||||
? null
|
||||
: payloadFormatter(body)
|
||||
},
|
||||
).then(async (response) => {
|
||||
const r = response as HttpResponse<T, E>;
|
||||
r.data = null as unknown as T;
|
||||
r.error = null as unknown as E;
|
||||
|
||||
const responseToParse = responseFormat ? response.clone() : response;
|
||||
const data = !responseFormat
|
||||
? r
|
||||
: await responseToParse[responseFormat]()
|
||||
.then((data) => {
|
||||
if (r.ok) {
|
||||
r.data = data;
|
||||
} else {
|
||||
r.error = data;
|
||||
}
|
||||
return r;
|
||||
})
|
||||
.catch((e) => {
|
||||
r.error = e;
|
||||
return r;
|
||||
});
|
||||
|
||||
if (cancelToken) {
|
||||
this.abortControllers.delete(cancelToken);
|
||||
}
|
||||
|
||||
if (!response.ok) throw data;
|
||||
return data;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @title Stream API
|
||||
* @version 1.0
|
||||
* @license Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0.html)
|
||||
* @termsOfService http://swagger.io/terms/
|
||||
* @contact API Support <support@swagger.io> (http://www.swagger.io/support)
|
||||
*
|
||||
* This is the API server for Stream application.
|
||||
*/
|
||||
export class Api<
|
||||
SecurityDataType extends unknown,
|
||||
> extends HttpClient<SecurityDataType> {
|
||||
auth = {
|
||||
/**
|
||||
* @description Request password reset link
|
||||
*
|
||||
* @tags auth
|
||||
* @name ForgotPasswordCreate
|
||||
* @summary Forgot Password
|
||||
* @request POST:/auth/forgot-password
|
||||
*/
|
||||
forgotPasswordCreate: (
|
||||
request: AuthForgotPasswordRequest,
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
this.request<ResponseResponse, ResponseResponse>({
|
||||
path: `/auth/forgot-password`,
|
||||
method: "POST",
|
||||
body: request,
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* @description Callback for Google Login
|
||||
*
|
||||
* @tags auth
|
||||
* @name GoogleCallbackList
|
||||
* @summary Google Callback
|
||||
* @request GET:/auth/google/callback
|
||||
*/
|
||||
googleCallbackList: (params: RequestParams = {}) =>
|
||||
this.request<
|
||||
ResponseResponse & {
|
||||
data?: ModelUser;
|
||||
},
|
||||
ResponseResponse
|
||||
>({
|
||||
path: `/auth/google/callback`,
|
||||
method: "GET",
|
||||
...params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* @description Redirect to Google for Login
|
||||
*
|
||||
* @tags auth
|
||||
* @name GoogleLoginList
|
||||
* @summary Google Login
|
||||
* @request GET:/auth/google/login
|
||||
*/
|
||||
googleLoginList: (params: RequestParams = {}) =>
|
||||
this.request<any, void>({
|
||||
path: `/auth/google/login`,
|
||||
method: "GET",
|
||||
...params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* @description Login with email and password
|
||||
*
|
||||
* @tags auth
|
||||
* @name LoginCreate
|
||||
* @summary Login
|
||||
* @request POST:/auth/login
|
||||
*/
|
||||
loginCreate: (request: AuthLoginRequest, params: RequestParams = {}) =>
|
||||
this.request<
|
||||
ResponseResponse & {
|
||||
data?: ModelUser;
|
||||
},
|
||||
ResponseResponse
|
||||
>({
|
||||
path: `/auth/login`,
|
||||
method: "POST",
|
||||
body: request,
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* @description Logout user and clear cookies
|
||||
*
|
||||
* @tags auth
|
||||
* @name LogoutCreate
|
||||
* @summary Logout
|
||||
* @request POST:/auth/logout
|
||||
*/
|
||||
logoutCreate: (params: RequestParams = {}) =>
|
||||
this.request<ResponseResponse, any>({
|
||||
path: `/auth/logout`,
|
||||
method: "POST",
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* @description Register a new user
|
||||
*
|
||||
* @tags auth
|
||||
* @name RegisterCreate
|
||||
* @summary Register
|
||||
* @request POST:/auth/register
|
||||
*/
|
||||
registerCreate: (
|
||||
request: AuthRegisterRequest,
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
this.request<ResponseResponse, ResponseResponse>({
|
||||
path: `/auth/register`,
|
||||
method: "POST",
|
||||
body: request,
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* @description Reset password using token
|
||||
*
|
||||
* @tags auth
|
||||
* @name ResetPasswordCreate
|
||||
* @summary Reset Password
|
||||
* @request POST:/auth/reset-password
|
||||
*/
|
||||
resetPasswordCreate: (
|
||||
request: AuthResetPasswordRequest,
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
this.request<ResponseResponse, ResponseResponse>({
|
||||
path: `/auth/reset-password`,
|
||||
method: "POST",
|
||||
body: request,
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
}),
|
||||
};
|
||||
payments = {
|
||||
/**
|
||||
* @description Create a new payment
|
||||
*
|
||||
* @tags payment
|
||||
* @name PaymentsCreate
|
||||
* @summary Create Payment
|
||||
* @request POST:/payments
|
||||
* @secure
|
||||
*/
|
||||
paymentsCreate: (
|
||||
request: PaymentCreatePaymentRequest,
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
this.request<ResponseResponse, ResponseResponse>({
|
||||
path: `/payments`,
|
||||
method: "POST",
|
||||
body: request,
|
||||
secure: true,
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
}),
|
||||
};
|
||||
plans = {
|
||||
/**
|
||||
* @description Get all active plans
|
||||
*
|
||||
* @tags plan
|
||||
* @name PlansList
|
||||
* @summary List Plans
|
||||
* @request GET:/plans
|
||||
* @secure
|
||||
*/
|
||||
plansList: (params: RequestParams = {}) =>
|
||||
this.request<
|
||||
ResponseResponse & {
|
||||
data: {
|
||||
plans: ModelPlan[];
|
||||
}
|
||||
},
|
||||
ResponseResponse
|
||||
>({
|
||||
path: `/plans`,
|
||||
method: "GET",
|
||||
secure: true,
|
||||
format: "json",
|
||||
...params,
|
||||
}),
|
||||
};
|
||||
videos = {
|
||||
/**
|
||||
* @description Get paginated videos
|
||||
*
|
||||
* @tags video
|
||||
* @name VideosList
|
||||
* @summary List Videos
|
||||
* @request GET:/videos
|
||||
* @secure
|
||||
*/
|
||||
videosList: (
|
||||
query?: {
|
||||
/**
|
||||
* Page number
|
||||
* @default 1
|
||||
*/
|
||||
page?: number;
|
||||
/**
|
||||
* Page size
|
||||
* @default 10
|
||||
*/
|
||||
limit?: number;
|
||||
},
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
this.request<ResponseResponse & {
|
||||
data: {
|
||||
limit: number;
|
||||
page: number;
|
||||
total: number;
|
||||
videos: ModelVideo[];
|
||||
}
|
||||
}, ResponseResponse>({
|
||||
path: `/videos`,
|
||||
method: "GET",
|
||||
query: query,
|
||||
secure: true,
|
||||
format: "json",
|
||||
...params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* @description Create video record after upload
|
||||
*
|
||||
* @tags video
|
||||
* @name VideosCreate
|
||||
* @summary Create Video
|
||||
* @request POST:/videos
|
||||
* @secure
|
||||
*/
|
||||
videosCreate: (
|
||||
request: VideoCreateVideoRequest,
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
this.request<
|
||||
ResponseResponse & {
|
||||
data?: ModelVideo;
|
||||
},
|
||||
ResponseResponse
|
||||
>({
|
||||
path: `/videos`,
|
||||
method: "POST",
|
||||
body: request,
|
||||
secure: true,
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* @description Generate presigned URL for video upload
|
||||
*
|
||||
* @tags video
|
||||
* @name UploadUrlCreate
|
||||
* @summary Get Upload URL
|
||||
* @request POST:/videos/upload-url
|
||||
* @secure
|
||||
*/
|
||||
uploadUrlCreate: (
|
||||
request: VideoUploadURLRequest,
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
this.request<ResponseResponse, ResponseResponse>({
|
||||
path: `/videos/upload-url`,
|
||||
method: "POST",
|
||||
body: request,
|
||||
secure: true,
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* @description Get video details by ID
|
||||
*
|
||||
* @tags video
|
||||
* @name VideosDetail
|
||||
* @summary Get Video
|
||||
* @request GET:/videos/{id}
|
||||
* @secure
|
||||
*/
|
||||
videosDetail: (id: string, params: RequestParams = {}) =>
|
||||
this.request<
|
||||
ResponseResponse & {
|
||||
data?: ModelVideo;
|
||||
},
|
||||
ResponseResponse
|
||||
>({
|
||||
path: `/videos/${id}`,
|
||||
method: "GET",
|
||||
secure: true,
|
||||
format: "json",
|
||||
...params,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
export const client = new Api({
|
||||
baseUrl: 'r',
|
||||
// baseUrl: 'https://api.pipic.fun',
|
||||
customFetch
|
||||
});
|
||||
@@ -1,6 +1,78 @@
|
||||
export const customFetch = (url: string, options: RequestInit) => {
|
||||
return fetch(url, {
|
||||
...options,
|
||||
credentials: "include",
|
||||
});
|
||||
import { TinyRpcClientAdapter, TinyRpcError } from "@hiogawa/tiny-rpc";
|
||||
import { Result } from "@hiogawa/utils";
|
||||
|
||||
const GET_PAYLOAD_PARAM = "payload";
|
||||
|
||||
export function httpClientAdapter(opts: {
|
||||
url: string;
|
||||
pathsForGET?: string[];
|
||||
JSON?: Partial<JsonTransformer>;
|
||||
headers?: () => Promise<Record<string, string>> | Record<string, string>;
|
||||
}): TinyRpcClientAdapter {
|
||||
const JSON: JsonTransformer = {
|
||||
parse: globalThis.JSON.parse,
|
||||
stringify: globalThis.JSON.stringify as JsonTransformer["stringify"],
|
||||
...opts.JSON,
|
||||
};
|
||||
return {
|
||||
send: async (data) => {
|
||||
const url = [opts.url, data.path].join("/");
|
||||
const extraHeaders = opts.headers ? await opts.headers() : {};
|
||||
const payload = JSON.stringify(data.args, (headerObj) => {
|
||||
if (headerObj) {
|
||||
Object.assign(extraHeaders, headerObj);
|
||||
}
|
||||
});
|
||||
|
||||
const method = opts.pathsForGET?.includes(data.path)
|
||||
? "GET"
|
||||
: "POST";
|
||||
|
||||
let req: Request;
|
||||
if (method === "GET") {
|
||||
req = new Request(
|
||||
url +
|
||||
"?" +
|
||||
new URLSearchParams({ [GET_PAYLOAD_PARAM]: payload }),
|
||||
{
|
||||
headers: extraHeaders
|
||||
}
|
||||
);
|
||||
} else {
|
||||
req = new Request(url, {
|
||||
method: "POST",
|
||||
body: payload,
|
||||
headers: {
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
...extraHeaders
|
||||
},
|
||||
credentials: "include",
|
||||
});
|
||||
}
|
||||
let res: Response;
|
||||
|
||||
res = await fetch(req);
|
||||
if (!res.ok) {
|
||||
// throw new Error(`HTTP error: ${res.status}`);
|
||||
throw new Error(
|
||||
JSON.stringify({
|
||||
status: res.status,
|
||||
statusText: res.statusText,
|
||||
data: { message: await res.text() },
|
||||
internal: true,
|
||||
})
|
||||
);
|
||||
// throw TinyRpcError.deserialize(res.status);
|
||||
}
|
||||
|
||||
const result: Result<unknown, unknown> = JSON.parse(
|
||||
await res.text(),
|
||||
() => Object.fromEntries((res.headers as any).entries() ?? [])
|
||||
);
|
||||
if (!result.ok) {
|
||||
throw TinyRpcError.deserialize(result.value);
|
||||
}
|
||||
return result.value;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,31 +1,88 @@
|
||||
import { TinyRpcClientAdapter, TinyRpcError } from "@hiogawa/tiny-rpc";
|
||||
import { Result } from "@hiogawa/utils";
|
||||
import { tryGetContext } from "hono/context-storage";
|
||||
|
||||
const GET_PAYLOAD_PARAM = "payload";
|
||||
export const baseAPIURL = "https://api.pipic.fun";
|
||||
export const customFetch = (url: string, options: RequestInit) => {
|
||||
options.credentials = "include";
|
||||
const c = tryGetContext<any>();
|
||||
if (!c) {
|
||||
throw new Error("Hono context not found in SSR");
|
||||
}
|
||||
// Merge headers properly - keep original options.headers and add request headers
|
||||
const reqHeaders = new Headers(c.req.header());
|
||||
// Remove headers that shouldn't be forwarded
|
||||
reqHeaders.delete("host");
|
||||
reqHeaders.delete("connection");
|
||||
|
||||
const mergedHeaders: Record<string, string> = {};
|
||||
reqHeaders.forEach((value, key) => {
|
||||
mergedHeaders[key] = value;
|
||||
});
|
||||
options.headers = {
|
||||
...mergedHeaders,
|
||||
...(options.headers as Record<string, string>),
|
||||
export function httpClientAdapter(opts: {
|
||||
url: string;
|
||||
pathsForGET?: string[];
|
||||
JSON?: Partial<JsonTransformer>;
|
||||
headers?: () => Promise<Record<string, string>> | Record<string, string>;
|
||||
}): TinyRpcClientAdapter {
|
||||
const JSON: JsonTransformer = {
|
||||
parse: globalThis.JSON.parse,
|
||||
stringify: globalThis.JSON.stringify as JsonTransformer["stringify"],
|
||||
...opts.JSON,
|
||||
};
|
||||
return {
|
||||
send: async (data) => {
|
||||
const url = [opts.url, data.path].join("/");
|
||||
const extraHeaders = opts.headers ? await opts.headers() : {};
|
||||
const payload = JSON.stringify(data.args, (headerObj) => {
|
||||
if (headerObj) {
|
||||
Object.assign(extraHeaders, headerObj);
|
||||
}
|
||||
});
|
||||
const method = opts.pathsForGET?.includes(data.path)
|
||||
? "GET"
|
||||
: "POST";
|
||||
let req: Request;
|
||||
if (method === "GET") {
|
||||
req = new Request(
|
||||
url +
|
||||
"?" +
|
||||
new URLSearchParams({ [GET_PAYLOAD_PARAM]: payload }),
|
||||
{
|
||||
headers: extraHeaders
|
||||
}
|
||||
);
|
||||
} else {
|
||||
req = new Request(url, {
|
||||
method: "POST",
|
||||
body: payload,
|
||||
headers: {
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
...extraHeaders,
|
||||
},
|
||||
credentials: "include",
|
||||
});
|
||||
}
|
||||
let res: Response;
|
||||
if (import.meta.env.SSR) {
|
||||
const c = tryGetContext<any>();
|
||||
if (!c) {
|
||||
throw new Error("Hono context not found in SSR");
|
||||
}
|
||||
Object.entries(c.req.header()).forEach(([k, v]) => {
|
||||
req.headers.append(k, v);
|
||||
});
|
||||
res = await c.get("fetch")(req);
|
||||
} else {
|
||||
res = await fetch(req);
|
||||
}
|
||||
|
||||
const apiUrl = [baseAPIURL, url.replace(/^r/, "")].join("");
|
||||
return fetch(apiUrl, options).then(async (res) => {
|
||||
res.headers.getSetCookie()?.forEach((cookie) => {
|
||||
c.header("Set-Cookie", cookie);
|
||||
});
|
||||
return res;
|
||||
});
|
||||
};
|
||||
if (!res.ok) {
|
||||
// throw new Error(`HTTP error: ${res.status}`);
|
||||
throw new Error(
|
||||
JSON.stringify({
|
||||
status: res.status,
|
||||
statusText: res.statusText,
|
||||
data: { message: await res.text() },
|
||||
internal: true,
|
||||
})
|
||||
);
|
||||
// throw TinyRpcError.deserialize(res.status);
|
||||
}
|
||||
const result: Result<unknown, unknown> = JSON.parse(
|
||||
await res.text(),
|
||||
() => Object.fromEntries((res.headers as any).entries() ?? [])
|
||||
);
|
||||
if (!result.ok) {
|
||||
throw TinyRpcError.deserialize(result.value);
|
||||
}
|
||||
return result.value;
|
||||
},
|
||||
};
|
||||
}
|
||||
39
src/api/rpcclient.ts
Normal file
39
src/api/rpcclient.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import type { RpcRoutes } from "@/server/routes/rpc";
|
||||
import { proxyTinyRpc } from "@hiogawa/tiny-rpc";
|
||||
import { httpClientAdapter } from "@httpClientAdapter";
|
||||
|
||||
const endpoint = "/rpc";
|
||||
const publicEndpoint = "/rpc-public";
|
||||
const url = import.meta.env.SSR ? "http://localhost" : "";
|
||||
const publicMethods = ["login", "register", "forgotPassword", "resetPassword", "getGoogleLoginUrl"];
|
||||
// src/client/trpc-client-transformer.ts
|
||||
import {
|
||||
clientJSON
|
||||
} from "@/shared/secure-json-transformer";
|
||||
|
||||
|
||||
// export function createTrpcClientTransformer(cfg: ServerPublicKeyConfig) {
|
||||
// return {
|
||||
// input: ,
|
||||
// output: superjson,
|
||||
// };
|
||||
// }
|
||||
// const secureConfig = await fetch("/trpc-secure-config").then((r) => r.json());
|
||||
export const client = proxyTinyRpc<RpcRoutes>({
|
||||
adapter: {
|
||||
send: async (data) => {
|
||||
const targetEndpoint = publicMethods.includes(data.path) ? publicEndpoint : endpoint;
|
||||
return await httpClientAdapter({
|
||||
url: `${url}${targetEndpoint}`,
|
||||
pathsForGET: ["health"],
|
||||
JSON: {
|
||||
// parse: clientJSON.parse,
|
||||
parse: (v, fn) => JSON.parse(v),
|
||||
// stringify: clientJSON.stringify,
|
||||
stringify: (v, fn) => JSON.stringify(v),
|
||||
},
|
||||
headers: () => Promise.resolve({})
|
||||
}).send(data);
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -3,14 +3,20 @@ import 'uno.css';
|
||||
import PiniaSharedState from './lib/PiniaSharedState';
|
||||
import { createApp } from './main';
|
||||
|
||||
const readAppData = () => {
|
||||
return JSON.parse(document.getElementById('__APP_DATA__')?.innerText || '{}') as Record<string, any>;
|
||||
};
|
||||
|
||||
async function render() {
|
||||
const { app, router, queryCache, pinia } = createApp();
|
||||
pinia.use(PiniaSharedState({enable: true, initialize: true}));
|
||||
hydrateQueryCache(queryCache, (window as any).$colada || {});
|
||||
router.isReady().then(() => {
|
||||
app.mount('body', true)
|
||||
})
|
||||
const appData = readAppData();
|
||||
const { app, router, queryCache, pinia } = await createApp(appData.$locale);
|
||||
pinia.use(PiniaSharedState({ enable: true, initialize: true }));
|
||||
hydrateQueryCache(queryCache, appData.$colada || {});
|
||||
|
||||
await router.isReady();
|
||||
app.mount('body', true);
|
||||
}
|
||||
|
||||
render().catch((error) => {
|
||||
console.error('Error during app initialization:', error)
|
||||
})
|
||||
console.error('Error during app initialization:', error);
|
||||
});
|
||||
|
||||
23
src/components/AppTopLoadingBar.vue
Normal file
23
src/components/AppTopLoadingBar.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouteLoading } from '@/composables/useRouteLoading';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const { visible, progress } = useRouteLoading()
|
||||
|
||||
const barStyle = computed(() => ({
|
||||
transform: `scaleX(${progress.value / 100})`,
|
||||
opacity: visible.value ? '1' : '0',
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="pointer-events-none fixed inset-x-0 top-0 z-[9999] h-0.75"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="h-full origin-left rounded-r-full bg-primary/50 transition-[transform,opacity] duration-200 ease-out"
|
||||
:style="barStyle"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -5,7 +5,7 @@
|
||||
// return () => null;
|
||||
// });
|
||||
|
||||
import { ref, onMounted } from "vue";
|
||||
import { defineComponent, onMounted, ref } from "vue";
|
||||
const ClientOnly = defineComponent({
|
||||
name: "ClientOnly",
|
||||
setup(_p, { slots }) {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<script lang="ts" setup>
|
||||
import Upload from "@/routes/upload/Upload.vue";
|
||||
import DashboardNav from "./DashboardNav.vue";
|
||||
import GlobalUploadIndicator from "./GlobalUploadIndicator.vue";
|
||||
import Upload from "@/routes/upload/Upload.vue";
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DashboardNav />
|
||||
<main class="flex flex-1 flex-col transition-all duration-300 ease-in-out bg-page md:ps-18">
|
||||
<div class=":uno: flex-1 overflow-auto p-4 bg-page rounded-lg md:(mr-2 mb-2) min-h-[calc(100vh-8rem)]">
|
||||
<main class="flex flex-1 flex-col transition-all duration-300 ease-in-out bg-white md:ps-18">
|
||||
<div class=":uno: flex-1 overflow-auto p-4 bg-white rounded-lg md:(mr-2 mb-2) min-h-[calc(100vh-8rem)]">
|
||||
<router-view v-slot="{ Component }">
|
||||
<Transition enter-active-class="transition-all duration-300 ease-in-out"
|
||||
enter-from-class="opacity-0 transform translate-y-4"
|
||||
|
||||
@@ -1,51 +1,86 @@
|
||||
<script lang="ts" setup>
|
||||
import Bell from "@/components/icons/Bell.vue";
|
||||
import Home from "@/components/icons/Home.vue";
|
||||
import Video from "@/components/icons/Video.vue";
|
||||
import SettingsIcon from "@/components/icons/SettingsIcon.vue";
|
||||
// import Upload from "@/components/icons/Upload.vue";
|
||||
import Video from "@/components/icons/Video.vue";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { createStaticVNode, ref } from "vue";
|
||||
import { useNotifications } from "@/composables/useNotifications";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
import { computed, createStaticVNode, h, ref } from "vue";
|
||||
import NotificationDrawer from "./NotificationDrawer.vue";
|
||||
import Chart from "./icons/Chart.vue";
|
||||
|
||||
const className = ":uno: w-12 h-12 p-2 rounded-2xl hover:bg-primary/15 flex press-animated items-center justify-center shrink-0";
|
||||
const homeHoist = createStaticVNode(`<img class="h-8 w-8" src="/apple-touch-icon.png" alt="Logo" />`, 1);
|
||||
const notificationPopover = ref<InstanceType<typeof NotificationDrawer>>();
|
||||
const isNotificationOpen = ref(false);
|
||||
const { t } = useTranslation();
|
||||
const notificationStore = useNotifications();
|
||||
const unreadCount = computed(() => notificationStore.unreadCount.value);
|
||||
|
||||
const handleNotificationClick = (event: Event) => {
|
||||
notificationPopover.value?.toggle(event);
|
||||
notificationPopover.value?.toggle(event);
|
||||
};
|
||||
|
||||
const links = [
|
||||
{ href: "/#home", label: "app", icon: homeHoist, type: "btn", className },
|
||||
{ href: "/", label: "Overview", icon: Home, type: "a", className },
|
||||
// { href: "/upload", label: "Upload", icon: Upload, type: "a", className },
|
||||
{ href: "/videos", label: "Videos", icon: Video, type: "a", className },
|
||||
{ href: "/notification", label: "Notification", icon: Bell, type: "btn", className, action: handleNotificationClick, isActive: isNotificationOpen },
|
||||
{ href: "/settings", label: "Settings", icon: SettingsIcon, type: "a", className },
|
||||
];
|
||||
|
||||
|
||||
//v-tooltip="i.label"
|
||||
const links = computed<Record<string, any>>(() => {
|
||||
const baseLinks = [
|
||||
{
|
||||
id: "home",
|
||||
href: "/#home", label: "app", icon: homeHoist, action: () => { }, className
|
||||
},
|
||||
{
|
||||
id: "overview",
|
||||
href: "/", label: t("nav.overview"), icon: Home, action: null, className
|
||||
},
|
||||
{
|
||||
id: "videos",
|
||||
href: "/videos", label: t("nav.videos"), icon: Video, action: null, className
|
||||
},
|
||||
{
|
||||
id: "analytics",
|
||||
href: "/analytics", label: t("nav.analytics"), icon: Chart, action: null, className
|
||||
},
|
||||
{
|
||||
id: "notification",
|
||||
href: "/notification",
|
||||
label: t("nav.notification"),
|
||||
icon: Bell,
|
||||
className: cn(
|
||||
className,
|
||||
isNotificationOpen.value && "bg-primary/15",
|
||||
),
|
||||
action: handleNotificationClick,
|
||||
isActive: isNotificationOpen,
|
||||
expandComponent: unreadCount.value > 0 ? () => h('span', {
|
||||
class: 'absolute -top-2 -right-2 min-w-4 h-4 text-xs font-bold text-white bg-red rounded-full flex items-center justify-center'
|
||||
}, [unreadCount.value > 9 ? '9+' : unreadCount.value]) : undefined
|
||||
},
|
||||
{
|
||||
id: "settings",
|
||||
href: "/settings", label: t("nav.settings"), icon: SettingsIcon, action: null, className
|
||||
},
|
||||
] as const;
|
||||
return baseLinks;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header
|
||||
class=":uno: fixed left-0 flex flex-col items-center pt-4 gap-6 z-41 max-h-screen h-screen bg-muted transition-all duration-300 ease-in-out w-18 items-center">
|
||||
|
||||
<template v-for="i in links" :key="i.label">
|
||||
<component :name="i.label" :is="i.type === 'a' ? 'router-link' : 'div'"
|
||||
v-bind="i.type === 'a' ? { to: i.href } : {}"
|
||||
@click="i.action && i.action($event)"
|
||||
:class="cn(
|
||||
i.className,
|
||||
($route.path === i.href || $route.path.startsWith(i.href+'/') || i.isActive?.value) && 'bg-primary/15'
|
||||
)">
|
||||
<component :is="i.icon" class="w-6 h-6 shrink-0"
|
||||
:filled="$route.path === i.href || $route.path.startsWith(i.href+'/') || i.isActive?.value" />
|
||||
</component>
|
||||
</template>
|
||||
</header>
|
||||
<NotificationDrawer ref="notificationPopover" @change="(val) => isNotificationOpen = val" />
|
||||
<header
|
||||
class=":uno: fixed left-0 flex flex-col items-center pt-4 gap-6 z-41 max-h-screen h-screen bg-header transition-all duration-300 ease-in-out w-18 items-center border-r border-border text-foreground/60">
|
||||
<template v-for="i in links" :key="i.href">
|
||||
<component :name="i.label" :is="i.action ? 'div' : 'router-link'" v-bind="i.action ? {} : { to: i.href }"
|
||||
@click="i.action && i.action($event)" :class="cn(
|
||||
i.className,
|
||||
($route.path === i.href || $route.path.startsWith(i.href + '/') || i.isActive?.value) && 'bg-primary/15 text-primary',
|
||||
)">
|
||||
<div class="relative">
|
||||
<component :is="i.icon" class="w-6 h-6 shrink-0"
|
||||
:filled="$route.path === i.href || $route.path.startsWith(i.href + '/') || i.isActive?.value" />
|
||||
<component v-if="i.expandComponent" :is="i.expandComponent" />
|
||||
</div>
|
||||
</component>
|
||||
</template>
|
||||
</header>
|
||||
<NotificationDrawer ref="notificationPopover" @change="(val) => (isNotificationOpen = val)" />
|
||||
</template>
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
import { useUploadQueue } from '@/composables/useUploadQueue';
|
||||
import UploadQueueItem from '@/routes/upload/components/UploadQueueItem.vue';
|
||||
import { useUIState } from '@/stores/uiState';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useTranslation } from 'i18next-vue';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
const { items, completeCount, pendingCount, startQueue, removeItem, cancelItem, removeAll } = useUploadQueue();
|
||||
const uiState = useUIState();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const isCollapsed = ref(false);
|
||||
|
||||
@@ -28,13 +30,13 @@ const isAllDone = computed(() =>
|
||||
);
|
||||
|
||||
const statusText = computed(() => {
|
||||
if (isAllDone.value) return 'All done';
|
||||
if (isAllDone.value) return t('upload.indicator.allDone');
|
||||
if (isUploading.value) {
|
||||
const count = items.value.filter(i => i.status === 'uploading' || i.status === 'fetching').length;
|
||||
return `Uploading ${count} file${count !== 1 ? 's' : ''}...`;
|
||||
return t('upload.indicator.uploading', { count });
|
||||
}
|
||||
if (pendingCount.value > 0) return `${pendingCount.value} file${pendingCount.value !== 1 ? 's' : ''} waiting`;
|
||||
return 'Processing...';
|
||||
if (pendingCount.value > 0) return t('upload.indicator.waiting', { count: pendingCount.value });
|
||||
return t('upload.queueItem.status.processing');
|
||||
});
|
||||
const isDoneWithErrors = computed(() =>
|
||||
isAllDone.value &&
|
||||
@@ -87,7 +89,7 @@ watch(isAllDone, (newItems) => {
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm font-semibold leading-tight truncate">{{ statusText }}</p>
|
||||
<p class="text-xs text-slate-400 leading-tight mt-0.5">
|
||||
{{ completeCount }} of {{ items.length }} complete
|
||||
{{ t('upload.indicator.completeProgress', { complete: completeCount, total: items.length }) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -100,17 +102,17 @@ watch(isAllDone, (newItems) => {
|
||||
stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polygon points="5 3 19 12 5 21 5 3" />
|
||||
</svg>
|
||||
Start
|
||||
{{ t('upload.indicator.start') }}
|
||||
</button>
|
||||
<button v-else-if="isDoneWithErrors" @click.stop="doneUpload"
|
||||
class="flex items-center gap-1.5 text-xs font-semibold px-3 py-1.5 bg-green-500 hover:bg-green-500/80 text-white rounded-lg transition-all">
|
||||
View Videos
|
||||
{{ t('upload.indicator.viewVideos') }}
|
||||
</button>
|
||||
<!-- Clear queue -->
|
||||
<!-- Add more files -->
|
||||
<button @click.stop="uiState.uploadDialogVisible = true"
|
||||
class="w-7 h-7 flex items-center justify-center text-slate-400 hover:text-white hover:bg-white/10 rounded-lg transition-all"
|
||||
title="Add more files">
|
||||
:title="t('upload.indicator.addMoreFiles')">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M5 12h14" />
|
||||
|
||||
@@ -1,122 +1,51 @@
|
||||
<script setup lang="ts">
|
||||
import NotificationItem from '@/routes/notification/components/NotificationItem.vue';
|
||||
import { useNotifications } from '@/composables/useNotifications';
|
||||
import { onClickOutside } from '@vueuse/core';
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import { useTranslation } from 'i18next-vue';
|
||||
import BellOff from './icons/BellOff.vue';
|
||||
|
||||
// Ensure client-side only rendering to avoid hydration mismatch
|
||||
const isMounted = ref(false);
|
||||
onMounted(() => {
|
||||
isMounted.value = true;
|
||||
void notificationStore.fetchNotifications();
|
||||
});
|
||||
|
||||
// Emit event when visibility changes
|
||||
const emit = defineEmits(['change']);
|
||||
|
||||
type NotificationType = 'info' | 'success' | 'warning' | 'error' | 'video' | 'payment' | 'system';
|
||||
|
||||
interface Notification {
|
||||
id: string;
|
||||
type: NotificationType;
|
||||
title: string;
|
||||
message: string;
|
||||
time: string;
|
||||
read: boolean;
|
||||
actionUrl?: string;
|
||||
actionLabel?: string;
|
||||
}
|
||||
|
||||
const visible = ref(false);
|
||||
const drawerRef = ref(null);
|
||||
const { t } = useTranslation();
|
||||
const notificationStore = useNotifications();
|
||||
|
||||
// Mock notifications data
|
||||
const notifications = ref<Notification[]>([
|
||||
{
|
||||
id: '1',
|
||||
type: 'video',
|
||||
title: 'Video processing complete',
|
||||
message: 'Your video "Summer Vacation 2024" has been successfully processed.',
|
||||
time: '2 min ago',
|
||||
read: false,
|
||||
actionUrl: '/video',
|
||||
actionLabel: 'View'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'payment',
|
||||
title: 'Payment successful',
|
||||
message: 'Your subscription to Pro Plan has been renewed successfully.',
|
||||
time: '1 hour ago',
|
||||
read: false,
|
||||
actionUrl: '/payments-and-plans',
|
||||
actionLabel: 'Receipt'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
type: 'warning',
|
||||
title: 'Storage almost full',
|
||||
message: 'You have used 85% of your storage quota.',
|
||||
time: '3 hours ago',
|
||||
read: false,
|
||||
actionUrl: '/payments-and-plans',
|
||||
actionLabel: 'Upgrade'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
type: 'success',
|
||||
title: 'Upload successful',
|
||||
message: 'Your video "Product Demo v2" has been uploaded.',
|
||||
time: '1 day ago',
|
||||
read: true
|
||||
}
|
||||
]);
|
||||
|
||||
const unreadCount = computed(() => notifications.value.filter(n => !n.read).length);
|
||||
const unreadCount = computed(() => notificationStore.unreadCount.value);
|
||||
const mutableNotifications = computed(() => notificationStore.notifications.value.slice(0, 8));
|
||||
|
||||
const toggle = (event?: Event) => {
|
||||
console.log(event);
|
||||
// Prevent event propagation to avoid immediate closure by onClickOutside
|
||||
if (event) {
|
||||
// We don't stop propagation here to let other listeners work,
|
||||
// but we might need to ignore the trigger element in onClickOutside
|
||||
// However, since the trigger is outside this component, simple toggle logic works
|
||||
// if we use a small delay or ignore ref.
|
||||
// Best approach: "toggle" usually comes from a button click.
|
||||
}
|
||||
visible.value = !visible.value;
|
||||
console.log(visible.value);
|
||||
if (visible.value && !notificationStore.loaded.value) {
|
||||
void notificationStore.fetchNotifications();
|
||||
}
|
||||
};
|
||||
|
||||
// Handle click outside
|
||||
onClickOutside(drawerRef, (event) => {
|
||||
// We can just set visible to false.
|
||||
// Note: If the toggle button is clicked, it might toggle it back on immediately
|
||||
// if the click event propagates.
|
||||
// The user calls `toggle` from the parent's button click handler.
|
||||
// If that button is outside `drawerRef` (which it is), this will fire.
|
||||
// To avoid conflict, we usually check if the target is the trigger.
|
||||
// But we don't have access to the trigger ref here.
|
||||
// A common workaround is to use `ignore` option if we had the ref,
|
||||
// or relying on the fact that if this fires, it sets specific state to false.
|
||||
// If the button click then fires `toggle`, it might set it true again.
|
||||
// Optimization: check if visible is true before closing.
|
||||
onClickOutside(drawerRef, () => {
|
||||
if (visible.value) {
|
||||
visible.value = false;
|
||||
}
|
||||
}, {
|
||||
ignore: ['[name="Notification"]'] // Assuming the trigger button has this class or we can suggest adding a class to the trigger
|
||||
ignore: ['[name="Notification"]']
|
||||
});
|
||||
|
||||
const handleMarkRead = (id: string) => {
|
||||
const notification = notifications.value.find(n => n.id === id);
|
||||
if (notification) notification.read = true;
|
||||
const handleMarkRead = async (id: string) => {
|
||||
await notificationStore.markRead(id);
|
||||
};
|
||||
|
||||
const handleDelete = (id: string) => {
|
||||
notifications.value = notifications.value.filter(n => n.id !== id);
|
||||
const handleDelete = async (id: string) => {
|
||||
await notificationStore.deleteNotification(id);
|
||||
};
|
||||
|
||||
const handleMarkAllRead = () => {
|
||||
notifications.value.forEach(n => n.read = true);
|
||||
const handleMarkAllRead = async () => {
|
||||
await notificationStore.markAllRead();
|
||||
};
|
||||
|
||||
watch(visible, (val) => {
|
||||
@@ -134,10 +63,9 @@ defineExpose({ toggle });
|
||||
leave-to-class="opacity-0 -translate-x-4">
|
||||
<div v-if="visible" ref="drawerRef"
|
||||
class="fixed top-0 left-[80px] bottom-0 w-[380px] bg-white rounded-2xl border border-gray-300 p-3 z-50 flex flex-col shadow-lg my-3">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between p-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<h3 class="font-semibold text-gray-900">Notifications</h3>
|
||||
<h3 class="font-semibold text-gray-900">{{ t('notification.title') }}</h3>
|
||||
<span v-if="unreadCount > 0"
|
||||
class="px-2 py-0.5 text-xs font-medium bg-primary text-white rounded-full">
|
||||
{{ unreadCount }}
|
||||
@@ -145,49 +73,44 @@ defineExpose({ toggle });
|
||||
</div>
|
||||
<button v-if="unreadCount > 0" @click="handleMarkAllRead"
|
||||
class="text-sm text-primary hover:underline font-medium">
|
||||
Mark all read
|
||||
{{ t('notification.actions.markAllRead') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Notification List -->
|
||||
<div class="flex flex-col flex-1 overflow-y-auto gap-2">
|
||||
<template v-if="notifications.length > 0">
|
||||
<div v-for="notification in notifications" :key="notification.id"
|
||||
<template v-if="notificationStore.loading.value">
|
||||
<div v-for="i in 4" :key="i" class="p-4 rounded-xl border border-gray-200 animate-pulse">
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="w-10 h-10 rounded-full bg-gray-200"></div>
|
||||
<div class="flex-1 space-y-2">
|
||||
<div class="h-4 bg-gray-200 rounded w-1/3"></div>
|
||||
<div class="h-3 bg-gray-200 rounded w-2/3"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="mutableNotifications.length > 0">
|
||||
<div v-for="notification in mutableNotifications" :key="notification.id"
|
||||
class="border-b border-gray-50 last:border-0">
|
||||
<NotificationItem :notification="notification" @mark-read="handleMarkRead"
|
||||
@delete="handleDelete" isDrawer />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Empty state -->
|
||||
<div v-else class="py-12 text-center">
|
||||
<span class="i-lucide-bell-off w-12 h-12 text-gray-300 mx-auto block mb-3"></span>
|
||||
<p class="text-gray-500 text-sm">No notifications</p>
|
||||
<BellOff class="w-12 h-12 text-gray-300 mx-auto block mb-3" />
|
||||
<p class="text-gray-500 text-sm">{{ t('notification.empty.title') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div v-if="notifications.length > 0" class="p-3 border-t border-gray-100 bg-gray-50/50">
|
||||
<div v-if="mutableNotifications.length > 0" class="p-3 border-t border-gray-100 bg-gray-50/50">
|
||||
<router-link to="/notification"
|
||||
class="block w-full text-center text-sm text-primary font-medium hover:underline"
|
||||
@click="visible = false">
|
||||
View all notifications
|
||||
{{ t('notification.actions.viewAll') }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<!-- <style>
|
||||
.notification-popover {
|
||||
border-radius: 16px !important;
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.12) !important;
|
||||
border: 1px solid rgba(0, 0, 0, 0.08) !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.notification-popover .p-popover-content {
|
||||
padding: 0 !important;
|
||||
}
|
||||
</style> -->
|
||||
|
||||
67
src/components/OfflineOverlay.vue
Normal file
67
src/components/OfflineOverlay.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<script setup lang="ts">
|
||||
import AppButton from '@/components/ui/AppButton.vue'
|
||||
import { useNetworkStatus } from '@/composables/useNetworkStatus'
|
||||
import { useTranslation } from 'i18next-vue'
|
||||
import { onBeforeUnmount, onMounted } from 'vue'
|
||||
|
||||
const { t } = useTranslation()
|
||||
const { isOffline, startListening, stopListening } = useNetworkStatus()
|
||||
|
||||
onMounted(() => {
|
||||
startListening()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
stopListening()
|
||||
})
|
||||
|
||||
function reloadPage() {
|
||||
if (typeof window === 'undefined') return
|
||||
|
||||
window.location.reload()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="isOffline"
|
||||
class="fixed inset-0 z-[10000] flex items-center justify-center bg-slate-950/80 px-6 backdrop-blur-sm"
|
||||
role="alert"
|
||||
aria-live="assertive"
|
||||
>
|
||||
<div class="w-full max-w-md rounded-2xl border border-border bg-white p-8 text-center shadow-2xl">
|
||||
<div class="mx-auto mb-6 flex h-16 w-16 items-center justify-center rounded-full bg-danger/10 text-danger">
|
||||
<svg
|
||||
class="h-8 w-8"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.8"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M2 8.82a15 15 0 0 1 20 0" />
|
||||
<path d="M5 12.86a10 10 0 0 1 14 0" />
|
||||
<path d="M8.5 16.43a5 5 0 0 1 7 0" />
|
||||
<path d="M12 20h.01" />
|
||||
<path d="M3 3l18 18" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<h2 class="text-xl font-semibold text-foreground">
|
||||
{{ t('network.offline.title') }}
|
||||
</h2>
|
||||
|
||||
<p class="mt-3 text-sm leading-6 text-foreground/70">
|
||||
{{ t('network.offline.description') }}
|
||||
</p>
|
||||
|
||||
<div class="mt-6 flex justify-center">
|
||||
<AppButton @click="reloadPage">
|
||||
{{ t('network.offline.action') }}
|
||||
</AppButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
75
src/components/PopupAdsRuntime.vue
Normal file
75
src/components/PopupAdsRuntime.vue
Normal file
@@ -0,0 +1,75 @@
|
||||
<script setup lang="ts">
|
||||
import { client as rpcClient } from '@/api/rpcclient';
|
||||
import ClientOnly from '@/components/ClientOnly';
|
||||
import { onMounted, onBeforeUnmount } from 'vue';
|
||||
|
||||
let activeItem: any | null = null;
|
||||
let clickHandler: ((event: MouseEvent) => void) | null = null;
|
||||
let scriptNode: HTMLScriptElement | null = null;
|
||||
let triggerCount = 0;
|
||||
|
||||
const triggerKey = (id: string) => `popup_ad_triggers:${id}`;
|
||||
|
||||
const cleanupScript = () => {
|
||||
if (scriptNode?.parentNode) {
|
||||
scriptNode.parentNode.removeChild(scriptNode);
|
||||
}
|
||||
scriptNode = null;
|
||||
};
|
||||
|
||||
const attachUrlHandler = () => {
|
||||
if (!activeItem?.id || typeof window === 'undefined') return;
|
||||
const maxTriggers = Number(activeItem.maxTriggersPerSession || 1);
|
||||
triggerCount = Number(sessionStorage.getItem(triggerKey(activeItem.id)) || '0');
|
||||
|
||||
clickHandler = () => {
|
||||
if (!activeItem?.value || triggerCount >= maxTriggers) return;
|
||||
triggerCount += 1;
|
||||
sessionStorage.setItem(triggerKey(activeItem.id), String(triggerCount));
|
||||
window.open(activeItem.value, '_blank', 'noopener,noreferrer');
|
||||
};
|
||||
|
||||
window.addEventListener('click', clickHandler, { capture: true });
|
||||
};
|
||||
|
||||
const attachScript = () => {
|
||||
if (!activeItem?.value || typeof document === 'undefined') return;
|
||||
cleanupScript();
|
||||
scriptNode = document.createElement('script');
|
||||
scriptNode.async = true;
|
||||
scriptNode.text = activeItem.value;
|
||||
document.body.appendChild(scriptNode);
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const response = await rpcClient.getActivePopupAd();
|
||||
activeItem = response.item || null;
|
||||
if (!activeItem?.isActive) return;
|
||||
|
||||
if (activeItem.type === 'script') {
|
||||
attachScript();
|
||||
return;
|
||||
}
|
||||
|
||||
if (activeItem.type === 'url') {
|
||||
attachUrlHandler();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (clickHandler && typeof window !== 'undefined') {
|
||||
window.removeEventListener('click', clickHandler, { capture: true } as EventListenerOptions);
|
||||
}
|
||||
cleanupScript();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClientOnly>
|
||||
<span class="hidden" />
|
||||
</ClientOnly>
|
||||
</template>
|
||||
@@ -1,3 +1,12 @@
|
||||
<template>
|
||||
<router-view/>
|
||||
<ClientOnly>
|
||||
<AppTopLoadingBar />
|
||||
<OfflineOverlay />
|
||||
</ClientOnly>
|
||||
<router-view />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import ClientOnly from '@/components/ClientOnly';
|
||||
import AppTopLoadingBar from '@/components/AppTopLoadingBar.vue'
|
||||
import OfflineOverlay from '@/components/OfflineOverlay.vue'
|
||||
</script>
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import { computed } from 'vue';
|
||||
|
||||
type Variant = 'primary' | 'secondary' | 'danger' | 'ghost';
|
||||
type Size = 'sm' | 'md';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
variant?: Variant;
|
||||
size?: Size;
|
||||
loading?: boolean;
|
||||
disabled?: boolean;
|
||||
type?: 'button' | 'submit' | 'reset';
|
||||
}>(), {
|
||||
variant: 'primary',
|
||||
size: 'md',
|
||||
loading: false,
|
||||
disabled: false,
|
||||
type: 'button',
|
||||
});
|
||||
|
||||
const baseClass = 'inline-flex items-center justify-center gap-2 rounded-md font-medium transition-all press-animated select-none';
|
||||
|
||||
const sizeClass = computed(() => {
|
||||
switch (props.size) {
|
||||
case 'sm':
|
||||
return 'px-3 py-1.5 text-sm';
|
||||
case 'md':
|
||||
default:
|
||||
return 'px-4 py-2 text-sm';
|
||||
}
|
||||
});
|
||||
|
||||
const variantClass = computed(() => {
|
||||
switch (props.variant) {
|
||||
case 'secondary':
|
||||
return 'bg-muted/50 text-foreground hover:bg-muted border border-border';
|
||||
case 'danger':
|
||||
return 'bg-danger text-white hover:bg-danger/90';
|
||||
case 'ghost':
|
||||
return 'bg-transparent text-foreground/70 hover:text-foreground hover:bg-muted/50';
|
||||
case 'primary':
|
||||
default:
|
||||
return 'bg-primary text-white hover:bg-primary/90';
|
||||
}
|
||||
});
|
||||
|
||||
const disabledClass = computed(() => (props.disabled || props.loading) ? 'opacity-60 cursor-not-allowed' : '');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
:type="type"
|
||||
:disabled="disabled || loading"
|
||||
:class="cn(baseClass, sizeClass, variantClass, disabledClass)"
|
||||
>
|
||||
<span v-if="loading" class="inline-flex items-center" aria-hidden="true">
|
||||
<svg class="w-4 h-4 animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 0 1 8-8v4a4 4 0 0 0-4 4H4z" />
|
||||
</svg>
|
||||
</span>
|
||||
<slot name="icon" />
|
||||
<slot />
|
||||
</button>
|
||||
</template>
|
||||
@@ -1,110 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import XIcon from '@/components/icons/XIcon.vue';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||||
|
||||
// Ensure client-side only rendering to avoid hydration mismatch
|
||||
const isMounted = ref(false);
|
||||
onMounted(() => {
|
||||
isMounted.value = true;
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
visible: boolean;
|
||||
title?: string;
|
||||
closable?: boolean;
|
||||
maxWidthClass?: string;
|
||||
}>(), {
|
||||
title: '',
|
||||
closable: true,
|
||||
maxWidthClass: 'max-w-lg',
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', value: boolean): void;
|
||||
(e: 'close'): void;
|
||||
}>();
|
||||
|
||||
const close = () => {
|
||||
emit('update:visible', false);
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const onKeydown = (e: KeyboardEvent) => {
|
||||
if (!props.visible) return;
|
||||
if (!props.closable) return;
|
||||
if (e.key === 'Escape') close();
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(v) => {
|
||||
if (typeof window === 'undefined') return;
|
||||
if (v) window.addEventListener('keydown', onKeydown);
|
||||
else window.removeEventListener('keydown', onKeydown);
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (typeof window === 'undefined') return;
|
||||
window.removeEventListener('keydown', onKeydown);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport v-if="isMounted" to="body">
|
||||
<Transition
|
||||
enter-active-class="transition-all duration-200 ease-out"
|
||||
enter-from-class="opacity-0"
|
||||
enter-to-class="opacity-100"
|
||||
leave-active-class="transition-all duration-150 ease-in"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div v-if="visible" class="fixed inset-0 z-[9999]">
|
||||
<!-- Backdrop -->
|
||||
<div
|
||||
class="absolute inset-0 bg-black/30"
|
||||
@click="closable && close()"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
<!-- Panel -->
|
||||
<div class="absolute inset-0 flex items-center justify-center p-4">
|
||||
<div :class="cn('w-full bg-surface border border-border rounded-lg shadow-lg overflow-hidden', maxWidthClass)">
|
||||
<!-- Header slot -->
|
||||
<div v-if="$slots.header" class="px-5 py-4 border-b border-border">
|
||||
<slot name="header" :close="close" />
|
||||
</div>
|
||||
<!-- Default title -->
|
||||
<div v-else-if="title" class="flex items-center justify-between gap-3 px-5 py-4 border-b border-border">
|
||||
<h3 class="text-sm font-semibold text-foreground">
|
||||
{{ title }}
|
||||
</h3>
|
||||
<button
|
||||
v-if="closable"
|
||||
type="button"
|
||||
class="p-1 rounded-md text-foreground/60 hover:text-foreground hover:bg-muted/50 transition-all"
|
||||
@click="close"
|
||||
aria-label="Close"
|
||||
>
|
||||
<XIcon class="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="p-5">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<!-- Footer slot -->
|
||||
<div v-if="$slots.footer" class="px-5 py-4 border-t border-border bg-muted/20">
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
</template>
|
||||
@@ -1,46 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
modelValue: boolean;
|
||||
disabled?: boolean;
|
||||
ariaLabel?: string;
|
||||
}>(), {
|
||||
disabled: false,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: boolean): void;
|
||||
(e: 'change', value: boolean): void;
|
||||
}>();
|
||||
|
||||
const toggle = () => {
|
||||
if (props.disabled) return;
|
||||
const next = !props.modelValue;
|
||||
emit('update:modelValue', next);
|
||||
emit('change', next);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
:aria-checked="modelValue"
|
||||
:aria-label="ariaLabel"
|
||||
:disabled="disabled"
|
||||
@click="toggle"
|
||||
:class="cn(
|
||||
'relative inline-flex h-6 w-11 items-center rounded-full transition-colors',
|
||||
disabled ? 'opacity-60 cursor-not-allowed' : 'cursor-pointer',
|
||||
modelValue ? 'bg-primary' : 'bg-border'
|
||||
)"
|
||||
>
|
||||
<span
|
||||
:class="cn(
|
||||
'inline-block h-5 w-5 transform rounded-full bg-white shadow-sm transition-transform',
|
||||
modelValue ? 'translate-x-5' : 'translate-x-1'
|
||||
)"
|
||||
/>
|
||||
</button>
|
||||
</template>
|
||||
@@ -47,9 +47,3 @@ const props = defineProps<Props>();
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.empty-state {
|
||||
min-height: 400px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -60,7 +60,7 @@ const getButtonClass = (variant?: string) => {
|
||||
<!-- Title & Actions -->
|
||||
<div class="flex items-start justify-between gap-4 flex-wrap">
|
||||
<div class="flex-1 min-w-0">
|
||||
<h1 v-if="typeof props.title == 'string'" class="text-3xl font-bold text-gray-900 mb-1">{{ title }}</h1>
|
||||
<h1 v-if="typeof props.title == 'string'" class="text-2xl font-bold text-gray-900 mb-1">{{ title }}</h1>
|
||||
<component v-else :is="title" />
|
||||
<p v-if="description" class="text-gray-600">{{ description }}</p>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { useTranslation } from 'i18next-vue';
|
||||
import { VNode } from 'vue';
|
||||
|
||||
interface Trend {
|
||||
@@ -6,7 +7,7 @@ interface Trend {
|
||||
isPositive: boolean;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
export interface StatProps {
|
||||
title: string;
|
||||
value: string | number;
|
||||
icon?: string | VNode;
|
||||
@@ -14,10 +15,12 @@ interface Props {
|
||||
color?: 'primary' | 'success' | 'warning' | 'danger' | 'info';
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
withDefaults(defineProps<StatProps>(), {
|
||||
color: 'primary'
|
||||
});
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
// const gradients = {
|
||||
// primary: 'from-primary/20 to-primary/5',
|
||||
// success: 'from-success/20 to-success/5',
|
||||
@@ -37,7 +40,7 @@ const iconColors = {
|
||||
|
||||
<template>
|
||||
<div :class="[
|
||||
'transform translate-y-0 relative overflow-hidden rounded-2xl p-6 bg-surface',
|
||||
'transform translate-y-0 relative overflow-hidden rounded-2xl p-6 bg-header',
|
||||
// gradients[color],
|
||||
'border border-gray-300 transition-all duration-300',
|
||||
// 'group cursor-pointer'
|
||||
@@ -46,7 +49,7 @@ const iconColors = {
|
||||
<div class="relative z-10">
|
||||
<div class="flex items-start justify-between mb-3">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-600 mb-1">{{ title }}</p>
|
||||
<p class="text-sm font-medium text-gray-600 mb-1">{{ $t(title) }}</p>
|
||||
<p class="text-3xl font-bold text-gray-900">{{ value }}</p>
|
||||
</div>
|
||||
|
||||
@@ -76,7 +79,7 @@ const iconColors = {
|
||||
</svg>
|
||||
{{ Math.abs(trend.value) }}%
|
||||
</span>
|
||||
<span class="text-gray-500">vs last month</span>
|
||||
<span class="text-gray-500">{{ t('overview.stats.trendVsLastMonth') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
fill="#a6acb9" />
|
||||
<path
|
||||
d="M74 42c-18 0-32 14-32 32v320c0 18 14 32 32 32h320c18 0 32-14 32-32V74c0-18-14-32-32-32H74zM10 74c0-35 29-64 64-64h320c35 0 64 29 64 64v320c0 35-29 64-64 64H74c-35 0-64-29-64-64V74zm208 256v-80h-80c-9 0-16-7-16-16s7-16 16-16h80v-80c0-9 7-16 16-16s16 7 16 16v80h80c9 0 16 7 16 16s-7 16-16 16h-80v80c0 9-7 16-16 16s-16-7-16-16z"
|
||||
fill="#1e3050" />
|
||||
fill="currentColor" />
|
||||
</svg>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" class="v-mid m-a" height="24" viewBox="-10 -226 468 468">
|
||||
<path
|
||||
d="M64-184c-18 0-32 14-32 32v320c0 18 14 32 32 32h320c18 0 32-14 32-32v-320c0-18-14-32-32-32H64zM0-152c0-35 29-64 64-64h320c35 0 64 29 64 64v320c0 35-29 64-64 64H64c-35 0-64-29-64-64v-320zm208 256V24h-80c-9 0-16-7-16-16s7-16 16-16h80v-80c0-9 7-16 16-16s16 7 16 16v80h80c9 0 16 7 16 16s-7 16-16 16h-80v80c0 9-7 16-16 16s-16-7-16-16z"
|
||||
fill="#1e3050" />
|
||||
fill="currentColor" />
|
||||
</svg>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 518"><path d="M234 124v256c58 3 113 25 156 63l47 41c9 8 23 10 34 5 12-5 19-16 19-29V44c0-13-7-24-19-29-11-5-25-3-34 5l-47 41c-43 38-98 60-156 63z" fill="#a6acb9"/><path d="M138 124c-71 0-128 57-128 128s57 128 128 128v96c0 18 14 32 32 32h32c18 0 32-14 32-32V124h-96z" fill="#1e3050"/></svg>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" width="500" height="518" viewBox="-10 -244 500 518"><path d="M461-229c12 5 19 16 19 29v416c0 13-7 24-19 29-11 5-25 3-34-5l-47-41c-43-38-98-60-156-63v96c0 18-14 32-32 32h-32c-18 0-32-14-32-32v-96C57 136 0 79 0 8s57-128 128-128h85c61 0 121-23 167-63l47-41c9-8 23-10 34-5zM224 72c70 3 138 29 192 74v-276c-54 45-122 71-192 74V72z" fill="currentColor"/></svg>
|
||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 518"><path d="M234 124v256c58 3 113 25 156 63l47 41c9 8 23 10 34 5 12-5 19-16 19-29V44c0-13-7-24-19-29-11-5-25-3-34 5l-47 41c-43 38-98 60-156 63z" fill="var(--fill1)"/><path d="M138 124c-71 0-128 57-128 128s57 128 128 128v96c0 18 14 32 32 32h32c18 0 32-14 32-32V124h-96z" fill="currentColor"/></svg>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="-10 -242 500 516"><path d="M448-194v404l-26-24c-50-47-114-75-182-81V-89c68-6 132-34 182-81l26-24zM240 137c60 6 116 31 160 72l34 32c5 4 12 7 19 7 15 0 27-12 27-27v-425c0-16-12-28-27-28-7 0-14 3-19 8l-34 31c-50 47-116 73-185 73h-87C57-120 0-63 0 8c0 60 41 110 96 124v84c0 27 22 48 48 48h48c27 0 48-21 48-48v-79zm-40-1h8v80c0 9-7 16-16 16h-48c-9 0-16-7-16-16v-80h72zm0-224h8v192h-80c-53 0-96-43-96-96s43-96 96-96h72z" fill="currentColor"/></svg>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
|
||||
@@ -5,9 +5,6 @@ defineProps<{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z" />
|
||||
<path d="M12 9v4" />
|
||||
<path d="M12 17h.01" />
|
||||
</svg>
|
||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 580 524"><path d="M10 448c0 36 30 66 67 66h427c36 0 66-30 66-66 0-12-3-23-8-33L353 47c-13-23-37-37-63-37s-50 14-63 37L19 415c-6 10-9 21-9 33zm301-46c0 12-9 21-21 21s-21-9-21-21 9-21 21-21 21 9 21 21zm-35-238c0-8 6-14 14-14s14 6 14 14v168c0 8-6 14-14 14s-14-6-14-14V164z" fill="color-mix(in srgb, currentColor 40%, transparent)"/><path d="M290 423c-12 0-21-9-21-21s9-21 21-21 21 9 21 21-9 21-21 21zm14-91c0 8-6 14-14 14s-14-6-14-14V164c0-8 6-14 14-14s14 6 14 14v168z" fill="currentColor"/></svg>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" width="531" height="488" viewBox="-13 -224 531 488"><path d="M253-106c9 0 18 8 18 18V56c0 10-9 18-18 18-10 0-18-8-18-18V-88c0-10 8-18 18-18zm0 279c14 0 27-12 27-27s-13-27-27-27c-15 0-27 12-27 27s12 27 27 27zm-63-350c12-23 36-37 63-37 26 0 50 14 62 37l180 324c13 22 13 50 0 72s-37 35-62 35H73c-26 0-50-13-63-35s-13-50 0-72l180-324zm63-1c-14 0-26 7-32 19L41 165c-6 11-6 24 0 35 7 11 19 18 32 18h360c12 0 24-7 31-18 6-11 6-24 0-35L284-159c-6-12-18-19-31-19z" fill="currentColor"/></svg>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 468 532"><path d="M10 391c0 19 16 35 36 35h376c20 0 36-16 36-35 0-9-3-16-8-23l-10-12c-30-37-46-84-46-132v-22c0-77-55-142-128-157v-3c0-18-14-32-32-32s-32 14-32 32v3C129 60 74 125 74 202v22c0 48-16 95-46 132l-10 12c-5 7-8 14-8 23z" fill="#a6acb9"/><path d="M172 474c7 28 32 48 62 48s55-20 62-48H172z" fill="#1e3050"/></svg>
|
||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 468 532"><path d="M10 391c0 19 16 35 36 35h376c20 0 36-16 36-35 0-9-3-16-8-23l-10-12c-30-37-46-84-46-132v-22c0-77-55-142-128-157v-3c0-18-14-32-32-32s-32 14-32 32v3C129 60 74 125 74 202v22c0 48-16 95-46 132l-10 12c-5 7-8 14-8 23z" fill="var(--fill1)"/><path d="M172 474c7 28 32 48 62 48s55-20 62-48H172z" fill="currentColor"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" v-else viewBox="-10 -258 468 532">
|
||||
<path
|
||||
d="M224-248c-13 0-24 11-24 24v10C119-203 56-133 56-48v15C56 4 46 41 27 74L5 111c-3 6-5 13-5 19 0 21 17 38 38 38h372c21 0 38-17 38-38 0-6-2-13-5-19l-22-37c-19-33-29-70-29-108v-14c0-85-63-155-144-166v-10c0-13-11-24-24-24zm168 368H56l12-22c24-40 36-85 36-131v-15c0-66 54-120 120-120s120 54 120 120v15c0 46 12 91 36 131l12 22zm-236 96c10 28 37 48 68 48s58-20 68-48H156z"
|
||||
|
||||
7
src/components/icons/BellDot.vue
Normal file
7
src/components/icons/BellDot.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" v-if="filled" viewBox="0 0 660 535"><path d="M106 394c0 19 16 35 36 35h376c20 0 36-16 36-35 0-9-3-16-8-23l-10-12c-30-37-46-84-46-132v-22c0-77-55-142-128-157v-3c0-18-14-32-32-32s-32 14-32 32v3c-73 15-128 80-128 157v22c0 48-16 95-46 132l-10 12c-5 7-8 14-8 23z" fill="var(--fill1)"/><path d="M616 28c5 12 0 26-12 31l-56 24c-13 5-27 0-32-13-5-12 0-26 13-31l56-24c12-5 26 0 31 13zM10 197c0-13 11-24 24-24h64c13 0 24 11 24 24s-11 24-24 24H34c-13 0-24-11-24-24zm258 280h124c-7 28-32 48-62 48s-55-20-62-48zm294-304h64c13 0 24 11 24 24s-11 24-24 24h-64c-13 0-24-11-24-24s11-24 24-24zM57 59c-13-5-18-19-13-31 5-13 19-18 32-13l56 24c12 5 17 19 12 31-5 13-19 18-31 13L57 59z" fill="var(--fill4)"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" v-else width="660" height="535" viewBox="-10 -261 660 535"><path d="M606-233c-5-13-19-18-31-13l-56 24c-13 5-18 19-13 31 5 13 19 18 31 13l56-24c13-5 18-19 13-31zm-286-15c-13 0-24 11-24 24v10c-81 11-144 81-144 166v15c0 37-10 74-29 107l-22 37c-3 6-5 13-5 19 0 21 17 38 38 38h372c21 0 38-17 38-38 0-6-2-13-5-19l-22-37c-19-33-29-70-29-108v-14c0-85-63-155-144-166v-10c0-13-11-24-24-24zm168 368H152l12-22c24-40 36-85 36-131v-15c0-66 54-120 120-120s120 54 120 120v15c0 46 12 91 36 131l12 22zm-236 96c10 28 37 48 68 48s58-20 68-48H252zM0-64c0 13 11 24 24 24h64c13 0 24-11 24-24s-11-24-24-24H24C11-88 0-77 0-64zm552-24c-13 0-24 11-24 24s11 24 24 24h64c13 0 24-11 24-24s-11-24-24-24h-64zM47-202l56 24c12 5 26 0 31-12 5-13 0-27-13-32l-56-24c-12-5-26 0-31 13-5 12 0 26 13 31z" fill="currentColor"/></svg>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
defineProps<{ filled?: boolean }>();
|
||||
</script>
|
||||
@@ -1,12 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
filled?: boolean;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" />
|
||||
<path d="M13.73 21a2 2 0 0 1-3.46 0" />
|
||||
</svg>
|
||||
</template>
|
||||
7
src/components/icons/BellOff.vue
Normal file
7
src/components/icons/BellOff.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 600"><path d="M76 425c0 19 16 35 36 35h246L140 242v16c0 48-16 94-46 132l-10 12c-5 7-8 14-8 22zm0 0zm162 83c7 28 32 48 62 48s55-20 62-48H238z" fill="var(--fill1)"/><path d="M19 19c9-9 25-9 34 0l120 120c23-30 56-52 95-60v-3c0-18 14-32 32-32s32 14 32 32v3c73 15 128 80 128 157v22c0 48 16 95 46 132l10 12c5 7 8 14 8 23 0 17-13 32-30 35l87 87c9 10 9 25 0 34s-25 9-34 0L19 53c-9-9-9-25 0-34z" fill="var(--fill4)"/></svg>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" width="600" height="601" viewBox="-12 -292 600 601"><path d="M41-273c-9-9-25-9-34 0s-9 25 0 34l528 528c9 10 25 10 34 0 9-9 9-24 0-34l-88-88c18-3 31-18 31-37 0-6-2-13-5-19l-22-37c-19-33-29-70-29-108v-14c0-85-63-155-144-166v-10c0-13-11-24-24-24s-24 11-24 24v10c-42 6-79 27-105 59L41-273zm152 152c22-29 56-47 95-47 66 0 120 54 120 120v15c0 46 12 91 36 131l12 22h-22L193-121zM133 98c19-33 31-71 34-109l-47-47v25c0 37-10 74-29 107l-22 37c-3 6-5 13-5 19 0 21 17 38 38 38h244l-48-48H120l13-22zm87 118c10 28 37 48 68 48s58-20 68-48H220z" fill="currentColor"/></svg>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
defineProps<{ filled?: boolean }>();
|
||||
</script>
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" v-if="filled" viewBox="0 0 580 524"><path d="M10 234v112c0 46 38 84 84 84s84-38 84-84V234c0-46-38-84-84-84s-84 38-84 84zM206 94v252c0 46 38 84 84 84s84-38 84-84V94c0-46-38-84-84-84s-84 38-84 84zm196 56v196c0 46 38 84 84 84s84-38 84-84V150c0-46-38-84-84-84s-84 38-84 84z" fill="#a6acb9"/><path d="M10 500c0-8 6-14 14-14h532c8 0 14 6 14 14s-6 14-14 14H24c-8 0-14-6-14-14z" fill="#1e3050"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" v-else viewBox="-10 -226 532 468"><path d="M272-184c9 0 16 7 16 16v352c0 9-7 16-16 16h-32c-9 0-16-7-16-16v-352c0-9 7-16 16-16h32zm-32-32c-26 0-48 22-48 48v352c0 27 22 48 48 48h32c27 0 48-21 48-48v-352c0-26-21-48-48-48h-32zM80 8c9 0 16 7 16 16v160c0 9-7 16-16 16H48c-9 0-16-7-16-16V24c0-9 7-16 16-16h32zM48-24C22-24 0-2 0 24v160c0 27 22 48 48 48h32c27 0 48-21 48-48V24c0-26-21-48-48-48H48zm384-96h32c9 0 16 7 16 16v288c0 9-7 16-16 16h-32c-9 0-16-7-16-16v-288c0-9 7-16 16-16zm-48 16v288c0 27 22 48 48 48h32c27 0 48-21 48-48v-288c0-26-21-48-48-48h-32c-26 0-48 22-48 48z" fill="#1e3050"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" v-if="filled" viewBox="0 0 600 564"><path d="M21 254c11 14 31 16 45 5l141-112 108 81c12 8 28 8 39-1l140-112v39c0 18 14 32 32 32s32-14 32-32V42c0-18-14-32-32-32H414c-18 0-32 14-32 32s14 32 32 32h29l-110 88-108-82c-11-8-28-8-39 1L26 209c-14 11-16 31-5 45zm25 108v96c0 18 14 32 32 32s32-14 32-32v-96c0-18-14-32-32-32s-32 14-32 32zm128-96v192c0 18 14 32 32 32s32-14 32-32V266c0-18-14-32-32-32s-32 14-32 32z" fill="var(--fill1)"/><path d="M446 554c80 0 144-64 144-144s-64-144-144-144-144 64-144 144 64 144 144 144zm0-240c9 0 16 7 16 16v8h16c9 0 16 7 16 16s-7 16-16 16h-46c-5 0-10 5-10 10s4 9 8 10l45 8c20 4 35 22 35 42 0 23-19 42-42 42h-6v8c0 9-7 16-16 16s-16-7-16-16v-8h-16c-9 0-16-7-16-16s7-16 16-16h54c5 0 10-4 10-10 0-5-4-9-8-10l-45-8c-20-4-35-21-35-42 0-22 18-41 40-42v-8c0-9 7-16 16-16z" fill="var(--fill4)"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" v-else viewBox="0 0 599 564"><path d="M421 10c-13 0-24 11-24 24s11 24 24 24h53L333 178 221 80c-8-7-20-8-29-2L24 190c-11 7-14 22-7 33s22 14 33 7l153-102 114 100c9 8 23 8 32 0L509 91v55c0 13 11 24 24 24s24-11 24-24V34c0-13-11-24-24-24H421zM205 234c-13 0-24 11-24 24v208c0 13 11 24 24 24s24-11 24-24V258c0-13-11-24-24-24zM69 330c-13 0-24 11-24 24v112c0 13 11 24 24 24s24-11 24-24V354c0-13-11-24-24-24zm376 224c80 0 144-64 144-144s-64-144-144-144-144 64-144 144 64 144 144 144zm0-240c9 0 16 7 16 16v8h16c9 0 16 7 16 16s-7 16-16 16h-46c-5 0-10 5-10 10s4 9 8 10l45 8c20 4 35 22 35 42 0 23-19 42-42 42h-6v8c0 9-7 16-16 16s-16-7-16-16v-8h-16c-9 0-16-7-16-16s7-16 16-16h54c5 0 10-4 10-10 0-5-4-9-8-10l-45-8c-20-4-35-21-35-42 0-22 18-41 40-42v-8c0-9 7-16 16-16z" fill="var(--fill4)"/></svg>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
defineProps<{ filled?: boolean }>();
|
||||
|
||||
@@ -5,9 +5,6 @@ defineProps<{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="9"/>
|
||||
<path d="M12 16V8"/>
|
||||
<path d="M9.5 10a2.5 2.5 0 0 1 5 0v4a2.5 2.5 0 0 1-5 0"/>
|
||||
</svg>
|
||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 532"><path d="M10 435c0 48 39 87 88 87h305c48 0 87-39 87-87 0-87-38-169-105-224l-48-41H164l-49 41C48 266 10 348 10 435zM138 36c0 4 1 8 3 12l37 74h144l37-74c2-4 3-8 3-12 0-14-12-26-26-26H164c-14 0-26 12-26 26zm44 275c0-29 23-53 52-53v-4c0-11 9-20 20-20s20 9 20 20v4h8c11 0 20 9 20 20s-9 20-20 20h-47c-7 0-13 6-13 13 0 6 4 11 10 12l42 7c25 4 44 26 44 52s-19 47-44 51v5c0 11-9 20-20 20s-20-9-20-20v-4h-24c-11 0-20-9-20-20s9-20 20-20h56c6 0 12-5 12-12 0-6-4-12-10-13l-42-7c-25-4-44-26-44-51z" fill="var(--fill1)"/><path d="M162 122c-13 0-24 11-24 24s11 24 24 24h176c13 0 24-11 24-24s-11-24-24-24H162zm92 112c-11 0-20 9-20 20v4c-29 0-52 24-52 53 0 25 19 47 44 51l42 7c6 1 10 7 10 13 0 7-6 12-12 12h-56c-11 0-20 9-20 20s9 20 20 20h24v4c0 11 9 20 20 20s20-9 20-20v-5c25-4 44-25 44-51s-18-48-44-52l-42-7c-6-1-10-6-10-13 0-6 6-12 13-12h47c11 0 20-9 20-20s-9-20-20-20h-8v-4c0-11-9-20-20-20z" fill="currentColor"/></svg>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" width="500" height="532" viewBox="6 -258 500 532"><path d="m379-191-46 81c84 77 163 154 163 279 0 52-43 95-95 95H111c-52 0-95-43-95-95C16 44 96-33 179-110l-46-81c-3-6-5-12-5-19 0-21 17-38 38-38h180c21 0 38 17 38 38 0 7-2 13-5 19zM227-88l-1 1C134-4 64 61 64 169c0 26 21 47 47 47h290c26 0 47-21 47-47C448 61 378-4 286-87l-1-1h-58zm-7-48h72l37-64H183l37 64zm40 96c11 0 20 9 20 20v4h8c11 0 20 9 20 20s-9 20-20 20h-47c-7 0-13 6-13 13 0 6 4 11 10 12l42 7c25 4 44 26 44 52s-19 47-44 51v5c0 11-9 20-20 20s-20-9-20-20v-4h-24c-11 0-20-9-20-20s9-20 20-20h56c6 0 12-5 12-12 0-6-4-12-10-13l-42-7c-25-4-44-26-44-51 0-29 23-53 52-53v-4c0-11 9-20 20-20z" fill="currentColor"/></svg>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" v-if="filled" viewBox="0 0 532 404"><path d="M10 74c0-35 29-64 64-64h384c35 0 64 29 64 64v32H10V74zm0 96h512v160c0 35-29 64-64 64H74c-35 0-64-29-64-64V170zm64 136c0 13 11 24 24 24h48c13 0 24-11 24-24s-11-24-24-24H98c-13 0-24 11-24 24zm144 0c0 13 11 24 24 24h64c13 0 24-11 24-24s-11-24-24-24h-64c-13 0-24 11-24 24z" fill="#a6acb9"/><path d="M10 106h512v64H10zm0 0z" fill="#1e3050"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" v-else viewBox="-10 -194 532 404"><path d="M448-136c9 0 16 7 16 16v32H48v-32c0-9 7-16 16-16h384zm16 112v160c0 9-7 16-16 16H64c-9 0-16-7-16-16V-24h416zM64-184c-35 0-64 29-64 64v256c0 35 29 64 64 64h384c35 0 64-29 64-64v-256c0-35-29-64-64-64H64zM80 96c0 13 11 24 24 24h48c13 0 24-11 24-24s-11-24-24-24h-48c-13 0-24 11-24 24zm144 0c0 13 11 24 24 24h64c13 0 24-11 24-24s-11-24-24-24h-64c-13 0-24 11-24 24z" fill="#1e3050"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" v-if="filled" viewBox="0 0 532 404"><path d="M10 74c0-35 29-64 64-64h384c35 0 64 29 64 64v32H10V74zm0 96h512v160c0 35-29 64-64 64H74c-35 0-64-29-64-64V170zm64 136c0 13 11 24 24 24h48c13 0 24-11 24-24s-11-24-24-24H98c-13 0-24 11-24 24zm144 0c0 13 11 24 24 24h64c13 0 24-11 24-24s-11-24-24-24h-64c-13 0-24 11-24 24z" fill="var(--fill1)"/><path d="M10 106h512v64H10zm0 0z" fill="currentColor"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" v-else viewBox="-10 -194 532 404"><path d="M448-136c9 0 16 7 16 16v32H48v-32c0-9 7-16 16-16h384zm16 112v160c0 9-7 16-16 16H64c-9 0-16-7-16-16V-24h416zM64-184c-35 0-64 29-64 64v256c0 35 29 64 64 64h384c35 0 64-29 64-64v-256c0-35-29-64-64-64H64zM80 96c0 13 11 24 24 24h48c13 0 24-11 24-24s-11-24-24-24h-48c-13 0-24 11-24 24zm144 0c0 13 11 24 24 24h64c13 0 24-11 24-24s-11-24-24-24h-64c-13 0-24 11-24 24z" fill="currentColor"/></svg>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
defineProps<{ filled?: boolean }>();
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
fill="#a6acb9" />
|
||||
<path
|
||||
d="M208 26c3 0 7 0 10 1v103c0 31 25 56 56 56h103c1 3 1 7 1 11v261c0 27-21 48-48 48H74c-26 0-48-21-48-48V74c0-26 22-48 48-48h134zm156 137c2 2 4 4 6 7h-96c-22 0-40-18-40-40V34c3 2 5 4 7 6l123 123zM74 10c-35 0-64 29-64 64v384c0 35 29 64 64 64h256c35 0 64-29 64-64V197c0-17-7-34-19-46L253 29c-12-12-28-19-45-19H74zm144 272c9 0 16 7 16 16v96c0 9-7 16-16 16h-96c-9 0-16-7-16-16v-96c0-9 7-16 16-16h96zm-96-16c-18 0-32 14-32 32v96c0 18 14 32 32 32h96c18 0 32-14 32-32v-18l40 25c10 7 24-1 24-13v-84c0-12-14-20-24-13l-40 25v-18c0-18-14-32-32-32h-96zm176 38v84l-48-30v-24l48-30z"
|
||||
fill="#1e3050" />
|
||||
fill="currentColor" />
|
||||
</svg>
|
||||
<!-- Remote link icon -->
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="0 0 596 564">
|
||||
@@ -15,7 +15,7 @@
|
||||
fill="#a6acb9" />
|
||||
<path
|
||||
d="M570 274H458c-2 118-55 195-99 230 116-14 207-111 211-230zM269 498c10 3 21 5 32 6-9-7-18-16-27-26l6-18c18 22 36 37 50 45 40-22 109-99 112-231H335l4-16h103c-3-132-72-209-112-231-39 22-107 96-112 224l-16 5c3-117 56-193 99-228C185 42 94 139 90 258h104l-55 16H90c0 5 1 10 1 14l-16 5c0-9-1-18-1-27C74 125 189 10 330 10s256 115 256 256-115 256-256 256c-23 0-45-3-66-9l5-15zm301-240c-4-119-95-216-211-230 44 35 97 112 99 230h112zM150 414l2 5 46 92 60-205-204 60 91 46 5 2zM31 373l-21-11 23-7 231-68 18-5-5 18-68 232-7 22-60-120-94 94-6 5-11-11 5-6 95-94-100-49z"
|
||||
fill="#1e3050" />
|
||||
fill="currentColor" />
|
||||
</svg>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
|
||||
@@ -5,5 +5,6 @@ defineProps<{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="524" height="524" viewBox="-10 -242 524 524"><path d="M252-232C113-232 0-119 0 20s113 252 252 252S504 159 504 20 391-232 252-232zM37 2c7-92 73-168 161-191-42 55-68 122-71 191H37zm0 36h89c4 69 30 136 71 191-87-23-153-98-160-191zm213 198c-50-52-83-125-87-198h179c-5 73-37 146-88 198h-4zM378 38h89c-7 92-73 168-161 191 42-55 68-122 71-191zm0 0zm0-36c-4-69-30-136-71-191 87 23 153 99 160 191h-89zM254-196c51 53 83 125 87 198H163c4-73 36-145 87-198h4z" fill="currentColor"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" v-if="filled" viewBox="0 0 627 563"><path d="M10 241h112c5-88 35-169 71-222C94 49 20 135 10 241zm0 48c10 106 84 193 183 222-36-52-66-134-71-222H10zm160-48h190c-4-62-22-121-45-165-13-25-27-44-38-56-6-5-10-8-12-10-2 2-6 5-12 10-11 12-25 31-38 56-23 44-41 103-45 165zm0 48c4 62 22 121 45 166 13 25 27 43 38 55 6 5 10 8 12 10 2-2 6-5 12-10 6-6 13-15 20-25-10-23-16-49-16-76 0-45 16-87 42-120H170zM337 19c34 50 64 126 70 210 21-8 43-12 66-12 15 0 30 2 44 5-16-97-87-175-180-203z" fill="var(--fill1)"/><path d="M473 553c80 0 144-64 144-144s-64-144-144-144-144 64-144 144 64 144 144 144zm87-145c-19-28-51-47-87-47s-68 19-87 47l-25-19c24-36 65-60 112-60s88 24 113 60l-26 19zm-23 17-26 19c-8-11-22-19-38-19s-30 8-38 19l-26-19c15-19 38-32 64-32s49 13 64 32zm-84 48c0-11 9-20 20-20s20 9 20 20-9 20-20 20-20-9-20-20z" fill="currentColor"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" v-else viewBox="22 -258 628 564"><path d="M288 232c-1 0-7-1-18-12-12-11-24-28-36-49-22-39-39-92-41-147h160c17-19 38-35 62-46-7-76-37-145-70-187 81 22 144 87 162 169 12 1 23 3 34 5-21-121-126-213-253-213C147-248 32-133 32 8s115 256 256 256c17 0 33-2 49-5-10-14-18-30-23-47-3 3-5 6-7 8-12 11-18 12-19 12zM384-8H192c3-55 20-107 42-147 12-21 24-38 35-49 12-10 18-12 19-12s7 2 18 12c12 11 24 28 36 49 22 40 39 92 41 147zm0 0zM160-8H65c6-97 75-177 166-201-35 44-67 120-71 201zM65 24h95c4 82 36 157 71 201C140 201 71 121 65 24zm431 16c62 0 112 50 112 112s-50 112-112 112-112-50-112-112S434 40 496 40zm0 256c80 0 144-64 144-144S576 8 496 8 352 72 352 152s64 144 144 144zm96-165c-24-26-58-43-96-43s-72 17-96 43l25 21c17-20 43-32 71-32s54 12 71 32l25-21zm-96 13c-21 0-41 8-55 22l25 21c8-7 19-11 30-11 12 0 22 4 30 11l25-21c-14-14-34-22-55-22zm0 92c11 0 20-9 20-20s-9-20-20-20-20 9-20 20 9 20 20 20z" fill="currentColor"/></svg>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 468 503"><path d="M10 397v32c0 35 29 64 64 64h320c35 0 64-29 64-64v-32c0-35-29-64-64-64H266v32c0 18-14 32-32 32s-32-14-32-32v-32H74c-35 0-64 29-64 64zm392 16c0 13-11 24-24 24s-24-11-24-24 11-24 24-24 24 11 24 24z" fill="#a6acb9"/><path d="M234 397c18 0 32-14 32-32V122l41 42c13 12 33 12 46 0 12-13 12-33 0-46l-96-96c-13-12-33-12-46 0l-96 96c-12 13-12 33 0 46 13 12 33 12 46 0l41-42v243c0 18 14 32 32 32z" fill="#1e3050"/></svg>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="-10 -260 468 502"><path d="M248 80c0 13-11 24-24 24s-24-11-24-24v-246l-63 63c-9 9-25 9-34 0s-9-25 0-34l104-104c9-9 25-9 34 0l104 104c9 9 9 25 0 34s-25 9-34 0l-63-63V80zm-96-8H64c-9 0-16 7-16 16v80c0 9 7 16 16 16h320c9 0 16-7 16-16V88c0-9-7-16-16-16h-88V24h88c35 0 64 29 64 64v80c0 35-29 64-64 64H64c-35 0-64-29-64-64V88c0-35 29-64 64-64h88v48zm168 56c0-13 11-24 24-24s24 11 24 24-11 24-24 24-24-11-24-24z" fill="#1e3050"/></svg>
|
||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 468 503"><path d="M10 397v32c0 35 29 64 64 64h320c35 0 64-29 64-64v-32c0-35-29-64-64-64H266v32c0 18-14 32-32 32s-32-14-32-32v-32H74c-35 0-64 29-64 64zm392 16c0 13-11 24-24 24s-24-11-24-24 11-24 24-24 24 11 24 24z" fill="#a6acb9"/><path d="M234 397c18 0 32-14 32-32V122l41 42c13 12 33 12 46 0 12-13 12-33 0-46l-96-96c-13-12-33-12-46 0l-96 96c-12 13-12 33 0 46 13 12 33 12 46 0l41-42v243c0 18 14 32 32 32z" fill="currentColor"/></svg>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="-10 -260 468 502"><path d="M248 80c0 13-11 24-24 24s-24-11-24-24v-246l-63 63c-9 9-25 9-34 0s-9-25 0-34l104-104c9-9 25-9 34 0l104 104c9 9 9 25 0 34s-25 9-34 0l-63-63V80zm-96-8H64c-9 0-16 7-16 16v80c0 9 7 16 16 16h320c9 0 16-7 16-16V88c0-9-7-16-16-16h-88V24h88c35 0 64 29 64 64v80c0 35-29 64-64 64H64c-35 0-64-29-64-64V88c0-35 29-64 64-64h88v48zm168 56c0-13 11-24 24-24s24 11 24 24-11 24-24 24-24-11-24-24z" fill="currentColor"/></svg>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
defineProps<{ filled?: boolean }>();
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" v-if="filled" viewBox="0 0 539 535">
|
||||
<path d="M61 281c2-1 4-3 6-5L269 89l202 187c2 2 4 4 6 5v180c0 35-29 64-64 64H125c-35 0-64-29-64-64V281z"
|
||||
fill="#a6acb9" />
|
||||
fill="var(--fill1)" />
|
||||
<path
|
||||
d="M247 22c13-12 32-12 44 0l224 208c13 12 13 32 1 45s-32 14-45 2L269 89 67 276c-13 12-33 12-45-1s-12-33 1-45L247 22z"
|
||||
fill="#1e3050" />
|
||||
fill="currentColor" />
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" v-else viewBox="-11 -259 535 533">
|
||||
<path
|
||||
d="M272-242c-9-8-23-8-32 0L8-34C-2-25-3-10 6 0s24 11 34 2l8-7v205c0 35 29 64 64 64h288c35 0 64-29 64-64V-5l8 7c10 9 25 8 34-2s8-25-2-34L272-242zM416-48v248c0 9-7 16-16 16H112c-9 0-16-7-16-16V-48l160-144L416-48z"
|
||||
fill="#1e3050" />
|
||||
fill="currentColor" />
|
||||
</svg>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
|
||||
7
src/components/icons/Inbox.vue
Normal file
7
src/components/icons/Inbox.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" v-if="filled" viewBox="0 0 532 468"><path d="M10 268v126c0 35 29 64 64 64h384c35 0 64-29 64-64V268c0-3 0-6-1-9L494 65c-5-32-32-55-64-55H102c-32 0-59 23-64 55L11 259c-1 3-1 6-1 9zm64-2 28-192h328l28 192h-60c-12 0-23 7-29 18l-14 28c-6 11-17 18-29 18H206c-12 0-23-7-29-18l-14-28c-5-11-17-18-29-18H74z" fill="var(--fill1)"/><path d="M249 291c9 9 25 9 34 0l64-64c9-9 9-25 0-34s-25-9-34 0l-23 23v-86c0-13-11-24-24-24s-24 11-24 24v86l-23-23c-9-9-25-9-34 0-9 10-9 25 0 34l64 64z" fill="var(--fill4)"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" v-else width="532" height="468" viewBox="-10 -226 532 468"><path d="M98-184c-16 0-30 12-32 27L35 56h100c11 0 21 5 27 14l23 34h142l23-34c6-9 16-14 27-14h100l-31-213c-2-15-16-27-31-27H98zM32 168c0 18 14 32 32 32h384c18 0 32-14 32-32V88H377l-23 34c-6 9-16 14-26 14H185c-11 0-21-5-27-14l-23-34H32v80zm2-329c5-32 32-55 64-55h317c31 0 58 23 63 55l33 227c1 3 1 6 1 10v92c0 35-29 64-64 64H64c-35 0-64-29-64-64V76c0-4 0-7 1-10l33-227zM339-21l-72 72c-6 7-16 7-22 0l-72-72c-6-6-6-16 0-22s16-6 22 0l45 44v-121c0-9 7-16 16-16s16 7 16 16V1l45-44c6-6 16-6 22 0 7 6 7 16 0 22z" fill="currentColor"/></svg>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
defineProps<{ filled?: boolean }>();
|
||||
</script>
|
||||
7
src/components/icons/LanguageIcon.vue
Normal file
7
src/components/icons/LanguageIcon.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 536"><path d="M269 477c58-131 80-180 128-288 5-12 16-19 29-19s24 7 29 19l128 288c7 16 0 35-16 42s-35 0-42-16l-20-45H347l-20 45c-7 16-26 23-42 16s-23-26-16-42zm107-83h100l-50-113-50 113z" fill="var(--fill1)"/><path d="M170 10c18 0 32 14 32 32v32h128c18 0 32 14 32 32s-14 32-32 32h-10l-8 23c-16 45-41 87-72 122 14 9 29 17 44 24l51 22-26 59-51-23c-23-10-45-22-66-36-21 17-44 32-69 44l-35 18c-15 8-35 1-43-15-7-15-1-35 15-43l34-17c17-8 32-18 47-28-14-13-27-27-39-41l-21-24c-11-14-9-34 5-46 13-11 33-9 45 5l20 24c11 14 24 27 37 39 28-31 50-66 64-106v-1H42c-18 0-32-14-32-32s14-32 32-32h96V42c0-18 14-32 32-32z" fill="var(--fill3)"/></svg>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="0 0 599 535"><path d="M178 10c13 0 24 11 24 24v56h136c13 0 24 11 24 24s-11 24-24 24h-16l-17 38c-18 44-45 83-78 116 14 10 29 18 44 25l61 28 72-161c4-8 13-14 22-14 10 0 18 6 22 14l136 304c5 12 0 27-12 32s-26 0-32-12l-29-66H341l-29 66c-5 12-20 17-32 12s-17-20-12-32l45-99-61-29c-22-9-42-21-61-35-18 14-37 26-57 36l-57 30c-12 7-26 2-32-10-6-11-2-26 10-32l57-30c14-7 27-16 40-25-27-26-51-55-70-88-6-11-3-26 9-33 11-6 26-2 33 9 17 30 39 58 65 81 31-30 55-66 72-106l9-19H34c-13 0-24-11-24-24s11-24 24-24h120V34c0-13 11-24 24-24zm311 384-63-141-63 141h126z" fill="currentColor"/></svg>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
defineProps<{ filled?: boolean }>();
|
||||
</script>
|
||||
@@ -6,12 +6,12 @@
|
||||
fill="#a6acb9" />
|
||||
<path
|
||||
d="M394 42c18 0 32 14 32 32v64H42V74c0-18 14-32 32-32h320zM42 394V170h96v256H74c-18 0-32-14-32-32zm128 32V170h256v224c0 18-14 32-32 32H170zM74 10c-35 0-64 29-64 64v320c0 35 29 64 64 64h320c35 0 64-29 64-64V74c0-35-29-64-64-64H74z"
|
||||
fill="#1e3050" />
|
||||
fill="currentColor" />
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" v-else class="v-mid m-a" height="24" width="24" viewBox="-10 -226 468 468">
|
||||
<path
|
||||
d="M384-184c18 0 32 14 32 32v64H32v-64c0-18 14-32 32-32h320zM32 168V-56h96v256H64c-18 0-32-14-32-32zm128 32V-56h256v224c0 18-14 32-32 32H160zM64-216c-35 0-64 29-64 64v320c0 35 29 64 64 64h320c35 0 64-29 64-64v-320c0-35-29-64-64-64H64z"
|
||||
fill="#1e3050" />
|
||||
fill="currentColor" />
|
||||
</svg>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 596 404"><path d="M74 170v64c0 53 43 96 96 96h96v64h64v-64h96c53 0 96-43 96-96v-64c0-53-43-96-96-96h-96V10h-64v64h-96c-53 0-96 43-96 96zm96 0h256v64H170v-64z" fill="#a6acb9"/><path d="M170 10C82 10 10 82 10 170v64c0 88 72 160 160 160h96v-64h-96c-53 0-96-43-96-96v-64c0-53 43-96 96-96h96V10h-96zm256 384c88 0 160-72 160-160v-64c0-88-72-160-160-160h-96v64h96c53 0 96 43 96 96v64c0 53-43 96-96 96h-96v64h96zM202 170h-32v64h256v-64H202z" fill="#1e3050"/></svg>
|
||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 596 404"><path d="M74 170v64c0 53 43 96 96 96h96v64h64v-64h96c53 0 96-43 96-96v-64c0-53-43-96-96-96h-96V10h-64v64h-96c-53 0-96 43-96 96zm96 0h256v64H170v-64z" fill="#a6acb9"/><path d="M170 10C82 10 10 82 10 170v64c0 88 72 160 160 160h96v-64h-96c-53 0-96-43-96-96v-64c0-53 43-96 96-96h96V10h-96zm256 384c88 0 160-72 160-160v-64c0-88-72-160-160-160h-96v64h96c53 0 96 43 96 96v64c0 53-43 96-96 96h-96v64h96zM202 170h-32v64h256v-64H202z" fill="currentColor"/></svg>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="-10 -194 596 404"><path d="M160-184C72-184 0-112 0-24v64c0 88 72 160 160 160h96v-64h-96c-53 0-96-43-96-96v-64c0-53 43-96 96-96h96v-64h-96zm256 384c88 0 160-72 160-160v-64c0-88-72-160-160-160h-96v64h96c53 0 96 43 96 96v64c0 53-43 96-96 96h-96v64h96zM192-24h-32v64h256v-64H192z" fill="currentColor"/></svg>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
|
||||
7
src/components/icons/ListIcon.vue
Normal file
7
src/components/icons/ListIcon.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 468 468"><path d="M10 170v224c0 35 29 64 64 64h320c35 0 64-29 64-64V170H10zm96 88c0-13 11-24 24-24h208c13 0 24 11 24 24s-11 24-24 24H130c-13 0-24-11-24-24zm0 112c0-13 11-24 24-24h208c13 0 24 11 24 24s-11 24-24 24H130c-13 0-24-11-24-24z" fill="var(--fill1)"/><path d="M74 10c-35 0-64 29-64 64v96h448V74c0-35-29-64-64-64H74zm240 48h64c7 0 12 4 15 10 2 6 1 13-4 17l-32 32c-6 7-16 7-22 0l-32-32c-5-4-6-11-4-17 3-6 9-10 15-10z" fill="currentColor"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" v-else viewBox="-10 -226 468 468"><path d="M64-184h320c18 0 32 14 32 32v64H32v-64c0-18 14-32 32-32zM32-56h384v224c0 18-14 32-32 32H64c-18 0-32-14-32-32V-56zM0-152v320c0 35 29 64 64 64h320c35 0 64-29 64-64v-320c0-35-29-64-64-64H64c-35 0-64 29-64 64zM112 8c-9 0-16 7-16 16s7 16 16 16h224c9 0 16-7 16-16s-7-16-16-16H112zm0 96c-9 0-16 7-16 16s7 16 16 16h224c9 0 16-7 16-16s-7-16-16-16H112zm200-260c-5 0-9 3-11 7-2 5-1 10 3 14l24 24c4 4 12 4 17 0l24-24c3-4 4-9 2-14-2-4-6-7-11-7h-48z" fill="currentColor"/></svg>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
defineProps<{ filled?: boolean }>();
|
||||
</script>
|
||||
@@ -5,7 +5,8 @@ defineProps<{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" viewBox="0 0 532 404"><path d="M10 58c0 15 7 29 19 38l208 156c17 13 41 13 58 0L503 96c12-9 19-23 19-38v272c0 35-29 64-64 64H74c-35 0-64-29-64-64V58z" fill="var(--fill1)"/><path d="M58 10c-26 0-48 22-48 48 0 15 7 29 19 38l208 156c17 13 41 13 58 0L503 96c12-9 19-23 19-38 0-26-21-48-48-48H58z" fill="var(--fill4)"/></svg>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect width="20" height="16" x="2" y="4" rx="2" />
|
||||
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" />
|
||||
</svg>
|
||||
|
||||
10
src/components/icons/MoneyCheck.vue
Normal file
10
src/components/icons/MoneyCheck.vue
Normal file
@@ -0,0 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
filled?: boolean;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 610 500"><path d="M10 74c0-35 29-64 64-64h384c35 0 64 29 64 64v73c-21 3-41 13-58 29l-58 58H306c-13 0-24 11-24 24s11 24 24 24h52l-64 64c-13 14-23 30-28 48H74c-35 0-64-29-64-64V74zm76 93c0 25 19 47 44 51l42 7c6 1 10 7 10 13 0 7-5 12-12 12h-56c-11 0-20 9-20 20s9 20 20 20h24v4c0 11 9 20 20 20s20-9 20-20v-5c25-4 44-25 44-51s-18-48-44-52l-41-7c-6-1-11-6-11-12 0-7 6-13 13-13h47c11 0 20-9 20-20s-9-20-20-20h-8v-4c0-11-9-20-20-20s-20 9-20 20v4c-29 0-52 24-52 53zm196-21c0 13 11 24 24 24h128c13 0 24-11 24-24s-11-24-24-24H306c-13 0-24 11-24 24z" fill="var(--fill1)"/><path d="m298 473 12-60c3-12 9-24 18-33l119-119 80 80-119 119c-9 9-20 15-33 18l-59 12h-3c-8 0-15-7-15-15v-3zm0 0zm251-154-80-80 29-29c22-22 58-22 80 0s22 58 0 80l-29 29z" fill="currentColor"/></svg>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="0 0 607 500"><path d="M74 42h384c18 0 32 14 32 32v81c11-3 22-5 32-5V74c0-35-28-64-64-64H74c-35 0-64 29-64 64v256c0 35 29 64 64 64h189l1-4c1-9 4-19 8-28H74c-17 0-32-14-32-32V74c0-18 15-32 32-32zm240 192c-8 0-16 7-16 16s8 16 16 16h44l32-32h-76zm-16-80c0 9 8 16 16 16h96c9 0 16-7 16-16s-7-16-16-16h-96c-8 0-16 7-16 16zM170 98c-8 0-16 7-16 16v8h-1c-26 0-47 21-47 46 0 23 17 42 39 46l45 8c7 1 12 7 12 14 0 8-6 14-14 14h-58c-8 0-16 7-16 16s8 16 16 16h24v8c0 9 8 16 16 16 9 0 16-7 16-16v-8h2c26 0 46-21 46-46 0-23-16-42-38-46l-46-8c-6-1-12-7-12-14 0-8 7-14 15-14h49c9 0 16-7 16-16s-7-16-16-16h-16v-8c0-9-7-16-16-16zm182 288 102-102 50 51-102 102c-4 5-11 8-17 9l-51 8 9-51c1-6 4-12 8-17zm0 0zm124-125 21-20c14-14 37-14 51 0s14 36 0 50l-21 21-51-51zM311 398l-12 75c0 1-1 1-1 2 0 8 7 15 15 15h3l74-13c13-2 26-8 35-17l145-146c27-26 27-69 0-96-26-26-69-26-96 0L329 364c-9 9-16 21-18 34z" fill="currentColor"/></svg>
|
||||
</template>
|
||||
@@ -1,13 +1,6 @@
|
||||
<template>
|
||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"
|
||||
stroke="none">
|
||||
<path
|
||||
d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34a.9959.9959 0 0 0-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z" />
|
||||
</svg>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"></path>
|
||||
</svg>
|
||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 534 533"><path d="m303 93 104 104 34 34 62-62c14-13 21-32 21-51s-7-38-21-51l-36-36c-13-13-32-21-51-21s-38 8-51 21l-62 62z" fill="#a6acb9"/><path d="m95 377-24 87 86-25c7-1 13-5 18-10l-71-69c-4 5-8 10-9 17zm174-250 34-34 104 104 34 34-34 34-198 198c-11 11-24 19-39 23L42 521c-8 2-17 0-23-6s-9-15-6-23l35-128c5-15 12-28 23-39l198-198z" fill="#1e3050"/></svg>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" width="534" height="533" viewBox="-12 -258 534 533"><path d="M404-216c-11 0-21 4-28 12l-59 58 93 93 58-58c8-8 12-18 12-29s-4-21-12-28l-35-36c-8-8-18-12-29-12zM93 78l93 93L387-30l-93-93L93 78zm-21 25c-2 3-4 7-5 11L36 229l114-32c4-1 8-3 11-5l-89-89zm281-330c13-13 32-21 51-21s38 8 51 21l36 36c13 13 21 32 21 51s-8 38-21 51L197 205c-11 11-24 19-39 23L30 263c-8 2-17 0-23-6s-9-15-6-23l35-128c5-15 12-28 23-39l294-294z" fill="currentColor"/></svg>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
@@ -5,7 +5,8 @@ defineProps<{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 628 628"><path d="M286 343 618 10 394 618 286 343z" fill="var(--fill1)"/><path d="M618 10 10 234l276 109L618 10z" fill="var(--fill4)"/></svg>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="m22 2-7 20-4-9-9-4Z" />
|
||||
<path d="M22 2 11 13" />
|
||||
</svg>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 567 580"><path d="M18 190c-8 14-6 32 5 43l37 36v42l-37 36c-11 12-13 29-5 43l46 80c8 14 24 21 40 17l50-14c11 8 23 15 36 21l13 50c4 15 18 26 34 26h93c16 0 30-11 34-26l13-50c13-6 25-13 36-21l50 14c15 4 32-3 40-17l46-80c8-14 6-31-6-43l-37-36c1-7 1-14 1-21s0-14-1-21l37-36c12-11 14-29 6-43l-46-80c-8-14-24-21-40-17l-50 14c-11-8-23-15-36-21l-13-50c-4-15-18-26-34-26h-93c-16 0-30 11-34 26l-13 50c-13 6-25 13-36 21l-50-13c-16-5-32 2-40 16l-46 80zm377 100c1 41-20 79-55 99-35 21-79 21-114 0-35-20-56-58-54-99-2-41 19-79 54-99 35-21 79-21 114 0 35 20 56 58 55 99zm-195 0c-2 31 14 59 40 75 27 15 59 15 86 0 26-16 42-44 41-75 1-31-15-59-41-75-27-15-59-15-86 0-26 16-42 44-40 75z" fill="#a6acb9"/><path d="M283 206c46 0 84 37 84 84 0 46-37 84-83 84-47 0-85-37-85-84 0-46 37-84 84-84zm1 196c61 0 111-51 111-112 0-62-51-112-112-112-62 0-112 51-112 112 0 62 51 112 113 112z" fill="#1e3050"/></svg>
|
||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 567 580"><path d="M18 190c-8 14-6 32 5 43l37 36v42l-37 36c-11 12-13 29-5 43l46 80c8 14 24 21 40 17l50-14c11 8 23 15 36 21l13 50c4 15 18 26 34 26h93c16 0 30-11 34-26l13-50c13-6 25-13 36-21l50 14c15 4 32-3 40-17l46-80c8-14 6-31-6-43l-37-36c1-7 1-14 1-21s0-14-1-21l37-36c12-11 14-29 6-43l-46-80c-8-14-24-21-40-17l-50 14c-11-8-23-15-36-21l-13-50c-4-15-18-26-34-26h-93c-16 0-30 11-34 26l-13 50c-13 6-25 13-36 21l-50-13c-16-5-32 2-40 16l-46 80zm377 100c1 41-20 79-55 99-35 21-79 21-114 0-35-20-56-58-54-99-2-41 19-79 54-99 35-21 79-21 114 0 35 20 56 58 55 99zm-195 0c-2 31 14 59 40 75 27 15 59 15 86 0 26-16 42-44 41-75 1-31-15-59-41-75-27-15-59-15-86 0-26 16-42 44-40 75z" fill="var(--fill1)"/><path d="M283 206c46 0 84 37 84 84 0 46-37 84-83 84-47 0-85-37-85-84 0-46 37-84 84-84zm1 196c61 0 111-51 111-112 0-62-51-112-112-112-62 0-112 51-112 112 0 62 51 112 113 112z" fill="currentColor"/></svg>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<template>
|
||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"
|
||||
stroke="none">
|
||||
<path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" />
|
||||
</svg>
|
||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 468 548"><path d="M42 122v352c0 35 29 64 64 64h256c35 0 64-29 64-64V122H42zm64 88c0-13 11-24 24-24s24 11 24 24v240c0 13-11 24-24 24s-24-11-24-24V210zm104 0c0-13 11-24 24-24s24 11 24 24v240c0 13-11 24-24 24s-24-11-24-24V210zm104 0c0-13 11-24 24-24s24 11 24 24v240c0 13-11 24-24 24s-24-11-24-24V210z" fill="color-mix(in srgb, var(--colors-danger-DEFAULT) 40%, transparent)"/><path d="M177 10c-14 0-26 9-30 22l-9 26H42c-18 0-32 14-32 32s14 32 32 32h384c18 0 32-14 32-32s-14-32-32-32h-96l-9-26c-4-13-16-22-30-22H177z" fill="var(--colors-danger-DEFAULT)"/></svg>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="3 6 5 6 21 6"></polyline>
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" v-if="filled" class="min-w-[28px]" viewBox="0 0 596 468">
|
||||
<path
|
||||
d="M10 314c0-63 41-117 98-136-1-8-2-16-2-24 0-79 65-144 144-144 55 0 104 31 128 77 14-8 30-13 48-13 53 0 96 43 96 96 0 16-4 31-10 44 44 20 74 64 74 116 0 71-57 128-128 128H154c-79 0-144-64-144-144zm199-73c-9 9-9 25 0 34s25 9 34 0l31-31v102c0 13 11 24 24 24s24-11 24-24V244l31 31c9 9 25 9 34 0s9-25 0-34l-72-72c-10-9-25-9-34 0l-72 72z"
|
||||
fill="#a6acb9" />
|
||||
fill="var(--fill1)" />
|
||||
<path
|
||||
d="M281 169c9-9 25-9 34 0l72 72c9 9 9 25 0 34s-25 9-34 0l-31-31v102c0 13-11 24-24 24s-24-11-24-24V244l-31 31c-9 9-25 9-34 0s-9-25 0-34l72-72z"
|
||||
fill="#1e3050" />
|
||||
fill="currentColor" />
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" v-else class="min-w-[28px]" viewBox="-10 -226 596 468">
|
||||
<path
|
||||
d="M240-216c-88 0-160 72-160 160 0 5 0 10 1 15C33-18 0 31 0 88c0 80 65 144 144 144h304c71 0 128-57 128-128 0-50-28-93-70-114 4-12 6-25 6-38 0-66-54-120-120-120-11 0-23 2-33 5-30-33-72-53-119-53zM128-56c0-62 50-112 112-112 38 0 71 19 91 47 7 10 20 13 30 8 9-4 20-7 31-7 40 0 72 32 72 72 0 14-4 27-11 38-4 7-5 15-2 22s9 13 16 14c35 9 61 41 61 78 0 44-36 80-80 80H144c-53 0-96-43-96-96 0-43 28-79 67-91 11-4 18-16 16-29-2-7-3-16-3-24zm177 7c-9-9-25-9-34 0l-64 64c-9 9-9 25 0 34 10 9 25 9 34 0l23-23v86c0 13 11 24 24 24s24-11 24-24V26l23 23c9 9 25 9 34 0s9-25 0-34l-64-64z"
|
||||
fill="#1e3050" />
|
||||
fill="currentColor" />
|
||||
</svg>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
|
||||
10
src/components/icons/UserIcon copy.vue
Normal file
10
src/components/icons/UserIcon copy.vue
Normal file
@@ -0,0 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
filled?: boolean;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 531"><path d="M10 150c1 99 41 281 214 363 16 8 36 8 52 0 173-82 214-264 214-363 0-26-16-48-38-57L263 13c-4-2-9-3-13-3s-8 1-12 3L48 93c-22 9-38 31-38 57zm128 212c0-44 36-80 80-80h64c44 0 80 36 80 80 0 9-7 16-16 16H154c-9 0-16-7-16-16zm168-176c0 31-25 56-56 56s-56-25-56-56 25-56 56-56 56 25 56 56z" fill="#a6acb9"/><path d="M282 282c44 0 80 36 80 80 0 9-7 16-16 16H154c-9 0-16-7-16-16 0-44 36-80 80-80h64zm-32-40c-31 0-56-25-56-56s25-56 56-56 56 25 56 56-25 56-56 56z" fill="currentColor"/></svg>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" width="518" height="532" viewBox="-3 -258 518 532"><path d="M368 120h-33l-22-64H199l-21 64h-34l32-96h160l32 96zM256-8c-35 0-64-29-64-64s29-64 64-64c36 0 64 29 64 64S292-8 256-8zm0-96c-17 0-32 14-32 32s15 32 32 32c18 0 32-14 32-32s-14-32-32-32zm0 368-12-5C92 193 7 26 17-135l1-20 238-93 239 93 1 20c9 161-76 328-227 394l-13 5zM49-133c-7 147 67 302 207 362 140-60 215-215 208-362l-208-81-207 81z" fill="currentColor"/></svg>
|
||||
</template>
|
||||
@@ -5,6 +5,6 @@ defineProps<{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 531"><path d="M10 150c1 99 41 281 214 363 16 8 36 8 52 0 173-82 214-264 214-363 0-26-16-48-38-57L263 13c-4-2-9-3-13-3s-8 1-12 3L48 93c-22 9-38 31-38 57zm128 212c0-44 36-80 80-80h64c44 0 80 36 80 80 0 9-7 16-16 16H154c-9 0-16-7-16-16zm168-176c0 31-25 56-56 56s-56-25-56-56 25-56 56-56 56 25 56 56z" fill="#a6acb9"/><path d="M282 282c44 0 80 36 80 80 0 9-7 16-16 16H154c-9 0-16-7-16-16 0-44 36-80 80-80h64zm-32-40c-31 0-56-25-56-56s25-56 56-56 56 25 56 56-25 56-56 56z" fill="#1e3050"/></svg>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" width="518" height="532" viewBox="-3 -258 518 532"><path d="M368 120h-33l-22-64H199l-21 64h-34l32-96h160l32 96zM256-8c-35 0-64-29-64-64s29-64 64-64c36 0 64 29 64 64S292-8 256-8zm0-96c-17 0-32 14-32 32s15 32 32 32c18 0 32-14 32-32s-14-32-32-32zm0 368-12-5C92 193 7 26 17-135l1-20 238-93 239 93 1 20c9 161-76 328-227 394l-13 5zM49-133c-7 147 67 302 207 362 140-60 215-215 208-362l-208-81-207 81z" fill="#1e3050"/></svg>
|
||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 531"><path d="M10 150c1 99 41 281 214 363 16 8 36 8 52 0 173-82 214-264 214-363 0-26-16-48-38-57L263 13c-4-2-9-3-13-3s-8 1-12 3L48 93c-22 9-38 31-38 57zm128 212c0-44 36-80 80-80h64c44 0 80 36 80 80 0 9-7 16-16 16H154c-9 0-16-7-16-16zm168-176c0 31-25 56-56 56s-56-25-56-56 25-56 56-56 56 25 56 56z" fill="#a6acb9"/><path d="M282 282c44 0 80 36 80 80 0 9-7 16-16 16H154c-9 0-16-7-16-16 0-44 36-80 80-80h64zm-32-40c-31 0-56-25-56-56s25-56 56-56 56 25 56 56-25 56-56 56z" fill="currentColor"/></svg>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" width="518" height="532" viewBox="-3 -258 518 532"><path d="M368 120h-33l-22-64H199l-21 64h-34l32-96h160l32 96zM256-8c-35 0-64-29-64-64s29-64 64-64c36 0 64 29 64 64S292-8 256-8zm0-96c-17 0-32 14-32 32s15 32 32 32c18 0 32-14 32-32s-14-32-32-32zm0 368-12-5C92 193 7 26 17-135l1-20 238-93 239 93 1 20c9 161-76 328-227 394l-13 5zM49-133c-7 147 67 302 207 362 140-60 215-215 208-362l-208-81-207 81z" fill="currentColor"/></svg>
|
||||
</template>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" v-if="filled" viewBox="0 0 532 404">
|
||||
<path d="M10 74v256c0 35 29 64 64 64h256c35 0 64-29 64-64V74c0-35-29-64-64-64H74c-35 0-64 29-64 64z"
|
||||
fill="#a6acb9" />
|
||||
fill="var(--fill1)" />
|
||||
<path d="M394 135v134l90 72c4 3 9 5 14 5 13 0 24-11 24-24V82c0-13-11-24-24-24-5 0-10 2-14 5l-90 72z"
|
||||
fill="#1e3050" />
|
||||
fill="currentColor" />
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" v-else viewBox="22 -194 564 404">
|
||||
<path
|
||||
d="M96-136c-9 0-16 7-16 16v256c0 9 7 16 16 16h256c9 0 16-7 16-16v-256c0-9-7-16-16-16H96zm-64 16c0-35 29-64 64-64h256c35 0 64 29 64 64v256c0 35-29 64-64 64H96c-35 0-64-29-64-64v-256zm506-11c4-3 9-5 14-5 13 0 24 11 24 24v240c0 13-11 24-24 24-5 0-10-2-14-5l-74-55V32l64 48V-64l-64 48v-60l74-55z"
|
||||
fill="#1e3050" />
|
||||
fill="currentColor" />
|
||||
</svg>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 564 468"><path d="M42 170h241c-40 35-65 87-65 144 0 17 2 33 6 48H74c-18 0-32-14-32-32V170z" fill="#a6acb9"/><path d="M458 42H345l-96 96h84c-18 8-35 19-50 32H42v160c0 18 14 32 32 32h150c3 11 7 22 11 32H74c-35 0-64-29-64-64V74c0-35 29-64 64-64h384c35 0 64 29 64 64v84c-11-8-23-15-35-20h3V74c0-5-1-10-3-14l-63 63c-5-1-9-1-14-1-11 0-23 1-33 3l82-83h-1zM43 138l96-96H74c-18 0-32 14-32 32v64h1zm46 0h114l96-96H185l-96 96zm321 288c62 0 112-50 112-112s-50-112-112-112-112 50-112 112 50 112 112 112zm0-256c80 0 144 64 144 144s-64 144-144 144-144-64-144-144 64-144 144-144zm-40 74c5-3 11-3 16 0l94 56c4 3 7 8 7 14s-3 11-7 14l-94 56c-5 3-11 3-16 0s-8-8-8-14V258c0-6 3-11 8-14zm24 98 46-28-46-28v56z" fill="#1e3050"/></svg>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" width="564" height="468" viewBox="22 -194 564 468"><path d="M480-152H367l-96 96h84c-18 8-35 19-50 32H64v160c0 18 14 32 32 32h150c3 11 7 22 11 32H96c-35 0-64-29-64-64v-256c0-35 29-64 64-64h384c35 0 64 29 64 64v84c-11-8-23-15-35-20h3v-64c0-5-1-10-3-14l-63 63c-5-1-9-1-14-1-11 0-23 1-33 3l82-83h-1zM65-56l96-96H96c-18 0-32 14-32 32v64h1zm46 0h114l96-96H207l-96 96zm321 288c62 0 112-50 112-112S494 8 432 8 320 58 320 120s50 112 112 112zm0-256c80 0 144 64 144 144s-64 144-144 144-144-64-144-144S352-24 432-24zm-40 74c5-3 11-3 16 0l94 56c4 3 7 8 7 14s-3 11-7 14l-94 56c-5 3-11 3-16 0s-8-8-8-14V64c0-6 3-11 8-14zm24 98 46-28-46-28v56z" fill="#1e3050"/></svg>
|
||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 564 468"><path d="M10 74v256c0 35 29 64 64 64h161c-11-24-17-51-17-80 0-106 86-192 192-192 42 0 81 13 112 36V74c0-15-5-29-14-40l-46 46c-16-4-34-6-52-6h-10l64-64h-92l-1 1-95 95h-68l96-96h-92l-1 1-95 95H48l96-96H74c-35 0-64 29-64 64z" fill="var(--fill1)"/><path d="M266 314c0-80 64-144 144-144s144 64 144 144-64 144-144 144-144-64-144-144zm104-62c-5 3-8 8-8 14v96c0 6 3 11 8 14s11 3 16 0l80-48c5-3 8-8 8-14s-3-11-8-14l-80-48c-5-3-11-3-16 0z" fill="currentColor"/></svg>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" width="564" height="468" viewBox="22 -194 564 468"><path d="M480-152H367l-96 96h84c-18 8-35 19-50 32H64v160c0 18 14 32 32 32h150c3 11 7 22 11 32H96c-35 0-64-29-64-64v-256c0-35 29-64 64-64h384c35 0 64 29 64 64v84c-11-8-23-15-35-20h3v-64c0-5-1-10-3-14l-63 63c-5-1-9-1-14-1-11 0-23 1-33 3l82-83h-1zM65-56l96-96H96c-18 0-32 14-32 32v64h1zm46 0h114l96-96H207l-96 96zm321 288c62 0 112-50 112-112S494 8 432 8 320 58 320 120s50 112 112 112zm0-256c80 0 144 64 144 144s-64 144-144 144-144-64-144-144S352-24 432-24zm-40 74c5-3 11-3 16 0l94 56c4 3 7 8 7 14s-3 11-7 14l-94 56c-5 3-11 3-16 0s-8-8-8-14V64c0-6 3-11 8-14zm24 98 46-28-46-28v56z" fill="currentColor"/></svg>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
|
||||
8
src/components/icons/hard-drive.vue
Normal file
8
src/components/icons/hard-drive.vue
Normal file
@@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 468 468"><path d="M10 74c0-35 29-64 64-64h320c35 0 64 29 64 64v256c0-35-29-64-64-64H74c-35 0-64 29-64 64V74zm288 288c0 18-14 32-32 32s-32-14-32-32 14-32 32-32 32 14 32 32z" fill="var(--fill1)"/><path d="M10 330c0-35 29-64 64-64h320c35 0 64 29 64 64v64c0 35-29 64-64 64H74c-35 0-64-29-64-64v-64zm288 32c0-18-14-32-32-32s-32 14-32 32 14 32 32 32 32-14 32-32zm64 32c18 0 32-14 32-32s-14-32-32-32-32 14-32 32 14 32 32 32z" fill="currentColor"/></svg>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="-10 -226 468 468"><path d="M64-184c-18 0-32 14-32 32V17c9-6 20-9 32-9h320c12 0 23 3 32 9v-169c0-18-14-32-32-32H64zM32 72v96c0 18 14 32 32 32h320c18 0 32-14 32-32V72c0-18-14-32-32-32H64c-18 0-32 14-32 32zM0 72v-224c0-35 29-64 64-64h320c35 0 64 29 64 64v320c0 35-29 64-64 64H64c-35 0-64-29-64-64V72zm256 24c13 0 24 11 24 24s-11 24-24 24-24-11-24-24 11-24 24-24zm96 0c13 0 24 11 24 24s-11 24-24 24-24-11-24-24 11-24 24-24z" fill="currentColor"/></svg>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineProps<{ filled?: boolean }>();
|
||||
</script>
|
||||
@@ -1,42 +0,0 @@
|
||||
import { createStaticVNode } from "vue";
|
||||
|
||||
export const Home = createStaticVNode(`<svg aria-hidden="true" aria-label="" class="v-mid m-a" height="24" role="img" viewBox="0 0 24 24" width="24">
|
||||
<path
|
||||
d="M4.6 22.73A107 107 0 0 0 11 23h2.22c2.43-.04 4.6-.16 6.18-.27A3.9 3.9 0 0 0 23 18.8v-8.46a4 4 0 0 0-1.34-3L14.4.93a3.63 3.63 0 0 0-4.82 0L2.34 7.36A4 4 0 0 0 1 10.35v8.46a3.9 3.9 0 0 0 3.6 3.92M13.08 2.4l7.25 6.44a2 2 0 0 1 .67 1.5v8.46a1.9 1.9 0 0 1-1.74 1.92q-1.39.11-3.26.19V16a4 4 0 0 0-8 0v4.92q-1.87-.08-3.26-.19A1.9 1.9 0 0 1 3 18.81v-8.46a2 2 0 0 1 .67-1.5l7.25-6.44a1.63 1.63 0 0 1 2.16 0M13.12 21h-2.24a1 1 0 0 1-.88-1v-4a2 2 0 1 1 4 0v4a1 1 0 0 1-.88 1">
|
||||
</path>
|
||||
</svg>`, 1);
|
||||
export const HomeFilled = createStaticVNode(`<svg aria-hidden="true" aria-label="" class="v-mid m-a" height="24" role="img" viewBox="0 0 24 24" width="24">
|
||||
<path
|
||||
d="M9.59.92a3.63 3.63 0 0 1 4.82 0l7.25 6.44A4 4 0 0 1 23 10.35v8.46a3.9 3.9 0 0 1-3.6 3.92 106 106 0 0 1-14.8 0A3.9 3.9 0 0 1 1 18.8v-8.46a4 4 0 0 1 1.34-3zM12 16a5 5 0 0 1-3.05-1.04l-1.23 1.58a7 7 0 0 0 8.56 0l-1.23-1.58A5 5 0 0 1 12 16">
|
||||
</path>
|
||||
</svg>`, 1);
|
||||
export const Dashboard = createStaticVNode(`<svg aria-hidden="true" aria-label="" class="v-mid m-a" height="24" role="img" viewBox="0 0 24 24" width="24">
|
||||
<path
|
||||
d="M23 5a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v14a4 4 0 0 0 4 4h14a4 4 0 0 0 4-4zm-10 6V3h6a2 2 0 0 1 2 2v6zm8 8a2 2 0 0 1-2 2h-6v-8h8zM5 3h6v18H5a2 2 0 0 1-2-2V5c0-1.1.9-2 2-2">
|
||||
</path>
|
||||
</svg>`, 1);
|
||||
export const DashboardFilled = createStaticVNode(`<svg aria-hidden="true" aria-label="" class="v-mid m-a" height="24" role="img" viewBox="0 0 24 24" width="24">
|
||||
<path
|
||||
d="M11 23H5a4 4 0 0 1-4-4V5a4 4 0 0 1 4-4h6zm12-4a4 4 0 0 1-4 4h-6V13h10zM19 1a4 4 0 0 1 4 4v6H13V1z">
|
||||
</path>
|
||||
</svg>`, 1);
|
||||
export const Add = createStaticVNode(`<svg aria-hidden="true" aria-label="" class="v-mid m-a" height="24" role="img" viewBox="0 0 24 24" width="24">
|
||||
<path
|
||||
d="M11 11H6v2h5v5h2v-5h5v-2h-5V6h-2zM5 1a4 4 0 0 0-4 4v14a4 4 0 0 0 4 4h14a4 4 0 0 0 4-4V5a4 4 0 0 0-4-4zm16 4v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5c0-1.1.9-2 2-2h14a2 2 0 0 1 2 2">
|
||||
</path>
|
||||
</svg>`, 1);
|
||||
export const AddFilled = createStaticVNode(`<svg aria-hidden="true" aria-label="" class="v-mid m-a" height="24" role="img" viewBox="0 0 24 24" width="24">
|
||||
<path
|
||||
d="M1 5a4 4 0 0 1 4-4h14a4 4 0 0 1 4 4v14a4 4 0 0 1-4 4H5a4 4 0 0 1-4-4zm10 6H6v2h5v5h2v-5h5v-2h-5V6h-2z">
|
||||
</path>
|
||||
</svg>`, 1);
|
||||
export const Bell = createStaticVNode(`<svg aria-hidden="true" aria-label="" class="v-mid m-a" height="24" role="img" viewBox="0 0 24 24" width="24">
|
||||
<path
|
||||
d="M16 19h8v-2h-.34a3.15 3.15 0 0 1-3.12-2.76l-.8-6.41a7.8 7.8 0 0 0-15.48 0l-.8 6.41A3.15 3.15 0 0 1 .34 17H0v2h8v1h.02a3.4 3.4 0 0 0 3.38 3h1.2a3.4 3.4 0 0 0 3.38-3H16zm1.75-10.92.8 6.4c.12.95.5 1.81 1.04 2.52H4.4c.55-.7.92-1.57 1.04-2.51l.8-6.41a5.8 5.8 0 0 1 11.5 0M13.4 19c.33 0 .6.27.6.6 0 .77-.63 1.4-1.4 1.4h-1.2a1.4 1.4 0 0 1-1.4-1.4c0-.33.27-.6.6-.6z">
|
||||
</path>
|
||||
</svg>`, 1);
|
||||
export const BellFilled = createStaticVNode(`<svg aria-hidden="true" aria-label="" class="v-mid m-a" height="24" role="img" viewBox="0 0 24 24" width="24">
|
||||
<path
|
||||
d="M20.54 14.24A3.15 3.15 0 0 0 23.66 17H24v2h-8v1h-.02a3.4 3.4 0 0 1-3.38 3h-1.2a3.4 3.4 0 0 1-3.38-3H8v-1H0v-2h.34a3.15 3.15 0 0 0 3.12-2.76l.8-6.41a7.8 7.8 0 0 1 15.48 0zM10 19.6c0 .77.63 1.4 1.4 1.4h1.2c.77 0 1.4-.63 1.4-1.4a.6.6 0 0 0-.6-.6h-2.8a.6.6 0 0 0-.6.6" ></path>
|
||||
</svg>`, 1);
|
||||
export const Search = createStaticVNode(`<svg aria-hidden="true" aria-label="" class="v-mid m-a" height="24" role="img" viewBox="0 0 24 24" width="24"><path d="M17.33 18.74a10 10 0 1 1 1.41-1.41l4.47 4.47-1.41 1.41zM11 3a8 8 0 1 0 0 16 8 8 0 0 0 0-16"></path></svg>`, 1);
|
||||
7
src/components/icons/shield-user.vue
Normal file
7
src/components/icons/shield-user.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 531"><path d="M10 150c1 99 41 281 214 363 16 8 36 8 52 0 173-82 214-264 214-363 0-26-16-48-38-57L263 13c-4-2-9-3-13-3s-8 1-12 3L48 93c-22 9-38 31-38 57zm128 212c0-44 36-80 80-80h64c44 0 80 36 80 80 0 9-7 16-16 16H154c-9 0-16-7-16-16zm168-176c0 31-25 56-56 56s-56-25-56-56 25-56 56-56 56 25 56 56z" fill="var(--fill1)"/><path d="M282 282c44 0 80 36 80 80 0 9-7 16-16 16H154c-9 0-16-7-16-16 0-44 36-80 80-80h64zm-32-40c-31 0-56-25-56-56s25-56 56-56 56 25 56 56-25 56-56 56z" fill="currentColor"/></svg>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" width="500" height="529" viewBox="6 -257 500 529"><path d="M231-240c16-7 34-7 50 0l177 75 4 2c18 9 32 28 34 50v24c-6 102-52 267-214 344l-5 2c-11 5-24 5-35 2l-6-1-6-3C68 178 22 14 17-88l-1-20c0-25 14-45 34-55l4-2 177-75zm38 29c-7-3-15-3-22-1l-3 1-177 75c-11 5-19 16-19 28l1 18c5 96 48 246 194 316l4 2c7 2 15 2 22-2l14-7c144-78 181-236 181-327v-3c-1-10-7-19-17-24l-2-1-176-75zm19 235c44 0 80 36 80 80 0 9-7 16-16 16s-16-7-16-16c0-26-21-48-48-48h-64c-26 0-48 22-48 48 0 9-7 16-16 16s-16-7-16-16c0-44 36-80 80-80h64zM256-8c-35 0-64-29-64-64s29-64 64-64 64 29 64 64-29 64-64 64zm0-96c-18 0-32 14-32 32s14 32 32 32 32-14 32-32-14-32-32-32z" fill="currentColor"/></svg>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
defineProps<{ filled?: boolean }>();
|
||||
</script>
|
||||
7
src/components/icons/windows.vue
Normal file
7
src/components/icons/windows.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 596 468"><path d="M170 74v64h64V74h288v192h-96v64h96c35 0 64-29 64-64V74c0-35-29-64-64-64H234c-35 0-64 29-64 64z" fill="var(--fill1)"/><path d="M74 138c-35 0-64 29-64 64v192c0 35 29 64 64 64h288c35 0 64-29 64-64V202c0-35-29-64-64-64H74zm24 80h240c13 0 24 11 24 24s-11 24-24 24H98c-13 0-24-11-24-24s11-24 24-24z" fill="var(--fill4)"/></svg>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" width="596" height="468" viewBox="-10 -226 596 468"><path d="M512-184H224c-18 0-32 14-32 32v16h-32v-16c0-35 29-64 64-64h288c35 0 64 29 64 64V40c0 35-29 64-64 64h-48V72h48c18 0 32-14 32-32v-192c0-18-14-32-32-32zM352-56H64c-18 0-32 14-32 32V8h352v-32c0-18-14-32-32-32zm32 96H32v128c0 18 14 32 32 32h288c18 0 32-14 32-32V40zM64-88h288c35 0 64 29 64 64v192c0 35-29 64-64 64H64c-35 0-64-29-64-64V-24c0-35 29-64 64-64z" fill="currentColor"/></svg>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
defineProps<{ filled?: boolean }>();
|
||||
</script>
|
||||
71
src/components/ui/AppButton.vue
Normal file
71
src/components/ui/AppButton.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import { cva } from "class-variance-authority";
|
||||
import { ButtonHTMLAttributes, computed } from 'vue';
|
||||
type UiButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger';
|
||||
type UiButtonSize = 'sm' | 'md' | 'lg' | 'icon' | 'icon-sm' | 'icon-lg';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
variant?: UiButtonVariant;
|
||||
size?: UiButtonSize;
|
||||
block?: boolean;
|
||||
disabled?: boolean;
|
||||
loading?: boolean;
|
||||
type?: 'button' | 'submit' | 'reset';
|
||||
onClick?: ButtonHTMLAttributes['onClick'];
|
||||
}>(),
|
||||
{
|
||||
variant: 'primary',
|
||||
size: 'md',
|
||||
block: false,
|
||||
disabled: false,
|
||||
loading: false,
|
||||
type: 'button',
|
||||
},
|
||||
);
|
||||
|
||||
const isDisabled = computed(() => props.disabled || props.loading);
|
||||
const buttonVariants = cva(":uno: inline-flex items-center justify-center gap-2 rounded-md border font-medium whitespace-nowrap shadow-[0_1px_0_rgba(27,31,36,0.04),0_1px_3px_rgba(27,31,36,0.12)] outline-none transition-[transform,box-shadow,background-color,border-color,color,opacity] duration-150 ease-out active:translate-y-[0.5px] hover:shadow-[0_2px_0_rgba(27,31,36,0.06)] disabled:cursor-not-allowed disabled:opacity-60 focus-visible:ring-4",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
primary: 'border-transparent bg-primary text-white hover:bg-primaryHover focus-visible:ring-primary/25',
|
||||
secondary: 'border-border bg-white text-text hover:bg-header focus-visible:ring-#0969da/20',
|
||||
ghost: 'border-transparent bg-transparent text-text hover:bg-header focus-visible:ring-#0969da/20 shadow-none',
|
||||
danger: 'border-transparent bg-danger text-white hover:opacity-92 focus-visible:ring-danger/20',
|
||||
},
|
||||
size: {
|
||||
sm: 'min-h-[28px] px-3 text-[12px] leading-[20px]',
|
||||
md: 'min-h-[32px] px-3 text-[14px] leading-[20px]',
|
||||
lg: 'min-h-[36px] px-4 text-[14px] leading-[20px]',
|
||||
icon: 'min-h-0 p-2',
|
||||
'icon-sm': 'min-h-0 p-1',
|
||||
'icon-lg': 'min-h-0 p-3',
|
||||
},
|
||||
block: {
|
||||
true: 'w-full',
|
||||
false: '',
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: props.variant,
|
||||
size: props.size,
|
||||
block: props.block,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button :type="type" :disabled="isDisabled" :class="cn(buttonVariants({variant, size, block}))" v-on:click="onClick" :aria-busy="loading || undefined">
|
||||
<span
|
||||
v-if="loading"
|
||||
class="h-4 w-4 shrink-0 animate-spin rounded-full border-2 border-current border-r-transparent"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<slot v-else name="icon" />
|
||||
<slot />
|
||||
</button>
|
||||
</template>
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import AppButton from '@/components/app/AppButton.vue';
|
||||
import AppDialog from '@/components/app/AppDialog.vue';
|
||||
import AppButton from '@/components/ui/AppButton.vue';
|
||||
import AppDialog from '@/components/ui/AppDialog.vue';
|
||||
import AlertTriangleIcon from '@/components/icons/AlertTriangleIcon.vue';
|
||||
import { useAppConfirm } from '@/composables/useAppConfirm';
|
||||
|
||||
117
src/components/ui/AppDialog.vue
Normal file
117
src/components/ui/AppDialog.vue
Normal file
@@ -0,0 +1,117 @@
|
||||
<script setup lang="ts">
|
||||
import XIcon from '@/components/icons/XIcon.vue';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useTranslation } from 'i18next-vue';
|
||||
import { onBeforeUnmount, watch } from 'vue';
|
||||
import ClientOnly from '../ClientOnly';
|
||||
|
||||
// Ensure client-side only rendering to avoid hydration mismatch
|
||||
const props = withDefaults(defineProps<{
|
||||
visible: boolean;
|
||||
title?: string;
|
||||
closable?: boolean;
|
||||
maxWidthClass?: string;
|
||||
}>(), {
|
||||
title: '',
|
||||
closable: true,
|
||||
maxWidthClass: 'max-w-lg',
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', value: boolean): void;
|
||||
(e: 'close'): void;
|
||||
}>();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const close = () => {
|
||||
emit('update:visible', false);
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const onKeydown = (e: KeyboardEvent) => {
|
||||
if (!props.visible) return;
|
||||
if (!props.closable) return;
|
||||
if (e.key === 'Escape') close();
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(v) => {
|
||||
if (typeof window === 'undefined') return;
|
||||
if (v) {
|
||||
document.body.style.overflow = 'hidden';
|
||||
window.addEventListener('keydown', onKeydown);
|
||||
}
|
||||
else {
|
||||
document.body.style.overflow = 'unset';
|
||||
window.removeEventListener('keydown', onKeydown);
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (typeof window === 'undefined') return;
|
||||
window.removeEventListener('keydown', onKeydown);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClientOnly>
|
||||
<Teleport to="body">
|
||||
<Transition
|
||||
enter-active-class="transition-all duration-200 ease-out"
|
||||
enter-from-class="opacity-0"
|
||||
enter-to-class="opacity-100"
|
||||
leave-active-class="transition-all duration-150 ease-in"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div v-if="visible" class="fixed inset-0 z-[9999]">
|
||||
<!-- Backdrop -->
|
||||
<div
|
||||
class="absolute inset-0 bg-black/40"
|
||||
@click="closable && close()"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
<!-- Panel -->
|
||||
<div class="absolute inset-0 flex items-center justify-center p-4">
|
||||
<div :class="cn('w-full bg-white border border-border rounded-lg shadow-lg overflow-hidden', maxWidthClass)">
|
||||
<!-- Header slot -->
|
||||
<div v-if="$slots.header" class="px-5 py-4 border-b border-border">
|
||||
<slot name="header" :close="close" />
|
||||
</div>
|
||||
<!-- Default title -->
|
||||
<div v-else-if="title" class="flex items-center justify-between gap-3 px-5 py-4 border-b border-border">
|
||||
<h3 class="font-semibold text-foreground">
|
||||
{{ title }}
|
||||
</h3>
|
||||
<button
|
||||
v-if="closable"
|
||||
type="button"
|
||||
class="p-1 rounded-md text-foreground/60 hover:text-foreground hover:bg-muted/50 transition-all"
|
||||
@click="close"
|
||||
:aria-label="t('common.close')"
|
||||
>
|
||||
<XIcon class="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="p-5 max-h-[80vh] overflow-y-auto">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<!-- Footer slot -->
|
||||
<div v-if="$slots.footer" class="px-5 py-4 border-t border-border bg-muted/20">
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
@@ -1,12 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { computed, useSlots } from 'vue';
|
||||
// Vue macro is available at compile time; provide a safe fallback for typecheck.
|
||||
declare const defineModelModifiers: undefined | (<T>() => T);
|
||||
|
||||
|
||||
type Props = {
|
||||
as?: 'input' | 'textarea' | 'select';
|
||||
modelValue?: string | number | null;
|
||||
type?: string;
|
||||
placeholder?: string;
|
||||
@@ -21,14 +20,17 @@ type Props = {
|
||||
max?: number | string;
|
||||
step?: number | string;
|
||||
maxlength?: number;
|
||||
rows?: number;
|
||||
};
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
as: 'input',
|
||||
modelValue: '',
|
||||
type: 'text',
|
||||
placeholder: '',
|
||||
readonly: false,
|
||||
disabled: false,
|
||||
rows: 3,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -40,10 +42,13 @@ const modelModifiers = (typeof defineModelModifiers === 'function'
|
||||
? defineModelModifiers<{ number?: boolean }>()
|
||||
: ({} as { number?: boolean }));
|
||||
|
||||
const isNumberLike = computed(() => props.type === 'number' || !!modelModifiers.number);
|
||||
const isNumberLike = computed(() => props.as === 'input' && (props.type === 'number' || !!modelModifiers.number));
|
||||
const hasLeadingSlot = computed(() => props.as === 'input' && !!useSlots().prefix);
|
||||
const isTextarea = computed(() => props.as === 'textarea');
|
||||
const isSelect = computed(() => props.as === 'select');
|
||||
|
||||
const onInput = (e: Event) => {
|
||||
const el = e.target as HTMLInputElement;
|
||||
const el = e.target as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
|
||||
const raw = el.value;
|
||||
if (isNumberLike.value) {
|
||||
if (raw === '') {
|
||||
@@ -61,16 +66,45 @@ const onKeyup = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Enter') emit('enter');
|
||||
};
|
||||
|
||||
const baseInputClass = 'w-full px-3 py-2 rounded-md border border-border bg-surface text-foreground placeholder:text-foreground/40 focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary/50 disabled:opacity-60 disabled:cursor-not-allowed';
|
||||
const baseInputClass = 'w-full px-3 py-2 rounded-md border border-border bg-header text-foreground placeholder:text-foreground/40 focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary/50 disabled:opacity-60 disabled:cursor-not-allowed';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn('relative', wrapperClass)">
|
||||
<div v-if="$slots.prefix" class="absolute left-3 top-1/2 -translate-y-1/2 text-foreground/50">
|
||||
<div v-if="hasLeadingSlot" class="absolute left-3 top-1/2 -translate-y-1/2 text-foreground/50">
|
||||
<slot name="prefix" />
|
||||
</div>
|
||||
|
||||
<textarea
|
||||
v-if="isTextarea"
|
||||
:id="id"
|
||||
:name="name"
|
||||
:value="modelValue ?? ''"
|
||||
:rows="rows"
|
||||
:placeholder="placeholder"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:maxlength="maxlength"
|
||||
:class="cn(baseInputClass, inputClass)"
|
||||
@input="onInput"
|
||||
@keyup="onKeyup"
|
||||
/>
|
||||
|
||||
<select
|
||||
v-else-if="isSelect"
|
||||
:id="id"
|
||||
:name="name"
|
||||
:value="modelValue ?? ''"
|
||||
:disabled="disabled"
|
||||
:class="cn(baseInputClass, inputClass)"
|
||||
@change="onInput"
|
||||
@keyup="onKeyup"
|
||||
>
|
||||
<slot />
|
||||
</select>
|
||||
|
||||
<input
|
||||
v-else
|
||||
:id="id"
|
||||
:name="name"
|
||||
:type="type"
|
||||
@@ -83,7 +117,7 @@ const baseInputClass = 'w-full px-3 py-2 rounded-md border border-border bg-surf
|
||||
:max="max"
|
||||
:step="step"
|
||||
:maxlength="maxlength"
|
||||
:class="cn(baseInputClass, $slots.prefix ? 'pl-10' : '', inputClass)"
|
||||
:class="cn(baseInputClass, hasLeadingSlot ? 'pl-10' : '', inputClass)"
|
||||
@input="onInput"
|
||||
@keyup="onKeyup"
|
||||
/>
|
||||
55
src/components/ui/AppSwitch.vue
Normal file
55
src/components/ui/AppSwitch.vue
Normal file
@@ -0,0 +1,55 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface SwitchProps {
|
||||
disabled?: boolean;
|
||||
ariaLabel?: string;
|
||||
class?: string; // Đổi từ className sang class cho chuẩn Vue
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<SwitchProps>(), {
|
||||
disabled: false,
|
||||
});
|
||||
|
||||
// Vue 3.4+ - Quản lý v-model cực gọn
|
||||
const modelValue = defineModel<boolean>({ default: false });
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'change', value: boolean): void;
|
||||
}>();
|
||||
|
||||
const toggle = () => {
|
||||
if (props.disabled) return;
|
||||
modelValue.value = !modelValue.value;
|
||||
emit('change', modelValue.value);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
:aria-checked="modelValue"
|
||||
:aria-label="ariaLabel"
|
||||
:disabled="disabled"
|
||||
@click="toggle"
|
||||
:class="cn(
|
||||
// Layout & Size
|
||||
'relative inline-flex h-6 w-11 shrink-0 items-center rounded-full border-2 border-transparent transition-colors duration-200',
|
||||
// Focus states (UnoCSS style)
|
||||
'outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2',
|
||||
// Status states
|
||||
disabled ? 'op-50 cursor-not-allowed' : 'cursor-pointer',
|
||||
modelValue ? 'bg-primary' : 'bg-gray-200 dark:bg-dark-300',
|
||||
props.class
|
||||
)"
|
||||
>
|
||||
<span
|
||||
:class="cn(
|
||||
// Toggle thumb
|
||||
'pointer-events-none block h-5 w-5 rounded-full bg-white shadow-sm transition-transform duration-200',
|
||||
modelValue ? 'translate-x-5' : 'translate-x-0'
|
||||
)"
|
||||
/>
|
||||
</button>
|
||||
</template>
|
||||
@@ -91,7 +91,7 @@ onBeforeUnmount(() => {
|
||||
type="button"
|
||||
class="p-1 rounded-md text-foreground/50 hover:text-foreground hover:bg-muted/50 transition-all"
|
||||
@click="dismiss(t.id)"
|
||||
aria-label="Dismiss"
|
||||
:aria-label="$t('toast.dismissAria')"
|
||||
>
|
||||
<XIcon class="w-4 h-4" />
|
||||
</button>
|
||||
75
src/components/ui/AsyncSelect.vue
Normal file
75
src/components/ui/AsyncSelect.vue
Normal file
@@ -0,0 +1,75 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
|
||||
interface SelectOption {
|
||||
label: string;
|
||||
value: string | number;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
loadOptions: () => Promise<SelectOption[]>;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
placeholder: 'Please select...',
|
||||
disabled: false,
|
||||
});
|
||||
|
||||
const modelValue = defineModel<string | number>();
|
||||
|
||||
const options = ref<SelectOption[]>([]);
|
||||
const loading = ref(false);
|
||||
const error = ref<string | null>(null);
|
||||
|
||||
const fetchData = async () => {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
try {
|
||||
options.value = await props.loadOptions();
|
||||
} catch {
|
||||
error.value = 'Failed to load options';
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(fetchData);
|
||||
watch(() => props.loadOptions, fetchData);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-2">
|
||||
<div class="relative w-full">
|
||||
<select
|
||||
v-model="modelValue"
|
||||
:disabled="loading || disabled"
|
||||
class="w-full appearance-none rounded-md border border-border bg-header px-3 py-2 pr-10 text-sm text-foreground outline-none transition-all focus:border-primary/50 focus:ring-2 focus:ring-primary/30 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
>
|
||||
<option value="" disabled>{{ placeholder }}</option>
|
||||
<option
|
||||
v-for="opt in options"
|
||||
:key="opt.value"
|
||||
:value="opt.value"
|
||||
>
|
||||
{{ opt.label }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3 text-foreground/40">
|
||||
<div v-if="loading" class="h-4 w-4 animate-spin rounded-full border-2 border-current border-r-transparent" />
|
||||
<div v-else class="i-carbon-chevron-down text-lg" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
v-if="error"
|
||||
type="button"
|
||||
@click="fetchData"
|
||||
class="text-xs font-medium text-danger transition hover:opacity-80"
|
||||
>
|
||||
{{ error }} · Retry
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
254
src/components/ui/BaseTable.vue
Normal file
254
src/components/ui/BaseTable.vue
Normal file
@@ -0,0 +1,254 @@
|
||||
<script setup lang="ts" generic="TData extends Record<string, any>">
|
||||
import AppButton from '@/components/ui/AppButton.vue';
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
FlexRender,
|
||||
getCoreRowModel,
|
||||
getSortedRowModel,
|
||||
useVueTable,
|
||||
type ColumnDef,
|
||||
type ColumnMeta,
|
||||
type Row,
|
||||
type SortingState,
|
||||
type Updater,
|
||||
} from '@tanstack/vue-table';
|
||||
import { useTranslation } from 'i18next-vue';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
type TableColumnMeta = ColumnMeta<TData, any> & {
|
||||
headerClass?: string;
|
||||
cellClass?: string;
|
||||
};
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
data: TData[];
|
||||
columns: ColumnDef<TData, any>[];
|
||||
loading?: boolean;
|
||||
emptyText?: string;
|
||||
tableClass?: string;
|
||||
wrapperClass?: string;
|
||||
headerRowClass?: string;
|
||||
bodyRowClass?: string | ((row: Row<TData>) => string | undefined);
|
||||
getRowId?: (originalRow: TData, index: number) => string;
|
||||
pagination?: boolean;
|
||||
currentPage?: number;
|
||||
totalPages?: number;
|
||||
totalRecords?: number;
|
||||
rowsPerPage?: number;
|
||||
pageSizeOptions?: number[];
|
||||
canPreviousPage?: boolean;
|
||||
canNextPage?: boolean;
|
||||
skeletonRows?: number;
|
||||
}>(), {
|
||||
loading: false,
|
||||
emptyText: 'No data available.',
|
||||
pagination: false,
|
||||
currentPage: 1,
|
||||
totalPages: 1,
|
||||
totalRecords: 0,
|
||||
rowsPerPage: 10,
|
||||
pageSizeOptions: () => [],
|
||||
canPreviousPage: false,
|
||||
canNextPage: false,
|
||||
skeletonRows: 10,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'previous-page'): void;
|
||||
(e: 'next-page'): void;
|
||||
(e: 'page-size-change', value: number): void;
|
||||
}>();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const sorting = ref<SortingState>([]);
|
||||
|
||||
function updateSorting(updaterOrValue: Updater<SortingState>) {
|
||||
sorting.value = typeof updaterOrValue === 'function'
|
||||
? updaterOrValue(sorting.value)
|
||||
: updaterOrValue;
|
||||
}
|
||||
|
||||
const table = useVueTable<TData>({
|
||||
get data() {
|
||||
return props.data;
|
||||
},
|
||||
get columns() {
|
||||
return props.columns;
|
||||
},
|
||||
getRowId: props.getRowId,
|
||||
state: {
|
||||
get sorting() {
|
||||
return sorting.value;
|
||||
},
|
||||
},
|
||||
onSortingChange: updateSorting,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
});
|
||||
|
||||
function resolveBodyRowClass(row: Row<TData>) {
|
||||
return typeof props.bodyRowClass === 'function'
|
||||
? props.bodyRowClass(row)
|
||||
: props.bodyRowClass;
|
||||
}
|
||||
|
||||
const shouldRenderPagination = computed(() => (
|
||||
props.pagination
|
||||
&& !props.loading
|
||||
&& table.getRowModel().rows.length > 0
|
||||
));
|
||||
|
||||
const skeletonRowIndexes = computed(() =>
|
||||
Array.from({ length: Math.max(1, props.skeletonRows) }, (_, index) => index)
|
||||
);
|
||||
|
||||
const skeletonColumnIndexes = computed(() =>
|
||||
Array.from({ length: Math.max(1, props.columns.length) }, (_, index) => index)
|
||||
);
|
||||
|
||||
function previousPage() {
|
||||
if (!props.canPreviousPage) return;
|
||||
emit('previous-page');
|
||||
}
|
||||
|
||||
function nextPage() {
|
||||
if (!props.canNextPage) return;
|
||||
emit('next-page');
|
||||
}
|
||||
|
||||
function changePageSize(event: Event) {
|
||||
const nextValue = Number((event.target as HTMLSelectElement).value) || props.rowsPerPage;
|
||||
emit('page-size-change', nextValue);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn('overflow-x-auto rounded-xl border border-gray-200 bg-white', wrapperClass)">
|
||||
<table :class="cn('w-full min-w-[48rem] border-collapse', tableClass)">
|
||||
<thead class="bg-header">
|
||||
<tr
|
||||
v-for="headerGroup in table.getHeaderGroups()"
|
||||
:key="headerGroup.id"
|
||||
:class="cn('border-b border-gray-200', headerRowClass)"
|
||||
>
|
||||
<th
|
||||
v-for="header in headerGroup.headers"
|
||||
:key="header.id"
|
||||
:class="cn(
|
||||
'px-4 py-3 text-left text-sm font-medium text-gray-600',
|
||||
header.column.getCanSort() && !header.isPlaceholder && 'cursor-pointer select-none',
|
||||
(header.column.columnDef.meta as TableColumnMeta | undefined)?.headerClass
|
||||
)"
|
||||
@click="header.column.getToggleSortingHandler()?.($event)"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<FlexRender
|
||||
v-if="!header.isPlaceholder"
|
||||
:render="header.column.columnDef.header"
|
||||
:props="header.getContext()"
|
||||
/>
|
||||
<span
|
||||
v-if="header.column.getCanSort()"
|
||||
class="text-[10px] uppercase tracking-wide text-gray-400"
|
||||
>
|
||||
{{ header.column.getIsSorted() === 'asc' ? 'asc' : header.column.getIsSorted() === 'desc' ? 'desc' : '' }}
|
||||
</span>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<template v-if="loading">
|
||||
<tr v-if="$slots.loading">
|
||||
<td
|
||||
:colspan="columns.length || 1"
|
||||
class="px-4 py-10 text-center text-sm text-gray-500"
|
||||
>
|
||||
<slot name="loading" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr
|
||||
v-for="rowIndex in skeletonRowIndexes"
|
||||
v-else
|
||||
:key="`skeleton-row-${rowIndex}`"
|
||||
class="border-b border-gray-200 last:border-b-0"
|
||||
>
|
||||
<td
|
||||
v-for="columnIndex in skeletonColumnIndexes"
|
||||
:key="`skeleton-cell-${rowIndex}-${columnIndex}`"
|
||||
class="px-4 py-3 align-middle"
|
||||
>
|
||||
<div class="animate-pulse space-y-2">
|
||||
<div
|
||||
:class="cn(
|
||||
'h-4 rounded bg-muted/50',
|
||||
columnIndex === skeletonColumnIndexes.length - 1
|
||||
? 'ml-auto w-16'
|
||||
: 'w-full max-w-[12rem]'
|
||||
)"
|
||||
/>
|
||||
<div
|
||||
v-if="columnIndex === 0"
|
||||
class="h-3 w-24 rounded bg-muted/40"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<tr v-else-if="!table.getRowModel().rows.length">
|
||||
<td
|
||||
:colspan="columns.length || 1"
|
||||
class="px-4 py-10 text-center text-sm text-gray-500"
|
||||
>
|
||||
<slot name="empty">
|
||||
{{ emptyText }}
|
||||
</slot>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr
|
||||
v-for="row in table.getRowModel().rows"
|
||||
v-else
|
||||
:key="row.id"
|
||||
:class="cn(
|
||||
'border-b border-gray-200 transition-colors last:border-b-0 hover:bg-gray-50',
|
||||
resolveBodyRowClass(row)
|
||||
)"
|
||||
>
|
||||
<td
|
||||
v-for="cell in row.getVisibleCells()"
|
||||
:key="cell.id"
|
||||
:class="cn(
|
||||
'px-4 py-3 align-middle',
|
||||
(cell.column.columnDef.meta as TableColumnMeta | undefined)?.cellClass
|
||||
)"
|
||||
>
|
||||
<FlexRender
|
||||
:render="cell.column.columnDef.cell"
|
||||
:props="cell.getContext()"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div v-if="shouldRenderPagination" class="flex flex-col gap-3 border-t border-gray-200 bg-muted/20 px-6 py-4 text-xs text-foreground/55 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>{{ t('common.page', { current: currentPage, total: totalPages }) }} · {{ totalRecords }} {{ t('common.records') }}</div>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label v-if="pageSizeOptions.length" class="flex items-center gap-2">
|
||||
<span>{{ t('common.rowsPerPage') }}</span>
|
||||
<select class="rounded-md border border-border bg-background px-2 py-1 text-xs text-foreground" :value="String(rowsPerPage)" @change="changePageSize">
|
||||
<option v-for="option in pageSizeOptions" :key="option" :value="String(option)">{{ option }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<div class="flex items-center gap-2 xl:justify-end">
|
||||
<AppButton size="sm" variant="secondary" :disabled="!canPreviousPage" @click="previousPage">{{ t('common.previous') }}</AppButton>
|
||||
<AppButton size="sm" variant="secondary" :disabled="!canNextPage" @click="nextPage">{{ t('common.next') }}</AppButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
58
src/composables/useAdminRuntimeMqtt.ts
Normal file
58
src/composables/useAdminRuntimeMqtt.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { TinyMqttClient } from "@/lib/liteMqtt";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { computed, onBeforeUnmount, watch } from "vue";
|
||||
|
||||
type RuntimeMessage = {
|
||||
topic: string;
|
||||
payload: any;
|
||||
};
|
||||
|
||||
const mqttBrokerUrl = "wss://mqtt-dashboard.com:8884/mqtt";
|
||||
|
||||
export function useAdminRuntimeMqtt(onMessage: (message: RuntimeMessage) => void) {
|
||||
const auth = useAuthStore();
|
||||
let client: TinyMqttClient | undefined;
|
||||
|
||||
const isAdmin = computed(() => auth.user?.role?.toUpperCase?.() === "ADMIN");
|
||||
|
||||
const connect = () => {
|
||||
if (import.meta.env.SSR || !isAdmin.value) return;
|
||||
disconnect();
|
||||
client = new TinyMqttClient(
|
||||
mqttBrokerUrl,
|
||||
["picpic/events", "picpic/logs/#", "picpic/job/+"],
|
||||
(topic, raw) => {
|
||||
try {
|
||||
onMessage({ topic, payload: JSON.parse(raw) });
|
||||
} catch {
|
||||
onMessage({ topic, payload: raw });
|
||||
}
|
||||
},
|
||||
);
|
||||
client.connect();
|
||||
};
|
||||
|
||||
const disconnect = () => {
|
||||
client?.disconnect();
|
||||
client = undefined;
|
||||
};
|
||||
|
||||
const stopWatch = watch(
|
||||
() => [auth.user?.id, auth.user?.role],
|
||||
() => {
|
||||
if (isAdmin.value) {
|
||||
connect();
|
||||
} else {
|
||||
disconnect();
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
stopWatch();
|
||||
disconnect();
|
||||
});
|
||||
|
||||
return { disconnect };
|
||||
}
|
||||
@@ -30,12 +30,16 @@ const state = reactive<AppConfirmState>({
|
||||
});
|
||||
|
||||
const requireConfirm = (options: AppConfirmOptions) => {
|
||||
const defaultHeader = 'Confirm';
|
||||
const defaultAccept = 'OK';
|
||||
const defaultReject = 'Cancel';
|
||||
|
||||
state.visible = true;
|
||||
state.loading = false;
|
||||
state.message = options.message;
|
||||
state.header = options.header ?? 'Confirm';
|
||||
state.acceptLabel = options.acceptLabel ?? 'OK';
|
||||
state.rejectLabel = options.rejectLabel ?? 'Cancel';
|
||||
state.header = options.header ?? defaultHeader;
|
||||
state.acceptLabel = options.acceptLabel ?? defaultAccept;
|
||||
state.rejectLabel = options.rejectLabel ?? defaultReject;
|
||||
state.accept = options.accept;
|
||||
state.reject = options.reject;
|
||||
};
|
||||
|
||||
47
src/composables/useNetworkStatus.ts
Normal file
47
src/composables/useNetworkStatus.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
const isOffline = ref(false)
|
||||
|
||||
let listenersCount = 0
|
||||
|
||||
function syncNetworkStatus() {
|
||||
if (typeof navigator === 'undefined') return
|
||||
|
||||
isOffline.value = !navigator.onLine
|
||||
}
|
||||
|
||||
function handleNetworkStatusChange() {
|
||||
syncNetworkStatus()
|
||||
}
|
||||
|
||||
function startListening() {
|
||||
if (typeof window === 'undefined') return
|
||||
|
||||
if (listenersCount === 0) {
|
||||
syncNetworkStatus()
|
||||
window.addEventListener('online', handleNetworkStatusChange)
|
||||
window.addEventListener('offline', handleNetworkStatusChange)
|
||||
}
|
||||
|
||||
listenersCount += 1
|
||||
}
|
||||
|
||||
function stopListening() {
|
||||
if (typeof window === 'undefined' || listenersCount === 0) return
|
||||
|
||||
listenersCount -= 1
|
||||
|
||||
if (listenersCount === 0) {
|
||||
window.removeEventListener('online', handleNetworkStatusChange)
|
||||
window.removeEventListener('offline', handleNetworkStatusChange)
|
||||
}
|
||||
}
|
||||
|
||||
export function useNetworkStatus() {
|
||||
return {
|
||||
isOffline,
|
||||
syncNetworkStatus,
|
||||
startListening,
|
||||
stopListening,
|
||||
}
|
||||
}
|
||||
166
src/composables/useNotifications.ts
Normal file
166
src/composables/useNotifications.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
import { client as rpcClient } from '@/api/rpcclient';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useTranslation } from 'i18next-vue';
|
||||
|
||||
export type NotificationType = 'info' | 'success' | 'warning' | 'error' | 'video' | 'payment' | 'system';
|
||||
|
||||
export type AppNotification = {
|
||||
id: string;
|
||||
type: NotificationType;
|
||||
title: string;
|
||||
message: string;
|
||||
time: string;
|
||||
read: boolean;
|
||||
actionUrl?: string;
|
||||
actionLabel?: string;
|
||||
createdAt?: string;
|
||||
};
|
||||
|
||||
type NotificationApiItem = {
|
||||
id?: string;
|
||||
type?: string;
|
||||
title?: string;
|
||||
message?: string;
|
||||
read?: boolean;
|
||||
actionUrl?: string;
|
||||
actionLabel?: string;
|
||||
createdAt?: string;
|
||||
};
|
||||
|
||||
type IncomingNotificationEnvelope = {
|
||||
type?: string;
|
||||
payload?: NotificationApiItem;
|
||||
};
|
||||
|
||||
const notifications = ref<AppNotification[]>([]);
|
||||
const loading = ref(false);
|
||||
const loaded = ref(false);
|
||||
|
||||
const normalizeType = (value?: string): NotificationType => {
|
||||
switch ((value || '').toLowerCase()) {
|
||||
case 'video':
|
||||
case 'payment':
|
||||
case 'warning':
|
||||
case 'error':
|
||||
case 'success':
|
||||
case 'system':
|
||||
return value as NotificationType;
|
||||
default:
|
||||
return 'info';
|
||||
}
|
||||
};
|
||||
|
||||
const mapNotification = (item: NotificationApiItem): AppNotification => ({
|
||||
id: item.id || '',
|
||||
type: normalizeType(item.type),
|
||||
title: item.title || '',
|
||||
message: item.message || '',
|
||||
time: '',
|
||||
read: Boolean(item.read),
|
||||
actionUrl: item.actionUrl || undefined,
|
||||
actionLabel: item.actionLabel || undefined,
|
||||
createdAt: item.createdAt,
|
||||
});
|
||||
|
||||
const upsertNotification = (item: NotificationApiItem) => {
|
||||
const mapped = mapNotification({ ...item, read: item.read ?? false });
|
||||
if (!mapped.id) return;
|
||||
|
||||
const index = notifications.value.findIndex(notification => notification.id === mapped.id);
|
||||
if (index >= 0) {
|
||||
notifications.value[index] = { ...notifications.value[index], ...mapped };
|
||||
return;
|
||||
}
|
||||
|
||||
notifications.value = [mapped, ...notifications.value];
|
||||
};
|
||||
|
||||
export function useNotifications() {
|
||||
const { t, i18next } = useTranslation();
|
||||
|
||||
const formatRelativeTime = (value?: string) => {
|
||||
if (!value) return '';
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) return '';
|
||||
|
||||
const diffMs = Date.now() - date.getTime();
|
||||
const minutes = Math.max(1, Math.floor(diffMs / 60000));
|
||||
if (minutes < 60) return t('notification.time.minutesAgo', { count: minutes });
|
||||
const hours = Math.floor(minutes / 60);
|
||||
if (hours < 24) return t('notification.time.hoursAgo', { count: hours });
|
||||
const days = Math.floor(hours / 24);
|
||||
return t('notification.time.daysAgo', { count: Math.max(1, days) });
|
||||
};
|
||||
|
||||
const hydrateNotification = (item: NotificationApiItem): AppNotification => ({
|
||||
...mapNotification(item),
|
||||
time: formatRelativeTime(item.createdAt),
|
||||
});
|
||||
|
||||
const fetchNotifications = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const response = await rpcClient.listNotifications();
|
||||
notifications.value = (response.notifications || []).map(hydrateNotification);
|
||||
loaded.value = true;
|
||||
return notifications.value;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const ingestRealtimeNotification = (raw: string | IncomingNotificationEnvelope) => {
|
||||
try {
|
||||
|
||||
const envelope = typeof raw === 'string' ? JSON.parse(raw) as IncomingNotificationEnvelope : raw;
|
||||
if (envelope?.type !== 'notification.created' || !envelope.payload) return false;
|
||||
upsertNotification(envelope.payload);
|
||||
notifications.value = notifications.value.map(item => ({
|
||||
...item,
|
||||
time: formatRelativeTime(item.createdAt),
|
||||
}));
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const markRead = async (id: string) => {
|
||||
if (!id) return;
|
||||
await rpcClient.markNotificationRead({ id });
|
||||
const item = notifications.value.find(notification => notification.id === id);
|
||||
if (item) item.read = true;
|
||||
};
|
||||
|
||||
const deleteNotification = async (id: string) => {
|
||||
if (!id) return;
|
||||
await rpcClient.deleteNotification({ id });
|
||||
notifications.value = notifications.value.filter(notification => notification.id !== id);
|
||||
};
|
||||
|
||||
const markAllRead = async () => {
|
||||
await rpcClient.markAllNotificationsRead();
|
||||
notifications.value = notifications.value.map(item => ({ ...item, read: true }));
|
||||
};
|
||||
|
||||
const clearAll = async () => {
|
||||
await rpcClient.clearNotifications();
|
||||
notifications.value = [];
|
||||
};
|
||||
|
||||
const unreadCount = computed(() => notifications.value.filter(item => !item.read).length);
|
||||
|
||||
return {
|
||||
notifications,
|
||||
loading,
|
||||
loaded,
|
||||
unreadCount,
|
||||
locale: computed(() => i18next.resolvedLanguage),
|
||||
fetchNotifications,
|
||||
ingestRealtimeNotification,
|
||||
markRead,
|
||||
deleteNotification,
|
||||
markAllRead,
|
||||
clearAll,
|
||||
};
|
||||
}
|
||||
59
src/composables/useRouteLoading.ts
Normal file
59
src/composables/useRouteLoading.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
const visible = ref(false)
|
||||
const progress = ref(0)
|
||||
|
||||
let timer: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
function start() {
|
||||
if (timer) clearInterval(timer)
|
||||
|
||||
visible.value = true
|
||||
progress.value = 8
|
||||
|
||||
timer = setInterval(() => {
|
||||
if (progress.value < 80) {
|
||||
progress.value += Math.random() * 12
|
||||
} else if (progress.value < 95) {
|
||||
progress.value += Math.random() * 3
|
||||
}
|
||||
}, 200)
|
||||
}
|
||||
|
||||
function finish() {
|
||||
if (timer) {
|
||||
clearInterval(timer)
|
||||
timer = null
|
||||
}
|
||||
|
||||
progress.value = 100
|
||||
|
||||
setTimeout(() => {
|
||||
visible.value = false
|
||||
progress.value = 0
|
||||
}, 250)
|
||||
}
|
||||
|
||||
function fail() {
|
||||
if (timer) {
|
||||
clearInterval(timer)
|
||||
timer = null
|
||||
}
|
||||
|
||||
progress.value = 100
|
||||
|
||||
setTimeout(() => {
|
||||
visible.value = false
|
||||
progress.value = 0
|
||||
}, 250)
|
||||
}
|
||||
|
||||
export function useRouteLoading() {
|
||||
return {
|
||||
visible,
|
||||
progress,
|
||||
start,
|
||||
finish,
|
||||
fail,
|
||||
}
|
||||
}
|
||||
76
src/composables/useSettingsPreferencesQuery.ts
Normal file
76
src/composables/useSettingsPreferencesQuery.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { client as rpcClient } from '@/api/rpcclient';
|
||||
import type { Preferences } from '@/server/api/proto/app/v1/common';
|
||||
import type { UpdatePreferencesRequest } from '@/server/api/proto/app/v1/account';
|
||||
import { useQuery } from '@pinia/colada';
|
||||
|
||||
export const SETTINGS_PREFERENCES_QUERY_KEY = ['settings', 'preferences'] as const;
|
||||
|
||||
export type SettingsPreferencesSnapshot = {
|
||||
emailNotifications: boolean;
|
||||
pushNotifications: boolean;
|
||||
marketingNotifications: boolean;
|
||||
telegramNotifications: boolean;
|
||||
language: string;
|
||||
locale: string;
|
||||
};
|
||||
|
||||
export type NotificationSettingsDraft = {
|
||||
email: boolean;
|
||||
push: boolean;
|
||||
marketing: boolean;
|
||||
telegram: boolean;
|
||||
};
|
||||
|
||||
type PreferencesResponse = {
|
||||
preferences?: Preferences;
|
||||
};
|
||||
|
||||
const DEFAULT_SETTINGS_PREFERENCES_SNAPSHOT: SettingsPreferencesSnapshot = {
|
||||
emailNotifications: true,
|
||||
pushNotifications: true,
|
||||
marketingNotifications: false,
|
||||
telegramNotifications: false,
|
||||
language: 'en',
|
||||
locale: 'en',
|
||||
};
|
||||
|
||||
const normalizePreferencesSnapshot = (responseData: unknown): SettingsPreferencesSnapshot => {
|
||||
const preferences = (responseData as PreferencesResponse | undefined)?.preferences;
|
||||
|
||||
return {
|
||||
emailNotifications: preferences?.emailNotifications ?? DEFAULT_SETTINGS_PREFERENCES_SNAPSHOT.emailNotifications,
|
||||
pushNotifications: preferences?.pushNotifications ?? DEFAULT_SETTINGS_PREFERENCES_SNAPSHOT.pushNotifications,
|
||||
marketingNotifications: preferences?.marketingNotifications ?? DEFAULT_SETTINGS_PREFERENCES_SNAPSHOT.marketingNotifications,
|
||||
telegramNotifications: preferences?.telegramNotifications ?? DEFAULT_SETTINGS_PREFERENCES_SNAPSHOT.telegramNotifications,
|
||||
language: preferences?.language ?? DEFAULT_SETTINGS_PREFERENCES_SNAPSHOT.language,
|
||||
locale: preferences?.locale ?? DEFAULT_SETTINGS_PREFERENCES_SNAPSHOT.locale,
|
||||
};
|
||||
};
|
||||
|
||||
export const createNotificationSettingsDraft = (
|
||||
snapshot: SettingsPreferencesSnapshot = DEFAULT_SETTINGS_PREFERENCES_SNAPSHOT,
|
||||
): NotificationSettingsDraft => ({
|
||||
email: snapshot.emailNotifications,
|
||||
push: snapshot.pushNotifications,
|
||||
marketing: snapshot.marketingNotifications,
|
||||
telegram: snapshot.telegramNotifications,
|
||||
});
|
||||
|
||||
export const toNotificationPreferencesPayload = (
|
||||
draft: NotificationSettingsDraft,
|
||||
): UpdatePreferencesRequest => ({
|
||||
emailNotifications: draft.email,
|
||||
pushNotifications: draft.push,
|
||||
marketingNotifications: draft.marketing,
|
||||
telegramNotifications: draft.telegram,
|
||||
});
|
||||
|
||||
export function useSettingsPreferencesQuery() {
|
||||
return useQuery({
|
||||
key: () => SETTINGS_PREFERENCES_QUERY_KEY,
|
||||
query: async () => {
|
||||
const response = await rpcClient.getPreferences();
|
||||
return normalizePreferencesSnapshot(response);
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { client } from '@/api/rpcclient';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
export interface QueueItem {
|
||||
@@ -282,22 +283,19 @@ export function useUploadQueue() {
|
||||
if (!item.file || !item.uploadedUrls) return;
|
||||
|
||||
try {
|
||||
const response = await fetch('/merge', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
filename: item.file.name,
|
||||
chunks: item.uploadedUrls,
|
||||
size: item.file.size
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'Merge failed');
|
||||
const data = await client.merge(item.file.name, item.uploadedUrls, item.file.size);
|
||||
// const response = await fetch('/merge', {
|
||||
// method: 'POST',
|
||||
// headers: { 'Content-Type': 'application/json' },
|
||||
// body: JSON.stringify({
|
||||
// filename: item.file.name,
|
||||
// chunks: item.uploadedUrls,
|
||||
// size: item.file.size
|
||||
// })
|
||||
// });
|
||||
if (!data) {
|
||||
throw new Error('No response from server');
|
||||
}
|
||||
|
||||
item.status = 'complete';
|
||||
item.progress = 100;
|
||||
item.uploaded = item.total;
|
||||
|
||||
38
src/composables/useUsageQuery.ts
Normal file
38
src/composables/useUsageQuery.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { client as rpcClient } from '@/api/rpcclient';
|
||||
import { useQuery } from '@pinia/colada';
|
||||
|
||||
export const USAGE_QUERY_KEY = ['usage'] as const;
|
||||
|
||||
export type UsageSnapshot = {
|
||||
totalVideos: number;
|
||||
totalStorage: number;
|
||||
};
|
||||
|
||||
type UsageResponse = {
|
||||
totalVideos?: number;
|
||||
totalStorage?: number;
|
||||
};
|
||||
|
||||
const DEFAULT_USAGE_SNAPSHOT: UsageSnapshot = {
|
||||
totalVideos: 0,
|
||||
totalStorage: 0,
|
||||
};
|
||||
|
||||
const normalizeUsageSnapshot = (responseData: unknown): UsageSnapshot => {
|
||||
const usage = responseData as UsageResponse | undefined;
|
||||
|
||||
return {
|
||||
totalVideos: usage?.totalVideos ?? DEFAULT_USAGE_SNAPSHOT.totalVideos,
|
||||
totalStorage: usage?.totalStorage ?? DEFAULT_USAGE_SNAPSHOT.totalStorage,
|
||||
};
|
||||
};
|
||||
|
||||
export function useUsageQuery() {
|
||||
return useQuery({
|
||||
key: () => USAGE_QUERY_KEY,
|
||||
query: async () => {
|
||||
const response = await rpcClient.getUsage();
|
||||
return normalizeUsageSnapshot(response);
|
||||
},
|
||||
});
|
||||
}
|
||||
7
src/i18n/constants.ts
Normal file
7
src/i18n/constants.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export const supportedLocales = ['en', 'vi'] as const;
|
||||
|
||||
export type SupportedLocale = (typeof supportedLocales)[number];
|
||||
|
||||
export const defaultLocale: SupportedLocale = 'en';
|
||||
|
||||
export const localeCookieKey = 'i18next';
|
||||
@@ -1,26 +1,23 @@
|
||||
import { Hono } from 'hono';
|
||||
|
||||
import { apiProxyMiddleware } from './server/middlewares/apiProxy';
|
||||
import { setupMiddlewares } from './server/middlewares/setup';
|
||||
import { registerDisplayRoutes } from './server/routes/display';
|
||||
import { registerManifestRoutes } from './server/routes/manifest';
|
||||
import { registerMergeRoutes } from './server/routes/merge';
|
||||
import { registerAuthRoutes } from './server/routes/auth';
|
||||
import { registerRpcRoutes } from './server/routes/rpc';
|
||||
import { registerSSRRoutes } from './server/routes/ssr';
|
||||
import { registerWellKnownRoutes } from './server/routes/wellKnown';
|
||||
|
||||
import { setupServices } from './server/services/grpcClient';
|
||||
import { serveStatic } from 'hono/bun';
|
||||
const app = new Hono();
|
||||
|
||||
// Global middlewares
|
||||
setupMiddlewares(app);
|
||||
|
||||
// API proxy middleware (handles /r/*)
|
||||
app.use(apiProxyMiddleware);
|
||||
|
||||
setupServices(app);
|
||||
// Routes
|
||||
registerWellKnownRoutes(app);
|
||||
registerMergeRoutes(app);
|
||||
registerDisplayRoutes(app);
|
||||
registerManifestRoutes(app);
|
||||
registerAuthRoutes(app);
|
||||
registerRpcRoutes(app);
|
||||
if (!import.meta.env.DEV) {
|
||||
app.use(serveStatic({ root: './dist/client' }))
|
||||
}
|
||||
registerSSRRoutes(app);
|
||||
|
||||
export default app;
|
||||
|
||||
@@ -85,7 +85,9 @@ export class TinyMqttClient implements ITinyMqttClient {
|
||||
break;
|
||||
case 0xD0: // PINGRESP
|
||||
break;
|
||||
case 0x30: // PUBLISH
|
||||
case 0x30: // PUBLISH QoS 0
|
||||
case 0x32: // PUBLISH QoS 1
|
||||
case 0x34: // PUBLISH QoS 2
|
||||
this.parsePublish(data);
|
||||
break;
|
||||
}
|
||||
@@ -102,9 +104,32 @@ export class TinyMqttClient implements ITinyMqttClient {
|
||||
}
|
||||
|
||||
private parsePublish(data: Uint8Array): void {
|
||||
const tLen = (data[2] << 8) | data[3];
|
||||
const topic = this.decoder.decode(data.slice(4, 4 + tLen));
|
||||
const payload = this.decoder.decode(data.slice(4 + tLen));
|
||||
let multiplier = 1;
|
||||
let remainingLength = 0;
|
||||
let offset = 1;
|
||||
let encodedByte = 0;
|
||||
|
||||
do {
|
||||
encodedByte = data[offset++];
|
||||
remainingLength += (encodedByte & 127) * multiplier;
|
||||
multiplier *= 128;
|
||||
} while ((encodedByte & 128) !== 0 && offset < data.length);
|
||||
|
||||
const variableHeaderStart = offset;
|
||||
const topicLength = (data[offset] << 8) | data[offset + 1];
|
||||
offset += 2;
|
||||
|
||||
const topic = this.decoder.decode(data.slice(offset, offset + topicLength));
|
||||
offset += topicLength;
|
||||
|
||||
const qos = (data[0] >> 1) & 0x03;
|
||||
if (qos > 0) {
|
||||
offset += 2; // packet identifier
|
||||
}
|
||||
|
||||
const consumedFromVariableHeader = offset - variableHeaderStart;
|
||||
const payloadLength = Math.max(0, remainingLength - consumedFromVariableHeader);
|
||||
const payload = this.decoder.decode(data.slice(offset, offset + payloadLength));
|
||||
this.onMessage(topic, payload);
|
||||
}
|
||||
}
|
||||
|
||||
43
src/lib/translation/index.ts
Normal file
43
src/lib/translation/index.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import i18next from "i18next";
|
||||
import I18NextHttpBackend, { HttpBackendOptions } from "i18next-http-backend";
|
||||
|
||||
const backendOptions: HttpBackendOptions = {
|
||||
loadPath: `${process.env.FRONTEND_BASE_URL || ''}/locales/{{lng}}/{{ns}}.json`,
|
||||
|
||||
request: async (_options, url, _payload, callback) => {
|
||||
try {
|
||||
const res = await fetch(url);
|
||||
|
||||
if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`);
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
callback(null, { data, status: 200 });
|
||||
} catch (error) {
|
||||
console.error("Lỗi fetch file ngôn ngữ i18n:", error);
|
||||
callback(error as any, { status: 500, data: '' });
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export const createI18nInstance = async (lng: string) => {
|
||||
const i18n = i18next.createInstance();
|
||||
|
||||
await i18n
|
||||
.use(I18NextHttpBackend)
|
||||
.init({
|
||||
lng,
|
||||
supportedLngs: ["en", "vi"],
|
||||
fallbackLng: "en",
|
||||
defaultNS: "translation",
|
||||
ns: ['translation'],
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
backend: backendOptions,
|
||||
});
|
||||
|
||||
return i18n;
|
||||
};
|
||||
|
||||
export default createI18nInstance;
|
||||
119
src/lib/utils.ts
119
src/lib/utils.ts
@@ -5,7 +5,10 @@ import { twMerge } from "tailwind-merge";
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
export function debounce<Func extends (...args: any[]) => any>(func: Func, wait: number): Func {
|
||||
export function debounce<Func extends (...args: any[]) => any>(
|
||||
func: Func,
|
||||
wait: number
|
||||
): Func {
|
||||
let timeout: ReturnType<typeof setTimeout> | null;
|
||||
return function (this: any, ...args: any[]) {
|
||||
if (timeout) clearTimeout(timeout);
|
||||
@@ -17,8 +20,8 @@ export function debounce<Func extends (...args: any[]) => any>(func: Func, wait:
|
||||
type AspectInfo = {
|
||||
width: number;
|
||||
height: number;
|
||||
ratio: string; // ví dụ: "16:9"
|
||||
float: number; // ví dụ: 1.777...
|
||||
ratio: string; // ví dụ: "16:9"
|
||||
float: number; // ví dụ: 1.777...
|
||||
};
|
||||
|
||||
function gcd(a: number, b: number): number {
|
||||
@@ -39,7 +42,7 @@ export function getImageAspectRatio(url: string): Promise<AspectInfo> {
|
||||
width: w,
|
||||
height: h,
|
||||
ratio: `${w / g}:${h / g}`,
|
||||
float: w / h
|
||||
float: w / h,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -49,48 +52,104 @@ export function getImageAspectRatio(url: string): Promise<AspectInfo> {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const formatBytes = (bytes?: number) => {
|
||||
if (!bytes) return '0 B';
|
||||
if (!bytes) return "0 B";
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
const sizes = ["B", "KB", "MB", "GB", "TB"];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
const value = parseFloat((bytes / Math.pow(k, i)).toFixed(2));
|
||||
return `${value} ${sizes[i]}`;
|
||||
// return `${new Intl.NumberFormat(getRuntimeLocaleTag()).format(value)} ${sizes[i]}`;
|
||||
};
|
||||
|
||||
export const formatDuration = (seconds?: number) => {
|
||||
if (!seconds) return '0:00';
|
||||
if (!seconds) return "0:00";
|
||||
const h = Math.floor(seconds / 3600);
|
||||
const m = Math.floor((seconds % 3600) / 60);
|
||||
const s = Math.floor(seconds % 60);
|
||||
|
||||
if (h > 0) {
|
||||
return `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
|
||||
return `${h}:${m.toString().padStart(2, "0")}:${s
|
||||
.toString()
|
||||
.padStart(2, "0")}`;
|
||||
}
|
||||
return `${m}:${s.toString().padStart(2, '0')}`;
|
||||
return `${m}:${s.toString().padStart(2, "0")}`;
|
||||
};
|
||||
|
||||
export const formatDate = (dateString: string = "", dateOnly: boolean = false) => {
|
||||
if (!dateString) return '';
|
||||
return new Date(dateString).toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
...(dateOnly ? {} : { hour: '2-digit', minute: '2-digit' })
|
||||
});
|
||||
};
|
||||
export const formatDate = (
|
||||
dateString: string = "",
|
||||
dateOnly: boolean = false
|
||||
) => {
|
||||
if (!dateString) return "";
|
||||
|
||||
export const getStatusSeverity = (status: string = "") => {
|
||||
const date = new Date(dateString);
|
||||
if (Number.isNaN(date.getTime())) return dateString;
|
||||
|
||||
const year = date.getUTCFullYear();
|
||||
const month = `${date.getUTCMonth() + 1}`.padStart(2, "0");
|
||||
const day = `${date.getUTCDate()}`.padStart(2, "0");
|
||||
|
||||
if (dateOnly) {
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
|
||||
const hours = `${date.getUTCHours()}`.padStart(2, "0");
|
||||
const minutes = `${date.getUTCMinutes()}`.padStart(2, "0");
|
||||
|
||||
return `${year}-${month}-${day} ${hours}:${minutes} UTC`;
|
||||
};
|
||||
type Status = "success" | "failed" | "pending" | string;
|
||||
export const getStatusSeverity = (status: Status = "") => {
|
||||
switch (status) {
|
||||
case 'success':
|
||||
case 'ready':
|
||||
return 'success';
|
||||
case 'failed':
|
||||
return 'danger';
|
||||
case 'pending':
|
||||
return 'warn';
|
||||
case "success":
|
||||
case "ready":
|
||||
return "success";
|
||||
case "failed":
|
||||
return "danger";
|
||||
case "pending":
|
||||
return "warn";
|
||||
default:
|
||||
return 'info';
|
||||
return "info";
|
||||
}
|
||||
};
|
||||
export const getStatusStyles = (status: Status = "") => {
|
||||
switch (status) {
|
||||
case "success":
|
||||
return "bg-success/10 text-success";
|
||||
case "failed":
|
||||
return "bg-danger/10 text-danger";
|
||||
case "pending":
|
||||
return "bg-warning/10 text-warning";
|
||||
default:
|
||||
return "bg-info/10 text-info";
|
||||
}
|
||||
};
|
||||
export const isAdmin = (role: string = "") => {
|
||||
const r = String(role).toLowerCase();
|
||||
return r === "admin" || r === "superadmin";
|
||||
};
|
||||
export type ApiErrorPayload = {
|
||||
code?: number;
|
||||
message?: string;
|
||||
data?: Record<string, any>;
|
||||
};
|
||||
export const getApiErrorPayload = (error: unknown): ApiErrorPayload | null => {
|
||||
if (!error || typeof error !== "object") return null;
|
||||
const candidate = error as {
|
||||
error?: ApiErrorPayload;
|
||||
data?: ApiErrorPayload;
|
||||
message?: string;
|
||||
};
|
||||
|
||||
if (candidate.error && typeof candidate.error === "object")
|
||||
return candidate.error;
|
||||
if (candidate.data && typeof candidate.data === "object")
|
||||
return candidate.data;
|
||||
if (candidate.message) return { message: candidate.message };
|
||||
|
||||
return null;
|
||||
};
|
||||
export const getApiErrorMessage = (error: unknown, fallback: string) => {
|
||||
const payload = getApiErrorPayload(error);
|
||||
return payload?.message || fallback;
|
||||
};
|
||||
|
||||
42
src/main.ts
42
src/main.ts
@@ -1,17 +1,29 @@
|
||||
import { PiniaColada, useQueryCache } from '@pinia/colada';
|
||||
import { createHead as CSRHead } from "@unhead/vue/client";
|
||||
import { createHead as SSRHead } from "@unhead/vue/server";
|
||||
import { createPinia } from "pinia";
|
||||
import { createHead as CSRHead } from '@unhead/vue/client';
|
||||
import { createHead as SSRHead } from '@unhead/vue/server';
|
||||
import { createPinia } from 'pinia';
|
||||
import { createSSRApp } from 'vue';
|
||||
import { RouterView } from 'vue-router';
|
||||
|
||||
import I18NextVue from 'i18next-vue';
|
||||
|
||||
import { withErrorBoundary } from './lib/hoc/withErrorBoundary';
|
||||
import createI18nInstance from './lib/translation';
|
||||
import createAppRouter from './routes';
|
||||
|
||||
const bodyClass = ":uno: font-sans text-gray-800 antialiased flex flex-col min-h-screen"
|
||||
export function createApp() {
|
||||
const bodyClass = ':uno: font-sans text-gray-800 antialiased flex flex-col min-h-screen';
|
||||
|
||||
const getSerializedAppData = () => {
|
||||
if (typeof document === 'undefined') return {} as Record<string, any>;
|
||||
return JSON.parse(document.getElementById('__APP_DATA__')?.innerText || '{}') as Record<string, any>;
|
||||
};
|
||||
|
||||
export async function createApp(lng: string = 'en') {
|
||||
const pinia = createPinia();
|
||||
const app = createSSRApp(withErrorBoundary(RouterView));
|
||||
|
||||
const head = import.meta.env.SSR ? SSRHead() : CSRHead();
|
||||
const appData = !import.meta.env.SSR ? getSerializedAppData() : ({} as Record<string, any>);
|
||||
|
||||
app.use(head);
|
||||
app.directive('nh', {
|
||||
@@ -20,11 +32,13 @@ export function createApp() {
|
||||
}
|
||||
});
|
||||
app.use(pinia);
|
||||
const i18next = await createI18nInstance(lng);
|
||||
app.use(I18NextVue, { i18next });
|
||||
app.use(PiniaColada, {
|
||||
pinia,
|
||||
plugins: [
|
||||
(context) => {
|
||||
// console.log("PiniaColada plugin initialized for store:", context);
|
||||
() => {
|
||||
// reserved for query plugins
|
||||
}
|
||||
],
|
||||
queryOptions: {
|
||||
@@ -32,19 +46,21 @@ export function createApp() {
|
||||
refetchOnWindowFocus: false,
|
||||
ssrCatchError: true,
|
||||
}
|
||||
// optional options
|
||||
})
|
||||
// app.use(vueSWR({ revalidateOnFocus: false }));
|
||||
});
|
||||
|
||||
const queryCache = useQueryCache();
|
||||
const router = createAppRouter();
|
||||
app.use(router);
|
||||
|
||||
if (!import.meta.env.SSR) {
|
||||
Object.entries(JSON.parse(document.getElementById("__APP_DATA__")?.innerText || "{}")).forEach(([key, value]) => {
|
||||
Object.entries(appData).forEach(([key, value]) => {
|
||||
(window as any)[key] = value;
|
||||
});
|
||||
if ((window as any).$p) {
|
||||
pinia.state.value = (window as any).$p;
|
||||
}
|
||||
}
|
||||
|
||||
const router = createAppRouter();
|
||||
app.use(router);
|
||||
|
||||
return { app, router, head, pinia, bodyClass, queryCache };
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ModelVideo } from "@/api/client";
|
||||
import type { Video as ModelVideo } from "@/server/api/proto/app/v1/common";
|
||||
|
||||
export const mockVideos: ModelVideo[] = [
|
||||
{
|
||||
@@ -9,7 +9,7 @@ export const mockVideos: ModelVideo[] = [
|
||||
duration: 345, // 5m 45s
|
||||
status: 'ready',
|
||||
size: 1024 * 1024 * 45, // 45MB
|
||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 24 * 2).toISOString(), // 2 days ago
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 2).toISOString(), // 2 days ago
|
||||
views: 12500,
|
||||
url: '#'
|
||||
},
|
||||
@@ -20,9 +20,8 @@ export const mockVideos: ModelVideo[] = [
|
||||
thumbnail: 'https://picsum.photos/seed/video2/640/360',
|
||||
duration: 890, // 14m 50s
|
||||
status: 'processing',
|
||||
processing_status: '75%',
|
||||
size: 1024 * 1024 * 128, // 128MB
|
||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 5).toISOString(), // 5 hours ago
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 5).toISOString(), // 5 hours ago
|
||||
views: 0,
|
||||
url: '#'
|
||||
},
|
||||
@@ -34,7 +33,7 @@ export const mockVideos: ModelVideo[] = [
|
||||
duration: 120, // 2m 00s
|
||||
status: 'ready',
|
||||
size: 1024 * 1024 * 25, // 25MB
|
||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 24 * 7).toISOString(), // 1 week ago
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 7).toISOString(), // 1 week ago
|
||||
views: 340,
|
||||
url: '#'
|
||||
},
|
||||
@@ -46,7 +45,7 @@ export const mockVideos: ModelVideo[] = [
|
||||
duration: 1800, // 30m 00s
|
||||
status: 'ready',
|
||||
size: 1024 * 1024 * 350, // 350MB
|
||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 24 * 14).toISOString(), // 2 weeks ago
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 14).toISOString(), // 2 weeks ago
|
||||
views: 12,
|
||||
url: '#'
|
||||
},
|
||||
@@ -58,7 +57,7 @@ export const mockVideos: ModelVideo[] = [
|
||||
duration: 600, // 10m 00s
|
||||
status: 'failed',
|
||||
size: 1024 * 1024 * 80, // 80MB
|
||||
created_at: new Date(Date.now() - 1000 * 60 * 30).toISOString(), // 30 mins ago
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 30).toISOString(), // 30 mins ago
|
||||
views: 0,
|
||||
url: '#'
|
||||
},
|
||||
@@ -70,7 +69,7 @@ export const mockVideos: ModelVideo[] = [
|
||||
duration: 5400, // 1h 30m
|
||||
status: 'ready',
|
||||
size: 1024 * 1024 * 1024 * 2.5, // 2.5GB
|
||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 24 * 30).toISOString(), // 1 month ago
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 30).toISOString(), // 1 month ago
|
||||
views: 45000,
|
||||
url: '#'
|
||||
},
|
||||
@@ -82,7 +81,7 @@ export const mockVideos: ModelVideo[] = [
|
||||
duration: 1540,
|
||||
status: 'ready',
|
||||
size: 1024 * 1024 * 200,
|
||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 24 * 3).toISOString(),
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 3).toISOString(),
|
||||
views: 8900,
|
||||
url: '#'
|
||||
},
|
||||
@@ -94,7 +93,7 @@ export const mockVideos: ModelVideo[] = [
|
||||
duration: 3200,
|
||||
status: 'ready',
|
||||
size: 1024 * 1024 * 800,
|
||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||
views: 1500,
|
||||
url: '#'
|
||||
},
|
||||
@@ -106,7 +105,7 @@ export const mockVideos: ModelVideo[] = [
|
||||
duration: 3200,
|
||||
status: 'ready',
|
||||
size: 1024 * 1024 * 800,
|
||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||
views: 1500,
|
||||
url: '#'
|
||||
},
|
||||
@@ -118,7 +117,7 @@ export const mockVideos: ModelVideo[] = [
|
||||
duration: 3200,
|
||||
status: 'ready',
|
||||
size: 1024 * 1024 * 800,
|
||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||
views: 1500,
|
||||
url: '#'
|
||||
},
|
||||
@@ -130,7 +129,7 @@ export const mockVideos: ModelVideo[] = [
|
||||
duration: 3200,
|
||||
status: 'ready',
|
||||
size: 1024 * 1024 * 800,
|
||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||
views: 1500,
|
||||
url: '#'
|
||||
},
|
||||
@@ -142,7 +141,7 @@ export const mockVideos: ModelVideo[] = [
|
||||
duration: 3200,
|
||||
status: 'ready',
|
||||
size: 1024 * 1024 * 800,
|
||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||
views: 1500,
|
||||
url: '#'
|
||||
},
|
||||
@@ -154,7 +153,7 @@ export const mockVideos: ModelVideo[] = [
|
||||
duration: 3200,
|
||||
status: 'ready',
|
||||
size: 1024 * 1024 * 800,
|
||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||
views: 1500,
|
||||
url: '#'
|
||||
},
|
||||
@@ -166,7 +165,7 @@ export const mockVideos: ModelVideo[] = [
|
||||
duration: 3200,
|
||||
status: 'ready',
|
||||
size: 1024 * 1024 * 800,
|
||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||
views: 1500,
|
||||
url: '#'
|
||||
},
|
||||
@@ -178,7 +177,7 @@ export const mockVideos: ModelVideo[] = [
|
||||
duration: 3200,
|
||||
status: 'ready',
|
||||
size: 1024 * 1024 * 800,
|
||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||
views: 1500,
|
||||
url: '#'
|
||||
},
|
||||
@@ -190,7 +189,7 @@ export const mockVideos: ModelVideo[] = [
|
||||
duration: 3200,
|
||||
status: 'ready',
|
||||
size: 1024 * 1024 * 800,
|
||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||
views: 1500,
|
||||
url: '#'
|
||||
},
|
||||
@@ -202,7 +201,7 @@ export const mockVideos: ModelVideo[] = [
|
||||
duration: 3200,
|
||||
status: 'ready',
|
||||
size: 1024 * 1024 * 800,
|
||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||
views: 1500,
|
||||
url: '#'
|
||||
},
|
||||
@@ -214,7 +213,7 @@ export const mockVideos: ModelVideo[] = [
|
||||
duration: 3200,
|
||||
status: 'ready',
|
||||
size: 1024 * 1024 * 800,
|
||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||
views: 1500,
|
||||
url: '#'
|
||||
},
|
||||
@@ -226,7 +225,7 @@ export const mockVideos: ModelVideo[] = [
|
||||
duration: 3200,
|
||||
status: 'ready',
|
||||
size: 1024 * 1024 * 800,
|
||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||
views: 1500,
|
||||
url: '#'
|
||||
},
|
||||
@@ -238,7 +237,7 @@ export const mockVideos: ModelVideo[] = [
|
||||
duration: 3200,
|
||||
status: 'ready',
|
||||
size: 1024 * 1024 * 800,
|
||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||
views: 1500,
|
||||
url: '#'
|
||||
},
|
||||
@@ -250,7 +249,7 @@ export const mockVideos: ModelVideo[] = [
|
||||
duration: 3200,
|
||||
status: 'ready',
|
||||
size: 1024 * 1024 * 800,
|
||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||
views: 1500,
|
||||
url: '#'
|
||||
},
|
||||
@@ -262,7 +261,7 @@ export const mockVideos: ModelVideo[] = [
|
||||
duration: 3200,
|
||||
status: 'ready',
|
||||
size: 1024 * 1024 * 800,
|
||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||
views: 1500,
|
||||
url: '#'
|
||||
},
|
||||
@@ -274,7 +273,7 @@ export const mockVideos: ModelVideo[] = [
|
||||
duration: 3200,
|
||||
status: 'ready',
|
||||
size: 1024 * 1024 * 800,
|
||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||
views: 1500,
|
||||
url: '#'
|
||||
},
|
||||
@@ -340,7 +339,7 @@ export const updateMockVideo = async (id: string, updates: { title: string; desc
|
||||
...mockVideos[videoIndex],
|
||||
title: updates.title,
|
||||
description: updates.description,
|
||||
updated_at: new Date().toISOString()
|
||||
updatedAt: new Date().toISOString()
|
||||
};
|
||||
return mockVideos[videoIndex];
|
||||
};
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
<template>
|
||||
<vue-head :input="{title: '404 - Page Not Found'}"/>
|
||||
<vue-head :input="{ title: t('notFound.headTitle') }" />
|
||||
<div class="mx-auto text-center mt-20 flex flex-col items-center gap-4">
|
||||
<h1>404 - Page Not Found</h1>
|
||||
<p>The page you are looking for does not exist.</p>
|
||||
<router-link class="btn btn-primary" to="/">Go back to Home</router-link>
|
||||
<h1>{{ t('notFound.title') }}</h1>
|
||||
<p>{{ t('notFound.description') }}</p>
|
||||
<router-link class="btn btn-primary" to="/">{{ t('notFound.backHome') }}</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { VueHead } from "@/components/VueHead";
|
||||
import { VueHead } from '@/components/VueHead';
|
||||
import { useTranslation } from 'i18next-vue';
|
||||
|
||||
const { t } = useTranslation();
|
||||
</script>
|
||||
22
src/routes/analytics/Analytics.vue
Normal file
22
src/routes/analytics/Analytics.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div>
|
||||
<PageHeader title="Analytics"
|
||||
description="Your streaming analytics will be displayed here."
|
||||
:breadcrumbs="breadcrumbs" />
|
||||
<div class="p-6 rounded-xl border border-gray-200">
|
||||
<h2 class="text-lg font-semibold mb-4">Coming Soon</h2>
|
||||
<p class="text-gray-600">We are working hard to bring you detailed analytics about your streams. Stay tuned!</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useTranslation } from 'i18next-vue';
|
||||
|
||||
const {t} = useTranslation()
|
||||
const breadcrumbs = computed(() => [
|
||||
{ label: t('pageHeader.dashboard'), to: '/overview' },
|
||||
{ label: t('pageHeader.settings'), to: '/settings' },
|
||||
{ label: "Analytics"}
|
||||
// ...(currentItem.value ? [{ label: currentItem.value.label + (currentItem.value.value.includes("admin") ? " (Admin)" : "") }] : []),
|
||||
]);
|
||||
</script>
|
||||
@@ -2,16 +2,16 @@
|
||||
<div class="w-full">
|
||||
<form @submit.prevent="onFormSubmit" class="flex flex-col gap-4 w-full">
|
||||
<div class="text-sm text-gray-600 mb-2">
|
||||
Enter your email address and we'll send you a link to reset your password.
|
||||
{{ t('auth.forgot.description') }}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<label for="email" class="text-sm font-medium text-gray-700">Email address</label>
|
||||
<AppInput id="email" v-model="form.email" type="email" placeholder="you@example.com" />
|
||||
<label for="email" class="text-sm font-medium text-gray-700">{{ t('auth.forgot.email') }}</label>
|
||||
<AppInput id="email" v-model="form.email" type="email" :placeholder="t('auth.forgot.placeholders.email')" />
|
||||
<p v-if="errors.email" class="text-xs text-red-500 mt-0.5">{{ errors.email }}</p>
|
||||
</div>
|
||||
|
||||
<AppButton type="submit" class="w-full">Send Reset Link</AppButton>
|
||||
<AppButton type="submit" class="w-full">{{ t('auth.forgot.sendResetLink') }}</AppButton>
|
||||
|
||||
<div class="text-center mt-2">
|
||||
<router-link to="/login" replace
|
||||
@@ -20,7 +20,7 @@
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M10 19l-7-7m0 0l7-7m-7 7h18"></path>
|
||||
</svg>
|
||||
Back to Sign in
|
||||
{{ t('auth.forgot.backToSignIn') }}
|
||||
</router-link>
|
||||
</div>
|
||||
</form>
|
||||
@@ -28,12 +28,14 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { client } from '@/api/client';
|
||||
import { client as rpcClient } from '@/api/rpcclient';
|
||||
import { useAppToast } from '@/composables/useAppToast';
|
||||
import { reactive } from 'vue';
|
||||
import { useTranslation } from 'i18next-vue';
|
||||
import { z } from 'zod';
|
||||
|
||||
const toast = useAppToast();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const form = reactive({
|
||||
email: ''
|
||||
@@ -42,7 +44,7 @@ const form = reactive({
|
||||
const errors = reactive<{ email?: string }>({});
|
||||
|
||||
const schema = z.object({
|
||||
email: z.string().min(1, { message: 'Email is required.' }).email({ message: 'Invalid email address.' })
|
||||
email: z.string().min(1, { message: t('auth.forgot.errors.emailRequired') }).email({ message: t('auth.forgot.errors.emailInvalid') })
|
||||
});
|
||||
|
||||
const onFormSubmit = () => {
|
||||
@@ -57,12 +59,22 @@ const onFormSubmit = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
client.auth.forgotPasswordCreate({ email: form.email })
|
||||
rpcClient.forgotPassword({ email: form.email })
|
||||
.then(() => {
|
||||
toast.add({ severity: 'success', summary: 'Success', detail: 'Reset link sent', life: 3000 });
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: t('auth.forgot.toast.successSummary'),
|
||||
detail: t('auth.forgot.toast.successDetail'),
|
||||
life: 3000,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.add({ severity: 'error', summary: 'Error', detail: error.message || 'An error occurred', life: 3000 });
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: t('auth.forgot.toast.errorSummary'),
|
||||
detail: error.message || t('auth.forgot.toast.errorDetail'),
|
||||
life: 3000,
|
||||
});
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
69
src/routes/auth/google-finalize.vue
Normal file
69
src/routes/auth/google-finalize.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<div class="flex flex-col items-center gap-3 py-6 text-center">
|
||||
<div class="i-svg-spinners-90-ring-with-bg h-10 w-10 text-blue-600"></div>
|
||||
<p class="text-sm text-gray-600">{{ message }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAppToast } from '@/composables/useAppToast';
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
import { computed, onMounted } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
const auth = useAuthStore();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const toast = useAppToast();
|
||||
|
||||
const status = computed(() => String(route.query.status ?? 'error'));
|
||||
const reason = computed(() => String(route.query.reason ?? 'google_login_failed'));
|
||||
|
||||
const reasonMessages: Record<string, string> = {
|
||||
missing_code: 'Google did not return an authorization code.',
|
||||
access_denied: 'Google login was cancelled.',
|
||||
exchange_failed: 'Failed to verify your Google sign-in. Please try again.',
|
||||
userinfo_failed: 'Failed to load your Google account information.',
|
||||
userinfo_parse_failed: 'Failed to read your Google account information.',
|
||||
missing_email: 'Your Google account did not provide an email address.',
|
||||
create_user_failed: 'Failed to create your account.',
|
||||
update_user_failed: 'Failed to update your account.',
|
||||
reload_user_failed: 'Failed to finish signing you in.',
|
||||
session_failed: 'Failed to create your sign-in session.',
|
||||
fetch_me_failed: 'Signed in with Google, but failed to load your account.',
|
||||
google_login_failed: 'Google login failed. Please try again.',
|
||||
};
|
||||
|
||||
const errorMessage = computed(() => reasonMessages[reason.value] ?? reasonMessages.google_login_failed);
|
||||
const message = computed(() => status.value === 'success' ? 'Signing you in with Google...' : errorMessage.value);
|
||||
|
||||
onMounted(async () => {
|
||||
if (status.value !== 'success') {
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: 'Google login failed',
|
||||
detail: errorMessage.value,
|
||||
life: 5000,
|
||||
});
|
||||
await router.replace({ name: 'login', query: { reason: reason.value } });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const user = await auth.fetchMe();
|
||||
if (!user) {
|
||||
throw new Error('missing_user');
|
||||
}
|
||||
|
||||
await router.replace({ name: 'overview' });
|
||||
} catch {
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: 'Google login failed',
|
||||
detail: 'Signed in with Google, but failed to load your account.',
|
||||
life: 5000,
|
||||
});
|
||||
await router.replace({ name: 'login', query: { reason: 'fetch_me_failed' } });
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -5,12 +5,12 @@
|
||||
class=":uno: w-full shadow-xl bg-white p-6 rounded-xl relative before:(content-[''] absolute inset-[-5px] translate-0 z-[-1] opacity-50 rounded-xl bg-[linear-gradient(135deg,var(--glow-stop-1)_0,var(--glow-stop-2)_25%,var(--glow-stop-3)_50%,var(--glow-stop-4)_75%,var(--glow-stop-5)_100%)] animate-[glow-enter-blur_1s_ease_.5s_both]) after:(content-[''] absolute inset-[-1px] translate-0 z-[-1] opacity-50 rounded-xl bg-[linear-gradient(135deg,transparent_0,transparent_34%,transparent_49%,#fff_57%,#fff_64%,var(--glow-stop-1)_66%,var(--glow-stop-2)_75%,var(--glow-stop-3)_83%,var(--glow-stop-4)_92%,var(--glow-stop-5)_100%)] bg-[length:300%_300%] bg-[position:0_0] bg-no-repeat transition-background-position duration-800 ease animate-[glow-enter-stroke_.5s_ease_.5s_both])">
|
||||
<div class="mb-6">
|
||||
<h2 class="text-xl font-medium text-gray-900">
|
||||
{{ content[route.name as keyof typeof content]?.title || '' }}
|
||||
{{ content[route.name as keyof typeof content.value]?.title || '' }}
|
||||
</h2>
|
||||
<vue-head :input="{
|
||||
title: content[route.name as keyof typeof content]?.headTitle || 'Authentication',
|
||||
title: content[route.name as keyof typeof content.value]?.headTitle || t('app.name'),
|
||||
meta: [
|
||||
{ name: 'description', content: content[route.name as keyof typeof content]?.subtitle || '' }
|
||||
{ name: 'description', content: content[route.name as keyof typeof content.value]?.subtitle || '' }
|
||||
]
|
||||
}" />
|
||||
</div>
|
||||
@@ -18,29 +18,38 @@
|
||||
</div>
|
||||
<router-link to="/" class="inline-flex items-center justify-center w-6 h-6 mt-10 group w-full">
|
||||
<img class="w-6 h-6" src="/apple-touch-icon.png" alt="Logo" /> <span
|
||||
class="text-[#6a6a6a] font-medium group-hover:text-gray-900">EcoStream</span>
|
||||
class="text-[#6a6a6a] font-medium group-hover:text-gray-900">{{ t('app.name') }}</span>
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { useTranslation } from 'i18next-vue';
|
||||
import { computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
const route = useRoute();
|
||||
const content = {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const content = computed(() => ({
|
||||
login: {
|
||||
headTitle: "Login to your account",
|
||||
title: 'Sign in to your dashboard',
|
||||
subtitle: 'Please enter your details to sign in.'
|
||||
headTitle: t('auth.layout.login.headTitle'),
|
||||
title: t('auth.layout.login.title'),
|
||||
subtitle: t('auth.layout.login.subtitle')
|
||||
},
|
||||
signup: {
|
||||
headTitle: "Create your account",
|
||||
title: 'Create your account',
|
||||
subtitle: 'Please fill in the information to create your account.'
|
||||
headTitle: t('auth.layout.signup.headTitle'),
|
||||
title: t('auth.layout.signup.title'),
|
||||
subtitle: t('auth.layout.signup.subtitle')
|
||||
},
|
||||
forgot: {
|
||||
title: 'Forgot your password?',
|
||||
subtitle: "Enter your email address and we'll send you a link to reset your password.",
|
||||
headTitle: "Reset your password"
|
||||
title: t('auth.layout.forgot.title'),
|
||||
subtitle: t('auth.layout.forgot.subtitle'),
|
||||
headTitle: t('auth.layout.forgot.headTitle')
|
||||
},
|
||||
'google-auth-finalize': {
|
||||
title: 'Google sign in',
|
||||
subtitle: 'Completing your Google sign in.',
|
||||
headTitle: 'Google sign in - Holistream'
|
||||
}
|
||||
}
|
||||
}));
|
||||
</script>
|
||||
@@ -2,17 +2,17 @@
|
||||
<div class="w-full">
|
||||
<form @submit.prevent="onFormSubmit" class="flex flex-col gap-4 w-full">
|
||||
<div class="flex flex-col gap-1">
|
||||
<label for="email" class="text-sm font-medium text-gray-700">Email</label>
|
||||
<AppInput id="email" v-model="form.email" type="text" placeholder="Enter your email"
|
||||
<label for="email" class="text-sm font-medium text-gray-700">{{ t('auth.login.email') }}</label>
|
||||
<AppInput id="email" v-model="form.email" type="text" :placeholder="t('auth.signup.placeholders.email')"
|
||||
:disabled="auth.loading" />
|
||||
<p v-if="errors.email" class="text-xs text-red-500 mt-0.5">{{ errors.email }}</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<label for="password" class="text-sm font-medium text-gray-700">Password</label>
|
||||
<label for="password" class="text-sm font-medium text-gray-700">{{ t('auth.login.password') }}</label>
|
||||
<div class="relative">
|
||||
<AppInput id="password" v-model="form.password" :type="showPassword ? 'text' : 'password'"
|
||||
placeholder="Enter your password" :disabled="auth.loading" />
|
||||
:placeholder="t('auth.signup.placeholders.password')" :disabled="auth.loading" />
|
||||
<button type="button"
|
||||
class="absolute right-2 top-1/2 -translate-y-1/2 p-1 text-gray-400 hover:text-gray-600"
|
||||
@click="showPassword = !showPassword" tabindex="-1">
|
||||
@@ -31,22 +31,15 @@
|
||||
<p v-if="errors.password" class="text-xs text-red-500 mt-0.5">{{ errors.password }}</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<input id="remember-me" v-model="form.rememberMe" type="checkbox"
|
||||
class="w-4 h-4 rounded border-gray-300 text-primary focus:ring-primary"
|
||||
:disabled="auth.loading" />
|
||||
<label for="remember-me" class="text-sm text-gray-900">Remember me</label>
|
||||
</div>
|
||||
<div class="flex items-center justify-end">
|
||||
<div class="text-sm">
|
||||
<router-link to="/forgot"
|
||||
class="text-blue-600 hover:text-blue-500 hover:underline">Forgot
|
||||
password?</router-link>
|
||||
class="text-blue-600 hover:text-blue-500 hover:underline">{{ t('auth.login.forgotPassword') }}</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AppButton type="submit" :loading="auth.loading" class="w-full">
|
||||
{{ auth.loading ? 'Signing in...' : 'Sign in' }}
|
||||
{{ auth.loading ? `${t('common.loading')}...` : t('auth.login.signIn') }}
|
||||
</AppButton>
|
||||
|
||||
<div class="relative">
|
||||
@@ -54,7 +47,7 @@
|
||||
<div class="w-full border-t border-gray-300"></div>
|
||||
</div>
|
||||
<div class="relative flex justify-center text-sm">
|
||||
<span class="px-2 bg-white text-gray-500">Or continue with</span>
|
||||
<span class="px-2 bg-white text-gray-500">{{ t('auth.login.google') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -64,13 +57,13 @@
|
||||
<path
|
||||
d="M12.545,10.239v3.821h5.445c-0.712,2.315-2.647,3.972-5.445,3.972c-3.332,0-6.033-2.701-6.033-6.032s2.701-6.032,6.033-6.032c1.498,0,2.866,0.549,3.921,1.453l2.814-2.814C17.503,2.988,15.139,2,12.545,2C7.021,2,2.543,6.477,2.543,12s4.478,10,10.002,10c8.396,0,10.249-7.85,9.426-11.748L12.545,10.239z" />
|
||||
</svg>
|
||||
Google
|
||||
{{ t('auth.login.google') }}
|
||||
</AppButton>
|
||||
<div class="mt-2 flex flex-col items-center justify-center gap-1 text-sm text-gray-600">
|
||||
<p class="text-center text-sm text-gray-600">
|
||||
Don't have an account?
|
||||
{{ t('auth.login.noAccount') }}
|
||||
<router-link to="/sign-up"
|
||||
class="font-medium text-blue-600 hover:text-blue-500 hover:underline">Sign up</router-link>
|
||||
class="font-medium text-blue-600 hover:text-blue-500 hover:underline">{{ t('auth.login.signUp') }}</router-link>
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
@@ -81,11 +74,13 @@
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
import { useAppToast } from '@/composables/useAppToast';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { useTranslation } from 'i18next-vue';
|
||||
import { z } from 'zod';
|
||||
|
||||
const toast = useAppToast();
|
||||
const auth = useAuthStore();
|
||||
const showPassword = ref(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const form = reactive({
|
||||
email: '',
|
||||
@@ -96,8 +91,8 @@ const form = reactive({
|
||||
const errors = reactive<{ email?: string; password?: string }>({});
|
||||
|
||||
const schema = z.object({
|
||||
email: z.string().min(1, { message: 'Email or username is required.' }),
|
||||
password: z.string().min(1, { message: 'Password is required.' })
|
||||
email: z.string().min(1, { message: t('auth.login.errors.emailRequired') }),
|
||||
password: z.string().min(1, { message: t('auth.login.errors.passwordRequired') })
|
||||
});
|
||||
|
||||
watch(() => auth.error, (newError) => {
|
||||
|
||||
@@ -2,22 +2,22 @@
|
||||
<div class="w-full">
|
||||
<form @submit.prevent="onFormSubmit" class="flex flex-col gap-4 w-full">
|
||||
<div class="flex flex-col gap-1">
|
||||
<label for="name" class="text-sm font-medium text-gray-700">Full Name</label>
|
||||
<AppInput id="name" v-model="form.name" placeholder="John Doe" />
|
||||
<label for="name" class="text-sm font-medium text-gray-700">{{ t('auth.signup.fullName') }}</label>
|
||||
<AppInput id="name" v-model="form.name" :placeholder="t('auth.signup.placeholders.name')" />
|
||||
<p v-if="errors.name" class="text-xs text-red-500 mt-0.5">{{ errors.name }}</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<label for="email" class="text-sm font-medium text-gray-700">Email address</label>
|
||||
<AppInput id="email" v-model="form.email" type="email" placeholder="you@example.com" />
|
||||
<label for="email" class="text-sm font-medium text-gray-700">{{ t('auth.signup.email') }}</label>
|
||||
<AppInput id="email" v-model="form.email" type="email" :placeholder="t('auth.signup.placeholders.email')" />
|
||||
<p v-if="errors.email" class="text-xs text-red-500 mt-0.5">{{ errors.email }}</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<label for="password" class="text-sm font-medium text-gray-700">Password</label>
|
||||
<label for="password" class="text-sm font-medium text-gray-700">{{ t('auth.signup.password') }}</label>
|
||||
<div class="relative">
|
||||
<AppInput id="password" v-model="form.password" :type="showPassword ? 'text' : 'password'"
|
||||
placeholder="Create a password" />
|
||||
:placeholder="t('auth.signup.placeholders.password')" />
|
||||
<button type="button"
|
||||
class="absolute right-2 top-1/2 -translate-y-1/2 p-1 text-gray-400 hover:text-gray-600"
|
||||
@click="showPassword = !showPassword" tabindex="-1">
|
||||
@@ -33,16 +33,36 @@
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<small class="text-gray-500">Must be at least 8 characters.</small>
|
||||
<small class="text-gray-500">{{ t('auth.signup.passwordHint') }}</small>
|
||||
<p v-if="errors.password" class="text-xs text-red-500 mt-0.5">{{ errors.password }}</p>
|
||||
</div>
|
||||
|
||||
<AppButton type="submit" class="w-full">Create Account</AppButton>
|
||||
<div v-if="refUsername" class="rounded-lg border border-blue-200 bg-blue-50 px-3 py-2 text-sm text-blue-700">
|
||||
Signing up with referral: <span class="font-medium">@{{ refUsername }}</span>
|
||||
</div>
|
||||
|
||||
<AppButton type="submit" class="w-full">{{ t('auth.signup.createAccount') }}</AppButton>
|
||||
|
||||
<div class="relative">
|
||||
<div class="absolute inset-0 flex items-center">
|
||||
<div class="w-full border-t border-gray-300"></div>
|
||||
</div>
|
||||
<div class="relative flex justify-center text-sm">
|
||||
<span class="px-2 bg-white text-gray-500">{{ t('auth.login.google') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AppButton type="button" variant="secondary" class="w-full flex items-center justify-center gap-2" @click="signupWithGoogle">
|
||||
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12.545,10.239v3.821h5.445c-0.712,2.315-2.647,3.972-5.445,3.972c-3.332,0-6.033-2.701-6.033-6.032s2.701-6.032,6.033-6.032c1.498,0,2.866,0.549,3.921,1.453l2.814-2.814C17.503,2.988,15.139,2,12.545,2C7.021,2,2.543,6.477,2.543,12s4.478,10,10.002,10c8.396,0,10.249-7.85,9.426-11.748L12.545,10.239z" />
|
||||
</svg>
|
||||
Continue with Google
|
||||
</AppButton>
|
||||
|
||||
<p class="mt-4 text-center text-sm text-gray-600">
|
||||
Already have an account?
|
||||
{{ t('auth.signup.alreadyHave') }}
|
||||
<router-link to="/login"
|
||||
class="font-medium text-blue-600 hover:text-blue-500 hover:underline">Sign in</router-link>
|
||||
class="font-medium text-blue-600 hover:text-blue-500 hover:underline">{{ t('auth.signup.signIn') }}</router-link>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
@@ -50,11 +70,16 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import { useTranslation } from 'i18next-vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { z } from 'zod';
|
||||
|
||||
const auth = useAuthStore();
|
||||
const route = useRoute();
|
||||
const showPassword = ref(false);
|
||||
const { t } = useTranslation();
|
||||
const refUsername = computed(() => String(route.query.ref || '').trim());
|
||||
|
||||
const form = reactive({
|
||||
name: '',
|
||||
@@ -65,9 +90,9 @@ const form = reactive({
|
||||
const errors = reactive<{ name?: string; email?: string; password?: string }>({});
|
||||
|
||||
const schema = z.object({
|
||||
name: z.string().min(1, { message: 'Name is required.' }),
|
||||
email: z.string().min(1, { message: 'Email is required.' }).email({ message: 'Invalid email address.' }),
|
||||
password: z.string().min(8, { message: 'Password must be at least 8 characters.' })
|
||||
name: z.string().min(1, { message: t('auth.signup.errors.nameRequired') }),
|
||||
email: z.string().min(1, { message: t('auth.signup.errors.emailRequired') }).email({ message: t('auth.signup.errors.emailInvalid') }),
|
||||
password: z.string().min(8, { message: t('auth.signup.errors.passwordMin') })
|
||||
});
|
||||
|
||||
const onFormSubmit = () => {
|
||||
@@ -84,6 +109,10 @@ const onFormSubmit = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
auth.register(form.name, form.email, form.password);
|
||||
auth.register(form.name, form.email, form.password, refUsername.value || undefined);
|
||||
};
|
||||
|
||||
const signupWithGoogle = () => {
|
||||
auth.loginWithGoogle(refUsername.value || undefined);
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,231 +1,373 @@
|
||||
<script setup lang="ts">
|
||||
import { useTranslation } from 'i18next-vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const signalItems = computed(() => [
|
||||
{ label: t('home.features.live.bitrate'), value: t('home.features.live.bitrateValue') },
|
||||
{ label: t('home.features.live.fps'), value: t('home.features.live.fpsValue') },
|
||||
{ label: t('home.features.live.latency'), value: t('home.features.live.latencyValue') },
|
||||
]);
|
||||
|
||||
const featurePills = computed(() => [
|
||||
t('home.features.global.title'),
|
||||
t('home.features.encoding.title'),
|
||||
t('home.features.analytics.title'),
|
||||
]);
|
||||
|
||||
const getFeatureList = (key: string): string[] => {
|
||||
const localized = t(key, { returnObjects: true });
|
||||
return Array.isArray(localized) ? localized.map((item) => String(item)) : [];
|
||||
};
|
||||
|
||||
const pricing = computed(() => ({
|
||||
title: t('home.pricing.title'),
|
||||
subtitle: t('home.pricing.subtitle'),
|
||||
packs: [
|
||||
{
|
||||
name: t('home.pricing.hobby.name'),
|
||||
price: '$0',
|
||||
features: getFeatureList('home.pricing.hobby.features'),
|
||||
buttonText: t('home.pricing.hobby.button'),
|
||||
tag: '',
|
||||
},
|
||||
{
|
||||
name: t('home.pricing.pro.name'),
|
||||
price: '$29',
|
||||
features: getFeatureList('home.pricing.pro.features'),
|
||||
buttonText: t('home.pricing.pro.button'),
|
||||
tag: t('home.pricing.pro.tag'),
|
||||
},
|
||||
{
|
||||
name: t('home.pricing.scale.name'),
|
||||
price: '$99',
|
||||
features: getFeatureList('home.pricing.scale.features'),
|
||||
buttonText: t('home.pricing.scale.button'),
|
||||
tag: t('home.pricing.scale.tag'),
|
||||
}
|
||||
]
|
||||
}));
|
||||
|
||||
const featuredTag = computed(() => t('home.pricing.pro.tag'));
|
||||
const scaleTag = computed(() => t('home.pricing.scale.tag'));
|
||||
const isFeaturedPack = (tag: string) => tag === featuredTag.value;
|
||||
const isScalePack = (tag: string) => tag === scaleTag.value;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class=":m: relative pt-32 pb-20 lg:pt-48 lg:pb-32 overflow-hidden min-h-svh flex">
|
||||
<!-- <div class="absolute inset-0 bg-grid-pattern opacity-[0.4] -z-10"></div> -->
|
||||
<div
|
||||
class=":m: absolute top-0 right-0 -translate-y-1/2 translate-x-1/2 w-[800px] h-[800px] bg-primary-light/40 rounded-full blur-3xl -z-10 mix-blend-multiply animate-pulse duration-1000">
|
||||
</div>
|
||||
<div
|
||||
class=":m: absolute bottom-0 left-0 translate-y-1/2 -translate-x-1/2 w-[600px] h-[600px] bg-teal-100/50 rounded-full blur-3xl -z-10 mix-blend-multiply">
|
||||
</div>
|
||||
|
||||
|
||||
<div class="max-w-7xl m-auto px-4 sm:px-6 lg:px-8 text-center">
|
||||
<h1
|
||||
class="text-5xl md:text-7xl font-extrabold tracking-tight text-slate-900 mb-6 leading-[1.1] animate-backwards">
|
||||
Video infrastructure for <br>
|
||||
<span class="text-gradient">modern internet.</span>
|
||||
</h1>
|
||||
|
||||
<p class="text-xl text-slate-500 max-w-2xl mx-auto mb-10 leading-relaxed animate-backwards delay-50">
|
||||
Seamlessly host, encode, and stream video with our developer-first API.
|
||||
Optimized for speed, built for scale.
|
||||
</p>
|
||||
|
||||
<div class="flex flex-col sm:flex-row justify-center gap-4">
|
||||
<RouterLink to="/get-started" class="flex btn btn-success !rounded-xl !p-4 press-animated">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" viewBox="46 -286 524 580">
|
||||
<path d="M56 284v-560L560 4 56 284z" fill="#fff" />
|
||||
</svg>
|
||||
Get Started
|
||||
</RouterLink>
|
||||
<RouterLink to="/docs" class="flex btn btn-outline-primary !rounded-xl">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="28" viewBox="0 0 596 468">
|
||||
<path
|
||||
d="M10 314c0-63 41-117 98-136-1-8-2-16-2-24 0-79 65-144 144-144 55 0 104 31 128 77 14-8 30-13 48-13 53 0 96 43 96 96 0 16-4 31-10 44 44 20 74 64 74 116 0 71-57 128-128 128H154c-79 0-144-64-144-144zm199-73c-9 9-9 25 0 34s25 9 34 0l31-31v102c0 13 11 24 24 24s24-11 24-24V244l31 31c9 9 25 9 34 0s9-25 0-34l-72-72c-10-9-25-9-34 0l-72 72z"
|
||||
fill="#14a74b" />
|
||||
<path
|
||||
d="M281 169c9-9 25-9 34 0l72 72c9 9 9 25 0 34s-25 9-34 0l-31-31v102c0 13-11 24-24 24s-24-11-24-24V244l-31 31c-9 9-25 9-34 0s-9-25 0-34l72-72z"
|
||||
fill="#fff" />
|
||||
</svg>
|
||||
Upload video
|
||||
</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="features" class="py-24 bg-white">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="mb-16 md:text-center max-w-3xl mx-auto">
|
||||
<h2 class="text-3xl font-bold text-slate-900 mb-4">Everything you need to ship video</h2>
|
||||
<p class="text-lg text-slate-500">Focus on building your product. We'll handle the complex video
|
||||
infrastructure.</p>
|
||||
<div class="bg-white text-slate-900">
|
||||
<section class="relative overflow-hidden border-b border-slate-100 bg-gradient-to-b from-slate-50 via-white to-white">
|
||||
<div class="pointer-events-none absolute inset-0">
|
||||
<div class="absolute inset-0 opacity-60 bg-[linear-gradient(rgba(148,163,184,0.12)_1px,transparent_1px),linear-gradient(90deg,rgba(148,163,184,0.12)_1px,transparent_1px)] bg-[length:64px_64px] [mask-image:linear-gradient(to_bottom,rgba(0,0,0,0.55),transparent_78%)]"></div>
|
||||
<div class="absolute inset-x-0 top-0 h-[28rem] bg-[radial-gradient(circle_at_top,rgba(20,167,75,0.12),transparent_58%)]"></div>
|
||||
<div class="absolute -left-16 top-28 h-56 w-56 rounded-full bg-primary/10 blur-3xl"></div>
|
||||
<div class="absolute right-0 top-20 h-72 w-72 rounded-full bg-sky-100 blur-3xl"></div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div
|
||||
class=":m: md:col-span-2 bg-slate-50 rounded-2xl p-8 border border-slate-100 hover:border-primary/60 transition-all group overflow-hidden relative">
|
||||
<div class="relative z-10">
|
||||
<div
|
||||
class="w-12 h-12 bg-white rounded-xl flex items-center justify-center mb-6 border border-slate-100">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="532" viewBox="-8 -258 529 532">
|
||||
<path
|
||||
d="M342 32c-2 69-16 129-35 172-10 23-22 40-32 49-10 10-16 11-19 11h-1c-3 0-9-1-19-11-10-9-22-26-32-49-19-43-33-103-35-172h173zm169 0c-9 103-80 188-174 219 30-51 50-129 53-219h121zm-390 0c3 89 23 167 53 218C80 219 11 134 2 32h119zm53-266c-30 51-50 129-53 218H2c9-102 78-186 172-218zm82-14c3 0 9 1 19 11 10 9 22 26 32 50 19 42 33 102 35 171H169c3-69 16-129 35-171 10-24 22-41 32-50s16-11 19-11h1zm81 13c94 31 165 116 174 219H390c-3-90-23-168-53-219z"
|
||||
fill="#059669" />
|
||||
</svg>
|
||||
<div class="relative mx-auto max-w-7xl px-4 pb-18 pt-28 sm:px-6 lg:px-8 lg:pb-24 lg:pt-34">
|
||||
<div class="grid items-center gap-12 lg:grid-cols-[1.02fr_0.98fr] lg:gap-14">
|
||||
<div>
|
||||
<div class="inline-flex items-center gap-2 rounded-full border border-primary/15 bg-white/90 px-4 py-2 text-xs font-semibold uppercase tracking-[0.22em] text-primary shadow-sm">
|
||||
<span class="h-2 w-2 rounded-full bg-primary"></span>
|
||||
{{ t('home.features.live.onAir') }}
|
||||
</div>
|
||||
<h3 class="text-xl font-bold text-slate-900 mb-2">Global Edge Network</h3>
|
||||
<p class="text-slate-500 max-w-md">Content delivered from 200+ PoPs worldwide. Automatic region
|
||||
selection ensures the lowest latency for every viewer.</p>
|
||||
</div>
|
||||
<div class="absolute right-0 bottom-0 opacity-10 translate-x-1/4 translate-y-1/4">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="-10 -258 532 532">
|
||||
<path
|
||||
d="M464 8c0-19-3-38-8-56l-27-5c-8-2-15 2-19 9-6 11-19 17-31 13l-14-5c-8-2-17 0-22 5-4 4-4 10 0 14l33 33c5 5 8 12 8 19 0 12-8 23-20 26l-6 1c-3 1-6 5-6 9v12c0 13-4 27-13 38l-25 34c-6 8-16 13-26 13-18 0-32-14-32-32V88c0-9-7-16-16-16h-32c-26 0-48-22-48-48V-4c0-13 6-24 16-32l39-30c6-4 13-6 20-6 3 0 7 1 10 2l32 10c7 3 15 3 22 1l36-9c10-2 17-11 17-22 0-8-5-16-13-20l-29-15c-3-2-8-1-11 2l-4 4c-4 4-11 7-17 7-4 0-8-1-11-3l-15-7c-7-4-15-2-20 4l-13 17c-6 7-16 8-22 1-3-2-5-6-5-10v-41c0-6-1-11-4-16l-10-18C102-154 48-79 48 8c0 115 93 208 208 208S464 123 464 8zM0 8c0-141 115-256 256-256S512-133 512 8 397 264 256 264 0 149 0 8z"
|
||||
fill="#1e3050" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class=":m: md:row-span-2 bg-slate-900 rounded-2xl p-8 text-white relative overflow-hidden group">
|
||||
<div class=":m: absolute inset-0 bg-gradient-to-b from-slate-800/50 to-transparent"></div>
|
||||
<div class="relative z-10">
|
||||
<div
|
||||
class=":m: w-12 h-12 bg-white/10 rounded-xl flex items-center justify-center mb-6 backdrop-blur-sm border border-white/10">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" viewBox="-10 -146 468 384">
|
||||
<path
|
||||
d="M392-136c-31 0-56 25-56 56v280c0 16 13 28 28 28h28c31 0 56-25 56-56V-80c0-31-25-56-56-56zM168 4c0-31 25-56 56-56h28c16 0 28 13 28 28v224c0 16-12 28-28 28h-56c-15 0-28-12-28-28V4zM0 88c0-31 25-56 56-56h28c16 0 28 13 28 28v140c0 16-12 28-28 28H56c-31 0-56-25-56-56V88z"
|
||||
fill="#fff" />
|
||||
</svg>
|
||||
<h1 class="mt-7 max-w-4xl text-5xl font-bold leading-[1.02] tracking-tight text-slate-900 sm:text-6xl lg:text-7xl">
|
||||
<span class="block">{{ t('home.hero.titleLine1') }}</span>
|
||||
<span class="mt-2 block bg-[linear-gradient(135deg,#0f172a_0%,#14a74b_55%,#0ea5e9_100%)] bg-clip-text text-transparent">
|
||||
{{ t('home.hero.titleLine2') }}
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
<p class="mt-6 max-w-2xl text-lg leading-8 text-slate-600 lg:text-xl">
|
||||
{{ t('home.hero.subtitle') }}
|
||||
</p>
|
||||
|
||||
<div class="mt-9 flex flex-col gap-3 sm:flex-row">
|
||||
<RouterLink to="/sign-up" class="btn btn-success !rounded-xl !px-6 !py-3.5 shadow-sm">
|
||||
{{ t('home.hero.getStarted') }}
|
||||
</RouterLink>
|
||||
<RouterLink to="/login" class="btn btn-outline-primary !rounded-xl !px-6 !py-3.5">
|
||||
{{ t('home.hero.uploadVideo') }}
|
||||
</RouterLink>
|
||||
</div>
|
||||
<h3 class="text-xl font-bold mb-2">Live Streaming API</h3>
|
||||
<p class="text-slate-400 text-sm leading-relaxed mb-8">Scale to millions of concurrent viewers
|
||||
with ultra-low latency. RTMP ingest and HLS playback supported natively.</p>
|
||||
|
||||
<!-- Visual -->
|
||||
<div
|
||||
class="bg-slate-800/50 rounded-lg p-4 border border-white/5 font-mono text-xs text-brand-300">
|
||||
<div class="flex justify-between items-center mb-3 border-b border-white/5 pb-2">
|
||||
<span class="text-slate-500">Live Status</span>
|
||||
<div class="mt-10 grid gap-4 sm:grid-cols-3">
|
||||
<div
|
||||
v-for="signal in signalItems"
|
||||
:key="signal.label"
|
||||
class="rounded-2xl border border-slate-200 bg-white px-5 py-4"
|
||||
>
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.18em] text-slate-400">
|
||||
{{ signal.label }}
|
||||
</p>
|
||||
<p class="mt-2 text-xl font-bold text-slate-900">
|
||||
{{ signal.value }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative mx-auto w-full max-w-[36rem] lg:mr-0">
|
||||
<div class="rounded-[2rem] border border-slate-200 bg-white p-5 shadow-[0_24px_70px_rgba(15,23,42,0.08)] sm:p-6">
|
||||
<div class="flex items-center justify-between border-b border-slate-100 pb-4">
|
||||
<div>
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.18em] text-slate-400">
|
||||
{{ t('home.features.live.status') }}
|
||||
</p>
|
||||
<h2 class="mt-2 text-2xl font-bold tracking-tight text-slate-900">
|
||||
{{ t('home.features.live.title') }}
|
||||
</h2>
|
||||
</div>
|
||||
<span class="inline-flex items-center gap-2 rounded-full bg-primary/10 px-3 py-1 text-xs font-semibold text-primary">
|
||||
<span class="h-2 w-2 rounded-full bg-primary"></span>
|
||||
{{ t('home.features.live.onAir') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p class="mt-5 text-sm leading-7 text-slate-600 sm:text-base">
|
||||
{{ t('home.features.live.description') }}
|
||||
</p>
|
||||
|
||||
<div class="mt-5 rounded-[1.5rem] bg-slate-50 p-4 sm:p-5">
|
||||
<div class="space-y-3">
|
||||
<div
|
||||
v-for="signal in signalItems"
|
||||
:key="signal.label"
|
||||
class="flex items-center justify-between rounded-2xl border border-slate-200 bg-white px-4 py-3"
|
||||
>
|
||||
<span class="text-sm text-slate-500">{{ signal.label }}</span>
|
||||
<span class="text-sm font-semibold text-slate-900">{{ signal.value }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-5 grid grid-cols-12 items-end gap-2">
|
||||
<span
|
||||
v-for="n in 12"
|
||||
:key="n"
|
||||
class="rounded-full bg-[linear-gradient(180deg,rgba(20,167,75,0.95),rgba(20,167,75,0.2))]"
|
||||
:style="{ height: `${34 + (n % 5) * 12}px`, opacity: 0.4 + (n % 4) * 0.13 }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-5 flex flex-wrap gap-2.5">
|
||||
<span
|
||||
class=":m: flex items-center gap-1.5 text-red-500 text-[10px] uppercase font-bold tracking-wider animate-pulse"><span
|
||||
class="w-1.5 h-1.5 rounded-full bg-red-500 animate-pulse"></span> On Air</span>
|
||||
v-for="pill in featurePills"
|
||||
:key="pill"
|
||||
class="rounded-full border border-slate-200 bg-slate-50 px-3 py-1.5 text-xs font-medium text-slate-600"
|
||||
>
|
||||
{{ pill }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<div class="flex justify-between"><span class="text-slate-400">Bitrate:</span> <span
|
||||
class="text-white">6000 kbps</span></div>
|
||||
<div class="flex justify-between"><span class="text-slate-400">FPS:</span> <span
|
||||
class="text-white">60</span></div>
|
||||
<div class="flex justify-between"><span class="text-slate-400">Latency:</span> <span
|
||||
class="text-brand-400">~2s</span></div>
|
||||
</div>
|
||||
|
||||
<div class="absolute -right-4 top-12 hidden w-56 rounded-2xl border border-slate-200 bg-white p-4 shadow-xl lg:block">
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.18em] text-slate-400">
|
||||
{{ t('home.features.global.title') }}
|
||||
</p>
|
||||
<p class="mt-3 text-sm leading-6 text-slate-600">
|
||||
{{ t('home.features.global.description') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="absolute -left-4 bottom-8 hidden w-64 rounded-2xl border border-slate-200 bg-white p-4 shadow-xl lg:block">
|
||||
<div class="flex items-center justify-between">
|
||||
<p class="text-sm font-semibold text-slate-900">
|
||||
{{ t('home.features.analytics.title') }}
|
||||
</p>
|
||||
<span class="rounded-full bg-slate-100 px-2 py-1 text-[11px] font-medium text-slate-500">
|
||||
{{ t('home.features.live.onAir') }}
|
||||
</span>
|
||||
</div>
|
||||
<p class="mt-2 text-sm leading-6 text-slate-600">
|
||||
{{ t('home.features.analytics.description') }}
|
||||
</p>
|
||||
<div class="mt-4 grid grid-cols-4 items-end gap-2">
|
||||
<span class="h-10 rounded-full bg-slate-200"></span>
|
||||
<span class="h-18 rounded-full bg-primary/70"></span>
|
||||
<span class="h-14 rounded-full bg-sky-300"></span>
|
||||
<span class="h-22 rounded-full bg-slate-900"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Standard Feature -->
|
||||
<div
|
||||
class=":m: bg-slate-50 rounded-2xl p-8 border border-slate-100 transition-all group hover:(border-brand-200 shadow-lg shadow-brand-500/5)">
|
||||
<div
|
||||
class=":m: w-12 h-12 bg-white rounded-xl shadow-sm flex items-center justify-center mb-6 text-purple-600 border border-slate-100">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" viewBox="0 0 570 570">
|
||||
<path
|
||||
d="M50 428c-5 5-5 14 0 19s14 5 19 0l237-237c5-5 5-14 0-19s-14-5-19 0L50 428zm16-224c-5 5-5 13 0 19 5 5 14 5 19 0l12-12c5-5 5-14 0-19-6-5-14-5-20 0l-11 12zM174 60c-5 5-5 13 0 19 5 5 14 5 19 0l12-12c5-5 5-14 0-19-6-5-14-5-20 0l-11 12zm215 29c-5 5-5 14 0 19s14 5 19 0l39-39c5-5 5-14 0-19s-14-5-19 0l-39 39zm21 357c-5 5-5 14 0 19s14 5 19 0l18-18c5-5 5-14 0-19s-14-5-19 0l-18 18z"
|
||||
fill="#a6acb9" />
|
||||
<path
|
||||
d="M170 26c14-15 36-15 50 0l18 18c15 14 15 36 0 50l-18 18c-14 15-36 15-50 0l-18-18c-15-14-15-36 0-50l18-18zm35 41c5-5 5-14 0-19-6-5-14-5-20 0l-11 12c-5 5-5 13 0 19 5 5 14 5 19 0l12-12zm204 342c21-21 55-21 76 0l18 18c21 21 21 55 0 76l-18 18c-21 21-55 21-76 0l-18-18c-21-21-21-55 0-76l18-18zm38 38c5-5 5-14 0-19s-14-5-19 0l-18 18c-5 5-5 14 0 19s14 5 19 0l18-18zM113 170c-15-15-37-15-51 0l-18 18c-14 14-14 36 0 50l18 18c14 15 37 15 51 0l18-18c14-14 14-36 0-50l-18-18zm-16 41-12 12c-5 5-14 5-19 0-5-6-5-14 0-20l11-11c6-5 14-5 20 0 5 5 5 14 0 19zM485 31c-21-21-55-21-76 0l-39 39c-21 21-21 55 0 76l54 54c21 21 55 21 76 0l39-39c21-21 21-55 0-76l-54-54zm-38 38-39 39c-5 5-14 5-19 0s-5-14 0-19l39-39c5-5 14-5 19 0s5 14 0 19zm-49 233c21-21 21-55 0-76l-54-54c-21-21-55-21-76 0L31 409c-21 21-21 55 0 76l54 54c21 21 55 21 76 0l237-237zm-92-92L69 447c-5 5-14 5-19 0s-5-14 0-19l237-237c5-5 14-5 19 0s5 14 0 19z"
|
||||
fill="#1e3050" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-xl font-bold text-slate-900 mb-2">Instant Encoding</h3>
|
||||
<p class="text-slate-500 text-sm">Upload raw files and get optimized HLS/DASH streams in seconds.
|
||||
<section id="features" class="border-b border-slate-100 bg-slate-50/70 py-20">
|
||||
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div class="mx-auto mb-14 max-w-3xl text-center">
|
||||
<p class="text-sm font-semibold uppercase tracking-[0.22em] text-primary">
|
||||
{{ t('home.features.heading') }}
|
||||
</p>
|
||||
<h2 class="mt-4 text-4xl font-bold tracking-tight text-slate-900 sm:text-5xl">
|
||||
{{ t('home.features.heading') }}
|
||||
</h2>
|
||||
<p class="mt-4 text-lg leading-8 text-slate-600">
|
||||
{{ t('home.features.subtitle') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Standard Feature -->
|
||||
<div
|
||||
class=":m: bg-slate-50 rounded-2xl p-8 border border-slate-100 transition-all group hover:(border-brand-200 shadow-lg shadow-brand-500/5)">
|
||||
<div
|
||||
class=":m: w-12 h-12 bg-white rounded-xl shadow-sm flex items-center justify-center mb-6 text-orange-600 border border-slate-100">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" viewBox="-10 -226 532 468">
|
||||
<path
|
||||
d="M32-216c18 0 32 14 32 32v336c0 9 7 16 16 16h400c18 0 32 14 32 32s-14 32-32 32H80c-44 0-80-36-80-80v-336c0-18 14-32 32-32zM144-24c18 0 32 14 32 32v64c0 18-14 32-32 32s-32-14-32-32V8c0-18 14-32 32-32zm144-64V72c0 18-14 32-32 32s-32-14-32-32V-88c0-18 14-32 32-32s32 14 32 32zm80 32c18 0 32 14 32 32v96c0 18-14 32-32 32s-32-14-32-32v-96c0-18 14-32 32-32zm144-96V72c0 18-14 32-32 32s-32-14-32-32v-224c0-18 14-32 32-32s32 14 32 32z"
|
||||
fill="#1e3050" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-xl font-bold text-slate-900 mb-2">Deep Analytics</h3>
|
||||
<p class="text-slate-500 text-sm">Session-level insights, quality of experience (QoE) metrics, and
|
||||
more.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- Pricing -->
|
||||
<section id="pricing" class="py-24 border-t border-slate-100 bg-white">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center mb-16">
|
||||
<h2 class="text-3xl font-bold text-slate-900 mb-4">{{ pricing.title }}</h2>
|
||||
<p class="text-slate-500">{{ pricing.subtitle }}</p>
|
||||
</div>
|
||||
<div class="grid gap-6 lg:grid-cols-3">
|
||||
<article class="rounded-3xl border border-slate-200 bg-white p-8 transition-all duration-200 ease-out hover:-translate-y-1 hover:shadow-[0_18px_44px_rgba(15,23,42,0.08)]">
|
||||
<div class="inline-flex h-12 w-12 items-center justify-center rounded-2xl bg-primary/10 text-primary">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" viewBox="-8 -258 529 532" fill="none">
|
||||
<path
|
||||
d="M342 32c-2 69-16 129-35 172-10 23-22 40-32 49-10 10-16 11-19 11h-1c-3 0-9-1-19-11-10-9-22-26-32-49-19-43-33-103-35-172h173zm169 0c-9 103-80 188-174 219 30-51 50-129 53-219h121zm-390 0c3 89 23 167 53 218C80 219 11 134 2 32h119zm53-266c-30 51-50 129-53 218H2c9-102 78-186 172-218zm82-14c3 0 9 1 19 11 10 9 22 26 32 50 19 42 33 102 35 171H169c3-69 16-129 35-171 10-24 22-41 32-50s16-11 19-11h1zm81 13c94 31 165 116 174 219H390c-3-90-23-168-53-219z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="mt-5 text-2xl font-bold tracking-tight text-slate-900">
|
||||
{{ t('home.features.global.title') }}
|
||||
</h3>
|
||||
<p class="mt-3 text-base leading-7 text-slate-600">
|
||||
{{ t('home.features.global.description') }}
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-8 w-full">
|
||||
<div v-for="pack in pricing.packs" :key="pack.name"
|
||||
:class="cn(':uno: p-8 rounded-2xl relative overflow-hidden hover:border-primary transition-colors flex flex-col justify-between', pack.tag == 'POPULAR' ? 'border-primary/80 border-2' : 'border-slate-200 border')"
|
||||
:style="{ background: pack.bg }">
|
||||
<div v-if="pack.tag"
|
||||
class=":m: absolute top-0 right-0 bg-primary/80 text-white text-xs font-bold px-3 py-1 rounded-bl-lg uppercase">
|
||||
{{ pack.tag }}</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-slate-900 text-xl mb-2">{{ pack.name }}</h3>
|
||||
<div class="flex items-baseline gap-1 mb-6">
|
||||
<span class="text-4xl font-bold text-slate-900">{{ pack.price }}</span>
|
||||
<span class="text-slate-500">/mo</span>
|
||||
<article class="rounded-3xl border border-slate-200 bg-white p-8 transition-all duration-200 ease-out hover:-translate-y-1 hover:shadow-[0_18px_44px_rgba(15,23,42,0.08)]">
|
||||
<div class="inline-flex h-12 w-12 items-center justify-center rounded-2xl bg-violet-50 text-violet-600">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" viewBox="0 0 570 570" fill="none">
|
||||
<path
|
||||
d="M50 428c-5 5-5 14 0 19s14 5 19 0l237-237c5-5 5-14 0-19s-14-5-19 0L50 428zm16-224c-5 5-5 13 0 19 5 5 14 5 19 0l12-12c5-5 5-14 0-19-6-5-14-5-20 0l-11 12zM174 60c-5 5-5 13 0 19 5 5 14 5 19 0l12-12c5-5 5-14 0-19-6-5-14-5-20 0l-11 12zm215 29c-5 5-5 14 0 19s14 5 19 0l39-39c5-5 5-14 0-19s-14-5-19 0l-39 39zm21 357c-5 5-5 14 0 19s14 5 19 0l18-18c5-5 5-14 0-19s-14-5-19 0l-18 18z"
|
||||
fill="#a6acb9"
|
||||
/>
|
||||
<path
|
||||
d="M170 26c14-15 36-15 50 0l18 18c15 14 15 36 0 50l-18 18c-14 15-36 15-50 0l-18-18c-15-14-15-36 0-50l18-18zm35 41c5-5 5-14 0-19-6-5-14-5-20 0l-11 12c-5 5-5 13 0 19 5 5 14 5 19 0l12-12zm204 342c21-21 55-21 76 0l18 18c21 21 21 55 0 76l-18 18c-21 21-55 21-76 0l-18-18c-21-21-21-55 0-76l18-18zm38 38c5-5 5-14 0-19s-14-5-19 0l-18 18c-5 5-5 14 0 19s14 5 19 0l18-18zM113 170c-15-15-37-15-51 0l-18 18c-14 14-14 36 0 50l18 18c14 15 37 15 51 0l18-18c14-14 14-36 0-50l-18-18zm-16 41-12 12c-5 5-14 5-19 0-5-6-5-14 0-20l11-11c6-5 14-5 20 0 5 5 5 14 0 19zM485 31c-21-21-55-21-76 0l-39 39c-21 21-21 55 0 76l54 54c21 21 55 21 76 0l39-39c21-21 21-55 0-76l-54-54zm-38 38-39 39c-5 5-14 5-19 0s-5-14 0-19l39-39c5-5 14-5 19 0s5 14 0 19zm-49 233c21-21 21-55 0-76l-54-54c-21-21-55-21-76 0L31 409c-21 21-21 55 0 76l54 54c21 21 55 21 76 0l237-237zm-92-92L69 447c-5 5-14 5-19 0s-5-14 0-19l237-237c5-5 14-5 19 0s5 14 0 19z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="mt-5 text-2xl font-bold tracking-tight text-slate-900">
|
||||
{{ t('home.features.encoding.title') }}
|
||||
</h3>
|
||||
<p class="mt-3 text-base leading-7 text-slate-600">
|
||||
{{ t('home.features.encoding.description') }}
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article class="rounded-3xl border border-slate-200 bg-white p-8 transition-all duration-200 ease-out hover:-translate-y-1 hover:shadow-[0_18px_44px_rgba(15,23,42,0.08)]">
|
||||
<div class="inline-flex h-12 w-12 items-center justify-center rounded-2xl bg-amber-50 text-amber-600">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" viewBox="-10 -226 532 468" fill="none">
|
||||
<path
|
||||
d="M32-216c18 0 32 14 32 32v336c0 9 7 16 16 16h400c18 0 32 14 32 32s-14 32-32 32H80c-44 0-80-36-80-80v-336c0-18 14-32 32-32zM144-24c18 0 32 14 32 32v64c0 18-14 32-32 32s-32-14-32-32V8c0-18 14-32 32-32zm144-64V72c0 18-14 32-32 32s-32-14-32-32V-88c0-18 14-32 32-32s32 14 32 32zm80 32c18 0 32 14 32 32v96c0 18-14 32-32 32s-32-14-32-32v-96c0-18 14-32 32-32zm144-96V72c0 18-14 32-32 32s-32-14-32-32v-224c0-18 14-32 32-32s32 14 32 32z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="mt-5 text-2xl font-bold tracking-tight text-slate-900">
|
||||
{{ t('home.features.analytics.title') }}
|
||||
</h3>
|
||||
<p class="mt-3 text-base leading-7 text-slate-600">
|
||||
{{ t('home.features.analytics.description') }}
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 rounded-3xl border border-slate-200 bg-white p-6 sm:p-8">
|
||||
<div class="grid gap-6 lg:grid-cols-[1fr_0.9fr] lg:items-center">
|
||||
<div>
|
||||
<p class="text-sm font-semibold uppercase tracking-[0.22em] text-primary">
|
||||
{{ t('home.features.live.title') }}
|
||||
</p>
|
||||
<h3 class="mt-3 text-3xl font-bold tracking-tight text-slate-900">
|
||||
{{ t('home.features.live.title') }}
|
||||
</h3>
|
||||
<p class="mt-4 max-w-2xl text-base leading-8 text-slate-600">
|
||||
{{ t('home.features.live.description') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-[1.75rem] bg-slate-50 p-4">
|
||||
<div class="space-y-3">
|
||||
<div
|
||||
v-for="signal in signalItems"
|
||||
:key="`summary-${signal.label}`"
|
||||
class="flex items-center justify-between rounded-2xl border border-slate-200 bg-white px-4 py-3"
|
||||
>
|
||||
<span class="text-sm text-slate-500">{{ signal.label }}</span>
|
||||
<span class="text-sm font-semibold text-slate-900">{{ signal.value }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="space-y-3 mb-8 text-sm text-slate-600">
|
||||
<li v-for="value in pack.features" :key="value" class="flex items-center gap-3"><Check-Icon
|
||||
class="fas fa-check text-brand-500" /> {{ value }}</li>
|
||||
</ul>
|
||||
<router-link to="/sign-up"
|
||||
:class="cn('btn flex justify-center w-full !py-2.5', pack.tag == 'POPULAR' ? 'btn-primary' : 'btn-outline-primary')">{{
|
||||
pack.buttonText }}</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section id="pricing" class="bg-white py-20">
|
||||
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div class="mx-auto mb-14 max-w-3xl text-center">
|
||||
<p class="text-sm font-semibold uppercase tracking-[0.22em] text-primary">
|
||||
{{ pricing.title }}
|
||||
</p>
|
||||
<h2 class="mt-4 text-4xl font-bold tracking-tight text-slate-900 sm:text-5xl">
|
||||
{{ pricing.title }}
|
||||
</h2>
|
||||
<p class="mt-4 text-lg leading-8 text-slate-600">
|
||||
{{ pricing.subtitle }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-6 lg:grid-cols-3 lg:items-stretch">
|
||||
<article
|
||||
v-for="pack in pricing.packs"
|
||||
:key="pack.name"
|
||||
:class="[
|
||||
'relative flex h-full flex-col overflow-hidden rounded-3xl border p-8 shadow-sm transition-all duration-200 ease-out hover:-translate-y-1 hover:shadow-[0_18px_44px_rgba(15,23,42,0.08)]',
|
||||
isFeaturedPack(pack.tag)
|
||||
? 'border-primary bg-primary/[0.04] ring-1 ring-primary/15'
|
||||
: isScalePack(pack.tag)
|
||||
? 'border-slate-200 bg-slate-50/80'
|
||||
: 'border-slate-200 bg-white'
|
||||
]"
|
||||
>
|
||||
<div
|
||||
v-if="pack.tag"
|
||||
:class="[
|
||||
'absolute right-5 top-5 rounded-full px-3 py-1 text-xs font-semibold',
|
||||
isFeaturedPack(pack.tag) ? 'bg-primary text-white' : 'bg-slate-900 text-white'
|
||||
]"
|
||||
>
|
||||
{{ pack.tag }}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-sm font-semibold uppercase tracking-[0.18em] text-slate-500">
|
||||
{{ pack.name }}
|
||||
</p>
|
||||
<div class="mt-5 flex items-end gap-2">
|
||||
<span class="text-5xl font-bold tracking-tight text-slate-900">{{ pack.price }}</span>
|
||||
<span class="pb-2 text-slate-500">{{ t('home.pricing.perMonth') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="mt-8 space-y-4">
|
||||
<li v-for="value in pack.features" :key="value" class="flex items-start gap-3">
|
||||
<span class="mt-0.5 inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-primary/10 text-primary">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||||
<path d="M5 12.5l4.2 4.2L19 7" />
|
||||
</svg>
|
||||
</span>
|
||||
<span class="text-slate-600">{{ value }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<RouterLink
|
||||
to="/sign-up"
|
||||
:class="[
|
||||
'mt-10 inline-flex w-full items-center justify-center !rounded-xl !py-3.5',
|
||||
isFeaturedPack(pack.tag) ? 'btn btn-success' : 'btn btn-outline-primary'
|
||||
]"
|
||||
>
|
||||
{{ pack.buttonText }}
|
||||
</RouterLink>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { Head } from '@unhead/vue/components'
|
||||
import { cn } from '@/lib/utils';
|
||||
const pricing = {
|
||||
title: "Simple, transparent pricing",
|
||||
subtitle: "Choose the plan that fits your needs. No hidden fees.",
|
||||
packs: [
|
||||
{
|
||||
name: "Hobby",
|
||||
price: "$0",
|
||||
features: [
|
||||
"Unlimited upload",
|
||||
"1 Hour of Storage",
|
||||
"Standard Support",
|
||||
],
|
||||
buttonText: "Start Free",
|
||||
tag: "",
|
||||
bg: "#f9fafb",
|
||||
},
|
||||
{
|
||||
name: "Pro",
|
||||
price: "$29",
|
||||
features: [
|
||||
"Ads free player",
|
||||
"Support M3U8",
|
||||
"Unlimited upload",
|
||||
"Custom ads"
|
||||
],
|
||||
buttonText: "Get Started",
|
||||
tag: "POPULAR",
|
||||
bg: "#eff6ff",
|
||||
},
|
||||
{
|
||||
name: "Scale",
|
||||
price: "$99",
|
||||
features: [
|
||||
"5 TB Bandwidth",
|
||||
"500 Hours Storage",
|
||||
"Priority Support"
|
||||
],
|
||||
buttonText: "Contact Sales",
|
||||
tag: "Best Value",
|
||||
bg: "#eef4f7",
|
||||
}
|
||||
]
|
||||
}
|
||||
</script>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user