add videovault
This commit is contained in:
parent
2943ace31b
commit
5e09ae35f0
8 changed files with 61 additions and 124 deletions
BIN
src/assets/projects/videovault/dashboard.png
Normal file
BIN
src/assets/projects/videovault/dashboard.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 247 KiB |
BIN
src/assets/projects/videovault/edit.png
Normal file
BIN
src/assets/projects/videovault/edit.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 835 KiB |
BIN
src/assets/projects/videovault/frontpage.png
Normal file
BIN
src/assets/projects/videovault/frontpage.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.7 MiB |
BIN
src/assets/projects/videovault/player.png
Normal file
BIN
src/assets/projects/videovault/player.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
|
|
@ -1,32 +1,19 @@
|
||||||
---
|
---
|
||||||
// ──────────────────────────────────────────────────────────────
|
|
||||||
// Types & Props
|
|
||||||
// ──────────────────────────────────────────────────────────────
|
|
||||||
import type { ImageMetadata } from "astro";
|
import type { ImageMetadata } from "astro";
|
||||||
|
import { Icon } from "astro-icon/components";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
/** Array of images for the carousel */
|
|
||||||
images: {
|
images: {
|
||||||
/** Astro image metadata – we only need the `src` field */
|
|
||||||
src: ImageMetadata;
|
src: ImageMetadata;
|
||||||
/** Alt text for the image (required for accessibility) */
|
|
||||||
alt: string;
|
alt: string;
|
||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const { images } = Astro.props;
|
const { images } = Astro.props;
|
||||||
|
|
||||||
// ──────────────────────────────────────────────────────────────
|
|
||||||
// Unique ID – needed so multiple carousels on the same page don’t
|
|
||||||
// clash when we query the DOM from the <script> block.
|
|
||||||
// ──────────────────────────────────────────────────────────────
|
|
||||||
const carouselId = `carousel-${Math.random().toString(36).slice(2, 11)}`;
|
const carouselId = `carousel-${Math.random().toString(36).slice(2, 11)}`;
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- ──────────────────────────────────────────────────────────────
|
|
||||||
Carousel markup – all styling is done with Tailwind classes.
|
|
||||||
The outer <section> gets the proper ARIA roles/labels.
|
|
||||||
────────────────────────────────────────────────────────────── -->
|
|
||||||
<section
|
<section
|
||||||
id={carouselId}
|
id={carouselId}
|
||||||
class="relative overflow-hidden rounded-lg"
|
class="relative overflow-hidden rounded-lg"
|
||||||
|
|
@ -44,7 +31,7 @@ const carouselId = `carousel-${Math.random().toString(36).slice(2, 11)}`;
|
||||||
src={image.src.src}
|
src={image.src.src}
|
||||||
alt={image.alt}
|
alt={image.alt}
|
||||||
class={`
|
class={`
|
||||||
absolute inset-0 w-full h-full object-cover
|
absolute inset-0 w-full h-full object-cover object-top
|
||||||
transition-opacity duration-500 ease-in-out
|
transition-opacity duration-500 ease-in-out
|
||||||
${i === 0 ? "opacity-100" : "opacity-0"}
|
${i === 0 ? "opacity-100" : "opacity-0"}
|
||||||
carousel-slide
|
carousel-slide
|
||||||
|
|
@ -67,19 +54,7 @@ const carouselId = `carousel-${Math.random().toString(36).slice(2, 11)}`;
|
||||||
aria-label="Previous slide"
|
aria-label="Previous slide"
|
||||||
data-dir="prev"
|
data-dir="prev"
|
||||||
>
|
>
|
||||||
<!-- Heroicon: chevron‑left -->
|
<Icon name="mdi:chevron-left" class="h-5 w-5" />
|
||||||
<svg
|
|
||||||
class="h-5 w-5"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke="currentColor"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
<span class="sr-only">Previous</span>
|
<span class="sr-only">Previous</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|
@ -94,19 +69,7 @@ const carouselId = `carousel-${Math.random().toString(36).slice(2, 11)}`;
|
||||||
aria-label="Next slide"
|
aria-label="Next slide"
|
||||||
data-dir="next"
|
data-dir="next"
|
||||||
>
|
>
|
||||||
<!-- Heroicon: chevron‑right -->
|
<Icon name="mdi:chevron-right" class="h-5 w-5" />
|
||||||
<svg
|
|
||||||
class="h-5 w-5"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke="currentColor"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
<span class="sr-only">Next</span>
|
<span class="sr-only">Next</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|
@ -120,11 +83,6 @@ const carouselId = `carousel-${Math.random().toString(36).slice(2, 11)}`;
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- ──────────────────────────────────────────────────────────────
|
|
||||||
Tailwind utilities are already available in the project.
|
|
||||||
No extra CSS is required – everything lives in the classes above.
|
|
||||||
────────────────────────────────────────────────────────────── -->
|
|
||||||
|
|
||||||
<script define:vars={{ carouselId }}>
|
<script define:vars={{ carouselId }}>
|
||||||
// -----------------------------------------------------------------
|
// -----------------------------------------------------------------
|
||||||
// Carousel logic – runs once the component is in the DOM.
|
// Carousel logic – runs once the component is in the DOM.
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,14 @@
|
||||||
---
|
---
|
||||||
import { Icon } from "astro-icon/components";
|
import { Icon } from "astro-icon/components";
|
||||||
import { projects } from "../consts";
|
import { type Project } from "../consts";
|
||||||
import Carousel from "./Carousel.astro";
|
import Carousel from "./Carousel.astro";
|
||||||
import ThreeColumnSection from "./ThreeColumnSection.astro";
|
import ThreeColumnSection from "./ThreeColumnSection.astro";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
projects: Project[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { projects } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<section class="py-16 bg-mytheme-200/70 dark:bg-mytheme-700/50">
|
<section class="py-16 bg-mytheme-200/70 dark:bg-mytheme-700/50">
|
||||||
|
|
|
||||||
123
src/consts.ts
123
src/consts.ts
|
|
@ -3,6 +3,10 @@ import optimizer1 from "./assets/projects/optimizer/Discretize-Gear-Optimizer-08
|
||||||
import optimizer2 from "./assets/projects/optimizer/Discretize-Gear-Optimizer-08-05-2025_11_53_AM.png";
|
import optimizer2 from "./assets/projects/optimizer/Discretize-Gear-Optimizer-08-05-2025_11_53_AM.png";
|
||||||
import optimizer3 from "./assets/projects/optimizer/Discretize-Gear-Optimizer-08-05-2025_11_54_AM.png";
|
import optimizer3 from "./assets/projects/optimizer/Discretize-Gear-Optimizer-08-05-2025_11_54_AM.png";
|
||||||
import optimizer4 from "./assets/projects/optimizer/Discretize-Gear-Optimizer-08-05-2025_11_55_AM.png";
|
import optimizer4 from "./assets/projects/optimizer/Discretize-Gear-Optimizer-08-05-2025_11_55_AM.png";
|
||||||
|
import videovault_dashboard from "./assets/projects/videovault/dashboard.png";
|
||||||
|
import videovault_edit from "./assets/projects/videovault/edit.png";
|
||||||
|
import videovault_frontpage from "./assets/projects/videovault/frontpage.png";
|
||||||
|
import videovault_player from "./assets/projects/videovault/player.png";
|
||||||
|
|
||||||
export const SITE_TITLE = "Alex Daichendt";
|
export const SITE_TITLE = "Alex Daichendt";
|
||||||
export const SITE_DESCRIPTION =
|
export const SITE_DESCRIPTION =
|
||||||
|
|
@ -10,6 +14,7 @@ export const SITE_DESCRIPTION =
|
||||||
|
|
||||||
export interface Project {
|
export interface Project {
|
||||||
title: string;
|
title: string;
|
||||||
|
featured: boolean;
|
||||||
live_url?: string;
|
live_url?: string;
|
||||||
repo_url?: string;
|
repo_url?: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
|
@ -24,91 +29,59 @@ export interface Project {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const projects: Project[] = [
|
export const projects: Project[] = [
|
||||||
// {
|
|
||||||
// title: "daichendt.one (this site)",
|
|
||||||
// live_url: "https://www.youtube.com/watch?v=XfELJU1mRMg",
|
|
||||||
// repo_url: "https://github.com/AlexDaichendt/site",
|
|
||||||
// description: "Personal website and blog.",
|
|
||||||
// tech_stack: ["Astro", "Tailwind CSS"],
|
|
||||||
// duration: "2022 - Present",
|
|
||||||
// deliverables: [
|
|
||||||
// "Cloudflare Workers & KV Integration",
|
|
||||||
// "Lightweight & easy to maintain",
|
|
||||||
// ],
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// title: "Radio Player",
|
|
||||||
// live_url: "https://video.taxi/en/functions/simultaneous-interpreting/",
|
|
||||||
// description: "A radio player for the VIDEO.TAXI website. Allows users to listen to radio stations with dubbed audio with the voice of the original speaker.",
|
|
||||||
// tech_stack: ["React", "TypeScript", "Vite", "Go", "Docker"],
|
|
||||||
// duration: "2024 - Present",
|
|
||||||
// complexity: 4,
|
|
||||||
// company: "TV1 GmbH",
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
title: "VIDEO.TAXI Meetings",
|
title: "VideoVault",
|
||||||
live_url: "https://meetings.video.taxi/",
|
featured: true,
|
||||||
|
live_url: "https://videovault.shiverpeak.xyz/",
|
||||||
|
repo_url: "https://github.com/AlexDaichendt/VideoVault",
|
||||||
description:
|
description:
|
||||||
"Records, transcribes, and translates meetings (Webex, Teams, Zoom). Interfaces with the GraphQL API of VIDEO.TAXI. Architecure, development, and deployment all done by me. Greenfield project.",
|
"A private, self-hosted video vault for your personal use. Supports multiple video transcodes, search, and is blazingly fast thanks to a backend written in Rust and a zero-js (vanilla) frontend.",
|
||||||
tech_stack: [
|
tech_stack: [
|
||||||
"Svelte",
|
"Rust",
|
||||||
"TypeScript",
|
"Axum",
|
||||||
|
"sqlx",
|
||||||
|
"Tera Templates",
|
||||||
"Tailwind CSS",
|
"Tailwind CSS",
|
||||||
"Express",
|
"Node.js",
|
||||||
"GraphQL",
|
"pnpm",
|
||||||
"PostgreSQL",
|
"cargo",
|
||||||
"Docker",
|
"ffmpeg",
|
||||||
"OpenAPI",
|
|
||||||
],
|
],
|
||||||
duration: "2024 - Present",
|
duration: "2025 - Present",
|
||||||
company: "TV1 GmbH",
|
|
||||||
deliverables: [
|
deliverables: [
|
||||||
"Live Updating Dashboard",
|
"HLS Streaming of videos",
|
||||||
"Meeting Bots for Teams, Zoom and Webex",
|
"Video Scrubbing",
|
||||||
"Keycloak integration",
|
"Aspect Ratio Awareness",
|
||||||
|
"Multiple Video Transcodes",
|
||||||
|
"External Transcoding Workers",
|
||||||
|
"Responsive, lightweight design",
|
||||||
|
"User Accounts via local, LDAP or OIDC",
|
||||||
|
"Sharing with timestamps",
|
||||||
|
"Quick and simple deployment (Docker)",
|
||||||
|
],
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
alt: "Frontpage",
|
||||||
|
src: videovault_frontpage,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
alt: "VideoVault Dashboard",
|
||||||
|
src: videovault_dashboard,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
alt: "VideoVault Video Player",
|
||||||
|
src: videovault_player,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
alt: "VideoVault Edit",
|
||||||
|
src: videovault_edit,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
images: [],
|
|
||||||
},
|
},
|
||||||
// {
|
|
||||||
// title: "Netbox PDU Plugin",
|
|
||||||
// repo_url: "https://github.com/AlexDaichendt/axians-netbox-plugin-pdu",
|
|
||||||
// description:
|
|
||||||
// "Netbox plugin to read out power usage of PDUs. Forked and maintained from the original plugin by Axians. Used in production by multiple companies.",
|
|
||||||
// tech_stack: ["Python", "Django", "Netbox"],
|
|
||||||
// duration: "2023 - Present",
|
|
||||||
// complexity: 4,
|
|
||||||
// company: "TV1 GmbH",
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// title: "Latency IRQ Analyzer",
|
|
||||||
// repo_url: "https://github.com/AlexDaichendt/latency-irq-analyzer",
|
|
||||||
// description:
|
|
||||||
// "Quick uni project for overlaying latency files with IRQ data.",
|
|
||||||
// tech_stack: ["NodeJS", "Highcharts"],
|
|
||||||
// duration: "2024",
|
|
||||||
// complexity: 2,
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// title: "Netbox Agent",
|
|
||||||
// description:
|
|
||||||
// "Reads out lshw, dmidecode and other data of a server and creates Netbox devices. Forked from the original project.",
|
|
||||||
// tech_stack: ["Python"],
|
|
||||||
// duration: "2023 - Present",
|
|
||||||
// complexity: 2,
|
|
||||||
// company: "TV1 GmbH",
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// title: "magewell-exporter",
|
|
||||||
// repo_url: "https://github.com/TV1-EU/magewell-exporter",
|
|
||||||
// description:
|
|
||||||
// "Prometheus exporter for Magewell AiO encoders. Allows monitoring of the capture card status and video signal. Used in production by TV1.",
|
|
||||||
// tech_stack: ["NodeJs", "Typescript"],
|
|
||||||
// duration: "2023",
|
|
||||||
// complexity: 4,
|
|
||||||
// company: "TV1 GmbH",
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
title: "Discretize: Gear Optimizer",
|
title: "Discretize: Gear Optimizer",
|
||||||
|
featured: true,
|
||||||
live_url: "https://optimizer.discretize.eu/",
|
live_url: "https://optimizer.discretize.eu/",
|
||||||
repo_url: "https://github.com/discretize/discretize-gear-optimizer",
|
repo_url: "https://github.com/discretize/discretize-gear-optimizer",
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ import Link from "../components/Link.astro";
|
||||||
import Picture from "../components/Picture.astro";
|
import Picture from "../components/Picture.astro";
|
||||||
import ProjectSection from "../components/ProjectSection.astro";
|
import ProjectSection from "../components/ProjectSection.astro";
|
||||||
import ThreeColumnSection from "../components/ThreeColumnSection.astro";
|
import ThreeColumnSection from "../components/ThreeColumnSection.astro";
|
||||||
|
import { projects } from "../consts";
|
||||||
import BaseLayout from "../layouts/BaseLayout.astro";
|
import BaseLayout from "../layouts/BaseLayout.astro";
|
||||||
|
|
||||||
const love = [
|
const love = [
|
||||||
{
|
{
|
||||||
emoji: "🦀",
|
emoji: "🦀",
|
||||||
|
|
@ -142,5 +142,5 @@ const skills = {
|
||||||
<div slot="right"></div>
|
<div slot="right"></div>
|
||||||
</ThreeColumnSection>
|
</ThreeColumnSection>
|
||||||
|
|
||||||
<ProjectSection />
|
<ProjectSection projects={projects.filter((project) => project.featured)} />
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue