diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..5ac62ae --- /dev/null +++ b/.dockerignore @@ -0,0 +1,11 @@ +.git +.gitignore +.idea +.vscode +.svelte-kit +build +node_modules +npm-debug.log* +pnpm-debug.log* +.env +.env.* diff --git a/.gitea/workflows/prod.yml b/.gitea/workflows/prod.yml new file mode 100644 index 0000000..6bdc57e --- /dev/null +++ b/.gitea/workflows/prod.yml @@ -0,0 +1,37 @@ +name: Build and Push Docker Image + +on: + push: + branches: + - main + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Docker login to Gitea Registry + run: | + echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login "${{ vars.REGISTRY_URL }}" \ + -u "${{ secrets.REGISTRY_USER }}" \ + --password-stdin + + - name: Build Docker image + run: | + docker build \ + -t "${{ vars.REGISTRY_URL }}/${{ vars.IMAGE_NAME }}:latest" \ + -t "${{ vars.REGISTRY_URL }}/${{ vars.IMAGE_NAME }}:${{ gitea.sha }}" \ + . + + - name: Push Docker image + run: | + docker push "${{ vars.REGISTRY_URL }}/${{ vars.IMAGE_NAME }}:latest" + docker push "${{ vars.REGISTRY_URL }}/${{ vars.IMAGE_NAME }}:${{ gitea.sha }}" + + - name: Deploy + run: | + curl https://dokploy.robosoft-solutions.de/api/deploy/compose/vETS7jxOixux3wDPobAaL diff --git a/.idea/ShadcnHelperSettings.xml b/.idea/ShadcnHelperSettings.xml new file mode 100644 index 0000000..38aa1f2 --- /dev/null +++ b/.idea/ShadcnHelperSettings.xml @@ -0,0 +1,2615 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f534bb2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +# syntax=docker/dockerfile:1 + +FROM node:24-alpine AS base +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" +RUN corepack enable && corepack prepare pnpm@11.15.1 --activate + +FROM base AS dependencies +WORKDIR /app +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./ +RUN pnpm install --frozen-lockfile + +FROM dependencies AS build +COPY . . +RUN pnpm build && pnpm prune --prod --ignore-scripts + +FROM node:24-alpine AS runtime +WORKDIR /app +ENV NODE_ENV="production" +ENV HOST="0.0.0.0" +ENV PORT="3000" + +COPY --from=build --chown=node:node /app/package.json ./ +COPY --from=build --chown=node:node /app/node_modules ./node_modules +COPY --from=build --chown=node:node /app/build ./build + +USER node +EXPOSE 3000 +CMD ["node", "build/index.js"] diff --git a/components.json b/components.json new file mode 100644 index 0000000..eaea37d --- /dev/null +++ b/components.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://shadcn-svelte.com/schema.json", + "tailwind": { + "css": "src/routes/layout.css", + "baseColor": "neutral" + }, + "aliases": { + "components": "$lib/components", + "utils": "$lib/utils", + "ui": "$lib/components/ui", + "hooks": "$lib/hooks", + "lib": "$lib" + }, + "typescript": true, + "registry": "https://shadcn-svelte.com/registry", + "style": "nova", + "iconLibrary": "lucide", + "menuColor": "default", + "menuAccent": "subtle" +} diff --git a/package.json b/package.json index ed546a6..2dc2ef8 100644 --- a/package.json +++ b/package.json @@ -14,23 +14,33 @@ "format": "prettier --write ." }, "devDependencies": { - "@sveltejs/adapter-auto": "^7.0.1", + "@fontsource-variable/geist": "^5.2.9", + "@lucide/svelte": "^1.18.0", + "@sveltejs/adapter-node": "^5.5.7", "@sveltejs/kit": "^2.63.0", "@sveltejs/vite-plugin-svelte": "^7.1.2", "@tailwindcss/forms": "^0.5.11", "@tailwindcss/typography": "^0.5.19", "@tailwindcss/vite": "^4.3.0", + "bits-ui": "2.18.1", + "class-variance-authority": "0.7.1", + "clsx": "2.1.1", "prettier": "^3.8.3", "prettier-plugin-svelte": "^4.1.0", "prettier-plugin-tailwindcss": "^0.8.0", + "shadcn-svelte": "1.3.0", "svelte": "^5.56.1", "svelte-check": "^4.6.0", + "tailwind-merge": "3.6.0", + "tailwind-variants": "^3.2.2", "tailwindcss": "^4.3.0", + "tw-animate-css": "^1.4.0", "typescript": "^6.0.3", "vite": "^8.0.16" }, "dependencies": { - "@skeletonlabs/skeleton": "^4.15.2", - "@skeletonlabs/skeleton-svelte": "^4.15.2" + "fflate": "^0.8.3", + "pdf-lib": "^1.17.1", + "pdfjs-dist": "^6.0.227" } } diff --git a/plan/subdocuments-plan.md b/plan/subdocuments-plan.md new file mode 100644 index 0000000..00da8c3 --- /dev/null +++ b/plan/subdocuments-plan.md @@ -0,0 +1,256 @@ +# Plan: Sub-documents within a ZIP archive + +## Goal + +For a processed ZIP archive the user can already **reorder** attachments and **export** them as a single merged PDF. This feature adds the ability to: + +1. **Select** some of the archive's attachments and **group them into a "sub-document"** (a named, ordered subset of attachments). +2. **Reorder** attachments *within* a sub-document — and **reorder** the sub-documents themselves — exactly like the parent archive today. +3. **Choose an export mode** for the sub-documents: + - **Separate files** → each sub-document becomes its own downloaded PDF. + - **One PDF** → all sub-documents are merged into a single downloaded PDF (the existing top-level "PDF herunterladen" behaviour, now scoped to sub-documents when they exist). + +The existing flow (no sub-documents created → one merged PDF for the whole archive) stays intact as a fallback. + +--- + +## Open decisions (please confirm before implementation) + +These affect the data model and UI. Default proposals are marked ✅. + +### D1 — Partition model +- ✅ **A. Optional grouping.** Attachments can be "loose" (not part of any sub-document) *or* members of a sub-document. Sub-documents are created on demand by selecting attachments. +- B. **Exclusive partition.** Once the user starts creating sub-documents, every attachment must belong to exactly one. +- C. **Shared/multi-membership.** An attachment can appear in multiple sub-documents (references, not moves). + +> Recommendation: **A**. It is the least disruptive, matches the current "flat list" mental model, and degrades gracefully when the user does nothing. + +### D2 — Selection lifecycle +- ✅ **Selection mode toggle.** A small "Auswählen" button in the archive header switches the editor into selection mode. Only then are checkboxes rendered on each thumbnail; in normal mode the editor looks exactly like today (no permanent checkbox clutter). While in selection mode the user toggles attachments, then clicks "Teildokument erstellen" to consume the selection (which clears the selection but leaves selection mode on, so they can immediately create another sub-document). Exiting selection mode via a "Fertig" button (or Esc) clears any pending selection. +- A sub-document is created from the *current* selection. Afterwards each attachment can be moved into / out of / between sub-documents via drag-drop or a context action. +- Alternative considered: persistent always-visible checkboxes — rejected as visually noisy for the common case where the user only wants to reorder. + +### D3 — Download scope when sub-documents exist +The existing single archive-level "PDF herunterladen" button behaviour needs clarification. Proposed: +- ✅ When **no** sub-documents exist → today's behaviour (merge all attachments into one PDF). +- When sub-documents **do** exist → show a **download mode toggle** offered to the user at export time: + - **Getrennt** (separate): one PDF per sub-document (loose attachments are downloaded individually, matching their kind — PDF passthrough, image as-is or as a tiny PDF — TBD in D4). + - **Eine PDF** (one): all sub-documents (and loose attachments, optionally — see D5) merged in sub-document order, with attachment order preserved inside each. +- ✅ "Separate" downloads are triggered sequentially (same pattern as today's `exportAllZipArchivesAsPdf`). + +### D4 — Format of "separate" downloads +- ✅ Each sub-document is downloaded as a **PDF** (merge its attachments with the existing `mergeProcessedZipArchive` logic, scoped to the sub-document's attachments). Naming: `{archiveName} - {subDocumentName}.pdf`. +- B. Each sub-document is downloaded as a **ZIP** containing its original files. +- C. Mixed: keep each attachment's original format (PDFs passthrough, images as image files). + +> Recommendation: **A** for consistency with the existing PDF-first export. We can revisit if the actual need is "preserve original files." + +### D5 — Treatment of loose (ungrouped) attachments at export +- ✅ When exporting **separate**: loose attachments are downloaded each as their own PDF (or skipped — needs a quick confirm). +- ✅ When exporting **one PDF**: loose attachments are appended at the end (in their existing order) — or skipped. +- B. Ignore loose attachments entirely during export. + +> Recommendation: **include loose attachments** at the end of both modes, so nothing is silently lost. Confirm with user. + +### D6 — Naming of sub-documents +- ✅ A sub-document has a user-editable name (default e.g. `Teildokument 1`, `Teildokument 2`, …) shown next to its attachments, mirroring the archive name input. + +### D7 — Persistence +- ✅ Sub-document structure lives only in component state (same as today's reordering) — no localStorage persistence required for v1. (We already do not persist reordering.) + +--- + +## Data model (`src/lib/zip-processing.ts`) + +Introduce a `SubDocument` type alongside `ZipAttachment` and extend `ProcessedZipArchive`. + +```ts +export type SubDocument = { + id: string; // stable id for keyed {#each} and drag state + name: string; // user-editable + attachments: ZipAttachment[]; +}; + +export type ProcessedZipArchive = { + name: string; + attachments: ZipAttachment[]; // loose attachments (unchanged shape) + subDocuments: SubDocument[]; // NEW — defaults to [] for backward compat +}; +``` + +Notes: +- Attachments inside `SubDocument` reuse the existing `ZipAttachment` type unchanged. +- `extractZipEntries` populates `subDocuments: []`. All existing call sites continue to work. +- A sub-document's `id` is generated with `crypto.randomUUID()` (available in browser + modern Node) — never re-derived from index, so reordering stays stable. + +### New pure helpers (unit-test-friendly, no DOM) + +```ts +// Create a sub-document from a list of attachments. +export const createSubDocument = ( + name: string, + attachments: ZipAttachment[] +): SubDocument => ({ id: crypto.randomUUID(), name, attachments }); + +// Move an attachment from the loose list (or another sub-document) into a sub-document. +export const moveAttachmentIntoSubDocument = ( + archive: ProcessedZipArchive, + attachmentPath: string, + source: { kind: 'loose' } | { kind: 'subDocument'; id: string }, + target: { kind: 'loose'; index: number } | { kind: 'subDocument'; id: string; index: number } +): ProcessedZipArchive => { /* immutable update */ }; + +// Reorder sub-documents (same shape as the existing moveAttachment helper). +export const moveSubDocument = ( + archive: ProcessedZipArchive, + fromIndex: number, + toIndex: number +): ProcessedZipArchive => { /* ... */ }; + +// Flatten the archive into an ordered list of attachments for "one PDF" export. +// sub-document order is preserved; loose attachments appended at the end (per D5). +export const flattenArchiveForMerge = ( + archive: ProcessedZipArchive +): ZipAttachment[] => { + const ordered: ZipAttachment[] = []; + for (const sub of archive.subDocuments) ordered.push(...sub.attachments); + ordered.push(...archive.attachments); + return ordered; +}; + +// Build one merged-PDF byte array per export unit. +// - mode 'single' → returns [ { name, bytes } ] from the whole archive (existing behaviour) +// - mode 'separate' → returns one entry per sub-document (+ loose attachments per D5) +export type ExportUnit = { name: string; bytes: Uint8Array }; +export const buildArchiveExport = async ( + archive: ProcessedZipArchive, + mode: 'single' | 'separate' +): Promise => { /* ... */ }; +``` + +`mergeProcessedZipArchive` is refactored to delegate to a new `mergeAttachments(attachments): Promise` (a thin rename of the existing inner loop) so both "single" and "separate" share the same merging code. + +--- + +## UI + +### `ProcessedZipArchiveEditor.svelte` (main work) + +State additions: +- `subDocuments = $state([])` synced from `archive.subDocuments` in the existing `$effect`. +- `selectionMode = $state(false)` — whether selection checkboxes are currently shown on thumbnails. +- `selectedPaths = $state>([])` — currently selected attachment paths (keyed by `attachment.path`, which is unique within an archive after extraction). Only meaningful while `selectionMode` is on. +- `downloadMode = $state<'single' | 'separate'>('single')` (only shown when sub-documents exist). +- Existing drag handlers generalised to handle three drag sources/targets: + - loose attachment ↔ loose attachment (today's behaviour), + - loose attachment → sub-document, + - sub-document attachment ↔ sub-document attachment, + - sub-document ↔ sub-document (reorder). +- Drag-and-drop is disabled while in selection mode (so a click toggles selection rather than starting a drag). + +Layout: +- Keep the existing horizontal `
    ` for **loose** attachments. +- In the header, add a small toggle button labelled **"Auswählen"** (icon: `IconMenu` or a checkmark lucide icon). When active it reads **"Fertig"** (and selection mode is on). In normal mode thumbnails have no checkbox at all — the editor looks identical to today. +- While in selection mode, each thumbnail card shows a **checkbox** in the top-left corner and the whole card becomes a click target that toggles its membership in `selectedPaths`. Drag-to-reorder is suspended in this mode to avoid click/drag ambiguity. +- A new **inline action bar** appears above the loose-attachment list when `selectionMode && selectedPaths.size > 0`: + - Button **"Teildokument erstellen"** → calls `createSubDocument` with the selected attachments, removes them from the loose list, clears `selectedPaths`, and keeps `selectionMode` on so the user can immediately build the next sub-document. +- `Esc` exits selection mode (clearing `selectedPaths`); the "Fertig" button does the same. +- Below the loose list, render `subDocuments` as a **vertical stack of "sub-document blocks"**. Each block is itself a horizontal reorderable `
      ` of attachments (same `AttachmentPreview`, same drag handle `::`, same thumbnail width). Each block has: + - an editable name input (mirrors archive name input styling), + - a count label, + - a "remove" action that returns its attachments back to the loose list, + - a draggable header (`::`) to reorder the block among its siblings. +- Sub-document blocks are draggable as a whole via their header (HTML5 DnD, mirroring the existing pattern in `handleDragStart/Drop`). + +Export controls (header area): +- If `subDocuments.length === 0` → existing **"PDF herunterladen"** button + behaviour (unchanged). +- If `subDocuments.length > 0` → replace with a **segmented control** (`Eine PDF` / `Getrennt`) plus the download button labeled accordingly: + - `Eine PDF` → `buildArchiveExport(archive, 'single')`, one `downloadPdfBytes` call. + - `Getrennt` → `buildArchiveExport(archive, 'separate')`, sequential `downloadPdfBytes` per unit (matches today's `exportAllZipArchivesAsPdf` loop). + +All state changes flow through `onArchiveChange` (already plumbed to `updateProcessedZipFile` in `+page.svelte`) so the top-level array stays the source of truth. + +### `+page.svelte` +- `exportAllZipArchivesAsPdf` needs to honour per-archive export mode. Two options: + - ✅ Keep it simple: bulk export uses the existing **"single"** mode for every archive (sub-documents flattened). Add a note in the tooltip. + - B. Read each archive's last-chosen mode (requires propagating `downloadMode` up). Defer to a follow-up if desired. +- The settings-drawer bulk export button label stays; behaviour unchanged for v1. + +### No changes required +- `ZipDropzone`, `AttachmentPreview`, services, `xml-reading.service`, `zip-inflating.service`, layout. + +--- + +## Implementation phases + +### Phase 1 — Data model + pure helpers +- Edit `src/lib/zip-processing.ts`: + - Add `SubDocument` type and extend `ProcessedZipArchive` with `subDocuments: SubDocument[]` (default `[]`). + - Set `subDocuments: []` in `extractZipEntries`. + - Extract `mergeAttachments(attachments: ZipAttachment[]): Promise` from `mergeProcessedZipArchive`. + - Add the pure helpers: `createSubDocument`, `moveAttachmentIntoSubDocument`, `moveSubDocument`, `removeAttachmentFromSubDocument` (returns attachments to the loose list), `flattenArchiveForMerge`, `buildArchiveExport`. +- Verification: TypeScript compiles (`pnpm check` / `svelte-check`); existing tests (if any) still pass; existing UI still renders unchanged. + +### Phase 2 — Sub-document block component +- New `src/lib/components/SubDocumentEditor.svelte`: + - Props: `subDocument`, `thumbnailWidth`, `onSubDocumentChange`, `onRemove`, `drag handlers` (or pass a generic reorder callback). + - Renders the editable name, count, remove button, draggable header, horizontal attachment list (reusing `AttachmentPreview` + the same DnD styling). + - Internally mirrors `ProcessedZipArchiveEditor`'s drag logic, scoped to the block. +- Verify: can be unit-rendered in isolation (storybook-equivalent or a throwaway page snippet). + +### Phase 3 — Selection mode + sub-document creation in `ProcessedZipArchiveEditor` +- Add `selectionMode` and `selectedPaths` state and the "Auswählen" / "Fertig" header toggle. +- While `selectionMode` is on, render a checkbox on each loose thumbnail and make the whole card a click-to-toggle target; suspend drag-to-reorder to avoid click/drag ambiguity. +- Add the inline action bar with **"Teildokument erstellen"**, visible when a selection exists. +- On create: call `createSubDocument`, remove the selected attachments from the loose list, clear `selectedPaths`, keep `selectionMode` on, and emit `onArchiveChange`. +- `Esc` / "Fertig" exits selection mode and clears the selection. +- On sub-document removal: push its attachments back into the loose list (appended at the end by default). +- Verify: toggling selection mode shows/hides checkboxes; in normal mode the editor looks identical to today; creating, populating, and removing sub-documents works end-to-end with reordering still working for loose attachments. + +### Phase 4 — Sub-document drag interactions +- Generalise the existing HTML5 DnD handlers to support the three move scenarios listed above. Use the `dataTransfer` payload to encode `{ source: 'loose' | `sub:${id}`, index }`. +- Reorder sub-document blocks via header drag. +- Verify: items can be moved loose↔sub and sub↔sub; sub-document order can be changed; nothing is duplicated or lost (count invariant: `loose.length + Σ sub.attachments.length === total attachments`). + +### Phase 5 — Export modes +- Replace header download button with the segmented control + button when sub-documents exist. +- Wire `buildArchiveExport(archive, mode)` to `downloadPdfBytes` (single) and the sequential loop (separate). +- `+page.svelte`: keep `exportAllZipArchivesAsPdf` in single mode; optionally surface a small hint when an archive has sub-documents. +- Verify: + - No sub-documents → existing single-PDF download unchanged. + - With sub-documents, "Eine PDF" flattens and downloads one PDF. + - With sub-documents, "Getrennt" downloads N files named `{archiveName} - {subName}.pdf`. + +### Phase 6 — Polish +- Empty states (e.g. empty sub-document block, no loose attachments). +- Disable the create-sub-document button when selection is empty. +- Accessible labels + keyboard operability for checkboxes / segmented control (`bits-ui` SegmentedControl if available, otherwise native radio group). +- Update `agents.md` "ZIP/PDF processing details" + "UI/state details" sections to mention sub-documents. +- Manual smoke test on a real `xjustiz` ZIP with nested archives. + +--- + +## Verification checklist (definition of done) + +- [ ] `pnpm check` (or `svelte-check`) passes with no new errors. +- [ ] `pnpm build` succeeds. +- [ ] Loading a ZIP without creating sub-documents produces the exact same merged PDF as today. +- [ ] Toggling "Auswählen" shows checkboxes on thumbnails; toggling "Fertig" (or pressing Esc) hides them and clears the selection. In normal mode the editor looks identical to today (no checkboxes). +- [ ] Selecting ≥1 attachments and clicking "Teildokument erstellen" removes them from the loose list and adds a named sub-document block, then clears the selection while staying in selection mode for the next group. +- [ ] Drag-reordering works for: loose attachments, attachments inside a sub-document, sub-document blocks themselves, and moving an attachment loose↔sub. +- [ ] With sub-documents present: + - "Eine PDF" downloads a single PDF containing every attachment (sub-documents first, then loose). + - "Getrennt" downloads one PDF per sub-document, each correctly named. +- [ ] Removing a sub-document returns its attachments to the loose list; total attachment count is invariant. +- [ ] Bulk "Alle ZIP-Archive als PDF exportieren" still works (single mode). +- [ ] `agents.md` updated. + +--- + +## Out of scope (follow-ups) + +- Persisting sub-document structure to `localStorage`. +- Renaming attachments themselves (only sub-document names are editable in v1). +- Exporting "separate" as ZIPs or original-format files (see D4-C). +- Per-archive export mode remembered by the bulk exporter (D3 follow-up). +- Drag-and-drop between *different archives* in `+page.svelte` (today each editor is self-contained). diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 32f3ff5..82f7eed 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,16 +8,25 @@ importers: .: dependencies: - '@skeletonlabs/skeleton': - specifier: ^4.15.2 - version: 4.15.2(tailwindcss@4.3.0) - '@skeletonlabs/skeleton-svelte': - specifier: ^4.15.2 - version: 4.15.2(svelte@5.56.3) + fflate: + specifier: ^0.8.3 + version: 0.8.3 + pdf-lib: + specifier: ^1.17.1 + version: 1.17.1 + pdfjs-dist: + specifier: ^6.0.227 + version: 6.0.227 devDependencies: - '@sveltejs/adapter-auto': - specifier: ^7.0.1 - version: 7.0.1(@sveltejs/kit@2.64.0(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3)(vite@8.0.16(jiti@2.7.0)))(svelte@5.56.3)(typescript@6.0.3)(vite@8.0.16(jiti@2.7.0))) + '@fontsource-variable/geist': + specifier: ^5.2.9 + version: 5.2.9 + '@lucide/svelte': + specifier: ^1.18.0 + version: 1.18.0(svelte@5.56.3) + '@sveltejs/adapter-node': + specifier: ^5.5.7 + version: 5.5.7(@sveltejs/kit@2.64.0(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3)(vite@8.0.16(jiti@2.7.0)))(svelte@5.56.3)(typescript@6.0.3)(vite@8.0.16(jiti@2.7.0))) '@sveltejs/kit': specifier: ^2.63.0 version: 2.64.0(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3)(vite@8.0.16(jiti@2.7.0)))(svelte@5.56.3)(typescript@6.0.3)(vite@8.0.16(jiti@2.7.0)) @@ -33,6 +42,15 @@ importers: '@tailwindcss/vite': specifier: ^4.3.0 version: 4.3.0(vite@8.0.16(jiti@2.7.0)) + bits-ui: + specifier: 2.18.1 + version: 2.18.1(@internationalized/date@3.12.2)(@sveltejs/kit@2.64.0(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3)(vite@8.0.16(jiti@2.7.0)))(svelte@5.56.3)(typescript@6.0.3)(vite@8.0.16(jiti@2.7.0)))(svelte@5.56.3) + class-variance-authority: + specifier: 0.7.1 + version: 0.7.1 + clsx: + specifier: 2.1.1 + version: 2.1.1 prettier: specifier: ^3.8.3 version: 3.8.4 @@ -42,15 +60,27 @@ importers: prettier-plugin-tailwindcss: specifier: ^0.8.0 version: 0.8.0(prettier-plugin-svelte@4.1.0(prettier@3.8.4)(svelte@5.56.3))(prettier@3.8.4) + shadcn-svelte: + specifier: 1.3.0 + version: 1.3.0(svelte@5.56.3) svelte: specifier: ^5.56.1 version: 5.56.3 svelte-check: specifier: ^4.6.0 version: 4.6.0(picomatch@4.0.4)(svelte@5.56.3)(typescript@6.0.3) + tailwind-merge: + specifier: 3.6.0 + version: 3.6.0 + tailwind-variants: + specifier: ^3.2.2 + version: 3.2.2(tailwind-merge@3.6.0)(tailwindcss@4.3.0) tailwindcss: specifier: ^4.3.0 version: 4.3.0 + tw-animate-css: + specifier: ^1.4.0 + version: 1.4.0 typescript: specifier: ^6.0.3 version: 6.0.3 @@ -78,8 +108,11 @@ packages: '@floating-ui/utils@0.2.11': resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} - '@internationalized/date@3.12.0': - resolution: {integrity: sha512-/PyIMzK29jtXaGU23qTvNZxvBXRtKbNnGDFD+PY6CZw/Y8Ex8pFUzkuCJCG9aOqmShjqhS9mPqP6Dk5onQY8rQ==} + '@fontsource-variable/geist@5.2.9': + resolution: {integrity: sha512-TP+QSBG3wxKGPE33CbMy/L0Nu3qvJ6Fy81Yc4LnQ95xH+i+cfEp8fyU8/kfV14YwszxIFPhnoMTbjL71waVpyQ==} + + '@internationalized/date@3.12.2': + resolution: {integrity: sha512-FY1Y+H64NDs+HAF6omlnWxm3mEpfgaCSWtL5l551ZZfImA+kGjPFgrnJrGjH6lfmLL0g8Z/mBu1R3kufeCp6Jw==} '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -97,6 +130,86 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@lucide/svelte@1.18.0': + resolution: {integrity: sha512-S1cjiJflMLIirDZtZ8ou2Vy0xMrOqEQzYzDo29AUxoJiOjUfMHUeClvprJsGUEr60RYDJzEgkjSkWm1nlN9iyQ==} + peerDependencies: + svelte: ^5 + + '@napi-rs/canvas-android-arm64@1.0.0': + resolution: {integrity: sha512-3hNKJObUK7JsCF9aJlVCs1J0/KE/gGfZNeK8MO1ge6bB3aicr5walGme9t9No1f/oyk9GgvdAT/rjSdsx3gbIw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@napi-rs/canvas-darwin-arm64@1.0.0': + resolution: {integrity: sha512-ZIja19/BiGz2puhki+WUYSRriwFeFJ8Mi9eK3hZdSS85w4Y60cuEAJVhMCfKwswQkKkUtrnzdKMBuO7TupvexA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@napi-rs/canvas-darwin-x64@1.0.0': + resolution: {integrity: sha512-hImggWc82jqZVpEsFR9S7PE9OQYjq/H/D7vwCGB6X1jRH+UVBP1+1niJTPBOat1B154T6GKK7/kcFtoWgjgFzQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@napi-rs/canvas-linux-arm-gnueabihf@1.0.0': + resolution: {integrity: sha512-hlJRy6d+kWLKVOG/+1rEvNQVURZ0DxxRPJsLmEWwhwiXZUJc0BF5o9esALHSEP4CoJK4wChRtj3hnyBgVx2oWA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@napi-rs/canvas-linux-arm64-gnu@1.0.0': + resolution: {integrity: sha512-5Hru4T3RXkosRQafcjelv7AUzw9mXqmGYsxnzeDDOWveFCJyEPMSJltvGCM+jfH98seOCbfwm9KyFg6Jm5FhAA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@napi-rs/canvas-linux-arm64-musl@1.0.0': + resolution: {integrity: sha512-LTUl9jS8WsLSUGaxQZKQkxfluOJRpgvBuxxdM4pYcjib+di8AU4OzQc6+L6SzGMLcKc9H0RAjojRatBhTMqYdg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@napi-rs/canvas-linux-riscv64-gnu@1.0.0': + resolution: {integrity: sha512-Iz931SAZf+WVDzpjk52Q3ffW3zw0YflFwEZMgs036Wfu1kX/LrwT9wGjsuSqyduqefUkl91/vTdAjn8hQu5ezA==} + engines: {node: '>= 10'} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@napi-rs/canvas-linux-x64-gnu@1.0.0': + resolution: {integrity: sha512-pFEQ5eFK4JusgN1K6KkO9DKP/Hi1WMJOkF8Ch03/khTc4bFbCKkCCsJG4YcOMOW9bI4XbT2/eMAWxhO0xaWgPA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@napi-rs/canvas-linux-x64-musl@1.0.0': + resolution: {integrity: sha512-jnvr8NrLHiZ3NCiOKWqDbkI4Ah+QDrqtZ+sddPZBltEb1mQ2coSvCSJYfict+oAwcm0c970oTmVySpjKP/lnaA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@napi-rs/canvas-win32-arm64-msvc@1.0.0': + resolution: {integrity: sha512-y2j9/Gfd5joqiqxdP/L1smqjQ+uAx3C4N0EC7bDHrnZEEH8ToM/OC5p3uHvtj4Lq591aHj+ArL01UDLNwT5HgQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@napi-rs/canvas-win32-x64-msvc@1.0.0': + resolution: {integrity: sha512-qwdhh9N6Gge/hC4pL9S1tQp0iKwhSl/dYjg7+RGp9k26iRGRi5MqqUyKGOXIWli0zOcuy5Y2wIH/jk2ry6i/jA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@napi-rs/canvas@1.0.0': + resolution: {integrity: sha512-Jqxcy1XOIqj+lH9sl1GT+il6GR3uQv13vI2mrwubP3uT8Olak2ClDrK2RnxlQKjwv8BRr4b3ug0YR7c6hBX8wg==} + engines: {node: '>= 10'} + '@napi-rs/wasm-runtime@1.1.5': resolution: {integrity: sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==} peerDependencies: @@ -106,6 +219,12 @@ packages: '@oxc-project/types@0.133.0': resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==} + '@pdf-lib/standard-fonts@1.0.0': + resolution: {integrity: sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==} + + '@pdf-lib/upng@1.0.1': + resolution: {integrity: sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==} + '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} @@ -207,18 +326,188 @@ packages: '@rolldown/pluginutils@1.0.1': resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} - '@skeletonlabs/skeleton-common@4.15.2': - resolution: {integrity: sha512-y7KZn++Av8UHdoeaaguQ7zIS1HlK7Y5jyPDnHy65wJ60iS97fMtQV0kGhqVoYCWhor+xrjbAFjWtaPOPPDEMYA==} - - '@skeletonlabs/skeleton-svelte@4.15.2': - resolution: {integrity: sha512-vZkRhR701EOHVXVKh/NFRSY8D9LPBvmdNFdtfgqO7TEg77sypJUvERl2dxErTLY1QjVklVpjsHRCTaSLn+1Ntg==} + '@rollup/plugin-commonjs@29.0.3': + resolution: {integrity: sha512-ZaOxZceP7SOUW7Lqw5IRVweSQYWaeIPnXIGLiB690EBA3FGJTO40EEr2L5yZplJWsgTCogILRSpcAe7+U0Otdg==} + engines: {node: '>=16.0.0 || 14 >= 14.17'} peerDependencies: - svelte: ^5.29.0 + rollup: ^2.68.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true - '@skeletonlabs/skeleton@4.15.2': - resolution: {integrity: sha512-5O23Py76nw56aoieV2b2T7MJ6xS0DwDjUULpwLnCXxXOnmzADEoWoQxb/ABbqg04IMOygtRzK3HO22I1+kFsog==} + '@rollup/plugin-json@6.1.0': + resolution: {integrity: sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==} + engines: {node: '>=14.0.0'} peerDependencies: - tailwindcss: ^4.0.0 + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/plugin-node-resolve@16.0.3': + resolution: {integrity: sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^2.78.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/plugin-replace@6.0.3': + resolution: {integrity: sha512-J4RZarRvQAm5IF0/LwUUg+obsm+xZhYnbMXmXROyoSE1ATJe3oXSb9L5MMppdxP2ylNSjv6zFBwKYjcKMucVfA==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/pluginutils@5.4.0': + resolution: {integrity: sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.62.2': + resolution: {integrity: sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.62.2': + resolution: {integrity: sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.62.2': + resolution: {integrity: sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.62.2': + resolution: {integrity: sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.62.2': + resolution: {integrity: sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.62.2': + resolution: {integrity: sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + resolution: {integrity: sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.62.2': + resolution: {integrity: sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.62.2': + resolution: {integrity: sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.62.2': + resolution: {integrity: sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.62.2': + resolution: {integrity: sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.62.2': + resolution: {integrity: sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.62.2': + resolution: {integrity: sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.62.2': + resolution: {integrity: sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.62.2': + resolution: {integrity: sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.62.2': + resolution: {integrity: sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.62.2': + resolution: {integrity: sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.62.2': + resolution: {integrity: sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.62.2': + resolution: {integrity: sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.62.2': + resolution: {integrity: sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.62.2': + resolution: {integrity: sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.62.2': + resolution: {integrity: sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.62.2': + resolution: {integrity: sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.62.2': + resolution: {integrity: sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.62.2': + resolution: {integrity: sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==} + cpu: [x64] + os: [win32] '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -228,10 +517,10 @@ packages: peerDependencies: acorn: ^8.9.0 - '@sveltejs/adapter-auto@7.0.1': - resolution: {integrity: sha512-dvuPm1E7M9NI/+canIQ6KKQDU2AkEefEZ2Dp7cY6uKoPq9Z/PhOXABe526UdW2mN986gjVkuSLkOYIBnS/M2LQ==} + '@sveltejs/adapter-node@5.5.7': + resolution: {integrity: sha512-uOfc9eVlI3A37RRSaKcgrheBYPrfJwC9VMqDp8x/O6tlKdcLLvHThSWD0KNIbjQ/d+7bwLGx3vx6aowAcRfd2g==} peerDependencies: - '@sveltejs/kit': ^2.0.0 + '@sveltejs/kit': ^2.4.0 '@sveltejs/kit@2.64.0': resolution: {integrity: sha512-24MXA/xyugUqJd09DD4v4bLd4k7FaYhdBck/pTXiu3XFtDBcwXYpJc1SO1+lqdjkNBUo1r4eek5FS7cfzBQUmA==} @@ -376,156 +665,12 @@ packages: '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@types/resolve@1.20.2': + resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} + '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} - '@zag-js/accordion@1.39.1': - resolution: {integrity: sha512-GA3m7gRTm3weSe1eMlHIsTNztcjZ6joIaRgxxKil7q/UX0xIVVGDy0aCr6oo7FAuoMiOOBVurYXILpFZ30nOXA==} - - '@zag-js/anatomy@1.39.1': - resolution: {integrity: sha512-p2iFAs2pVQgv5iCDAftA7g9Z/fUYXW94dRIGk415TSbkp/YDENydm/JtRoNctp302UIx4Eeuc5QBR+7h5kuISA==} - - '@zag-js/aria-hidden@1.39.1': - resolution: {integrity: sha512-wiwcz3N086qBMEU3VKfHhcvGm6Jm1PIcDXys/jEqiKPtHoYZhDip0n0cPOoasss/A1oS39QFVdk3WpLXGu3Izw==} - - '@zag-js/auto-resize@1.39.1': - resolution: {integrity: sha512-ditIo9mW7fapq+4yx3/8hMpMZlWaoOy66EOzUz8dSVqnxnTWAjnTICu/9zFh8pkWerlzGTtDOJPP1oZ8S/rgVg==} - - '@zag-js/avatar@1.39.1': - resolution: {integrity: sha512-LWrgJ0bebnXPSL+uehA9z6BlCD/MZEOQBJqH/F2QQFSAAZXUUDKtzVDmc+UtwjDsHXqqTghi+v2atQJHNMcJ2g==} - - '@zag-js/carousel@1.39.1': - resolution: {integrity: sha512-5z5z3IldUgZ/R+KZLNQDoJFNTXzYd28YOmgfWH61Vvyv+RarX8kwZW8ajW/fNiqcWXyhW3/VMU0lArrfjbQVtQ==} - - '@zag-js/collapsible@1.39.1': - resolution: {integrity: sha512-Zgccg/t7M8i0JVwZPPgW7XB7kGhTO475hsmwkF/8CYLqBBckVDHUARp2we24hENCm/98eez6R0eDEmE+tldFWA==} - - '@zag-js/collection@1.39.1': - resolution: {integrity: sha512-fyOyKmP7MRo0/U8mBmB7KgHRXHhXP27LCcasy3x+qTAQtuEfYG1EPhKuj07oBWlX/2qfcKYn2R3YopHcqFcCiA==} - - '@zag-js/combobox@1.39.1': - resolution: {integrity: sha512-fmStpG+k4xrxCzqUX0ssnOMeoSietWm5ir3qmEZcagzNqNycAXMvOELAIeyXi87Kut6aDGhxLOV7o395HVXl/w==} - - '@zag-js/core@1.39.1': - resolution: {integrity: sha512-Yp0r49QLYXe2j7fgyAiilH4umXFydCnr5hcRDwJU+sxvUAlq00JQIJIEK2pT6k8cJiNNsFEV5WkOX7jsqpAX2A==} - - '@zag-js/date-picker@1.39.1': - resolution: {integrity: sha512-t9q1H0aZQJkbzKTR2Bn5vMwaoFoirxekiSxw8ju0F0vr4Kg4BJ9yueOQm5I2wALKnJbZu4Ua5MgzlrDF3CQt3A==} - peerDependencies: - '@internationalized/date': '>=3.0.0' - - '@zag-js/date-utils@1.39.1': - resolution: {integrity: sha512-i4SvBhru2Yz/zsHT0XvyFhf4a+pAKYkWXeVfU0RvF2S6mPTfgaMFF9ZNPq5Sy8K31EtAa6AVXcybYaYnibn1FA==} - peerDependencies: - '@internationalized/date': '>=3.0.0' - - '@zag-js/dialog@1.39.1': - resolution: {integrity: sha512-q+HTmfuRDRZthln9mb7i52wdltQOZlw3+nw3a2uygEe9xuEtHBwUz31XJzkn2UWQqhAt7cC39OwykhNLKrfkqA==} - - '@zag-js/dismissable@1.39.1': - resolution: {integrity: sha512-7/soy93Ersd5qedhSL/+CDcZ9gNTQV0ooDcqKtM8b4IxwD4rgWwGsewJY+tbKmOqaZobwa0YcWV2+YGgI23ESw==} - - '@zag-js/dom-query@1.39.1': - resolution: {integrity: sha512-k01aXeUWLyJfB61CODaXj4PLhYmVpnVMFrC+3nk/XCn1MW7my8L/8KVg0m4W8n+X9MhpaLWsZDmK/dwED/3qSw==} - - '@zag-js/file-upload@1.39.1': - resolution: {integrity: sha512-cErPOnPwPyneUXpelsfm75DKn0/4SI8aqQnlbrqo522PEqAQyDfDdBsqebGgKWG3F0A++kKFp9LO9A5zCrw5gA==} - - '@zag-js/file-utils@1.39.1': - resolution: {integrity: sha512-ll/W5o74SMmoAS+l7PkmmGjPj4PLCSG/cwQh1Y/+LpaSev0YiR3Nk2OzRIIPtm3NivYVxKGawaCOf1RvT/82LQ==} - - '@zag-js/floating-panel@1.39.1': - resolution: {integrity: sha512-IfPbf3pwJGqBWHec/rPzpdPjfMCLed59LlEophvRy49FEdksv8eN6nr9DXl2wWZEoQhH99scXfLMbtEZsPsFWg==} - - '@zag-js/focus-trap@1.39.1': - resolution: {integrity: sha512-2ZzVefHMotvtxUo/gP4R45Szw/EPaPkTKEHaug6/il62SPDbkFODF+5r1zXyLbLuwCHq0apvQasg/ONLihwlXw==} - - '@zag-js/focus-visible@1.39.1': - resolution: {integrity: sha512-iEuTOYHE8HRn/7ULC9c9BTTWo0C0MJRCbYVxbh/d7v8qAuq4CS76pdfceNo3KeWbb968T+yiG6q0AjiHsr8IOw==} - - '@zag-js/i18n-utils@1.39.1': - resolution: {integrity: sha512-TKRLQQlHgJ4cxsHo3tZPtbFjGu9m1UPtfezRGFKq7A8czhdqRhaCpaWF849cd6dI7x6rWvvTan858gOFpyANnQ==} - - '@zag-js/interact-outside@1.39.1': - resolution: {integrity: sha512-LnSbA+txMsFmzNPn84QKH01x2yJv4At/eKHn6rT2PyxXkJQIh8PvCTS3zVz4Syw11cmhcXt2eRwhzx8yImV92w==} - - '@zag-js/listbox@1.39.1': - resolution: {integrity: sha512-Mz0UpdXobdTQTyjM+Avgi7pDVB2dKyaUHqw3TloeleQL3VwTqClclkwHXtLYYE+oXa0zOet37wI9mzfaYx9iZQ==} - - '@zag-js/live-region@1.39.1': - resolution: {integrity: sha512-E7YNd0QGzJ2n1ZhnI2smv+klwifsNRf9QaDCx7quVJCVYywpupsBK4R25KN75S1z8XaK+jAy6HYKj8DIhYjYeg==} - - '@zag-js/menu@1.39.1': - resolution: {integrity: sha512-bRDGLGkiGhzNtORBXkbBQV/xp2zEkwpYIepfWCaUoFwKUmx7GGnShTBFxJyq0u2D4IkS9GOwcqm20EhMv6V+TA==} - - '@zag-js/pagination@1.39.1': - resolution: {integrity: sha512-3Q1B9/g3ajhvXjuGffJ7otyXcXK5+uhdbE5A9CZa4bsW3pf25L9Cp+ZAjdXQMDc8T4jhZJAKFmDJfQgtr1oEIw==} - - '@zag-js/popover@1.39.1': - resolution: {integrity: sha512-aO3ExO/O7Sa3ovdozFI6SujhNOpYdCca4bImnAiovDL8DY8zN3UNQebu35IQvw9/aRsx9VKSJL1AqzJJUImFRw==} - - '@zag-js/popper@1.39.1': - resolution: {integrity: sha512-h0UMY2dXJNfM3OvMQ9t9LzlmwvpCgjloz2IvU1txY3r32UIy7ve1H70zkKagLtLRxFTuWmhumYUPULPo/6a1DA==} - - '@zag-js/progress@1.39.1': - resolution: {integrity: sha512-1IHyOw8DqPs3YH149Oj7W9a5oEfY5pc9GAVOPGbzYxVK/W8d/NIjVxa565I3J5cDJ0s6z3FrMSXMWUwr1ML4tw==} - - '@zag-js/radio-group@1.39.1': - resolution: {integrity: sha512-+sC9xcAyY/GbY+8HpKlbPgSyOxBLUSB18s6fe6K1wdmyom4PM0nmhLouuxisbFZYHOyfQwAOMo+ainRENB2hzQ==} - - '@zag-js/rating-group@1.39.1': - resolution: {integrity: sha512-IfdxWmM+3zpztx/HcE3bWob72sZNb1+BzK4tSySLVyjeqs8OzLDzrCbKqt10DmibnNOvpbjbq4eX4P5hV9YN7Q==} - - '@zag-js/rect-utils@1.39.1': - resolution: {integrity: sha512-5gJ0PzeUme76xTWG+4XythWgmGgDKV4XAxEUaB3KKDtXgjDHwtu7PwKLIzFtlaaSf/U23PY+RNVBVCYg1GmZog==} - - '@zag-js/remove-scroll@1.39.1': - resolution: {integrity: sha512-uZfPR3Gl9sQFo+tJ7kbuwsBhw+RIZwWFnMDgrz5LIwSNGN6hsyC4HGOxe29clkWQ2X2AjqqmEMETwgX7Jg+wxA==} - - '@zag-js/scroll-snap@1.39.1': - resolution: {integrity: sha512-AzCc8MAAVqkiK5Y0cJZ24OIBZDQrUmEexACMuR6M5yZmlcEbS0EA/d6Wq+LSR1JMVTD4B+UwcMj1D3vJQ90ZTw==} - - '@zag-js/slider@1.39.1': - resolution: {integrity: sha512-OEA9R7Ly5cw+6ANofnMpuHH3rAo8gZEnxy7iEwePu11pq2RCnt8DSj2V+uqU+dTq15Uup1LSzRgJfTnAC4Z85A==} - - '@zag-js/steps@1.39.1': - resolution: {integrity: sha512-DC6swMpwITTB0DyCSxlpWyPNSUN9ul9jz4N6aAyQ0L1IK/noF/YYTZRAcXNSRzN4iutO/2mFGGbwGq/oVf+gPA==} - - '@zag-js/store@1.39.1': - resolution: {integrity: sha512-zFpwP4lhiBVD9987rwAfZNVa2/f/xx4mhbCE1EEw31zxLAozY2jONeJ3UzPP05VbzKlRHBcvkaXAJQQGegTwFA==} - - '@zag-js/svelte@1.39.1': - resolution: {integrity: sha512-ZOyZjyvjePZdrkNTy5fa92ijeeID9e+3LRGziKGIII3JSEvwfkG/Buf8W84N8VHFxi0G0GcKmgVCDgKHyGQYoQ==} - peerDependencies: - svelte: '>=5' - - '@zag-js/switch@1.39.1': - resolution: {integrity: sha512-ikeQ42c0vyyPLeyW9U0dvcqTV1Ekpx5jZ050R905HGJ2GeWE0uBGuHbMpTG5U6Pwb0a+TMzqAr+jMsquVTCwzg==} - - '@zag-js/tabs@1.39.1': - resolution: {integrity: sha512-P2RThO1gX9SFsNqrAGPsXJxrjn5YqP6MFs9mdExU+tzzZyVjJQADkAmh98C0eEaCb6HKLpJZ/17hrnLDhm1Tig==} - - '@zag-js/tags-input@1.39.1': - resolution: {integrity: sha512-tc0+bd9FiUJwa+wY2hSVVGHLIBC3C3rOZX/4zjchRMs1xgl92c1/tYbytXny7ABB8ZMHveG7MtgDppVF4VkwBg==} - - '@zag-js/toast@1.39.1': - resolution: {integrity: sha512-K7ndEfBTKDds10iQKCQUmin74s6V4BEIypAIyQxs18gQB9TCn5+wff886JAzecIKPY97PDQHDKjYR71yzRC7/g==} - - '@zag-js/toggle-group@1.39.1': - resolution: {integrity: sha512-KS4Bo17foMKXVBhQjocRf4GQxMV4pMXclTo14IWjldaHs2HIrNJ0Ar0Ri+vo47BBKBNsXs4HuNvfbMdQj94wEA==} - - '@zag-js/tooltip@1.39.1': - resolution: {integrity: sha512-IsxFj7l8kPciwIyYJWlmQ7mhXocbjXxLj3m9z099slYOF7lApA33/ndY32w9ptrI4/nUh2nldzw6eRfSpVnuOA==} - - '@zag-js/tree-view@1.39.1': - resolution: {integrity: sha512-sm6qUZjO0OaqBqO5s55KU+l5p1wXfUVScoen7BYVoFBuROH7qAZJi8YMclGvnnlyV506i8Hk0qqWnLg0F38jCA==} - - '@zag-js/types@1.39.1': - resolution: {integrity: sha512-w3vVpgxmdJvMDvv19DXTtFI6kJL6TXw//U0Z1BAc3rnDA9orcB9Ryw4uMNvIzFA607CgssyJcWDaQ/M3yAcbJw==} - - '@zag-js/utils@1.39.1': - resolution: {integrity: sha512-9k741cH7L655Ua3tedTkuMblcXVXVgCLTB9svp9oTjA7oatpOpYF4z43kgAQVjyThNXMJ7AvtO4C80ajQLTScg==} - acorn@8.16.0: resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} engines: {node: '>=0.4.0'} @@ -539,14 +684,31 @@ packages: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} + bits-ui@2.18.1: + resolution: {integrity: sha512-KkemzKFH4T3gt3H+P86JcnAWExjByv/6vlwjm/BoCwTPHu03yiCdxbghdJLvFReQTe0acCAiRcKfmixxD6XvlA==} + engines: {node: '>=20'} + peerDependencies: + '@internationalized/date': ^3.8.1 + svelte: ^5.33.0 + chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} + + commondir@1.0.1: + resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + cookie@0.6.0: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} @@ -556,13 +718,14 @@ packages: engines: {node: '>=4'} hasBin: true - csstype@3.2.3: - resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -574,6 +737,10 @@ packages: resolution: {integrity: sha512-yJN/BOOLxcOW2aQgeif9mSnaUB8KtvmMMp56oA1kx1CRfBKbhZm2pJ+NBY+3eOboHxix8lfjWpHE0Ei5U8RbSA==} engines: {node: '>=10.13.0'} + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + esm-env@1.2.2: resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} @@ -585,6 +752,9 @@ packages: '@typescript-eslint/types': optional: true + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -594,14 +764,37 @@ packages: picomatch: optional: true + fflate@0.8.3: + resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + inline-style-parser@0.2.7: + resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} + engines: {node: '>= 0.4'} + + is-module@1.0.0: + resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} + + is-reference@1.2.1: + resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} + is-reference@3.0.3: resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} @@ -690,6 +883,10 @@ packages: locate-character@3.0.0: resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -710,10 +907,26 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + node-fetch-native@1.6.7: + resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} + obug@2.1.2: resolution: {integrity: sha512-AWGB9WFcRXOQs48Z/udjI5ZcZMHXwX8XPByNpOydgcGsDLIzjGizhoMWJyKAWze7AVW/2W1i+/gPX4YtKe5cyg==} engines: {node: '>=12.20.0'} + pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + pdf-lib@1.17.1: + resolution: {integrity: sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==} + + pdfjs-dist@6.0.227: + resolution: {integrity: sha512-/P6M4SXw+70waMVLUM7rdRtvo+dEzqE1t6W/zQNvBETo2MaRa5rrvCcAYdfWGiUzadTgM0lJmRApUrW0d9zgKg==} + engines: {node: '>=22.13.0 || >=24'} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -796,18 +1009,34 @@ packages: engines: {node: '>=14'} hasBin: true - proxy-compare@3.0.1: - resolution: {integrity: sha512-V9plBAt3qjMlS1+nC8771KNf6oJ12gExvaxnNzN/9yVRLdTv/lc+oJlnSzrdYDAvBfTStPCoiaCOTmTs0adv7Q==} - readdirp@4.1.2: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} + engines: {node: '>= 0.4'} + hasBin: true + rolldown@1.0.3: resolution: {integrity: sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + rollup@4.62.2: + resolution: {integrity: sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + runed@0.35.1: + resolution: {integrity: sha512-2F4Q/FZzbeJTFdIS/PuOoPRSm92sA2LhzTnv6FXhCoENb3huf5+fDuNOg1LNvGOouy3u/225qxmuJvcV3IZK5Q==} + peerDependencies: + '@sveltejs/kit': ^2.21.0 + svelte: ^5.7.0 + peerDependenciesMeta: + '@sveltejs/kit': + optional: true + sade@1.8.1: resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} engines: {node: '>=6'} @@ -815,6 +1044,12 @@ packages: set-cookie-parser@3.1.0: resolution: {integrity: sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==} + shadcn-svelte@1.3.0: + resolution: {integrity: sha512-Pd4ICWTkTks/b2YU4c9vF2XsX1x5HFPRl5bKszS1LcnWS83x+7T4WiIvbWz8Qh9knkcGZ+SCz1+Dmhdq+AYooA==} + hasBin: true + peerDependencies: + svelte: ^5.0.0 + sirv@3.0.2: resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} engines: {node: '>=18'} @@ -823,6 +1058,13 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + style-to-object@1.0.14: + resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + svelte-check@4.6.0: resolution: {integrity: sha512-KhVnDFDSid57mmZtHz8gfW8AAGylOZ0vPnOIzVmAL+urzwK8sBYXRss953gD8T0OdgAQ11mdWhE6uadmtOz8TQ==} engines: {node: '>= 18.0.0'} @@ -831,10 +1073,32 @@ packages: svelte: ^4.0.0 || ^5.0.0-next.0 typescript: '>=5.0.0' + svelte-toolbelt@0.10.6: + resolution: {integrity: sha512-YWuX+RE+CnWYx09yseAe4ZVMM7e7GRFZM6OYWpBKOb++s+SQ8RBIMMe+Bs/CznBMc0QPLjr+vDBxTAkozXsFXQ==} + engines: {node: '>=18', pnpm: '>=8.7.0'} + peerDependencies: + svelte: ^5.30.2 + svelte@5.56.3: resolution: {integrity: sha512-w7JvrM5IFl5cmfbY0TLik9o7mjRUJmRMhOR51tBPu708Gr/MjbGs7VnJnr/B0CaXeI4vtnOh7RKxDr0cwhMdDA==} engines: {node: '>=18'} + tabbable@6.4.0: + resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==} + + tailwind-merge@3.6.0: + resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==} + + tailwind-variants@3.2.2: + resolution: {integrity: sha512-Mi4kHeMTLvKlM98XPnK+7HoBPmf4gygdFmqQPaDivc3DpYS6aIY6KiG/PgThrGvii5YZJqRsPz0aPyhoFzmZgg==} + engines: {node: '>=16.x', pnpm: '>=7.x'} + peerDependencies: + tailwind-merge: '>=3.0.0' + tailwindcss: '*' + peerDependenciesMeta: + tailwind-merge: + optional: true + tailwindcss@4.3.0: resolution: {integrity: sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==} @@ -850,9 +1114,15 @@ packages: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} + tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tw-animate-css@1.4.0: + resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} + typescript@6.0.3: resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} engines: {node: '>=14.17'} @@ -944,7 +1214,9 @@ snapshots: '@floating-ui/utils@0.2.11': {} - '@internationalized/date@3.12.0': + '@fontsource-variable/geist@5.2.9': {} + + '@internationalized/date@3.12.2': dependencies: '@swc/helpers': 0.5.23 @@ -967,6 +1239,58 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@lucide/svelte@1.18.0(svelte@5.56.3)': + dependencies: + svelte: 5.56.3 + + '@napi-rs/canvas-android-arm64@1.0.0': + optional: true + + '@napi-rs/canvas-darwin-arm64@1.0.0': + optional: true + + '@napi-rs/canvas-darwin-x64@1.0.0': + optional: true + + '@napi-rs/canvas-linux-arm-gnueabihf@1.0.0': + optional: true + + '@napi-rs/canvas-linux-arm64-gnu@1.0.0': + optional: true + + '@napi-rs/canvas-linux-arm64-musl@1.0.0': + optional: true + + '@napi-rs/canvas-linux-riscv64-gnu@1.0.0': + optional: true + + '@napi-rs/canvas-linux-x64-gnu@1.0.0': + optional: true + + '@napi-rs/canvas-linux-x64-musl@1.0.0': + optional: true + + '@napi-rs/canvas-win32-arm64-msvc@1.0.0': + optional: true + + '@napi-rs/canvas-win32-x64-msvc@1.0.0': + optional: true + + '@napi-rs/canvas@1.0.0': + optionalDependencies: + '@napi-rs/canvas-android-arm64': 1.0.0 + '@napi-rs/canvas-darwin-arm64': 1.0.0 + '@napi-rs/canvas-darwin-x64': 1.0.0 + '@napi-rs/canvas-linux-arm-gnueabihf': 1.0.0 + '@napi-rs/canvas-linux-arm64-gnu': 1.0.0 + '@napi-rs/canvas-linux-arm64-musl': 1.0.0 + '@napi-rs/canvas-linux-riscv64-gnu': 1.0.0 + '@napi-rs/canvas-linux-x64-gnu': 1.0.0 + '@napi-rs/canvas-linux-x64-musl': 1.0.0 + '@napi-rs/canvas-win32-arm64-msvc': 1.0.0 + '@napi-rs/canvas-win32-x64-msvc': 1.0.0 + optional: true + '@napi-rs/wasm-runtime@1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: '@emnapi/core': 1.10.0 @@ -976,6 +1300,14 @@ snapshots: '@oxc-project/types@0.133.0': {} + '@pdf-lib/standard-fonts@1.0.0': + dependencies: + pako: 1.0.11 + + '@pdf-lib/upng@1.0.1': + dependencies: + pako: 1.0.11 + '@polka/url@1.0.0-next.29': {} '@rolldown/binding-android-arm64@1.0.3': @@ -1029,44 +1361,123 @@ snapshots: '@rolldown/pluginutils@1.0.1': {} - '@skeletonlabs/skeleton-common@4.15.2': {} - - '@skeletonlabs/skeleton-svelte@4.15.2(svelte@5.56.3)': + '@rollup/plugin-commonjs@29.0.3(rollup@4.62.2)': dependencies: - '@internationalized/date': 3.12.0 - '@skeletonlabs/skeleton-common': 4.15.2 - '@zag-js/accordion': 1.39.1 - '@zag-js/avatar': 1.39.1 - '@zag-js/carousel': 1.39.1 - '@zag-js/collapsible': 1.39.1 - '@zag-js/collection': 1.39.1 - '@zag-js/combobox': 1.39.1 - '@zag-js/date-picker': 1.39.1(@internationalized/date@3.12.0) - '@zag-js/dialog': 1.39.1 - '@zag-js/file-upload': 1.39.1 - '@zag-js/floating-panel': 1.39.1 - '@zag-js/listbox': 1.39.1 - '@zag-js/menu': 1.39.1 - '@zag-js/pagination': 1.39.1 - '@zag-js/popover': 1.39.1 - '@zag-js/progress': 1.39.1 - '@zag-js/radio-group': 1.39.1 - '@zag-js/rating-group': 1.39.1 - '@zag-js/slider': 1.39.1 - '@zag-js/steps': 1.39.1 - '@zag-js/svelte': 1.39.1(svelte@5.56.3) - '@zag-js/switch': 1.39.1 - '@zag-js/tabs': 1.39.1 - '@zag-js/tags-input': 1.39.1 - '@zag-js/toast': 1.39.1 - '@zag-js/toggle-group': 1.39.1 - '@zag-js/tooltip': 1.39.1 - '@zag-js/tree-view': 1.39.1 - svelte: 5.56.3 + '@rollup/pluginutils': 5.4.0(rollup@4.62.2) + commondir: 1.0.1 + estree-walker: 2.0.2 + fdir: 6.5.0(picomatch@4.0.4) + is-reference: 1.2.1 + magic-string: 0.30.21 + picomatch: 4.0.4 + optionalDependencies: + rollup: 4.62.2 - '@skeletonlabs/skeleton@4.15.2(tailwindcss@4.3.0)': + '@rollup/plugin-json@6.1.0(rollup@4.62.2)': dependencies: - tailwindcss: 4.3.0 + '@rollup/pluginutils': 5.4.0(rollup@4.62.2) + optionalDependencies: + rollup: 4.62.2 + + '@rollup/plugin-node-resolve@16.0.3(rollup@4.62.2)': + dependencies: + '@rollup/pluginutils': 5.4.0(rollup@4.62.2) + '@types/resolve': 1.20.2 + deepmerge: 4.3.1 + is-module: 1.0.0 + resolve: 1.22.12 + optionalDependencies: + rollup: 4.62.2 + + '@rollup/plugin-replace@6.0.3(rollup@4.62.2)': + dependencies: + '@rollup/pluginutils': 5.4.0(rollup@4.62.2) + magic-string: 0.30.21 + optionalDependencies: + rollup: 4.62.2 + + '@rollup/pluginutils@5.4.0(rollup@4.62.2)': + dependencies: + '@types/estree': 1.0.9 + estree-walker: 2.0.2 + picomatch: 4.0.4 + optionalDependencies: + rollup: 4.62.2 + + '@rollup/rollup-android-arm-eabi@4.62.2': + optional: true + + '@rollup/rollup-android-arm64@4.62.2': + optional: true + + '@rollup/rollup-darwin-arm64@4.62.2': + optional: true + + '@rollup/rollup-darwin-x64@4.62.2': + optional: true + + '@rollup/rollup-freebsd-arm64@4.62.2': + optional: true + + '@rollup/rollup-freebsd-x64@4.62.2': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.62.2': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-x64-musl@4.62.2': + optional: true + + '@rollup/rollup-openbsd-x64@4.62.2': + optional: true + + '@rollup/rollup-openharmony-arm64@4.62.2': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.62.2': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.62.2': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.62.2': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.62.2': + optional: true '@standard-schema/spec@1.1.0': {} @@ -1074,9 +1485,14 @@ snapshots: dependencies: acorn: 8.16.0 - '@sveltejs/adapter-auto@7.0.1(@sveltejs/kit@2.64.0(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3)(vite@8.0.16(jiti@2.7.0)))(svelte@5.56.3)(typescript@6.0.3)(vite@8.0.16(jiti@2.7.0)))': + '@sveltejs/adapter-node@5.5.7(@sveltejs/kit@2.64.0(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3)(vite@8.0.16(jiti@2.7.0)))(svelte@5.56.3)(typescript@6.0.3)(vite@8.0.16(jiti@2.7.0)))': dependencies: + '@rollup/plugin-commonjs': 29.0.3(rollup@4.62.2) + '@rollup/plugin-json': 6.1.0(rollup@4.62.2) + '@rollup/plugin-node-resolve': 16.0.3(rollup@4.62.2) + '@rollup/plugin-replace': 6.0.3(rollup@4.62.2) '@sveltejs/kit': 2.64.0(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3)(vite@8.0.16(jiti@2.7.0)))(svelte@5.56.3)(typescript@6.0.3)(vite@8.0.16(jiti@2.7.0)) + rollup: 4.62.2 '@sveltejs/kit@2.64.0(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3)(vite@8.0.16(jiti@2.7.0)))(svelte@5.56.3)(typescript@6.0.3)(vite@8.0.16(jiti@2.7.0))': dependencies: @@ -1200,357 +1616,51 @@ snapshots: '@types/estree@1.0.9': {} + '@types/resolve@1.20.2': {} + '@types/trusted-types@2.0.7': {} - '@zag-js/accordion@1.39.1': - dependencies: - '@zag-js/anatomy': 1.39.1 - '@zag-js/core': 1.39.1 - '@zag-js/dom-query': 1.39.1 - '@zag-js/types': 1.39.1 - '@zag-js/utils': 1.39.1 - - '@zag-js/anatomy@1.39.1': {} - - '@zag-js/aria-hidden@1.39.1': - dependencies: - '@zag-js/dom-query': 1.39.1 - - '@zag-js/auto-resize@1.39.1': - dependencies: - '@zag-js/dom-query': 1.39.1 - - '@zag-js/avatar@1.39.1': - dependencies: - '@zag-js/anatomy': 1.39.1 - '@zag-js/core': 1.39.1 - '@zag-js/dom-query': 1.39.1 - '@zag-js/types': 1.39.1 - '@zag-js/utils': 1.39.1 - - '@zag-js/carousel@1.39.1': - dependencies: - '@zag-js/anatomy': 1.39.1 - '@zag-js/core': 1.39.1 - '@zag-js/dom-query': 1.39.1 - '@zag-js/scroll-snap': 1.39.1 - '@zag-js/types': 1.39.1 - '@zag-js/utils': 1.39.1 - - '@zag-js/collapsible@1.39.1': - dependencies: - '@zag-js/anatomy': 1.39.1 - '@zag-js/core': 1.39.1 - '@zag-js/dom-query': 1.39.1 - '@zag-js/types': 1.39.1 - '@zag-js/utils': 1.39.1 - - '@zag-js/collection@1.39.1': - dependencies: - '@zag-js/utils': 1.39.1 - - '@zag-js/combobox@1.39.1': - dependencies: - '@zag-js/anatomy': 1.39.1 - '@zag-js/collection': 1.39.1 - '@zag-js/core': 1.39.1 - '@zag-js/dismissable': 1.39.1 - '@zag-js/dom-query': 1.39.1 - '@zag-js/focus-visible': 1.39.1 - '@zag-js/live-region': 1.39.1 - '@zag-js/popper': 1.39.1 - '@zag-js/types': 1.39.1 - '@zag-js/utils': 1.39.1 - - '@zag-js/core@1.39.1': - dependencies: - '@zag-js/dom-query': 1.39.1 - '@zag-js/utils': 1.39.1 - - '@zag-js/date-picker@1.39.1(@internationalized/date@3.12.0)': - dependencies: - '@internationalized/date': 3.12.0 - '@zag-js/anatomy': 1.39.1 - '@zag-js/core': 1.39.1 - '@zag-js/date-utils': 1.39.1(@internationalized/date@3.12.0) - '@zag-js/dismissable': 1.39.1 - '@zag-js/dom-query': 1.39.1 - '@zag-js/live-region': 1.39.1 - '@zag-js/popper': 1.39.1 - '@zag-js/types': 1.39.1 - '@zag-js/utils': 1.39.1 - - '@zag-js/date-utils@1.39.1(@internationalized/date@3.12.0)': - dependencies: - '@internationalized/date': 3.12.0 - - '@zag-js/dialog@1.39.1': - dependencies: - '@zag-js/anatomy': 1.39.1 - '@zag-js/aria-hidden': 1.39.1 - '@zag-js/core': 1.39.1 - '@zag-js/dismissable': 1.39.1 - '@zag-js/dom-query': 1.39.1 - '@zag-js/focus-trap': 1.39.1 - '@zag-js/remove-scroll': 1.39.1 - '@zag-js/types': 1.39.1 - '@zag-js/utils': 1.39.1 - - '@zag-js/dismissable@1.39.1': - dependencies: - '@zag-js/dom-query': 1.39.1 - '@zag-js/interact-outside': 1.39.1 - '@zag-js/utils': 1.39.1 - - '@zag-js/dom-query@1.39.1': - dependencies: - '@zag-js/types': 1.39.1 - - '@zag-js/file-upload@1.39.1': - dependencies: - '@zag-js/anatomy': 1.39.1 - '@zag-js/core': 1.39.1 - '@zag-js/dom-query': 1.39.1 - '@zag-js/file-utils': 1.39.1 - '@zag-js/i18n-utils': 1.39.1 - '@zag-js/types': 1.39.1 - '@zag-js/utils': 1.39.1 - - '@zag-js/file-utils@1.39.1': - dependencies: - '@zag-js/i18n-utils': 1.39.1 - - '@zag-js/floating-panel@1.39.1': - dependencies: - '@zag-js/anatomy': 1.39.1 - '@zag-js/core': 1.39.1 - '@zag-js/dom-query': 1.39.1 - '@zag-js/popper': 1.39.1 - '@zag-js/rect-utils': 1.39.1 - '@zag-js/store': 1.39.1 - '@zag-js/types': 1.39.1 - '@zag-js/utils': 1.39.1 - - '@zag-js/focus-trap@1.39.1': - dependencies: - '@zag-js/dom-query': 1.39.1 - - '@zag-js/focus-visible@1.39.1': - dependencies: - '@zag-js/dom-query': 1.39.1 - - '@zag-js/i18n-utils@1.39.1': - dependencies: - '@zag-js/dom-query': 1.39.1 - - '@zag-js/interact-outside@1.39.1': - dependencies: - '@zag-js/dom-query': 1.39.1 - '@zag-js/utils': 1.39.1 - - '@zag-js/listbox@1.39.1': - dependencies: - '@zag-js/anatomy': 1.39.1 - '@zag-js/collection': 1.39.1 - '@zag-js/core': 1.39.1 - '@zag-js/dom-query': 1.39.1 - '@zag-js/focus-visible': 1.39.1 - '@zag-js/types': 1.39.1 - '@zag-js/utils': 1.39.1 - - '@zag-js/live-region@1.39.1': {} - - '@zag-js/menu@1.39.1': - dependencies: - '@zag-js/anatomy': 1.39.1 - '@zag-js/core': 1.39.1 - '@zag-js/dismissable': 1.39.1 - '@zag-js/dom-query': 1.39.1 - '@zag-js/focus-visible': 1.39.1 - '@zag-js/popper': 1.39.1 - '@zag-js/rect-utils': 1.39.1 - '@zag-js/types': 1.39.1 - '@zag-js/utils': 1.39.1 - - '@zag-js/pagination@1.39.1': - dependencies: - '@zag-js/anatomy': 1.39.1 - '@zag-js/core': 1.39.1 - '@zag-js/dom-query': 1.39.1 - '@zag-js/types': 1.39.1 - '@zag-js/utils': 1.39.1 - - '@zag-js/popover@1.39.1': - dependencies: - '@zag-js/anatomy': 1.39.1 - '@zag-js/aria-hidden': 1.39.1 - '@zag-js/core': 1.39.1 - '@zag-js/dismissable': 1.39.1 - '@zag-js/dom-query': 1.39.1 - '@zag-js/focus-trap': 1.39.1 - '@zag-js/popper': 1.39.1 - '@zag-js/remove-scroll': 1.39.1 - '@zag-js/types': 1.39.1 - '@zag-js/utils': 1.39.1 - - '@zag-js/popper@1.39.1': - dependencies: - '@floating-ui/dom': 1.7.6 - '@zag-js/dom-query': 1.39.1 - '@zag-js/utils': 1.39.1 - - '@zag-js/progress@1.39.1': - dependencies: - '@zag-js/anatomy': 1.39.1 - '@zag-js/core': 1.39.1 - '@zag-js/dom-query': 1.39.1 - '@zag-js/types': 1.39.1 - '@zag-js/utils': 1.39.1 - - '@zag-js/radio-group@1.39.1': - dependencies: - '@zag-js/anatomy': 1.39.1 - '@zag-js/core': 1.39.1 - '@zag-js/dom-query': 1.39.1 - '@zag-js/focus-visible': 1.39.1 - '@zag-js/types': 1.39.1 - '@zag-js/utils': 1.39.1 - - '@zag-js/rating-group@1.39.1': - dependencies: - '@zag-js/anatomy': 1.39.1 - '@zag-js/core': 1.39.1 - '@zag-js/dom-query': 1.39.1 - '@zag-js/types': 1.39.1 - '@zag-js/utils': 1.39.1 - - '@zag-js/rect-utils@1.39.1': {} - - '@zag-js/remove-scroll@1.39.1': - dependencies: - '@zag-js/dom-query': 1.39.1 - - '@zag-js/scroll-snap@1.39.1': - dependencies: - '@zag-js/dom-query': 1.39.1 - - '@zag-js/slider@1.39.1': - dependencies: - '@zag-js/anatomy': 1.39.1 - '@zag-js/core': 1.39.1 - '@zag-js/dom-query': 1.39.1 - '@zag-js/types': 1.39.1 - '@zag-js/utils': 1.39.1 - - '@zag-js/steps@1.39.1': - dependencies: - '@zag-js/anatomy': 1.39.1 - '@zag-js/core': 1.39.1 - '@zag-js/dom-query': 1.39.1 - '@zag-js/types': 1.39.1 - '@zag-js/utils': 1.39.1 - - '@zag-js/store@1.39.1': - dependencies: - proxy-compare: 3.0.1 - - '@zag-js/svelte@1.39.1(svelte@5.56.3)': - dependencies: - '@zag-js/core': 1.39.1 - '@zag-js/types': 1.39.1 - '@zag-js/utils': 1.39.1 - svelte: 5.56.3 - - '@zag-js/switch@1.39.1': - dependencies: - '@zag-js/anatomy': 1.39.1 - '@zag-js/core': 1.39.1 - '@zag-js/dom-query': 1.39.1 - '@zag-js/focus-visible': 1.39.1 - '@zag-js/types': 1.39.1 - '@zag-js/utils': 1.39.1 - - '@zag-js/tabs@1.39.1': - dependencies: - '@zag-js/anatomy': 1.39.1 - '@zag-js/core': 1.39.1 - '@zag-js/dom-query': 1.39.1 - '@zag-js/types': 1.39.1 - '@zag-js/utils': 1.39.1 - - '@zag-js/tags-input@1.39.1': - dependencies: - '@zag-js/anatomy': 1.39.1 - '@zag-js/auto-resize': 1.39.1 - '@zag-js/core': 1.39.1 - '@zag-js/dom-query': 1.39.1 - '@zag-js/interact-outside': 1.39.1 - '@zag-js/live-region': 1.39.1 - '@zag-js/types': 1.39.1 - '@zag-js/utils': 1.39.1 - - '@zag-js/toast@1.39.1': - dependencies: - '@zag-js/anatomy': 1.39.1 - '@zag-js/core': 1.39.1 - '@zag-js/dismissable': 1.39.1 - '@zag-js/dom-query': 1.39.1 - '@zag-js/types': 1.39.1 - '@zag-js/utils': 1.39.1 - - '@zag-js/toggle-group@1.39.1': - dependencies: - '@zag-js/anatomy': 1.39.1 - '@zag-js/core': 1.39.1 - '@zag-js/dom-query': 1.39.1 - '@zag-js/types': 1.39.1 - '@zag-js/utils': 1.39.1 - - '@zag-js/tooltip@1.39.1': - dependencies: - '@zag-js/anatomy': 1.39.1 - '@zag-js/core': 1.39.1 - '@zag-js/dom-query': 1.39.1 - '@zag-js/focus-visible': 1.39.1 - '@zag-js/popper': 1.39.1 - '@zag-js/types': 1.39.1 - '@zag-js/utils': 1.39.1 - - '@zag-js/tree-view@1.39.1': - dependencies: - '@zag-js/anatomy': 1.39.1 - '@zag-js/collection': 1.39.1 - '@zag-js/core': 1.39.1 - '@zag-js/dom-query': 1.39.1 - '@zag-js/types': 1.39.1 - '@zag-js/utils': 1.39.1 - - '@zag-js/types@1.39.1': - dependencies: - csstype: 3.2.3 - - '@zag-js/utils@1.39.1': {} - acorn@8.16.0: {} aria-query@5.3.1: {} axobject-query@4.1.0: {} + bits-ui@2.18.1(@internationalized/date@3.12.2)(@sveltejs/kit@2.64.0(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3)(vite@8.0.16(jiti@2.7.0)))(svelte@5.56.3)(typescript@6.0.3)(vite@8.0.16(jiti@2.7.0)))(svelte@5.56.3): + dependencies: + '@floating-ui/core': 1.7.5 + '@floating-ui/dom': 1.7.6 + '@internationalized/date': 3.12.2 + esm-env: 1.2.2 + runed: 0.35.1(@sveltejs/kit@2.64.0(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3)(vite@8.0.16(jiti@2.7.0)))(svelte@5.56.3)(typescript@6.0.3)(vite@8.0.16(jiti@2.7.0)))(svelte@5.56.3) + svelte: 5.56.3 + svelte-toolbelt: 0.10.6(@sveltejs/kit@2.64.0(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3)(vite@8.0.16(jiti@2.7.0)))(svelte@5.56.3)(typescript@6.0.3)(vite@8.0.16(jiti@2.7.0)))(svelte@5.56.3) + tabbable: 6.4.0 + transitivePeerDependencies: + - '@sveltejs/kit' + chokidar@4.0.3: dependencies: readdirp: 4.1.2 + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + clsx@2.1.1: {} + commander@14.0.3: {} + + commondir@1.0.1: {} + cookie@0.6.0: {} cssesc@3.0.0: {} - csstype@3.2.3: {} - deepmerge@4.3.1: {} + dequal@2.0.3: {} + detect-libc@2.1.2: {} devalue@5.8.1: {} @@ -1560,21 +1670,45 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.3 + es-errors@1.3.0: {} + esm-env@1.2.2: {} esrap@2.2.11: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + estree-walker@2.0.2: {} + fdir@6.5.0(picomatch@4.0.4): optionalDependencies: picomatch: 4.0.4 + fflate@0.8.3: {} + fsevents@2.3.3: optional: true + function-bind@1.1.2: {} + graceful-fs@4.2.11: {} + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + inline-style-parser@0.2.7: {} + + is-core-module@2.16.2: + dependencies: + hasown: 2.0.4 + + is-module@1.0.0: {} + + is-reference@1.2.1: + dependencies: + '@types/estree': 1.0.9 + is-reference@3.0.3: dependencies: '@types/estree': 1.0.9 @@ -1634,6 +1768,8 @@ snapshots: locate-character@3.0.0: {} + lz-string@1.5.0: {} + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -1646,8 +1782,25 @@ snapshots: nanoid@3.3.12: {} + node-fetch-native@1.6.7: {} + obug@2.1.2: {} + pako@1.0.11: {} + + path-parse@1.0.7: {} + + pdf-lib@1.17.1: + dependencies: + '@pdf-lib/standard-fonts': 1.0.0 + '@pdf-lib/upng': 1.0.1 + pako: 1.0.11 + tslib: 1.14.1 + + pdfjs-dist@6.0.227: + optionalDependencies: + '@napi-rs/canvas': 1.0.0 + picocolors@1.1.1: {} picomatch@4.0.4: {} @@ -1676,10 +1829,15 @@ snapshots: prettier@3.8.4: {} - proxy-compare@3.0.1: {} - readdirp@4.1.2: {} + resolve@1.22.12: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.16.2 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + rolldown@1.0.3: dependencies: '@oxc-project/types': 0.133.0 @@ -1701,12 +1859,60 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.0.3 '@rolldown/binding-win32-x64-msvc': 1.0.3 + rollup@4.62.2: + dependencies: + '@types/estree': 1.0.9 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.62.2 + '@rollup/rollup-android-arm64': 4.62.2 + '@rollup/rollup-darwin-arm64': 4.62.2 + '@rollup/rollup-darwin-x64': 4.62.2 + '@rollup/rollup-freebsd-arm64': 4.62.2 + '@rollup/rollup-freebsd-x64': 4.62.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.62.2 + '@rollup/rollup-linux-arm-musleabihf': 4.62.2 + '@rollup/rollup-linux-arm64-gnu': 4.62.2 + '@rollup/rollup-linux-arm64-musl': 4.62.2 + '@rollup/rollup-linux-loong64-gnu': 4.62.2 + '@rollup/rollup-linux-loong64-musl': 4.62.2 + '@rollup/rollup-linux-ppc64-gnu': 4.62.2 + '@rollup/rollup-linux-ppc64-musl': 4.62.2 + '@rollup/rollup-linux-riscv64-gnu': 4.62.2 + '@rollup/rollup-linux-riscv64-musl': 4.62.2 + '@rollup/rollup-linux-s390x-gnu': 4.62.2 + '@rollup/rollup-linux-x64-gnu': 4.62.2 + '@rollup/rollup-linux-x64-musl': 4.62.2 + '@rollup/rollup-openbsd-x64': 4.62.2 + '@rollup/rollup-openharmony-arm64': 4.62.2 + '@rollup/rollup-win32-arm64-msvc': 4.62.2 + '@rollup/rollup-win32-ia32-msvc': 4.62.2 + '@rollup/rollup-win32-x64-gnu': 4.62.2 + '@rollup/rollup-win32-x64-msvc': 4.62.2 + fsevents: 2.3.3 + + runed@0.35.1(@sveltejs/kit@2.64.0(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3)(vite@8.0.16(jiti@2.7.0)))(svelte@5.56.3)(typescript@6.0.3)(vite@8.0.16(jiti@2.7.0)))(svelte@5.56.3): + dependencies: + dequal: 2.0.3 + esm-env: 1.2.2 + lz-string: 1.5.0 + svelte: 5.56.3 + optionalDependencies: + '@sveltejs/kit': 2.64.0(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3)(vite@8.0.16(jiti@2.7.0)))(svelte@5.56.3)(typescript@6.0.3)(vite@8.0.16(jiti@2.7.0)) + sade@1.8.1: dependencies: mri: 1.2.0 set-cookie-parser@3.1.0: {} + shadcn-svelte@1.3.0(svelte@5.56.3): + dependencies: + commander: 14.0.3 + node-fetch-native: 1.6.7 + postcss: 8.5.15 + svelte: 5.56.3 + tailwind-merge: 3.6.0 + sirv@3.0.2: dependencies: '@polka/url': 1.0.0-next.29 @@ -1715,6 +1921,12 @@ snapshots: source-map-js@1.2.1: {} + style-to-object@1.0.14: + dependencies: + inline-style-parser: 0.2.7 + + supports-preserve-symlinks-flag@1.0.0: {} + svelte-check@4.6.0(picomatch@4.0.4)(svelte@5.56.3)(typescript@6.0.3): dependencies: '@jridgewell/trace-mapping': 0.3.31 @@ -1728,6 +1940,15 @@ snapshots: transitivePeerDependencies: - picomatch + svelte-toolbelt@0.10.6(@sveltejs/kit@2.64.0(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3)(vite@8.0.16(jiti@2.7.0)))(svelte@5.56.3)(typescript@6.0.3)(vite@8.0.16(jiti@2.7.0)))(svelte@5.56.3): + dependencies: + clsx: 2.1.1 + runed: 0.35.1(@sveltejs/kit@2.64.0(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3)(vite@8.0.16(jiti@2.7.0)))(svelte@5.56.3)(typescript@6.0.3)(vite@8.0.16(jiti@2.7.0)))(svelte@5.56.3) + style-to-object: 1.0.14 + svelte: 5.56.3 + transitivePeerDependencies: + - '@sveltejs/kit' + svelte@5.56.3: dependencies: '@jridgewell/remapping': 2.3.5 @@ -1749,6 +1970,16 @@ snapshots: transitivePeerDependencies: - '@typescript-eslint/types' + tabbable@6.4.0: {} + + tailwind-merge@3.6.0: {} + + tailwind-variants@3.2.2(tailwind-merge@3.6.0)(tailwindcss@4.3.0): + dependencies: + tailwindcss: 4.3.0 + optionalDependencies: + tailwind-merge: 3.6.0 + tailwindcss@4.3.0: {} tapable@2.3.3: {} @@ -1760,8 +1991,12 @@ snapshots: totalist@3.0.1: {} + tslib@1.14.1: {} + tslib@2.8.1: {} + tw-animate-css@1.4.0: {} + typescript@6.0.3: {} util-deprecate@1.0.2: {} diff --git a/src/lib/actions/ripple.ts b/src/lib/actions/ripple.ts new file mode 100644 index 0000000..c934a96 --- /dev/null +++ b/src/lib/actions/ripple.ts @@ -0,0 +1,31 @@ +export function ripple(node: HTMLElement) { + function handleClick(event: MouseEvent) { + const circle = document.createElement("span"); + + const rect = node.getBoundingClientRect(); + const size = Math.max(rect.width, rect.height); + + circle.style.width = `${size}px`; + circle.style.height = `${size}px`; + + circle.style.left = `${event.clientX - rect.left - size / 2}px`; + circle.style.top = `${event.clientY - rect.top - size / 2}px`; + + circle.className = + "absolute rounded-full bg-white/30 animate-ripple pointer-events-none"; + + node.appendChild(circle); + + circle.addEventListener("animationend", () => { + circle.remove(); + }); + } + + node.addEventListener("click", handleClick); + + return { + destroy() { + node.removeEventListener("click", handleClick); + } + }; +} \ No newline at end of file diff --git a/src/lib/components/AttachmentPreview.svelte b/src/lib/components/AttachmentPreview.svelte new file mode 100644 index 0000000..eadfa78 --- /dev/null +++ b/src/lib/components/AttachmentPreview.svelte @@ -0,0 +1,129 @@ + + +
      + {#if previewError} +
      + {previewError} +
      + {:else if imageUrl} + {attachment.name} + {:else} +
      + Vorschau wird erstellt +
      + {/if} +
      diff --git a/src/lib/components/Button.svelte b/src/lib/components/Button.svelte deleted file mode 100644 index 9f09578..0000000 --- a/src/lib/components/Button.svelte +++ /dev/null @@ -1,38 +0,0 @@ - - - diff --git a/src/lib/components/Card.svelte b/src/lib/components/Card.svelte deleted file mode 100644 index 7b26b8e..0000000 --- a/src/lib/components/Card.svelte +++ /dev/null @@ -1,23 +0,0 @@ - - -
      - {#if title} -
      {@render title()}
      - {/if} - {#if content} -
      {@render content()}
      - {/if} - {#if footer} -
      {@render footer()}
      - {/if} -
      \ No newline at end of file diff --git a/src/lib/components/ProcessedZipArchiveEditor.svelte b/src/lib/components/ProcessedZipArchiveEditor.svelte new file mode 100644 index 0000000..086f9d6 --- /dev/null +++ b/src/lib/components/ProcessedZipArchiveEditor.svelte @@ -0,0 +1,502 @@ + + + + +
      +
      +
      + +

      + {looseAttachments.length} lose Datei{looseAttachments.length === 1 ? '' : 'en'} + {#if hasSubDocuments} + · {subDocuments.length} Teildokument{subDocuments.length === 1 ? '' : 'e'} + {/if} +

      +
      + +
      + + + {#if hasSubDocuments} +
      + {#each [{ value: 'single', label: 'Eine PDF' }, { value: 'separate', label: 'Getrennt' }] as option (option.value)} + + {/each} +
      + {/if} + + +
      +
      + + {#if mergeError} +
      + {mergeError} +
      + {/if} + + {#if selectionMode && selectedPaths.size > 0} +
      + {selectedPaths.size} Datei{selectedPaths.size === 1 ? '' : 'en'} ausgewählt + +
      + {/if} + +
        + {#each looseAttachments as attachment, index (attachment.path)} +
      • handleLooseDragStart(index, event)} + ondragover={(event) => handleLooseDragOver(index, event)} + ondrop={(event) => handleLooseDrop(index, event)} + ondragend={handleLooseDragEnd} + > + {#if selectionMode} + + {:else} +
        + + +
        + {attachment.name} +
        +
        + + {/if} +
      • + {/each} +
      + + {#if looseAttachments.length === 0 && subDocuments.length === 0} +
      + Keine Dateien in diesem Archiv. Lade ein anderes ZIP, um weiterzuarbeiten. +
      + {/if} + + {#if hasSubDocuments} +
      + {#each subDocuments as subDocument, blockIndex (subDocument.id)} + dissolveSubDocumentBlock(subDocument.id)} + onAttachmentMove={handleAttachmentMove} + onBlockDragStart={handleBlockDragStart} + onBlockDragOver={handleBlockDragOver} + onBlockDrop={handleBlockDrop} + onBlockDragEnd={handleBlockDragEnd} + /> + {/each} +
      + {/if} +
      diff --git a/src/lib/components/SubDocumentEditor.svelte b/src/lib/components/SubDocumentEditor.svelte new file mode 100644 index 0000000..81164fe --- /dev/null +++ b/src/lib/components/SubDocumentEditor.svelte @@ -0,0 +1,286 @@ + + +
      +
      + + + + + + {attachments.length} Datei{attachments.length === 1 ? '' : 'en'} + + + +
      + + {#if attachments.length === 0} +
        { + if (event.dataTransfer?.types.includes(ATTACHMENT_MIME)) { + event.preventDefault(); + } + }} + ondrop={(event) => { + const source = parseAttachmentPayload(event); + + if (!source) { + return; + } + + event.preventDefault(); + + const target: AttachmentLocation = { + kind: 'subDocument', + id: subDocument.id, + index: 0 + }; + + onAttachmentMove(source, target); + dragIndex = null; + dropIndex = null; + }} + > +
      • + Dieses Teildokument ist leer. Ziehe Dateien hierher, um sie hinzuzufügen. +
      • +
      + {:else} +
        + {#each attachments as attachment, index (attachment.path)} +
      • handleCardDragStart(index, event)} + ondragover={(event) => handleCardDragOver(index, event)} + ondrop={(event) => handleCardDrop(index, event)} + ondragend={handleCardDragEnd} + > +
        + + +
        + {attachment.name} +
        +
        + +
      • + {/each} +
      + {/if} +
      diff --git a/src/lib/components/ZipDropzone.svelte b/src/lib/components/ZipDropzone.svelte index 213f926..ae4e919 100644 --- a/src/lib/components/ZipDropzone.svelte +++ b/src/lib/components/ZipDropzone.svelte @@ -62,7 +62,7 @@ diff --git a/src/lib/components/icons/IconChain.svelte b/src/lib/components/icons/IconChain.svelte new file mode 100644 index 0000000..a60f9fb --- /dev/null +++ b/src/lib/components/icons/IconChain.svelte @@ -0,0 +1,8 @@ + + + + + + + diff --git a/src/lib/components/icons/IconFile.svelte b/src/lib/components/icons/IconFile.svelte new file mode 100644 index 0000000..413e422 --- /dev/null +++ b/src/lib/components/icons/IconFile.svelte @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/src/lib/components/icons/IconMenu.svelte b/src/lib/components/icons/IconMenu.svelte new file mode 100644 index 0000000..68ca63d --- /dev/null +++ b/src/lib/components/icons/IconMenu.svelte @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/src/lib/components/icons/IconNeedle.svelte b/src/lib/components/icons/IconNeedle.svelte new file mode 100644 index 0000000..20ce66b --- /dev/null +++ b/src/lib/components/icons/IconNeedle.svelte @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/src/lib/components/icons/IconOffice.svelte b/src/lib/components/icons/IconOffice.svelte new file mode 100644 index 0000000..035e0ec --- /dev/null +++ b/src/lib/components/icons/IconOffice.svelte @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + diff --git a/src/lib/components/icons/IconPerson.svelte b/src/lib/components/icons/IconPerson.svelte new file mode 100644 index 0000000..a11a6c0 --- /dev/null +++ b/src/lib/components/icons/IconPerson.svelte @@ -0,0 +1,4 @@ + + + + diff --git a/src/lib/components/icons/IconSettings.svelte b/src/lib/components/icons/IconSettings.svelte new file mode 100644 index 0000000..21b9306 --- /dev/null +++ b/src/lib/components/icons/IconSettings.svelte @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/src/lib/components/icons/IconTag.svelte b/src/lib/components/icons/IconTag.svelte new file mode 100644 index 0000000..b732550 --- /dev/null +++ b/src/lib/components/icons/IconTag.svelte @@ -0,0 +1,4 @@ + + + + diff --git a/src/lib/components/icons/IconTrash.svelte b/src/lib/components/icons/IconTrash.svelte new file mode 100644 index 0000000..60a4e0a --- /dev/null +++ b/src/lib/components/icons/IconTrash.svelte @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/src/lib/components/icons/IconUnknown.svelte b/src/lib/components/icons/IconUnknown.svelte new file mode 100644 index 0000000..1491e90 --- /dev/null +++ b/src/lib/components/icons/IconUnknown.svelte @@ -0,0 +1,7 @@ + + + + + + diff --git a/src/lib/components/ui/button.svelte b/src/lib/components/ui/button.svelte new file mode 100644 index 0000000..71b850a --- /dev/null +++ b/src/lib/components/ui/button.svelte @@ -0,0 +1,89 @@ + + + + +{#if href} + + {@render children?.()} + {#if icon} + {@const Icon = icon} + + {/if} + +{:else} + +{/if} diff --git a/src/lib/components/ui/card/card-action.svelte b/src/lib/components/ui/card/card-action.svelte new file mode 100644 index 0000000..7c48844 --- /dev/null +++ b/src/lib/components/ui/card/card-action.svelte @@ -0,0 +1,23 @@ + + +
      + {@render children?.()} +
      diff --git a/src/lib/components/ui/card/card-content.svelte b/src/lib/components/ui/card/card-content.svelte new file mode 100644 index 0000000..4f60ee3 --- /dev/null +++ b/src/lib/components/ui/card/card-content.svelte @@ -0,0 +1,20 @@ + + +
      + {@render children?.()} +
      diff --git a/src/lib/components/ui/card/card-description.svelte b/src/lib/components/ui/card/card-description.svelte new file mode 100644 index 0000000..9b20ac7 --- /dev/null +++ b/src/lib/components/ui/card/card-description.svelte @@ -0,0 +1,20 @@ + + +

      + {@render children?.()} +

      diff --git a/src/lib/components/ui/card/card-footer.svelte b/src/lib/components/ui/card/card-footer.svelte new file mode 100644 index 0000000..1efff6d --- /dev/null +++ b/src/lib/components/ui/card/card-footer.svelte @@ -0,0 +1,20 @@ + + +
      + {@render children?.()} +
      diff --git a/src/lib/components/ui/card/card-header.svelte b/src/lib/components/ui/card/card-header.svelte new file mode 100644 index 0000000..7eb69e1 --- /dev/null +++ b/src/lib/components/ui/card/card-header.svelte @@ -0,0 +1,23 @@ + + +
      + {@render children?.()} +
      diff --git a/src/lib/components/ui/card/card-title.svelte b/src/lib/components/ui/card/card-title.svelte new file mode 100644 index 0000000..1523fc5 --- /dev/null +++ b/src/lib/components/ui/card/card-title.svelte @@ -0,0 +1,20 @@ + + +
      + {@render children?.()} +
      diff --git a/src/lib/components/ui/card/card.svelte b/src/lib/components/ui/card/card.svelte new file mode 100644 index 0000000..2b4f81a --- /dev/null +++ b/src/lib/components/ui/card/card.svelte @@ -0,0 +1,22 @@ + + +
      img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl group/card flex flex-col", className)} + {...restProps} +> + {@render children?.()} +
      diff --git a/src/lib/components/ui/card/index.ts b/src/lib/components/ui/card/index.ts new file mode 100644 index 0000000..4d3fce4 --- /dev/null +++ b/src/lib/components/ui/card/index.ts @@ -0,0 +1,25 @@ +import Root from "./card.svelte"; +import Content from "./card-content.svelte"; +import Description from "./card-description.svelte"; +import Footer from "./card-footer.svelte"; +import Header from "./card-header.svelte"; +import Title from "./card-title.svelte"; +import Action from "./card-action.svelte"; + +export { + Root, + Content, + Description, + Footer, + Header, + Title, + Action, + // + Root as Card, + Content as CardContent, + Description as CardDescription, + Footer as CardFooter, + Header as CardHeader, + Title as CardTitle, + Action as CardAction, +}; diff --git a/src/lib/nom-theme.css b/src/lib/nom-theme.css deleted file mode 100644 index 67d7e22..0000000 --- a/src/lib/nom-theme.css +++ /dev/null @@ -1,108 +0,0 @@ -[data-theme='nominandum'] { - --text-scaling: 1.067; - - --base-font-color: var(--color-surface-900); - --base-font-color-dark: var(--color-surface-50); - --base-font-family: Nunito, Helvetica Neue, sans-serif; - --base-font-size: inherit; - --base-line-height: 1.5; - --base-font-weight: 400; - - --heading-font-color: var(--color-primary-900); - --heading-font-color-dark: var(--color-primary-900); - --heading-font-family: Nunito, Helvetica Neue, sans-serif; - --heading-font-weight: 400; - - --anchor-font-color: var(--color-primary-600); - --anchor-font-color-dark: var(--color-primary-300); - --anchor-text-decoration: none; - --anchor-text-decoration-hover: underline; - - --spacing: 0.25rem; - --radius-base: 0.5rem; - --radius-container: 24px; - - --default-border-width: 1px; - --default-divide-width: 1px; - --default-ring-width: 2px; - - --body-background-color: #EEF4F6; - --body-background-color-dark: #0f172a; - - /* Primary: Material Blue */ - /* Very Light / Ghostly */ - --color-primary-50: oklch(96.8% 0.010 253deg); - --color-primary-100: oklch(92.8% 0.020 253deg); - --color-primary-200: oklch(86.5% 0.040 253deg); - --color-primary-300: oklch(77.5% 0.060 253deg); - --color-primary-400: oklch(68.5% 0.080 253deg); - - /* Mid-range / Muted Tones */ - --color-primary-500: oklch(59.5% 0.090 253deg); - --color-primary-600: oklch(52.5% 0.095 253deg); - --color-primary-700: oklch(45.5% 0.095 253deg); - - /* Dark Range (Matching your base color) */ - --color-primary-800: oklch(38.5% 0.090 253deg); - --color-primary-900: oklch(31.5% 0.090 253deg); /* YOUR BASE COLOR */ - --color-primary-950: oklch(23.5% 0.070 253deg); - - --color-primary-contrast-dark: var(--color-primary-950); - --color-primary-contrast-light: white; - --color-primary-contrast-50: var(--color-primary-contrast-dark); - --color-primary-contrast-100: var(--color-primary-contrast-dark); - --color-primary-contrast-200: var(--color-primary-contrast-dark); - --color-primary-contrast-300: var(--color-primary-contrast-dark); - --color-primary-contrast-400: white; - --color-primary-contrast-500: white; - --color-primary-contrast-600: white; - --color-primary-contrast-700: white; - --color-primary-contrast-800: white; - --color-primary-contrast-900: white; - --color-primary-contrast-950: white; - - /* Secondary: dezentes Blau-Grau */ - --color-secondary-50: oklch(97% 0.01 250deg); - --color-secondary-100: oklch(93.5% 0.015 250deg); - --color-secondary-200: oklch(88.5% 0.022 250deg); - --color-secondary-300: oklch(80% 0.035 250deg); - --color-secondary-400: oklch(68% 0.045 250deg); - --color-secondary-500: oklch(56% 0.055 250deg); - --color-secondary-600: oklch(48% 0.05 250deg); - --color-secondary-700: oklch(40% 0.045 250deg); - --color-secondary-800: oklch(32% 0.04 250deg); - --color-secondary-900: oklch(24% 0.035 250deg); - --color-secondary-950: oklch(17% 0.03 250deg); - - --color-secondary-contrast-dark: var(--color-secondary-950); - --color-secondary-contrast-light: white; - - /* Tertiary: Akzent Cyan */ - --color-tertiary-50: oklch(97% 0.025 220deg); - --color-tertiary-100: oklch(93% 0.045 220deg); - --color-tertiary-200: oklch(87% 0.07 220deg); - --color-tertiary-300: oklch(78% 0.105 220deg); - --color-tertiary-400: oklch(69% 0.135 220deg); - --color-tertiary-500: oklch(60% 0.15 220deg); - --color-tertiary-600: oklch(52% 0.135 220deg); - --color-tertiary-700: oklch(44% 0.115 220deg); - --color-tertiary-800: oklch(36% 0.09 220deg); - --color-tertiary-900: oklch(29% 0.065 220deg); - --color-tertiary-950: oklch(21% 0.045 220deg); - - /* Surface: Material-nahe kühle Grautöne */ - --color-surface-50: oklch(98.5% 0.004 255deg); - --color-surface-100: oklch(96.5% 0.006 255deg); - --color-surface-200: oklch(92.5% 0.01 255deg); - --color-surface-300: oklch(86.5% 0.015 255deg); - --color-surface-400: oklch(74% 0.02 255deg); - --color-surface-500: oklch(62% 0.025 255deg); - --color-surface-600: oklch(50% 0.025 255deg); - --color-surface-700: oklch(39% 0.025 255deg); - --color-surface-800: oklch(29% 0.025 255deg); - --color-surface-900: oklch(21% 0.025 255deg); - --color-surface-950: oklch(14% 0.025 255deg); - - --color-surface-contrast-dark: var(--color-surface-950); - --color-surface-contrast-light: white; -} \ No newline at end of file diff --git a/src/lib/services/xml-reading.service.ts b/src/lib/services/xml-reading.service.ts new file mode 100644 index 0000000..a35f855 --- /dev/null +++ b/src/lib/services/xml-reading.service.ts @@ -0,0 +1,93 @@ +export type XJustizMetadata = { + sender: string; + receiver: string; + documentNames: string[]; +}; + +const XML_TEXT_DECODER = new TextDecoder(); + +const matchesElementName = (element: Element, requestedName: string) => { + const requestedLocalName = requestedName.split('.').pop() ?? requestedName; + + return ( + element.localName === requestedName || + element.localName === requestedLocalName || + element.tagName === requestedName || + element.tagName === requestedLocalName || + element.tagName.endsWith(`:${requestedLocalName}`) + ); +}; + +const getDirectChildByLocalName = (element: Element, localName: string) => + Array.from(element.children).find((child) => matchesElementName(child, localName)) ?? null; + +const getFirstDescendantByLocalName = (root: Document | Element, localName: string) => + Array.from(root.querySelectorAll('*')).find((element) => + matchesElementName(element, localName) + ) ?? null; + +const getTextContentByPath = (root: Element, path: string[]) => { + let current: Element | null = root; + + for (const localName of path) { + current = current ? getDirectChildByLocalName(current, localName) : null; + + if (!current) { + return null; + } + } + + const textContent = current.textContent?.trim(); + + return textContent ? textContent : null; +}; + +const getTextContentByLocalName = (root: Document | Element, localName: string) => + getFirstDescendantByLocalName(root, localName)?.textContent?.trim() ?? null; + +const parseXmlDocument = (xmlBytes: Uint8Array) => { + const parser = new DOMParser(); + const document = parser.parseFromString(XML_TEXT_DECODER.decode(xmlBytes), 'application/xml'); + + if (document.querySelector('parsererror')) { + return null; + } + + return document; +}; + +export const parseXJustizMetadata = (xmlBytes: Uint8Array): XJustizMetadata | null => { + const document = parseXmlDocument(xmlBytes); + + if (!document) { + return null; + } + + const sender = getTextContentByLocalName(document, 'aktenzeichen.absender') ?? ''; + const receiver = getTextContentByLocalName(document, 'aktenzeichen.empfaenger') ?? ''; + const schriftgutobjekte = getFirstDescendantByLocalName(document, 'schriftgutobjekte'); + + if (!schriftgutobjekte) { + return { + sender, + receiver, + documentNames: [] + }; + } + + const dokumentNodes = Array.from(schriftgutobjekte.children).filter((child) => + matchesElementName(child, 'dokument') + ); + + const documentNames = dokumentNodes + .map((dokument) => + getTextContentByPath(dokument, ['xjustiz.fachspezifischeDaten', 'datei', 'dateiname']) + ) + .filter((name): name is string => Boolean(name)); + + return { + sender, + receiver, + documentNames + }; +}; diff --git a/src/lib/services/zip-inflating.service.ts b/src/lib/services/zip-inflating.service.ts new file mode 100644 index 0000000..4082758 --- /dev/null +++ b/src/lib/services/zip-inflating.service.ts @@ -0,0 +1,13 @@ +import { unzip } from 'fflate'; + +export const unzipArchive = (data: Uint8Array) => + new Promise>((resolve, reject) => { + unzip(data, (error, files) => { + if (error) { + reject(error); + return; + } + + resolve(files); + }); + }); diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..55b3a91 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,13 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type WithoutChild = T extends { child?: any } ? Omit : T; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type WithoutChildren = T extends { children?: any } ? Omit : T; +export type WithoutChildrenOrChild = WithoutChildren>; +export type WithElementRef = T & { ref?: U | null }; diff --git a/src/lib/zip-processing.ts b/src/lib/zip-processing.ts new file mode 100644 index 0000000..2137660 --- /dev/null +++ b/src/lib/zip-processing.ts @@ -0,0 +1,628 @@ +import { PDFDocument } from 'pdf-lib'; + +import { parseXJustizMetadata, type XJustizMetadata } from './services/xml-reading.service'; +import { unzipArchive } from './services/zip-inflating.service'; + +export type ZipAttachmentKind = 'pdf' | 'image'; + +export type ZipAttachment = { + name: string; + path: string; + kind: ZipAttachmentKind; + data: Uint8Array; +}; + +export type SubDocument = { + id: string; + name: string; + attachments: ZipAttachment[]; +}; + +export type ProcessedZipArchive = { + name: string; + attachments: ZipAttachment[]; + subDocuments: SubDocument[]; +}; + +export type ExportMode = 'single' | 'separate'; + +export type ExportUnit = { + name: string; + bytes: Uint8Array; +}; + +/** + * Drag source/target descriptors shared between the archive editor and the + * sub-document editor so HTML5 `dataTransfer` payloads stay consistent. + */ +export type AttachmentLocation = + | { kind: 'loose'; index: number } + | { kind: 'subDocument'; id: string; index: number }; + +const IMAGE_EXTENSIONS = new Set([ + '.avif', + '.bmp', + '.gif', + '.heic', + '.jpeg', + '.jpg', + '.jp2', + '.png', + '.tif', + '.tiff', + '.webp' +]); + +const ZIP_META_FILE_NAME = 'xjustiz_nachricht.xml'; + +const getBaseName = (path: string) => path.split('/').pop() ?? path; + +const getCurrentDateString = (date: Date) => + `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; + +const createArchiveName = (date: Date, metadata: XJustizMetadata | null) => { + const datePrefix = getCurrentDateString(date); + + if (!metadata) { + return `${datePrefix}_unbekannt_unbekannt.pdf`; + } + + return `${datePrefix}_${metadata.sender}_${metadata.receiver}.pdf`; +}; + +const getExtension = (path: string) => { + const baseName = getBaseName(path).toLowerCase(); + const lastDotIndex = baseName.lastIndexOf('.'); + + return lastDotIndex === -1 ? '' : baseName.slice(lastDotIndex); +}; + +const isPdf = (path: string) => getExtension(path) === '.pdf'; + +const isImage = (path: string) => IMAGE_EXTENSIONS.has(getExtension(path)); + +const isZipArchive = (path: string) => getExtension(path) === '.zip'; + +const normalizeKey = (value: string) => value.replaceAll('\\', '/').toLowerCase().trim(); + +const isMacMetadataEntry = (path: string) => { + const normalizedPath = normalizeKey(path); + const baseName = getBaseName(normalizedPath); + + return ( + normalizedPath.startsWith('__macosx/') || + normalizedPath.includes('/__macosx/') || + baseName === '.ds_store' || + baseName.startsWith('._') + ); +}; +const orderAttachmentsByDocumentNames = (attachments: ZipAttachment[], documentNames: string[]) => { + const buckets = new Map(); + + for (const attachment of attachments) { + const keys = new Set([normalizeKey(attachment.path), normalizeKey(attachment.name)]); + + for (const key of keys) { + const bucket = buckets.get(key) ?? []; + bucket.push(attachment); + buckets.set(key, bucket); + } + } + + const usedAttachments = new Set(); + const orderedAttachments: ZipAttachment[] = []; + + const takeAttachment = (key: string) => { + const bucket = buckets.get(key); + + if (!bucket) { + return null; + } + + const nextAttachment = bucket.find((attachment) => !usedAttachments.has(attachment)) ?? null; + + if (nextAttachment) { + usedAttachments.add(nextAttachment); + } + + return nextAttachment; + }; + + for (const documentName of documentNames) { + const normalizedName = normalizeKey(documentName); + const normalizedBaseName = normalizeKey(getBaseName(documentName)); + const matchedAttachment = takeAttachment(normalizedName) ?? takeAttachment(normalizedBaseName); + + if (matchedAttachment) { + orderedAttachments.push(matchedAttachment); + } + } + + for (const attachment of attachments) { + if (!usedAttachments.has(attachment)) { + orderedAttachments.push(attachment); + } + } + + return orderedAttachments; +}; + +const shouldSkipZipEntry = (path: string) => isMacMetadataEntry(path); + +const isMetadataFile = (path: string) => getBaseName(path).toLowerCase() === ZIP_META_FILE_NAME; + +const createAttachment = (path: string, data: Uint8Array): ZipAttachment => ({ + name: getBaseName(path), + path, + kind: isPdf(path) ? 'pdf' : 'image', + data +}); + +const extractZipEntries = async (archiveBytes: Uint8Array): Promise => { + const files = await unzipArchive(archiveBytes); + const attachments: ZipAttachment[] = []; + const nestedArchives: ProcessedZipArchive[] = []; + let xjustizNachrichtXml: Uint8Array | null = null; + + for (const [path, data] of Object.entries(files)) { + if (shouldSkipZipEntry(path)) { + continue; + } + + if (isMetadataFile(path)) { + xjustizNachrichtXml = data; + continue; + } + + if (isZipArchive(path)) { + const childArchives = await extractZipEntries(data); + nestedArchives.push(...childArchives); + continue; + } + + if (isPdf(path) || isImage(path)) { + attachments.push(createAttachment(path, data)); + } + } + + const metadata = xjustizNachrichtXml ? parseXJustizMetadata(xjustizNachrichtXml) : null; + const documentNames = metadata?.documentNames ?? []; + const orderedAttachments = orderAttachmentsByDocumentNames(attachments, documentNames); + const archiveName = createArchiveName(new Date(), metadata); + const archives: ProcessedZipArchive[] = []; + + if (orderedAttachments.length > 0) { + archives.push({ + name: archiveName, + attachments: orderedAttachments, + subDocuments: [] + }); + } + + return [...archives, ...nestedArchives]; +}; + +export const extractZipArchives = async (file: File): Promise => { + const archiveBytes = new Uint8Array(await file.arrayBuffer()); + + return extractZipEntries(archiveBytes); +}; + +const loadImageElement = (src: string) => + new Promise((resolve, reject) => { + const image = new Image(); + image.decoding = 'async'; + image.onload = () => resolve(image); + image.onerror = () => reject(new Error(`Unable to decode image asset: ${src}`)); + image.src = src; + }); + +const toArrayBuffer = (bytes: Uint8Array) => + (() => { + const copy = new Uint8Array(bytes.byteLength); + copy.set(bytes); + return copy.buffer; + })(); + +const convertImageBytesToPng = async (imageBytes: Uint8Array) => { + if (typeof document === 'undefined') { + throw new Error('Image conversion requires a browser environment.'); + } + + const objectUrl = URL.createObjectURL(new Blob([toArrayBuffer(imageBytes)])); + + try { + const image = await loadImageElement(objectUrl); + const canvas = document.createElement('canvas'); + canvas.width = image.naturalWidth || image.width; + canvas.height = image.naturalHeight || image.height; + + const context = canvas.getContext('2d'); + + if (!context) { + throw new Error('Could not create a canvas context for image conversion.'); + } + + context.drawImage(image, 0, 0); + + const pngBlob = await new Promise((resolve, reject) => { + canvas.toBlob((blob) => { + if (blob) { + resolve(blob); + return; + } + + reject(new Error('Could not encode image asset as PNG.')); + }, 'image/png'); + }); + + return new Uint8Array(await pngBlob.arrayBuffer()); + } finally { + URL.revokeObjectURL(objectUrl); + } +}; + +const embedAttachmentImage = async (pdfDocument: PDFDocument, attachment: ZipAttachment) => { + const extension = getExtension(attachment.path); + const imageData = attachment.data; + + if (extension === '.jpg' || extension === '.jpeg') { + return pdfDocument.embedJpg(imageData); + } + + if (extension === '.png') { + return pdfDocument.embedPng(imageData); + } + + const pngBytes = await convertImageBytesToPng(imageData); + + return pdfDocument.embedPng(pngBytes); +}; + +const mergeAttachmentIntoPdf = async (mergedPdf: PDFDocument, attachment: ZipAttachment) => { + if (attachment.kind === 'pdf') { + const sourcePdf = await PDFDocument.load(attachment.data, { ignoreEncryption: true }); + const copiedPages = await mergedPdf.copyPages(sourcePdf, sourcePdf.getPageIndices()); + + for (const page of copiedPages) { + mergedPdf.addPage(page); + } + + return; + } + + try { + const embeddedImage = await embedAttachmentImage(mergedPdf, attachment); + const page = mergedPdf.addPage([embeddedImage.width, embeddedImage.height]); + + page.drawImage(embeddedImage, { + x: 0, + y: 0, + width: page.getWidth(), + height: page.getHeight() + }); + } catch (error) { + throw error; + } +}; + +export const mergeAttachments = async (attachments: ZipAttachment[]): Promise => { + if (attachments.length === 0) { + throw new Error('Cannot create a PDF from an empty attachment list.'); + } + + const mergedPdf = await PDFDocument.create(); + + for (const attachment of attachments) { + await mergeAttachmentIntoPdf(mergedPdf, attachment); + } + + return mergedPdf.save(); +}; + +export const mergeProcessedZipArchive = async (archive: ProcessedZipArchive) => { + if (archive.attachments.length === 0 && archive.subDocuments.length === 0) { + throw new Error('Cannot create a PDF from an empty archive.'); + } + + return mergeAttachments(flattenArchiveForMerge(archive)); +}; + +/** + * Flatten the archive into a single ordered attachment list, preserving + * sub-document order first and appending loose attachments at the end. + */ +export const flattenArchiveForMerge = (archive: ProcessedZipArchive): ZipAttachment[] => { + const ordered: ZipAttachment[] = []; + + for (const subDocument of archive.subDocuments) { + ordered.push(...subDocument.attachments); + } + + ordered.push(...archive.attachments); + + return ordered; +}; + +/** + * Create a new sub-document with a stable id from a list of attachments. + */ +export const createSubDocument = (name: string, attachments: ZipAttachment[]): SubDocument => ({ + id: crypto.randomUUID(), + name, + attachments: attachments.map((attachment) => ({ ...attachment })) +}); + +/** + * Reorder sub-documents inside an archive. Matches the semantics of a single + * `splice(fromIndex, 1)` -> `splice(toIndex, 0, moved)` move. + */ +export const moveSubDocument = ( + archive: ProcessedZipArchive, + fromIndex: number, + toIndex: number +): ProcessedZipArchive => { + if ( + fromIndex === toIndex || + fromIndex < 0 || + toIndex < 0 || + fromIndex >= archive.subDocuments.length || + toIndex >= archive.subDocuments.length + ) { + return archive; + } + + const nextSubDocuments = [...archive.subDocuments]; + const [movedSubDocument] = nextSubDocuments.splice(fromIndex, 1); + + nextSubDocuments.splice(toIndex, 0, movedSubDocument); + + return { ...archive, subDocuments: nextSubDocuments }; +}; + +const removeAt = (list: T[], index: number): T | null => { + if (index < 0 || index >= list.length) { + return null; + } + + const [item] = list.splice(index, 1); + + return item ?? null; +}; + +const insertAt = (list: T[], index: number, item: T) => { + const clampedIndex = Math.max(0, Math.min(index, list.length)); + list.splice(clampedIndex, 0, item); +}; + +/** + * Reorder an attachment within its current location (loose list or a single + * sub-document's attachment list). Used by the per-list drag handlers. + */ +export const moveAttachmentInPlace = ( + archive: ProcessedZipArchive, + location: AttachmentLocation, + toIndex: number +): ProcessedZipArchive => { + if (location.kind === 'loose') { + const nextAttachments = [...archive.attachments]; + const moved = removeAt(nextAttachments, location.index); + + if (!moved) { + return archive; + } + + insertAt(nextAttachments, toIndex, moved); + + return { ...archive, attachments: nextAttachments }; + } + + let changed = false; + + const nextSubDocuments = archive.subDocuments.map((subDocument) => { + if (subDocument.id !== location.id) { + return subDocument; + } + + const nextAttachments = [...subDocument.attachments]; + const moved = removeAt(nextAttachments, location.index); + + if (!moved) { + return subDocument; + } + + insertAt(nextAttachments, toIndex, moved); + changed = true; + + return { ...subDocument, attachments: nextAttachments }; + }); + + return changed ? { ...archive, subDocuments: nextSubDocuments } : archive; +}; + +/** + * Move an attachment from a source location to a target location. Either side + * may be the loose list or any sub-document. The attachment moves (not copies), + * so the total attachment count is invariant. When source and target point to + * the same list, this degenerates to an in-place reorder (explicitly delegated + * to `moveAttachmentInPlace` so the indices are normalized correctly). + */ +export const moveAttachmentIntoSubDocument = ( + archive: ProcessedZipArchive, + source: AttachmentLocation, + target: AttachmentLocation +): ProcessedZipArchive => { + const sameList = + source.kind === 'loose' && target.kind === 'loose' + ? true + : source.kind === 'subDocument' && target.kind === 'subDocument' + ? source.id === target.id + : false; + + if (sameList) { + return moveAttachmentInPlace(archive, source, target.index); + } + + const nextSubDocuments = archive.subDocuments.map((subDocument) => ({ + ...subDocument, + attachments: [...subDocument.attachments] + })); + const looseAttachments = [...archive.attachments]; + + const removeFromList = (location: AttachmentLocation): ZipAttachment | null => { + if (location.kind === 'loose') { + return removeAt(looseAttachments, location.index); + } + + const target_ = nextSubDocuments.find((item) => item.id === location.id); + + return target_ ? removeAt(target_.attachments, location.index) : null; + }; + + const insertIntoList = (attachment: ZipAttachment, location: AttachmentLocation) => { + if (location.kind === 'loose') { + insertAt(looseAttachments, location.index, attachment); + return; + } + + const target_ = nextSubDocuments.find((item) => item.id === location.id); + + if (!target_) { + // fall back to loose list end if the referenced sub-document vanished + looseAttachments.push(attachment); + return; + } + + insertAt(target_.attachments, location.index, attachment); + }; + + const movedAttachment = removeFromList(source); + + if (!movedAttachment) { + return archive; + } + + insertIntoList(movedAttachment, target); + + return { + ...archive, + attachments: looseAttachments, + subDocuments: nextSubDocuments + }; +}; + +/** + * Remove a single attachment from a sub-document and return it to the end of + * the loose list. Useful as a targeted "move back to loose" action. + */ +export const removeAttachmentFromSubDocument = ( + archive: ProcessedZipArchive, + subDocumentId: string, + attachmentIndex: number +): ProcessedZipArchive => { + const nextSubDocuments = archive.subDocuments.map((subDocument) => ({ + ...subDocument, + attachments: [...subDocument.attachments] + })); + const nextLooseAttachments = [...archive.attachments]; + + const target_ = nextSubDocuments.find((item) => item.id === subDocumentId); + + if (!target_) { + return archive; + } + + const moved = removeAt(target_.attachments, attachmentIndex); + + if (!moved) { + return archive; + } + + nextLooseAttachments.push(moved); + + return { + ...archive, + attachments: nextLooseAttachments, + subDocuments: nextSubDocuments + }; +}; + +/** + * Remove an entire sub-document and return its attachments to the end of the + * loose list. The sub-document ids of the remaining sub-documents are + * preserved. + */ +export const dissolveSubDocument = ( + archive: ProcessedZipArchive, + subDocumentId: string +): ProcessedZipArchive => { + const dissolved = archive.subDocuments.find((item) => item.id === subDocumentId); + + if (!dissolved) { + return archive; + } + + return { + ...archive, + attachments: [...archive.attachments, ...dissolved.attachments], + subDocuments: archive.subDocuments.filter((item) => item.id !== subDocumentId) + }; +}; + +const stripPdfExtension = (name: string) => + name.toLowerCase().endsWith('.pdf') ? name.slice(0, -'.pdf'.length) : name; + +/** + * Build one or more PDF export units from the archive. + * + * - `mode: 'single'` mirrors today's behaviour: one merged PDF covering + * sub-documents first, then loose attachments, named after the archive. + * - `mode: 'separate'` produces one PDF per sub-document (named + * `{archiveName} - {subDocumentName}.pdf`) plus a single merged PDF for any + * loose attachments (named after the archive). Empty groups are skipped. + */ +export const buildArchiveExport = async ( + archive: ProcessedZipArchive, + mode: ExportMode +): Promise => { + if (mode === 'single') { + const attachments = flattenArchiveForMerge(archive); + + if (attachments.length === 0) { + throw new Error('Cannot create a PDF from an empty archive.'); + } + + return [ + { + name: stripPdfExtension(archive.name), + bytes: await mergeAttachments(attachments) + } + ]; + } + + const separateUnits: ExportUnit[] = []; + + for (const subDocument of archive.subDocuments) { + if (subDocument.attachments.length === 0) { + continue; + } + + separateUnits.push({ + name: `${stripPdfExtension(archive.name)} - ${stripPdfExtension(subDocument.name)}`, + bytes: await mergeAttachments(subDocument.attachments) + }); + } + + if (archive.attachments.length > 0) { + separateUnits.push({ + name: stripPdfExtension(archive.name), + bytes: await mergeAttachments(archive.attachments) + }); + } + + if (separateUnits.length === 0) { + throw new Error('Cannot create a PDF from an empty archive.'); + } + + return separateUnits; +}; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 8db6d11..52e8ac8 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,35 +1,211 @@ {#snippet content()} {#if selectedZipFiles.length === 0} - + + + + + {:else} -
      -
      -

      ZIP-Dateien geladen

      -
      +
      + + Settings + ⚙️ + -
        - {#each selectedZipFiles as file} -
      • - {file.name} -
      • - {/each} -
      +
      +
      +
      + + + +
      + + +
      +
      +
      +
      +
      + {#if pendingZipCount > 0} +
      + Noch {pendingZipCount} ZIP-Datei{pendingZipCount === 1 ? '' : 'en'} in Bearbeitung. +
      + {/if} + + {#if processedZipFiles.length > 0} +
      + {#each processedZipFiles as file, index} + updateProcessedZipFile(index, updatedArchive)} + /> + {/each} +
      + {/if} +
      {/if} {/snippet}
      -

      beA-Edit

      - +
      +

      beA-Edit

      +
      + {@render content()}
      diff --git a/src/routes/layout.css b/src/routes/layout.css index 8c267aa..e2394e6 100644 --- a/src/routes/layout.css +++ b/src/routes/layout.css @@ -1,16 +1,161 @@ @import 'tailwindcss'; -@import '@skeletonlabs/skeleton/themes/cerberus'; -@import '@skeletonlabs/skeleton'; -@import '@skeletonlabs/skeleton-svelte'; -@import '../lib/nom-theme.css'; +/*@import '../globals.css';*/ +@import "tw-animate-css"; +@import "shadcn-svelte/tailwind.css"; +@import "@fontsource-variable/geist"; + +@custom-variant dark (&:is(.dark *)); @plugin '@tailwindcss/forms'; @plugin '@tailwindcss/typography'; +@font-face { + font-family: 'Nunito'; + font-style: normal; + font-weight: 100 900; + src: url('$lib/assets/Nunito-VariableFont_wght.ttf') format('truetype'); +} -@font-face{ - font-family: 'Nunito'; - font-style: normal; - font-weight: 100 900; - src: url('$lib/assets/Nunito-VariableFont_wght.ttf') format("truetype"); -} \ No newline at end of file +:root { + --background: #EEF4F6; + --foreground: #122F62; + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: #122f62; + --primary-foreground: oklch(0.985 0 0); + --secondary: #EA544B; + --secondary-end: #FF6F6D; + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: #d6dce5; + --accent-foreground: oklch(0.205 0 0); + --destructive: #ff0027; + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.87 0 0); + --chart-2: oklch(0.556 0 0); + --chart-3: oklch(0.439 0 0); + --chart-4: oklch(0.371 0 0); + --chart-5: oklch(0.269 0 0); + --radius: 16px; + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); +} + +.dark { + --background: #212121; + --foreground: #FFFFFF; + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: #92b2eb; + --primary-foreground: oklch(0.205 0 0); + --secondary: linear-gradient(90deg, #EA544B 0%, #FF6F6D 100%); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: #ff0027; + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --chart-1: oklch(0.87 0 0); + --chart-2: oklch(0.556 0 0); + --chart-3: oklch(0.439 0 0); + --chart-4: oklch(0.371 0 0); + --chart-5: oklch(0.269 0 0); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); +} + +@theme inline { + --font-sans: 'Nunito', sans-serif; + --color-sidebar-ring: var(--sidebar-ring); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar: var(--sidebar); + --color-chart-5: var(--chart-5); + --color-chart-4: var(--chart-4); + --color-chart-3: var(--chart-3); + --color-chart-2: var(--chart-2); + --color-chart-1: var(--chart-1); + --color-ring: var(--ring); + --color-input: var(--input); + --color-border: var(--border); + --color-destructive: var(--destructive); + --color-accent-foreground: var(--accent-foreground); + --color-accent: var(--accent); + --color-muted-foreground: var(--muted-foreground); + --color-muted: var(--muted); + --color-secondary-foreground: var(--secondary-foreground); + --color-secondary: var(--secondary); + --color-primary-foreground: var(--primary-foreground); + --color-primary: var(--primary); + --color-popover-foreground: var(--popover-foreground); + --color-popover: var(--popover); + --color-card-foreground: var(--card-foreground); + --color-card: var(--card); + --color-foreground: var(--foreground); + --color-background: var(--background); + --radius-sm: calc(var(--radius) * 0.6); + --radius-md: calc(var(--radius) * 0.8); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) * 1.4); + --radius-2xl: calc(var(--radius) * 1.8); + --radius-3xl: calc(var(--radius) * 2.2); + --radius-4xl: calc(var(--radius) * 2.6); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } + html { + @apply font-sans; + } +} + + +/* ripple */ + +@keyframes ripple { + from { + transform: scale(0); + opacity: 0.8; + } + + to { + transform: scale(4); + opacity: 0; + } +} + +.animate-ripple { + animation: ripple 600ms ease-out; +} + \ No newline at end of file diff --git a/uat.yml b/uat.yml new file mode 100644 index 0000000..fac4fb8 --- /dev/null +++ b/uat.yml @@ -0,0 +1,37 @@ +name: Build and Push Docker Image + +on: + push: + branches: + - develop + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Docker login to Gitea Registry + run: | + echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login "${{ vars.REGISTRY_URL }}" \ + -u "${{ secrets.REGISTRY_USER }}" \ + --password-stdin + + - name: Build Docker image + run: | + docker build \ + -t "${{ vars.REGISTRY_URL }}/${{ vars.IMAGE_NAME }}:dev" \ + -t "${{ vars.REGISTRY_URL }}/${{ vars.IMAGE_NAME }}:${{ gitea.sha }}" \ + . + + - name: Push Docker image + run: | + docker push "${{ vars.REGISTRY_URL }}/${{ vars.IMAGE_NAME }}:dev" + docker push "${{ vars.REGISTRY_URL }}/${{ vars.IMAGE_NAME }}:${{ gitea.sha }}" + + - name: Deploy + run: | + curl https://dokploy.robosoft-solutions.de/api/deploy/compose/Miw8GJXF_v4Ebavr3mJwa diff --git a/vite.config.ts b/vite.config.ts index 5765ebc..5d92263 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,5 +1,5 @@ import tailwindcss from '@tailwindcss/vite'; -import adapter from '@sveltejs/adapter-auto'; +import adapter from '@sveltejs/adapter-node'; import { sveltekit } from '@sveltejs/kit/vite'; import { defineConfig } from 'vite'; @@ -13,9 +13,6 @@ export default defineConfig({ filename.split(/[/\\]/).includes('node_modules') ? undefined : true }, - // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. - // If your environment is not supported, or you settled on a specific environment, switch out the adapter. - // See https://svelte.dev/docs/kit/adapters for more information about adapters. adapter: adapter() }) ]