diff --git a/copilot-instructions.md b/.github/copilot-instructions.md similarity index 100% rename from copilot-instructions.md rename to .github/copilot-instructions.md diff --git a/.github/instructions/erp-demo-docs.instructions.md b/.github/instructions/erp-demo-docs.instructions.md deleted file mode 100644 index 8d9a3ec..0000000 --- a/.github/instructions/erp-demo-docs.instructions.md +++ /dev/null @@ -1,301 +0,0 @@ ---- -applyTo: "docs/erp-demo/**" ---- - -# ERP Demo documentation authoring - -These instructions apply when writing or editing documentation pages inside `docs/erp-demo/`. - -The files in this folder are AsciiDoc (`.adoc`) pages that get published on a React CV portfolio website. - ---- - -## File naming and placement - -- Each page is a single `.adoc` file inside `docs/erp-demo/docs/`. -- The `diagrams/` folder lives at `docs/erp-demo/docs/diagrams/`. -- The filename (without extension) is the **slug**: lowercase, hyphen-separated words. -- The slug must match the `slug` field of the corresponding entry in `docs/erp-demo/erp-docs.json`. - ---- - -## erp-docs.json - -`docs/erp-demo/erp-docs.json` is the single source of truth for page order, titles, and hierarchy. -Keep it in sync whenever pages are added, removed, or reordered. - -```json -[ - { "slug": "introduction", "title": "Introduction", "level": 1 }, - { "slug": "architecture-overview", "title": "Architecture Overview", "level": 1 }, - { "slug": "react-frontend", "title": "React Frontend", "level": 2 } -] -``` - -Level values: `1` = chapter, `2` = section, `3` = sub-section. -The CV website derives section numbers (e.g. `2.1`, `2.1.3`) and prev/next navigation automatically from this file. - ---- - -## Document structure - -Do **not** include a document title (`= Title`) — the page title is supplied by the CV website from `erp-docs.json`. - -Start content directly with body text or with `==` section headings: - -```asciidoc -== Overview - -This section describes the overall architecture. - -=== Backend - -The backend is built with ASP.NET Core. -``` - -Use `==` for top-level sections within the page, `===` for sub-sections, `====` for deeper nesting. - -Optional document attributes at the top of the file: - -```asciidoc -:toc: left -:toclevels: 3 -:sectnums: -:icons: font -``` - ---- - -## Custom macros - -The CV website renderer supports the following macros in addition to standard AsciiDoc. - -### Inline macros (inside paragraph text) - -```asciidoc -Implemented in skill:C#[] using skill:React[] for the frontend. - -Contact: email:[] -Contact: email:[href="you@example.com",text="Send email"] -``` - -### Buttons (block macros — one per line) - -```asciidoc -github::[href="https://github.com/org/repo",text="View on GitHub"] -download::[href="https://example.com/v1.zip",text="Download v1.0"] -website::[href="https://example.com",text="Live Demo"] -linkedin::[href="https://linkedin.com/in/you",text="LinkedIn"] - -linux::[href="https://example.com/app.AppImage",text="Download for Linux"] -windows::[href="https://example.com/app.zip",text="Download for Windows"] -macos::[href="https://example.com/app.dmg",text="Download for macOS"] - -firefox::[href="https://addons.mozilla.org/…",text="Get for Firefox"] -chrome::[href="https://chrome.google.com/webstore/…",text="Get for Chrome"] -``` - -### Custom button - -```asciidoc -btn::[href="https://…",icon="FaDownload",text="Download Release"] -``` - -`icon` accepts any `react-icons/fa` name (e.g. `FaGithub`, `FaExternalLinkAlt`). - -### Video embeds - -```asciidoc -youtube::dQw4w9WgXcQ[title="Demo walkthrough",width=800] - -webm::./demo.mp4[max-width=700,start=2,autoplay,auto-loop] -webm::./demo.mp4[max-width=700,controls] -``` - -`webm` attributes: - -| Attribute | Description | -|---|---| -| `max-width` | Pixel cap (default 700) | -| `start` | Start offset in seconds | -| `autoplay` | Autoplay + muted (boolean flag) | -| `auto-loop` | Loop the video (boolean flag) | -| `controls` | Show native controls, overrides autoplay | - -### Highlight box - -```asciidoc -highlight::[title="Note",text="This feature requires .NET 8.",shadow] -``` - -All attributes are optional. `shadow` is a boolean flag (no value needed). - -To embed skill badges or other tags inside a highlight, use a passthrough block: - -```asciidoc -++++ - - Built with ASP.NET Core and React. - -++++ -``` - -### DOT / Graphviz diagrams - -Diagrams can be authored as **separate `.dot` files** (recommended) or as inline blocks. - -#### External `.dot` file (recommended) - -Place the `.dot` file alongside (or in a subdirectory of) your `.adoc` file and reference it with `dotgraph::`: - -```asciidoc -dotgraph::./diagrams/system-architecture.dot[caption="System Architecture",max-width=700] -dotgraph::./order-flow.dot[engine=neato] -``` - -The file is bundled by the CV website's build at compile time. No copy step needed for `.dot` files — only `.adoc` files are copied manually. - -File layout example: -``` -docs/erp-demo/ - erp-docs.json - docs/ - architecture-overview.adoc - diagrams/ - system-architecture.dot - order-flow.dot -``` - -#### Inline DOT block - -For quick or one-off diagrams, embed the DOT source directly in the `.adoc` file: - -```asciidoc -[graphviz,caption="Order flow",max-width=700,engine=dot] -.... -digraph orders { - rankdir=TB; - node [shape=box]; - Created -> Processing -> Shipped -> Delivered; - Processing -> Cancelled; -} -.... -``` - -Use `----` instead of `....` as delimiter — both work. - -#### Available attributes (both forms) - -| Attribute | Description | -|---|---| -| `caption` | Figure caption shown below the diagram | -| `max-width` | Maximum pixel width, e.g. `700` | -| `align` | `left`, `right`, or default center | -| `engine` | Layout engine: `dot` (default), `neato`, `fdp`, `sfdp`, `circo`, `twopi` | - ---- - -## Passthrough blocks - -Use `++++` passthrough blocks to write raw HTML when macros are not sufficient: - -```asciidoc -++++ -Kubernetes -Docker -Entity Framework Core -++++ -``` - ---- - -## Standard AsciiDoc features - -### Code blocks - -```asciidoc -[source,csharp] ----- -public class OrderService -{ - public Task GetByIdAsync(Guid id) => _repo.FindAsync(id); -} ----- - -[source,sql] ----- -SELECT * FROM orders WHERE status = 'pending'; ----- -``` - -### Admonitions - -```asciidoc -NOTE: Requires .NET 8 or later. - -TIP: Use the `--watch` flag during development for live reload. - -IMPORTANT: Run database migrations before deploying a new version. - -WARNING: This operation will delete all seed data. - -CAUTION: Not supported on Windows without WSL. -``` - -### Tables - -```asciidoc -[cols="1,1,3",options="header"] -|=== -| Field | Type | Description - -| id -| UUID -| Primary key, auto-generated - -| created_at -| timestamp -| Set automatically on insert -|=== -``` - -### Images - -```asciidoc -image::./screenshot.png[alt="Dashboard screenshot",width=800] -``` - -Place image files alongside the `.adoc` file in `docs/erp-demo/docs/`. - -### Blockquotes - -```asciidoc -[quote,Author Name] -____ -The quote text goes here. -____ -``` - -### Description lists - -```asciidoc -term one:: - Definition of term one. - -term two:: - Definition of term two. -``` - ---- - -## Adding a new page - -1. Create `docs/erp-demo/docs/.adoc`. -2. Add an entry at the desired position in `docs/erp-demo/erp-docs.json`: - ```json - { "slug": "your-slug", "title": "Your Page Title", "level": 2 } - ``` -3. Copy the `.adoc` file to `src/data/projects/ERPDemo/docs/` in the CV website repository. -4. Copy `docs/erp-demo/erp-docs.json` to the CV website as well (it replaces the one there). - -No code changes are needed in the CV website. diff --git a/.gitignore b/.gitignore index 924bc97..af48465 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ - # your stuff is in here /public/data/ /src/data/ +public/favicon.png # Node modules node_modules/ @@ -101,3 +101,15 @@ temp/ # Ensure lockfiles remain tracked # (do NOT ignore package-lock.json, yarn.lock, pnpm-lock.yaml) + +# Ignore all git projects in a specific folder +git_projects/ + +# Resume is after npm run dist there +data/ + +# Archives +*.zip +*.tar +*.tar.7z +*.tar.gz \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f345fde --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "search.useIgnoreFiles": false +} \ No newline at end of file diff --git a/LICENSE b/LICENSE index 5f779f4..27532a8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Daniel Roth +Copyright (c) 2025 Daniel Roth and Jonas Roth Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 0095a2a..445c806 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Personal CV Web Page This project is a personal CV web page built with React and TypeScript. It showcases various projects, provides detailed project pages, and includes a responsive design for mobile compatibility. -See [here](http://daniel.mailbase.info) for an example where this template is used. +See [here](http://jonas.mailbase.info) for an example where this template is used or [here](http://danielroth1.github.io/CVWebpageTemplate/) for the original author's demo page. ## Features - modern design @@ -16,17 +16,21 @@ Fork (if you want) or clone the repository directly: git clone git@github.com:danielroth1/CVWebpageTemplate.git ``` -You only have add the content via files in the `/src/data` folder. +You only have to add content via files in the `src/data` folder. After you have done the following customizations: -- Add your resume information like name, title, e-mail, skills to `resume.json` -- Add your "about me" section (ABOUT_ME.md) -- Add your "contact" page (CONTACT.md) -- Add a personal picture or use a placeholder if you are not comfortable with it (peronal_photo.jpg) -- Add your own projects as a display of your achievements - - Add a project description (projects.json + `README.md` inside `projects//`) - - Add a preview image for each project `/projects//preview.` - - After adding new videos, run `npm run compress-videos` - - Automatically count all lines of code: Specify the projects source location in `/multi-loc-config.json` and run `npm run multi-loc`. Alternatively, manually change the count in `/projects//cloc.json` +- Add your resume information (name, title, e-mail, skills) to `src/data/resume.json` +- Add your "about me" section in `src/data/ABOUT_ME.md` + - You can use the dynamic placeholder `` in Markdown or AsciiDoc to automatically calculate and display your years of experience based on the earliest start date in `src/data/resume.json`. +- Add your contact page in `src/data/CONTACT.md` +- Add a personal picture (or placeholder) at `src/data/personal_photo.jpg` +- Add projects to showcase your work: + - Add a project description (`projects.json` + `README.md` inside `src/data/projects//`) + - Add preview media for each project under `src/data/projects//preview.*` + - After adding videos run `npm run compress-videos` + - Count lines of code by configuring `multi-loc-config.json` and running `npm run multi-loc`, or provide a manual `cloc.json` for each project. +- Clone project repositories into the `git_projects/` folder to include them in automated code statistics + - Update those repositories with `npm run update-git-projects` + - The build process runs code statistics for repositories in `git_projects/` by default optional: - Add your resume as pdf for download (resume.pdf) @@ -34,22 +38,22 @@ optional: - Color match your skills by grouping them in `skills.json` - Change the Skill colors in `/src/utils/SkillColors.ts` -Set it up +Setup ``` -npm run install +npm install ``` -Try it out +Run in development mode ``` npm run dev ``` -Create distributable +Build for production ``` npm run build ``` -and deploy it. I have provided a script for deployment on a ftp file server, see scripts/deploy.sh +Deploy ``` ./scripts/deploy.sh ``` @@ -65,6 +69,11 @@ Create code statistics npm run multi-loc ``` +Update all git repositories in git_projects/ +``` +npm run update-git-projects +``` + Compress your videos (recommended) ``` npm run compress-videos @@ -179,3 +188,31 @@ npm run compress-videos ``` This creates `[name].min.webm` (VP9) and `[name].min.mp4` (H.264) beside each `*.webm` under `src/data/**`. The renderer will automatically use these smaller variants when available. Requires `ffmpeg` (macOS: `brew install ffmpeg`). + +## Automated skill groups + +- Run `npm run build-skills` (or rely on the automatic call that runs before `npm run dev` and `npm run build`) to regenerate `src/data/skills.json` from `src/data/resume.json` and `src/data/projects.json`. +- The generator is implemented in `scripts/build-skills.js` and: + - preserves resume categories as ordered groups, + - deduplicates skills across sources, + - collects project-only skills into a `Project Skills` group, + - exposes `metadata.singleUseSkills` for skills that appear in exactly one project. +- Run the generator manually to inspect results or integrate it in CI before publishing. + +## Custom markdown tags + +| Tag | Purpose | Example usage | +| --- | --- | --- | +| ```` | Responsive video embed with automatic MP4/WebM fallbacks. | ```` | +| ```` | Lightweight YouTube iframe wrapper. | ```` | +| ```` | Renders a colored skill badge inline with text. | ``Mastered React for the UI.`` | +| ```` | Generic icon button; set `kind="browser"`, `kind="download"`, etc. | ``Open in Firefox`` | +| ```` | GitHub button matching the component styling. | ``View on GitHub`` | +| ```` | LinkedIn call-to-action button. | ``Connect on LinkedIn`` | +| ```` | Neutral website button (covers the requested `` tag). | ``Portfolio`` | +| ```` / ```` / ```` / ```` | Download buttons with platform icons; `` accepts `os="windows"`. | ``Download for Windows`` | +| ```` / ```` | Browser-specific buttons that link to hosted demos. | ``Try in Chrome`` | +| ```` | Emphasized content block with optional `title` and `shadow`. | ``Ship early and iterate.`` | +| ```` | Mailto button that defaults to the resume email when `href` is omitted. | ``Get in touch`` | + +All of these tags are wired up inside [src/utils/markdownComponents.ts](src/utils/markdownComponents.ts), so you can review or extend them as needed. diff --git a/documentation/IMAGE_VIEWER_GUIDE.md b/documentation/IMAGE_VIEWER_GUIDE.md new file mode 100644 index 0000000..6f37dc0 --- /dev/null +++ b/documentation/IMAGE_VIEWER_GUIDE.md @@ -0,0 +1,306 @@ +# Image Viewer Component Documentation + +## Overview + +The Image Viewer is a production-ready React component that provides an elegant, accessible modal for viewing images with advanced features like zoom, pan, keyboard navigation, and touch gestures. + +## Features + +- **Zoom Control**: Mouse wheel, pinch gesture, and zoom buttons (1-5x magnification) +- **Pan**: Click and drag to move zoomed images +- **Keyboard Navigation**: Arrow keys to navigate gallery, +/- to zoom, ESC to close +- **Touch Gestures**: Pinch to zoom, swipe (via arrow key support) +- **Responsive Design**: Optimized for desktop, tablet, and mobile screens +- **Accessibility**: Full ARIA labels, focus management, keyboard support +- **Captions**: Optional titles and descriptions for images +- **Gallery Navigation**: Previous/Next buttons with counter display +- **Auto-Detection**: Automatically detects images with `image-viewer-trigger` class + +## Installation & Setup + +### CVWebpageTemplate + +The ImageViewer is already integrated via the `ImageViewerProvider` wrapper in `src/App.tsx`. No additional setup needed. + +### ERP_System + +The ImageViewer is integrated in `/apps/frontend/src/main.tsx` via the `ImageViewerProvider` wrapper. No additional setup needed. + +## Usage + +### Basic Usage (Automatic via CSS Class) + +Simply add the `image-viewer-trigger` class to any image element: + +```html +My photo +``` + +Clicking the image opens the viewer. If multiple images have this class, they form a gallery. + +### Markdown Integration + +For markdown-rendered content, images are automatically tagged. If you need to manually apply the viewer class: + +```tsx +import { ensureImageViewerClass } from '@/utils/imageViewerRenderers'; +import { useRef, useEffect } from 'react'; + +export function MarkdownPage() { + const contentRef = useRef(null); + + useEffect(() => { + if (contentRef.current) { + ensureImageViewerClass(contentRef.current); + } + }, []); + + return
{/* rendered markdown */}
; +} +``` + +### Manual Component Usage + +Use the `ImageViewer` component directly for custom image galleries: + +```tsx +import { ImageViewer, type ImageViewerImage } from '@/components/ImageViewer/ImageViewer'; +import { useState } from 'react'; + +export function GalleryPage() { + const [isOpen, setIsOpen] = useState(false); + const [currentIndex, setCurrentIndex] = useState(0); + + const images: ImageViewerImage[] = [ + { + src: '/photo1.jpg', + alt: 'Photo 1', + title: 'First Photo', + caption: 'This is the first photo in the gallery', + }, + { + src: '/photo2.jpg', + alt: 'Photo 2', + caption: 'Second photo', + }, + ]; + + return ( + <> + + + setIsOpen(false)} + onNavigate={setCurrentIndex} + /> + + ); +} +``` + +### Using the Hook + +For advanced use cases, use the `useImageViewer` hook: + +```tsx +import { useImageViewer } from '@/components/ImageViewer/useImageViewer'; +import { ImageViewer } from '@/components/ImageViewer/ImageViewer'; + +export function CustomGallery() { + const { isOpen, images, currentIndex, onClose, onNavigate } = useImageViewer(); + + return ( + + ); +} +``` + +## Component Props + +### ImageViewer Props + +```typescript +interface ImageViewerProps { + isOpen: boolean; // Whether the viewer is open + images: ImageViewerImage[]; // Array of images to display + initialIndex?: number; // Starting image index (default: 0) + onClose: () => void; // Callback when viewer closes + onNavigate?: (index: number) => void; // Callback when navigating +} +``` + +### ImageViewerImage Interface + +```typescript +interface ImageViewerImage { + src: string; // Image source URL (required) + alt?: string; // Alt text for accessibility + caption?: string; // Caption shown below image + title?: string; // Title shown above caption +} +``` + +## Keyboard Shortcuts + +| Key | Action | +|-----|--------| +| `ESC` | Close viewer | +| `←` / `→` | Previous / Next image | +| `+` / `-` | Zoom in / out | + +## Renderer Integration + +### Markdown Integration + +```tsx +import { extendMarkdownItWithImageViewer } from '@/utils/imageViewerRenderers'; + +// Apply to markdown-it instance +extendMarkdownItWithImageViewer(markdownIt); +``` + +### Post-Processing HTML + +```tsx +import { addImageViewerClassToHtml } from '@/utils/imageViewerRenderers'; + +const html = markdownIt.render(markdown); +const processedHtml = addImageViewerClassToHtml(html); +``` + +### React Hook for DOM Elements + +```tsx +import { useEnsureImageViewerClass } from '@/utils/imageViewerRenderers'; +import { useRef } from 'react'; + +export function MyComponent() { + const ref = useRef(null); + useEnsureImageViewerClass(ref); + + return
{/* content with images */}
; +} +``` + +## Styling & Customization + +The ImageViewer uses CSS Modules for scoped styling. To customize: + +1. Edit `ImageViewer.module.css` directly +2. Or extend by passing additional classes via CSS class modifications + +Key CSS classes: +- `.overlay` - Dark background overlay +- `.container` - Main viewer container +- `.image` - Image element +- `.controls` - Zoom control toolbar +- `.navButton` - Navigation arrow buttons +- `.closeButton` - Close button +- `.caption` - Caption container +- `.counter` - Image counter (X / Y) + +## Accessibility Features + +- **ARIA Labels**: All buttons have descriptive `aria-label` attributes +- **Keyboard Navigation**: Full keyboard support via shortcuts +- **Focus Management**: Focus returns to clicked image on close +- **Screen Readers**: Semantic HTML with proper roles +- **Touch Accessible**: Large touch targets on mobile (2rem minimum) + +## Performance Considerations + +- Uses CSS transforms for smooth animations +- Debounces zoom and pan operations +- Lazy loads images only when viewer opens +- Minimal re-renders via React hooks +- Responsive breakpoints (768px, 480px) for mobile optimization + +## Browser Support + +- Chrome/Edge: Latest versions +- Firefox: Latest versions +- Safari: Latest versions (including iOS Safari) +- Mobile browsers: iOS Safari, Chrome Mobile, Firefox Mobile + +## Examples + +### Gallery with Mixed Media + +```tsx +const galleryImages: ImageViewerImage[] = [ + { + src: '/images/screenshot1.png', + title: 'Application Overview', + caption: 'Main dashboard showing key metrics', + }, + { + src: '/images/diagram.svg', + title: 'Architecture Diagram', + caption: 'System components and their relationships', + }, + { + src: '/images/results.jpg', + title: 'Results', + caption: 'Final output visualization', + }, +]; +``` + +### Documentation Page with Images + +```tsx +export function DocPage() { + const contentRef = useRef(null); + useEnsureImageViewerClass(contentRef); + + return ( +
+

Product Guide

+ Usage guide +

Follow the steps shown above...

+
+ ); +} +``` + +## Troubleshooting + +### Images not opening viewer +- Ensure `image-viewer-trigger` class is added to img elements +- Check that ImageViewerProvider wraps the app +- Verify images have proper `src` attributes + +### Zoom/pan not working +- Make sure you're using a supported browser +- Try disabling browser extensions +- Check console for JavaScript errors + +### Images appear blurry when zoomed +- Use high-resolution source images (300+ DPI) +- Avoid excessive zoom levels (max 5x) +- Check image optimization settings + +## Architecture + +The component uses a Provider pattern for seamless integration: + +``` +ImageViewerProvider + ↓ wraps entire app + ↓ provides ImageViewer modal + useImageViewer hook + ↓ auto-detects images with 'image-viewer-trigger' class + ↓ renders modally above other content +``` + +This ensures the viewer works globally without prop drilling. diff --git a/documentation/IMAGE_VIEWER_QUICKREF.md b/documentation/IMAGE_VIEWER_QUICKREF.md new file mode 100644 index 0000000..6038201 --- /dev/null +++ b/documentation/IMAGE_VIEWER_QUICKREF.md @@ -0,0 +1,175 @@ +# Image Viewer Quick Reference + +## Quick Start + +### 1. Mark Images +Add class to any image: +```html + +``` + +### 2. Done! +Clicking opens the viewer. That's it! + +--- + +## Keyboard Controls + +| Key | Action | +|-----|--------| +| `ESC` | Close | +| `←` / `→` | Navigate | +| `+` / `-` | Zoom | + +--- + +## Mouse Controls + +| Action | Result | +|--------|--------| +| Click image | Open viewer | +| Scroll wheel | Zoom | +| Drag (zoomed) | Pan | +| Click previous/next | Navigate | +| Click close button | Close | + +--- + +## Touch Controls + +| Gesture | Result | +|---------|--------| +| Tap image | Open viewer | +| Pinch | Zoom | +| Tap previous/next | Navigate | +| Tap close button | Close | + +--- + +## Component API + +### React Component Usage +```tsx +import { ImageViewer } from '@/components/ImageViewer/ImageViewer'; + + +``` + +### Hook Usage +```tsx +import { useImageViewer } from '@/components/ImageViewer/useImageViewer'; + +const { isOpen, images, currentIndex, onClose, onNavigate } = useImageViewer(); +``` + +### Provider (Already in App!) +```tsx +// Already wraps the app + + + +``` + +--- + +## Image Data Structure + +```typescript +interface ImageViewerImage { + src: string; // Required: image URL + alt?: string; // Optional: alt text + caption?: string; // Optional: caption below image + title?: string; // Optional: title above caption +} +``` + +Example: +```tsx +const images: ImageViewerImage[] = [ + { + src: '/photo1.jpg', + alt: 'Photo 1', + title: 'My Title', + caption: 'Photo taken in 2024' + }, + // more images... +]; +``` + +--- + +## Renderer Integration + +### Markdown Images +Images in markdown are auto-tagged (no setup needed). + +### Manual HTML Processing +```tsx +import { addImageViewerClassToHtml } from '@/utils/imageViewerRenderers'; + +const html = markdownIt.render(md); +const processed = addImageViewerClassToHtml(html); // adds class +``` + +### DOM Elements +```tsx +import { useEnsureImageViewerClass } from '@/utils/imageViewerRenderers'; + +export function Page() { + const ref = useRef(null); + useEnsureImageViewerClass(ref); + + return
{/* content */}
; +} +``` + +--- + +## CSS Customization + +Main CSS file: `ImageViewer.module.css` + +Key classes to override: +- `.overlay` - Dark background +- `.controls` - Zoom toolbar +- `.image` - Image element +- `.navButton` - Navigation arrows +- `.closeButton` - Close button + +--- + +## Troubleshooting + +**Images not opening?** +- Ensure `image-viewer-trigger` class exists +- Check ImageViewerProvider wraps the app + +**Zoom not working?** +- Make sure you're using a supported browser +- Check console for errors + +**Blurry images?** +- Use higher resolution source images (300+ DPI) +- Don't zoom beyond 5x + +--- + +## Integration Status + +✅ **CVWebpageTemplate** - Ready to use +✅ **ERP_System Frontend** - Ready to use + +Both projects have the viewer integrated and building successfully. + +--- + +## Full Documentation + +See [IMAGE_VIEWER_GUIDE.md](./IMAGE_VIEWER_GUIDE.md) for comprehensive documentation. + +See [IMPLEMENTATION_SUMMARY.md](./IMPLEMENTATION_SUMMARY.md) for technical details. diff --git a/index.html b/index.html index e25f4d2..4d61b53 100644 --- a/index.html +++ b/index.html @@ -4,8 +4,8 @@ Personal CV - - + +
diff --git a/package-lock.json b/package-lock.json index 2a36a39..087ccc7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,11 @@ "version": "1.0.0", "dependencies": { "@asciidoctor/core": "^3.0.4", + "@heroicons/react": "^2.2.0", "@react-spring/web": "^10.0.3", "@viz-js/viz": "^3.26.0", "framer-motion": "^12.23.24", - "html-react-parser": "^6.0.1", + "html-react-parser": "^6.1.5", "react": "^18.0.0", "react-dom": "^18.0.0", "react-icons": "^4.12.0", @@ -29,6 +30,7 @@ "@types/react-dom": "^18.0.0", "@vitejs/plugin-react": "^5.0.4", "autoprefixer": "^10.4.0", + "baseline-browser-mapping": "^2.9.11", "postcss": "^8.4.0", "prettier": "^3.3.3", "sharp": "^0.33.5", @@ -390,9 +392,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz", - "integrity": "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", "cpu": [ "ppc64" ], @@ -407,9 +409,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.11.tgz", - "integrity": "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", "cpu": [ "arm" ], @@ -424,9 +426,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.11.tgz", - "integrity": "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", "cpu": [ "arm64" ], @@ -441,9 +443,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.11.tgz", - "integrity": "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", "cpu": [ "x64" ], @@ -458,9 +460,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.11.tgz", - "integrity": "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", "cpu": [ "arm64" ], @@ -475,9 +477,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.11.tgz", - "integrity": "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", "cpu": [ "x64" ], @@ -492,9 +494,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.11.tgz", - "integrity": "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", "cpu": [ "arm64" ], @@ -509,9 +511,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.11.tgz", - "integrity": "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", "cpu": [ "x64" ], @@ -526,9 +528,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.11.tgz", - "integrity": "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", "cpu": [ "arm" ], @@ -543,9 +545,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.11.tgz", - "integrity": "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", "cpu": [ "arm64" ], @@ -560,9 +562,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.11.tgz", - "integrity": "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", "cpu": [ "ia32" ], @@ -577,9 +579,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.11.tgz", - "integrity": "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", "cpu": [ "loong64" ], @@ -594,9 +596,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.11.tgz", - "integrity": "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", "cpu": [ "mips64el" ], @@ -611,9 +613,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.11.tgz", - "integrity": "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", "cpu": [ "ppc64" ], @@ -628,9 +630,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.11.tgz", - "integrity": "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", "cpu": [ "riscv64" ], @@ -645,9 +647,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.11.tgz", - "integrity": "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", "cpu": [ "s390x" ], @@ -662,9 +664,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.11.tgz", - "integrity": "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", "cpu": [ "x64" ], @@ -679,9 +681,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.11.tgz", - "integrity": "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", "cpu": [ "arm64" ], @@ -696,9 +698,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.11.tgz", - "integrity": "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", "cpu": [ "x64" ], @@ -713,9 +715,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.11.tgz", - "integrity": "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", "cpu": [ "arm64" ], @@ -730,9 +732,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.11.tgz", - "integrity": "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", "cpu": [ "x64" ], @@ -747,9 +749,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.11.tgz", - "integrity": "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", "cpu": [ "arm64" ], @@ -764,9 +766,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.11.tgz", - "integrity": "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", "cpu": [ "x64" ], @@ -781,9 +783,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.11.tgz", - "integrity": "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", "cpu": [ "arm64" ], @@ -798,9 +800,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.11.tgz", - "integrity": "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", "cpu": [ "ia32" ], @@ -815,9 +817,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.11.tgz", - "integrity": "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", "cpu": [ "x64" ], @@ -831,6 +833,15 @@ "node": ">=18" } }, + "node_modules/@heroicons/react": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz", + "integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==", + "license": "MIT", + "peerDependencies": { + "react": ">= 16 || ^19.0.0-rc" + } + }, "node_modules/@img/sharp-darwin-arm64": { "version": "0.33.5", "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", @@ -1992,9 +2003,9 @@ } }, "node_modules/@viz-js/viz": { - "version": "3.26.0", - "resolved": "https://registry.npmjs.org/@viz-js/viz/-/viz-3.26.0.tgz", - "integrity": "sha512-q/m3rj4aTs38yezZsWABRSsI1auZIWXk5X2aOdz9B7oPR1eYIn9JMEBXY/HbTJwhgS1vEOESC+m0eO6p3imFyQ==", + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@viz-js/viz/-/viz-3.27.0.tgz", + "integrity": "sha512-XjU8elLpbRieto/kHYWqT01yXDDYBTZB/Ny8ZUuheQw3Kc61I5Eto5+3Lb1PMdjEkh+DKA4Or5b2kxUY3cEfnA==", "license": "MIT" }, "node_modules/ansi-regex": { @@ -2106,9 +2117,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.17", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.17.tgz", - "integrity": "sha512-j5zJcx6golJYTG6c05LUZ3Z8Gi+M62zRT/ycz4Xq4iCOdpcxwg7ngEYD4KA0eWZC7U17qh/Smq8bYbACJ0ipBA==", + "version": "2.9.11", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", + "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2129,9 +2140,9 @@ } }, "node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -2476,9 +2487,9 @@ "license": "MIT" }, "node_modules/dom-serializer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-3.0.0.tgz", - "integrity": "sha512-x+9D6nkC8tdXOQUS32egtZpZFLP90+HBZmWjuT920srbJvD/zPgFB9t4k3pEhlw5BQrXStQtRc1Y1zuriXk+Nw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-3.1.1.tgz", + "integrity": "sha512-4MEa38/QexBob6gFNwu+EGdWvhJ1OKuNwdYY3Y3NyeWDQfnGeDYQUDfIRzWu5B5gsv03so2Uxd28YC6zrsx3Lw==", "license": "MIT", "dependencies": { "domelementtype": "^3.0.0", @@ -2576,9 +2587,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz", - "integrity": "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -2589,32 +2600,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.11", - "@esbuild/android-arm": "0.25.11", - "@esbuild/android-arm64": "0.25.11", - "@esbuild/android-x64": "0.25.11", - "@esbuild/darwin-arm64": "0.25.11", - "@esbuild/darwin-x64": "0.25.11", - "@esbuild/freebsd-arm64": "0.25.11", - "@esbuild/freebsd-x64": "0.25.11", - "@esbuild/linux-arm": "0.25.11", - "@esbuild/linux-arm64": "0.25.11", - "@esbuild/linux-ia32": "0.25.11", - "@esbuild/linux-loong64": "0.25.11", - "@esbuild/linux-mips64el": "0.25.11", - "@esbuild/linux-ppc64": "0.25.11", - "@esbuild/linux-riscv64": "0.25.11", - "@esbuild/linux-s390x": "0.25.11", - "@esbuild/linux-x64": "0.25.11", - "@esbuild/netbsd-arm64": "0.25.11", - "@esbuild/netbsd-x64": "0.25.11", - "@esbuild/openbsd-arm64": "0.25.11", - "@esbuild/openbsd-x64": "0.25.11", - "@esbuild/openharmony-arm64": "0.25.11", - "@esbuild/sunos-x64": "0.25.11", - "@esbuild/win32-arm64": "0.25.11", - "@esbuild/win32-ia32": "0.25.11", - "@esbuild/win32-x64": "0.25.11" + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" } }, "node_modules/escalade": { @@ -2812,7 +2823,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "deprecated": "Glob versions prior to v9 are no longer supported", "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -3023,9 +3034,9 @@ } }, "node_modules/html-dom-parser": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/html-dom-parser/-/html-dom-parser-7.0.1.tgz", - "integrity": "sha512-loRBDTCY/05/jAC63J1X9ID+xjRucmpLkIcQO0IRbOubBo5ucnpUpyXXob9UMXOskMZlu7KPsDP/2KOMelzJNA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/html-dom-parser/-/html-dom-parser-8.0.0.tgz", + "integrity": "sha512-cWLJ8mt930VceOzVfY/J+T1ou9v3ENT0BgWqy5aEZjdFp37TYLXULjX8u0vD2rBpFAkK5IofOPl0y4Gq0QLIyA==", "funding": [ { "type": "github", @@ -3039,9 +3050,9 @@ } }, "node_modules/html-react-parser": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/html-react-parser/-/html-react-parser-6.0.1.tgz", - "integrity": "sha512-tIie2HSIk2Ct1tdupjd/DhBjskxN/NL5J4ncbUnk2smBr5UIfpPpitUo0imGfBM0BlOL7ac8RcqEwne1jXTcsQ==", + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/html-react-parser/-/html-react-parser-6.1.5.tgz", + "integrity": "sha512-jhdRR0fSkdkVlAmDIk5lS+IMSjCgFSfe+nI69DUwaxkWOgid2mk7RhoYxsQ7B6hzCghAI5YPz2B1jbskHhbaLg==", "funding": [ { "type": "github", @@ -3055,9 +3066,9 @@ "license": "MIT", "dependencies": { "domhandler": "6.0.1", - "html-dom-parser": "7.0.1", + "html-dom-parser": "8.0.0", "react-property": "2.0.2", - "style-to-js": "1.1.21" + "style-to-js": "2.0.2" }, "peerDependencies": { "@types/react": "0.14 || 15 || 16 || 17 || 18 || 19", @@ -3069,6 +3080,30 @@ } } }, + "node_modules/html-react-parser/node_modules/inline-style-parser": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.9.tgz", + "integrity": "sha512-IttN8BqKLxzUrnTqHI+/hwAJQItBAUjdoHERCDv+oOuX48VP6bzW8+QjOXSfoevzq3YrYAwX1uLHp8TqO9yIeg==", + "license": "MIT" + }, + "node_modules/html-react-parser/node_modules/style-to-js": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-2.0.2.tgz", + "integrity": "sha512-ctgbFvvi406Jvhi/s6TJtX6XYYCCTg6POgsL+SLmrl2RHFgwulepqtYPrtKuEmT8kmyk/+fxfNzuhRLO4zsASg==", + "license": "MIT", + "dependencies": { + "style-to-object": "2.0.2" + } + }, + "node_modules/html-react-parser/node_modules/style-to-object": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-2.0.2.tgz", + "integrity": "sha512-QmbXPUylqU80HAgTOJeEOdbA40W9KtxmjMhQy+tq+b8F2aoh3aQLuy82hh2jm2qsC9hR9eI0+eQidTMydKcqRA==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.9" + } + }, "node_modules/html-url-attributes": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", @@ -3129,9 +3164,9 @@ "license": "ISC" }, "node_modules/inline-style-parser": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", - "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", "license": "MIT" }, "node_modules/is-alphabetical": { @@ -3599,9 +3634,9 @@ } }, "node_modules/mdast-util-to-hast": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", - "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -4241,9 +4276,9 @@ } }, "node_modules/minimatch": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", - "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -5125,21 +5160,21 @@ } }, "node_modules/style-to-js": { - "version": "1.1.21", - "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", - "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.18.tgz", + "integrity": "sha512-JFPn62D4kJaPTnhFUI244MThx+FEGbi+9dw1b9yBBQ+1CZpV7QAT8kUtJ7b7EUNdHajjF/0x8fT+16oLJoojLg==", "license": "MIT", "dependencies": { - "style-to-object": "1.0.14" + "style-to-object": "1.0.11" } }, "node_modules/style-to-object": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", - "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.11.tgz", + "integrity": "sha512-5A560JmXr7wDyGLK12Nq/EYS38VkGlglVzkis1JEdbGWSnbQIEhZzTJhzURXN5/8WwwFCs/f/VVcmkTppbXLow==", "license": "MIT", "dependencies": { - "inline-style-parser": "0.2.7" + "inline-style-parser": "0.2.4" } }, "node_modules/sucrase": { @@ -5176,9 +5211,9 @@ } }, "node_modules/sucrase/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "license": "ISC", "dependencies": { @@ -5638,13 +5673,13 @@ } }, "node_modules/vite": { - "version": "7.1.10", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.10.tgz", - "integrity": "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", + "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.25.0", + "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", diff --git a/package.json b/package.json index 539744e..07169cc 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,11 @@ "version": "1.0.0", "private": true, "scripts": { - "start": "vite", + "start": "npm run build-skills --silent || true; vite", "extract-data-example": "node scripts/extract-data-example.js", - "dev": "npm run extract-data-example --silent || true; vite", - "build": "npm run extract-data-example --silent || true; vite build", + "build-skills": "node scripts/build-skills.js", + "dev": "npm run extract-data-example --silent || true; npm run build-skills --silent || true; vite", + "build": "npm run extract-data-example --silent || true; npm run build-skills --silent || true; npm run multi-loc --silent || true; vite build", "preview": "vite preview --port 3001", "test": "react-scripts test", "eject": "react-scripts eject", @@ -15,14 +16,16 @@ "loc": "node scripts/count-loc.js", "multi-loc": "node scripts/run-multi-loc.js", "compress-videos": "node scripts/compress-videos.js", - "make-favicon": "node scripts/make-favicon.js" + "make-favicon": "node scripts/make-favicon.js", + "update-git-projects": "bash scripts/update-git-projects.sh" }, "dependencies": { "@asciidoctor/core": "^3.0.4", + "@heroicons/react": "^2.2.0", "@react-spring/web": "^10.0.3", "@viz-js/viz": "^3.26.0", "framer-motion": "^12.23.24", - "html-react-parser": "^6.0.1", + "html-react-parser": "^6.1.5", "react": "^18.0.0", "react-dom": "^18.0.0", "react-icons": "^4.12.0", @@ -39,6 +42,7 @@ "@types/react-dom": "^18.0.0", "@vitejs/plugin-react": "^5.0.4", "autoprefixer": "^10.4.0", + "baseline-browser-mapping": "^2.9.11", "postcss": "^8.4.0", "prettier": "^3.3.3", "sharp": "^0.33.5", diff --git a/public/index.html b/public/index.html index fa30b41..b4a1d5b 100644 --- a/public/index.html +++ b/public/index.html @@ -3,6 +3,7 @@ + Vite + React + TS