This is a monorepo for XPRTZ websites built with Astro, consuming content from a Strapi CMS. It uses npm workspaces to manage multiple applications and shared libraries.
/website
├── apps/
│ ├── dotnet/ # Main .NET-focused website (xprtz.net)
│ └── learning/ # Learning platform for polyglot programming
├── libs/
│ ├── ui/ # Shared Astro/React UI components
│ └── cms/ # TypeScript types and API wrapper for Strapi
├── infrastructure/ # Infrastructure configuration
└── package.json # Root workspace configuration
The monorepo uses npm workspaces defined in the root package.json:
"workspaces": [
"apps/dotnet",
"apps/learning",
"libs/ui",
"libs/cms"
]- Apps:
@xprtz/dotnet,@xprtz/learning - Libraries:
@xprtz/ui,@xprtz/cms
Both apps depend on shared libraries using local file references:
"dependencies": {
"@xprtz/ui": "file:../../libs/ui",
"@xprtz/cms": "file:../../libs/cms"
}- Astro 5.16+ - Static site generator with islands architecture
- React 18.3+ - Client-side interactivity
- TypeScript 5.4+ - Type safety across the monorepo
- Tailwind CSS 3.4+ - Utility-first CSS framework
- Strapi CMS - Headless CMS for content management
@astrojs/tailwind- Tailwind integration@astrojs/react- React integration for interactive components@astrojs/sitemap- Sitemap generation@headlessui/react- Unstyled, accessible UI components@heroicons/react- Icon libraryembla-carousel- Carousel/slider librarymarked- Markdown parser
- Astro components:
PascalCase.astro(e.g.,Hero.astro,Footer.astro) - React components:
PascalCase.tsx(e.g.,Header.tsx) - TypeScript files:
camelCase.ts(e.g.,api.ts,page.ts) - Configuration files: Standard names (
package.json,astro.config.mjs,tsconfig.json)
- CMS models:
camelCase.tsinlibs/cms/models/ - Page components: File-based routing in
apps/*/src/pages/ - Layout files:
camelCase.astroinapps/*/src/layouts/ - Dynamic routes:
[param].astroor[...slug].astro
The system supports multiple sites using the PUBLIC_SITE environment variable:
"dotnet"- Main .NET-focused website (xprtz.net)"learning"- Learning platform for polyglot programming
All CMS queries filter content by site:
query: {
"filters[site][$eq]": import.meta.env.PUBLIC_SITE,
status: "published"
}PUBLIC_SITE- Site identifier ("dotnet" or "learning")PUBLIC_STRAPI_URL- Strapi CMS API base URLPUBLIC_IMAGES_URL- Image CDN base URL
const site = import.meta.env.PUBLIC_SITE || "no-site-found";
const imagesUrl = import.meta.env.PUBLIC_IMAGES_URL;npm run develop:dotnet # Start dotnet app dev server (port 3001)
npm run develop:learning # Start learning app dev server
npm run build # Build all workspaces
npm run build:dotnet # Build dotnet app only
npm run build:learning # Build learning app only
npm run build:ui # Type-check UI library
npm run format # Format and lint all filescd apps/dotnet
npm run develop # Start dev server
npm run build # Type-check and build
npm run preview # Preview production buildProvides shared TypeScript configuration for all workspaces with strict mode enabled.
- Use
.jsextensions in imports (TypeScript ESM requirement) - Type-only imports:
import { type Hero } from "@xprtz/cms" - Component imports:
import { ComponentRenderer } from "@xprtz/ui"
- Prettier for formatting (config in .prettierrc.json)
- ESLint for linting (config in eslint.config.mjs)
- TypeScript strict mode enabled
- External dependencies
- Workspace packages (
@xprtz/*) - Relative imports
- Functions:
camelCase(e.g.,fetchData,formatDate) - Types:
PascalCase(e.g.,Hero,Article,Page) - Constants:
UPPER_SNAKE_CASE(e.g.,CAROUSEL_CONFIG) - CSS classes: Tailwind utilities + custom kebab-case
- Current branch:
feature/technology-radar - Recent commits focus on radar chart animation improvements and accessibility enhancements
- Fixed tile zoom and list slide animations for mobile view
The Astro dev server may not properly hot-reload CSS changes from workspace dependencies (@xprtz/ui, @xprtz/cms). This has been resolved by configuring Vite in each app's astro.config.mjs:
export default defineConfig({
vite: {
server: {
watch: {
// Force Vite to watch workspace dependencies
ignored: ['!**/node_modules/@xprtz/ui/**']
}
},
optimizeDeps: {
// Prevent pre-bundling of workspace dependencies
exclude: ['@xprtz/ui']
}
}
})This configuration ensures that changes to UI library files trigger proper hot reloads without requiring dev server restarts.
The Technology Radar chart (RadarChart.astro) is a complex interactive component that displays technology items organized by quadrants and rings. It has different behaviors for desktop and mobile viewports.
- Desktop (>1000px): Full radar chart with quadrants that can zoom
- Mobile (≤1000px): Colored tiles representing each quadrant
- Hover Effect: Non-hovered quadrants dim to 50% opacity
- Zoom In:
- Clicked quadrant scales to 1.75x and centers
- Other quadrants fade to opacity 0
- Ring labels transform with the zoomed quadrant
- Duration: 500ms
- List Slide:
- After zoom, wrapper translates left by 200px
- Item list slides in from right
- Duration: 400ms
The mobile animations were redesigned to create smooth cross-slide transitions:
-
Opening (Tile → List):
- Tile zooms in via
tileZoomInkeyframe (scale 2.05) - 500ms - After zoom: Tile slides left AND fades out while list slides in from right - 400ms
- Both elements pass each other smoothly
- After slide completes, tile becomes
display: none
- Tile zooms in via
-
Closing (List → Tile):
- Tile positioned off-screen left with opacity 0
- List slides right while tile slides in from left AND fades in - 400ms
- After slide: Tile zooms out via
tileZoomOutkeyframe - 500ms
.sliding-out: Tiles slide left withtranslateX(-100%)and fade toopacity: 0. For lists, triggersslideOutkeyframe animation.sliding-in: Tiles positioned attranslateX(-100%)ready to slide in.zooming-in: TriggerstileZoomInkeyframe animation on tiles.zooming-out: TriggerstileZoomOutkeyframe animation on tiles.list-visible: Triggers listslideInkeyframe animation.list-animation-complete: Switches list toposition: staticafter animation
All mobile animations use CSS keyframe animations instead of transitions for better mobile compatibility:
- tileZoomIn keyframe: Animates tile from
scale(1)toscale(2.05)- defined in RadarChart.astro - tileZoomOut keyframe: Animates tile from
scale(2.05)toscale(1)- defined in RadarChart.astro - slideIn keyframe: Animates list from
translateX(100%)opacity 0 totranslateX(0)opacity 1 - defined in RadarQuadrantItemList.astro - slideOut keyframe: Animates list from
translateX(0)opacity 1 totranslateX(100%)opacity 0 - defined in RadarQuadrantItemList.astro - Keyframes are more reliable on mobile devices than CSS transitions
Defined in RadarChart.astro:200-205:
--fade-out-duration: 300ms;
--fade-out-delay: 100ms;
--fade-in-duration: 400ms;
--zoom-duration: 500ms;
--slide-duration: 400ms;
--transition-easing: cubic-bezier(0.4, 0, 0.2, 1);- DO NOT change the
position: staticrule for.list-animation-completeon mobile - this is required to prevent content overlap - List positioning uses
left: 50%withmargin-left: -192px(half of 384px width) to ensure proper centering during animation
- RadarQuadrant.astro - Individual quadrant rendering
- RadarQuadrantItemList.astro - Item list with slide animations
- radarUtils.ts - Shared constants and utilities
- UI components go in
libs/ui/src/ - CMS models go in
libs/cms/models/ - Export new components/types from respective
index.tsfiles - Register new UI components in
ComponentRenderer.astroif they're CMS-driven - Follow existing patterns for file naming and code structure
- Create type definition in
libs/cms/models/ - Export from
libs/cms/index.ts - Create corresponding Astro component in
libs/ui/src/ - Export from
libs/ui/index.ts - Add to
ComponentRenderer.astromapping if applicable - Ensure Strapi CMS has matching content type
- Start with understanding the CMS model structure
- Create or update TypeScript types in
libs/cms - Create or update UI components in
libs/ui - Use components in apps via
ComponentRendereror direct imports - Test with both dotnet and learning sites to ensure multi-tenant compatibility