---
title: "Views and Templates Category"
description: "Views and Templates"
type: "docs"
category: "doc"
tags: []
authors: [Anonymous]
date: "2026-09-09"
last_update: "2026-09-09"
time_minutes: 1
draft: false
unlisted: false
url: "https://www.derafu.dev/docs/ui"
---

# Views and Templates



---

## Renderer Project

Unified Template Rendering Made Simple For PHP

# Unified Template Rendering Made Simple For PHP

![GitHub last commit](https://img.shields.io/github/last-commit/derafu/renderer/main)
![CI Workflow](https://github.com/derafu/renderer/actions/workflows/ci.yml/badge.svg?branch=main&amp;event=push)
![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/derafu/renderer)
![GitHub Issues](https://img.shields.io/github/issues-raw/derafu/renderer)
![Total Downloads](https://poser.pugx.org/derafu/renderer/downloads)
![Monthly Downloads](https://poser.pugx.org/derafu/renderer/d/monthly)

A modern, flexible PHP template rendering library that provides a unified interface for multiple template engines and output formats.

## Features

- 🔄 **Unified Interface**: One consistent API for all template engines.
- 🚀 **Multiple Engine Support**: Works with Twig, PHP, Markdown and more.
- 📄 **Multiple Output Formats**: Generate HTML, PDF from any template.
- 🔌 **Extensible Architecture**: Easy to add new engines and formats.
- 🎨 **Powerful Formatting System**: Format data consistently across all templates.
- 🛡️ **Secure by Design**: Safe template rendering and file handling.
- 🪶 **Lightweight Core**: Only load what you need.
- ⚡ **Framework Agnostic**: Use with any PHP framework.

## Why Derafu\Renderer?

Traditional template systems often lock you into a single engine or require different handling for each format. Derafu\Renderer solves this by providing:

- A single, clean API for all template engines.
- Seamless switching between output formats.
- Consistent data formatting across all templates.
- Framework-agnostic design.
- Easy integration with existing systems.

## Installation

Install via Composer:

```bash
composer require derafu/renderer
```

## Basic Usage

```php
use Derafu\Renderer\Factory\RendererFactory;

// Create renderer with engines Twig and PDF.
$renderer = RendererFactory::create([
    &#039;engines&#039; =&gt; [&#039;twig&#039;, &#039;pdf&#039;],
    &#039;paths&#039; =&gt; [&#039;/path/to/templates&#039;],
]);

// Render templates in different engines.
$html = $renderer-&gt;render(&#039;template.html.twig&#039;, [&#039;name&#039; =&gt; &#039;John&#039;]);
$pdf = $renderer-&gt;render(&#039;template.html.twig&#039;, [&#039;name&#039; =&gt; &#039;John&#039;], [&#039;engine&#039; =&gt; &#039;pdf&#039;]);
```

## Template Engines

### Twig Templates
```php
// template.html.twig
&lt;h1&gt;Hello {{ name }}!&lt;/h1&gt;
&lt;p&gt;Today is {{ date|format_as(&#039;date.long&#039;) }}&lt;/p&gt;
```

### PHP Templates
```php
// template.php
&lt;h1&gt;Hello &lt;?= $name ?&gt;!&lt;/h1&gt;
&lt;p&gt;Today is &lt;?= $format_as($date, &#039;date.long&#039;) ?&gt;&lt;/p&gt;
```

### Markdown Templates
```markdown
# Hello {{ name }}!

Today is {{ date }}
```

## Advanced Usage

### Custom Engine Configuration
```php
$renderer = RendererFactory::create([
    &#039;engines&#039; =&gt; [&#039;twig&#039;, &#039;markdown&#039;, &#039;pdf&#039;],
    &#039;paths&#039; =&gt; [&#039;/path/to/templates&#039;],
    &#039;formatters&#039; =&gt; [
        &#039;date&#039; =&gt; function (string $date): string {
            $timestamp = strtotime($date);
            return date(&#039;d/m/Y&#039;, $timestamp);
        },
    ],
]);
```




---

## Twig Project

Derafu Twig

# Derafu Twig




---

### Introduction

UI Component and Extension Library

# UI Component and Extension Library

![GitHub last commit](https://img.shields.io/github/last-commit/derafu/twig/main)
![CI Workflow](https://github.com/derafu/twig/actions/workflows/ci.yml/badge.svg?branch=main&amp;event=push)
![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/derafu/twig)
![GitHub Issues](https://img.shields.io/github/issues-raw/derafu/twig)
![Total Downloads](https://poser.pugx.org/derafu/twig/downloads)
![Monthly Downloads](https://poser.pugx.org/derafu/twig/d/monthly)

**Derafu Twig** is a comprehensive UI component library for Twig, designed to work standalone or with any PHP framework. Built with Bootstrap 5 and Font Awesome 6, it provides a collection of reusable, customizable components for rapid web development.

## Features

- 🎨 **Rich Component Library**: Extensive collection of UI components including headers, footers, cards, grids, and more.
- 🎯 **Framework Agnostic**: Works with any PHP framework or standalone Twig.
- 🔧 **Highly Customizable**: Support for Bootstrap CSS variables for easy styling.
- 📱 **Responsive Design**: All components are mobile-first and fully responsive.
- 🧩 **Modular Architecture**: Components can be used independently or combined.
- 📦 **Bootstrap 5 Integration**: Leverages Bootstrap&#039;s grid system and utilities.
- 🎟️ **Font Awesome 6**: Integrated icon support.
- 🏷️ **MIT License**: Open-source and free to use.

## Component Categories

### Block Components

- **Layout**: Header, Footer, Grid.
- **Content**: Cards, Features (Grid, List, Icon, Table), Text with Image/Video.
- **Navigation**: Tabs, Steps, Timeline.
- **Interaction**: Accordion, CTA (Call to Action).
- **Showcasing**: Team, Testimonials, Hero, Image.
- **Comparison**: Tables, Boxes, Comparison grids.

## Extensions

- **Markdown**: render markdown content with `markdown` filter.
- **Translation**: `trans`/`t()`/`{% trans %}`/`{% trans_default_domain %}` —
  see [Translations](https://www.derafu.dev/docs/ui/twig/translations).

## Installation

Install the library using Composer:

```bash
composer require derafu/twig
```

## Basic Usage

1. Create the Twig environment with `TwigService` or register the components with `ComponentRegistrar`:

```php
use Derafu\Twig\Service\TwigService;

$options = [
    &#039;paths&#039; =&gt; [
        __DIR__ . &#039;/../templates&#039;,  // Path to the derafu-twig templates.
        __DIR__ . &#039;/pages&#039;,         // Path to your templates.
    ],
];

$twigService = new TwigService($options);

echo $twigService-&gt;render(&#039;example.html.twig&#039;);
```

2. Use components in your Twig templates:

```twig
{# Example using a card grid component #}
&lt;twig:block-card-grid
    :cols=&quot;3&quot;
    :cards=&quot;[
        {
            image: &#039;path/to/image.jpg&#039;,
            title: &#039;Card Title&#039;,
            content: &#039;Card content text&#039;,
            buttonText: &#039;Learn More&#039;,
            buttonUrl: &#039;#&#039;
        }
    ]&quot;
/&gt;
```

## Key Concepts

### Component Structure

Each component follows a consistent structure:

- PHP Class: Defines component properties and logic.
- Twig Template: Handles component rendering.
- CSS: Component-specific styles.

## Roadmap

- 🔌 More component variations.
- 📱 Enhanced mobile optimizations.
- 🎨 Additional theme presets.




---

### Components

Components

# Components

A collection of UI components for building easily web interfaces.




---

#### Accordion

Collapsible content sections.

&lt;twig:block-accordion
    :items=&quot;[
        {
            title: &#039;Lorem ipsum dolor sit amet, consectetur adipiscing elit.&#039;,
            content: &#039;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi malesuada tellus velit, in fringilla turpis interdum aliquet. Donec eget neque sit amet orci gravida eleifend. Fusce aliquet tempus gravida. Nam ullamcorper libero ac velit pharetra, in volutpat sem vulputate. Integer quis suscipit nibh, non tempor enim. Nulla ipsum leo, vulputate nec dolor nec, aliquet porttitor nibh. Donec maximus tellus vitae ornare dignissim. &lt;a href=\&#039;https://www.example.com/\&#039; target=\&#039;_blank\&#039;&gt;Link&lt;/a&gt;&#039;,
        },
        {
            title: &#039;Lorem ipsum dolor sit amet, consectetur adipiscing elit.&#039;,
            content: &#039;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi malesuada tellus velit, in fringilla turpis interdum aliquet. Donec eget neque sit amet orci gravida eleifend. Fusce aliquet tempus gravida. Nam ullamcorper libero ac velit pharetra, in volutpat sem vulputate. Integer quis suscipit nibh, non tempor enim. Nulla ipsum leo, vulputate nec dolor nec, aliquet porttitor nibh. Donec maximus tellus vitae ornare dignissim.&#039;,
        }
    ]&quot;
/&gt;

&lt;twig:block-accordion
    :items=&quot;[
        {
            title: &#039;Lorem ipsum dolor sit amet, consectetur adipiscing elit.&#039;,
            content: &#039;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi malesuada tellus velit, in fringilla turpis interdum aliquet. Donec eget neque sit amet orci gravida eleifend. Fusce aliquet tempus gravida. Nam ullamcorper libero ac velit pharetra, in volutpat sem vulputate. Integer quis suscipit nibh, non tempor enim. Nulla ipsum leo, vulputate nec dolor nec, aliquet porttitor nibh. Donec maximus tellus vitae ornare dignissim.&#039;,
            active: true
        },
        {
            title: &#039;Lorem ipsum dolor sit amet, consectetur adipiscing elit.&#039;,
            content: &#039;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi malesuada tellus velit, in fringilla turpis interdum aliquet. Donec eget neque sit amet orci gravida eleifend. Fusce aliquet tempus gravida. Nam ullamcorper libero ac velit pharetra, in volutpat sem vulputate. Integer quis suscipit nibh, non tempor enim. Nulla ipsum leo, vulputate nec dolor nec, aliquet porttitor nibh. Donec maximus tellus vitae ornare dignissim.&#039;,
        }
    ]&quot;
/&gt;




---

#### Alert

Alert messages for user feedback.

{% set types = [&#039;primary&#039;, &#039;secondary&#039;, &#039;success&#039;, &#039;danger&#039;, &#039;warning&#039;, &#039;info&#039;, &#039;light&#039;, &#039;dark&#039;] %}
{% for type in types %}
    &lt;twig:block-alert
      class=&quot;my-4&quot;
      type=&quot;{{ type }}&quot;
      content=&quot;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore &lt;a href=&#039;https://www.example.com/&#039; target=&#039;_blank&#039;&gt;magna aliqua.&lt;/a&gt;&quot;
    /&gt;
{% endfor %}




---

#### Box

Simple box container for content.

&lt;twig:block-box
    class=&quot;rounded&quot;
    title=&quot;Box with rounded corners&quot;
    content=&#039;Lorem ipsum dolor sit amet, consectetur adipiscing &lt;a href=&quot;https://www.example.com/&quot; target=&quot;_blank&quot;&gt;elit&lt;/a&gt;.&#039;
/&gt;

&lt;twig:block-box
    title=&quot;Box without rounded corners&quot;
    content=&quot;Lorem ipsum dolor sit amet, consectetur adipiscing elit.&quot;
/&gt;

&lt;twig:block-box
    title=&quot;Box without content&quot;
/&gt;




---

#### Boxes

Large box display for information.

&lt;twig:block-boxes
    class=&quot;my-4&quot;
    cols=&quot;1&quot;
    :boxes=&quot;[
        {
            icon: &#039;fa-solid fa-search&#039;,
            title: &#039;Lorem ipsum dolor sit amet&#039;,
            content: &#039;Lorem ipsum dolor sit amet, consectetur &lt;a href=\&#039;https://www.example.com/\&#039; target=\&#039;_blank\&#039;&gt;adipiscing elit.&lt;/a&gt;&#039;,
            buttonText: &#039;Lorem ipsum&#039;,
            buttonUrl: &#039;#&#039;
        }
    ]&quot;
/&gt;

&lt;twig:block-boxes
    class=&quot;my-4&quot;
    :boxes=&quot;[
        {
            icon: &#039;fa-solid fa-search&#039;,
            title: &#039;Lorem ipsum dolor sit amet&#039;,
            content: &#039;Lorem ipsum dolor sit amet, consectetur adipiscing elit.&#039;,
            buttonText: &#039;Lorem ipsum&#039;,
            buttonUrl: &#039;#&#039;
        },
        {
            icon: &#039;fa-solid fa-file&#039;,
            title: &#039;Lorem ipsum dolor sit amet&#039;,
            content: &#039;Lorem ipsum dolor sit amet, consectetur adipiscing elit.&#039;,
            buttonText: &#039;Lorem ipsum&#039;,
        }
    ]&quot;
/&gt;

&lt;twig:block-boxes
    class=&quot;my-4&quot;
    cols=&quot;3&quot;
    :boxes=&quot;[
        {
            icon: &#039;fa-solid fa-search&#039;,
            title: &#039;Lorem ipsum dolor sit amet&#039;,
            content: &#039;Lorem ipsum dolor sit amet, consectetur adipiscing elit.&#039;,
        },
        {
            icon: &#039;fa-solid fa-file&#039;,
            title: &#039;Lorem ipsum dolor sit amet&#039;,
            content: &#039;Lorem ipsum dolor sit amet, consectetur adipiscing elit.&#039;,
            buttonText: &#039;Lorem ipsum&#039;,
            buttonUrl: &#039;#&#039;
        },
        {
            icon: &#039;fa-solid fa-file&#039;,
            title: &#039;Lorem ipsum dolor sit amet&#039;,
            content: &#039;Lorem ipsum dolor sit amet, consectetur adipiscing elit.&#039;,
            buttonText: &#039;Lorem ipsum&#039;,
            buttonUrl: &#039;#&#039;
        }
    ]&quot;
/&gt;




---

#### Card Grid

Grid layout for multiple cards.

&lt;twig:block-card-grid
    class=&quot;my-4&quot;
    :cols=&quot;3&quot;
    :cards=&quot;[
        {
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            title: &#039;Twig&#039;,
            content: &#039;It is a twig. &lt;a href=\&#039;https://www.example.com/\&#039; target=\&#039;_blank\&#039;&gt;Link&lt;/a&gt;&#039;,
            buttonText: &#039;Learn about Twig&#039;,
            buttonUrl: &#039;https://twig.symfony.com&#039;,
            buttonColor: &#039;primary&#039;
        },
        {
            title: &#039;Twig&#039;,
            content: &#039;It is a twig.&#039;,
            buttonText: &#039;Learn about Twig&#039;,
            buttonUrl: &#039;https://twig.symfony.com&#039;
        },
        {
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            content: &#039;Twig is a twig&#039;
        }
    ]&quot;
/&gt;

&lt;twig:block-card-grid
    class=&quot;my-4&quot;
    :cols=&quot;4&quot;
    :cards=&quot;[
        {
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            title: &#039;Twig&#039;,
            content: &#039;It is a twig.&#039;,
            buttonText: &#039;Learn about Twig&#039;,
            buttonUrl: &#039;https://twig.symfony.com&#039;
        },
        {
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            title: &#039;Twig&#039;,
            content: &#039;It is a twig.&#039;,
            buttonText: &#039;Learn about Twig&#039;,
            buttonUrl: &#039;https://twig.symfony.com&#039;,
        },
        {
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            title: &#039;Twig&#039;,
            content: &#039;It is a twig.&#039;,
            buttonText: &#039;Learn about Twig&#039;,
        },
        {
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            title: &#039;Twig&#039;,
            content: &#039;It is a twig.&#039;,
            buttonText: &#039;Learn about Twig&#039;,
            buttonUrl: &#039;https://twig.symfony.com&#039;,
            buttonColor: &#039;success&#039;
        }
    ]&quot;
/&gt;

&lt;twig:block-card-grid
    class=&quot;my-4&quot;
    :cols=&quot;3&quot;
    :cards=&quot;[
        {
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            title: &#039;Twig&#039;,
            content: &#039;It is a twig.&#039;,
            buttonText: &#039;Learn about Twig&#039;,
            buttonUrl: &#039;https://twig.symfony.com&#039;,
            offset: 4
        }
    ]&quot;
/&gt;




---

#### Card

Card component for content display.

&lt;twig:block-card
    class=&quot;my-4&quot;
    image=&quot;/img/home/derafu-dev-programmer.png&quot;
    title=&quot;Card&quot;
    content=&quot;Lorem ipsum dolor sit amet, consectetur &lt;a href=&#039;https://www.example.com/&#039; target=&#039;_blank&#039;&gt;adipiscing elit.&lt;/a&gt;&quot;
    buttonText=&quot;More...&quot;
    buttonUrl=&quot;https://www.example.com/&quot;
/&gt;




---

#### Comparison

Side-by-side comparison of features.

&lt;twig:block-comparison
    class=&quot;my-4&quot;
    :plans=&quot;[
        {
            title: &#039;Alpha Plan&#039;,
            price: &#039;$20&lt;small&gt;.000&lt;/small&gt;&#039;,
            period: &#039;/month&#039;,
            features: [
                {
                    text: &#039;Up to &lt;b&gt;500&lt;/b&gt; monthly receipts&#039;,
                    tooltip: &#039;Monthly limit for issuing or synchronizing electronic honorarium receipts (EHR).&#039;
                },
                {
                    text: &#039;Allows other users&#039;,
                    value: false
                },
                {
                    text: &#039;API issuance&#039;,
                    value: true
                }
            ],
            note: &#039;Recommended for accountants or doctors.&#039;,
            buttonText: &#039;Sign up&#039;,
            buttonUrl: &#039;https://www.example.com/&#039;
        },
        {
            title: &#039;Beta Plan&#039;,
            price: &#039;$40&lt;small&gt;.000&lt;/small&gt;&#039;,
            period: &#039;/month&#039;,
            features: [
                {
                    text: &#039;Up to &lt;b&gt;2,000&lt;/b&gt; monthly receipts&#039;
                },
                {
                    text: &#039;Up to &lt;b&gt;2 users&lt;/b&gt; can work with the issuer&#039;
                },
                {
                    text: &#039;API issuance&#039;,
                    value: true
                }
            ],
            note: &#039;Recommended for professional societies.&#039;,
            buttonText: &#039;Sign up&#039;,
            buttonUrl: &#039;https://www.example.com/&#039;,
            highlighted: true
        },
        {
            title: &#039;Gamma&#039;,
            price: &#039;$ 80&lt;small&gt;.000&lt;/small&gt;&#039;,
            period: &#039;/ month&#039;,
            features: [
                {
                    text: &#039;Up to &lt;b&gt;5,000&lt;/b&gt; monthly receipts&#039;
                },
                {
                    text: &#039;Up to &lt;b&gt;3 users&lt;/b&gt; can work with the issuer&#039;
                },
                {
                    text: &#039;API issuance&#039;,
                    value: true
                }
            ],
            note: &#039;Recommended for notaries.&#039;,
            buttonText: &#039;Sign up&#039;,
            buttonUrl: &#039;https://www.example.com/&#039;
        }
    ]&quot;
/&gt;

&lt;twig:block-comparison
    class=&quot;my-4&quot;
    :plans=&quot;[
        {
            title: &#039;Android&#039;,
            priceIcon: &#039;fa-brands fa-android fa-2x&#039;,
            features: [
                {
                    text: &#039;Use the application with multiple &lt;a href=\&#039;https://www.example.com/\&#039; target=\&#039;_blank\&#039;&gt;companies&lt;/a&gt;&#039;
                },
                {
                    text: &#039;Issue receipts, invoices and shipping guides&#039;
                },
                {
                    text: &#039;Easy receipt and retail point of sale&#039;
                }
            ],
            note: &#039;Turn your Android device into a Point of Sale.&#039;,
            buttonText: &#039;Install from Google Play up to Android 9&#039;,
            buttonUrl: &#039;#&#039;
        },
        {
            title: &#039;iOS&#039;,
            priceIcon: &#039;fa-brands fa-apple fa-2x&#039;,
            features: [
                {
                    text: &#039;Use the application with multiple companies&#039;
                },
                {
                    text: &#039;Issue receipts, invoices and shipping guides&#039;
                },
                {
                    text: &#039;Easy receipt and retail point of sale&#039;
                }
            ],
            note: &#039;Check your company and issue tax documents easily.&#039;,
            buttonText: &#039;Install from App Store&#039;,
            buttonUrl: &#039;#&#039;,
            highlighted: true
        }
    ]&quot;
/&gt;




---

#### CTA

Call-to-action component.

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: The CTA block can be used in full width or in a container. In full with the CTA should not have rounded corners. In this example we use a container, that is the reason why we have rounded corners.&lt;/p&gt;

&lt;twig:block-cta
    class=&quot;my-4 rounded p-4&quot;
    title=&quot;Lorem ipsum dolor sit amet&quot;
    content=&#039;Lorem ipsum dolor sit amet, consectetur &lt;a href=&quot;https://www.example.com/&quot; target=&quot;_blank&quot;&gt;adipiscing elit.&lt;/a&gt;&#039;
    buttonText=&quot;More information&quot;
    buttonUrl=&quot;https://www.example.com/&quot;
/&gt;

&lt;twig:block-cta
    class=&quot;my-4 rounded p-4&quot;
    title=&quot;Lorem ipsum dolor sit amet&quot;
    content=&#039;Lorem ipsum dolor sit amet, consectetur &lt;a href=&quot;https://www.example.com/&quot; target=&quot;_blank&quot;&gt;adipiscing elit.&lt;/a&gt;&#039;
    buttonText=&quot;Disabled button&quot;
/&gt;




---

#### Features Grid

Grid layout for features.

&lt;twig:block-features-grid
    class=&quot;my-4&quot;
    :blocks=&quot;[
        {
            title: &#039;Lorem&#039;,
            subtitle: &#039;First block subtitle&#039;,
            features: [
                {
                    icon: &#039;fa-solid fa-cloud&#039;,
                    title: &#039;Feature 1&#039;,
                    content: &#039;Content of &lt;a href=\&#039;https://www.example.com/\&#039; target=\&#039;_blank\&#039;&gt;feature 1.&lt;/a&gt;&#039;
                },
                {
                    icon: &#039;fa-solid fa-cog&#039;,
                    title: &#039;Feature 2&#039;,
                    content: &#039;Content of feature 2.&#039;
                },
                {
                    icon: &#039;fa-solid fa-users&#039;,
                    title: &#039;Feature 3&#039;,
                    content: &#039;Content of feature 3.&#039;
                }
            ]
        },
        {
            title: &#039;Ipsum&#039;,
            subtitle: &#039;Subtitle of the second &lt;a href=\&#039;https://www.example.com/\&#039; target=\&#039;_blank\&#039;&gt;block&lt;/a&gt;.&#039;,
            features: [
                {
                    icon: &#039;fa-solid fa-star&#039;,
                    title: &#039;Feature 1&#039;,
                    content: &#039;Content of feature 1.&#039;
                },
                {
                    icon: &#039;fa-solid fa-heart&#039;,
                    title: &#039;Feature &lt;a href=\&#039;https://www.example.com/\&#039; target=\&#039;_blank\&#039;&gt;2&lt;/a&gt;&#039;,
                    content: &#039;Content of feature 2.&#039;
                },
                {
                    icon: &#039;fa-solid fa-bell&#039;,
                    title: &#039;Feature 3&#039;,
                    content: &#039;Content of feature 3.&#039;
                }
            ]
        }
    ]&quot;
/&gt;




---

#### Features Icon

Icon list for features.

{# Case 4 elements (one row of 4) #}
&lt;twig:block-features-icon
    class=&quot;my-4&quot;
    :features=&quot;[
        {
            icon: &#039;fa-solid fa-cloud&#039;,
            title: &#039;Software as a Service&#039;,
            content: &#039;We are a cloud software, you need Internet access to &lt;a href=\&#039;https://www.example.com/\&#039; target=\&#039;_blank\&#039;&gt;use it.&lt;/a&gt;&#039;
        },
        {
            icon: &#039;fa-solid fa-money-bill&#039;,
            title: &#039;&lt;a href=\&#039;https://www.example.com/\&#039; target=\&#039;_blank\&#039;&gt;Price&lt;/a&gt; per company&#039;,
            content: &#039;The Plus Service price is per company. If you have 3 companies, you will need to pay for 3 Plus Services.&#039;
        },
        {
            icon: &#039;fa-solid fa-arrows-left-right&#039;,
            title: &#039;SII Synchronization&#039;,
            content: &#039;Your tax documents are immediately sent to SII and we monitor their status.&#039;
        },
        {
            icon: &#039;fa-solid fa-check&#039;,
            title: &#039;Feature 4&#039;,
            content: &#039;Content of feature 4.&#039;
        }
    ]&quot;
/&gt;

{# Case 5 elements (row of 3 + row of 2) #}
&lt;twig:block-features-icon
    class=&quot;my-4&quot;
    :features=&quot;[
        {
            icon: &#039;fa-solid fa-cloud&#039;,
            title: &#039;Feature 1&#039;,
            content: &#039;Content 1&#039;
        },
        {
            icon: &#039;fa-solid fa-money-bill&#039;,
            title: &#039;Feature 2&#039;,
            content: &#039;Content 2&#039;
        },
        {
            icon: &#039;fa-solid fa-arrows-left-right&#039;,
            title: &#039;Feature 3&#039;,
            content: &#039;Content 3&#039;
        },
        {
            icon: &#039;fa-solid fa-check&#039;,
            title: &#039;Feature 4&#039;,
            content: &#039;Content 4&#039;
        },
        {
            icon: &#039;fa-solid fa-star&#039;,
            title: &#039;Feature 5&#039;,
            content: &#039;Content 5&#039;
        }
    ]&quot;
/&gt;

{# Case 8 elements (two rows of 4) #}
&lt;twig:block-features-icon
    class=&quot;my-4&quot;
    :features=&quot;[
        {
            icon: &#039;fa-solid fa-cloud&#039;,
            title: &#039;Feature 1&#039;,
            content: &#039;Content 1&#039;
        },
        {
            icon: &#039;fa-solid fa-money-bill&#039;,
            title: &#039;Feature 2&#039;,
            content: &#039;Content 2&#039;
        },
        {
            icon: &#039;fa-solid fa-arrows-left-right&#039;,
            title: &#039;Feature 3&#039;,
            content: &#039;Content 3&#039;
        },
        {
            icon: &#039;fa-solid fa-check&#039;,
            title: &#039;Feature 4&#039;,
            content: &#039;Content 4&#039;
        },
        {
            icon: &#039;fa-solid fa-star&#039;,
            title: &#039;Feature 5&#039;,
            content: &#039;Content 5&#039;
        },
        {
            icon: &#039;fa-solid fa-heart&#039;,
            title: &#039;Feature 6&#039;,
            content: &#039;Content 6&#039;
        },
        {
            icon: &#039;fa-solid fa-bell&#039;,
            title: &#039;Feature 7&#039;,
            content: &#039;Content 7&#039;
        },
        {
            icon: &#039;fa-solid fa-user&#039;,
            title: &#039;Feature 8&#039;,
            content: &#039;Content 8&#039;
        }
    ]&quot;
/&gt;




---

#### Features List

List of features with left and right alignment.

&lt;twig:block-features-list
    class=&quot;my-4&quot;
    :featuresLeft=&quot;[
        {
            icon: &#039;fa-solid fa-laptop fa-fw&#039;,
            title: &#039;Web &lt;a href=\&#039;https://www.example.com/\&#039; target=\&#039;_blank\&#039;&gt;Application.&lt;/a&gt;&#039;,
            content: &#039;With individual and &lt;a href=\&#039;https://www.example.com/\&#039; target=\&#039;_blank\&#039;&gt;bulk&lt;/a&gt; document issuance.&#039;
        },
        {
            icon: &#039;fa-solid fa-chart-line fa-fw&#039;,
            title: &#039;Reports&#039;,
            content: &#039;Charts and statistics to visualize your data in a simple way.&#039;
        },
        {
            icon: &#039;fa-solid fa-cogs fa-fw&#039;,
            title: &#039;Configuration&#039;,
            content: &#039;Customize the application according to your specific needs.&#039;
        }
    ]&quot;
    :featuresRight=&quot;[
        {
            icon: &#039;fa-solid fa-cubes fa-fw&#039;,
            title: &#039;Products and Services&#039;,
            content: &#039;Code your products and services to issue quickly.&#039;
        },
        {
            icon: &#039;fa-solid fa-cloud-download fa-fw&#039;,
            title: &#039;Backup&#039;,
            content: &#039;Download your data whenever you need it.&#039;
        },
        {
            icon: &#039;fa-solid fa-shield fa-fw&#039;,
            title: &#039;Security&#039;,
            content: &#039;Your data is protected with the highest security standards.&#039;
        }
    ]&quot;
/&gt;




---

#### Features Table

Table of features with values.

&lt;twig:block-features-table
    class=&quot;my-4&quot;
    title=&quot;Features&quot;
    :features=&quot;[
        {
            icon: &#039;fa-solid fa-folder&#039;,
            title: &#039;Taxpayers API&#039;,
            content: &#039;Tax status of third parties and &lt;a href=\&#039;https://www.example.com/\&#039; target=\&#039;_blank\&#039;&gt;economic activities&lt;/a&gt;&#039;,
            values: {
                &#039;SME Plan&#039;: true,
                &#039;Premium Plan&#039;: true,
                &#039;Pro Plan&#039;: true,
                &#039;Premier Plan&#039;: true,
                &#039;e600k Plan&#039;: true
            }
        },
        {
            icon: &#039;fa-solid fa-chart-line&#039;,
            title: &#039;SII API&#039;,
            content: &#039;Indicators: UF and complementary global tax.&#039;,
            values: {
                &#039;SME Plan&#039;: true,
                &#039;Premium Plan&#039;: true,
                &#039;Pro Plan&#039;: true,
                &#039;Premier Plan&#039;: true,
                &#039;e600k Plan&#039;: true
            }
        },
        {
            icon: &#039;fa-solid fa-file-lines&#039;,
            title: &#039;DTE API&#039;,
            content: &#039;Electronic tax documents.&#039;,
            values: {
                &#039;SME Plan&#039;: true,
                &#039;Premium Plan&#039;: true,
                &#039;Pro Plan&#039;: true,
                &#039;Premier Plan&#039;: true,
                &#039;e600k Plan&#039;: true
            }
        },
        {
            icon: &#039;fa-solid fa-file-invoice&#039;,
            title: &#039;BHE API&#039;,
            content: &#039;Issued and received fee receipts.&#039;,
            values: {
                &#039;SME Plan&#039;: false,
                &#039;Premium Plan&#039;: false,
                &#039;Pro Plan&#039;: true,
                &#039;Premier Plan&#039;: true,
                &#039;e600k Plan&#039;: true
            }
        },
        {
            icon: &#039;fa-solid fa-car&#039;,
            title: &#039;Vehicles API&#039;,
            content: &#039;Vehicle appraisal.&#039;,
            values: {
                &#039;SME Plan&#039;: false,
                &#039;Premium Plan&#039;: false,
                &#039;Pro Plan&#039;: false,
                &#039;Premier Plan&#039;: true,
                &#039;e600k Plan&#039;: true
            }
        },
        {
            icon: &#039;fa-solid fa-chart-bar&#039;,
            title: &#039;Queries every 24 hours&#039;,
            values: {
                &#039;SME Plan&#039;: &#039;&lt;span class=\&#039;lead\&#039;&gt;&lt;a href=\&#039;https://www.example.com/\&#039; target=\&#039;_blank\&#039;&gt;500&lt;/a&gt;&lt;/span&gt;&#039;,
                &#039;Premium Plan&#039;: &#039;&lt;span class=\&#039;lead\&#039;&gt;1.400&lt;/span&gt;&#039;,
                &#039;Pro Plan&#039;: &#039;&lt;span class=\&#039;lead\&#039;&gt;4.000&lt;/span&gt;&#039;,
                &#039;Premier Plan&#039;: &#039;&lt;span class=\&#039;lead\&#039;&gt;10.000&lt;/span&gt;&#039;,
                &#039;e600k Plan&#039;: &#039;&lt;span class=\&#039;lead\&#039;&gt;20.000&lt;/span&gt;&#039;
            }
        },
        {
            icon: &#039;fa-solid fa-dollar&#039;,
            title: &#039;&lt;a href=\&#039;https://www.example.com/\&#039; target=\&#039;_blank\&#039;&gt;Plan price&lt;/a&gt;&#039;,
            content: &#039;+ VAT / month&#039;,
            values: {
                &#039;SME Plan&#039;: &#039;&lt;span class=\&#039;lead\&#039;&gt;$40.000&lt;/span&gt;&#039;,
                &#039;Premium Plan&#039;: &#039;&lt;span class=\&#039;lead\&#039;&gt;$80.000&lt;/span&gt;&#039;,
                &#039;Pro Plan&#039;: &#039;&lt;span class=\&#039;lead\&#039;&gt;$150.000&lt;/span&gt;&#039;,
                &#039;Premier Plan&#039;: &#039;&lt;span class=\&#039;lead\&#039;&gt;$300.000&lt;/span&gt;&#039;,
                &#039;e600k Plan&#039;: &#039;&lt;span class=\&#039;lead\&#039;&gt;$600.000&lt;/span&gt;&#039;
            }
        }
    ]&quot;
/&gt;

&lt;twig:block-features-table
    class=&quot;my-4&quot;
    title=&quot;Plan Comparison&quot;
    :features=&quot;[
        {
            isHeader: true,
            title: &#039;API Access&#039;
        },
        {
            icon: &#039;fa-solid fa-folder&#039;,
            title: &#039;Taxpayers API&#039;,
            content: &#039;Tax data consultation&#039;,
            values: {
                &#039;Basic&#039;: true,
                &#039;Complete&#039;: true
            }
        },
        {
            icon: &#039;fa-solid fa-file-invoice&#039;,
            title: &#039;Documents API&#039;,
            content: &#039;Electronic invoices and receipts&#039;,
            values: {
                &#039;Basic&#039;: false,
                &#039;Complete&#039;: true
            }
        },
        {
            isHeader: true,
            title: &#039;Technical Support&#039;
        },
        {
            title: &#039;Email support&#039;,
            values: {
                &#039;Basic&#039;: true,
                &#039;Complete&#039;: true
            }
        },
        {
            isHeader: true,
            icon: &#039;fa-solid fa-dollar&#039;,
            title: &#039;Pricing&#039;
        },
        {
            icon: &#039;fa-solid fa-money-bill&#039;,
            title: &#039;Monthly cost&#039;,
            content: &#039;+ VAT&#039;,
            values: {
                &#039;Basic&#039;: &#039;&lt;span class=\&#039;lead\&#039;&gt;$35.000&lt;/span&gt;&#039;,
                &#039;Complete&#039;: &#039;&lt;span class=\&#039;lead\&#039;&gt;$90.000&lt;/span&gt;&#039;
            }
        }
    ]&quot;
/&gt;




---

#### Footer

Footer component with multiple layouts.

{# Example 1: Complete Footer with 4 columns #}
&lt;twig:block-footer
    class=&quot;my-4&quot;
    col1Title=&quot;Useful Links&quot;
    :col1Links=&quot;[
        {
            text: &#039;Academy&#039;,
            url: &#039;http://www.example.com&#039;,
            icon: &#039;fa-solid fa-graduation-cap fa-fw&#039;
        },
        {
            text: &#039;Jobs&#039;,
            url: &#039;http://www.example.com&#039;,
            icon: &#039;fa-solid fa-briefcase fa-fw&#039;,
            targetBlank: true
        },
        {
            text: &#039;Store&#039;,
            url: &#039;#&#039;,
            icon: &#039;fa-solid fa-shopping-cart fa-fw&#039;
        },
        {
            text: &#039;Bank Transfer Payments&#039;,
            url: &#039;#&#039;,
            icon: &#039;fa-solid fa-money-bill fa-fw&#039;
        }
    ]&quot;

    col2Title=&quot;Software Products&quot;
    :col2Links=&quot;[
        {
            text: &#039;Software 1&#039;,
            url: &#039;#&#039;,
            icon: &#039;fa-solid fa-file-invoice fa-fw&#039;
        },
        {
            text: &#039;Software 2&#039;,
            url: &#039;#&#039;,
            icon: &#039;fa-solid fa-network-wired&#039;
        },
        {
            text: &#039;Software 3&#039;,
            url: &#039;#&#039;,
            icon: &#039;fa-solid fa-cash-register fa-fw&#039;
        }
    ]&quot;

    col3Title=&quot;Contact Us&quot;
    :col3Links=&quot;[
        {
            text: &#039;derafu-twig@example.com&#039;,
            url: &#039;mailto:derafu-twig@example.com&#039;,
            icon: &#039;fa-solid fa-envelope fa-fw&#039;
        },
        {
            text: &#039;Schedule a Meeting&#039;,
            url: &#039;#&#039;,
            icon: &#039;fa-solid fa-calendar fa-fw&#039;
        },
        {
            text: &#039;Help Center&#039;,
            url: &#039;#&#039;,
            icon: &#039;fa-solid fa-question-circle fa-fw&#039;
        }
    ]&quot;

    col4Title=&quot;Derafu DEV&quot;
    col4Html=&quot;&lt;p&gt;From Chile to the world with ❤️&lt;/p&gt;&quot;
    :col4SocialIcons=&quot;[
        {
            icon: &#039;fa-brands fa-linkedin fa-2x&#039;,
            url: &#039;#&#039;,
            color: &#039;#0077b5&#039;,
            targetBlank: true
        },
        {
            icon: &#039;fa-brands fa-github fa-2x&#039;,
            url: &#039;#&#039;,
            color: &#039;#333&#039;,
            targetBlank: true
        }
    ]&quot;

    leftText=&quot;Copyright © Derafu DEV&quot;
    rightText=&quot;All rights reserved&quot;
    socialIconsCircular=&quot;true&quot;
/&gt;

{# Example 2: Minimalist Footer with 2 columns #}
&lt;twig:block-footer
    class=&quot;my-4&quot;
    col1Title=&quot;Our Company&quot;
    col1Html=&quot;&lt;p&gt;Innovative software solutions for modern businesses.&lt;/p&gt;&quot;
    :col1Links=&quot;[
        {
            text: &#039;About Us&#039;,
            url: &#039;#&#039;
        },
        {
            text: &#039;Contact&#039;,
            url: &#039;#&#039;
        }
    ]&quot;

    col2Title=&quot;Quick Links&quot;
    :col2Links=&quot;[
        {
            text: &#039;Documentation&#039;,
            url: &#039;#&#039;
        },
        {
            text: &#039;Support&#039;,
            url: &#039;#&#039;
        }
    ]&quot;

    leftText=&quot;© 2024 Derafu ORG&quot;
/&gt;

{# Example 3: Footer with emphasis on social media #}
&lt;twig:block-footer
    class=&quot;my-4&quot;
    col1Title=&quot;Follow Us&quot;
    col1Html=&quot;&lt;p&gt;Stay connected with us on social media&lt;/p&gt;&quot;
    :col1SocialIcons=&quot;[
        {
            icon: &#039;fa-brands fa-facebook fa-2x&#039;,
            url: &#039;http://www.example.com&#039;,
            color: &#039;#1877f2&#039;,
            targetBlank: true
        },
        {
            icon: &#039;fa-brands fa-twitter fa-2x&#039;,
            url: &#039;#&#039;,
            color: &#039;#1da1f2&#039;,
            targetBlank: true
        },
        {
            icon: &#039;fa-brands fa-instagram fa-2x&#039;,
            url: &#039;#&#039;,
            color: &#039;#e4405f&#039;,
            targetBlank: true
        },
        {
            icon: &#039;fa-brands fa-youtube fa-2x&#039;,
            url: &#039;#&#039;,
            color: &#039;#ff0000&#039;,
            targetBlank: true
        }
    ]&quot;

    col2Title=&quot;Community&quot;
    :col2Links=&quot;[
        {
            text: &#039;Blog&#039;,
            url: &#039;#&#039;,
            icon: &#039;fa-solid fa-blog&#039;
        },
        {
            text: &#039;Forum&#039;,
            url: &#039;#&#039;,
            icon: &#039;fa-solid fa-comments&#039;
        },
        {
            text: &#039;Events&#039;,
            url: &#039;#&#039;,
            icon: &#039;fa-solid fa-calendar-days&#039;
        }
    ]&quot;
    :col2SocialIcons=&quot;[
        {
            icon: &#039;fa-brands fa-discord fa-2x&#039;,
            url: &#039;#&#039;,
            color: &#039;#5865f2&#039;,
            targetBlank: true
        },
        {
            icon: &#039;fa-brands fa-telegram fa-2x&#039;,
            url: &#039;#&#039;,
            color: &#039;#0088cc&#039;,
            targetBlank: true
        }
    ]&quot;

    leftText=&quot;Join our community&quot;
    rightText=&#039;&lt;a href=&quot;#&quot;&gt;Privacy Policy&lt;/a&gt; | &lt;a href=&quot;#&quot;&gt;Terms of Use&lt;/a&gt;&#039;
/&gt;




---

#### Gallery

Grid of images and videos with a click-to-enlarge lightbox.

{#
    Basic gallery with images only. The &quot;caption&quot; field is optional per item
    (the first photo below has none).
    The &quot;caption&quot; text is shown two places: as an overlay at the bottom of
    each thumbnail in the grid, and again under the media inside the
    lightbox once you click a thumbnail to open it.
    &quot;size&quot; controls the lightbox modal size. Valid values: &quot;sm&quot;, &quot;lg&quot;, &quot;xl&quot;
    (default) and &quot;fullscreen&quot;.
#}
&lt;twig:block-gallery
    class=&quot;my-4&quot;
    :cols=&quot;4&quot;
    size=&quot;sm&quot;
    :items=&quot;[
        {
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
        },
        {
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            caption: &#039;Caption for photo 2&#039;
        },
        {
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            caption: &#039;Caption for photo 3&#039;
        },
        {
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            caption: &#039;Caption for photo 4&#039;
        }
    ]&quot;
/&gt;

{#
    Gallery mixing images and videos in the same grid. Video thumbnails
    without a custom &quot;thumbnail&quot; show a play icon over a dark background
    (see the next example for a video with a real poster image instead).
#}
&lt;twig:block-gallery
    class=&quot;my-4&quot;
    :cols=&quot;3&quot;
    :items=&quot;[
        {
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            caption: &#039;Photo 1&#039;
        },
        {
            video: &#039;https://www.youtube.com/watch?v=GOAEIMx39-w&#039;,
            caption: &#039;Video 1 (no thumbnail: play icon placeholder)&#039;
        },
        {
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            caption: &#039;Photo 2&#039;
        },
        {
            video: &#039;https://www.youtube.com/watch?v=GOAEIMx39-w&#039;,
            caption: &#039;Video 2 (no thumbnail: play icon placeholder)&#039;
        },
        {
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            caption: &#039;Photo 3&#039;
        },
        {
            video: &#039;https://www.youtube.com/watch?v=GOAEIMx39-w&#039;,
            caption: &#039;Video 3 (no thumbnail: play icon placeholder)&#039;
        }
    ]&quot;
/&gt;

{#
    Gallery combining every optional field: &quot;thumbnail&quot; as a custom poster
    for a video item, &quot;alt&quot; as the accessible/tooltip text shown on hover
    over image thumbnails, and &quot;caption&quot; shown under the thumbnail and in
    the lightbox.
#}
&lt;twig:block-gallery
    class=&quot;my-4&quot;
    :cols=&quot;6&quot;
    :items=&quot;[
        {
            video: &#039;https://www.youtube.com/watch?v=GOAEIMx39-w&#039;,
            thumbnail: &#039;/img/home/derafu-dev-programmer.png&#039;,
            alt: &#039;Product demo&#039;,
            caption: &#039;Video with poster&#039;
        },
        {
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            alt: &#039;Team photo&#039;,
            caption: &#039;Team&#039;
        },
        {
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            alt: &#039;Office&#039;,
            caption: &#039;Office&#039;
        },
        {
            video: &#039;https://www.youtube.com/watch?v=GOAEIMx39-w&#039;,
            alt: &#039;Tutorial&#039;,
            caption: &#039;Video without poster&#039;
        },
        {
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            alt: &#039;Event&#039;,
            caption: &#039;Event&#039;
        },
        {
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            alt: &#039;Conference&#039;,
            caption: &#039;Conference&#039;
        }
    ]&quot;
/&gt;

{# Gallery with a fullscreen lightbox (size=&quot;fullscreen&quot;) #}
&lt;twig:block-gallery
    class=&quot;my-4&quot;
    :cols=&quot;2&quot;
    size=&quot;fullscreen&quot;
    :items=&quot;[
        {
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            caption: &#039;Wide photo 1&#039;
        },
        {
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            caption: &#039;Wide photo 2&#039;
        }
    ]&quot;
/&gt;




---

#### Grid

Grid layout for content.

{% set grid_item_1 %}
    &lt;h2&gt;Item 1&lt;/h2&gt;
    &lt;p&gt;orem ipsum dolor sit amet, consectetur adipiscing elit. Morbi malesuada tellus velit, in fringilla turpis interdum aliquet. Donec eget neque sit amet orci gravida eleifend. Fusce aliquet &lt;a href=&quot;https://www.example.com/&quot; target=&quot;_blank&quot;&gt;tempus gravida.&lt;/a&gt;&lt;/p&gt;
{% endset %}

{% set grid_item_2 %}
    &lt;twig:block-image
        title=&quot;What is Twig?&quot;
        image=&quot;/img/home/derafu-dev-programmer.png&quot;
        content=&quot;&lt;p&gt;A small branch.&lt;/p&gt;&quot;
        :buttons=&quot;[
            {
                text: &#039;Learn about Twig&#039;,
                url: &#039;https://twig.symfony.com/&#039;
            }
        ]&quot;
    /&gt;
{% endset %}

{% set grid_item_3 %}
    &lt;twig:block-image
        title=&quot;Lorem ipsum&quot;
        image=&quot;/img/home/derafu-dev-programmer.png&quot;
        content=&quot;&lt;p&gt;Dolor sit amet&lt;/p&gt;&quot;
        :buttons=&quot;[
            {
                text: &#039;Consectetur&#039;,
                url: &#039;https://twig.symfony.com/&#039;
            }
        ]&quot;
    /&gt;
{% endset %}

{# Example 1: Grid without specified columns #}
&lt;twig:block-grid
    class=&quot;my-4&quot;
    :items=&quot;[
        grid_item_1,
        grid_item_2,
    ]&quot;
/&gt;

{# Example 2: Grid with 4 columns #}
&lt;twig:block-grid
    class=&quot;my-4&quot;
    cols=&quot;4&quot;
    :items=&quot;[
        grid_item_1,
        grid_item_2,
        grid_item_3,
        grid_item_2,
    ]&quot;
/&gt;




---

#### Header

Header component with multiple layouts.

&lt;twig:block-header
    class=&quot;mt-0&quot;
    logoImage=&quot;/img/home/derafu-dev-programmer.png&quot;
    logoText=&quot;&lt;span class=&#039;fw-normal&#039;&gt;Soft&lt;/span&gt;&lt;span class=&#039;fw-bold text-primary&#039;&gt;ware&lt;/span&gt;&quot;
    :leftNav=&quot;[
        {
            text: &#039;Home&#039;,
            url: &#039;#&#039;
        },
        {
            text: &#039;Solutions&#039;,
            icon: &#039;fa-solid fa-cubes&#039;,
            items: [
                {
                    text: &#039;Basic Solution&#039;,
                    icon: &#039;fa-solid fa-star&#039;,
                    url: &#039;#&#039;
                },
                {
                    type: &#039;divider&#039;
                },
                {
                    text: &#039;Advanced Solution&#039;,
                    icon: &#039;fa-solid fa-rocket&#039;,
                    items: [
                        {
                            text: &#039;Main Features&#039;,
                            url: &#039;#&#039;
                        },
                        {
                            icon: &#039;fa-solid fa-chart-line&#039;,
                            text: &#039;Analysis&#039;,
                            url: &#039;#&#039;
                        },
                        {
                            type: &#039;divider&#039;
                        },
                        {
                            icon: &#039;fa-solid fa-book-open&#039;,
                            text: &#039;Complete Guide&#039;,
                            url: &#039;#&#039;
                        }
                    ]
                }
            ]
        }
    ]&quot;
    :rightNav=&quot;[
        {
            text: &#039;Resources&#039;,
            url: &#039;#&#039;,
            icon: &#039;fa-solid fa-toolbox&#039;
        },
        {
            text: &#039;Integrations&#039;,
            icon: &#039;fa-solid fa-plug&#039;,
            items: [
                {
                    text: &#039;Platforms&#039;,
                    icon: &#039;fa-solid fa-desktop&#039;,
                    url: &#039;#&#039;
                },
                {
                    text: &#039;API&#039;,
                    icon: &#039;fa-solid fa-code&#039;,
                    url: &#039;#&#039;
                }
            ]
        }
    ]&quot;
    ctaIcon=&quot;fa-solid fa-rocket&quot;
    ctaText=&quot;Get Started&quot;
    ctaUrl=&quot;#&quot;
/&gt;

&lt;twig:block-header
    class=&quot;my-4&quot;
    logoImage=&quot;/img/home/derafu-dev-programmer.png&quot;
    logoText=&quot;&lt;span class=&#039;fw-normal&#039;&gt;Another&lt;/span&gt;&lt;span class=&#039;fw-bold text-primary&#039;&gt;Example&lt;/span&gt;&quot;
    :leftNav=&quot;[
        {
            text: &#039;Marketplace&#039;,
            icon: &#039;fa-solid fa-shopping-cart&#039;,
            sections: [
                {
                    title: &#039;Technology&#039;,
                    links: [
                        {
                            text: &#039;Software&#039;,
                            url: &#039;#&#039;,
                            icon: &#039;fa-solid fa-laptop-code&#039;
                        },
                        {
                            text: &#039;Hardware&#039;,
                            url: &#039;#&#039;,
                            icon: &#039;fa-solid fa-server&#039;
                        }
                    ]
                }
            ]
        }
    ]&quot;
    ctaText=&quot;Explore&quot;
    ctaUrl=&quot;#&quot;
/&gt;




---

#### Hero

Hero section for landing pages.

&lt;twig:block-hero
   class=&quot;my-4&quot;
   align=&quot;left&quot;
   background=&quot;/img/home/derafu-dev-programmer.png&quot;
   title=&quot;Lorem &lt;a href=&#039;https://www.example.com/&#039; target=&#039;_blank&#039;&gt;ipsum&lt;/a&gt;&quot;
   subtitle=&quot;Lorem ipsum dolor sit amet, consectetur adipiscing elit.&quot;
   :buttons=&quot;[
       {
           text: &#039;Example.com&#039;,
           url: &#039;https://www.example.com&#039;
       }
   ]&quot;
/&gt;

&lt;twig:block-hero
   class=&quot;my-4&quot;
   align=&quot;left&quot;
   background=&quot;/img/home/derafu-dev-programmer.png&quot;
   contentBackground=&quot;true&quot;
   title=&quot;Lorem ipsum&quot;
   subtitle=&quot;Lorem ipsum dolor sit amet, consectetur &lt;a href=&#039;https://www.example.com/&#039; target=&#039;_blank&#039;&gt;adipiscing elit.&lt;/a&gt;&quot;
   :buttons=&quot;[
       {
           text: &#039;Example.com&#039;,
           url: &#039;https://www.example.com&#039;
       }
   ]&quot;
/&gt;

&lt;twig:block-hero
   class=&quot;my-4&quot;
   align=&quot;right&quot;
   background=&quot;/img/home/derafu-dev-programmer.png&quot;
   title=&quot;Lorem ipsum&quot;
   subtitle=&quot;Lorem ipsum dolor sit amet, consectetur adipiscing elit.&quot;
   :buttons=&quot;[
       {
           text: &#039;Example.com&#039;,
           url: &#039;https://www.example.com&#039;
       }
   ]&quot;
/&gt;

&lt;twig:block-hero
    class=&quot;my-4&quot;
    title=&quot;Hero&quot;
    subtitle=&quot;Lorem ipsum dolor sit amet, consectetur adipiscing elit.&quot;
    :buttons=&quot;[
        {
            text: &#039;Example.com 1&#039;,
            url: &#039;https://www.example.com&#039;
        },
        {
            text: &#039;Example.com 2&#039;,
            url: &#039;https://www.example.com&#039;
        }
    ]&quot;
/&gt;

{% set sizes = [&#039;mini&#039;, &#039;small&#039;, &#039;medium&#039;, &#039;large&#039;, &#039;full&#039;] %}
{% for size in sizes %}
   &lt;twig:block-hero
       class=&quot;my-4&quot;
       align=&quot;right&quot;
       size=&quot;{{ size }}&quot;
       background=&quot;/img/home/derafu-dev-programmer.png&quot;
       title=&quot;Size: {{ size }}&quot;
       subtitle=&quot;Lorem ipsum dolor sit amet, consectetur adipiscing elit.&quot;
       :buttons=&quot;[
           {
               text: &#039;Example.com&#039;,
               url: &#039;https://www.example.com&#039;
           }
       ]&quot;
   /&gt;
{% endfor %}




---

#### Image Grid

Grid layout for images.

&lt;twig:block-image-grid
    class=&quot;my-4&quot;
    :cols=&quot;4&quot;
    :images=&quot;[
        {
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            url: &#039;https://www.example.com&#039;,
            tooltip: &#039;Company 1&#039;
        },
        {
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            tooltip: &#039;Company 2&#039;
        },
        {
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
        },
        {
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
        },
        {
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            url: &#039;https://www.example.com&#039;,
            tooltip: &#039;Company 1&#039;
        },
        {
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            tooltip: &#039;Company 2&#039;
        },
        {
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
        },
        {
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
        }
    ]&quot;
/&gt;




---

#### Image

Image component with title, content and buttons.

&lt;twig:block-image
    class=&quot;my-4&quot;
    title=&quot;What is Twig?&quot;
    image=&quot;/img/home/derafu-dev-programmer.png&quot;
    content=&quot;A &lt;a href=&#039;https://www.example.com/&#039; target=&#039;_blank&#039;&gt;small branch&lt;/a&gt;.&quot;
    :buttons=&quot;[
        {
            text: &#039;Learn about Twig&#039;,
            url: &#039;https://twig.symfony.com/&#039;
        }
    ]&quot;
/&gt;

&lt;twig:block-image
    class=&quot;my-4&quot;
    size=&quot;small&quot;
    title=&quot;What is &lt;a href=&#039;https://www.example.com/&#039; target=&#039;_blank&#039;&gt;Twig&lt;/a&gt;?&quot;
    image=&quot;/img/home/derafu-dev-programmer.png&quot;
    content=&quot;A small branch&quot;
    :buttons=&quot;[
        {
            text: &#039;Learn about Twig&#039;,
            url: &#039;https://twig.symfony.com/&#039;
        }
    ]&quot;
/&gt;




---

#### Media List

List of media items.

&lt;twig:block-media-list
    class=&quot;mb-5&quot;
    :items=&quot;[
        {
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            title: &#039;Title 1&#039;,
            content: &#039;&lt;p&gt;Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.&lt;/p&gt;&#039;
        },
        {
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            title: &#039;Title 2&#039;,
            content: &#039;&lt;p&gt;Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.&lt;/p&gt;&#039;
        },
        {
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            title: &#039;Title 3&#039;,
            content: &#039;&lt;p&gt;Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.&lt;/p&gt;&#039;
        }
    ]&quot;
/&gt;

&lt;twig:block-media-list
    class=&quot;mb-5&quot;
    :rounded=&quot;true&quot;
    :items=&quot;[
        {
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            title: &#039;Title 1&#039;,
            content: &#039;&lt;p&gt;Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.&lt;/p&gt;&#039;
        },
        {
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            title: &#039;Title 2&#039;,
            content: &#039;&lt;p&gt;Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.&lt;/p&gt;&#039;
        },
        {
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            title: &#039;Title 3&#039;,
            content: &#039;&lt;p&gt;Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.&lt;/p&gt;&#039;
        }
    ]&quot;
/&gt;




---

#### Modal

Popup dialog windows.

{# Simple modal #}
&lt;div class=&quot;mb-4&quot;&gt;
    &lt;button type=&quot;button&quot; class=&quot;btn btn-light d-block&quot; data-bs-toggle=&quot;modal&quot; data-bs-target=&quot;#modal-1&quot;&gt;
        Open simple modal
    &lt;/button&gt;
&lt;/div&gt;
&lt;twig:block-modal
    id=&quot;modal-1&quot;
    title=&quot;Modal Title&quot;
    content=&quot;This is a basic modal with title and &lt;a href=&#039;https://www.example.com/&#039; target=&#039;_blank&#039;&gt;close button.&lt;/a&gt;&quot;
    :buttons=&quot;[
        {
            text: &#039;Close&#039;,
            type: &#039;secondary&#039;,
            dismiss: true
        },
        {
            text: &#039;Save Changes&#039;,
            type: &#039;primary&#039;
        }
    ]&quot;
/&gt;

{# Complex modal #}
&lt;div class=&quot;mt-2 mb-3&quot;&gt;
    &lt;button type=&quot;button&quot; class=&quot;btn btn-light d-block&quot; data-bs-toggle=&quot;modal&quot; data-bs-target=&quot;#modal-2&quot;&gt;
        Open complex modal
    &lt;/button&gt;
&lt;/div&gt;
&lt;twig:block-modal
    id=&quot;modal-2&quot;
    title=&quot;Confirmation Required&quot;
    content=&quot;&lt;p&gt;This action cannot be undone. Are you sure you want to continue?&lt;/p&gt;&quot;
    size=&quot;lg&quot;
    :centered=&quot;true&quot;
    :withBackdrop=&quot;true&quot;
    :buttons=&quot;[
        {
            text: &#039;Cancel&#039;,
            type: &#039;light&#039;,
            dismiss: true
        },
        {
            text: &#039;Yes, continue&#039;,
            type: &#039;danger&#039;,
            attributes: &#039;onclick=\&#039;handleConfirmation()\&#039;&#039;
        }
    ]&quot;
/&gt;

{# Fullscreen modal with scrollable content #}
&lt;div class=&quot;mt-3 mb-4&quot;&gt;
    &lt;button type=&quot;button&quot; class=&quot;btn btn-light d-block&quot; data-bs-toggle=&quot;modal&quot; data-bs-target=&quot;#modal-3&quot;&gt;
        Open fullscreen modal with scrollable content
    &lt;/button&gt;
&lt;/div&gt;
&lt;twig:block-modal
    id=&quot;modal-3&quot;
    title=&quot;Terms and Conditions&quot;
    content=&quot;&lt;div style=&#039;height: 1000px&#039;&gt;Very long content...&lt;/div&gt;&quot;
    size=&quot;fullscreen&quot;
    :scrollable=&quot;true&quot;
    :buttons=&quot;[
        {
            text: &#039;Accept&#039;,
            type: &#039;success&#039;
        }
    ]&quot;
/&gt;

{# Basic modal #}
&lt;div class=&quot;mt-0&quot;&gt;
    &lt;button type=&quot;button&quot; class=&quot;btn btn-light d-block&quot; data-bs-toggle=&quot;modal&quot; data-bs-target=&quot;#modal-basic&quot;&gt;
        Open basic modal
    &lt;/button&gt;
&lt;/div&gt;
&lt;twig:block-modal
    id=&quot;modal-basic&quot;
    title=&quot;Basic Modal&quot;
    content=&quot;&lt;p&gt;This is a simple modal with title and close button.&lt;/p&gt;&quot;
    :buttons=&quot;[
        {
            text: &#039;Close&#039;,
            type: &#039;secondary&#039;,
            dismiss: true
        }
    ]&quot;
/&gt;

{# Modal without close button #}
&lt;div class=&quot;mt-2 mb-2&quot;&gt;
    &lt;button type=&quot;button&quot; class=&quot;btn btn-light d-block&quot; data-bs-toggle=&quot;modal&quot; data-bs-target=&quot;#modal-no-close&quot;&gt;
        Modal without X
    &lt;/button&gt;
&lt;/div&gt;
&lt;twig:block-modal
    id=&quot;modal-no-close&quot;
    title=&quot;You can&#039;t close me easily&quot;
    content=&quot;&lt;p&gt;This modal doesn&#039;t have the X button to close.&lt;/p&gt;&quot;
    :showClose=&quot;false&quot;
    :buttons=&quot;[
        {
            text: &#039;Got it&#039;,
            type: &#039;warning&#039;,
            dismiss: true
        }
    ]&quot;
/&gt;

{# Modal with static backdrop #}
&lt;div class=&quot;mt-3 mb-4&quot;&gt;
    &lt;button type=&quot;button&quot; class=&quot;btn btn-light d-block&quot; data-bs-toggle=&quot;modal&quot; data-bs-target=&quot;#modal-static&quot;&gt;
        Modal that does not close
    &lt;/button&gt;
&lt;/div&gt;
&lt;twig:block-modal
    id=&quot;modal-static&quot;
    title=&quot;You must make a decision&quot;
    content=&quot;&lt;p&gt;This modal won&#039;t close when clicking outside. You must use one of the buttons.&lt;/p&gt;&quot;
    :withBackdrop=&quot;true&quot;
    :buttons=&quot;[
        {
            text: &#039;Cancel&#039;,
            type: &#039;light&#039;,
            dismiss: true
        },
        {
            text: &#039;Accept&#039;,
            type: &#039;danger&#039;,
            dismiss: true
        }
    ]&quot;
/&gt;

{# Modal centered with small size #}
&lt;div class=&quot;my-4&quot;&gt;
    &lt;button type=&quot;button&quot; class=&quot;btn btn-light d-block&quot; data-bs-toggle=&quot;modal&quot; data-bs-target=&quot;#modal-sm-centered&quot;&gt;
        Modal SM centered
    &lt;/button&gt;
&lt;/div&gt;
&lt;twig:block-modal
    id=&quot;modal-sm-centered&quot;
    title=&quot;Small and Centered&quot;
    content=&quot;&lt;p&gt;A small modal centered vertically.&lt;/p&gt;&quot;
    size=&quot;sm&quot;
    :centered=&quot;true&quot;
    :buttons=&quot;[
        {
            text: &#039;OK&#039;,
            type: &#039;info&#039;,
            dismiss: true
        }
    ]&quot;
/&gt;

{# Modal large with scroll #}
&lt;div class=&quot;mt-3 mb-4&quot;&gt;
    &lt;button type=&quot;button&quot; class=&quot;btn btn-light d-block&quot; data-bs-toggle=&quot;modal&quot; data-bs-target=&quot;#modal-lg-scroll&quot;&gt;
        Modal large with scroll
    &lt;/button&gt;
&lt;/div&gt;
&lt;twig:block-modal
    id=&quot;modal-lg-scroll&quot;
    title=&quot;Terms and Conditions&quot;
    content=&quot;&lt;div&gt;
        &lt;h4&gt;1. Introduction&lt;/h4&gt;
        &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit...&lt;/p&gt;
    &lt;/div&gt;&quot;
    size=&quot;lg&quot;
    :scrollable=&quot;true&quot;
    :buttons=&quot;[
        {
            text: &#039;Reject&#039;,
            type: &#039;light&#039;,
            dismiss: true
        },
        {
            text: &#039;Accept Terms&#039;,
            type: &#039;success&#039;,
            dismiss: true
        }
    ]&quot;
/&gt;

{# Modal extra large with form #}
&lt;div class=&quot;mt-2 mb-3&quot;&gt;
    &lt;button type=&quot;button&quot; class=&quot;btn btn-light d-block&quot; data-bs-toggle=&quot;modal&quot; data-bs-target=&quot;#modal-xl-form&quot;&gt;
        Form in modal XL
    &lt;/button&gt;
&lt;/div&gt;
&lt;twig:block-modal
    id=&quot;modal-xl-form&quot;
    title=&quot;User Registration&quot;
    content=&quot;&lt;form id=&#039;userForm&#039;&gt;
        &lt;div class=&#039;row&#039;&gt;
            &lt;div class=&#039;col-md-6 mb-3&#039;&gt;
                &lt;label for=&#039;firstName&#039; class=&#039;form-label&#039;&gt;Name&lt;/label&gt;
                &lt;input type=&#039;text&#039; class=&#039;form-control&#039; id=&#039;firstName&#039; required&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/form&gt;&quot;
    size=&quot;xl&quot;
    :centered=&quot;true&quot;
    :buttons=&quot;[
        {
            text: &#039;Cancel&#039;,
            type: &#039;secondary&#039;,
            dismiss: true
        },
        {
            text: &#039;Register&#039;,
            type: &#039;dark&#039;,
            attributes: &#039;onclick=&amp;quot;document.getElementById(\&#039;userForm\&#039;).submit()&amp;quot;&#039;
        }
    ]&quot;
/&gt;

{# Modal fullscreen #}
&lt;div class=&quot;mt-3 mb-4&quot;&gt;
    &lt;button type=&quot;button&quot; class=&quot;btn btn-light d-block&quot; data-bs-toggle=&quot;modal&quot; data-bs-target=&quot;#modal-fullscreen&quot;&gt;
        View in full screen
    &lt;/button&gt;
&lt;/div&gt;
&lt;twig:block-modal
    id=&quot;modal-fullscreen&quot;
    title=&quot;Fullscreen Mode&quot;
    content=&quot;&lt;div class=&#039;text-center&#039;&gt;
        &lt;h1 class=&#039;display-1&#039;&gt;👋&lt;/h1&gt;
        &lt;p class=&#039;lead&#039;&gt;This modal takes up the entire screen&lt;/p&gt;
    &lt;/div&gt;&quot;
    size=&quot;fullscreen&quot;
    :buttons=&quot;[
        {
            text: &#039;Exit&#039;,
            type: &#039;outline-primary&#039;,
            dismiss: true
        }
    ]&quot;
/&gt;

{# Modal without footer #}
&lt;div class=&quot;my-4&quot;&gt;
    &lt;button type=&quot;button&quot; class=&quot;btn btn-light d-block&quot; data-bs-toggle=&quot;modal&quot; data-bs-target=&quot;#modal-no-footer&quot;&gt;
        Modal without footer
    &lt;/button&gt;
&lt;/div&gt;
&lt;twig:block-modal
    id=&quot;modal-no-footer&quot;
    title=&quot;Content Only&quot;
    content=&quot;&lt;p&gt;This modal has no buttons in the footer, only the close button in the header.&lt;/p&gt;&quot;
/&gt;




---

#### Offcanvas

Hidden sidebars.

{# Left Offcanvas (start) #}
&lt;div class=&quot;mb-4&quot;&gt;
    &lt;button class=&quot;btn btn-light d-block&quot; type=&quot;button&quot; data-bs-toggle=&quot;offcanvas&quot; data-bs-target=&quot;#offcanvas-left&quot;&gt;
        Left Menu
    &lt;/button&gt;
&lt;/div&gt;

&lt;twig:block-offcanvas
    id=&quot;offcanvas-left&quot;
    position=&quot;start&quot;
    title=&quot;Navigation Menu&quot;
    content=&quot;&lt;ul class=&#039;nav flex-column&#039;&gt;
        &lt;li class=&#039;nav-item&#039;&gt;&lt;a class=&#039;nav-link&#039; href=&#039;#&#039;&gt;Home&lt;/a&gt;&lt;/li&gt;
        &lt;li class=&#039;nav-item&#039;&gt;&lt;a class=&#039;nav-link&#039; href=&#039;#&#039;&gt;Products&lt;/a&gt;&lt;/li&gt;
        &lt;li class=&#039;nav-item&#039;&gt;&lt;a class=&#039;nav-link&#039; href=&#039;#&#039;&gt;Services&lt;/a&gt;&lt;/li&gt;
        &lt;li class=&#039;nav-item&#039;&gt;&lt;a class=&#039;nav-link&#039; href=&#039;#&#039;&gt;Contact&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;&quot;
/&gt;

{# Right Offcanvas with Form #}
&lt;div class=&quot;mt-2 mb-3&quot;&gt;
    &lt;button class=&quot;btn btn-light d-block&quot; type=&quot;button&quot; data-bs-toggle=&quot;offcanvas&quot; data-bs-target=&quot;#offcanvas-right&quot;&gt;
        Edit Profile
    &lt;/button&gt;
&lt;/div&gt;

&lt;twig:block-offcanvas
    id=&quot;offcanvas-right&quot;
    position=&quot;end&quot;
    title=&quot;Edit Profile&quot;
    content=&quot;&lt;form id=&#039;profileForm&#039;&gt;
        &lt;div class=&#039;mb-3&#039;&gt;
            &lt;label class=&#039;form-label&#039;&gt;Name&lt;/label&gt;
            &lt;input type=&#039;text&#039; class=&#039;form-control&#039;&gt;
        &lt;/div&gt;
        &lt;div class=&#039;mb-3&#039;&gt;
            &lt;label class=&#039;form-label&#039;&gt;Email&lt;/label&gt;
            &lt;input type=&#039;email&#039; class=&#039;form-control&#039;&gt;
        &lt;/div&gt;
        &lt;div class=&#039;mb-3&#039;&gt;
            &lt;label class=&#039;form-label&#039;&gt;Bio&lt;/label&gt;
            &lt;textarea class=&#039;form-control&#039; rows=&#039;3&#039;&gt;&lt;/textarea&gt;
        &lt;/div&gt;
    &lt;/form&gt;&quot;
    :buttons=&quot;[
        {
            text: &#039;Cancel&#039;,
            type: &#039;light&#039;,
            dismiss: true
        },
        {
            text: &#039;Save&#039;,
            type: &#039;success&#039;,
            attributes: &#039;onclick=&amp;quot;document.getElementById(\&#039;profileForm\&#039;).submit()&amp;quot;&#039;
        }
    ]&quot;
/&gt;

{# Top Offcanvas #}
&lt;div class=&quot;mt-3 mb-4&quot;&gt;
    &lt;button class=&quot;btn btn-light d-block&quot; type=&quot;button&quot; data-bs-toggle=&quot;offcanvas&quot; data-bs-target=&quot;#offcanvas-top&quot;&gt;
        Notifications
    &lt;/button&gt;
&lt;/div&gt;

&lt;twig:block-offcanvas
    id=&quot;offcanvas-top&quot;
    position=&quot;top&quot;
    title=&quot;Notifications&quot;
    :scrollable=&quot;true&quot;
    content=&quot;&lt;div class=&#039;list-group&#039;&gt;
        &lt;a href=&#039;#&#039; class=&#039;list-group-item list-group-item-action&#039;&gt;
            &lt;div class=&#039;d-flex w-100 justify-content-between&#039;&gt;
                &lt;h6 class=&#039;mb-1&#039;&gt;New message&lt;/h6&gt;
                &lt;small&gt;3 mins ago&lt;/small&gt;
            &lt;/div&gt;
            &lt;p class=&#039;mb-1&#039;&gt;You have a new support message.&lt;/p&gt;
        &lt;/a&gt;
        &lt;!-- More notifications... --&gt;
    &lt;/div&gt;&quot;
/&gt;

{# Bottom Offcanvas without Backdrop #}
&lt;div class=&quot;mt-2 mb-3&quot;&gt;
    &lt;button class=&quot;btn btn-light d-block&quot; type=&quot;button&quot; data-bs-toggle=&quot;offcanvas&quot; data-bs-target=&quot;#offcanvas-bottom&quot;&gt;
        Music Player
    &lt;/button&gt;
&lt;/div&gt;

&lt;twig:block-offcanvas
    id=&quot;offcanvas-bottom&quot;
    position=&quot;bottom&quot;
    :withBackdrop=&quot;false&quot;
    :showClose=&quot;false&quot;
    content=&quot;&lt;div class=&#039;d-flex align-items-center justify-content-between&#039;&gt;
        &lt;div class=&#039;d-flex align-items-center&#039;&gt;
            &lt;img src=&#039;/img/home/derafu-dev-programmer.png&#039; width=&#039;50&#039; height=&#039;50&#039; class=&#039;me-3&#039;&gt;
            &lt;div&gt;
                &lt;h6 class=&#039;mb-0&#039;&gt;Song Name&lt;/h6&gt;
                &lt;small&gt;Artist&lt;/small&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&#039;btn-group&#039;&gt;
            &lt;button class=&#039;btn btn-link&#039;&gt;&lt;i class=&#039;fas fa-backward&#039;&gt;&lt;/i&gt;&lt;/button&gt;
            &lt;button class=&#039;btn btn-link&#039;&gt;&lt;i class=&#039;fas fa-play&#039;&gt;&lt;/i&gt;&lt;/button&gt;
            &lt;button class=&#039;btn btn-link&#039;&gt;&lt;i class=&#039;fas fa-forward&#039;&gt;&lt;/i&gt;&lt;/button&gt;
        &lt;/div&gt;
    &lt;/div&gt;&quot;
/&gt;

{# Offcanvas with Scroll and No Backdrop #}
&lt;div class=&quot;mt-2 mb-3&quot;&gt;
    &lt;button class=&quot;btn btn-light d-block&quot; type=&quot;button&quot; data-bs-toggle=&quot;offcanvas&quot; data-bs-target=&quot;#offcanvas-scroll&quot;&gt;
        Documentation
    &lt;/button&gt;
&lt;/div&gt;

&lt;twig:block-offcanvas
    id=&quot;offcanvas-scroll&quot;
    position=&quot;end&quot;
    title=&quot;Documentation&quot;
    :scrollable=&quot;true&quot;
    :withBackdrop=&quot;false&quot;
    content=&quot;&lt;div&gt;
        &lt;h4&gt;Introduction&lt;/h4&gt;
        &lt;p&gt;Lorem ipsum dolor sit amet...&lt;/p&gt;
        &lt;h4&gt;Getting Started&lt;/h4&gt;
        &lt;p&gt;Sed do eiusmod tempor incididunt...&lt;/p&gt;
        &lt;h4&gt;Configuration&lt;/h4&gt;
        &lt;p&gt;Ut enim ad minim veniam...&lt;/p&gt;
        &lt;!-- More content to demonstrate scroll --&gt;
    &lt;/div&gt;&quot;
    :buttons=&quot;[
        {
            text: &#039;Close&#039;,
            type: &#039;secondary&#039;,
            dismiss: true
        }
    ]&quot;
/&gt;




---

#### Quote

Quotes with author and note.

&lt;twig:block-quote
    class=&quot;my-4&quot;
    content=&quot;Lorem ipsum dolor sit amet, consectetur adipiscing &lt;a href=&#039;https://www.example.com/&#039; target=&#039;_blank&#039;&gt;elit&lt;/a&gt;.&quot;
    author=&quot;John Doe&quot;
    note=&quot;Quote&quot;
    image=&quot;/img/home/derafu-dev-programmer.png&quot;
/&gt;

{# Example with individual margins #}
&lt;twig:block-quote
    class=&quot;my-4&quot;
    content=&quot;Another example quote with custom margins.&quot;
    author=&quot;Jane Smith&quot;
    note=&quot;&lt;a href=&#039;https://www.example.com/&#039; target=&#039;_blank&#039;&gt;Quote example&lt;/a&gt;&quot;
/&gt;

{# Example without margins #}
&lt;twig:block-quote
    class=&quot;my-4&quot;
    content=&quot;A quote without special margins.&quot;
    author=&quot;Anonymous&quot;
/&gt;




---

#### Scrollspy

Scrollspy component for navigation.

{# Define the contents for the sections #}
{% set section_content_introduccion %}
    &lt;p&gt;Lorem ipsum dolor sit amet consectetur adipisicing elit. &lt;a href=&quot;https://www.example.com/&quot; target=&quot;_blank&quot;&gt;Quisquam, quos.&lt;/a&gt;&lt;/p&gt;
    &lt;p&gt;Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.&lt;/p&gt;
{% endset %}

{% set section_content_features %}
    &lt;p&gt;Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.&lt;/p&gt;
    &lt;p&gt;Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.&lt;/p&gt;
    &lt;ul&gt;
        &lt;li&gt;&lt;strong&gt;Lorem  Ipsum&lt;/strong&gt; - Lorem ipsum dolor sit amet consectetur adipisicing elit.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Lorem  Ipsum&lt;/strong&gt; - Lorem ipsum dolor sit amet consectetur adipisicing elit.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Lorem  Ipsum&lt;/strong&gt; - Lorem ipsum dolor sit amet consectetur adipisicing elit.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Lorem  Ipsum&lt;/strong&gt; - Lorem ipsum dolor sit amet consectetur adipisicing elit.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Lorem  Ipsum&lt;/strong&gt; - Lorem ipsum dolor sit amet consectetur adipisicing elit.&lt;/li&gt;
    &lt;/ul&gt;
{% endset %}

{% set section_content_options %}
    &lt;p&gt;Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.&lt;/p&gt;
    &lt;ul&gt;
        &lt;li&gt;&lt;code&gt;Lorem&lt;/code&gt;: Lorem ipsum dolor sit amet consectetur adipisicing elit.&lt;/li&gt;
        &lt;li&gt;&lt;code&gt;Lorem&lt;/code&gt;: Lorem ipsum dolor sit amet consectetur adipisicing elit.&lt;/li&gt;
        &lt;li&gt;&lt;code&gt;Lorem&lt;/code&gt;: Lorem ipsum dolor sit amet consectetur adipisicing elit.&lt;/li&gt;
        &lt;li&gt;&lt;code&gt;Lorem&lt;/code&gt;: Lorem ipsum dolor sit amet consectetur adipisicing elit.&lt;/li&gt;
        &lt;li&gt;&lt;code&gt;Lorem&lt;/code&gt;: Lorem ipsum dolor sit amet consectetur adipisicing elit.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.&lt;/p&gt;
{% endset %}

{% set section_content_usage %}
    &lt;p&gt;Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.&lt;/p&gt;
    &lt;ol&gt;
        &lt;li&gt;Lorem ipsum dolor sit amet consectetur adipisicing elit.&lt;/li&gt;
        &lt;li&gt;Lorem ipsum dolor sit amet consectetur adipisicing elit.&lt;/li&gt;
        &lt;li&gt;Lorem ipsum dolor sit amet consectetur adipisicing elit.&lt;/li&gt;
        &lt;li&gt;Lorem ipsum dolor sit amet consectetur adipisicing elit.&lt;/li&gt;
        &lt;li&gt;Lorem ipsum dolor sit amet consectetur adipisicing elit.&lt;/li&gt;
    &lt;/ol&gt;
    &lt;p&gt;Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.&lt;/p&gt;
{% endset %}

{% set section_content_advanced %}
    &lt;p&gt;Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.&lt;/p&gt;
    &lt;ul&gt;
        &lt;li&gt;Lorem ipsum dolor sit amet consectetur adipisicing elit.&lt;/li&gt;
        &lt;li&gt;Lorem ipsum dolor sit amet consectetur adipisicing elit.&lt;/li&gt;
        &lt;li&gt;Lorem ipsum dolor sit amet consectetur adipisicing elit.&lt;/li&gt;
        &lt;li&gt;Lorem ipsum dolor sit amet consectetur adipisicing elit.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.&lt;/p&gt;
{% endset %}

{# Example 1: Vertical Scrollspy #}
&lt;twig:block-scrollspy
    class=&quot;my-4 shadow-none&quot;
    style=&quot;--scrollspy-list-active-bg: #0066cc;&quot;
    height=&quot;100vh&quot;
    :cols=&quot;3&quot;
    :sections=&quot;[
        {
            id: &#039;introduction&#039;,
            title: &#039;Lorem ipsum dolor sit amet&#039;,
            content: section_content_introduction
        },
        {
            id: &#039;features&#039;,
            title: &#039;Lorem&#039;,
            content: section_content_features
        },
        {
            id: &#039;options&#039;,
            title: &#039;Lorem&#039;,
            content: section_content_options
        }
    ]&quot;
/&gt;

&lt;hr class=&quot;my-4&quot;/&gt;

{# Example 2: Horizontal Scrollspy #}
&lt;twig:block-scrollspy
    class=&quot;my-4 shadow &quot;
    style=&quot;--scrollspy-list-active-bg: #6610f2;&quot;
    height=&quot;300px&quot;
    orientation=&quot;horizontal&quot;
    :cols=&quot;12&quot;
    :sections=&quot;[
        {
            id: &#039;introduction-h&#039;,
            title: &#039;Lorem ipsum dolor sit amet&#039;,
            content: section_content_introduction,
        },
        {
            id: &#039;features-h&#039;,
            title: &#039;Lorem&#039;,
            content: section_content_features,
        },
        {
            id: &#039;options-h&#039;,
            title: &#039;Lorem&#039;,
            content: section_content_options,
        },
            {
            id: &#039;usage-h&#039;,
            title: &#039;Lorem&#039;,
            content: section_content_usage,
        }
    ]&quot;
/&gt;




---

#### Stats

Statistics display with values and text.

&lt;twig:block-stats
    class=&quot;my-4&quot;
    :stats=&quot;[
        {
            value: &#039;$40.000&#039;,
            text: &#039;Base price up to quota of 2.000 &lt;a href=\&#039;https://www.example.com/\&#039; target=\&#039;_blank\&#039;&gt;documents&lt;/a&gt;&#039;
        },
        {
            value: &#039;$15&#039;,
            text: &#039;Price per extra document above quota&#039;
        },
        {
            value: &#039;24/7&#039;,
            text: &#039;Technical support available&#039;
        },
        {
            value: &#039;99.9%&#039;,
            text: &#039;Guaranteed uptime&#039;
        }
    ]&quot;
/&gt;

&lt;twig:block-stats
    class=&quot;my-4&quot;
    :stats=&quot;[
        {
            value: &#039;$40.000&#039;,
            text: &#039;Base price up to quota of 2.000 documents&#039;
        },
        {
            value: &#039;$15&#039;,
            text: &#039;Price per extra document above quota&#039;
        },
        {
            value: &#039;24/7&#039;,
            text: &#039;Technical support available&#039;
        },
        {
            value: &#039;99.9%&#039;,
            text: &#039;Guaranteed uptime&#039;
        }
    ]&quot;
    textPosition=&quot;top&quot;
/&gt;

&lt;twig:block-stats
    class=&quot;my-4&quot;
    :stats=&quot;[
        {
            value: &#039;$40.000&#039;,
            text: &#039;Base price up to quota of 2.000 documents&#039;
        },
        {
            value: &#039;$15&#039;,
            text: &#039;Price per extra document above quota&#039;
        },
        {
            value: &#039;24/7&#039;,
            text: &#039;Technical support available&#039;
        },
        {
            value: &#039;99.9%&#039;,
            text: &#039;Guaranteed uptime&#039;
        },
        {
            value: &#039;99.9%&#039;,
            text: &#039;Guaranteed uptime&#039;
        }
    ]&quot;
/&gt;

&lt;twig:block-stats
    class=&quot;my-4&quot;
    :stats=&quot;[
        {
            value: &#039;1.000&#039;,
            text: &#039;Customers&#039;
        },
        {
            value: &#039;10&#039;,
            text: &#039;Years&#039;
        },
        {
            value: &#039;8/5&#039;,
            text: &#039;Technical support&#039;
        },
        {
            value: &#039;99.9%&#039;,
            text: &#039;Uptime&#039;
        },
        {
            value: &#039;5&#039;,
            text: &#039;Products&#039;
        },
        {
            value: &#039;3&#039;,
            text: &#039;Countries&#039;
        }
    ]&quot;
/&gt;




---

#### Steps

Step-by-step guide component with variable steps and arrow design.

&lt;twig:block-steps
    class=&quot;my-4&quot;
    :steps=&quot;[
        {
            icon: &#039;fa-solid fa-list&#039;,
            title: &#039;Test Set&#039;,
            content: &#039;Standard DTE documents requested by the &lt;a href=\&#039;https://www.sii.cl/factura_electronica/factura_mercado/menu_certificacion.html\&#039; target=\&#039;_blank\&#039;&gt;SII&lt;/a&gt; are sent for validation.&#039;
        },
        {
            icon: &#039;fa-solid fa-file-lines&#039;,
            title: &#039;Simulation&#039;,
            content: &#039;Real DTE examples that the company will issue in production are sent to the SII.&#039;
        },
        {
            icon: &#039;fa-solid fa-arrows-left-right&#039;,
            title: &#039;Exchange&#039;,
            content: &#039;XML files associated with the response to a DTE exchange are sent to the SII.&#039;
        },
        {
            icon: &#039;fa-solid fa-file-pdf&#039;,
            title: &#039;PDF Samples&#039;,
            content: &#039;PDFs of all DTE documents from the test set and simulation are sent to the SII. &lt;a href=\&#039;#\&#039; class=\&#039;btn btn-primary\&#039;&gt;Link&lt;/a&gt;&#039;
        }
    ]&quot;
    arrowType=&quot;straight&quot;
/&gt;

&lt;twig:block-steps
    class=&quot;my-4&quot;
    :steps=&quot;[
        {
            icon: &#039;fa-solid fa-list&#039;,
            title: &#039;Test Set&#039;,
            content: &#039;Standard DTE documents requested by the &lt;a href=\&#039;https://www.sii.cl/factura_electronica/factura_mercado/menu_certificacion.html\&#039; target=\&#039;_blank\&#039;&gt;SII&lt;/a&gt; are sent for validation.&#039;
        },
        {
            icon: &#039;fa-solid fa-file-lines&#039;,
            title: &#039;Simulation&#039;,
            content: &#039;Real DTE examples that the company will issue in production are sent to the SII.&#039;
        },
        {
            icon: &#039;fa-solid fa-arrows-left-right&#039;,
            title: &#039;Exchange&#039;,
            content: &#039;XML files associated with the response to a DTE exchange are sent to the SII.&#039;
        },
        {
            icon: &#039;fa-solid fa-file-pdf&#039;,
            title: &#039;PDF Samples&#039;,
            content: &#039;PDFs of all DTE documents from the test set and simulation are sent to the SII.&#039;
        }
    ]&quot;
/&gt;

&lt;twig:block-steps
    class=&quot;my-4&quot;
    :steps=&quot;[
        {
            icon: &#039;fa-solid fa-list&#039;,
            title: &#039;Test Set&#039;,
            content: &#039;Standard DTE documents requested by the &lt;a href=\&#039;https://www.sii.cl/factura_electronica/factura_mercado/menu_certificacion.html\&#039; target=\&#039;_blank\&#039;&gt;SII&lt;/a&gt; are sent for validation.&#039;
        },
        {
            icon: &#039;fa-solid fa-arrows-left-right&#039;,
            title: &#039;Exchange&#039;,
            content: &#039;XML files associated with the response to a DTE exchange are sent to the SII.&#039;
        },
        {
            icon: &#039;fa-solid fa-file-pdf&#039;,
            title: &#039;PDF Samples&#039;,
            content: &#039;PDFs of all DTE documents from the test set and simulation are sent to the SII.&#039;
        }
    ]&quot;
    arrowType=&quot;straight&quot;
/&gt;

&lt;twig:block-steps
    class=&quot;my-4&quot;
    :steps=&quot;[
        {
            icon: &#039;fa-solid fa-list&#039;,
            title: &#039;Test Set&#039;,
            content: &#039;Standard DTE documents requested by the &lt;a href=\&#039;https://www.sii.cl/factura_electronica/factura_mercado/menu_certificacion.html\&#039; target=\&#039;_blank\&#039;&gt;SII&lt;/a&gt; are sent for validation.&#039;
        },
        {
            icon: &#039;fa-solid fa-arrows-left-right&#039;,
            title: &#039;Exchange&#039;,
            content: &#039;XML files associated with the response to a DTE exchange are sent to the SII.&#039;
        },
        {
            icon: &#039;fa-solid fa-file-pdf&#039;,
            title: &#039;PDF Samples&#039;,
            content: &#039;PDFs of all DTE documents from the test set and simulation are sent to the SII.&#039;
        }
    ]&quot;
/&gt;




---

#### Table

Data table component with styling options.

{# Basic table #}
&lt;twig:block-table
    class=&quot;my-4&quot;
    caption=&quot;Table&quot;
    :headers=&quot;[
        [
            {&#039;text&#039;: &#039;ID&#039;, &#039;scope&#039;: &#039;col&#039;, &#039;align&#039;: &#039;center&#039;},
            {&#039;text&#039;: &#039;Name&#039;, &#039;scope&#039;: &#039;col&#039;},
            {&#039;text&#039;: &#039;Email&#039;, &#039;scope&#039;: &#039;col&#039;},
            {&#039;text&#039;: &#039;Status&#039;, &#039;scope&#039;: &#039;col&#039;, &#039;align&#039;: &#039;center&#039;}
        ]
    ]&quot;
    :rows=&quot;[
        [&#039;1&#039;, &#039;&lt;a href=\&#039;https://www.example.com/\&#039; target=\&#039;_blank\&#039;&gt;John Smith&lt;/a&gt;&#039;, &#039;john@example.com&#039;, &#039;Active&#039;],
        [&#039;2&#039;, &#039;Mary Johnson&#039;, &#039;mary@example.com&#039;, &#039;Inactive&#039;],
        [&#039;3&#039;, &#039;Charles Brown&#039;, &#039;charles@example.com&#039;, &#039;Pending&#039;]
    ]&quot;
    :columns=&quot;[
        {&#039;index&#039;: 0, &#039;width&#039;: &#039;80px&#039;, &#039;align&#039;: &#039;center&#039;},
        {&#039;index&#039;: 3, &#039;align&#039;: &#039;center&#039;, &#039;class&#039;: &#039;fw-bold&#039;}
    ]&quot;
    :rowClasses=&quot;[
        {&#039;col&#039;: 3, &#039;values&#039;: {
            &#039;Active&#039;: &#039;table-success&#039;,
            &#039;Inactive&#039;: &#039;table-danger&#039;,
            &#039;Pending&#039;: &#039;table-warning&#039;
        }}
    ]&quot;
/&gt;

{# Table with complex headers and footer #}
&lt;twig:block-table
    class=&quot;my-4&quot;
    caption=&quot;Sales Report 2024&quot;
    :headers=&quot;[
        [
            {text: &#039;Region&#039;, scope: &#039;col&#039;, rowspan: &#039;2&#039;},
            {text: &#039;Quarterly Sales&#039;, scope: &#039;col&#039;, colspan: &#039;4&#039;, class: &#039;text-center&#039;},
        ],
        [
            {text: &#039;Q1&#039;, scope: &#039;col&#039;, align: &#039;end&#039;},
            {text: &#039;Q2&#039;, scope: &#039;col&#039;, align: &#039;end&#039;},
            {text: &#039;Q3&#039;, scope: &#039;col&#039;, align: &#039;end&#039;},
            {text: &#039;Q4&#039;, scope: &#039;col&#039;, align: &#039;end&#039;}
        ]
    ]&quot;
    :rows=&quot;[
        [&#039;North&#039;, &#039;10,500&#039;, &#039;12,300&#039;, &#039;11,800&#039;, &#039;13,200&#039;],
        [&#039;South&#039;, &#039;8,900&#039;, &#039;9,200&#039;, &#039;9,800&#039;, &#039;10,100&#039;],
        [&#039;East&#039;, &#039;11,200&#039;, &#039;11,500&#039;, &#039;12,100&#039;, &#039;12,800&#039;]
    ]&quot;
    :footer=&quot;[
        [
            {text: &#039;Total&#039;, colspan: &#039;1&#039;},
            {text: &#039;30,600&#039;, class: &#039;fw-bold text-end&#039;},
            {text: &#039;33,000&#039;, class: &#039;fw-bold text-end&#039;},
            {text: &#039;33,700&#039;, class: &#039;fw-bold text-end&#039;},
            {text: &#039;36,100&#039;, class: &#039;fw-bold text-end&#039;}
        ]
    ]&quot;
    :columns=&quot;[
        {width: &#039;120px&#039;},
        {align: &#039;end&#039;},
        {align: &#039;end&#039;},
        {align: &#039;end&#039;},
        {align: &#039;end&#039;}
    ]&quot;
    :bordered=&quot;true&quot;
    :striped=&quot;true&quot;
    :hover=&quot;false&quot;
/&gt;

{# Table with actions and toggle #}
&lt;twig:block-table
    class=&quot;my-4&quot;
    :headers=&quot;[
        [
            {text: &#039;Product&#039;, scope: &#039;col&#039;},
            {text: &#039;Category&#039;, scope: &#039;col&#039;},
            {text: &#039;Stock&#039;, scope: &#039;col&#039;, align: &#039;center&#039;},
            {text: &#039;Price&#039;, scope: &#039;col&#039;, align: &#039;end&#039;}
        ]
    ]&quot;
    :rows=&quot;[
        [&#039;Laptop XPS&#039;, &#039;Computers&#039;, &#039;15&#039;, &#039;$1,299.99&#039;],
        [&#039;Wireless Mouse&#039;, &#039;Accessories&#039;, &#039;45&#039;, &#039;$29.99&#039;],
        [&#039;Monitor 27\&#039;\&#039;&#039;, &#039;Displays&#039;, &#039;8&#039;, &#039;$299.99&#039;]
    ]&quot;
    :actions=&quot;[
        {
            text: &#039;Export to Excel&#039;,
            icon: &#039;fas fa-file-excel&#039;,
            url: &#039;/export/excel&#039;
        },
        {
            text: &#039;Export to PDF&#039;,
            icon: &#039;fas fa-file-pdf&#039;,
            url: &#039;/export/pdf&#039;
        },
        {type: &#039;divider&#039;},
        {
            text: &#039;Print&#039;,
            icon: &#039;fas fa-print&#039;,
            onclick: &#039;window.print()&#039;
        }
    ]&quot;
    :toggleable=&quot;true&quot;
    :visible=&quot;true&quot;
    :maxHeight=&quot;300&quot;
    :hover=&quot;false&quot;
/&gt;

{# Minimalist responsive table #}
&lt;twig:block-table
    class=&quot;my-4&quot;
    :headers=&quot;[
        [
            {text: &#039;#&#039;, scope: &#039;col&#039;},
            {text: &#039;Title&#039;, scope: &#039;col&#039;},
            {text: &#039;Date&#039;, scope: &#039;col&#039;},
            {text: &#039;Status&#039;, scope: &#039;col&#039;}
        ]
    ]&quot;
    :rows=&quot;[
        [&#039;1&#039;, &#039;Document 1&#039;, &#039;2024-02-22&#039;, &#039;&lt;span class=\&#039;badge bg-success\&#039;&gt;Approved&lt;/span&gt;&#039;],
        [&#039;2&#039;, &#039;Document 2&#039;, &#039;2024-02-21&#039;, &#039;&lt;span class=\&#039;badge bg-warning\&#039;&gt;Pending&lt;/span&gt;&#039;],
        [&#039;3&#039;, &#039;Document 3&#039;, &#039;2024-02-20&#039;, &#039;&lt;span class=\&#039;badge bg-danger\&#039;&gt;Rejected&lt;/span&gt;&#039;]
    ]&quot;
    :small=&quot;true&quot;
    :striped=&quot;false&quot;
    :bordered=&quot;false&quot;
    :hover=&quot;false&quot;
/&gt;




---

#### Tabs

Tabbed content interface, with horizontal and vertical layout.

{% set tab_content_1 %}
    &lt;twig:block-text-image
        title=&quot;Lorem ipsum dolor sit amet&quot;
        content=&quot;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque sit amet commodo odio. Mauris sagittis consequat quam interdum laoreet. Vivamus pretium pellentesque ligula, eget euismod turpis maximus sed.&quot;
        :buttons=&quot;[
            {
                text: &#039;Learn more&#039;,
                url: &#039;https://www.example.com&#039;
            },
            {
                text: &#039;Request a FREE trial period&#039;,
                url: &#039;https://www.example.com&#039;
            }
        ]&quot;
        image=&quot;/img/home/derafu-dev-programmer.png&quot;
        :insideTabs=&quot;true&quot;
    /&gt;
{% endset %}

{% set tab_content_2 %}
    &lt;h2&gt;Information&lt;/h2&gt;
    &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque sit amet commodo odio. Mauris sagittis consequat quam interdum laoreet. Vivamus pretium pellentesque ligula, eget euismod turpis maximus sed....&lt;/p&gt;
{% endset %}

&lt;twig:block-tabs
    class=&quot;my-4&quot;
    :tabs=&quot;[
        {
            id: &#039;features&#039;,
            title: &#039;Features&#039;,
            icon: &#039;fa-solid fa-list&#039;,
            content: tab_content_1
        },
        {
            id: &#039;info&#039;,
            title: &#039;Information&#039;,
            icon: &#039;fa-solid fa-info-circle&#039;,
            content: tab_content_2
        }
    ]&quot;
/&gt;

&lt;twig:block-tabs
    class=&quot;my-4&quot;
    position=&quot;vertical&quot;
    :withShadow=&quot;true&quot;
    :cols=&quot;3&quot;
    :tabs=&quot;[
        {
            id: &#039;features2&#039;,
            title: &#039;hello&#039;,
            icon: &#039;fa-solid fa-list&#039;,
            content: tab_content_1
        },
        {
            id: &#039;info2&#039;,
            title: &#039;Information&#039;,
            icon: &#039;fa-solid fa-info-circle&#039;,
            content: tab_content_2
        }
    ]&quot;
/&gt;




---

#### Team

Team members display with roles and social links.

&lt;twig:block-team
    class=&quot;my-4&quot;
    :cols=&quot;1&quot;
    :members=&quot;[
        {
            name: &#039;John Doe&#039;,
            role: &#039;Founder&#039;,
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            bio: &#039;Lorem ipsum dolor sit amet, consectetur adipiscing &lt;a href=\&#039;https://www.example.com/\&#039; target=\&#039;_blank\&#039;&gt;elit.&lt;/a href&gt;&#039;,
            links: [
                {
                    icon: &#039;fa-brands fa-linkedin&#039;,
                    text: &#039;LinkedIn&#039;,
                    url: &#039;https://www.example.com&#039;
                },
                {
                    icon: &#039;fa-brands fa-github&#039;,
                    text: &#039;GitHub&#039;,
                    url: &#039;https://www.example.com&#039;
                }
            ]
        }
    ]&quot;
/&gt;

&lt;twig:block-team
    class=&quot;my-4&quot;
    :cols=&quot;2&quot;
    :members=&quot;[
        {
            name: &#039;John Doe&#039;,
            role: &#039;Founder&#039;,
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            bio: &#039;Lorem ipsum dolor sit amet, consectetur adipiscing elit.&#039;,
            links: [
                {
                    icon: &#039;fa-brands fa-linkedin&#039;,
                    text: &#039;LinkedIn&#039;,
                    url: &#039;https://www.example.com&#039;
                },
                {
                    icon: &#039;fa-brands fa-github&#039;,
                    text: &#039;GitHub&#039;,
                    url: &#039;https://www.example.com&#039;
                }
            ]
        },
        {
            name: &#039;Jane Smith&#039;,
            role: &#039;Co-Founder&#039;,
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            bio: &#039;Lorem ipsum dolor sit amet, consectetur adipiscing elit.&#039;,
            links: [
                {
                    icon: &#039;fa-brands fa-linkedin&#039;,
                    text: &#039;LinkedIn&#039;,
                    url: &#039;https://www.example.com&#039;
                }
            ]
        }
    ]&quot;
/&gt;

&lt;twig:block-team
    class=&quot;my-4&quot;
    :cols=&quot;3&quot;
    :members=&quot;[
        {
            name: &#039;John Doe&#039;,
            role: &#039;Founder&#039;,
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            bio: &#039;Lorem ipsum dolor sit amet, consectetur adipiscing elit.&#039;,
            links: [
                {
                    icon: &#039;fa-brands fa-linkedin&#039;,
                    text: &#039;LinkedIn&#039;,
                    url: &#039;https://www.example.com&#039;
                },
                {
                    icon: &#039;fa-brands fa-github&#039;,
                    text: &#039;GitHub&#039;,
                    url: &#039;https://www.example.com&#039;
                }
            ]
        },
        {
            name: &#039;Jane Smith&#039;,
            role: &#039;Co-Founder&#039;,
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            bio: &#039;Lorem ipsum dolor sit amet, consectetur adipiscing elit.&#039;,
            links: [
                {
                    icon: &#039;fa-brands fa-linkedin&#039;,
                    text: &#039;LinkedIn&#039;,
                    url: &#039;https://www.example.com&#039;
                }
            ]
        },
        {
            name: &#039;Mike Johnson&#039;,
            role: &#039;Developer&#039;,
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            bio: &#039;Lorem ipsum dolor sit amet, consectetur adipiscing elit.&#039;,
            links: [
                {
                    icon: &#039;fa-brands fa-github&#039;,
                    text: &#039;GitHub&#039;,
                    url: &#039;https://www.example.com&#039;
                }
            ]
        }
    ]&quot;
/&gt;

&lt;twig:block-team
    class=&quot;my-4&quot;
    :cols=&quot;4&quot;
    :members=&quot;[
        {
            name: &#039;John Doe&#039;,
            role: &#039;Founder&#039;,
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            bio: &#039;Lorem ipsum dolor sit amet, consectetur adipiscing elit.&#039;,
            links: [
                {
                    icon: &#039;fa-brands fa-linkedin&#039;,
                    text: &#039;LinkedIn&#039;,
                    url: &#039;https://www.example.com&#039;
                }
            ]
        },
        {
            name: &#039;Jane Smith&#039;,
            role: &#039;Co-Founder&#039;,
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            bio: &#039;Lorem ipsum dolor sit amet, consectetur adipiscing elit.&#039;,
            links: [
                {
                    icon: &#039;fa-brands fa-github&#039;,
                    text: &#039;GitHub&#039;,
                    url: &#039;https://www.example.com&#039;
                }
            ]
        },
        {
            name: &#039;Saul Goodman&#039;,
            role: &#039;HR&#039;,
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            bio: &#039;Lorem ipsum dolor sit amet, consectetur adipiscing elit.&#039;,
            links: [
                {
                    icon: &#039;fa-brands fa-linkedin&#039;,
                    text: &#039;LinkedIn&#039;,
                    url: &#039;https://www.example.com&#039;
                }
            ]
        },
        {
            name: &#039;Mike Johnson&#039;,
            role: &#039;Developer&#039;,
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            bio: &#039;Lorem ipsum dolor sit amet, consectetur adipiscing elit.&#039;,
            links: [
                {
                    icon: &#039;fa-brands fa-github&#039;,
                    text: &#039;GitHub&#039;,
                    url: &#039;https://www.example.com&#039;
                }
            ]
        }
    ]&quot;
/&gt;




---

#### Testimonials

Testimonials display with background and image.

&lt;twig:block-testimonials
    class=&quot;my-4&quot;
    :testimonials=&quot;[
        {
            background: &#039;/img/home/derafu-dev-programmer.png&#039;,
            content: &#039;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi malesuada tellus velit, in fringilla turpis interdum aliquet. Donec eget neque sit amet orci gravida eleifend. Fusce aliquet tempus gravida.&#039;,
            author: &#039;John Doe&#039;,
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            note: &#039;Lorem ipsum&#039;,
            url: &#039;https://www.example.com&#039;
        },
        {
            background: &#039;/img/home/derafu-dev-programmer.png&#039;,
            content: &#039;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi malesuada tellus velit, in fringilla turpis interdum aliquet. Donec eget neque sit amet orci gravida eleifend. Fusce aliquet tempus gravida.&#039;,
            author: &#039;John Doe&#039;,
            note: &#039;Lorem ipsum&#039;,
            position: &#039;left&#039;
        },
        {
            background: &#039;/img/home/derafu-dev-programmer.png&#039;,
            content: &#039;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi malesuada tellus velit, in fringilla turpis interdum aliquet. Donec eget neque sit amet orci gravida eleifend. Fusce aliquet tempus gravida.&#039;,
            author: &#039;John Doe&#039;,
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            url: &#039;http://www.example.com&#039;,
            position: &#039;right&#039;
        }
    ]&quot;
/&gt;

&lt;twig:block-testimonials
    class=&quot;my-4&quot;
    interval=&quot;2000&quot;
    :testimonials=&quot;[
        {
            background: &#039;/img/home/derafu-dev-programmer.png&#039;,
            content: &#039;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi malesuada tellus velit, in fringilla turpis interdum aliquet. Donec eget neque sit amet orci gravida eleifend. Fusce aliquet &lt;a href=\&#039;https://www.example.com/\&#039; target=\&#039;_blank\&#039;&gt;tempus gravida.&lt;/a href&gt;&#039;,
            author: &#039;John Doe&#039;,
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            note: &#039;Lorem ipsum&#039;,
            url: &#039;http://www.example.com&#039;
        },
        {
            background: &#039;/img/home/derafu-dev-programmer.png&#039;,
            content: &#039;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi malesuada tellus velit, in fringilla turpis interdum aliquet. Donec eget neque sit amet orci gravida eleifend. Fusce aliquet tempus gravida.&#039;,
            author: &#039;John Doe&#039;,
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            url: &#039;http://www.example.com&#039;
        }
    ]&quot;
/&gt;

&lt;twig:block-testimonials
    class=&quot;my-4&quot;
    :testimonials=&quot;[
        {
            background: &#039;/img/home/derafu-dev-programmer.png&#039;,
            content: &#039;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi malesuada tellus velit, in fringilla turpis interdum aliquet. Donec eget neque sit amet orci gravida eleifend. Fusce aliquet tempus gravida.&#039;,
            author: &#039;John Doe&#039;,
            image: &#039;/img/home/derafu-dev-programmer.png&#039;,
            note: &#039;Lorem ipsum&#039;
        }
    ]&quot;
/&gt;




---

#### Text Image

Combined text and image layout.

&lt;twig:block-text-image
    class=&quot;my-4&quot;
    image=&quot;/img/home/derafu-dev-programmer.png&quot;
    title=&quot;Lorem ipsum dolor sit amet&quot;
    content=&quot;&lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi malesuada tellus velit, in fringilla turpis interdum aliquet. Donec eget neque sit amet orci gravida eleifend.&lt;/p&gt;&lt;p&gt;Fusce aliquet tempus gravida. Nam ullamcorper libero ac velit pharetra, in volutpat sem vulputate. Integer quis suscipit nibh, non tempor enim. Nulla ipsum leo, vulputate nec dolor nec, aliquet porttitor nibh. Donec maximus tellus vitae ornare &lt;a href=&#039;https://www.example.com/&#039; target=&#039;_blank&#039;&gt;tellus&lt;/a&gt; dignissim.&lt;/p&gt;&quot;
    :buttons=&quot;[
        {
            text: &#039;Example.com 1&#039;,
            url: &#039;https://www.example.com&#039;
        },
        {
            text: &#039;Example.com 2&#039;,
            url: &#039;https://www.example.com&#039;
        }
    ]&quot;
/&gt;

&lt;twig:block-text-image
    class=&quot;my-4&quot;
    image=&quot;/img/home/derafu-dev-programmer.png&quot;
    imagePosition=&quot;left&quot;
    title=&quot;Lorem ipsum dolor sit amet&quot;
    content=&quot;&lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi malesuada tellus velit, in fringilla turpis interdum aliquet. Donec eget neque sit amet orci gravida eleifend.&lt;/p&gt;&lt;p&gt;Fusce aliquet tempus gravida. Nam ullamcorper libero ac velit pharetra, in volutpat sem vulputate. Integer quis suscipit nibh, non tempor enim. Nulla ipsum leo, vulputate nec dolor nec, aliquet porttitor nibh. Donec maximus tellus vitae ornare dignissim.&lt;/p&gt;&quot;
    :buttons=&quot;[
        {
            text: &#039;Example.com&#039;,
            url: &#039;https://www.example.com&#039;
        }
    ]&quot;
/&gt;




---

#### Text Video

Combined text and video layout.

&lt;twig:block-text-video
    class=&quot;my-4&quot;
    video=&quot;https://www.youtube.com/watch?v=GOAEIMx39-w&quot;
    title=&quot;Lorem ipsum dolor sit amet&quot;
    content=&quot;&lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi malesuada tellus velit, in fringilla turpis interdum aliquet. Donec eget neque sit amet orci gravida eleifend.&lt;/p&gt;&lt;p&gt;Fusce aliquet tempus gravida. Nam ullamcorper libero ac velit pharetra, in volutpat sem vulputate. Integer quis suscipit nibh, non tempor enim. Nulla ipsum leo, vulputate nec dolor nec, aliquet porttitor nibh. Donec maximus tellus vitae ornare &lt;a href=&#039;https://www.example.com/&#039; target=&#039;_blank&#039;&gt;tellus&lt;/a&gt; dignissim.&lt;/p&gt;&quot;
    :buttons=&quot;[
        {
            text: &#039;Example.com 1&#039;,
            url: &#039;https://www.example.com&#039;
        },
        {
            text: &#039;Example.com 2&#039;,
            url: &#039;https://www.example.com&#039;
        }
    ]&quot;
/&gt;

&lt;twig:block-text-video
    class=&quot;my-4&quot;
    videoPosition=&quot;left&quot;
    video=&quot;https://www.youtube.com/watch?v=GOAEIMx39-w&quot;
    title=&quot;Lorem ipsum dolor sit amet&quot;
    content=&quot;&lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi malesuada tellus velit, in fringilla turpis interdum aliquet. Donec eget neque sit amet orci gravida eleifend.&lt;/p&gt;&lt;p&gt;Fusce aliquet tempus gravida. Nam ullamcorper libero ac velit pharetra, in volutpat sem vulputate. Integer quis suscipit nibh, non tempor enim. Nulla ipsum leo, vulputate nec dolor nec, aliquet porttitor nibh. Donec maximus tellus vitae ornare dignissim.&lt;/p&gt;&quot;
    :buttons=&quot;[
        {
            text: &#039;Example.com&#039;,
            url: &#039;https://www.example.com&#039;
        }
    ]&quot;
/&gt;




---

#### Timeline

Chronological event display with configurable alignment.

&lt;twig:block-timeline
    class=&quot;my-4&quot;
    linePosition=&quot;center&quot;
    :events=&quot;[
        {
            date: &#039;2023-06-15&#039;,
            title: &#039;Official Partnership&#039;,
            content: &#039;We become Official RPA Partner in Chile.&#039;,
            icon: &#039;fa-solid fa-handshake fa-fw&#039;
        },
        {
            date: &#039;2022-11&#039;,
            title: &#039;International Expansion&#039;,
            content: &#039;We started receiving requests from clients outside Chile.&#039;,
            icon: &#039;fa-solid fa-globe fa-fw&#039;
        },
        {
            date: &#039;2022-06&#039;,
            title: &#039;International Project&#039;,
            content: &#039;Product development for clients outside Chile.&#039;,
            icon: &#039;fa-solid fa-globe fa-fw&#039;
        },
        {
            date: &#039;2021-05&#039;,
            title: &#039;New Products&#039;,
            content: &#039;Creation of Software 1, Software 2 and &lt;a href=\&#039;https://www.example.com/\&#039; target=\&#039;_blank\&#039;&gt;Software 3&lt;/a&gt;.&#039;,
            icon: &#039;fa-solid fa-briefcase fa-fw&#039;
        }
    ]&quot;
/&gt;

&lt;twig:block-timeline
    class=&quot;my-4&quot;
    linePosition=&quot;left&quot;
    :events=&quot;[
        {
            date: &#039;2023-06-15&#039;,
            title: &#039;Official Partnership&#039;,
            content: &#039;We become Official RPA Partner in Chile.&#039;,
            icon: &#039;fa-solid fa-handshake fa-fw&#039;
        },
        {
            date: &#039;2022-11&#039;,
            title: &#039;International Expansion&#039;,
            content: &#039;We started receiving requests from clients outside Chile.&#039;,
            icon: &#039;fa-solid fa-globe fa-fw&#039;
        },
        {
            date: &#039;2022-06&#039;,
            title: &#039;International Project&#039;,
            content: &#039;Product development for clients outside Chile.&#039;,
            icon: &#039;fa-solid fa-globe&#039;
        },
        {
            date: &#039;2021-05&#039;,
            title: &#039;New Products&#039;,
            content: &#039;Creation of Software 1, Software 2 and &lt;a href=\&#039;https://www.example.com/\&#039; target=\&#039;_blank\&#039;&gt;Software 3&lt;/a&gt;.&#039;,
            icon: &#039;fa-solid fa-briefcase&#039;
        }
    ]&quot;
/&gt;

&lt;twig:block-timeline
    class=&quot;my-4&quot;
    linePosition=&quot;right&quot;
    :events=&quot;[
        {
            date: &#039;2023-06-15&#039;,
            title: &#039;Official Partnership&#039;,
            content: &#039;We become Official RPA Partner in Chile.&#039;,
            icon: &#039;fa-solid fa-handshake&#039;
        },
        {
            date: &#039;2022-11&#039;,
            title: &#039;International Expansion&#039;,
            content: &#039;We started receiving requests from clients outside Chile.&#039;,
            icon: &#039;fa-solid fa-globe&#039;
        },
        {
            date: &#039;2022-06&#039;,
            title: &#039;International Project&#039;,
            content: &#039;Product development for clients outside Chile.&#039;,
            icon: &#039;fa-solid fa-globe fa-fw&#039;
        },
        {
            date: &#039;2021-05&#039;,
            title: &#039;New Products&#039;,
            content: &#039;Creation of Software 1, Software 2 and &lt;a href=\&#039;https://www.example.com/\&#039; target=\&#039;_blank\&#039;&gt;Software 3&lt;/a&gt;.&#039;,
            icon: &#039;fa-solid fa-briefcase fa-fw&#039;
        }
    ]&quot;
/&gt;

&lt;twig:block-timeline
    class=&quot;my-4&quot;
    linePosition=&quot;left&quot;
    :events=&quot;[
        {
            date: &#039;2023&#039;,
            title: &#039;Official Partnership&#039;,
            content: &#039;We become Official RPA Partner in Chile.&#039;,
            icon: &#039;fa-solid fa-handshake&#039;
        },
        {
            date: &#039;2022&#039;,
            title: &#039;International Expansion&#039;,
            content: &#039;We started receiving requests from clients outside Chile.&#039;,
            icon: &#039;fa-solid fa-globe&#039;
        },
        {
            date: &#039;2022&#039;,
            title: &#039;International Project&#039;,
            content: &#039;Product development for clients outside Chile.&#039;,
            icon: &#039;fa-solid fa-globe fa-fw&#039;
        },
        {
            date: &#039;2021&#039;,
            title: &#039;New Products&#039;,
            content: &#039;Creation of Software 1, Software 2 and &lt;a href=\&#039;https://www.example.com/\&#039; target=\&#039;_blank\&#039;&gt;Software 3&lt;/a&gt;.&#039;,
            icon: &#039;fa-solid fa-briefcase fa-fw&#039;
        }
    ]&quot;
/&gt;




---

#### Title

Customizable title component.

&lt;twig:block-title
    class=&quot;my-4&quot;
    title=&quot;Title&quot;
    subtitle=&quot;Lorem ipsum dolor sit amet, consectetur &lt;a href=&#039;https://www.example.com/&#039; target=&#039;_blank&#039;&gt;adipiscing elit.&lt;/a&gt;&quot;
/&gt;

&lt;twig:block-title
    class=&quot;my-4&quot;
    title=&quot;Title&quot;
    subtitle=&quot;Lorem ipsum dolor sit amet, consectetur &lt;a href=&#039;https://www.example.com/&#039; target=&#039;_blank&#039;&gt;adipiscing elit.&lt;/a&gt;&quot;
    :border=&quot;true&quot;
/&gt;




---

#### Toast

Toast notifications with customizable types.

{% set types = [&#039;primary&#039;, &#039;secondary&#039;, &#039;success&#039;, &#039;danger&#039;, &#039;warning&#039;, &#039;info&#039;, &#039;light&#039;, &#039;dark&#039;] %}
{% for type in types %}
    &lt;twig:block-toast
        class=&quot;my-2&quot;
        type=&quot;{{ type }}&quot;
        title=&quot;Toast type: {{ type }}&quot;
        content=&quot;Lorem ipsum dolor sit amet, consectetur &lt;a href=&#039;https://www.example.com/&#039; target=&#039;_blank&#039;&gt;adipiscing elit.&lt;/a href&gt;&quot;
        time=&quot;5 mins ago&quot;
    /&gt;
{% endfor %}




---

#### Video Grid

Grid layout for multiple videos.

&lt;twig:block-video-grid
    class=&quot;my-4&quot;
    :cols=&quot;3&quot;
    :videos=&quot;[
        {
            video: &#039;https://www.youtube.com/watch?v=GOAEIMx39-w&#039;,
            title: &#039;Lorem ipsum&#039;,
            content: &#039;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore &lt;a href=\&#039;https://www.example.com/\&#039; target=\&#039;_blank\&#039;&gt;magna aliqua.&lt;/a&gt;&#039;,
            buttonText: &#039;Learn more&#039;,
            buttonUrl: &#039;#&#039;
        },
        {
            video: &#039;https://www.youtube.com/watch?v=GOAEIMx39-w&#039;,
            title: &#039;Lorem ipsum&#039;,
            content: &#039;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.&#039;,
            buttonText: &#039;Learn more&#039;,
            buttonUrl: &#039;&#039;
        },
        {
            video: &#039;https://www.youtube.com/watch?v=GOAEIMx39-w&#039;,
            title: &#039;Lorem ipsum&#039;,
            content: &#039;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.&#039;,
            buttonText: &#039;Learn more&#039;,
            buttonUrl: &#039;#&#039;
        }
    ]&quot;
/&gt;

&lt;twig:block-video-grid
    class=&quot;my-4&quot;
    :cols=&quot;2&quot;
    :videos=&quot;[
        {
            video: &#039;https://www.youtube.com/watch?v=GOAEIMx39-w&#039;
        },
        {
            video: &#039;https://www.youtube.com/watch?v=GOAEIMx39-w&#039;
        }
    ]&quot;
/&gt;

&lt;twig:block-video-grid
    class=&quot;my-4&quot;
    :cols=&quot;1&quot;
    :videos=&quot;[
        {
            video: &#039;https://www.youtube.com/watch?v=GOAEIMx39-w&#039;
        }
    ]&quot;
/&gt;




---

#### Video

Video player component.

&lt;twig:block-video
    class=&quot;my-4&quot;
    size=&quot;normal&quot;
    align=&quot;center&quot;
    title=&quot;Lorem ipsum dolor sit amet&quot;
    video=&quot;https://www.youtube.com/watch?v=GOAEIMx39-w&quot;
    content=&quot;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat &lt;a href=&#039;https://www.example.com/&#039; target=&#039;_blank&#039;&gt;nulla pariatur.&lt;/a&gt;&quot;
    :buttons=&quot;[
        {
            text: &#039;Learn more&#039;,
            url: &#039;#&#039;
        }
    ]&quot;
/&gt;

&lt;twig:block-video
    class=&quot;my-4&quot;
    size=&quot;small&quot;
    align=&quot;center&quot;
    title=&quot;Lorem ipsum dolor sit amet&quot;
    video=&quot;https://www.youtube.com/watch?v=GOAEIMx39-w&quot;
    content=&quot;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.&quot;
    :buttons=&quot;[
        {
            text: &#039;Learn more&#039;,
            url: &#039;#&#039;
        }
    ]&quot;
/&gt;




---

### Translations

Translations

# Translations

`Derafu\Twig\Extension\TranslationExtension` brings message translation to
Twig templates, powered by [`derafu/translation`](https://www.derafu.dev/docs/core/translation).
It mirrors the structure of Symfony&#039;s own Twig translation integration (a
`trans` filter, a `t()` function, a `{% trans %}` block tag, and a
`{% trans_default_domain %}` tag), but without depending on
`symfony/twig-bridge` — that package&#039;s only hard requirements are
`symfony/translation-contracts` and `twig/twig`, but it still ships 55 files
(~500KB) of Csrf, Form, HttpKernel, Security, Serializer, and Workflow
extensions this library has no use for. `TranslationExtension` depends only
on `derafu/translation` instead.

Like the rest of this ecosystem, it does nothing unless you explicitly wire
a translator — without one, everything still renders, using ICU formatting
directly instead of a translation catalogue.

## Registering It

Pass an instance in the `extensions` option, the same way you would add
any other Twig extension to `TwigCreator`/`TwigService`:

```php
use Derafu\Twig\Extension\TranslationExtension;
use Derafu\Twig\Service\TwigService;
use Derafu\Translation\TranslatorFactory;

$translator = TranslatorFactory::create(
    defaultLocale: &#039;es&#039;,
    fallbackLocales: [&#039;es&#039;, &#039;en&#039;],
);

$twigService = new TwigService([
    &#039;extra&#039; =&gt; false, // Keeps this example scoped to just this extension.
    &#039;extensions&#039; =&gt; [
        new TranslationExtension($translator, &#039;app+intl-icu&#039;),
    ],
]);
```

The constructor takes three optional arguments:

```php
final class TranslationExtension extends AbstractExtension
{
    public function __construct(
        ?TranslatorInterface $translator = null,
        ?string $domain = null,
        ?string $locale = null,
    ) {
    }
}
```

`$domain`/`$locale` are fallbacks used only when a call site doesn&#039;t specify
its own — they are never assumed or required. Prefer
`{% trans_default_domain %}` (below) to set a domain per template instead of
relying on this constructor argument, since a single `TranslationExtension`
instance is shared by every template rendered through that Twig
`Environment`.

## The `trans` Filter

```twig
{{ &#039;Hello {name}!&#039;|trans({&#039;name&#039;: user.name}) }}
{{ &#039;Hello {name}!&#039;|trans({&#039;name&#039;: user.name}, &#039;app+intl-icu&#039;) }}
{{ &#039;Hello {name}!&#039;|trans({&#039;name&#039;: user.name}, &#039;app+intl-icu&#039;, &#039;fr&#039;) }}
```

Placeholders use ICU syntax (`{name}`), the same convention used everywhere
else in this ecosystem — never `%name%`. The domain should generally carry
the `+intl-icu` suffix (see
[the ICU MessageFormat guide](https://www.derafu.dev/docs/core/translation)); without it, the
underlying translator falls back to plain `strtr()`, which does not
understand `{name}`-style placeholders.

## The `t()` Function

```twig
{% set message = t(&#039;Hello {name}!&#039;, {&#039;name&#039;: user.name}, &#039;app+intl-icu&#039;) %}
```

Builds a `Derafu\Translation\TranslatableMessage` without translating it —
useful to hand a translatable value to PHP code that will translate it
later, possibly with a different locale.

**Printing it directly does not use the configured translator.** `{{ t(...) }}`
casts the object to a string via `__toString()`, which always applies ICU
formatting only — the same fallback used when no translator is configured
at all. To translate a message immediately within a template, use the
`trans` filter or the `{% trans %}` tag below, both of which call the
translator directly; reach for `t()` only when the translation needs to
happen somewhere else, later.

## The `{% trans %}` Block Tag

```twig
{% trans %}Save{% endtrans %}
{% trans with {&#039;name&#039;: user.name} %}Hello {name}!{% endtrans %}
{% trans with {&#039;name&#039;: user.name} from &#039;app+intl-icu&#039; %}Hello {name}!{% endtrans %}
{% trans with {&#039;name&#039;: user.name} from &#039;app+intl-icu&#039; into &#039;fr&#039; %}Hello {name}!{% endtrans %}
```

Unlike Symfony&#039;s own `trans` tag, parameters are **never** auto-detected by
scanning the message text for `%name%` occurrences — that convention
predates ICU and is incompatible with it. Here, parameters always go
through `with`, explicitly, and placeholders in the text are ICU (`{name}`),
matching the `trans` filter.

The message must be plain text. Interpolating a Twig expression
(`{% trans %}Hello {{ user.name }}!{% endtrans %}`) raises a `SyntaxError`
at compile time: the translation id would depend on a runtime value and
would never match an entry in the translation catalogue.

&gt; **Never use the bare `#` ICU plural shorthand — in any of `trans`, `t()`,
&gt; or `{% trans %}`.** A literal `{#` in raw template text is always lexed
&gt; by Twig itself as the start of a `{# comment #}`, before this extension&#039;s
&gt; code ever runs, breaking the whole template outright (or, worse, silently
&gt; consuming content up to the next stray `#}`). That alone rules out the
&gt; `{% trans %}` tag for any ICU plural/select message using `#`
&gt; (`{count, plural, one {# item} other {# items}}`), since its body is raw
&gt; template text.
&gt;
&gt; A `{#` inside a Twig *string literal* — e.g. the message argument to the
&gt; `trans` filter — is lexed literally and is safe **when only Twig&#039;s own
&gt; extensions are registered**. That safety is not guaranteed once
&gt; `symfony/ux-twig-component` is also registered (the runtime behind
&gt; `&lt;twig:...&gt;` component tags): a real case surfaced where the exact same
&gt; `{#`, inside a `trans` filter&#039;s string argument, in a template that both
&gt; used a `&lt;twig:...&gt;` component and `{% extends %}`, broke the template
&gt; with an unrelated-looking error
&gt; (`A template that extends another one cannot include content outside
&gt; Twig blocks`) — nothing in the message pointed at `{#` as the cause. The
&gt; exact mechanism inside `ux-twig-component` was not identified (it is a
&gt; third-party dependency), but bisection on the real template confirmed
&gt; the `{#` substring as the trigger.
&gt;
&gt; **The rule that holds regardless of which extensions are registered:**
&gt; never write the bare `#` shorthand in an ICU plural/select message. This
&gt; applies to `trans`, `t()`, and `{% trans %}` alike.
&gt;
&gt; The fix is **not** to reference the same argument again as `{count}`:
&gt; real ICU (PHP&#039;s `MessageFormatter`, used both by the no-translator
&gt; fallback and, through Symfony, by a real translator) throws
&gt; `U_ARGUMENT_TYPE_MISMATCH` when the same argument name is used both as
&gt; the plural selector and as a literal reference inside a branch — `#`
&gt; exists specifically to avoid that conflict, it is not interchangeable
&gt; with a named reference to the same argument. The safe fix is to pass the
&gt; same value under **two different argument names**: one used only for
&gt; plural selection, one only for display:
&gt;
&gt; ```twig
&gt; {{ &#039;{count, plural, one {{n} item} other {{n} items}}&#039;|trans({&#039;count&#039;: items|length, &#039;n&#039;: items|length}, &#039;app+intl-icu&#039;) }}
&gt; ```

## The `{% trans_default_domain %}` Tag

Sets a default domain for every `trans` filter call and `{% trans %}` block
that follows it in the same template or block, and doesn&#039;t have its own
domain:

```twig
{% trans_default_domain &#039;app+intl-icu&#039; %}

{{ &#039;Save&#039;|trans }}
{% trans %}Cancel{% endtrans %}
{{ &#039;Delete&#039;|trans({}, &#039;admin+intl-icu&#039;) }} {# explicit domain still wins #}
```

The domain doesn&#039;t need to be a literal string — a dynamic expression works
too, evaluated once regardless of how many `trans` calls use it:

```twig
{% trans_default_domain app.locale == &#039;es&#039; ? &#039;es+intl-icu&#039; : &#039;en+intl-icu&#039; %}
```

The scope is the enclosing template or `{% block %}` — a
`trans_default_domain` set inside a block does not leak to the rest of the
template, and the template&#039;s own domain (if any) is restored once the block
ends.

## Fallback Without a Translator

`trans`, `t()`, and `{% trans %}` all work with no `Translator` configured
at all — every one of them falls back to
`TranslatableMessage::__toString()`, which still applies ICU formatting
(interpolating `{name}`-style placeholders correctly) even though nothing
gets translated:

```twig
{# No translator registered: renders &quot;Hello Juan!&quot; verbatim. #}
{{ &#039;Hello {name}!&#039;|trans({&#039;name&#039;: user.name}) }}
```

This is unlike `symfony/twig-bridge`, whose own no-translator fallback uses
a plain `strtr()`-based identity translator that does not understand ICU
placeholders at all.




---

## Markdown Project

Derafu Markdown

# Derafu Markdown




---

### Introduction

PHP Markdown Rendering Library

# PHP Markdown Rendering Library

![GitHub last commit](https://img.shields.io/github/last-commit/derafu/markdown/main)
![CI Workflow](https://github.com/derafu/markdown/actions/workflows/ci.yml/badge.svg?branch=main&amp;event=push)
![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/derafu/markdown)
![GitHub Issues](https://img.shields.io/github/issues-raw/derafu/markdown)
![Total Downloads](https://poser.pugx.org/derafu/markdown/downloads)
![Monthly Downloads](https://poser.pugx.org/derafu/markdown/d/monthly)

**Derafu Markdown** is a PHP library that provides a powerful Markdown rendering engine with support for advanced extensions. It leverages `league/commonmark` and additional features to enhance Markdown processing for documentation, blogs, and dynamic content.

## Features

- 📝 **Full Markdown Support**: Standard CommonMark and GitHub Flavored Markdown (GFM).
- 📚 **Extended Capabilities**: TOC, footnotes, mentions, permalinks, embeds, and more.
- 🎨 **Custom Attributes**: Add CSS classes and IDs to elements.
- 🔗 **External Link Handling**: Open in new tabs, add `nofollow`, etc.
- 🛠 **Highly Configurable**: Fine-tune Markdown behavior with options.
- 📦 **Easy Integration**: Works standalone or within any PHP project.
- 🏷 **MIT Licensed**: Open-source and free to use.

&gt; [!INFO] Admonition Extension
&gt;
&gt; This library includes an admonition extension to process common messages. For example: tip, info and warning.

## Installation

Install via Composer:

```bash
composer require derafu/markdown
```

## Usage

### Basic Rendering

```php
use Derafu\Markdown\Service\MarkdownCreator;
use Derafu\Markdown\Service\MarkdownService;

$markdownService = new MarkdownService(new MarkdownCreator());

echo $markdownService-&gt;render(&#039;example.md&#039;);
```

### Rendering with Layout

```php
$markdownService-&gt;render(&#039;example.md&#039;, [
    &#039;__view_layout&#039; =&gt; &#039;layout.php&#039;,
    &#039;__view_title&#039; =&gt; &#039;My Markdown Page&#039;
]);
```

## Available Extensions

### ✅ **GitHub Flavored Markdown (GFM)**
- Task lists:

```markdown
- [x] Completed
- [ ] Pending
```

- Tables:

```markdown
| Name  | Age |
|-------|-----|
| John  | 25  |
| Alice | 30  |
```

### 📌 **Table of Contents (TOC)**

```markdown
## Section 1
## Section 2
```

### 🔗 **Header Permalinks**

```markdown
### Important Header
```

Generates an anchor link like `#important-header`.

### 📝 **Footnotes**

```markdown
Here is a reference[^1].

[^1]: Footnote text.
```

### 🏷 **Custom Attributes**

```markdown
### Title {.custom-class}
```

### 🔗 **External Links Handling**

```markdown
[Google](https://www.google.com)
```

Adds attributes like `rel=&quot;noopener noreferrer&quot;`.

### 📌 **Mentions &amp; Issues**

```markdown
Hello @user, check issue #123.
```

Links to GitHub profiles and issues.

### 🎥 **Embeds**

```markdown
https://www.youtube.com/watch?v=dQw4w9WgXcQ
```

Automatically embeds videos.

## Advanced Configuration

You can customize the behavior of Markdown processing by passing an options array to `MarkdownCreator`:

```php
$options = [
    &#039;environment&#039; =&gt; [
        &#039;mentions&#039; =&gt; [
            &#039;@&#039; =&gt; [&#039;generator&#039; =&gt; &#039;https://github.com/%s&#039;],
            &#039;#&#039; =&gt; [&#039;generator&#039; =&gt; &#039;https://github.com/derafu/markdown/issues/%d&#039;]
        ]
    ]
];

$creator = new MarkdownCreator($options);
```

## Template Metadata Support

Markdown templates can include metadata in YAML format:

```markdown
---
__view_title: &quot;Markdown Template Guide&quot;
---
```




---

### Guide

Template Usage Guide

# Template Usage Guide

This document is designed to help you maximize the capabilities of Markdown within your application. By default, various extensions are configured to expand Markdown functionalities, allowing you to create dynamic and well-styled content easily.

## 1. Introduction to Markdown

Markdown is a lightweight markup language that allows you to write plain text documents and easily convert them into HTML. Its simple syntax makes it a popular choice for documentation, blogs, and other web applications. Below are some basic Markdown examples:

### Headings

```markdown
# Level 1 Heading

## Level 2 Heading

### Level 3 Heading
```

### Emphasis

```markdown
**Bold Text**

*Italic Text*

~~Strikethrough Text~~
```

### Lists

```markdown
- Unordered list item
- Another item

1. Ordered list item
2. Another item
```

### Links and Images

```markdown
[Link to Google](https://www.google.com)

![Alternative Image Text](https://www.example.com/image.jpg)
```

## 2. Markdown Extensions

You can extend Markdown capabilities with various extensions that enhance your content. Below is a description of the included extensions and how to use them.

### 2.1. GitHub Flavored Markdown (GFM)

GFM is an extended version of Markdown used on GitHub, adding features such as tables, task lists, and syntax-highlighted code blocks. To use GFM, simply write your Markdown content, and the extension will handle the rest.

#### Examples:

**Task Lists**:

```markdown
- [x] Completed task
- [ ] Pending task
```

**Tables**:

```markdown
| Header 1 | Header 2 |
| -------- | -------- |
| Cell 1   | Cell 2   |
```

**Code Blocks**:

```markdown
```php
echo &quot;This is PHP code&quot;;
```

### 2.2. Automatic Table of Contents (TOC)

The Table of Contents (TOC) extension allows you to automatically generate an index based on the document&#039;s headings. To include a TOC, simply place `[TOC]` where you want it to appear.

#### Example:

```markdown
## Section 1
Content of section 1.

## Section 2
Content of section 2.
```

### 2.3. Header Permalinks

Permalinks allow headers to have permanent links that users can copy and share easily. This extension automatically generates permalinks for all headers.

#### Example:

```markdown
### Important Header
```

This will automatically generate the permalink `#content-important-header`.

### 2.4. Footnotes

Footnotes allow you to add references or clarifications without disrupting the text flow. Use the syntax `[^1]` to mark a footnote and define its content at the bottom of the document.

#### Example:

```markdown
Here is a phrase with a footnote[^1].

[^1]: This is the footnote, appearing at the end of the document.
```

### 2.5. Definitions

Definition lists allow you to create glossaries or descriptive lists easily.

#### Example:

```markdown
Term 1
: Definition for term 1.

Term 2
: Definition for term 2.
```

### 2.6. Custom Attributes

This extension allows you to add custom HTML attributes to specific Markdown elements.

#### Example:

```markdown
### Header with Custom ID {.my-css-class}
```

### 2.7. External Links

You can define how external links are handled. You can configure them to open in a new window, add attributes like `nofollow`, and more.

#### Example:

```markdown
[External Link](https://www.google.com)
```

This will generate a link that opens in a new window with additional attributes like `rel=&quot;noopener noreferrer&quot;`.

### 2.8. Mentions

Mentions allow you to link, by default, to GitHub profiles or issues directly from Markdown.

#### Example:

```markdown
Hello @user, please check issue #123.
```

This will generate links to `https://github.com/user` and `https://github.com/derafu/markdown/issues/123`.

### 2.9. Embeds

The embed extension allows you to insert content from sites like YouTube directly into your document.

#### Example:

```markdown
https://www.youtube.com/watch?v=dQw4w9WgXcQ
```

This will embed the YouTube video instead of just displaying the link.

## 3. Advanced Options

Check the `MarkdownCreator` service to see the extensive customization options available for handling Markdown in your application. You can modify aspects such as:

- ID prefixes for permalinks.
- Custom styles for footnotes and tables.
- Advanced settings for mentions and embeds.

### Assigning Options

You must assign options when creating the `MarkdownCreator` service by passing an array with the options you want to modify.

Example:

```php
$options = [
    &#039;environment&#039; =&gt; [
        &#039;mentions&#039; =&gt; [
            &#039;@&#039; =&gt; [
                &#039;prefix&#039; =&gt; &#039;https://github.com/&#039;,
                &#039;pattern&#039; =&gt; &#039;[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}(?!\w)&#039;,
                &#039;generator&#039; =&gt; &#039;https://github.com/%s&#039;,
            ],
            &#039;#&#039; =&gt; [
                &#039;prefix&#039; =&gt; &#039;#&#039;,
                &#039;pattern&#039; =&gt; &#039;\d+&#039;,
                &#039;generator&#039; =&gt; &#039;https://github.com/derafu/markdown/issues/%d&#039;,
            ],
        ],
    ],
];
$creator = new MarkdownCreator($options);
```

## 4. Template Metadata Block

Markdown templates allow the inclusion of a metadata block placed at the beginning of the file. These metadata entries are in `YAML` format, and all defined keys will be passed to the rendered layout and available as extracted variables in it.

Example:

```markdown
---
__view_title: &quot;Markdown Template Usage Guide&quot;
---
```

With this metadata block, the `__view_title` index will be assigned to the layout data and can be used later as a variable within it.

## 5. Conclusion

The Markdown renderer used by this library, along with the preconfigured extensions, provides a powerful Markdown template rendering engine that allows you to create rich and dynamic documents effortlessly. With support for various extensions and advanced settings, you can fully customize the Markdown experience in your application.




---

## Form Project

Derafu Form

# Derafu Form




---

### Introduction

Declarative Forms, Seamless Rendering

# Declarative Forms, Seamless Rendering

![GitHub last commit](https://img.shields.io/github/last-commit/derafu/form/main)
![CI Workflow](https://github.com/derafu/form/actions/workflows/ci.yml/badge.svg?branch=main&amp;event=push)
![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/derafu/form)
![GitHub Issues](https://img.shields.io/github/issues-raw/derafu/form)
![Total Downloads](https://poser.pugx.org/derafu/form/downloads)
![Monthly Downloads](https://poser.pugx.org/derafu/form/d/monthly)

A modern PHP form library that leverages a declarative, schema-based approach to form definition and rendering, compatible with JSON Forms while providing a powerful backend-centric workflow.

&gt; [!NOTE] WIP
&gt;
&gt; Work In Progress.

## Key Features

- **Schema-Based Form Definition**: Define forms using structured arrays that clearly separate data schemas from UI layouts.
- **Backend-First Approach**: Built for PHP developers who want control over form generation with minimum effort.
- **Symfony-Compatible Twig Extensions**: Familiar syntax for Symfony developers, minimizing learning curve.
- **Decoupled Rendering System**: Separate your form logic from its presentation with specialized renderers.
- **JSON Forms Compatibility**: Define once, use anywhere - same schema can be used with frontend JSON Forms library.
- **Automatic Form Generation**: Generate form UI schemas automatically from your data models.
- **Extensible Architecture**: Easily add custom types, layouts and renderers.
- **Translatable, Optionally**: Validation error messages and input action
  labels (show/hide password, copy, etc.) can be translated by wiring an
  optional translator — see [Translations](translations).

## Why Choose This Library?

### Compared to Traditional PHP Form Libraries

Traditional PHP form libraries often tightly couple form definition with HTML generation, making it difficult to separate concerns. This library takes a different approach:

1. **Declarative Rather Than Imperative**: Define _what_ your form should be rather than _how_ it should be built, step by step.
2. **Clear Separation of Concerns**: Schema (data structure) is separate from UI schema (presentation) and data (values).
3. **Powerful Layouts Without HTML Knowledge**: Create complex, multi-column layouts without writing HTML.

### Compared to Frontend-Only Solutions

Unlike frontend-only form builders, this library:

1. **Keeps Validation Logic Server-Side**: Where it belongs for security-critical applications.
2. **Provides PHP-Native Form Definition**: No need to write JavaScript to define your forms.
3. **Works With or Without JavaScript**: Generate the same forms for both traditional and SPA applications.

## Installation

Install the library using Composer:

```shell
composer require derafu/form
```

## Basic Usage

### Creating a Simple Form

```php
// Define the form structure.
$definition = [
    &#039;schema&#039; =&gt; [
        &#039;type&#039; =&gt; &#039;object&#039;,
        &#039;properties&#039; =&gt; [
            &#039;name&#039; =&gt; [
                &#039;type&#039; =&gt; &#039;string&#039;,
                &#039;title&#039; =&gt; &#039;Full Name&#039;,
            ],
            &#039;email&#039; =&gt; [
                &#039;type&#039; =&gt; &#039;string&#039;,
                &#039;format&#039; =&gt; &#039;email&#039;,
                &#039;title&#039; =&gt; &#039;Email Address&#039;,
            ],
        ],
        &#039;required&#039; =&gt; [&#039;name&#039;, &#039;email&#039;],
    ],
    &#039;uischema&#039; =&gt; [
        &#039;type&#039; =&gt; &#039;VerticalLayout&#039;,
        &#039;elements&#039; =&gt; [
            [
                &#039;type&#039; =&gt; &#039;Control&#039;,
                &#039;scope&#039; =&gt; &#039;#/properties/name&#039;,
            ],
            [
                &#039;type&#039; =&gt; &#039;Control&#039;,
                &#039;scope&#039; =&gt; &#039;#/properties/email&#039;,
            ],
        ],
    ],
    &#039;data&#039; =&gt; [
        &#039;name&#039; =&gt; &#039;&#039;,
        &#039;email&#039; =&gt; &#039;&#039;,
    ],
];

// Create the form.
$form = $formFactory-&gt;create($definition);
```

### Rendering with Twig

```twig
{# Simple rendering of the entire form. #}
{{ form(form) }}

{# Or with more control over individual components. #}
{{ form_start(form) }}
    {{ form_row(form.fields.name) }}
    {{ form_row(form.fields.email) }}
    &lt;button type=&quot;submit&quot; class=&quot;btn btn-primary&quot;&gt;Submit&lt;/button&gt;
{{ form_end(form) }}
```

## Automatic Schema Generation

The library can automatically generate a schema from your data:

```php
$definition = [
    &#039;data&#039; =&gt; [
        &#039;name&#039; =&gt; &#039;John Doe&#039;,
        &#039;email&#039; =&gt; &#039;john.doe@example.com&#039;,
        &#039;age&#039; =&gt; 34,
        &#039;birthdate&#039; =&gt; &quot;1990-01-01&quot;,
    ],
];

// Schema and UI schema will be automatically generated.
$form = $formFactory-&gt;create($definition);
```

## Integration with JSON Forms

This library&#039;s form definitions are compatible with the [JSON Forms](https://jsonforms.io) JavaScript library, allowing you to use the same definitions for both server-side and client-side rendering:

```html
&lt;div id=&quot;json-form-container&quot;&gt;&lt;/div&gt;
```

```javascript
// https://raw.githubusercontent.com/derafu/form/refs/heads/main/assets/js/jsonforms-viewer.js
import &#039;./jsonforms-viewer.js&#039;;

// Render the form and save the reference.
const formDefinition = {{ jsonFormsDefinition | json_encode | raw }};
const formInstance = window.renderJsonForm(&#039;json-form-container&#039;, formDefinition);
// You can now use `formInstance` to get the form data or its errors.
```

### Development Requirements

To render forms using JSON Forms you must install:

```shell
npm install @jsonforms/core @jsonforms/react @jsonforms/material-renderers \
    react react-dom @mui/material @emotion/react @emotion/styled
```

## Form Layouts

The UI schema supports various layout types by default:

- **VerticalLayout**: Fields stacked vertically.
- **HorizontalLayout**: Fields arranged horizontally.
- **Group**: Logical grouping of fields, often with a label.
- **Categorization**: Tab-based layouts for complex forms.

Example:

```php
$uischema = [
    &#039;type&#039; =&gt; &#039;VerticalLayout&#039;,
    &#039;elements&#039; =&gt; [
        [
            &#039;type&#039; =&gt; &#039;Group&#039;,
            &#039;label&#039; =&gt; &#039;Personal Information&#039;,
            &#039;elements&#039; =&gt; [
                [&#039;type&#039; =&gt; &#039;Control&#039;, &#039;scope&#039; =&gt; &#039;#/properties/name&#039;],
                [&#039;type&#039; =&gt; &#039;Control&#039;, &#039;scope&#039; =&gt; &#039;#/properties/email&#039;],
            ],
        ],
        [
            &#039;type&#039; =&gt; &#039;Group&#039;,
            &#039;label&#039; =&gt; &#039;Address&#039;,
            &#039;elements&#039; =&gt; [
                [&#039;type&#039; =&gt; &#039;Control&#039;, &#039;scope&#039; =&gt; &#039;#/properties/street&#039;],
                [&#039;type&#039; =&gt; &#039;Control&#039;, &#039;scope&#039; =&gt; &#039;#/properties/city&#039;],
            ],
        ],
    ],
];
```




---

### Translations

Translations

# Translations

`derafu/form` has two independent, optional translation surfaces. Both are
powered by [`derafu/translation`](https://www.derafu.dev/docs/core/translation), and both do
nothing unless you explicitly wire a translator — without one, everything
renders in English exactly as before.

## 1. Validation Error Messages

`FormDataProcessor` runs field values through
[`derafu/data-processor`](https://www.derafu.dev/docs/data/data-processor) rules. When a rule
fails, it throws a `Derafu\DataProcessor\Exception\ValidationException` (or
one of the other translatable exceptions from that package — see
[its Translations guide](https://www.derafu.dev/docs/data/data-processor)). `FormDataProcessor`
accepts an optional translator and locale; when given, it translates each
error before adding it to the result:

```php
final class FormDataProcessor implements FormDataProcessorInterface
{
    public function __construct(
        FormRulesResolverInterface $resolver,
        ProcessorInterface $processor,
        UiSchemaRuleEvaluatorInterface $evaluator = new UiSchemaRuleEvaluator(),
        ?TranslatorInterface $translator = null,
        ?string $locale = null,
    ) {
    }
}
```

Without a translator, `getErrors()` returns the untranslated (English)
messages — the exact same behavior as before this parameter existed.

## 2. Input Action Labels

Controls rendered with the `Control` UI schema type can declare
`options.actions` — input-group buttons like &quot;show/hide password&quot;,
&quot;generate a random password&quot;, or &quot;copy the field&#039;s value&quot;. Their labels
(and the default copy confirmation message) are resolved by
`InputActionResolver`, which also accepts an optional translator:

```php
final class InputActionResolver
{
    public function __construct(
        ?TranslatorInterface $translator = null,
        ?string $locale = null,
    ) {
    }
}
```

An explicit `label` or `message` given directly in `options.actions` is
always used as-is and never translated — only the three built-in defaults
are:

```php
[
    &#039;type&#039; =&gt; &#039;Control&#039;,
    &#039;scope&#039; =&gt; &#039;#/properties/password&#039;,
    &#039;options&#039; =&gt; [
        &#039;actions&#039; =&gt; [&#039;toggle-password&#039;],
    ],
]
```

`InputActionResolver` isn&#039;t wired into the rendering pipeline directly —
`ElementRendererProvider` (used internally by `FormRendererFactory`)
forwards it to `ControlRenderer`. To use a translator-aware resolver, pass
your own `ElementRendererProvider` via the `element_renderers` factory
option (see [Activating It](#activating-it-plain-php) below).

## What Ships

`derafu/form` includes:

- `resources/translations/form+intl-icu.es.php` — a Spanish translation
  for the four strings `InputActionResolver` can produce (validation error
  messages come from `derafu/data-processor`&#039;s own translations instead —
  `derafu/form` doesn&#039;t duplicate them).
- `Derafu\Form\Translation\FormTranslationResourceProvider` — a
  `TranslationResourceProviderInterface` implementation pointing at that
  directory.

Neither does anything on its own — `derafu/form` is a library, it doesn&#039;t
build or own a `Translator`.

## Activating It (Plain PHP)

```php
use Derafu\DataProcessor\ProcessorFactory;
use Derafu\DataProcessor\Translation\DataProcessorTranslationResourceProvider;
use Derafu\Form\Processor\FormDataProcessor;
use Derafu\Form\Processor\FormRulesResolver;
use Derafu\Form\Translation\FormTranslationResourceProvider;
use Derafu\Translation\TranslatorFactory;

$translator = TranslatorFactory::create(
    defaultLocale: &#039;es&#039;,
    fallbackLocales: [&#039;es&#039;, &#039;en&#039;],
    resourceProviders: [
        new DataProcessorTranslationResourceProvider(), // Validation error messages.
        new FormTranslationResourceProvider(),           // Input action labels.
    ],
);

$processor = new FormDataProcessor(
    new FormRulesResolver(),
    ProcessorFactory::create(),
    translator: $translator,
    locale: &#039;es&#039;,
);
```

For the action labels, build the renderer with a translator-aware
`ElementRendererProvider`:

```php
use Derafu\Form\Factory\FormRendererFactory;
use Derafu\Form\Renderer\ElementRendererProvider;
use Derafu\Form\Renderer\Support\InputActionResolver;

$formRenderer = FormRendererFactory::create([
    &#039;element_renderers&#039; =&gt; new ElementRendererProvider(
        actionResolver: new InputActionResolver($translator, &#039;es&#039;),
    ),
]);
```

## Activating It (Dependency Injection)

If your application uses `symfony/dependency-injection`, import all three
recipe files. `form-services.yaml` already registers `InputActionResolver`
and `ElementRendererProvider` as autowired services, and wires
`FormRendererInterface`&#039;s `element_renderers` option to that
`ElementRendererProvider` — nothing extra to configure beyond registering
your own `Translator`:

```yaml
imports:
    - { resource: &#039;../vendor/derafu/translation/resources/config/translation-services.yaml&#039; }
    - { resource: &#039;../vendor/derafu/data-processor/resources/config/data-processor-services.yaml&#039; }
    - { resource: &#039;../vendor/derafu/form/resources/config/form-services.yaml&#039; }

services:
    Symfony\Component\Translation\Translator:
        factory: [&#039;Derafu\Translation\TranslatorFactory&#039;, &#039;create&#039;]
        arguments:
            $defaultLocale: &#039;es&#039;
            $fallbackLocales: [&#039;es&#039;, &#039;en&#039;]
            $resourceProviders: !tagged_iterator derafu_translation.resource_provider
```

Both `DataProcessorTranslationResourceProvider` and
`FormTranslationResourceProvider` are already tagged
`derafu_translation.resource_provider` in their respective recipe files, so
they&#039;re picked up automatically. `FormDataProcessor` and
`InputActionResolver` both autowire `?TranslatorInterface`, so they resolve
to the `Translator` above via the alias `translation-services.yaml`
provides — no explicit argument wiring needed for either of them.




---

### Examples

Examples of how to use the form component.

# Form Examples




---

#### From Data

The simplest form, created from data only.






---

#### Control

The default controls provided by the form library.






---

#### User Registration

Form to create a new user account.






---

#### User Login

A user login form with username, password and 2FA options.






---

#### User Profile with Cards

User profile form with grouped sections in cards.






---

#### User Profile with Tabs

User profile form with tabs and cards.






---

#### Setup Wizard

Multi-step wizard for account setup.






---

#### Invoice Issuance

Form for creating invoices with basic fields and line items.






---

#### Advanced Invoice Search

Complex search form for filtering invoices with conditional fields.






---

#### E-Invoicing Configuration

Comprehensive configuration form for electronic invoicing.






---

#### Contact Form

Simple contact form with default values and validation.






---

#### User Registration

Complex user registration form with nested fields and validation.






---

#### Collection

Collections and arrays in forms.






---

#### Editors

Different editors using controls.






---

#### Product Ecommerce

Product creation form with data processing rules.






---

#### Rules

JSON Forms rules in UI Schema.






---

#### Cascade

Select options in a cascade.






---

## Contact Form

Contact Form

# Contact Form

![GitHub last commit](https://img.shields.io/github/last-commit/derafu/contact-form/main)
![CI Workflow](https://github.com/derafu/contact-form/actions/workflows/ci.yml/badge.svg?branch=main&amp;event=push)
![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/derafu/contact-form)
![GitHub Issues](https://img.shields.io/github/issues-raw/derafu/contact-form)
![Total Downloads](https://poser.pugx.org/derafu/contact-form/downloads)
![Monthly Downloads](https://poser.pugx.org/derafu/contact-form/d/monthly)

## Requirements

The`derafu/form` and `derafu/data-processor` packages installed and configured in `services.yaml`.

Check the configuration in the [website skeleton](https://github.com/derafu/website/blob/main/config/services.yaml).

## Quick Start

Install the package:

```shell
composer require derafu/contact-form
```

Import the routes to your `routes.yaml`:

```yaml
imports:
    - { resource: &#039;../vendor/derafu/contact-form/resources/config/contact-form-routes.yaml&#039; }
```

Import the services to your `services.yaml`:

```yaml
imports:
    - { resource: &#039;../vendor/derafu/contact-form/resources/config/contact-form-services.yaml&#039; }
```

Copy the `templates/contact/index.html.twig` and `templates/contact/success.html.twig` to your project.

If you want to use the default templates (and not copy them), add the templates path to your service configuration:

```yaml
Derafu\Renderer\Contract\RendererInterface:
    factory: [&#039;Derafu\Renderer\Factory\RendererFactory&#039;, &#039;create&#039;]
    lazy: true
    arguments:
        $options:
            paths:
                - &#039;%kernel.project_dir%/vendor/derafu/contact-form/resources/templates&#039;
```

Configure the environment variables:

```yaml
# Source of the form for identifying it in the webhook.
# Useful for multiple forms in the same webhook.
FORM_CONTACT_SOURCE=derafu.dev

# URL and secret key for processing the contact form.
# Get the URL from the webhook service you are using.
# The secret key will be used to sign the payload using HMAC.
FORM_CONTACT_WEBHOOK_URL=
FORM_CONTACT_WEBHOOK_SECRET_KEY=

# Site and secret key for protect forms with captcha.
# Get the keys from https://www.hcaptcha.com/
CAPTCHA_SITE_KEY=
CAPTCHA_SECRET_KEY=
```




---

## JS Project

Derafu JS

# Derafu JS




---

### Introduction

Why use a JS framework if we can do all from scratch?

# Why use a JS framework if we can do all from scratch?

![GitHub last commit](https://img.shields.io/github/last-commit/derafu/js/main)
![CI Workflow](https://github.com/derafu/js/actions/workflows/ci.yml/badge.svg?branch=main&amp;event=push)
![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/derafu/js)
![GitHub Issues](https://img.shields.io/github/issues-raw/derafu/js)

This library provides a collection of utility functions for common web development tasks, organized into modular components for better maintainability and flexibility.

## Structure

The library is organized into the following modules:

### Main Modules

- `src/derafu.js` - Main module (exports all functions under the `derafu` namespace).

### Core Modules

- `src/tabs.js` - Tab management functions.
- `src/ui.js` - User interface functions (popups, tables, notifications, copy/paste, etc.).
- `src/utils.js` - Basic utility functions (password generation, cookies, language detection, number formatting, etc.).

### Validation Modules

- `src/validation/l10n-cl.js` - Chilean validation functions (RUT validation, etc.).

### Form Modules

- `src/form/fields.js` - Form field management functions.
- `src/form/tables.js` - Dynamic table functions.
- `src/form/validation.js` - Form validation functions.

### Compatibility Modules

- `src/__.js` - Main compatibility layer (exports all functions under the `__` namespace).
- `src/form.js` - Form compatibility layer (exports all form functions under the `Form` namespace).

## Quick Start

Import using the CDN of the module you need, for example UI module:

```html
&lt;script src=&quot;https://cdn.jsdelivr.net/npm/derafu-js@0.1.0/src/ui.js&quot;&gt;&lt;/script&gt;
```

Then use the function you need, for example notify function:

```javascript
UI.notify(&#039;Hello World!&#039;);
```

**Note**: Remember to load the dependencies first, in this example notyf is used for notifications.

## Usage

### Browser Usage

Load the modules in the correct order:

```html
&lt;!-- Load individual modules --&gt;
&lt;script src=&quot;src/utils.js&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;src/ui.js&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;src/validation/l10n-cl.js&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;src/tabs.js&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;src/form/validation.js&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;src/form/fields.js&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;src/form/tables.js&quot;&gt;&lt;/script&gt;

&lt;!-- Load compatibility layers --&gt;
&lt;script src=&quot;src/__.js&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;src/form.js&quot;&gt;&lt;/script&gt;
```

Then use the functions:

```javascript
// Using the compatibility layer (recommended for existing code).
__.generatePassword(12);
__.notify(&#039;Hello World!&#039;);
Form.check(&#039;myForm&#039;);

// Using individual modules (recommended for new code).
Utils.generatePassword(12);
UI.notify(&#039;Hello World!&#039;);
FormValidation.check(&#039;myForm&#039;);
```

### Node.js Usage

```javascript
const Utils = require(&#039;./src/utils.js&#039;);
const UI = require(&#039;./src/ui.js&#039;);

Utils.generatePassword(12);
UI.notify(&#039;Hello World!&#039;);
```

## Validation and Testing

Run the validation and test suite:

```bash
npm install
npm run format:check
npm run lint:check
npm run test
```




---

### Features

Features of the library.

# Features

## Utility Functions

- Password generation with cryptographic randomness.
- Cookie management.
- User language detection.
- Number formatting with locale support.
- Empty value checking.
- Integer/float validation.
- Object key extraction.
- String selector with object property concatenation.

## UI Functions

- Popup window management.
- Dynamic table creation.
- Notifications (with Notyf/Bootbox support).
- Clipboard operations (copy/paste).
- Social sharing (WhatsApp, etc.).
- Loading dialogs.
- Alert and confirmation dialogs.
- Smooth scrolling.
- URL deeplink management.
- Print functionality.

## Validation Functions

- Chilean RUT validation and formatting.
- Form field validation with multiple check types.
- Email validation (single and multiple).
- Date format validation.
- Phone number validation.
- Integer/real number validation.

## Form Functions

- Form submission via POST.
- Password field visibility toggle.
- Field expansion for long text.
- Dynamic form field initialization.
- Checkbox group management.
- Select option management.
- Dynamic table row management.

## Tab Functions

- Tab initialization and management.
- URL-based tab navigation.
- Magic link generation.
- Modal integration.
- Form element integration.




---

### Dependencies

Minimal dependencies for the library.

# Dependencies

The library is written in **pure vanilla JavaScript** but is designed to integrate seamlessly with popular external libraries when available. The code gracefully handles the absence of these dependencies.

&gt; [!CHECK] Why Vanilla JS?
&gt;
&gt; See &lt;http://vanilla-js.com&gt;

The library has minimal dependencies and works with:

- Modern browsers (ES6+).
- jQuery (for some form functions).
- Bootstrap (for UI components).
- Bootbox (for dialogs).
- Notyf (for notifications, optional)

## Useful Dependencies

### Bootstrap

Used for tab management and modal functionality:

- Tab initialization and navigation (`src/tabs.js`).
- Modal instance management.
- Bootstrap classes and data attributes.

### jQuery

Used for enhanced form field functionality:

- Select2 integration for improved select fields.
- Datepicker integration for date inputs.
- Dynamic field initialization.

### Notyf

Enhanced notifications (falls back to basic alerts):

- Toast notifications with icons.
- Configurable duration and positioning.
- Multiple notification types.

### Bootbox

Enhanced dialog boxes:

- Confirmation dialogs.
- Prompt dialogs for field editing.
- Customizable dialog options.

## Graceful Degradation

The library includes conditional checks to ensure functionality even when external dependencies are not available:

```javascript
// Example: jQuery availability check.
if (typeof window.jQuery !== &#039;function&#039;) {
    console.error(&#039;window or jQuery is not available.&#039;);
    return;
}

// Example: Bootstrap availability check.
if (typeof bootstrap !== &#039;undefined&#039;) {
    new bootstrap.Tab(element).show();
}
```

## Installation Recommendations

For full functionality, include these dependencies in your project:

```html
&lt;!-- Bootstrap CSS and JS --&gt;
&lt;link href=&quot;https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css&quot; rel=&quot;stylesheet&quot; /&gt;
&lt;script src=&quot;https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.bundle.min.js&quot;&gt;&lt;/script&gt;

&lt;!-- jQuery (for enhanced form features) --&gt;
&lt;script src=&quot;https://code.jquery.com/jquery-3.5.1.min.js&quot;&gt;&lt;/script&gt;

&lt;!-- Font Awesome (for icons in the UI) --&gt;
&lt;link href=&quot;https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.0/css/all.min.css&quot; rel=&quot;stylesheet&quot; /&gt;

&lt;!-- Optional: Bootbox for dialogs --&gt;
&lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/bootbox.js/6.0.4/bootbox.all.min.js&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/bootbox.js/6.0.4/bootbox.locales.min.js&quot;&gt;&lt;/script&gt;

&lt;!-- Optional: Notyf for notifications --&gt;
&lt;link href=&quot;https://cdn.jsdelivr.net/npm/notyf@3.10.0/notyf.min.css&quot; rel=&quot;stylesheet&quot; /&gt;
&lt;script src=&quot;https://cdn.jsdelivr.net/npm/notyf@3.10.0/notyf.min.js&quot;&gt;&lt;/script&gt;
```




---

## HTML Project

UX Components for HTML in PHP

# UX Components for HTML in PHP

![GitHub last commit](https://img.shields.io/github/last-commit/derafu/html/main)
![CI Workflow](https://github.com/derafu/html/actions/workflows/ci.yml/badge.svg?branch=main&amp;event=push)
![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/derafu/html)
![GitHub Issues](https://img.shields.io/github/issues-raw/derafu/html)
![Total Downloads](https://poser.pugx.org/derafu/html/downloads)
![Monthly Downloads](https://poser.pugx.org/derafu/html/d/monthly)

A few, legacy, HTML components rendered only with PHP.

You want modern components? See [UX Components for Twig](https://www.derafu.dev/docs/ui/twig).

&gt; [!NOTE] WIP
&gt;
&gt; Work In Progress.

## Table

...

## Form

...




---

## Chart Project

PHP Chart Library

# PHP Chart Library

![GitHub last commit](https://img.shields.io/github/last-commit/derafu/chart/main)
![CI Workflow](https://github.com/derafu/chart/actions/workflows/ci.yml/badge.svg?branch=main&amp;event=push)
![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/derafu/chart)
![GitHub Issues](https://img.shields.io/github/issues-raw/derafu/chart)
![Total Downloads](https://poser.pugx.org/derafu/chart/downloads)
![Monthly Downloads](https://poser.pugx.org/derafu/chart/d/monthly)

Derafu Chart is a PHP library for creating diverse charts. The library is designed to generate charts entirely in PHP using GD or other renderers. It supports a variety of chart types and is easy to extend.

## Features

- 📊 Multiple chart types: Bar, Line, Pie, Radar, Waterfall, and more.
- ⚙️ Fully customizable options.
- 📦 Lightweight and easy to integrate.
- 🖼️ Output in PNG and flexible rendering options.
- 💡 Extensible via custom renderers.
- 🧩 Encodable to JSON for interoperability.
- 🏷️ Open-source under the MIT license.

## Installation

You can install the library using Composer:

```bash
composer require derafu/chart
```

## Usage

You can choose the style that best suits your coding preferences or project requirements:

- Creating a Chart with the Factory.
- Creating a Chart with Method Chaining.

Both approaches allow flexibility depending on whether you prefer array-based configuration or object-oriented method chaining.

### Creating a Chart with the Factory

Here’s how to create a simple bar chart using the `ChartFactory`:

```php
use Derafu\Chart\ChartFactory;
use Derafu\Chart\Enum\ChartType;

$chart = (new ChartFactory())-&gt;renderFromArray([
    &#039;type&#039; =&gt; ChartType::BAR,
    &#039;title&#039; =&gt; &#039;Ventas&#039;,
    &#039;label_x&#039; =&gt; &#039;Mes&#039;,
    &#039;label_y&#039; =&gt; &#039;Ventas&#039;,
    &#039;datasets&#039; =&gt; [
        [
            &#039;points&#039; =&gt; [
                &#039;Enero&#039; =&gt; 10000,
                &#039;Febrero&#039; =&gt; 12000,
                &#039;Marzo&#039; =&gt; 1200,
                &#039;Abril&#039; =&gt; 1350,
                &#039;Mayo&#039; =&gt; 5000,
                &#039;Junio&#039; =&gt; 5000,
                &#039;Julio&#039; =&gt; 5000,
                &#039;Agosto&#039; =&gt; 5000,
                &#039;Septiembre&#039; =&gt; 15000,
            ],
        ],
    ],
]);

file_put_contents(&#039;chart.png&#039;, $chart);
```

### Creating a Chart with Method Chaining

Here’s how to create the same bar chart using method chaining:

```php
use Derafu\Chart\Chart;
use Derafu\Chart\Dataset;
use Derafu\Chart\Enum\ChartType;

$chart = (new Chart(ChartType::BAR))
    -&gt;setTitle(&#039;Ventas&#039;)
    -&gt;setLabelX(&#039;Mes&#039;)
    -&gt;setLabelY(&#039;Ventas&#039;)
    -&gt;addDataset(
        (new Dataset())
            -&gt;setLabel(&#039;2024&#039;)
            -&gt;setColor(&#039;blue&#039;)
            -&gt;addPoints([
                &#039;Enero&#039; =&gt; 10000,
                &#039;Febrero&#039; =&gt; 12000,
                &#039;Marzo&#039; =&gt; 1200,
                &#039;Abril&#039; =&gt; 1350,
                &#039;Mayo&#039; =&gt; 5000,
                &#039;Junio&#039; =&gt; 5000,
                &#039;Julio&#039; =&gt; 5000,
                &#039;Agosto&#039; =&gt; 5000,
                &#039;Septiembre&#039; =&gt; 15000,
            ])
    );

file_put_contents(&#039;chart.png&#039;, $chart-&gt;render());
```

## Chart Types

Here is an overview of the chart types supported, with examples.

### 1. Area Chart

Displays continuous data trends over time or categories. Ideal for showing volume and trends.

![Area Chart](https://www.derafu.dev/img/content/docs/ui/chart/chart-area.png)

### 2. Bar Chart

Shows discrete data comparisons using vertical bars. Perfect for categorical comparisons.

![Bar Chart](https://www.derafu.dev/img/content/docs/ui/chart/chart-bar.png)

### 3. Bubble Chart

Highlights relationships and distributions. Bubble size represents a third dimension.

![Bubble Chart](https://www.derafu.dev/img/content/docs/ui/chart/chart-bubble.png)

### 4. Horizontal Bar Chart

Provides an alternative for displaying bar data, especially useful when labels are long.

![Horizontal Bar Chart](https://www.derafu.dev/img/content/docs/ui/chart/chart-horizontal_bar.png)

**Text Output Example**:

```plaintext
Chart name: horizontal_bar

                                     Ventas

Enero      │ ██████████████████████████ 10.000
Febrero    │ ███████████████████████████████ 12.000
Marzo      │ ███ 1.200
Abril      │ ███ 1.350
Mayo       │ █████████████ 5.000
Junio      │ █████████████ 5.000
Julio      │ █████████████ 5.000
Agosto     │ █████████████ 5.000
Septiembre │ ██████████████████████████████████████ 15.000
           └────────────────────────────────────────────────────
           0         4K        8K        12K       16K       20K
```

### 5. Pie Chart

Represents proportions in a dataset. Great for visualizing percentages.

![Pie Chart](https://www.derafu.dev/img/content/docs/ui/chart/chart-pie.png)

### 6. Radar Chart

Compares multiple variables across multiple dimensions. Ideal for skill or performance analysis.

![Radar Chart](https://www.derafu.dev/img/content/docs/ui/chart/chart-radar_1.png)

### 7. Scatter Plot

Shows relationships between two variables using points. Commonly used for distribution analysis.

![Scatter Plot](https://www.derafu.dev/img/content/docs/ui/chart/chart-scatter.png)

### 8. Waterfall Chart

Visualizes cumulative changes in value over a sequence. Useful for profit and loss analysis.

![Waterfall Chart](https://www.derafu.dev/img/content/docs/ui/chart/chart-waterfall.png)

## Roadmap

- 🔍 Export charts to JSON (e.g., Chart.js, regression libraries).
- 📈 Support trendlines and regression models.
- 🔄 Add SVG rendering support.
- 🌐 Add more internationalization options.




---

## Vite Plugin D2 Project

A Vite plugin to convert D2 diagrams to Images

# A Vite plugin to convert D2 diagrams to Images

A Vite plugin that converts [D2](https://d2lang.com/) diagram files to SVG during the build process.

## Installation

```bash
npm install --save-dev vite-plugin-d2
```

Make sure you have D2 installed globally:

```bash
npm install -g d2
```

Or follow the official installation instructions at: [https://d2lang.com/tour/install/](https://d2lang.com/tour/install/)

## Usage

Add the plugin to your `vite.config.js`:

```js
import vitePluginD2 from &#039;vite-plugin-d2&#039;;

export default {
  plugins: [
    vitePluginD2({
      // Options (all optional).
      theme: 0,              // D2 theme (number).
      layout: &#039;dagre&#039;,       // Layout engine.
      sketch: false,         // Enable sketch mode.
      dark: false,           // Enable dark mode.
      pad: 10,               // Padding.
      diagramsDir: &#039;./assets/diagrams&#039;,  // Input directory for D2 files.
      outputDir: null,       // Custom output dir (uses Vite&#039;s outDir if null).
      outputFormat: &#039;svg&#039;,   // Output format (currently only &#039;svg&#039;).
      verbose: false         // Enable verbose output logging.
    })
  ]
};
```

## How it works

The plugin searches for `.d2` files in the specified directory (`diagramsDir`, defaults to `./assets/diagrams`). During the build process, this plugin will:

1. Find all `.d2` files in the specified directory.
2. Convert them to SVG using the D2 CLI.
3. Place the generated SVG files in the output directory.

For example, if you have:

- `./assets/diagrams/architecture.d2`

The plugin will generate:

- `./dist/architecture.svg` (or in your configured output directory).

## Output Directory Configuration

By default, the plugin will output the SVG files to Vite&#039;s configured build output directory (`build.outDir`, which defaults to `dist`).

You can customize the output location by setting the `outputDir` option. The relative structure of files within the `diagramsDir` will be preserved in the output.

## Example D2 File

```
# Example D2 diagram.
shape: sequence_diagram
actor User
app: Application
db: Database

User -&gt; app: Request data
app -&gt; db: Query
db -&gt; app: Results
app -&gt; User: Display data
```

## Options

| Option       | Type         | Default             | Description                                                |
|--------------|--------------|---------------------|------------------------------------------------------------|
| theme        | number       | 0                   | D2 theme number                                            |
| layout       | string       | &#039;dagre&#039;             | Layout engine (e.g., &#039;dagre&#039;, &#039;elk&#039;)                       |
| sketch       | boolean      | false               | Enable sketch/hand-drawn mode                              |
| dark         | boolean      | false               | Enable dark mode                                           |
| pad          | number       | 10                  | Padding around the diagram                                 |
| diagramsDir  | string       | &#039;./assets/diagrams&#039; | Directory containing D2 files                              |
| outputDir    | string\|null | null                | Custom output directory (uses Vite&#039;s build.outDir if null) |
| outputFormat | string       | &#039;svg&#039;               | Output format (currently only &#039;svg&#039; is supported)          |
| verbose      | boolean      | false               | Enable verbose output logging                              |

## Advanced Usage

### Custom Input and Output Directories

You can specify custom directories for your D2 files and the generated SVGs:

```js
vitePluginD2({
  diagramsDir: &#039;./src/diagrams&#039;,  // Custom input directory.
  outputDir: &#039;./public/images&#039;,   // Custom output directory.
  verbose: true                   // Enable detailed logging.
})
```

### Styling Options

Customize the appearance of your diagrams:

```js
vitePluginD2({
  theme: 3,           // Use theme number 3.
  sketch: true,       // Enable sketch mode for hand-drawn appearance.
  dark: true,         // Enable dark mode.
  pad: 20             // Add more padding (20px).
})
```

## Troubleshooting

If you encounter issues:

1. Make sure D2 is properly installed and available in your PATH.
2. Check if the input directory exists and contains .d2 files.
3. Enable verbose mode (`verbose: true`) to see detailed logs.
4. Verify you have the necessary permissions to write to the output directory.




---

## Content Project

Where knowledge becomes product

# Where knowledge becomes product




---

### Introduction

What derafu/content is and how its plugins fit together.

# Where knowledge becomes product

![GitHub last commit](https://img.shields.io/github/last-commit/derafu/content/main)
![CI Workflow](https://github.com/derafu/content/actions/workflows/ci.yml/badge.svg?branch=main&amp;event=push)
![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/derafu/content)
![GitHub Issues](https://img.shields.io/github/issues-raw/derafu/content)
![Total Downloads](https://poser.pugx.org/derafu/content/downloads)
![Monthly Downloads](https://poser.pugx.org/derafu/content/d/monthly)

Your content — documentation, FAQs, courses, blogs — is not just text. It&#039;s a valuable asset that can be packaged, reused, and leveraged like a digital product.

`derafu/content` is a PHP library that turns a directory of Markdown files into a content website: pages, navigation, tags, search and an interface AI agents can call directly. Every plugin adds one content type or one capability on top of the same core.

## Two kinds of configuration

Every plugin has, potentially, two independent configuration surfaces, and it is important not to confuse them:

1. **Plugin configuration**, in the website&#039;s `services.yaml`, under `derafu.content.config.plugins.&lt;name&gt;`. This is set once by whoever runs the website (which directory to scan, which URL to call, whether a feature is enabled at all).
2. **Content frontmatter**, the YAML block at the top of each Markdown file (`---title: ...---`). This is set by whoever writes a specific piece of content (its title, tags, whether it&#039;s a draft, etc.).

Both are documented per plugin, but the frontmatter fields are mostly the same across every content type — see [Content frontmatter](./frontmatter) for the fields shared by all of them. Each plugin&#039;s page only documents what it adds or overrides on top of that shared set.

## Content hierarchy

Content types that support nesting (Docs, Academy, FAQ, Pages) build their hierarchy from the filesystem: a subdirectory represents a level of nesting, and it needs a file with the *same name* as the directory to represent that level itself. For example:

```text
docs/
  facturacion.md              # Represents the &quot;facturacion&quot; section itself.
  facturacion/
    anular-dte.md              # A child of that section.
```

Without `facturacion.md`, the loader has no content item to attach `facturacion/anular-dte.md` to, and the whole subdirectory is silently skipped — it will not show up in listings, search, or the API export. This is the single most common cause of &quot;my content doesn&#039;t show up.&quot;

## Missing content is a 404, not a 500

Requesting a URI that doesn&#039;t exist (or a draft outside a local environment) throws `Derafu\Content\Exception\ContentNotFoundException`, which is mapped to **404 Not Found** — across every content plugin (Academy, Blog, Docs, FAQ, Pages) and [Storage](./storage) attachments alike, since they all resolve through the same `ContentRegistryInterface::get()`. A draft that isn&#039;t allowed is treated the same way on purpose, so a 403 doesn&#039;t reveal that it exists.

## Plugins

- **[Academy](./academy)**: Course management (course → module → lesson hierarchy).
- **[API](./api)**: Bulk JSON export of the content, meant for external indexing/RAG pipelines.
- **[Blog](./blog)**: Blog management.
- **[Docs](./docs)**: Documentation management.
- **[FAQ](./faq)**: FAQ management.
- **[MCP](./mcp)**: Exposes the content as an [MCP](https://modelcontextprotocol.io) (Model Context Protocol) server, so AI agents (Claude Code, Claude Desktop, Cursor, etc.) can search and fetch it directly as tools instead of relying on stale training data.
- **[Pages](./pages)**: Standalone pages management, rendered under a `/pages` prefix by default so it doesn&#039;t conflict with hand-authored flat pages.
- **[Search](./search)**: Semantic search engine integration, with an optional LLM-based conversational answer (&quot;ask&quot;) grounded on the indexed content.
- **[Sitemap](./sitemap)**: XML sitemap of every indexable content item, for search engines.
- **[Storage](./storage)**: Attachment storage and download management.

Every content item can also be rendered as HTML, Markdown or PDF, on top of the JSON used by the API and MCP plugins — see [PDF and Markdown export](./exports) for what those two add beyond the raw content (video/quiz rendering, bundling a whole section into one file, download buttons on the HTML view).




---

### Content Frontmatter

The YAML frontmatter fields shared by every content type (Academy, Blog, Docs, FAQ, Pages).

# Content frontmatter

Every Markdown content file starts with a YAML frontmatter block:

```markdown
---
title: &quot;How to void a DTE&quot;
description: &quot;Steps to void an electronic tax document&quot;
tags: [&quot;billing&quot;, &quot;chile&quot;]
draft: false
---

The body of the content, in Markdown, starts here.
```

These fields are defined once, in `AbstractContentItem`, and apply to **every** content type (Academy lessons/modules/courses, Blog posts, Docs, FAQ questions, Pages). A plugin&#039;s own page only lists fields it adds or overrides on top of this shared set — if a plugin&#039;s page doesn&#039;t mention a field from this table, it behaves exactly as described here.

Unknown keys are not rejected: the frontmatter schema allows undefined keys, so a plugin (or a Twig template) can read a custom field with `item.metadata(&#039;my_field&#039;)` without declaring it anywhere first.

## Identity and SEO

| Field | Type | Default | Description |
|---|---|---|---|
| `title` | string | file name | Title of the content. |
| `description` | string | auto (see `preview` below) | Meta description and card/preview text. `summary` is a deprecated alias. |
| `keywords` | array of strings | `[]` | Extra keywords added to the `&lt;meta name=&quot;keywords&quot;&gt;` tag, on top of the tags. |
| `image` | string | none | Absolute or relative URL used for `og:image` and card previews. |
| `video` | string | none | Video URL. YouTube &quot;watch&quot; URLs are automatically rewritten to embed URLs. |
| `slug` | string | slugified file name | Overrides the URL segment used for this item. |
| `tags` | array of strings | `[]` | Tags shown on the item and used to filter listings (`/type/tags/{tag}`). |
| `authors` | string, or array of strings/objects (`{name, slug}`) | `Anonymous` | `author` (singular) is a deprecated alias. |

## Publishing

| Field | Type | Default | Description |
|---|---|---|---|
| `draft` | bool | `false` | Drafts are only visible when `APP_ENV=local` or the request host is `localhost`; hidden otherwise. |
| `unlisted` | bool | `false` | Still reachable by direct URL, but excluded from listings/tag pages unless explicitly filtered by `id`/`uri`. |
| `date` | string or timestamp | `YYYY-MM-DD-` prefix in the file name, or file creation time | Publish date. `created` is a deprecated alias. |
| `last_update` | string or timestamp | file modification time | Shown as &quot;last updated on&quot;. |
| `deprecated` | bool, string or timestamp | `false` | `true` uses the file&#039;s modification time; a string/timestamp sets a specific deprecation date. |
| `indexable` | bool | `!draft &amp;&amp; !unlisted &amp;&amp; !deprecated`, and at least 100 characters of body | Whether the item should be considered for the `/api/content.json` export and, from there, external indexing (Qdrant, etc.). |
| `searchable` | bool | `!draft &amp;&amp; !unlisted &amp;&amp; !deprecated` | Whether the item should be considered a candidate for the site&#039;s own search (`search` plugin) — independent of `indexable`. |
| `time` | int (minutes) | auto-estimated from word count (200 wpm) | Reading time. Set it explicitly when the estimate is off (code-heavy pages, etc.). |

## Sidebar and table of contents

| Field | Type | Default | Description |
|---|---|---|---|
| `pagination_label` | string | `title` | Label used in the previous/next pagination links. |
| `sidebar_label` | string | `title` | Label used in the sidebar, which can differ from the page title. |
| `sidebar_position` | int | descending by `date` (newest first) | `order` is a deprecated alias. Lower sorts first. |
| `sidebar_class_name` | string | none | Extra CSS class added to the sidebar entry. |
| `sidebar_custom_props` | array | `[]` | Arbitrary data made available to the sidebar template. |
| `hide_title` | bool | `false` | Hides the `&lt;h1&gt;` rendered from `title`. |
| `hide_table_of_contents` | bool | `false` (`true` for Blog and FAQ) | Hides the in-page table of contents. |
| `toc_min_heading_level` | int (2-6) | `2` | Minimum heading level included in the table of contents. |
| `toc_max_heading_level` | int (2-6) | `6` | Maximum heading level included in the table of contents. |

## Authoring helpers

| Field | Type | Default | Description |
|---|---|---|---|
| `has_twig` | bool | auto-detected (`&lt;twig:` in the body) | Whether the Markdown body should be rendered as Twig before being rendered as Markdown, so it can use Twig components. |

Content is organized hierarchically on the filesystem for the plugins that support nesting — see [Content hierarchy](./introduction#content-content-hierarchy) for the &quot;one file per directory level&quot; rule that trips people up most often.




---

### Academy Plugin

Course management, with a course → module → lesson hierarchy.

# Academy plugin

Manages online courses with a three-level hierarchy: **course → module → lesson**. Each level is a content item on its own (with its own title, description, tags, etc.), built from the filesystem structure — see [Content hierarchy](./introduction#content-content-hierarchy).

```text
resources/content/academy/
  getting-started.md                       # Course.
  getting-started/
    introduction.md                        # Module.
    introduction/
      what-is-this.md                      # Lesson.
      how-it-works.md                      # Lesson.
```

## Configuration (`services.yaml`)

Enabled under `derafu.content.config.plugins.academy`:

```yaml
parameters:
    derafu.content.config:
        plugins:
            academy:
              path: &#039;resources/content/academy&#039;
              academyTitle: &#039;Academy&#039;
              academyDescription: &#039;Do you want to learn about a topic? Start a course with us!&#039;
```

| Option | Type | Default | Description |
|---|---|---|---|
| `path` | string | `resources/content/academy` | Directory scanned for course content, relative to the website root. |
| `academyTitle` | string | `Academy` | Title used for the academy&#039;s own SEO metadata. |
| `academyDescription` | string | `Do you want to learn about a topic? Start a course with us!` | Description used for the academy&#039;s own SEO metadata. |
| `include` | array of glob patterns | `[&#039;**.{markdown,md}&#039;]` | Files considered content, relative to `path`. |
| `exclude` | array of glob patterns | `[]` | Files excluded even if matched by `include`. |
| `showReadingTime` | bool | `true` | Whether to show the estimated reading time. |
| `showLastUpdateAuthor` | bool | `true` | Whether to show who last updated the lesson. |
| `showLastUpdateTime` | bool | `true` | Whether to show when the lesson was last updated. |
| `tags` | array of strings, or string | `[]` | Predefined tags for the academy. |
| `onInlineTags` | `ignore`\|`log`\|`warn`\|`throw` | `warn` | What to do when a lesson uses a tag that isn&#039;t in the predefined `tags` list. |

## Content frontmatter

Courses and modules use exactly the [generic content frontmatter](./frontmatter), nothing added.

Lessons add one field:

| Field | Type | Default | Description |
|---|---|---|---|
| `test` | string | none | A reference to a JSON quiz attachment: either `?attachment=&lt;filename&gt;`, or a literal path ending in `/_attachments/&lt;filename&gt;` (see [Storage](./storage)) — both resolve to the same local attachment. Parsed into a structured test, shown as an interactive quiz on the lesson&#039;s own page, and rendered in full in the [PDF and Markdown exports](./exports). Also switches the lesson&#039;s sidebar icon. A `test` value that resolves to neither form is passed through as-is to the quiz widget instead, unparsed. |

`time` on a course or module is not read from its own frontmatter — it is always the sum of its lessons&#039; `time` (explicit or estimated).

## Quiz JSON format

```json
{
    &quot;title&quot;: &quot;Introduction quiz&quot;,
    &quot;description&quot;: &quot;Check what you remember from this lesson.&quot;,
    &quot;questions&quot;: [
        {
            &quot;type&quot;: &quot;multiple_choice&quot;,
            &quot;text&quot;: &quot;Which of these is correct?&quot;,
            &quot;options&quot;: [
                { &quot;text&quot;: &quot;Option A&quot;, &quot;is_correct&quot;: true },
                { &quot;text&quot;: &quot;Option B&quot;, &quot;is_correct&quot;: false }
            ],
            &quot;allow_multiple&quot;: false,
            &quot;explanation&quot;: &quot;Option A is correct because...&quot;
        },
        {
            &quot;type&quot;: &quot;true_false&quot;,
            &quot;text&quot;: &quot;This statement is true.&quot;,
            &quot;answer&quot;: true,
            &quot;explanation&quot;: &quot;...&quot;
        }
    ]
}
```

| Field | Type | Description |
|---|---|---|
| `title` | string | Title of the test. |
| `description` | string | Optional description. |
| `questions[].type` | `multiple_choice`\|`true_false` | Question type. A `true_false` question needs no `options` — its two options (&quot;True&quot;/&quot;False&quot;) are generated from `answer`. |
| `questions[].options[].is_correct` | bool | Whether this option is a correct answer. More than one can be `true` when `allow_multiple` is `true`. |
| `questions[].explanation` | string | Shown in the PDF/Markdown exports&#039; answer key, and in the lesson&#039;s own interactive quiz when an answer is marked incorrect. |




---

### Blog Plugin

Blog management, with archive, tags and an RSS feed.

# Blog plugin

Manages blog posts: listing, tag pages (`/blog/tags/{tag}`), a date archive (`/blog/archive/{archive}`) and an RSS feed (`/blog/rss.xml`).

## Configuration (`services.yaml`)

Enabled under `derafu.content.config.plugins.blog`:

```yaml
parameters:
    derafu.content.config:
        plugins:
            blog:
              path: &#039;resources/content/blog&#039;
              blogTitle: &#039;Blog&#039;
              postsPerPage: 10
```

| Option | Type | Default | Description |
|---|---|---|---|
| `path` | string | `resources/content/blog` | Directory scanned for posts, relative to the website root. |
| `blogTitle` | string | `Blog` | Title used for the blog&#039;s own SEO metadata. |
| `blogDescription` | string | `Thoughts, stories, and the latest from our world.` | Description used for the blog&#039;s own SEO metadata. |
| `blogSidebarCount` | int | `5` | Number of recent posts shown in the sidebar. |
| `blogSidebarTitle` | string | `Recent posts` | Title of the recent-posts sidebar. |
| `include` | array of glob patterns | `[&#039;**.{markdown,md}&#039;]` | Files considered content, relative to `path`. |
| `exclude` | array of glob patterns | `[]` | Files excluded even if matched by `include`. |
| `postsPerPage` | int | `10` | Posts per page in the listing. |
| `showReadingTime` | bool | `true` | Whether to show the estimated reading time. |
| `feedOptions` | array | see below | RSS feed configuration (`/blog/rss.xml`). |
| `showLastUpdateAuthor` | bool | `true` | Whether to show who last updated the post. |
| `showLastUpdateTime` | bool | `true` | Whether to show when the post was last updated. |
| `tags` | array of strings, or string | `[]` | Predefined tags for the blog. |
| `onInlineTags` | `ignore`\|`log`\|`warn`\|`throw` | `warn` | What to do when a post uses a tag that isn&#039;t in the predefined `tags` list. |

### `feedOptions`

| Option | Type | Default | Description |
|---|---|---|---|
| `limit` | int | `20` | Number of posts included in the feed. |
| `title` | string | `blogTitle` | Overrides the feed title. |
| `description` | string | `blogDescription` | Overrides the feed description. |
| `copyright` | string | none | Copyright line of the feed. |
| `language` | string | none | Feed language. |
| `sortPosts` | `descending`\|`ascending` | `descending` | Sort order of the posts in the feed. |

## Content frontmatter

Blog posts use the [generic content frontmatter](./frontmatter), with one override:

| Field | Type | Default | Description |
|---|---|---|---|
| `hide_table_of_contents` | bool | `true` | Overrides the generic default of `false` — blog posts hide the table of contents unless explicitly re-enabled. |




---

### Docs Plugin

Documentation management, with a sidebar and nested sections.

# Docs plugin

Manages documentation pages, nested arbitrarily deep — see [Content hierarchy](./introduction#content-content-hierarchy). Renders as HTML, Markdown or PDF depending on the requested format (`Accept` header, or `.md`/`.pdf` suffix) — see [PDF and Markdown export](./exports) for what the last two add, including bundling a whole section into one file with `?full=1`.

## Configuration (`services.yaml`)

Enabled under `derafu.content.config.plugins.docs`:

```yaml
parameters:
    derafu.content.config:
        plugins:
            docs:
              path: &#039;resources/content/docs&#039;
              sidebarDepth: 5
```

| Option | Type | Default | Description |
|---|---|---|---|
| `path` | string | `resources/content/docs` | Directory scanned for docs, relative to the website root. |
| `include` | array of glob patterns | `[&#039;**.{markdown,md}&#039;]` | Files considered content, relative to `path`. |
| `exclude` | array of glob patterns | `[]` | Files excluded even if matched by `include`. |
| `sidebarPath` | bool | `true` | Whether to show the sidebar, auto-generated from the content hierarchy. There is no support for a custom, manually curated sidebar file — sort/label docs via `sidebar_position`/`sidebar_label` in their frontmatter instead. |
| `sidebarCollapsible` | bool | `true` | Whether sidebar categories can be collapsed. |
| `sidebarCollapsed` | bool | `true` | Whether sidebar categories start collapsed. |
| `sidebarDepth` | int | `5` | Maximum nesting depth shown in the sidebar. |
| `showLastUpdateAuthor` | bool | `true` | Whether to show who last updated the doc. |
| `showLastUpdateTime` | bool | `true` | Whether to show when the doc was last updated. |
| `breadcrumbs` | bool | `true` | Whether to show breadcrumbs above the doc. |
| `tags` | array of strings, or string | `[]` | Predefined tags for the docs. |
| `onInlineTags` | `ignore`\|`log`\|`warn`\|`throw` | `warn` | What to do when a doc uses a tag that isn&#039;t in the predefined `tags` list. |

## Content frontmatter

Docs use the [generic content frontmatter](./frontmatter), plus a few fields read directly by the docs template (not part of the shared schema, but supported the same way via arbitrary metadata):

| Field | Type | Default | Description |
|---|---|---|---|
| `show_title` | bool | `false` | Renders `# {title}` at the top of the body — useful together with `hide_title: true` if you want the title placed differently than the default heading. |
| `show_description` | bool | `false` | Renders the `description` right below the title inside the body. |
| `iframe` | string | none | URL embedded as a full-width `&lt;iframe&gt;` below the content. |
| `openapi` | string | none | URL of an OpenAPI/Swagger spec; renders a full Swagger UI below the content instead of (or in addition to) the Markdown body. |
| `show_source` | bool | `false` | Shows the raw source of the file (Markdown or Twig) in a code block below the content. |
| `show_children` | bool | `false` | Renders a card grid linking to this doc&#039;s direct children (title + description), useful for section landing pages. |

`hide_table_of_contents` keeps the generic default (`false`) for this plugin.




---

### FAQ Plugin

FAQ management, with nested sections and a sidebar.

# FAQ plugin

Manages frequently asked questions, nested arbitrarily deep — see [Content hierarchy](./introduction#content-content-hierarchy). Structurally very similar to [Docs](./docs), just under `/faq` instead of `/docs`.

## Configuration (`services.yaml`)

Enabled under `derafu.content.config.plugins.faq`:

```yaml
parameters:
    derafu.content.config:
        plugins:
            faq: ~
```

| Option | Type | Default | Description |
|---|---|---|---|
| `path` | string | `resources/content/faq` | Directory scanned for questions, relative to the website root. |
| `include` | array of glob patterns | `[&#039;**.{markdown,md}&#039;]` | Files considered content, relative to `path`. |
| `exclude` | array of glob patterns | `[]` | Files excluded even if matched by `include`. |
| `sidebarPath` | bool | `true` | Whether to show the sidebar, auto-generated from the content hierarchy. There is no support for a custom, manually curated sidebar file — sort/label questions via `sidebar_position`/`sidebar_label` in their frontmatter instead. |
| `sidebarCollapsible` | bool | `true` | Whether sidebar categories can be collapsed. |
| `sidebarCollapsed` | bool | `true` | Whether sidebar categories start collapsed. |
| `sidebarDepth` | int | `5` | Maximum nesting depth shown in the sidebar. |
| `showLastUpdateAuthor` | bool | `false` | Whether to show who last updated the question. |
| `showLastUpdateTime` | bool | `true` | Whether to show when the question was last updated. |
| `breadcrumbs` | bool | `true` | Whether to show breadcrumbs above the question. |
| `tags` | array of strings, or string | `[]` | Predefined tags for the FAQ. |
| `onInlineTags` | `ignore`\|`log`\|`warn`\|`throw` | `warn` | What to do when a question uses a tag that isn&#039;t in the predefined `tags` list. |

## Content frontmatter

FAQ questions use the [generic content frontmatter](./frontmatter), with one override and one addition:

| Field | Type | Default | Description |
|---|---|---|---|
| `hide_table_of_contents` | bool | `true` | Overrides the generic default of `false` — questions hide the table of contents unless explicitly re-enabled. |
| `show_children` | bool | `false` | Renders a card grid linking to this question&#039;s direct children (title + description), useful for section landing pages. |




---

### Pages Plugin

Standalone pages management, rendered as HTML, Markdown, PDF or JSON.

# Pages plugin

Manages standalone pages (an &quot;about us&quot;, a landing page, changelog, etc.), including `.html.twig` files in addition to Markdown. Same content model and format negotiation (HTML/Markdown/PDF/JSON) as [Docs](./docs)/[FAQ](./faq), just without a listing/tag view — pages are meant to be linked directly, not browsed.

## Route and the `/pages` prefix

| Route | Path | Description |
|---|---|---|
| `pages_page` | `GET /pages/{uri}` | Shows a page. Format negotiated the same way as every other content plugin (`Accept` header, or `.md`/`.pdf`/`.json` suffix). |

The route is registered under `/pages` **on purpose**, not at the root (`/about` instead of `/pages/about`). The `{page:.+}` pattern is a catch-all, and the router tries routes with parameters *before* it tries file-based routes (`Derafu\Routing\Parser\FileSystemParser`, used for standalone Twig pages placed directly in `templates/pages/` by the website itself, with no content model). A catch-all with no prefix would shadow every one of those file-based pages: the router would never even get to ask the file-system parser about a URI this route already claims, since it checks parsers in order and stops at the first one that matches.

We verified this in practice: with the plugin enabled and content under `resources/content/pages/`, every existing hand-written route (static ones, and file-based ones from `templates/pages/`) kept working exactly as before — only URIs actually starting with `/pages/` are affected.

If a website wants pages at the root anyway, it can register its own route pointing at the same handler instead of importing this one:

```yaml
pages_page:
  path: /{page:.+}
  handler: &#039;Derafu\Content\Plugin\Pages\PagesController::show&#039;
```

Registering that **before** any route that relies on `FileSystemParser`&#039;s auto-discovery turns this trade-off off for that whole website: from then on, every flat page must be a content item (title/tags/draft/etc. via frontmatter), not a hand-authored Twig template dropped into `templates/pages/`. Pick one mechanism per website, not both, once this is unprefixed.

## Configuration (`services.yaml`)

Enabled under `derafu.content.config.plugins.pages`:

```yaml
parameters:
    derafu.content.config:
        plugins:
            pages:
              path: &#039;resources/content/pages&#039;
```

| Option | Type | Default | Description |
|---|---|---|---|
| `path` | string | `resources/content/pages` | Directory scanned for pages, relative to the website root. |
| `include` | array of glob patterns | `[&#039;**.{markdown,md,html.twig}&#039;]` | Files considered content, relative to `path`. Unlike other plugins, this also matches `.html.twig` files. |
| `exclude` | array of glob patterns | `[]` | Files excluded even if matched by `include`. |
| `showLastUpdateAuthor` | bool | `false` | Whether to show who last updated the page. |
| `showLastUpdateTime` | bool | `false` | Whether to show when the page was last updated. |

## Content frontmatter

Pages use exactly the [generic content frontmatter](./frontmatter), nothing added.




---

### PDF and Markdown Export

What the PDF and Markdown exports add on top of the raw content, and how to bundle a whole section into one file.

# PDF and Markdown export

Every content item (Academy, Blog, Docs, FAQ, Pages) renders as HTML, Markdown, PDF or JSON — format negotiated via the `Accept` header, or a `.md`/`.pdf`/`.json` suffix on the URL. HTML is the normal browsing experience and JSON is the same shape used by [API](./api)/[MCP](./mcp); this page covers what is specific to the PDF and Markdown exports.

## Root-relative URLs are resolved to absolute ones

A content body written for the website itself references images and internal links with root-relative paths (`/img/foo.png`, `/docs/other-page`). Neither export has a &quot;current page&quot; to resolve a root-relative path against, so both make the body self-contained wherever it ends up — an LLM&#039;s context window, someone else&#039;s Markdown viewer, a PDF saved to disk:

- **Markdown**: both images and internal links are rewritten to absolute URLs.
- **PDF**: only links are rewritten. Images are left root-relative on purpose — the PDF engine resolves them from the local filesystem instead of fetching them over HTTP, which is slower and, on a single-worker server, can tie up the whole process waiting on a request back to itself.

## Video and quiz are shown, not just left in the frontmatter

A frontmatter `video` (see [Content frontmatter](./frontmatter)) is a plain URL in YAML that neither export would otherwise surface anywhere a reader (or an LLM) would notice it:

- **PDF**: a clickable YouTube thumbnail linking to the watch page, or a plain link for a non-YouTube URL.
- **Markdown**: the same thumbnail and link, as a Markdown image wrapped in a link.

An [Academy](./academy) lesson&#039;s `test` (quiz) is rendered in full — every option, not only the correct one, plus the explanation, since the wrong options and the explanation are real information (useful to know what an answer is commonly confused with, or why it&#039;s wrong):

- **PDF**: the questions with checkbox-style options first (to answer on paper), then a dedicated &quot;Answers&quot; section on its own page, with the correct option(s) checked and the explanation for each question.
- **Markdown**: each question as a GFM task list (`- [x]`/`- [ ]`, the correct option(s) checked), followed by the explanation as a blockquote.

## Bundling a whole section: `?full=1`

Content types with children — a Docs or FAQ section, an Academy course or module — accept `?full=1` on their `.pdf`/`.md` URL to bundle the whole subtree into a single file, instead of just the current item with a short list of links to its children:

- **PDF**: a cover page, a real table of contents with page numbers, then every descendant on its own page (Docs/FAQ children recursively; an Academy course&#039;s modules and, nested under each, its lessons).
- **Markdown**: every descendant&#039;s title and full body appended after the current item&#039;s, with heading levels reflecting how deep it is nested.

`?full=1` on an item with no children is a no-op: it falls through to the regular, single-item export.

## Download buttons on the HTML view

Every content page shows small, always-visible buttons for its PDF and Markdown exports, next to the tags/last-updated line — plain buttons, not a dropdown, so there is nothing to discover before they can be used:

- **PDF** opens inline: browsers render it in their own viewer, which already has its own save/print controls.
- **Markdown** downloads instead of opening inline: a browser has no native Markdown renderer, so opening it in a tab just shows unstyled raw text. A &quot;Copy&quot; button next to it copies the Markdown source straight to the clipboard — handy for pasting into an LLM — and confirms with a [Notyf](https://cdn.jsdelivr.net/npm/notyf@3.10.0/) toast when the site has it loaded, falling back to a brief change of the button&#039;s own text otherwise.

&quot;Full&quot; (and &quot;Full Copy&quot;) variants of these buttons appear next to the regular ones whenever the item actually has children to bundle.




---

### API Plugin

Bulk JSON export of the content, meant for external indexing/RAG pipelines.

# API plugin

Exposes a single endpoint, `GET /api/content.json`, that returns every **indexable** item across every content plugin (Academy, Blog, Docs, FAQ, Pages) as a flat JSON list, via `ContentService::allContent()` — the same aggregation the [Sitemap](./sitemap) plugin uses. This is the feed an external pipeline (e.g. a tokenizer that loads embeddings into Qdrant) is meant to consume, and it is the source of truth external tools index against — not a page-by-page crawl of the website.

```json
{
    &quot;meta&quot;: {
        &quot;count&quot;: 424,
        &quot;url&quot;: &quot;https://example.com/&quot;,
        &quot;generated&quot;: &quot;2026-08-17 16:52:39&quot;
    },
    &quot;data&quot;: [
        {
            &quot;id&quot;: &quot;docs_doc_facturacion_anular-dte&quot;,
            &quot;checksum&quot;: &quot;b256587c...&quot;,
            &quot;type&quot;: &quot;docs&quot;,
            &quot;category&quot;: &quot;doc&quot;,
            &quot;uri&quot;: &quot;facturacion/anular-dte&quot;,
            &quot;link&quot;: &quot;https://example.com/docs/facturacion/anular-dte.json&quot;,
            &quot;image&quot;: null,
            &quot;title&quot;: &quot;Anular un DTE&quot;,
            &quot;description&quot;: &quot;Pasos para anular un Documento Tributario Electrónico&quot;,
            &quot;authors&quot;: [{ &quot;name&quot;: &quot;Anonymous&quot;, &quot;slug&quot;: &quot;anonymous&quot; }],
            &quot;tags&quot;: [{ &quot;name&quot;: &quot;facturacion&quot;, &quot;slug&quot;: &quot;facturacion&quot;, &quot;count&quot;: 12 }],
            &quot;date&quot;: &quot;2026-03-12&quot;,
            &quot;last_update&quot;: &quot;2026-03-12&quot;,
            &quot;time&quot;: 2
        }
    ]
}
```

Items with an empty body (`data()`), or that are not `indexable` (see [generic frontmatter](./frontmatter)), are skipped.

## Configuration (`services.yaml`)

None. Enabling the plugin (`api: ~`) is all that&#039;s needed — it has no options of its own, it only reads whatever the Academy/Blog/Docs/FAQ/Pages plugins already loaded.

```yaml
parameters:
    derafu.content.config:
        plugins:
            api: ~
```

## Filtering

The endpoint accepts the same filters as [`ContentRegistry::filter()`](https://github.com/derafu/content) as query parameters — for example `/api/content.json?type=docs&amp;tag=facturacion&amp;limit=20&amp;page=1`. Useful keys: `type`, `category`, `tag`, `author`, `search`, `year`+`month`, `indexable`, `searchable`, `limit`, `page`.

## Content frontmatter

Not applicable — this plugin has no content items of its own, it aggregates the ones from the plugins that do.




---

### Sitemap Plugin

XML sitemap of every indexable content item, for search engines.

# Sitemap plugin

Exposes a single endpoint, `GET /sitemap.xml`, listing every **indexable** item across every content plugin (Academy, Blog, Docs, FAQ, Pages) — for search engine crawlers, not for AI agents (that&#039;s [MCP](./mcp)) nor for an indexing pipeline (that&#039;s [API](./api)). Deliberately does not emit `&lt;changefreq&gt;`/`&lt;priority&gt;`: modern crawlers (Google included) ignore both, so there is nothing to configure there.

```xml
&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; ?&gt;
&lt;urlset xmlns=&quot;http://www.sitemaps.org/schemas/sitemap/0.9&quot;&gt;
    &lt;url&gt;
        &lt;loc&gt;https://example.com/docs/facturacion/anular-dte&lt;/loc&gt;
        &lt;lastmod&gt;2026-03-12T17:11:58+00:00&lt;/lastmod&gt;
    &lt;/url&gt;
&lt;/urlset&gt;
```

Items with `indexable: false` (see [generic frontmatter](./frontmatter)) are skipped — the same rule the [API](./api) plugin&#039;s export uses.

## Configuration (`services.yaml`)

None. Enabling the plugin (`sitemap: ~`) is all that&#039;s needed.

```yaml
parameters:
    derafu.content.config:
        plugins:
            sitemap: ~
```

## Content frontmatter

Not applicable — this plugin has no content items of its own, it aggregates the ones from the plugins that do, the same way the [API](./api) plugin does (via `ContentService::allContent()`).




---

### MCP Plugin

Exposes the content as an MCP (Model Context Protocol) server for AI agents.

# MCP plugin

Exposes the content of the website as an [MCP](https://modelcontextprotocol.io) (Model Context Protocol) server, so AI agents (Claude Code, Claude Desktop, Cursor, claude.ai connectors, etc.) can search and fetch it directly as tools during their own conversation — instead of relying on stale training data or scraping HTML.

This is a different consumption channel than the [API](./api) plugin: the API plugin is a one-shot bulk export meant for an indexing pipeline (Qdrant, etc.); the MCP plugin is a live, callable interface for any MCP-capable agent.

## Endpoint

A single route, `POST /api/mcp` (also accepts `DELETE` to end a session, and `OPTIONS` for CORS preflight). It deliberately has **no `.json` suffix**: unlike every other endpoint in this package, its response is not always JSON — it can also be `text/event-stream` (SSE) when the protocol needs to keep the connection open mid-call. The MCP protocol negotiates that per-request via the `Accept` header, not via the URL, and every real MCP client already expects a plain endpoint URL with no extension.

It speaks JSON-RPC 2.0 over the &quot;Streamable HTTP&quot; transport of the [official PHP MCP SDK](https://github.com/modelcontextprotocol/php-sdk) (`mcp/sdk`), synchronously — there is no event loop involved, it fits the same request/response model as every other controller in this package.

## Configuration (`services.yaml`)

Enabled under `derafu.content.config.plugins.mcp`:

```yaml
parameters:
    derafu.content.config:
        plugins:
            mcp:
              ask:
                enabled: true
```

| Option | Type | Default | Description |
|---|---|---|---|
| `server_name` | string | `derafu-content` | Name announced to MCP clients during the `initialize` handshake. |
| `server_version` | string | `1.0.0` | Version announced to MCP clients during the `initialize` handshake. |
| `session_path` | string | a subdirectory of `sys_get_temp_dir()` | Directory where MCP sessions are persisted on disk between requests (a session is created on `initialize` and referenced by later calls via the `Mcp-Session-Id` header; since each request builds a new server instance, this cannot live in memory). Point it at something under the website&#039;s `var/` directory for a more durable/cleanable location. |
| `session_ttl` | int | `3600` | Time to live, in seconds, of a persisted session. |
| `ask.enabled` | bool | `false` | Whether the `ask` tool (LLM-backed conversational answers) is registered. See [Tools](#content-tools) below. |

This plugin does not handle authentication nor rate limiting — that is not its responsibility. If the endpoint needs to be protected, do it at the HTTP stack level (e.g. with the middlewares of `derafu/http`), before this controller is reached.

### The `mcp/sdk` dependency

`mcp/sdk` is a `require-dev` dependency (with a `suggest` entry) of `derafu/content`, not a hard `require`. If you enable this plugin, add it explicitly to **your own** `composer.json`:

```json
{
    &quot;require&quot;: {
        &quot;mcp/sdk&quot;: &quot;^0.7&quot;
    }
}
```

If you don&#039;t enable the plugin, nothing needs it: hitting `/api/mcp` without it installed and without the plugin configured fails with a normal &quot;plugin not found&quot; error, exactly like hitting `/api/search.json` without the `search` plugin configured — it does not affect any other endpoint of the website.

## Tools

| Tool | Always registered? | Description |
|---|---|---|
| `search_content` | yes | Semantic search across the indexed content, delegating to the [Search](./search) plugin&#039;s engine (Qdrant, or whatever is configured there). Accepts an optional `source` (`academy`, `blog`, `docs`, `faq`, `pages`, or `all`) to scope the search; omitting it or passing `all` searches every source at once. |
| `get_content` | yes | Fetch a single item by source and URI: full Markdown body plus metadata (title, tags, dates, authors). |
| `list_content` | yes | Browse/filter the items of a source (academy, blog, docs, faq, pages), optionally by tag, category or free-text search. |
| `list_tags` | yes | List the tags used in a source, with how many items use each one. |
| `ask` | only if `ask.enabled: true` | Ask a natural-language question and get a conversational answer from the LLM configured in the [Search](./search) plugin, grounded on the indexed content. |

`ask` is opt-in because, unlike the other four tools (which only ever read from the indexed content), it depends entirely on the quality of whatever LLM backend answers it — a bad or failed answer there makes the whole MCP server look unreliable, even though the rest of the tools never touch an LLM at all.

## Content frontmatter

Not applicable — this plugin has no content items of its own, it reuses whichever content plugins (Academy, Blog, Docs, FAQ, Pages) are already enabled.




---

### Search Plugin

Search engine integration, with an optional LLM-based conversational answer.

# Search plugin

Provides a search page and API that proxy to an **external** search engine — this plugin does not index anything itself, it queries whatever semantic/full-text search service you point it at (in practice, a service backed by Qdrant, fed by the [API](./api) plugin&#039;s export). It optionally also proxies to an LLM to answer questions conversationally, grounded on that same indexed content.

## Routes

| Route | Path | Description |
|---|---|---|
| `search` | `GET /search` | Search page (HTML). |
| `search_api` | `GET /api/search.json?q=...` | Search results as JSON. |
| `search_llm_query` | `GET /api/search/llm.json?q=...` | Conversational answer from the LLM (see [LLM backend](#content-llm-backend) below). |

## Configuration (`services.yaml`)

Enabled under `derafu.content.config.plugins.search`:

```yaml
parameters:
    derafu.content.config:
        plugins:
            search:
              url: &#039;https://search.example.com/api/search?collection=%s&amp;base_url=%s&amp;text=%s&#039;
              collection: &#039;my-site&#039;
              base_url: &#039;https://example.com&#039;
              llm_url: &#039;https://api.openai.com&#039;
              llm_model: &#039;gpt-4o-mini&#039;
              llm_api_key: &#039;%env(OPENAI_API_KEY)%&#039;
```

| Option | Type | Default | Description |
|---|---|---|---|
| `url` | string | *(required)* | `sprintf()` template for the search engine&#039;s URL. See [URL template](#content-url-template) below. |
| `collection` | string | none | Collection/index identifier, URL-encoded and injected into `url`. |
| `base_url` | string | none | Base URL of the website, URL-encoded and injected into `url` (useful when the search backend serves more than one site). |
| `llm_url` | string | none | Base URL of the LLM backend. Leave unset to disable the `ask` tool ([MCP](./mcp)) and make `search_llm_query` fail. |
| `llm_model` | string | none | Model name sent to the LLM backend. **Required if `llm_url` is set** — there is no generic default, since no single model name makes sense across every provider. `llm()` throws immediately with a clear message if `llm_url` is configured without it, instead of silently sending an empty model name to your backend. |
| `llm_api_key` | string | none | API key sent as `Authorization: Bearer &lt;key&gt;`. |
| `llm_completions_path` | string | `/v1/chat/completions` | Path of the chat completions endpoint, appended to `llm_url`. See [LLM backend](#content-llm-backend). |

### URL template

`url` is a `sprintf()` template consumed in this order — trailing placeholders can be omitted:

1. `%s` → collection (only if both `collection` and `base_url` are set).
2. `%s` → base URL (only if both `collection` and `base_url` are set).
3. `%s` → the URL-encoded search query (always present, always last).

If `collection` or `base_url` is not configured, `url` is expected to have a single `%s` for the query only.

## LLM backend

The `llm_*` options configure a client for **any backend exposing an OpenAI-compatible chat completions endpoint** — the `{model, messages}` request / `choices[0].message.content` response shape that has become a de facto standard. With no code changes, that covers:

- OpenAI itself (`llm_url: https://api.openai.com`, default `llm_completions_path`).
- [OpenRouter](https://openrouter.ai) (`llm_url: https://openrouter.ai/api`, default `llm_completions_path`, `llm_model` like `anthropic/claude-sonnet-4.5`) — itself proxies Claude, Gemini, Llama, etc. through the same shape.
- Anthropic&#039;s own [OpenAI-compatible endpoint](https://platform.claude.com/docs/en/api/openai-sdk) (`llm_url: https://api.anthropic.com`, default `llm_completions_path`).
- Self-hosted [Open WebUI](https://openwebui.com) — needs `llm_completions_path: /api/chat/completions` instead of the default, since it does not follow the standard path.
- Groq, Together AI, Ollama, Azure OpenAI (`Authorization: Bearer` variant), and most other providers.

A backend that does not speak this protocol at all (a provider&#039;s native, non-compatible API) is not supported today, but the client is behind an interface (`Derafu\Content\Plugin\Search\Contract\LlmClientInterface`) precisely so an alternative implementation can be swapped in later without touching `SearchController` or the `ask` MCP tool.

Failures from either the search engine or the LLM backend (unreachable host, non-200 response, unexpected response shape) surface as `SearchUpstreamException`, mapped to **502 Bad Gateway** — not a generic 500 — since the request to this website was fine, it&#039;s whatever it depends on that failed. The error message includes whatever detail the upstream returned (`error`/`error.message`/`detail`, depending on the provider).

## Content frontmatter

Not applicable — this plugin has no content items of its own; `searchable` (see [generic frontmatter](./frontmatter)) is read by whoever builds the external search index, not by this plugin.




---

### Storage Plugin

Attachment storage and download management for any content type.

# Storage plugin

Serves file attachments that live next to a content item — images, PDFs, quiz files for an [Academy](./academy) lesson, etc. — through a single, uniform URL, regardless of which content plugin owns the item.

## Attachment convention

An attachment is any file placed in a `_attachments/` subdirectory next to its content file, named after that file (without extension):

```text
resources/content/academy/getting-started/introduction/what-is-this.md
resources/content/academy/getting-started/introduction/what-is-this/_attachments/
  cheat-sheet.pdf
  quiz.json
```

`what-is-this.md`&#039;s attachments are exactly the files under `what-is-this/_attachments/`. Reference one from frontmatter as `?attachment=&lt;filename&gt;` — see the `test` field of [Academy](./academy) lessons for a real example.

## Route

| Route | Path | Description |
|---|---|---|
| `content_storage_download` | `GET /{type}/{uri}/_attachments/{attachment}` | Downloads an attachment. `type` is the content type (`docs`, `academy`, etc.), `uri` is the item&#039;s URI. |

## Configuration (`services.yaml`)

None. Enabling the plugin (`storage: ~`) is all that&#039;s needed — it has no options of its own, it only serves whatever attachments already exist next to items loaded by the other content plugins.

```yaml
parameters:
    derafu.content.config:
        plugins:
            storage: ~
```

## Content frontmatter

Not applicable — attachments are files on disk, not content items, and are not referenced through frontmatter fields of their own (beyond however a specific plugin points at them, like Academy&#039;s `test`).




---

### Content Cache

What the built-in content cache does and does not speed up, and how to swap its backend.

# Content cache

Every content plugin that supports nesting (Academy, Blog, Docs, FAQ, Pages) builds its item tree by scanning the filesystem and parsing the YAML frontmatter of every single file — on every request, since this package has no build step and no long-running process. The content cache exists to avoid redoing that scan-and-parse work on every request; it is not a general-purpose page cache.

## What it caches

Exactly one thing: the **already-built item tree** of a content registry (the result of `AbstractContentRegistry::all()`) — titles, tags, dates, hierarchy, everything derived from frontmatter. It is keyed by the plugin&#039;s `path` + `include` + `exclude` options, so two registries pointed at the same content share one cache entry regardless of which plugin instantiated them first.

This benefits every operation that needs to look at more than one item — listings, tag pages, the sidebar, [Sitemap](./sitemap), and [API](./api)&#039;s `allContent()` export — since those are exactly the operations that today pay the &quot;read and parse everything&quot; cost on every request, whether or not the specific page requested needed most of that data.

## What it does **not** cache

To avoid false expectations:

- **Rendered output.** HTML, Markdown, PDF and JSON responses are all still generated fresh on every request — Twig compilation, the `markdown`/`twig` filters, PDF generation. Caching the index does not cache a page.
- **HTTP-level caching.** No `Cache-Control` or `ETag` header is set by any content controller. A reverse proxy or CDN in front of the site would still treat every request as uncacheable unless configured to do otherwise, independently of this feature.
- **[Search](./search)&#039;s external calls.** Queries to the search engine and the LLM backend are never cached — they hit the configured `url`/`llm_url` on every call.
- **[MCP](./mcp) tool responses.** Each tool call resolves fresh against the (possibly cached) registries; the MCP layer itself adds no caching of its own.
- **[Storage](./storage) attachment downloads.** Files are streamed from disk on every request.

In short: this makes it cheaper to *know what content exists and what it&#039;s about*. It does not make any specific page&#039;s response faster to render or serve.

## Default: a real filesystem cache, no configuration needed

Caching is on by default, with no service to run. The package&#039;s own `content-services.yaml` (imported by every site that enables this package) wires a `Symfony\Component\Cache\Adapter\FilesystemAdapter` under namespace `derafu_content`, 60-second TTL, rooted at `%kernel.cache_dir%` — the exact same `var/cache/&lt;env&gt;/` directory the rest of a `derafu/kernel`-based app already uses (compiled container, etc.). Concretely, that means `var/cache/dev/derafu_content/` in a `dev` environment, `var/cache/prod/derafu_content/` in `prod`, and so on — clearing it is `rm -rf var/cache/&lt;env&gt;/derafu_content`, the same mental model as clearing any other cache in the app, nothing extra to remember.

`Derafu\Content\ContentContext` itself defaults to a `FilesystemAdapter` under the system temp directory if no cache pool is injected at all — that fallback only matters if this package is used standalone, outside the shipped `services.yaml` (e.g. directly in PHP, or in this package&#039;s own test suite). Any real site importing `content-services.yaml` gets the `var/cache/&lt;env&gt;/derafu_content` location described above, not the temp directory.

## Freshness

Each cache entry expires after 60 seconds. There is no manual invalidation and no filesystem-change detection: editing a Markdown file is not reflected instantly, only once its registry&#039;s entry expires (up to 60 seconds later) or the process restarts. This is a deliberate trade-off — a short TTL removes the &quot;rescan everything on every request&quot; cost without needing any invalidation logic — but it does mean content changes are not instantaneous the way they were without caching.

On a multi-worker or multi-server deployment, each worker/server builds and caches its own copy independently unless they share the cache backend (e.g. all pointed at the same Redis instance) — see below.

## Disabling the cache: `cache.enabled`

Enabled under `derafu.content.config`:

```yaml
parameters:
    derafu.content.config:
        cache:
            enabled: true
```

| Option | Type | Default | Description |
|---|---|---|---|
| `enabled` | bool | `true` | Whether the content cache is used at all. When `false`, `ContentContext::cache()` returns `null` and every registry falls back to `AbstractContentRegistry`&#039;s own `null`-cache path: it rescans and reparses on every request, with no pool involved. |

This package reads that value as a plain boolean — it does not know about environments or kernels. Sourcing it from an environment variable, with whatever per-environment default a site wants, is entirely the site&#039;s own `services.yaml` concern:

```yaml
parameters:
    derafu.content.config:
        cache:
            enabled: &#039;%env(bool:DERAFU_CONTENT_CACHE_ENABLED)%&#039;
```

`DERAFU_CONTENT_CACHE_ENABLED` then comes from wherever the site already resolves per-environment values (a `.env`/`.env.local` cascade, a `when@dev:` config block using `%kernel.environment%`, etc.) — this package never reads `$_SERVER[&#039;APP_ENV&#039;]` or the kernel for this decision, only the boolean it&#039;s handed.

## Using a different cache backend

The cache pool is the `derafu_content.cache` service, typed against `Psr\Cache\CacheItemPoolInterface` (PSR-6) where it&#039;s injected into `ContentContext`. A site&#039;s own `services.yaml` is loaded after the package&#039;s, so redefining that same service ID replaces it outright — no need to touch `ContentContextInterface`&#039;s own definition:

```yaml
services:
    derafu_content.cache:
        class: Symfony\Component\Cache\Adapter\RedisAdapter
        arguments:
            $redis: &#039;@Redis&#039;
            $namespace: &#039;derafu_content&#039;
            $defaultLifetime: 60
```

This is the right move on a multi-worker or multi-server deployment: with the default `FilesystemAdapter`, each worker/server builds and caches its own copy independently; pointing `derafu_content.cache` at a shared backend (Redis, Memcached) instead means they all share one cache instead of duplicating the work. This is a different concern than `cache.enabled` above: swapping the backend still caches, just somewhere shared; `cache.enabled: false` skips caching altogether regardless of which backend is wired.

## Content frontmatter

Not applicable — caching operates on already-loaded items, it adds no frontmatter fields of its own.





---
Last updated on 09/09/2026

