A modern, responsive website for Magnus Ohle, a web and advertising agency serving Tübingen and Reutlingen, Germany. Built with Next.js, TypeScript, and Tailwind CSS.
- Modern Design: Clean, professional design with smooth animations and transitions
- Responsive Layout: Optimized for all devices from mobile to desktop
- Performance Optimized: Fast loading times and optimized assets
- Accessibility: WCAG 2.1 AA compliant design
- Dark Mode Support: Automatic and manual theme switching
- SEO Friendly: Optimized for search engines with:
- Dynamic sitemap generation
- Customized robots.txt
- Structured data (JSON-LD)
- Comprehensive meta tags
- See SEO.md for details
- Multi-language: German content with potential for expansion
- Home: Agency introduction and service overview
- About: Company history, team, and values
- Services: Detailed service offerings
- Portfolio: Showcase of client projects
- Blog: Articles and insights
- Contact: Contact form and location information
- Legal Pages: Privacy Policy, Terms & Conditions, Accessibility Statement
- Framework: Next.js 14
- Language: TypeScript
- Styling: Tailwind CSS
- Animations: Framer Motion
- State Management: React Hooks
- Form Handling: React Hook Form (planned)
- Deployment: Vercel (recommended)
- Fixed funnel chart component to properly handle color picker and centered bar visualization settings
- Improved chart rendering and responsiveness
- Added detailed debugging logs for better troubleshooting
- Created comprehensive documentation for statistical chart components
- Node.js 18.17.0 or later
- npm or yarn
-
Clone the repository:
git clone https://github.com/Mvgnu/magnusohle.git cd byrlo-site -
Install dependencies:
npm install # or yarn install -
Run the development server:
npm run dev # or yarn dev -
Open http://localhost:3002 in your browser to see the result.
To deploy the site to production, we recommend using Vercel:
- Push your code to a GitHub repository
- Import the project in Vercel
- Configure your deployment settings
- Deploy
Alternatively, you can use the included deploy script to run the site:
./deploy.shThis will build and start the application on http://localhost:3002.
byrlo-site/
├── public/ # Static assets
├── src/
│ ├── app/ # Next.js app router pages
│ ├── components/ # Reusable UI components
│ ├── sections/ # Page sections
│ ├── lib/ # Utility functions and helpers
│ ├── styles/ # Global styles
│ └── types/ # TypeScript type definitions
├── next.config.ts # Next.js configuration
├── tailwind.config.ts # Tailwind CSS configuration
├── tsconfig.json # TypeScript configuration
└── package.json # Project dependencies and scripts
The project uses Tailwind CSS for styling. You can customize the design by modifying the tailwind.config.ts file.
Most of the content is defined directly in the page components. For dynamic content, you can integrate a headless CMS like Contentful, Sanity, or Strapi.
Replace the placeholder images with your own images in the appropriate components. Make sure to update the next.config.ts file if you're using external image sources.
The website is optimized for modern browsers:
- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
This project is licensed under the MIT License - see the LICENSE file for details.
For any questions or support, please contact:
- Email: hi@magnusohle.de
- Website: magnusohle.de
A collection of interactive and visually appealing components for enhancing blog posts with data visualizations, interactive elements, and rich media.
We've added several new components and made significant improvements to the codebase:
-
New Components:
SliderElement: A responsive image slider/carousel with autoplay and touch supportCalloutElement: Highlighted message boxes for important informationCodePreview: Code snippets with syntax highlighting and live previews- Box Plot Element: Added a new component for visualizing statistical distributions with quartiles, medians, and outliers
-
Key Improvements:
- Enhanced type safety across all components
- Better error handling with graceful fallbacks
- Consistent styling and dark mode support
- Improved accessibility with proper ARIA attributes
- Better mobile responsiveness
-
Bug Fixes:
- Fixed type errors in the
BlogElementsRenderer - Improved data handling to prevent "undefined is not an object" errors
- Fixed component rendering with empty or incomplete data
- Fixed Chart.js registration issue in BoxPlotElement where "bar" was not registered as an element
- Added proper TypeScript type handling for custom Chart.js properties
- Improved error handling throughout visualization components
- Fixed type errors in the
-
Performance Improvements:
- Enhanced data loading patterns across all components
- Created reusable hooks for common data operations
- Implemented better error boundaries for component stability
The Magnus Ohle blog components are designed to enhance blog posts with interactive data visualizations. Each component is built to be:
- Responsive: Works across all device sizes
- Interactive: Supports user interaction like hovering, clicking, and filtering
- Accessible: Follows accessibility guidelines for all users
- Customizable: Provides multiple options for styling and behavior
- Dark Mode Compatible: Looks great in both light and dark themes
An interactive force-directed graph component with customizable nodes and links, supports different layouts and interactive features.
Features:
- Force-directed, hierarchical, and circular layouts
- Node highlighting and selection
- Interactive zooming and panning
- Pros/cons display for selected nodes
- Draggable nodes
- Customizable colors and sizes
Props:
interface NodeDiagramProps {
title?: string;
description?: string;
nodes: Node[]; // Nodes with id, label, group, size, color, pros, cons
links: Link[]; // Links between nodes with optional strength and labels
width?: number;
height?: number;
layout?: 'force' | 'hierarchical' | 'circular';
showLabels?: boolean;
interactive?: boolean;
onNodeClick?: (node: Node) => void;
}Usage Example:
import { NodeDiagramElement } from '@/components/blog-elements';
const MyComponent = () => {
const nodes = [
{ id: '1', label: 'Node 1', group: 'A', pros: ['Fast', 'Reliable'], cons: ['Expensive'] },
{ id: '2', label: 'Node 2', group: 'B', pros: ['Cheap'], cons: ['Slow'] },
// ...more nodes
];
const links = [
{ source: '1', target: '2', strength: 0.5 },
// ...more links
];
return (
<NodeDiagramElement
title="System Architecture"
description="Diagram showing components and their relationships"
nodes={nodes}
links={links}
layout="force"
interactive={true}
showLabels={true}
/>
);
};A component for visualizing conversion funnels with abandonment reasons and optional comparison data.
Features:
- Visualization of conversion stages
- Detailed abandonment reasons with recommendations
- Comparison between different scenarios (e.g., mobile vs desktop)
- Interactive tooltips with additional information
- Tabular data view
Props:
interface FunnelChartElementProps {
title?: string;
description?: string;
data: {
stages: {
label: string;
value: number;
abandonmentReasons?: {
reason: string;
count: number;
percentage: number;
details?: string;
recommendations?: string[];
}[];
tooltip?: {
title: string;
content: string;
action?: {
label: string;
onClick: () => void;
};
};
}[];
comparison?: {
label: string;
stages: {
label: string;
value: number;
}[];
};
};
onLoad?: () => void;
}Usage Example:
import { FunnelChartElement } from '@/components/blog-elements';
const MyComponent = () => {
const funnelData = {
stages: [
{
label: 'Website Visits',
value: 10000,
tooltip: { title: 'Visits', content: 'Total number of website visits' }
},
{
label: 'Product Views',
value: 6000,
abandonmentReasons: [
{ reason: 'Navigation issues', count: 2000, percentage: 50,
recommendations: ['Improve site navigation', 'Add search functionality'] },
{ reason: 'Content quality', count: 2000, percentage: 50 }
]
},
// ...more stages
],
comparison: {
label: 'Last Month',
stages: [
{ label: 'Website Visits', value: 8000 },
{ label: 'Product Views', value: 4800 },
// ...more stages
]
}
};
return <FunnelChartElement
title="Conversion Funnel"
description="Analysis of customer journey through the sales funnel"
data={funnelData}
/>;
};A component for displaying word clouds with various customization options and interactive features.
Features:
- Various word shapes (circle, square, diamond, star, triangle)
- Category filtering
- Animated timeframes to show word frequency changes
- Interactive tooltips
- Customizable colors and sizes
Props:
interface WordCloudElementProps {
title?: string;
data: {
words: {
text: string;
value: number;
category?: string;
color?: string;
shape?: 'circle' | 'square' | 'diamond' | 'star' | 'triangle';
tooltip?: {
title: string;
content: string;
action?: {
label: string;
onClick: () => void;
};
};
}[];
timeframes?: {
label: string;
words: Word[];
}[];
};
shape?: 'circle' | 'rectangle' | 'custom';
onLoad?: () => void;
}Usage Example:
import { WordCloudElement } from '@/components/blog-elements';
const MyComponent = () => {
const wordCloudData = {
words: [
{ text: 'React', value: 100, category: 'Frontend', shape: 'circle' },
{ text: 'Node.js', value: 80, category: 'Backend', shape: 'square' },
{ text: 'TypeScript', value: 90, category: 'Language', shape: 'diamond' },
// ...more words
],
timeframes: [
{
label: '2020',
words: [
{ text: 'React', value: 70, category: 'Frontend' },
{ text: 'Node.js', value: 60, category: 'Backend' },
// ...more words
]
},
{
label: '2021',
words: [
{ text: 'React', value: 85, category: 'Frontend' },
{ text: 'Node.js', value: 70, category: 'Backend' },
// ...more words
]
},
// ...more timeframes
]
};
return <WordCloudElement title="Technology Trends" data={wordCloudData} />;
};A component for displaying heatmaps with various interactive features and customization options.
Features:
- Multiple color schemes
- Interactive zooming and panning
- Time-based animation for data changes
- Cell selection with highlighting
- Optional overlay images
- Detailed tooltips
Props:
interface HeatmapElementProps {
title?: string;
data: {
data: number[][];
xLabels: string[];
yLabels: string[];
title?: string;
xAxisLabel?: string;
yAxisLabel?: string;
timeframes?: {
label: string;
data: number[][];
}[];
overlayImage?: string;
colorScheme?: 'reds' | 'blues' | 'greens' | 'viridis' | 'custom';
customColors?: string[];
cellLabels?: string[][];
cellTooltips?: {
title: string;
content: string;
action?: {
label: string;
onClick: () => void;
};
}[][];
};
onLoad?: () => void;
}Usage Example:
import { HeatmapElement } from '@/components/blog-elements';
const MyComponent = () => {
const heatmapData = {
data: [
[10, 20, 30, 40],
[15, 25, 35, 45],
[5, 15, 25, 35]
],
xLabels: ['Q1', 'Q2', 'Q3', 'Q4'],
yLabels: ['Product A', 'Product B', 'Product C'],
colorScheme: 'blues',
cellTooltips: [
[
{ title: 'Q1 - Product A', content: '10 units sold' },
{ title: 'Q2 - Product A', content: '20 units sold' },
// ...more tooltips
],
// ...more rows
],
timeframes: [
{
label: '2020',
data: [
[8, 16, 24, 32],
[12, 20, 28, 36],
[4, 12, 20, 28]
]
},
{
label: '2021',
data: [
[10, 20, 30, 40],
[15, 25, 35, 45],
[5, 15, 25, 35]
]
},
// ...more timeframes
]
};
return <HeatmapElement title="Quarterly Sales" data={heatmapData} />;
};A component for visualizing flows between nodes in different categories, similar to a Sankey diagram. Perfect for showing relationships and transitions between distinct groups.
Features:
- Interactive nodes and links with tooltips
- Zoom and pan capability for exploring large diagrams
- Gradient-colored flows between nodes
- Responsive design that adapts to container width
- Customizable node widths, padding, and colors
- Error boundary protection against rendering failures
Props:
interface AlluvialDiagramElementProps {
data: AlluvialDiagramData;
title?: string;
description?: string;
width?: number;
height?: number;
textColor?: string;
backgroundColor?: string;
enableZoom?: boolean;
minZoom?: number;
maxZoom?: number;
}
interface AlluvialDiagramData {
nodes: AlluvialNode[];
links: AlluvialLink[];
nodeWidth?: number;
nodePadding?: number;
iterations?: number;
colors?: string[];
height?: number;
showLabels?: boolean;
showValues?: boolean;
title?: string;
subtitle?: string;
nodeAlign?: 'justify' | 'left' | 'right' | 'center';
}Usage Example:
import { AlluvialDiagramElement } from '@/components/blog-elements/statistics';
const MyComponent = () => {
const diagramData = {
nodes: [
{ id: 'client1', name: 'Indecisive Client', group: 'Clients' },
{ id: 'client2', name: 'Tech Startup', group: 'Clients' },
{ id: 'req1', name: 'Make It Pop', group: 'Requirements' },
{ id: 'req2', name: 'Like Apple But Different', group: 'Requirements' },
{ id: 'dev1', name: 'CSS Wizard', group: 'Developers' },
{ id: 'dev2', name: 'React Enthusiast', group: 'Developers' },
],
links: [
{ source: 'client1', target: 'req1', value: 30 },
{ source: 'client2', target: 'req2', value: 25 },
{ source: 'req1', target: 'dev1', value: 25 },
{ source: 'req2', target: 'dev2', value: 30 },
],
title: 'Web Design Project Flow',
height: 400,
nodeWidth: 15,
nodePadding: 10,
showLabels: true,
showValues: true
};
return (
<AlluvialDiagramElement
data={diagramData}
enableZoom={true}
backgroundColor="transparent"
/>
);
};For detailed documentation, please refer to README-ALLUVIAL-DIAGRAM.md.
The AlluvialDiagram component has been enhanced with the following improvements:
-
Flickering Prevention: Fixed issues with diagram flickering using:
- Debounced state updates
- Stable component keys
- Memoized expensive calculations
- Optimized rendering lifecycle
-
Error Handling: Enhanced error recovery with:
- Robust error boundaries
- Automatic retry mechanisms (up to 3 attempts)
- Detailed error reporting
- Fallback UI components
-
Debugging Tools: Added comprehensive debugging tools:
- Component lifecycle logging
- Performance measurement utilities
- Visual debug indicators
- State inspection helpers
-
Rendering Optimizations:
- React.memo for preventing unnecessary re-renders
- useMemo and useCallback for stable callbacks and values
- Optimized D3 operations with cleanup
For detailed debugging information, see README-ALLUVIAL-DIAGRAM-DEBUGGING.md.
A component for displaying code snippets with syntax highlighting and optional live previews.
- Syntax highlighting for multiple programming languages
- Tab interface to switch between code and preview
- Copy-to-clipboard functionality
- Dark mode compatibility
- Loading indicator for preview content
- Customizable styling
- Accessible with proper ARIA attributes
interface CodePreviewProps {
code: string;
language?: string;
title?: string;
previewComponent?: React.ReactNode;
defaultPreview?: boolean;
enablePreview?: boolean;
className?: string;
}// Basic usage with just code
<CodePreview
code="const greeting = 'Hello, world!';"
language="javascript"
title="Greeting Example"
/>
// With live preview
<CodePreview
code="<button className='bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded'>Click me</button>"
language="jsx"
title="Button Example"
previewComponent={<button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">Click me</button>}
enablePreview={true}
defaultPreview={false}
/>A responsive and accessible image slider/carousel with automatic slideshows, touch navigation, and keyboard controls.
- Smooth transitions between slides with fade effects
- Supports autoplay with configurable intervals
- Touch-enabled swipe gestures for mobile users
- Keyboard navigation (arrow keys and spacebar)
- Responsive design that works on all screen sizes
- Accessibility features with proper ARIA attributes
- Play/pause controls for autoplay
- Image captions support
- Dark mode compatibility
interface SliderImage {
src: string;
alt: string;
caption?: string;
}
interface SliderElementProps {
title?: string;
description?: string;
images: SliderImage[];
autoPlay?: boolean;
interval?: number; // in milliseconds
}const images = [
{
src: "/images/slide1.jpg",
alt: "Product overview",
caption: "Our flagship product in action"
},
{
src: "/images/slide2.jpg",
alt: "Product features",
caption: "Key features highlighted"
}
];
<SliderElement
title="Product Gallery"
description="Explore our product from all angles"
images={images}
autoPlay={true}
interval={5000}
/>A versatile callout component for displaying important messages, warnings, tips, and other highlighted content.
- Multiple callout types (info, success, warning, error, tip, code, note)
- Customizable icons for each callout type
- Consistent styling with appropriate colors for each type
- Dark mode compatibility
- Accessible with proper ARIA attributes
- Allows HTML content through dangerouslySetInnerHTML (use carefully)
type CalloutType = 'info' | 'success' | 'warning' | 'error' | 'tip' | 'code' | 'note';
interface CalloutElementProps {
title?: string;
type?: CalloutType;
content: string;
icon?: boolean;
className?: string;
}<CalloutElement
title="Important Note"
type="warning"
content="This action cannot be undone. Please make sure you want to proceed."
icon={true}
/>
<CalloutElement
type="tip"
content="You can use keyboard shortcuts to navigate faster."
/>
<CalloutElement
title="Code Example"
type="code"
content="const greeting = 'Hello, world!';"
/>To use these components in your blog posts, you can import them from the @/components/blog-elements directory.
import {
NodeDiagramElement,
FunnelChartElement,
WordCloudElement,
HeatmapElement
} from '@/components/blog-elements';
// Then use them in your component
const MyBlogPost = () => {
return (
<div>
<h1>My Blog Post</h1>
<p>Some text...</p>
<NodeDiagramElement
title="System Architecture"
nodes={nodes}
links={links}
/>
{/* More content... */}
<FunnelChartElement
title="Conversion Funnel"
description="Analysis of customer journey through the sales funnel"
data={funnelData}
/>
{/* More content... */}
</div>
);
};To install the project dependencies:
npm install
# or
yarn installTo start the development server:
npm run dev
# or
yarn devThis will start the development server at http://localhost:3000.
To build the project for production:
npm run build
# or
yarn buildTo run tests:
npm test
# or
yarn testAll components use a consistent color scheme based on Tübingen colors defined in @/constants/colors.ts. They're designed to work seamlessly in both light and dark mode, with proper contrast ratios for accessibility.
The components are built with responsive design in mind and will adapt to different screen sizes. On mobile, some interactive features may be simplified for better touch interaction.
This project is proprietary and owned by Magnus Ohle. All rights reserved.
- Q-Q Plot: Statistical visualization for distribution comparison
- Cartogram: Geographic data visualization with region distortion
- Ridgeline Plot: Distribution visualization across categories
- 3D Plots
- Surface plots for terrain and mathematical functions
- 3D scatter plots for multi-dimensional data
- Interactive rotation and zoom
- Full theme support
- Responsive design
- Contour plots for level sets and density visualization
- Advanced 3D plot features
- Custom color scales
- Multiple surface layers
- Advanced lighting and shading
- Export to 3D formats
- Network graphs for relationship visualization
- Sankey diagrams for flow visualization
- Chord diagrams for relationship matrices
- Minimal modernism with clean, uncluttered interfaces
- Consistent styling across all visualizations
- Full theme support (light/dark/responsive)
- Responsive design for all screen sizes
- Interactive features for enhanced data exploration
- Accessibility considerations for all components
- Performance optimization for 3D rendering
- Touch-friendly controls for mobile devices
The site includes a GDPR-compliant static map component that doesn't load any external data:
- No external map services (OpenStreetMap, Google Maps, etc.)
- No tracking or third-party cookies
- No consent required for displaying maps
- Full interactivity with purely client-side code
For detailed information about this implementation, see README-STATIC-MAP.md.
Usage:
<MapComponent
center={[48.52, 9.05]}
address="Haaggasse 10, 71070 Tübingen, Deutschland"
title="Magnus Ohle"
description="Webdesign & Entwicklung"
/>