Success-story migration + new plugins + JSON/UX polish

- Success Stories: full migration from old site (48/48, audited),
  markets n:n, quotation content block, columns content block,
  pretty SEO detail URLs via SuccessStoryPathRewrite middleware,
  list/detail split (list page 4 / story page), detailUrl/backUrl
- New plugins: VITEC Locations (grid/list/map + RTE map text),
  VITEC Customer Logos (color/bw logic, only-show-selected),
  VITEC Card (one plugin for product/story/market/solution
  with reloading FlexForm + custom backend preview renderer)
- Eventlist: layout dropdown (list/grid/teaserbar) in settings
- Hero section CB: Images/Video tabs, background video + overlay
- Product JSON: full category rootline (parents), fixed missing
  ConnectionPool import (all-products crash), category tree map
- Backend preview CSS: container-query responsive (narrow columns)
- Docs: ISO architecture spec, root + extension READMEs

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
khaccount
2026-07-24 09:53:33 +02:00
parent aafcb5dcb6
commit eae40f4286
110 changed files with 8502 additions and 279 deletions

View File

@@ -168,3 +168,7 @@ bash fix-asset-perms.sh # fix asset permission
[GPL2.0orlater](LICENSE) — © evomedien.
<div align="center"><sub>VITEC · TYPO3 headless backend + React front end</sub></div>
vitecdev
17ZJfy75sJoU68J6

View File

@@ -22,6 +22,7 @@
"friendsoftypo3/headless": "^5.0@rc",
"georgringer/news": "^14.0",
"netzbewegung/nb-headless-content-blocks": "^0.0.23",
"plan2net/backend-category-hierarchy": "^14.0",
"typo3/cms-backend": "^14.3",
"typo3/cms-belog": "^14.3",
"typo3/cms-beuser": "^14.3",

68
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "894f07b60885efbdf0a5979a6879e631",
"content-hash": "09c5be5fca6a03ab5f0ed81662f20f36",
"packages": [
{
"name": "b13/container",
@@ -2834,6 +2834,66 @@
},
"time": "2026-07-08T07:01:06+00:00"
},
{
"name": "plan2net/backend-category-hierarchy",
"version": "14.0.0",
"source": {
"type": "git",
"url": "https://github.com/plan2net/backend-category-hierarchy.git",
"reference": "ee40a998fadba5545401425a0bc224482d0f643a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/plan2net/backend-category-hierarchy/zipball/ee40a998fadba5545401425a0bc224482d0f643a",
"reference": "ee40a998fadba5545401425a0bc224482d0f643a",
"shasum": ""
},
"require": {
"php": "^8.2",
"typo3/cms-backend": "^13.4 || ^14.0",
"typo3/cms-core": "^13.4 || ^14.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.64",
"phpstan/extension-installer": "^1.4",
"phpstan/phpstan": "^2.0",
"phpstan/phpstan-phpunit": "^2.0",
"phpstan/phpstan-strict-rules": "^2.0",
"phpunit/phpunit": "^11.5",
"typo3/testing-framework": "^9.3 || ^10.0",
"vimeo/psalm": "^6.0"
},
"suggest": {
"georgringer/news": "*"
},
"type": "typo3-cms-extension",
"extra": {
"typo3/cms": {
"extension-key": "backend_category_hierarchy"
}
},
"autoload": {
"psr-4": {
"Plan2net\\BackendCategoryHierarchy\\": "Classes/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"GPL-2.0-or-later"
],
"description": "Show category hierarchy information in list view",
"homepage": "https://plan2.net",
"keywords": [
"TYPO3 CMS",
"backend",
"category"
],
"support": {
"issues": "https://github.com/plan2net/backend-category-hierarchy/issues",
"source": "https://github.com/plan2net/backend-category-hierarchy/tree/14.0.0"
},
"time": "2026-05-17T07:58:49+00:00"
},
{
"name": "psr/cache",
"version": "3.0.0",
@@ -9411,10 +9471,10 @@
},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {},
"platform-dev": {},
"platform": [],
"platform-dev": [],
"platform-overrides": {
"php": "8.4.0"
},
"plugin-api-version": "2.9.0"
"plugin-api-version": "2.3.0"
}

View File

@@ -69,4 +69,26 @@ routeEnhancers:
aspects:
news-title:
type: NewsTitle
UsecaseDetail:
type: Extbase
limitToPages:
- 4
- 26
- 25
extension: Vitec
plugin: Usecaseshow
routes:
-
routePath: '/{usecase_slug}'
_controller: 'Usecase::show'
_arguments:
usecase_slug: usecase
defaultController: 'Usecase::show'
requirements:
usecase_slug: '[a-zA-Z0-9\-]+'
aspects:
usecase_slug:
type: PersistedAliasMapper
tableName: tx_vitec_domain_model_usecase
routeFieldName: slug
websiteTitle: ''

View File

@@ -0,0 +1,708 @@
<?php
declare(strict_types=1);
namespace Evomedien\Vitec\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use TYPO3\CMS\Core\Core\Bootstrap;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\DataHandling\DataHandler;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* One-time (idempotent) migration of the old vitec.com Success Stories into
* the new tx_vitec_domain_model_usecase model.
*
* Source: migrations JSON produced on the OLD site by
* migrations/export_success_stories.php, plus the staged original files under
* fileadmin/success_stories_import/ (same relative identifiers as on the old
* site).
*
* Mapping (see analysis in the session / architecture spec):
* pages(title,slug,SEO) -> usecase base + seo_*
* mask_web__markets_card -> subtitle, card_image, customer_logo
* mask_web__success_story__promo -> hero (bgimage, overlay color, theme)
* use_case element (APP) -> industries (sys_category) -> markets MM
* page content (flux tree) -> inline content_elements:
* text -> text CE (blockquote quotes -> vitec_quotation)
* image/textmedia -> image/textmedia CE + FAL
* html -> html CE
* 2-column flux rows -> vitec_columns content block
*
* Usage:
* vendor/bin/typo3 vitec:import-success-stories --dry-run
* vendor/bin/typo3 vitec:import-success-stories --only=meydan-racecourse
* vendor/bin/typo3 vitec:import-success-stories --force (reimport existing)
*/
#[AsCommand(
name: 'vitec:import-success-stories',
description: 'Import success stories from the old-website export JSON into the usecase model'
)]
final class ImportSuccessStoriesCommand extends Command
{
private const TABLE = 'tx_vitec_domain_model_usecase';
private const FILES_BASE = '/success_stories_import';
/** Old market2 token -> canonical industry title. */
private const INDUSTRY_TOKENS = [
'sports' => 'Sports', 'venues' => 'Venues', 'government' => 'Government',
'military' => 'Military', 'corporate' => 'Corporate', 'broadcast' => 'Broadcast',
'education' => 'Education', 'healthcare' => 'Healthcare',
'hospitality' => 'Hospitality & Leisure', 'leisure' => 'Hospitality & Leisure',
'accomodation' => 'Accommodation', 'accommodation' => 'Accommodation',
];
/** CTypes that never become content elements. */
private const SKIP_CTYPES = [
'shortcut', 'div', 'fluxbs5templates_buttonlink', 'mask_web__jumpmenu',
'mask_web__markets_card', 'mask_web__success_story__full_width_promo',
'mask_web__success_top_story', 'mask_use_case', 'header',
];
private const CONTAINER_CTYPES = ['fluxbs5templates_fluidrow', 'fluxbs5templates_container'];
private array $export = [];
private array $pagesByUid = [];
private array $contentByUid = [];
private array $childrenByParent = []; // parentCeUid => list of child CEs
private array $rootsByPid = []; // pageUid => list of colPos-0/5 CEs
private array $cardByPageUid = [];
private array $useCaseByKey = [];
private array $fileRefs = []; // "table:uid:field" => refs[]
private array $catTitleByUid = [];
private array $catsByCeUid = [];
private array $marketUidByTitle = [];
private array $fileUidCache = [];
private string $collectionTable = '';
protected function configure(): void
{
$this->addOption('file', null, InputOption::VALUE_REQUIRED, 'Path to success_stories_export.json', 'var/transient/success_stories_export.json');
$this->addOption('pid', null, InputOption::VALUE_REQUIRED, 'Storage pid for new usecase records (default: pid of existing records)');
$this->addOption('only', null, InputOption::VALUE_REQUIRED, 'Import only the story whose slug tail matches');
$this->addOption('dry-run', null, InputOption::VALUE_NONE, 'Analyse and report only, write nothing');
$this->addOption('force', null, InputOption::VALUE_NONE, 'Delete + recreate stories that already exist (matched by slug)');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
Bootstrap::initializeBackendAuthentication();
$file = (string)$input->getOption('file');
if (!is_file($file)) {
$output->writeln("<error>Export file not found: $file</error>");
return Command::FAILURE;
}
$this->export = json_decode((string)file_get_contents($file), true) ?? [];
if (!isset($this->export['pages'])) {
$output->writeln('<error>Export JSON malformed (no pages key)</error>');
return Command::FAILURE;
}
$dryRun = (bool)$input->getOption('dry-run');
$force = (bool)$input->getOption('force');
$only = (string)($input->getOption('only') ?? '');
$this->buildIndexes();
$storagePid = $this->resolveStoragePid($input->getOption('pid'));
if ($storagePid <= 0) {
$output->writeln('<error>No storage pid — pass --pid or create one usecase record manually first.</error>');
return Command::FAILURE;
}
$this->collectionTable = (string)($GLOBALS['TCA']['tt_content']['columns']['vitec_items']['config']['foreign_table'] ?? '');
$rootUids = array_map(static fn(array $r): int => (int)$r['uid'], $this->export['storyRoots']);
$stories = array_values(array_filter(
$this->export['pages'],
static fn(array $p): bool => (int)($p['sys_language_uid'] ?? 0) === 0 && in_array((int)$p['pid'], $rootUids, true)
));
$output->writeln(sprintf(
'<info>%d stories in export · storage pid %d · %s</info>',
count($stories),
$storagePid,
$dryRun ? 'DRY-RUN' : 'LIVE'
));
if (!$dryRun) {
$this->ensureMarkets($output, $storagePid);
}
$summary = [];
foreach ($stories as $page) {
$slugTail = basename((string)$page['slug']);
if ($only !== '' && $slugTail !== $only) {
continue;
}
try {
$summary[] = $this->importStory($page, $storagePid, $dryRun, $force, $output);
} catch (\Throwable $e) {
$summary[] = [$slugTail, 'ERROR: ' . $e->getMessage(), 0, 0, 0];
$output->writeln("<error> $slugTail: {$e->getMessage()}</error>");
}
}
$output->writeln('');
$output->writeln(str_pad('story', 55) . str_pad('action', 12) . str_pad('CEs', 5) . str_pad('imgs', 6) . 'markets');
foreach ($summary as [$slug, $action, $ces, $imgs, $mk]) {
$output->writeln(str_pad(substr($slug, 0, 53), 55) . str_pad($action, 12) . str_pad((string)$ces, 5) . str_pad((string)$imgs, 6) . $mk);
}
return Command::SUCCESS;
}
// =========================================================== index building
private function buildIndexes(): void
{
foreach ($this->export['pages'] as $p) {
$this->pagesByUid[(int)$p['uid']] = $p;
}
$allCes = array_merge($this->export['content'], $this->export['storyElementsElsewhere']);
foreach ($allCes as $c) {
$this->contentByUid[(int)$c['uid']] = $c;
}
// flux tree: colPos >= 100 encodes parentUid*100 + column
foreach ($this->export['content'] as $c) {
$colPos = (int)($c['colPos'] ?? 0);
if ($colPos >= 100 && isset($this->contentByUid[intdiv($colPos, 100)])) {
$this->childrenByParent[intdiv($colPos, 100)][] = $c;
} else {
$this->rootsByPid[(int)$c['pid']][] = $c;
}
}
foreach ($this->childrenByParent as &$list) {
usort($list, static fn($a, $b) => [($a['colPos'] % 100), $a['sorting']] <=> [($b['colPos'] % 100), $b['sorting']]);
}
unset($list);
foreach ($this->rootsByPid as &$list) {
usort($list, static fn($a, $b) => $a['sorting'] <=> $b['sorting']);
}
unset($list);
// story card by target page uid (tx_mask_web__markets_card_link = "427" | "t3://page?uid=427")
foreach ($allCes as $c) {
if (($c['CType'] ?? '') !== 'mask_web__markets_card') {
continue;
}
$link = (string)($c['tx_mask_web__markets_card_link'] ?? '');
if (preg_match('/uid=(\d+)/', $link, $m)) {
$target = (int)$m[1];
} elseif (ctype_digit(trim($link))) {
$target = (int)trim($link);
} else {
continue;
}
// prefer the newest non-hidden card per target
$known = $this->cardByPageUid[$target] ?? null;
if ($known === null || ((int)$c['tstamp'] > (int)$known['tstamp'] && empty($c['hidden']))) {
$this->cardByPageUid[$target] = $c;
}
}
// use_case (APP) elements by normalised title
foreach ($allCes as $c) {
if (($c['CType'] ?? '') !== 'mask_use_case') {
continue;
}
foreach ([(string)($c['header'] ?? ''), (string)($c['tx_mask_tile_customer'] ?? '')] as $t) {
$k = $this->normTitle($t);
if ($k !== '' && !isset($this->useCaseByKey[$k])) {
$this->useCaseByKey[$k] = $c;
}
}
}
foreach ($this->export['fileReferences'] as $r) {
if (!empty($r['hidden'])) {
continue;
}
$this->fileRefs[$r['tablenames'] . ':' . $r['uid_foreign'] . ':' . $r['fieldname']][] = $r;
}
foreach ($this->export['categories'] as $c) {
$this->catTitleByUid[(int)$c['uid']] = (string)$c['title'];
}
foreach ($this->export['categoryAssignments'] as $a) {
if ($a['tablenames'] === 'tt_content') {
$this->catsByCeUid[(int)$a['uid_foreign']][] = $this->catTitleByUid[(int)$a['category_uid']] ?? '';
}
}
}
/** Resolve a content-blocks column name: prefixed variant if it exists in TCA. */
private function cbCol(string $table, string $name): string
{
return isset($GLOBALS['TCA'][$table]['columns']['vitec_' . $name]) ? 'vitec_' . $name : $name;
}
private function normTitle(string $t): string
{
return preg_replace('/[^a-z0-9]/', '', strtolower($t)) ?? '';
}
private function refs(string $table, int $uid, string $field): array
{
return $this->fileRefs[$table . ':' . $uid . ':' . $field] ?? [];
}
// ============================================================== market setup
private function resolveStoragePid(mixed $option): int
{
if ($option !== null && (int)$option > 0) {
return (int)$option;
}
$row = GeneralUtility::makeInstance(ConnectionPool::class)
->getConnectionForTable(self::TABLE)
->select(['pid'], self::TABLE, ['deleted' => 0], [], [], 1)
->fetchAssociative();
return (int)($row['pid'] ?? 0);
}
/** Ensure one market record per canonical industry; fill title->uid map. */
private function ensureMarkets(OutputInterface $output, int $fallbackPid): void
{
$conn = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('tx_vitec_domain_model_market');
$rows = $conn->select(['uid', 'title', 'pid'], 'tx_vitec_domain_model_market', ['deleted' => 0])->fetchAllAssociative();
$marketPid = $fallbackPid;
foreach ($rows as $r) {
$this->marketUidByTitle[$this->normTitle((string)$r['title'])] = (int)$r['uid'];
$marketPid = (int)$r['pid'];
}
$canonical = array_unique(array_values(self::INDUSTRY_TOKENS));
$datamap = [];
foreach ($canonical as $i => $title) {
if (!isset($this->marketUidByTitle[$this->normTitle($title)])) {
$datamap['tx_vitec_domain_model_market']['NEWMARKET' . $i] = ['pid' => $marketPid, 'title' => $title];
}
}
if ($datamap !== []) {
$dh = GeneralUtility::makeInstance(DataHandler::class);
$dh->start($datamap, []);
$dh->process_datamap();
foreach ($datamap['tx_vitec_domain_model_market'] as $key => $fields) {
$uid = (int)($dh->substNEWwithIDs[$key] ?? 0);
if ($uid > 0) {
$this->marketUidByTitle[$this->normTitle($fields['title'])] = $uid;
$output->writeln(" <info>created market: {$fields['title']} ($uid)</info>");
}
}
}
}
/** @return string[] canonical industry titles for one story */
private function industriesForStory(array $page, ?array $card, ?array $useCase): array
{
$out = [];
if ($useCase !== null) {
foreach ($this->catsByCeUid[(int)$useCase['uid']] ?? [] as $t) {
if ($t !== '') {
$out[] = $t;
}
}
}
if ($out === [] && $card !== null) {
$tokens = preg_split('/\s+/', strtolower((string)($card['tx_mask_markets_card_market2'] ?? ''))) ?: [];
foreach ($tokens as $tok) {
if (isset(self::INDUSTRY_TOKENS[$tok])) {
$out[] = self::INDUSTRY_TOKENS[$tok];
}
}
}
return array_values(array_unique($out));
}
// =============================================================== story import
/** @return array{0:string,1:string,2:int,3:int,4:string} */
private function importStory(array $page, int $storagePid, bool $dryRun, bool $force, OutputInterface $output): array
{
$pageUid = (int)$page['uid'];
$slugTail = basename((string)$page['slug']);
$slug = '/' . $slugTail;
$card = $this->cardByPageUid[$pageUid] ?? null;
$useCase = $this->useCaseByKey[$this->normTitle((string)$page['title'])] ?? null;
$promo = null;
foreach ($this->rootsByPid[$pageUid] ?? [] as $ce) {
if ($ce['CType'] === 'mask_web__success_story__full_width_promo') {
$promo = $ce;
break;
}
}
$industries = $this->industriesForStory($page, $card, $useCase);
$ceSpecs = $this->transformContent($pageUid);
// ---- existing?
$conn = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable(self::TABLE);
$existing = $conn->select(['uid'], self::TABLE, ['slug' => $slug, 'deleted' => 0])->fetchAssociative()
?: $conn->select(['uid'], self::TABLE, ['slug' => $slugTail, 'deleted' => 0])->fetchAssociative();
$imgCount = 0;
foreach ($ceSpecs as $s) {
$imgCount += count($s['files'] ?? []);
foreach ($s['items'] ?? [] as $it) {
$imgCount += count($it['files'] ?? []);
}
}
if ($dryRun) {
$output->writeln(sprintf(
' <comment>%s</comment> ces=%d quotes=%d cols=%d imgs=%d markets=[%s] card=%s uc=%s hero=%s%s',
$slugTail,
count($ceSpecs),
count(array_filter($ceSpecs, fn($s) => $s['CType'] === 'vitec_quotation')),
count(array_filter($ceSpecs, fn($s) => $s['CType'] === 'vitec_columns')),
$imgCount,
implode(',', $industries),
$card ? 'y' : 'MISSING',
$useCase ? 'y' : '-',
$promo ? 'y' : 'MISSING',
$existing ? ' EXISTS(uid=' . $existing['uid'] . ')' : ''
));
return [$slugTail, $existing ? 'dry(exists)' : 'dry', count($ceSpecs), $imgCount, implode(',', $industries)];
}
if ($existing) {
if (!$force) {
return [$slugTail, 'skipped', 0, 0, ''];
}
$dh = GeneralUtility::makeInstance(DataHandler::class);
$dh->start([], [self::TABLE => [(int)$existing['uid'] => ['delete' => 1]]]);
$dh->process_cmdmap();
}
// ---- assemble datamap
$datamap = [];
$storyKey = 'NEWSTORY1';
$refCounter = 0;
$newRef = function (string $identifier, string $table, string $key, string $field, array $meta = []) use (&$datamap, &$refCounter, $storagePid): ?string {
$fileUid = $this->fileUidFor($identifier);
if ($fileUid === null) {
return null;
}
$refKey = 'NEWREF' . (++$refCounter);
$datamap['sys_file_reference'][$refKey] = array_filter([
'pid' => $storagePid,
'uid_local' => 'sys_file_' . $fileUid,
'tablenames' => $table,
'uid_foreign' => $key,
'fieldname' => $field,
'title' => $meta['title'] ?? null,
'alternative' => $meta['alternative'] ?? null,
'description' => $meta['description'] ?? null,
'crop' => $meta['crop'] ?? null,
], static fn($v) => $v !== null && $v !== '');
return $refKey;
};
$attach = function (array $refs, string $table, string $key, string $newField) use ($newRef): string {
$ids = [];
foreach ($refs as $r) {
$id = $newRef((string)$r['identifier'], $table, $key, $newField, $r);
if ($id !== null) {
$ids[] = $id;
}
}
return implode(',', $ids);
};
// story base fields
$subtitle = trim(strip_tags((string)($card['tx_mask_web__markets_card_frontttext'] ?? '')));
if ($subtitle === '' && $useCase !== null) {
$subtitle = (string)($useCase['tx_mask_tile_teaser'] ?? '');
}
$textColor = strtolower((string)($promo['tx_mask_web__success_story__full_width_promo_textcolor'] ?? ''));
$overlay = (string)($promo['tx_mask_web__success_story__full_width_promo_color'] ?? '');
$story = [
'pid' => $storagePid,
'title' => (string)$page['title'],
'slug' => $slug,
'subtitle' => $subtitle,
'teaser' => (string)($page['abstract'] ?? '') !== '' ? (string)$page['abstract'] : (string)($page['description'] ?? ''),
'seo_title' => (string)($page['seo_title'] ?? ''),
'seo_description' => (string)($page['description'] ?? ''),
'og_title' => (string)($page['og_title'] ?? ''),
'og_description' => (string)($page['og_description'] ?? ''),
'twitter_title' => (string)($page['twitter_title'] ?? ''),
'twitter_description' => (string)($page['twitter_description'] ?? ''),
'no_index' => (int)($page['no_index'] ?? 0),
'no_follow' => (int)($page['no_follow'] ?? 0),
'hero_overlay_color' => $overlay,
'hero_overlay_opacity' => $overlay !== '' ? 0.4 : 0,
'text_theme' => in_array($textColor, ['#fff', '#ffffff', 'white'], true) ? 'light' : 'dark',
'markets' => implode(',', array_filter(array_map(
fn(string $t): int => $this->marketUidByTitle[$this->normTitle($t)] ?? 0,
$industries
))),
];
// FAL: card image, logo, hero, og/twitter
if ($card !== null) {
$story['card_image'] = $attach(
array_slice($this->refs('tt_content', (int)$card['uid'], 'tx_mask_web__markets_card_frontimage'), 0, 1),
self::TABLE, $storyKey, 'card_image'
);
$story['customer_logo'] = $attach(
array_slice($this->refs('tt_content', (int)$card['uid'], 'tx_mask_web__markets_card_customerlogo'), 0, 1),
self::TABLE, $storyKey, 'customer_logo'
);
}
$heroRefs = $promo !== null
? $this->refs('tt_content', (int)$promo['uid'], 'tx_mask_web__success_story__full_width_promo_image')
: [];
if ($heroRefs === []) {
$heroRefs = $this->refs('pages', $pageUid, 'heroimage_big');
}
$story['hero_bgimage'] = $attach(array_slice($heroRefs, 0, 1), self::TABLE, $storyKey, 'hero_bgimage');
$story['og_image'] = $attach(array_slice($this->refs('pages', $pageUid, 'og_image'), 0, 1), self::TABLE, $storyKey, 'og_image');
$story['twitter_image'] = $attach(array_slice($this->refs('pages', $pageUid, 'twitter_image'), 0, 1), self::TABLE, $storyKey, 'twitter_image');
// content elements
$ceKeys = [];
$ceCounter = 0;
$itemCounter = 0;
foreach ($ceSpecs as $spec) {
$ceKey = 'NEWCE' . (++$ceCounter);
$fields = $spec['fields'];
$fields['pid'] = $storagePid;
$fields['CType'] = $spec['CType'];
$fields['colPos'] = 999;
foreach ($spec['files'] ?? [] as $f) {
$fields[$f['field']] = $attach($f['refs'], 'tt_content', $ceKey, $f['field']);
}
if (($spec['CType'] === 'vitec_columns') && $this->collectionTable !== '' && !empty($spec['items'])) {
$itemKeys = [];
foreach ($spec['items'] as $item) {
$itemKey = 'NEWITEM' . (++$itemCounter);
$itemFields = $item['fields'];
$itemFields['pid'] = $storagePid;
foreach ($item['files'] ?? [] as $f) {
$itemFields[$f['field']] = $attach($f['refs'], $this->collectionTable, $itemKey, $f['field']);
}
$datamap[$this->collectionTable][$itemKey] = $itemFields;
$itemKeys[] = $itemKey;
}
$fields[$this->cbCol('tt_content', 'items')] = implode(',', $itemKeys);
}
$datamap['tt_content'][$ceKey] = $fields;
$ceKeys[] = $ceKey;
}
$story['content_elements'] = implode(',', $ceKeys);
$datamap[self::TABLE][$storyKey] = $story;
$dh = GeneralUtility::makeInstance(DataHandler::class);
$dh->start($datamap, []);
$dh->process_datamap();
if ($dh->errorLog !== []) {
throw new \RuntimeException('DataHandler: ' . implode(' | ', array_slice($dh->errorLog, 0, 3)));
}
return [$slugTail, $existing ? 'reimported' : 'created', count($ceSpecs), $imgCount, implode(',', $industries)];
}
// ========================================================== content transform
/** @return array<int,array<string,mixed>> ordered CE specs */
private function transformContent(int $pageUid): array
{
$specs = [];
$roots = $this->rootsByPid[$pageUid] ?? [];
// intro (colPos 5): hero subline — first element, CTA buttons stripped
foreach ($roots as $ce) {
if ((int)$ce['colPos'] === 5 && $ce['CType'] === 'text' && !empty($ce['bodytext'])) {
$body = preg_replace('/<p[^>]*>(?:(?!<\/p>).)*btn(?:(?!<\/p>).)*<\/p>/is', '', (string)$ce['bodytext']) ?? '';
if (trim(strip_tags($body)) !== '') {
$specs[] = ['CType' => 'text', 'fields' => ['header' => '', 'bodytext' => trim($body)], 'files' => []];
}
}
}
foreach ($roots as $ce) {
if ((int)$ce['colPos'] === 0) {
$this->transformCe($ce, $specs);
}
}
return $specs;
}
private function transformCe(array $ce, array &$specs): void
{
$ctype = (string)$ce['CType'];
if (in_array($ctype, self::SKIP_CTYPES, true)) {
return;
}
if (in_array($ctype, self::CONTAINER_CTYPES, true)) {
$this->transformContainer($ce, $specs);
return;
}
switch ($ctype) {
case 'text':
$specs[] = $this->textOrQuoteSpec($ce);
break;
case 'image':
case 'textmedia':
$field = $ctype === 'image' ? 'image' : 'assets';
$spec = [
'CType' => $ctype,
'fields' => array_filter([
'header' => (string)($ce['header'] ?? ''),
'header_layout' => (int)($ce['header_layout'] ?? 0),
'bodytext' => (string)($ce['bodytext'] ?? ''),
], static fn($v) => $v !== '' && $v !== 0),
'files' => [],
];
$refs = $this->refs('tt_content', (int)$ce['uid'], $field);
if ($refs !== []) {
$spec['files'][] = ['field' => $field, 'refs' => $refs];
}
if ($spec['files'] !== [] || ($spec['fields']['bodytext'] ?? '') !== '') {
$specs[] = $spec;
}
break;
case 'html':
if (!empty($ce['bodytext'])) {
$specs[] = ['CType' => 'html', 'fields' => ['bodytext' => (string)$ce['bodytext']], 'files' => []];
}
break;
default:
// unknown → keep text-ish content if any
if (!empty($ce['bodytext']) || !empty($ce['header'])) {
$specs[] = ['CType' => 'text', 'fields' => [
'header' => (string)($ce['header'] ?? ''),
'bodytext' => (string)($ce['bodytext'] ?? ''),
], 'files' => []];
}
}
}
/** Two clean columns -> vitec_columns; everything else flattens in order. */
private function transformContainer(array $ce, array &$specs): void
{
$children = $this->childrenByParent[(int)$ce['uid']] ?? [];
if ($children === []) {
return;
}
$byCol = [];
foreach ($children as $child) {
$byCol[(int)$child['colPos'] % 100][] = $child;
}
ksort($byCol);
$isSimple = static fn(array $c): bool => in_array($c['CType'], ['text', 'image'], true)
&& stripos((string)($c['bodytext'] ?? ''), '<blockquote') === false;
$cols = array_keys($byCol);
$allSimple = count($cols) === 2 && count($children) <= 4
&& array_reduce($children, static fn($ok, $c) => $ok && $isSimple($c), true);
if ($allSimple) {
$items = [];
foreach ($byCol as $colIdx => $colCes) {
$side = $colIdx === $cols[0] ? 'left' : 'right';
foreach ($colCes as $c) {
$t = $this->collectionTable;
if ($c['CType'] === 'text') {
$items[] = ['fields' => [
$this->cbCol($t, 'column') => $side, $this->cbCol($t, 'item_type') => 'text',
$this->cbCol($t, 'headline') => (string)($c['header'] ?? ''),
$this->cbCol($t, 'text') => (string)($c['bodytext'] ?? ''),
], 'files' => []];
} else {
$refs = $this->refs('tt_content', (int)$c['uid'], 'image');
$items[] = ['fields' => [
$this->cbCol($t, 'column') => $side, $this->cbCol($t, 'item_type') => 'image',
$this->cbCol($t, 'headline') => (string)($c['header'] ?? ''),
], 'files' => $refs !== [] ? [['field' => $this->cbCol($t, 'image'), 'refs' => array_slice($refs, 0, 1)]] : []];
}
}
}
$specs[] = [
'CType' => 'vitec_columns',
'fields' => ['header' => (string)($ce['header'] ?? ''), $this->cbCol('tt_content', 'layout') => 'cols_50_50'],
'files' => [],
'items' => $items,
];
return;
}
// flatten: children first; the container header only becomes a heading
// when the container actually contributed content (skips e.g. the old
// "Related Success Stories" section whose children are all shortcuts).
$childSpecs = [];
foreach ($byCol as $colCes) {
foreach ($colCes as $child) {
$this->transformCe($child, $childSpecs);
}
}
$header = trim((string)($ce['header'] ?? ''));
if ($childSpecs !== [] && $header !== '' && !str_starts_with($header, '///')) {
$specs[] = ['CType' => 'text', 'fields' => ['header' => $header], 'files' => []];
}
foreach ($childSpecs as $cs) {
$specs[] = $cs;
}
}
/** blockquote text CEs become vitec_quotation; the rest stay text. */
private function textOrQuoteSpec(array $ce): array
{
$body = (string)($ce['bodytext'] ?? '');
$header = (string)($ce['header'] ?? '');
if (stripos($body, '<blockquote') !== false) {
$quote = $body;
$name = '';
$position = '';
if (preg_match('/<blockquote[^>]*>(.*)<\/blockquote>/is', $body, $m)) {
$inner = $m[1];
// attribution paragraph: <p><strong>— Name</strong><br/>Position</p>
if (preg_match('/<p>\s*<strong>\s*(?:—|&mdash;|-)?\s*(.*?)<\/strong>\s*(?:<br\s*\/?>)?\s*(.*?)<\/p>\s*$/is', $inner, $a)) {
$name = trim(strip_tags($a[1]));
$position = trim(strip_tags($a[2]));
$inner = (string)preg_replace('/<p>\s*<strong>\s*(?:—|&mdash;|-)?.*?<\/p>\s*$/is', '', $inner);
}
$quote = trim(strip_tags($inner));
$quote = trim($quote, "“”\"' \u{201C}\u{201D}");
}
return ['CType' => 'vitec_quotation', 'fields' => [
$this->cbCol('tt_content', 'quote') => $quote,
$this->cbCol('tt_content', 'quote_name') => $name,
$this->cbCol('tt_content', 'quote_position') => $position,
], 'files' => []];
}
return ['CType' => 'text', 'fields' => array_filter([
'header' => strcasecmp($header, 'Quote') === 0 ? '' : $header,
'header_layout' => (int)($ce['header_layout'] ?? 0),
'bodytext' => $body,
], static fn($v) => $v !== '' && $v !== 0), 'files' => []];
}
// ==================================================================== files
/** Old-site identifier -> new-site sys_file uid (staged under FILES_BASE). */
private function fileUidFor(string $identifier): ?int
{
if (isset($this->fileUidCache[$identifier])) {
return $this->fileUidCache[$identifier] ?: null;
}
try {
$file = GeneralUtility::makeInstance(ResourceFactory::class)
->getFileObjectFromCombinedIdentifier('1:' . self::FILES_BASE . $identifier);
$uid = $file !== null ? (int)$file->getUid() : 0;
} catch (\Throwable $e) {
$uid = 0;
}
$this->fileUidCache[$identifier] = $uid;
return $uid ?: null;
}
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Evomedien\Vitec\Controller;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
/**
* CustomerController — Extbase registration target for the Customerlogos
* plugin. In headless mode the JSON output is produced by
* CustomerlogosJsonRenderer, not by this controller.
*/
class CustomerController extends ActionController
{
public function listAction(): ResponseInterface
{
return $this->htmlResponse();
}
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Evomedien\Vitec\Controller;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
/**
* LocationController — Extbase registration target for the Locationlist
* plugin. In headless mode the JSON output is produced by
* LocationsJsonRenderer, not by this controller.
*/
class LocationController extends ActionController
{
public function listAction(): ResponseInterface
{
return $this->htmlResponse();
}
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Evomedien\Vitec\Controller;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
/**
* ModelcardController — Extbase registration target for the VITEC Card
* plugin. In headless mode the JSON output is produced by
* ModelcardJsonRenderer, not by this controller.
*/
class ModelcardController extends ActionController
{
public function listAction(): ResponseInterface
{
return $this->htmlResponse();
}
}

View File

@@ -13,6 +13,9 @@ use Evomedien\Vitec\UserFunc\DownloadcardJsonRenderer;
use Evomedien\Vitec\UserFunc\DownloadcardcollectionJsonRenderer;
use Evomedien\Vitec\UserFunc\DatasheetsJsonRenderer;
use Evomedien\Vitec\UserFunc\EventlistJsonRenderer;
use Evomedien\Vitec\UserFunc\LocationsJsonRenderer;
use Evomedien\Vitec\UserFunc\CustomerlogosJsonRenderer;
use Evomedien\Vitec\UserFunc\ModelcardJsonRenderer;
use Evomedien\Vitec\UserFunc\NewsJsonRenderer;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
@@ -111,6 +114,9 @@ final class ContainerChildrenProcessor implements DataProcessorInterface
'vitec_downloadcardcollection' => [DownloadcardcollectionJsonRenderer::class, 'downloadcardcollection'],
'vitec_datasheets' => [DatasheetsJsonRenderer::class, 'datasheets'],
'vitec_eventlist' => [EventlistJsonRenderer::class, 'eventlist'],
'vitec_locationlist' => [LocationsJsonRenderer::class, 'locations'],
'vitec_customerlogos' => [CustomerlogosJsonRenderer::class, 'customerlogos'],
'vitec_modelcard' => [ModelcardJsonRenderer::class, 'card'],
'news_pi1' => [NewsJsonRenderer::class, 'news'],
'news_newsliststicky' => [NewsJsonRenderer::class, 'news'],
'news_newsselectedlist' => [NewsJsonRenderer::class, 'news'],

View File

@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
namespace Evomedien\Vitec\Middleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Pretty detail URLs for Success Stories.
*
* Public URL: /success-stories/<slug> (old-site SEO URLs)
* Real page: /success-stories/story/<slug> (subpage holding ONLY the
* usecaseshow plugin)
*
* TYPO3's page router always resolves /success-stories/<slug> to the LIST
* page (longest page-slug prefix), so the detail subpage could never answer
* that URL. This middleware rewrites the request path internally — before
* page resolution — whenever <slug> matches an existing (visible) success
* story. Real subpages of /success-stories are unaffected: no story match,
* no rewrite. The browser URL never changes.
*
* Exception-safe: any failure leaves the request untouched.
*/
final class SuccessStoryPathRewrite implements MiddlewareInterface
{
private const LIST_PATH = '/success-stories';
private const DETAIL_SEGMENT = 'story';
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$debug = 'no-match';
try {
$path = $request->getUri()->getPath();
if (preg_match('#^' . self::LIST_PATH . '/([a-zA-Z0-9\-]+)/?$#', $path, $m) === 1
&& $m[1] !== self::DETAIL_SEGMENT
) {
if ($this->isStorySlug($m[1])) {
$uri = $request->getUri()->withPath(
self::LIST_PATH . '/' . self::DETAIL_SEGMENT . '/' . $m[1]
);
$request = $request->withUri($uri);
$debug = 'rewritten';
} else {
$debug = 'slug-not-found:' . $m[1];
}
}
} catch (\Throwable $e) {
$debug = 'exception:' . substr($e->getMessage(), 0, 60);
}
// TEMP-DIAG header (remove after verification)
return $handler->handle($request)->withHeader('X-Vitec-Story-Rewrite', $debug);
}
private function isStorySlug(string $slug): bool
{
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_vitec_domain_model_usecase');
$row = $qb
->select('uid')
->from('tx_vitec_domain_model_usecase')
->where(
$qb->expr()->eq('slug', $qb->createNamedParameter($slug)),
$qb->expr()->eq('deleted', 0),
$qb->expr()->eq('hidden', 0)
)
->setMaxResults(1)
->executeQuery()
->fetchAssociative();
return $row !== false;
}
}

View File

@@ -0,0 +1,127 @@
<?php
declare(strict_types=1);
namespace Evomedien\Vitec\Preview;
use Doctrine\DBAL\ParameterType;
use TYPO3\CMS\Backend\Preview\StandardContentPreviewRenderer;
use TYPO3\CMS\Backend\View\BackendLayout\Grid\GridColumnItem;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Service\FlexFormService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Fluid\View\StandaloneView;
/**
* Page-module preview for the VITEC Card plugin (vitec_modelcard):
* type badge, selected record title, thumbnail and layout badge.
* Registered via TCA types.vitec_modelcard.previewRenderer.
*/
class ModelcardPreviewRenderer extends StandardContentPreviewRenderer
{
private const MODEL_TABLES = [
'product' => 'tx_vitec_domain_model_product',
'story' => 'tx_vitec_domain_model_usecase',
'market' => 'tx_vitec_domain_model_market',
'solution' => 'tx_vitec_domain_model_solution',
];
private const TYPE_LABELS = [
'product' => 'Product',
'story' => 'Success Story',
'market' => 'Market',
'solution' => 'Solution',
];
/** Card-image FAL field per model type (first hit wins). */
private const IMAGE_FIELDS = [
'product' => ['image', 'productimage'],
'story' => ['card_image', 'hero_bgimage'],
'market' => ['image'],
'solution' => ['image'],
];
public function renderPageModulePreviewContent(GridColumnItem $item): string
{
try {
$record = $item->getRecord();
$flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
$flexFormData = $flexFormService->convertFlexFormContentToArray($record['pi_flexform'] ?? '');
$settings = $flexFormData['settings'] ?? [];
$modelType = (string)($settings['modelType'] ?? 'product');
$layout = (string)($settings['layout'] ?? 'vertical');
$recordUid = (int)($settings[$modelType === 'story' ? 'story' : $modelType] ?? 0);
$title = '';
$subtitle = '';
$imageUrl = '';
if ($recordUid > 0 && isset(self::MODEL_TABLES[$modelType])) {
$table = self::MODEL_TABLES[$modelType];
$qb = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
$row = $qb
->select('*')
->from($table)
->where(
$qb->expr()->eq('uid', $qb->createNamedParameter($recordUid, ParameterType::INTEGER)),
$qb->expr()->eq('deleted', 0)
)
->executeQuery()
->fetchAssociative();
if ($row) {
$title = (string)($row['title'] ?? '');
$subtitle = (string)($row['subtitle'] ?? ($row['teaser'] ?? ''));
$imageUrl = $this->firstImageUrl($table, $recordUid, self::IMAGE_FIELDS[$modelType]);
}
}
$view = GeneralUtility::makeInstance(StandaloneView::class);
$view->setTemplatePathAndFilename('EXT:vitec/Resources/Private/Templates/Preview/Modelcard.html');
$view->assignMultiple([
'typeLabel' => self::TYPE_LABELS[$modelType] ?? $modelType,
'layout' => $layout,
'recordUid' => $recordUid,
'title' => $title,
'subtitle' => $subtitle,
'imageUrl' => $imageUrl,
]);
return $view->render();
} catch (\Throwable $e) {
return parent::renderPageModulePreviewContent($item);
}
}
/** @param string[] $fieldNames */
private function firstImageUrl(string $table, int $uid, array $fieldNames): string
{
try {
$qb = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file_reference');
foreach ($fieldNames as $fieldName) {
$ref = $qb
->select('uid')
->from('sys_file_reference')
->where(
$qb->expr()->eq('tablenames', $qb->createNamedParameter($table, ParameterType::STRING)),
$qb->expr()->eq('fieldname', $qb->createNamedParameter($fieldName, ParameterType::STRING)),
$qb->expr()->eq('uid_foreign', $qb->createNamedParameter($uid, ParameterType::INTEGER)),
$qb->expr()->eq('deleted', 0),
$qb->expr()->eq('hidden', 0)
)
->setMaxResults(1)
->executeQuery()
->fetchAssociative();
if ($ref) {
$file = GeneralUtility::makeInstance(ResourceFactory::class)
->getFileReferenceObject((int)$ref['uid']);
return (string)$file->getPublicUrl();
}
}
} catch (\Throwable $e) {
// no thumb
}
return '';
}
}

View File

@@ -15,6 +15,9 @@ use Evomedien\Vitec\UserFunc\DownloadcardJsonRenderer;
use Evomedien\Vitec\UserFunc\DownloadcardcollectionJsonRenderer;
use Evomedien\Vitec\UserFunc\DatasheetsJsonRenderer;
use Evomedien\Vitec\UserFunc\EventlistJsonRenderer;
use Evomedien\Vitec\UserFunc\LocationsJsonRenderer;
use Evomedien\Vitec\UserFunc\CustomerlogosJsonRenderer;
use Evomedien\Vitec\UserFunc\ModelcardJsonRenderer;
use Evomedien\Vitec\UserFunc\NewsJsonRenderer;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -76,6 +79,9 @@ final class ContentElementResolver
'vitec_downloadcardcollection' => [DownloadcardcollectionJsonRenderer::class, 'downloadcardcollection'],
'vitec_datasheets' => [DatasheetsJsonRenderer::class, 'datasheets'],
'vitec_eventlist' => [EventlistJsonRenderer::class, 'eventlist'],
'vitec_locationlist' => [LocationsJsonRenderer::class, 'locations'],
'vitec_customerlogos' => [CustomerlogosJsonRenderer::class, 'customerlogos'],
'vitec_modelcard' => [ModelcardJsonRenderer::class, 'card'],
'news_pi1' => [NewsJsonRenderer::class, 'news'],
'news_newsliststicky' => [NewsJsonRenderer::class, 'news'],
'news_newsselectedlist' => [NewsJsonRenderer::class, 'news'],

View File

@@ -45,7 +45,7 @@ final class UsecaseSerializer
'teaser' => (string)($u['teaser'] ?? ''),
'cardImage' => $this->image($uid, 'card_image'),
'customerLogo' => $this->image($uid, 'customer_logo'),
'market' => $this->market((int)($u['market'] ?? 0)),
'markets' => $this->relationMulti('tx_vitec_usecase_market_mm', 'tx_vitec_domain_model_market', $uid),
'categories' => $this->categories($uid),
'featured' => (bool)($u['featured'] ?? false),
'layoutVariant' => (string)($u['layout_variant'] ?? 'standard'),
@@ -75,7 +75,7 @@ final class UsecaseSerializer
'contentElements' => $this->contentElements($uid),
'related' => [
'show' => (bool)($u['show_related'] ?? true),
'market' => $base['market'],
'markets' => $base['markets'],
'solutions' => $this->relationMulti('tx_vitec_usecase_solution_mm', 'tx_vitec_domain_model_solution', $uid),
'products' => $this->relationMulti('tx_vitec_usecase_product_mm', 'tx_vitec_domain_model_product', $uid),
'categories' => $base['categories'],
@@ -129,6 +129,20 @@ final class UsecaseSerializer
$imageService = GeneralUtility::makeInstance(ImageService::class);
$fileReference = $resourceFactory->getFileReferenceObject((int)$ref['uid']);
if (str_contains((string)$fileReference->getMimeType(), 'svg')) {
// SVG: no processing/srcset — deliver the file as-is.
return [
'uid' => (int)$ref['uid'],
'url' => (string)$fileReference->getPublicUrl(),
'title' => (string)($ref['title'] ?? ''),
'alternative' => (string)($ref['alternative'] ?? ''),
'description' => (string)($ref['description'] ?? ''),
'srcset' => [],
'properties' => ['mimeType' => $fileReference->getMimeType()],
];
}
$srcset = [];
foreach (self::IMAGE_WIDTHS as $width) {
$variant = $imageService->applyProcessingInstructions(
@@ -165,6 +179,114 @@ final class UsecaseSerializer
}
}
/**
* All FAL images of one field (multi-reference), same shape as image().
*
* @return array<int,array<string,mixed>>
*/
public function images(int $recordUid, string $fieldName, ?string $table = null): array
{
try {
$table = $table ?? self::TABLE;
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference');
$refs = $qb
->select('sfr.uid')
->from('sys_file_reference', 'sfr')
->where(
$qb->expr()->eq('sfr.tablenames', $qb->createNamedParameter($table, ParameterType::STRING)),
$qb->expr()->eq('sfr.fieldname', $qb->createNamedParameter($fieldName, ParameterType::STRING)),
$qb->expr()->eq('sfr.uid_foreign', $qb->createNamedParameter($recordUid, ParameterType::INTEGER)),
$qb->expr()->eq('sfr.deleted', 0),
$qb->expr()->eq('sfr.hidden', 0)
)
->orderBy('sfr.sorting_foreign')
->executeQuery()
->fetchAllAssociative();
$out = [];
foreach ($refs as $i => $r) {
// reuse the single-image resolver per reference position
$img = $this->imageByReferenceUid((int)$r['uid']);
if ($img !== null) {
$out[] = $img;
}
}
return $out;
} catch (\Throwable $e) {
return [];
}
}
/**
* Resolve one sys_file_reference uid to the standard image shape.
*
* @return array<string,mixed>|null
*/
private function imageByReferenceUid(int $refUid): ?array
{
try {
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference');
$ref = $qb
->select('uid', 'title', 'description', 'alternative', 'crop')
->from('sys_file_reference')
->where($qb->expr()->eq('uid', $qb->createNamedParameter($refUid, ParameterType::INTEGER)))
->executeQuery()
->fetchAssociative();
if (!$ref) {
return null;
}
$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
$imageService = GeneralUtility::makeInstance(ImageService::class);
$fileReference = $resourceFactory->getFileReferenceObject((int)$ref['uid']);
if (str_contains((string)$fileReference->getMimeType(), 'svg')) {
// SVG: no processing/srcset — deliver the file as-is.
return [
'uid' => (int)$ref['uid'],
'url' => (string)$fileReference->getPublicUrl(),
'title' => (string)($ref['title'] ?? ''),
'alternative' => (string)($ref['alternative'] ?? ''),
'description' => (string)($ref['description'] ?? ''),
'srcset' => [],
'properties' => ['mimeType' => $fileReference->getMimeType()],
];
}
$srcset = [];
foreach (self::IMAGE_WIDTHS as $width) {
$variant = $imageService->applyProcessingInstructions(
$fileReference,
['width' => $width, 'crop' => $ref['crop'] ?? null]
);
$srcset[] = ['url' => $imageService->getImageUri($variant), 'width' => $width, 'descriptor' => $width . 'w'];
}
$default = $imageService->applyProcessingInstructions(
$fileReference,
['width' => 800, 'crop' => $ref['crop'] ?? null]
);
return [
'uid' => (int)$ref['uid'],
'url' => $imageService->getImageUri($default),
'title' => (string)($ref['title'] ?? ''),
'alternative' => (string)($ref['alternative'] ?? ''),
'description' => (string)($ref['description'] ?? ''),
'srcset' => $srcset,
'properties' => [
'width' => $fileReference->getProperty('width'),
'height' => $fileReference->getProperty('height'),
'mimeType' => $fileReference->getProperty('mime_type'),
],
];
} catch (\Throwable $e) {
return null;
}
}
/**
* FAL video (public URL only). $table as in image().
*
@@ -367,10 +489,39 @@ final class UsecaseSerializer
*/
private function resolveInlineElement(array $row): ?array
{
if ((string)($row['CType'] ?? '') === self::COLUMNS_CTYPE) {
$ctype = (string)($row['CType'] ?? '');
if ($ctype === self::COLUMNS_CTYPE) {
return $this->resolveColumnsElement($row);
}
return ContentElementResolver::normaliseRecord($row);
if ($ctype === 'vitec_quotation') {
$uid = (int)$row['uid'];
$logoField = array_key_exists('vitec_quote_logo', $row) ? 'vitec_quote_logo' : 'quote_logo';
return [
'id' => $uid,
'type' => 'vitec_quotation',
'colPos' => (int)($row['colPos'] ?? 0),
'sorting' => (int)($row['sorting'] ?? 0),
'header' => (string)($row['header'] ?? ''),
'quote' => (string)($row['vitec_quote'] ?? $row['quote'] ?? ''),
'name' => (string)($row['vitec_quote_name'] ?? $row['quote_name'] ?? ''),
'position' => (string)($row['vitec_quote_position'] ?? $row['quote_position'] ?? ''),
'logo' => $this->image($uid, $logoField, 'tt_content'),
];
}
$el = ContentElementResolver::normaliseRecord($row);
if (is_array($el)) {
$uid = (int)$row['uid'];
$media = [];
foreach (['image', 'assets', 'media'] as $f) {
foreach ($this->images($uid, $f, 'tt_content') as $img) {
$media[] = $img;
}
}
if ($media !== []) {
$el['images'] = $media;
}
}
return $el;
}
/**
@@ -445,15 +596,12 @@ final class UsecaseSerializer
$uid = (int)$it['uid'];
$get = static fn (string $k): string => (string)($it['vitec_' . $k] ?? $it[$k] ?? '');
$imageField = array_key_exists('vitec_image', $it) ? 'vitec_image' : 'image';
$videoField = array_key_exists('vitec_video', $it) ? 'vitec_video' : 'video';
return [
'type' => $get('item_type') !== '' ? $get('item_type') : 'text',
'headline' => $get('headline'),
'text' => $get('text'),
'image' => $this->image($uid, $imageField, $table),
'video' => $this->video($uid, $videoField, $table),
'image' => $this->image($uid, 'vitec_image', $table) ?? $this->image($uid, 'image', $table),
'video' => $this->video($uid, 'vitec_video', $table) ?? $this->video($uid, 'video', $table),
'link' => $get('link'),
'linkLabel' => $get('link_label'),
];

View File

@@ -0,0 +1,232 @@
<?php
declare(strict_types=1);
namespace Evomedien\Vitec\UserFunc;
use Doctrine\DBAL\ParameterType;
use TYPO3\CMS\Core\Attribute\AsAllowedCallable;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Service\FlexFormService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Service\ImageService;
/**
* UserFunc: render the VITEC customer logos as JSON (headless).
*
* Output under content.customerlogos:
* { "layout": "list|grid|carousel|marquee", "logos": [ … ] }
*
* Selection logic:
* - no customers selected -> ALL logos, each with color:false (b/w)
* - customers selected -> the selected ones FIRST (selection order,
* color:true), followed by all remaining logos (color:false)
*
* `render()` = top-level plugin / page discovery. `renderForRecord()` = one
* specific tt_content row (reused by ContainerChildrenProcessor for nested
* plugins). Exception-safe.
*/
class CustomerlogosJsonRenderer
{
private const TABLE = 'tx_vitec_domain_model_customer';
private const IMAGE_WIDTHS = [200, 400];
#[AsAllowedCallable]
public function render(string $content, array $conf): string
{
$row = is_array($this->cObj->data ?? null) ? $this->cObj->data : null;
if ($row && (string)($row['CType'] ?? '') === 'vitec_customerlogos') {
return $this->renderForRecord($row);
}
$pageId = 0;
$request = $GLOBALS['TYPO3_REQUEST'] ?? null;
if ($request !== null) {
$pageInfo = $request->getAttribute('frontend.page.information');
if ($pageInfo !== null) {
$pageId = (int)$pageInfo->getId();
}
}
if ($pageId <= 0) {
$pageId = (int)($GLOBALS['TSFE']->id ?? 0);
}
if ($pageId <= 0) {
return '';
}
$qb = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
$ces = $qb
->select('*')
->from('tt_content')
->where(
$qb->expr()->eq('pid', $qb->createNamedParameter($pageId, ParameterType::INTEGER)),
$qb->expr()->eq('CType', $qb->createNamedParameter('vitec_customerlogos', ParameterType::STRING)),
$qb->expr()->eq('deleted', 0),
$qb->expr()->eq('hidden', 0)
)
->executeQuery()
->fetchAllAssociative();
if (empty($ces)) {
return '';
}
return $this->renderForRecord($ces[0]);
}
/**
* @param array<string,mixed> $contentElement
*/
public function renderForRecord(array $contentElement): string
{
try {
$flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
$flexFormData = $flexFormService->convertFlexFormContentToArray($contentElement['pi_flexform'] ?? '');
$settings = $flexFormData['settings'] ?? [];
$layout = (string)($settings['layout'] ?? 'grid');
$debugMode = (bool)($settings['debug'] ?? false);
$selectedUids = GeneralUtility::intExplode(',', (string)($settings['customers'] ?? ''), true);
$onlySelected = (bool)($settings['onlySelected'] ?? false);
$qb = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable(self::TABLE);
$rows = $qb
->select('*')
->from(self::TABLE)
->where(
$qb->expr()->eq('deleted', 0),
$qb->expr()->eq('hidden', 0)
)
->orderBy('sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
$byUid = [];
foreach ($rows as $r) {
$byUid[(int)$r['uid']] = $r;
}
// Selected customers first (selection order, in color), then the rest (b/w).
$logos = [];
foreach ($selectedUids as $uid) {
if (isset($byUid[$uid])) {
$logos[] = $this->serializeCustomer($byUid[$uid], true);
unset($byUid[$uid]);
}
}
if (!($onlySelected && $selectedUids !== [])) {
foreach ($byUid as $r) {
$logos[] = $this->serializeCustomer($r, false);
}
}
$response = [
'layout' => $layout,
'logos' => $logos,
];
if ($debugMode) {
$response['debug'] = [
'count' => count($logos),
'selected' => $selectedUids,
'settings' => $settings,
];
}
return (string)json_encode($response);
} catch (\Throwable $e) {
return '';
}
}
/**
* @param array<string,mixed> $r
* @return array<string,mixed>
*/
private function serializeCustomer(array $r, bool $color): array
{
return [
'id' => (int)$r['uid'],
'name' => (string)($r['title'] ?? ''),
'emphasized' => (bool)($r['emphasize_logo'] ?? false),
'color' => $color,
'logo' => $this->logo((int)$r['uid']),
];
}
/**
* FAL logo with small srcset; SVGs are delivered unprocessed.
*
* @return array<string,mixed>|null
*/
private function logo(int $customerUid): ?array
{
try {
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference');
$ref = $qb
->select('uid', 'title', 'alternative', 'crop')
->from('sys_file_reference')
->where(
$qb->expr()->eq('tablenames', $qb->createNamedParameter(self::TABLE, ParameterType::STRING)),
$qb->expr()->eq('fieldname', $qb->createNamedParameter('logo', ParameterType::STRING)),
$qb->expr()->eq('uid_foreign', $qb->createNamedParameter($customerUid, ParameterType::INTEGER)),
$qb->expr()->eq('deleted', 0),
$qb->expr()->eq('hidden', 0)
)
->orderBy('sorting_foreign')
->setMaxResults(1)
->executeQuery()
->fetchAssociative();
if (!$ref) {
return null;
}
$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
$imageService = GeneralUtility::makeInstance(ImageService::class);
$fileReference = $resourceFactory->getFileReferenceObject((int)$ref['uid']);
if (str_contains((string)$fileReference->getMimeType(), 'svg')) {
// SVG: no processing/srcset — deliver the file as-is.
return [
'uid' => (int)$ref['uid'],
'url' => (string)$fileReference->getPublicUrl(),
'title' => (string)($ref['title'] ?? ''),
'alternative' => (string)($ref['alternative'] ?? ''),
'srcset' => [],
'properties' => ['mimeType' => $fileReference->getMimeType()],
];
}
$srcset = [];
foreach (self::IMAGE_WIDTHS as $width) {
$variant = $imageService->applyProcessingInstructions(
$fileReference,
['width' => $width, 'crop' => $ref['crop'] ?? null]
);
$srcset[] = ['url' => $imageService->getImageUri($variant), 'width' => $width, 'descriptor' => $width . 'w'];
}
$default = $imageService->applyProcessingInstructions(
$fileReference,
['width' => 400, 'crop' => $ref['crop'] ?? null]
);
return [
'uid' => (int)$ref['uid'],
'url' => $imageService->getImageUri($default),
'title' => (string)($ref['title'] ?? ''),
'alternative' => (string)($ref['alternative'] ?? ''),
'srcset' => $srcset,
'properties' => [
'width' => $fileReference->getProperty('width'),
'height' => $fileReference->getProperty('height'),
'mimeType' => $fileReference->getProperty('mime_type'),
],
];
} catch (\Throwable $e) {
return null;
}
}
}

View File

@@ -86,12 +86,14 @@ class EventlistJsonRenderer
$daysInAdvance = (int)($settings['daysinadvance'] ?? 0);
$limit = (int)($settings['limit'] ?? 0);
$debugMode = (bool)($settings['debug'] ?? false);
$layout = (string)($settings['layout'] ?? 'list');
$upcoming = $this->fetchEvents(true, $limit, $daysInAdvance);
$response = [
'events' => array_map(fn($e) => $this->serializeEvent($e), $upcoming),
'settings' => [
'layout' => $layout,
'showpast' => $showPast,
'daysinadvance' => $daysInAdvance,
'limit' => $limit,

View File

@@ -0,0 +1,197 @@
<?php
declare(strict_types=1);
namespace Evomedien\Vitec\UserFunc;
use Doctrine\DBAL\ParameterType;
use TYPO3\CMS\Core\Attribute\AsAllowedCallable;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Service\FlexFormService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
/**
* UserFunc: render the VITEC locations as JSON (headless).
*
* Output under content.locations:
* { "variant": "grid|list|map", "mapText": "<p>…</p>"|null, "locations": [ … ] }
*
* Each location follows the frontend contract (id, slug, name, countryCode,
* coordinates, address, contact, links, marker, sorting, active).
*
* `render()` = top-level plugin / page discovery. `renderForRecord()` = one
* specific tt_content row (reused by ContainerChildrenProcessor for nested
* plugins). Exception-safe.
*/
class LocationsJsonRenderer
{
private const TABLE = 'tx_vitec_domain_model_location';
#[AsAllowedCallable]
public function render(string $content, array $conf): string
{
$row = is_array($this->cObj->data ?? null) ? $this->cObj->data : null;
if ($row && (string)($row['CType'] ?? '') === 'vitec_locationlist') {
return $this->renderForRecord($row);
}
$pageId = 0;
$request = $GLOBALS['TYPO3_REQUEST'] ?? null;
if ($request !== null) {
$pageInfo = $request->getAttribute('frontend.page.information');
if ($pageInfo !== null) {
$pageId = (int)$pageInfo->getId();
}
}
if ($pageId <= 0) {
$pageId = (int)($GLOBALS['TSFE']->id ?? 0);
}
if ($pageId <= 0) {
return '';
}
$qb = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
$ces = $qb
->select('*')
->from('tt_content')
->where(
$qb->expr()->eq('pid', $qb->createNamedParameter($pageId, ParameterType::INTEGER)),
$qb->expr()->eq('CType', $qb->createNamedParameter('vitec_locationlist', ParameterType::STRING)),
$qb->expr()->eq('deleted', 0),
$qb->expr()->eq('hidden', 0)
)
->executeQuery()
->fetchAllAssociative();
if (empty($ces)) {
return '';
}
return $this->renderForRecord($ces[0]);
}
/**
* @param array<string,mixed> $contentElement
*/
public function renderForRecord(array $contentElement): string
{
try {
$flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
$flexFormData = $flexFormService->convertFlexFormContentToArray($contentElement['pi_flexform'] ?? '');
$settings = $flexFormData['settings'] ?? [];
$variant = (string)($settings['variant'] ?? 'grid');
$mapText = trim((string)($settings['maptext'] ?? ''));
$debugMode = (bool)($settings['debug'] ?? false);
$qb = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable(self::TABLE);
$rows = $qb
->select('*')
->from(self::TABLE)
->where(
$qb->expr()->eq('deleted', 0),
$qb->expr()->eq('hidden', 0)
)
->orderBy('sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
$locations = array_map(fn(array $r): array => $this->serializeLocation($r), $rows);
$response = [
'variant' => $variant,
'mapText' => $variant === 'map' && $mapText !== '' ? $mapText : null,
'locations' => $locations,
];
if ($debugMode) {
$response['debug'] = ['count' => count($locations), 'settings' => $settings];
}
return (string)json_encode($response);
} catch (\Throwable $e) {
return '';
}
}
/**
* @param array<string,mixed> $r
* @return array<string,mixed>
*/
private function serializeLocation(array $r): array
{
$name = (string)($r['title'] ?? '');
return [
'id' => (int)$r['uid'],
'slug' => (string)($r['slug'] ?? ''),
'name' => $name,
'countryCode' => (string)($r['country_code'] ?? ''),
'coordinates' => [
'latitude' => (float)($r['latitude'] ?? 0),
'longitude' => (float)($r['longitude'] ?? 0),
],
'address' => [
'company' => $this->nullIfEmpty($r['address_company'] ?? ''),
'street' => $this->nullIfEmpty($r['street'] ?? ''),
'additional' => $this->nullIfEmpty($r['address_additional'] ?? ''),
'postalCode' => $this->nullIfEmpty($r['postal_code'] ?? ''),
'city' => $this->nullIfEmpty($r['city'] ?? ''),
'region' => $this->nullIfEmpty($r['region'] ?? ''),
'country' => $this->nullIfEmpty($r['country'] ?? ''),
],
'contact' => [
'phone' => $this->nullIfEmpty($r['phone'] ?? ''),
'fax' => $this->nullIfEmpty($r['fax'] ?? ''),
'email' => $this->nullIfEmpty($r['email'] ?? ''),
],
'links' => [
'contact' => $this->resolveLink((string)($r['contact_link'] ?? '')),
'legal' => $this->resolveLinkLines((string)($r['legal_links'] ?? '')),
],
'marker' => [
'label' => (string)($r['marker_label'] ?? '') !== '' ? (string)$r['marker_label'] : $name,
'color' => (string)($r['marker_color'] ?? '#ff6633'),
'size' => (float)($r['marker_size'] ?? 0.5),
],
'sorting' => (int)($r['sorting'] ?? 0),
'active' => true,
];
}
private function nullIfEmpty(mixed $value): ?string
{
$value = trim((string)$value);
return $value === '' ? null : $value;
}
/** Resolve a typolink parameter (t3://…, page uid, URL) to a URL/path. */
private function resolveLink(string $parameter): ?string
{
$parameter = trim($parameter);
if ($parameter === '') {
return null;
}
try {
$cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
$url = $cObj->typoLink_URL(['parameter' => $parameter]);
return $url !== '' ? $url : $parameter;
} catch (\Throwable $e) {
return $parameter;
}
}
/** @return array<int,string> */
private function resolveLinkLines(string $lines): array
{
$out = [];
foreach (preg_split('/\r\n|\r|\n/', $lines) ?: [] as $line) {
$resolved = $this->resolveLink($line);
if ($resolved !== null) {
$out[] = $resolved;
}
}
return $out;
}
}

View File

@@ -0,0 +1,175 @@
<?php
declare(strict_types=1);
namespace Evomedien\Vitec\UserFunc;
use Doctrine\DBAL\ParameterType;
use Evomedien\Vitec\Service\UsecaseSerializer;
use TYPO3\CMS\Core\Attribute\AsAllowedCallable;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Service\FlexFormService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* UserFunc: render the VITEC Card plugin as JSON (headless).
*
* ONE plugin for four card types — the FlexForm picks the model type
* (product | story | market | solution) plus one record; ALL card data
* comes from the model. Output under content.card:
*
* { "modelType": "product", "layout": "vertical", "item": { … } }
*
* Image resolution is delegated to UsecaseSerializer::image() (generic,
* srcset + SVG-safe). `render()` = top-level plugin / page discovery,
* `renderForRecord()` = one tt_content row (container-nestable).
* Exception-safe.
*/
class ModelcardJsonRenderer
{
private const MODEL_TABLES = [
'product' => 'tx_vitec_domain_model_product',
'story' => 'tx_vitec_domain_model_usecase',
'market' => 'tx_vitec_domain_model_market',
'solution' => 'tx_vitec_domain_model_solution',
];
#[AsAllowedCallable]
public function render(string $content, array $conf): string
{
$row = is_array($this->cObj->data ?? null) ? $this->cObj->data : null;
if ($row && (string)($row['CType'] ?? '') === 'vitec_modelcard') {
return $this->renderForRecord($row);
}
$pageId = 0;
$request = $GLOBALS['TYPO3_REQUEST'] ?? null;
if ($request !== null) {
$pageInfo = $request->getAttribute('frontend.page.information');
if ($pageInfo !== null) {
$pageId = (int)$pageInfo->getId();
}
}
if ($pageId <= 0) {
$pageId = (int)($GLOBALS['TSFE']->id ?? 0);
}
if ($pageId <= 0) {
return '';
}
$qb = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
$ces = $qb
->select('*')
->from('tt_content')
->where(
$qb->expr()->eq('pid', $qb->createNamedParameter($pageId, ParameterType::INTEGER)),
$qb->expr()->eq('CType', $qb->createNamedParameter('vitec_modelcard', ParameterType::STRING)),
$qb->expr()->eq('deleted', 0),
$qb->expr()->eq('hidden', 0)
)
->executeQuery()
->fetchAllAssociative();
if (empty($ces)) {
return '';
}
return $this->renderForRecord($ces[0]);
}
/**
* @param array<string,mixed> $contentElement
*/
public function renderForRecord(array $contentElement): string
{
try {
$flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
$flexFormData = $flexFormService->convertFlexFormContentToArray($contentElement['pi_flexform'] ?? '');
$settings = $flexFormData['settings'] ?? [];
$modelType = (string)($settings['modelType'] ?? 'product');
$layout = (string)($settings['layout'] ?? 'vertical');
$debugMode = (bool)($settings['debug'] ?? false);
$recordUid = (int)($settings[$modelType === 'story' ? 'story' : $modelType] ?? 0);
$item = null;
if ($recordUid > 0 && isset(self::MODEL_TABLES[$modelType])) {
$row = $this->fetchRecord(self::MODEL_TABLES[$modelType], $recordUid);
if ($row !== null) {
$item = $this->serializeItem($modelType, $row);
}
}
$response = [
'modelType' => $modelType,
'layout' => $layout,
'item' => $item,
];
if ($debugMode) {
$response['debug'] = ['recordUid' => $recordUid, 'settings' => $settings];
}
return (string)json_encode($response);
} catch (\Throwable $e) {
return '';
}
}
/**
* @return array<string,mixed>|null
*/
private function fetchRecord(string $table, int $uid): ?array
{
$qb = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
$row = $qb
->select('*')
->from($table)
->where(
$qb->expr()->eq('uid', $qb->createNamedParameter($uid, ParameterType::INTEGER)),
$qb->expr()->eq('deleted', 0),
$qb->expr()->eq('hidden', 0)
)
->executeQuery()
->fetchAssociative();
return $row ?: null;
}
/**
* @param array<string,mixed> $row
* @return array<string,mixed>
*/
private function serializeItem(string $modelType, array $row): array
{
$serializer = GeneralUtility::makeInstance(UsecaseSerializer::class);
$uid = (int)$row['uid'];
if ($modelType === 'story') {
// Success stories already have the canonical card serialisation.
return $serializer->serializeListItem($row);
}
if ($modelType === 'product') {
return [
'uid' => $uid,
'title' => (string)($row['title'] ?? ''),
'slug' => (string)($row['slug'] ?? ''),
'subtitle' => (string)($row['subtitle'] ?? ''),
'teaser' => (string)($row['teaser'] ?? ''),
'image' => $serializer->image($uid, 'image', 'tx_vitec_domain_model_product')
?? $serializer->image($uid, 'productimage', 'tx_vitec_domain_model_product'),
];
}
// market | solution — identical field set.
return [
'uid' => $uid,
'title' => (string)($row['title'] ?? ''),
'subtitle' => (string)($row['subtitle'] ?? ''),
'teaser' => (string)($row['teaser'] ?? ''),
'description' => (string)($row['description'] ?? ''),
'image' => $serializer->image($uid, 'image', self::MODEL_TABLES[$modelType]),
];
}
}

View File

@@ -11,6 +11,7 @@ use Evomedien\Vitec\Domain\Repository\ProductRepository;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
use TYPO3\CMS\Core\Resource\FileReference;
use TYPO3\CMS\Core\Resource\ResourceFactory;
@@ -406,15 +407,61 @@ class ProductListJsonRenderer
->executeQuery()
->fetchAllAssociative();
return array_map(static function ($cat) {
return array_map(function ($cat) {
return [
'uid' => (int)$cat['uid'],
'title' => $cat['title'] ?? '',
'description' => $cat['description'] ?? '',
'parents' => $this->getCategoryParents((int)$cat['uid']),
];
}, $categories);
}
/** @var array<int,array{parent:int,title:string}>|null Lazy uid => [parent,title] map of all categories. */
private ?array $categoryTreeMap = null;
/**
* All ancestor categories of one category, ROOT FIRST (excluding itself).
* Backed by a once-per-request map of sys_category — cheap for any number
* of products. Cycle-safe.
*
* @return array<int,array{uid:int,title:string}>
*/
private function getCategoryParents(int $categoryUid): array
{
if ($this->categoryTreeMap === null) {
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_category');
$rows = $qb
->select('uid', 'parent', 'title')
->from('sys_category')
->where(
$qb->expr()->eq('deleted', 0),
$qb->expr()->eq('hidden', 0)
)
->executeQuery()
->fetchAllAssociative();
$this->categoryTreeMap = [];
foreach ($rows as $row) {
$this->categoryTreeMap[(int)$row['uid']] = [
'parent' => (int)$row['parent'],
'title' => (string)$row['title'],
];
}
}
$parents = [];
$seen = [$categoryUid => true];
$current = $this->categoryTreeMap[$categoryUid]['parent'] ?? 0;
while ($current > 0 && isset($this->categoryTreeMap[$current]) && !isset($seen[$current])) {
$seen[$current] = true;
array_unshift($parents, ['uid' => $current, 'title' => $this->categoryTreeMap[$current]['title']]);
$current = $this->categoryTreeMap[$current]['parent'];
}
return $parents;
}
private function getDownloadFileType(int $downloadUid): string
{
$qb = GeneralUtility::makeInstance(ConnectionPool::class)

View File

@@ -493,10 +493,56 @@ class ProductShowJsonRenderer
'uid' => (int)$cat['uid'],
'title' => $cat['title'],
'description' => $cat['description'] ?? '',
'parents' => $this->getCategoryParents((int)$cat['uid']),
];
}, $categories);
}
/** @var array<int,array{parent:int,title:string}>|null Lazy uid => [parent,title] map of all categories. */
private ?array $categoryTreeMap = null;
/**
* All ancestor categories of one category, ROOT FIRST (excluding itself).
* Backed by a once-per-request map of sys_category — cheap for any number
* of products. Cycle-safe.
*
* @return array<int,array{uid:int,title:string}>
*/
private function getCategoryParents(int $categoryUid): array
{
if ($this->categoryTreeMap === null) {
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_category');
$rows = $qb
->select('uid', 'parent', 'title')
->from('sys_category')
->where(
$qb->expr()->eq('deleted', 0),
$qb->expr()->eq('hidden', 0)
)
->executeQuery()
->fetchAllAssociative();
$this->categoryTreeMap = [];
foreach ($rows as $row) {
$this->categoryTreeMap[(int)$row['uid']] = [
'parent' => (int)$row['parent'],
'title' => (string)$row['title'],
];
}
}
$parents = [];
$seen = [$categoryUid => true];
$current = $this->categoryTreeMap[$categoryUid]['parent'] ?? 0;
while ($current > 0 && isset($this->categoryTreeMap[$current]) && !isset($seen[$current])) {
$seen[$current] = true;
array_unshift($parents, ['uid' => $current, 'title' => $this->categoryTreeMap[$current]['title']]);
$current = $this->categoryTreeMap[$current]['parent'];
}
return $parents;
}
private function getDownloadFileType(int $downloadUid): string
{
$qb = GeneralUtility::makeInstance(ConnectionPool::class)

View File

@@ -72,6 +72,22 @@ class UsecaseListJsonRenderer
public function renderForRecord(array $contentElement): string
{
try {
// Detail context: when the request carries a story argument the
// list stays silent — the usecaseshow plugin on the same page
// renders the story. Keeps old SEO URLs on one page.
$request = $GLOBALS['TYPO3_REQUEST'] ?? null;
$routing = $request?->getAttribute('routing');
$detailParam = null;
if ($routing instanceof \TYPO3\CMS\Core\Routing\PageArguments) {
$detailParam = $routing->getRouteArguments()['tx_vitec_usecaseshow']['usecase'] ?? null;
}
if ($detailParam === null || $detailParam === '') {
$detailParam = ($request?->getQueryParams() ?? [])['tx_vitec_usecaseshow']['usecase'] ?? null;
}
if ($detailParam !== null && $detailParam !== '') {
return '';
}
$flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
$flexFormData = $flexFormService->convertFlexFormContentToArray($contentElement['pi_flexform'] ?? '');
$settings = $flexFormData['settings'] ?? [];
@@ -92,8 +108,28 @@ class UsecaseListJsonRenderer
->fetchAllAssociative();
$serializer = GeneralUtility::makeInstance(UsecaseSerializer::class);
// Detail links: the PUBLIC URL is the PARENT path of the
// FlexForm-selected "Single PID" page plus the story slug —
// /success-stories/<slug>, not /success-stories/story/<slug>.
// The SuccessStoryPathRewrite middleware maps it back to the
// detail subpage at request time.
$singlePid = (int)($settings['singlePid'] ?? 0);
$detailBase = '';
if ($singlePid > 0) {
$detailPath = rtrim($this->resolvePageUrl($singlePid), '/');
$parent = str_contains($detailPath, '/') ? substr($detailPath, 0, (int)strrpos($detailPath, '/')) : '';
$detailBase = $parent !== '' ? $parent : $detailPath;
}
$usecases = array_map(
static fn (array $u): array => $serializer->serializeListItem($u),
static function (array $u) use ($serializer, $detailBase): array {
$item = $serializer->serializeListItem($u);
$item['detailUrl'] = ($detailBase !== '' && ($item['slug'] ?? '') !== '')
? $detailBase . '/' . ltrim((string)$item['slug'], '/')
: null;
return $item;
},
$rows
);
@@ -109,4 +145,15 @@ class UsecaseListJsonRenderer
return '';
}
}
/** Resolve a page uid to its frontend path (route enhancer aware). */
private function resolvePageUrl(int $pageUid): string
{
try {
$cObj = GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::class);
return (string)$cObj->typoLink_URL(['parameter' => (string)$pageUid]);
} catch (\Throwable $e) {
return '';
}
}
}

View File

@@ -77,16 +77,21 @@ class UsecaseShowJsonRenderer
$layout = (string)($settings['layout'] ?? 'default');
$debugMode = (bool)($settings['debug'] ?? false);
$usecaseUid = (int)($settings['usecase'] ?? 0);
// Fallback: uid/slug from the request (React detail route).
if (!$usecaseUid) {
$params = $GLOBALS['TYPO3_REQUEST']?->getQueryParams() ?? [];
$param = $params['tx_vitec_usecaseshow']['usecase'] ?? null;
if ($param !== null && $param !== '') {
$usecaseUid = is_numeric($param) ? (int)$param : $this->resolveSlug((string)$param);
}
// The story is resolved from the request ONLY (detail route from the
// list plugin's detailUrl): route-enhancer arguments live in the
// `routing` PageArguments, plain GET in query params.
$request = $GLOBALS['TYPO3_REQUEST'] ?? null;
$param = null;
$routing = $request?->getAttribute('routing');
if ($routing instanceof \TYPO3\CMS\Core\Routing\PageArguments) {
$param = $routing->getRouteArguments()['tx_vitec_usecaseshow']['usecase'] ?? null;
}
if ($param === null || $param === '') {
$param = ($request?->getQueryParams() ?? [])['tx_vitec_usecaseshow']['usecase'] ?? null;
}
$usecaseUid = ($param !== null && $param !== '')
? (is_numeric($param) ? (int)$param : $this->resolveSlug((string)$param))
: 0;
if (!$usecaseUid) {
return $debugMode
@@ -114,9 +119,11 @@ class UsecaseShowJsonRenderer
$serializer = GeneralUtility::makeInstance(UsecaseSerializer::class);
$backPid = (int)($settings['backPid'] ?? 0);
$response = [
'usecase' => $serializer->serializeDetail($usecase),
'layout' => $layout,
'backUrl' => $backPid > 0 ? ($this->resolvePageUrl($backPid) ?: null) : null,
];
if ($debugMode) {
@@ -145,4 +152,15 @@ class UsecaseShowJsonRenderer
return (int)($row['uid'] ?? 0);
}
/** Resolve a page uid to its frontend path (route enhancer aware). */
private function resolvePageUrl(int $pageUid): string
{
try {
$cObj = GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::class);
return (string)$cObj->typoLink_URL(['parameter' => (string)$pageUid]);
} catch (\Throwable $e) {
return '';
}
}
}

View File

@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<T3DataStructure>
<meta>
<langDisable>1</langDisable>
</meta>
<sheets>
<sDEF>
<ROOT>
<sheetTitle>Customer Logos Settings</sheetTitle>
<type>array</type>
<el>
<settings.layout>
<label>Layout</label>
<config>
<type>select</type>
<renderType>selectSingle</renderType>
<items>
<numIndex index="0">
<label>List</label>
<value>list</value>
</numIndex>
<numIndex index="1">
<label>Grid</label>
<value>grid</value>
</numIndex>
<numIndex index="2">
<label>Carousel</label>
<value>carousel</value>
</numIndex>
<numIndex index="3">
<label>Marquee (scrolling ticker)</label>
<value>marquee</value>
</numIndex>
</items>
<default>grid</default>
</config>
</settings.layout>
<settings.customers>
<label>Highlighted Customers</label>
<description>Empty = all logos black &amp; white. Selected customers are shown in color, in front of the remaining (b/w) logos.</description>
<config>
<type>select</type>
<renderType>selectMultipleSideBySide</renderType>
<foreign_table>tx_vitec_domain_model_customer</foreign_table>
<foreign_table_where>AND tx_vitec_domain_model_customer.hidden = 0 AND tx_vitec_domain_model_customer.deleted = 0 ORDER BY tx_vitec_domain_model_customer.title</foreign_table_where>
<size>8</size>
<minitems>0</minitems>
<maxitems>999</maxitems>
</config>
</settings.customers>
<settings.onlySelected>
<label>Only show selected</label>
<description>Show only the highlighted customers — the remaining (b/w) logos are omitted entirely.</description>
<config>
<type>check</type>
<default>0</default>
</config>
</settings.onlySelected>
<settings.debug>
<label>Allow Debug Output</label>
<config>
<type>check</type>
<default>0</default>
</config>
</settings.debug>
</el>
</ROOT>
</sDEF>
</sheets>
</T3DataStructure>

View File

@@ -9,6 +9,29 @@
<sheetTitle>Event List Settings</sheetTitle>
<type>array</type>
<el>
<settings.layout>
<label>Layout</label>
<config>
<type>select</type>
<renderType>selectSingle</renderType>
<items>
<numIndex index="0">
<label>List View</label>
<value>list</value>
</numIndex>
<numIndex index="1">
<label>Grid View</label>
<value>grid</value>
</numIndex>
<numIndex index="2">
<label>Teaser Bar</label>
<value>teaserbar</value>
</numIndex>
</items>
<default>list</default>
</config>
</settings.layout>
<settings.daysinadvance>
<label>Days in advance (0 = no limit)</label>
<description>Only show upcoming events starting within the next N days, e.g. 60.</description>

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<T3DataStructure>
<meta>
<langDisable>1</langDisable>
</meta>
<sheets>
<sDEF>
<ROOT>
<sheetTitle>Locations Settings</sheetTitle>
<type>array</type>
<el>
<settings.variant>
<label>Display Variant</label>
<config>
<type>select</type>
<renderType>selectSingle</renderType>
<items>
<numIndex index="0">
<label>Grid</label>
<value>grid</value>
</numIndex>
<numIndex index="1">
<label>List</label>
<value>list</value>
</numIndex>
<numIndex index="2">
<label>Map</label>
<value>map</value>
</numIndex>
</items>
<default>grid</default>
</config>
</settings.variant>
<settings.maptext>
<label>Map Text</label>
<description>Rich text shown alongside the map (Map variant only).</description>
<displayCond>FIELD:settings.variant:=:map</displayCond>
<config>
<type>text</type>
<rows>8</rows>
<enableRichtext>1</enableRichtext>
</config>
</settings.maptext>
<settings.debug>
<label>Allow Debug Output</label>
<config>
<type>check</type>
<default>0</default>
</config>
</settings.debug>
</el>
</ROOT>
</sDEF>
</sheets>
</T3DataStructure>

View File

@@ -0,0 +1,146 @@
<?xml version="1.0" encoding="UTF-8"?>
<T3DataStructure>
<meta>
<langDisable>1</langDisable>
</meta>
<sheets>
<sDEF>
<ROOT>
<sheetTitle>Card Settings</sheetTitle>
<type>array</type>
<el>
<settings.modelType>
<label>Card Type</label>
<onChange>reload</onChange>
<config>
<type>select</type>
<renderType>selectSingle</renderType>
<items>
<numIndex index="0">
<label>Product</label>
<value>product</value>
</numIndex>
<numIndex index="1">
<label>Success Story</label>
<value>story</value>
</numIndex>
<numIndex index="2">
<label>Market</label>
<value>market</value>
</numIndex>
<numIndex index="3">
<label>Solution</label>
<value>solution</value>
</numIndex>
</items>
<default>product</default>
</config>
</settings.modelType>
<settings.product>
<label>Product</label>
<displayCond>FIELD:settings.modelType:=:product</displayCond>
<config>
<type>select</type>
<renderType>selectSingle</renderType>
<items>
<numIndex index="0">
<label>— please choose —</label>
<value>0</value>
</numIndex>
</items>
<foreign_table>tx_vitec_domain_model_product</foreign_table>
<foreign_table_where>AND tx_vitec_domain_model_product.hidden = 0 AND tx_vitec_domain_model_product.deleted = 0 ORDER BY tx_vitec_domain_model_product.title</foreign_table_where>
</config>
</settings.product>
<settings.story>
<label>Success Story</label>
<displayCond>FIELD:settings.modelType:=:story</displayCond>
<config>
<type>select</type>
<renderType>selectSingle</renderType>
<items>
<numIndex index="0">
<label>— please choose —</label>
<value>0</value>
</numIndex>
</items>
<foreign_table>tx_vitec_domain_model_usecase</foreign_table>
<foreign_table_where>AND tx_vitec_domain_model_usecase.hidden = 0 AND tx_vitec_domain_model_usecase.deleted = 0 ORDER BY tx_vitec_domain_model_usecase.title</foreign_table_where>
</config>
</settings.story>
<settings.market>
<label>Market</label>
<displayCond>FIELD:settings.modelType:=:market</displayCond>
<config>
<type>select</type>
<renderType>selectSingle</renderType>
<items>
<numIndex index="0">
<label>— please choose —</label>
<value>0</value>
</numIndex>
</items>
<foreign_table>tx_vitec_domain_model_market</foreign_table>
<foreign_table_where>AND tx_vitec_domain_model_market.hidden = 0 AND tx_vitec_domain_model_market.deleted = 0 ORDER BY tx_vitec_domain_model_market.title</foreign_table_where>
</config>
</settings.market>
<settings.solution>
<label>Solution</label>
<displayCond>FIELD:settings.modelType:=:solution</displayCond>
<config>
<type>select</type>
<renderType>selectSingle</renderType>
<items>
<numIndex index="0">
<label>— please choose —</label>
<value>0</value>
</numIndex>
</items>
<foreign_table>tx_vitec_domain_model_solution</foreign_table>
<foreign_table_where>AND tx_vitec_domain_model_solution.hidden = 0 AND tx_vitec_domain_model_solution.deleted = 0 ORDER BY tx_vitec_domain_model_solution.title</foreign_table_where>
</config>
</settings.solution>
<settings.layout>
<label>Layout</label>
<config>
<type>select</type>
<renderType>selectSingle</renderType>
<items>
<numIndex index="0">
<label>Vertical (image top)</label>
<value>vertical</value>
</numIndex>
<numIndex index="1">
<label>Horizontal (image left)</label>
<value>horizontal</value>
</numIndex>
<numIndex index="2">
<label>Image Overlay</label>
<value>overlay</value>
</numIndex>
<numIndex index="3">
<label>Compact</label>
<value>compact</value>
</numIndex>
</items>
<default>vertical</default>
</config>
</settings.layout>
<settings.debug>
<label>Allow Debug Output</label>
<config>
<type>check</type>
<default>0</default>
</config>
</settings.debug>
</el>
</ROOT>
</sDEF>
</sheets>
</T3DataStructure>

View File

@@ -8,6 +8,17 @@
</sheetTitle>
<type>array</type>
<el>
<settings.backPid>
<label>Back to List View (page)</label>
<description>Page that holds the Success Story list plugin. Used to build the back link in the JSON output.</description>
<config>
<type>group</type>
<allowed>pages</allowed>
<size>1</size>
<maxitems>1</maxitems>
</config>
</settings.backPid>
<settings.debug>
<label>Allow Debug Output.</label>
<config>
@@ -19,20 +30,6 @@
</items>
</config>
</settings.debug>
<settings.usecase>
<label>
Usecase
</label>
<config>
<type>select</type>
<renderType>selectSingle</renderType>
<foreign_table>tx_vitec_domain_model_usecase</foreign_table>
<foreign_table_where>AND tx_vitec_domain_model_usecase.hidden = 0 AND tx_vitec_domain_model_usecase.deleted = 0 ORDER BY tx_vitec_domain_model_usecase.title</foreign_table_where>
<size>1</size>
<minitems>0</minitems>
<maxitems>1</maxitems>
</config>
</settings.usecase>
<settings.layout>
<label>
Layout

View File

@@ -6,6 +6,17 @@
<sheetTitle>Usecase List</sheetTitle>
<type>array</type>
<el>
<settings.singlePid>
<label>Single PID (detail page)</label>
<description>Page that holds the "Show single VITEC Success Story" plugin. Used to build the detail links in the JSON output.</description>
<config>
<type>group</type>
<allowed>pages</allowed>
<size>1</size>
<maxitems>1</maxitems>
</config>
</settings.singlePid>
<settings.debug>
<label>Allow Debug Output.</label>
<config>

View File

@@ -76,4 +76,16 @@ return [
'provider' => SvgIconProvider::class,
'source' => 'EXT:vitec/Resources/Public/Icons/vitec-ogimage.svg',
],
'vitec-plugin-locationlist' => [
'provider' => SvgIconProvider::class,
'source' => 'EXT:vitec/Resources/Public/Icons/vitec-plugin-locationlist.svg',
],
'vitec-plugin-customerlogos' => [
'provider' => SvgIconProvider::class,
'source' => 'EXT:vitec/Resources/Public/Icons/vitec-plugin-customerlogos.svg',
],
'vitec-plugin-modelcard' => [
'provider' => SvgIconProvider::class,
'source' => 'EXT:vitec/Resources/Public/Icons/vitec-plugin-modelcard.svg',
],
];

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
/**
* Frontend middleware registration for EXT:vitec.
*/
return [
'frontend' => [
'vitec/success-story-path-rewrite' => [
'target' => \Evomedien\Vitec\Middleware\SuccessStoryPathRewrite::class,
'after' => [
'typo3/cms-core/normalized-params-attribute',
],
'before' => [
'typo3/cms-frontend/site',
],
],
],
];

View File

@@ -160,6 +160,42 @@ tt_content {
}
}
}
vitec_modelcard < lib.contentElementWithHeader
vitec_modelcard {
fields {
content {
fields {
card = USER
card.userFunc = Evomedien\Vitec\UserFunc\ModelcardJsonRenderer->render
}
}
}
}
vitec_customerlogos < lib.contentElementWithHeader
vitec_customerlogos {
fields {
content {
fields {
customerlogos = USER
customerlogos.userFunc = Evomedien\Vitec\UserFunc\CustomerlogosJsonRenderer->render
}
}
}
}
vitec_locationlist < lib.contentElementWithHeader
vitec_locationlist {
fields {
content {
fields {
locations = USER
locations.userFunc = Evomedien\Vitec\UserFunc\LocationsJsonRenderer->render
}
}
}
}
}
# Include container (layout) rendering definitions

View File

@@ -35,6 +35,41 @@ use TYPO3\CMS\Extbase\Utility\ExtensionUtility;
$registerPluginWithFlexForm('Downloadcardcollection', 'Download Card Collection', 'FILE:EXT:vitec/Configuration/FlexForms/Downloadcardcollection.xml', 'vitec-plugin-downloadcardcollection');
$registerPluginWithFlexForm('Datasheets', 'Datasheets', 'FILE:EXT:vitec/Configuration/FlexForms/Datasheets.xml');
$registerPluginWithFlexForm('Eventlist', 'Event List', 'FILE:EXT:vitec/Configuration/FlexForms/Eventlist.xml', 'vitec-plugin-eventlist');
// Locations: lives in the "VITEC" wizard group, titled "VITEC Locations".
$GLOBALS['TCA']['tt_content']['columns']['CType']['config']['itemGroups']['vitec']
= 'LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:group.header';
ExtensionUtility::registerPlugin(
'Vitec',
'Modelcard',
'VITEC Card',
'vitec-plugin-modelcard',
'vitec',
'One card for a Product, Success Story, Market or Solution — all data from the model.',
'FILE:EXT:vitec/Configuration/FlexForms/Modelcard.xml'
);
$GLOBALS['TCA']['tt_content']['types']['vitec_modelcard']['previewRenderer']
= \Evomedien\Vitec\Preview\ModelcardPreviewRenderer::class;
ExtensionUtility::registerPlugin(
'Vitec',
'Customerlogos',
'VITEC Customer Logos',
'vitec-plugin-customerlogos',
'vitec',
'Customer logos in list, grid, carousel or marquee layout.',
'FILE:EXT:vitec/Configuration/FlexForms/Customerlogos.xml'
);
ExtensionUtility::registerPlugin(
'Vitec',
'Locationlist',
'VITEC Locations',
'vitec-plugin-locationlist',
'vitec',
'Locations as grid, list or interactive map.',
'FILE:EXT:vitec/Configuration/FlexForms/Locationlist.xml'
);
// Change frame_class to allow multiple selections.
$GLOBALS['TCA']['tt_content']['columns']['frame_class']['config']['renderType'] = 'selectCheckBox';

View File

@@ -0,0 +1,63 @@
<?php
return [
'ctrl' => [
'title' => 'VITEC Customer',
'label' => 'title',
'tstamp' => 'tstamp',
'crdate' => 'crdate',
'sortby' => 'sorting',
'delete' => 'deleted',
'enablecolumns' => [
'disabled' => 'hidden',
'starttime' => 'starttime',
'endtime' => 'endtime',
],
'searchFields' => 'title',
'iconfile' => 'EXT:vitec/Resources/Public/Icons/vitec-plugin-customerlogos.svg',
'security' => [
'ignorePageTypeRestriction' => true,
],
],
'types' => [
'1' => [
'showitem' =>
'title, logo, emphasize_logo,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access,
hidden, starttime, endtime',
],
],
'columns' => [
'hidden' => [
'exclude' => true,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.visible',
'config' => [
'type' => 'check',
'renderType' => 'checkboxToggle',
'items' => [['value' => '', 'label' => '', 'invertStateDisplay' => true]],
],
],
'starttime' => [
'exclude' => true,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime',
'config' => ['type' => 'datetime', 'default' => 0],
],
'endtime' => [
'exclude' => true,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime',
'config' => ['type' => 'datetime', 'default' => 0, 'range' => ['upper' => mktime(0, 0, 0, 1, 1, 2038)]],
],
'title' => [
'label' => 'Customer Name',
'config' => ['type' => 'input', 'size' => 40, 'eval' => 'trim', 'required' => true, 'default' => ''],
],
'logo' => [
'label' => 'Logo',
'config' => ['type' => 'file', 'maxitems' => 1, 'allowed' => 'common-image-types'],
],
'emphasize_logo' => [
'label' => 'Emphasize Logo',
'description' => 'Highlight this logo (e.g. larger) in the frontend.',
'config' => ['type' => 'check', 'renderType' => 'checkboxToggle', 'items' => [['value' => '', 'label' => '']]],
],
],
];

View File

@@ -0,0 +1,168 @@
<?php
return [
'ctrl' => [
'title' => 'VITEC Location',
'label' => 'title',
'tstamp' => 'tstamp',
'crdate' => 'crdate',
'sortby' => 'sorting',
'delete' => 'deleted',
'enablecolumns' => [
'disabled' => 'hidden',
'starttime' => 'starttime',
'endtime' => 'endtime',
],
'searchFields' => 'title,slug,city,country,country_code',
'iconfile' => 'EXT:vitec/Resources/Public/Icons/vitec-plugin-locationlist.svg',
'security' => [
'ignorePageTypeRestriction' => true,
],
],
'types' => [
'1' => [
'showitem' =>
'--div--;General,
title, slug, country_code,
--palette--;Coordinates;coords,
--div--;Address,
address_company, street, address_additional,
--palette--;;cityline,
region, country,
--div--;Contact & Links,
phone, fax, email, contact_link, legal_links,
--div--;Marker,
marker_label, marker_color, marker_size,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access,
hidden, starttime, endtime',
],
],
'palettes' => [
'coords' => ['showitem' => 'latitude, longitude'],
'cityline' => ['showitem' => 'postal_code, city'],
],
'columns' => [
'hidden' => [
'exclude' => true,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.visible',
'config' => [
'type' => 'check',
'renderType' => 'checkboxToggle',
'items' => [['value' => '', 'label' => '', 'invertStateDisplay' => true]],
],
],
'starttime' => [
'exclude' => true,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime',
'config' => ['type' => 'datetime', 'default' => 0],
],
'endtime' => [
'exclude' => true,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime',
'config' => ['type' => 'datetime', 'default' => 0, 'range' => ['upper' => mktime(0, 0, 0, 1, 1, 2038)]],
],
// ----------------------------------------------------------- General
'title' => [
'label' => 'Name',
'config' => ['type' => 'input', 'size' => 40, 'eval' => 'trim', 'required' => true, 'default' => ''],
],
'slug' => [
'label' => 'Slug',
'config' => [
'type' => 'slug',
'size' => 50,
'generatorOptions' => ['fields' => ['title'], 'fieldSeparator' => '-', 'replacements' => ['/' => '', ',' => '']],
'fallbackCharacter' => '-',
'eval' => 'uniqueInPid',
],
],
'country_code' => [
'label' => 'Country Code (ISO 3166-1 alpha-2)',
'description' => 'e.g. US, DE, GB',
'config' => ['type' => 'input', 'size' => 6, 'max' => 5, 'eval' => 'trim,upper', 'default' => ''],
],
'latitude' => [
'label' => 'Latitude',
'config' => ['type' => 'number', 'format' => 'decimal', 'default' => 0, 'range' => ['lower' => -90, 'upper' => 90]],
],
'longitude' => [
'label' => 'Longitude',
'config' => ['type' => 'number', 'format' => 'decimal', 'default' => 0, 'range' => ['lower' => -180, 'upper' => 180]],
],
// ----------------------------------------------------------- Address
'address_company' => [
'label' => 'Company (optional)',
'config' => ['type' => 'input', 'size' => 40, 'eval' => 'trim', 'default' => ''],
],
'street' => [
'label' => 'Street',
'config' => ['type' => 'input', 'size' => 40, 'eval' => 'trim', 'default' => ''],
],
'address_additional' => [
'label' => 'Additional (optional)',
'config' => ['type' => 'input', 'size' => 40, 'eval' => 'trim', 'default' => ''],
],
'postal_code' => [
'label' => 'Postal Code',
'config' => ['type' => 'input', 'size' => 10, 'max' => 20, 'eval' => 'trim', 'default' => ''],
],
'city' => [
'label' => 'City',
'config' => ['type' => 'input', 'size' => 30, 'eval' => 'trim', 'default' => ''],
],
'region' => [
'label' => 'Region / State (optional)',
'description' => 'e.g. GA',
'config' => ['type' => 'input', 'size' => 20, 'eval' => 'trim', 'default' => ''],
],
'country' => [
'label' => 'Country (display name)',
'config' => ['type' => 'input', 'size' => 30, 'eval' => 'trim', 'default' => ''],
],
// --------------------------------------------------- Contact & Links
'phone' => [
'label' => 'Phone',
'config' => ['type' => 'input', 'size' => 30, 'max' => 100, 'eval' => 'trim', 'default' => ''],
],
'fax' => [
'label' => 'Fax',
'config' => ['type' => 'input', 'size' => 30, 'max' => 100, 'eval' => 'trim', 'default' => ''],
],
'email' => [
'label' => 'E-Mail',
'config' => ['type' => 'email', 'size' => 40, 'default' => ''],
],
'contact_link' => [
'label' => 'Contact Link',
'config' => ['type' => 'link', 'allowedTypes' => ['page', 'url', 'email']],
],
'legal_links' => [
'label' => 'Legal Links',
'description' => 'One link per line (page link or URL).',
'config' => ['type' => 'text', 'cols' => 40, 'rows' => 3, 'eval' => 'trim'],
],
// ------------------------------------------------------------ Marker
'marker_label' => [
'label' => 'Marker Label (optional)',
'description' => 'Falls back to the location name.',
'config' => ['type' => 'input', 'size' => 40, 'eval' => 'trim', 'default' => ''],
],
'marker_color' => [
'label' => 'Marker Color',
'config' => ['type' => 'color', 'default' => '#ff6633'],
],
'marker_size' => [
'label' => 'Marker Size (scale, e.g. 0.5)',
'config' => [
'type' => 'number',
'format' => 'decimal',
'default' => 0.5,
'range' => ['lower' => 0, 'upper' => 5],
'slider' => ['step' => 0.05, 'width' => 200],
],
],
],
];

View File

@@ -34,7 +34,7 @@ return [
--div--;Detail · Content,
content_elements,
--div--;Detail · Related,
market, solutions, products, categories,
markets, solutions, products, categories,
--div--;SEO,
seo_title, seo_description, no_index, no_follow, canonical_link,
--palette--;Open Graph;ogPalette,
@@ -182,15 +182,16 @@ return [
],
// --------------------------------------------------- Detail · Related
'market' => [
'label' => 'Market',
'markets' => [
'label' => 'Markets',
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'type' => 'select',
'renderType' => 'selectMultipleSideBySide',
'foreign_table' => 'tx_vitec_domain_model_market',
'items' => [['label' => '', 'value' => 0]],
'default' => 0,
'maxitems' => 1,
'MM' => 'tx_vitec_usecase_market_mm',
'size' => 6,
'autoSizeMax' => 20,
'maxitems' => 9999,
],
],
'solutions' => [

View File

@@ -4,6 +4,11 @@ prefixFields: true
prefixType: vendor
fields:
# ──────────────────────────────────────────────────────────────────────
- identifier: tab_content
type: Tab
label: Content
- identifier: eyebrow
type: Text
max: 50
@@ -29,10 +34,6 @@ fields:
- identifier: subheader
useExistingField: true
- identifier: bodytext
useExistingField: true
enableRichtext: true
@@ -53,7 +54,33 @@ fields:
- identifier: Vitec/ButtonStyle
type: Basic
# --- Image ---
- identifier: hero_layout_variant
type: Select
renderType: selectSingle
default: fullscreen
items:
- label: Fullscreen
value: fullscreen
- label: Medium
value: md
- label: Small
value: sm
- label: Extra Small
value: xs
- identifier: show_logo_wall
type: Checkbox
default: 0
- identifier: debug
type: Checkbox
default: 0
# ──────────────────────────────────────────────────────────────────────
- identifier: tab_images
type: Tab
label: Images
- identifier: hero_bgimage
type: File
minitems: 0
@@ -82,7 +109,11 @@ fields:
- label: Fullwidth
value: fullwidth
# --- Video ---
# ──────────────────────────────────────────────────────────────────────
- identifier: tab_video
type: Tab
label: Video
- identifier: hero_video
type: File
minitems: 0
@@ -115,24 +146,28 @@ fields:
type: Checkbox
default: 1
- identifier: hero_layout_variant
type: Select
renderType: selectSingle
default: fullscreen
items:
- label: Fullscreen
value: fullscreen
- label: Medium
value: md
- label: Small
value: sm
- label: Extra Small
value: xs
# --- Background video with overlay layer ---
- identifier: hero_bgvideo
type: File
label: Background Video
description: 'Fullscreen background video behind the hero content.'
minitems: 0
maxitems: 1
allowed: mp4,webm,ogv,mov,m4v
- identifier: show_logo_wall
type: Checkbox
default: 0
- identifier: hero_overlay_color
type: Color
label: Overlay Color
description: 'Overlay layer on top of the background video/image.'
- identifier: debug
type: Checkbox
default: 0
- identifier: hero_overlay_opacity
type: Number
label: 'Overlay Opacity (01)'
format: decimal
default: 0.4
range:
lower: 0
upper: 1
slider:
step: 0.05
width: 200

View File

@@ -102,6 +102,28 @@
<trans-unit id="hero_bgimage.description">
<source>Optional background image placed behind the hero content.</source>
</trans-unit>
<trans-unit id="tab_content.label">
<source>Content</source>
</trans-unit>
<trans-unit id="tab_images.label">
<source>Images</source>
</trans-unit>
<trans-unit id="tab_video.label">
<source>Video</source>
</trans-unit>
<trans-unit id="hero_bgvideo.label">
<source>Background Video</source>
</trans-unit>
<trans-unit id="hero_bgvideo.description">
<source>Fullscreen background video behind the hero content.</source>
</trans-unit>
<trans-unit id="hero_overlay_color.label">
<source>Overlay Color</source>
</trans-unit>
<trans-unit id="hero_overlay_opacity.label">
<source>Overlay Opacity (01)</source>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32">
<path d="M6 8h8v8h-6l-2 8H4l2-8V8z" fill="#ff6a00"/>
<path d="M18 8h8v8h-6l-2 8h-2l2-8V8z" fill="#0a3d62"/>
</svg>

After

Width:  |  Height:  |  Size: 203 B

View File

@@ -0,0 +1,44 @@
name: vitec/quotation
group: vitec_custom_components
prefixFields: true
prefixType: vendor
fields:
- identifier: header_section
type: Palette
label: Header
fields:
- identifier: header
useExistingField: true
- type: Linebreak
- identifier: header_layout
useExistingField: true
- identifier: header_position
useExistingField: true
- identifier: date
useExistingField: true
- type: Linebreak
- identifier: header_link
useExistingField: true
- type: Linebreak
- identifier: subheader
useExistingField: true
- identifier: quote
type: Textarea
required: true
rows: 5
- identifier: quote_name
type: Text
max: 120
- identifier: quote_position
type: Text
max: 180
- identifier: quote_logo
type: File
minitems: 0
maxitems: 1
allowed: common-image-types

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2">
<file source-language="en" datatype="plaintext" original="messages">
<body>
<trans-unit id="title">
<source>VITEC · Customer Quotation</source>
</trans-unit>
<trans-unit id="description">
<source>Customer quote with name, position and optional logo.</source>
</trans-unit>
<trans-unit id="quote.label">
<source>Quote</source>
</trans-unit>
<trans-unit id="quote.description">
<source>Plain quote text without quotation marks — the frontend adds them.</source>
</trans-unit>
<trans-unit id="quote_name.label">
<source>Name</source>
</trans-unit>
<trans-unit id="quote_position.label">
<source>Position / Company</source>
</trans-unit>
<trans-unit id="quote_logo.label">
<source>Logo (optional)</source>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -0,0 +1,27 @@
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
<f:layout name="Preview"/>
<f:section name="Content">
<f:asset.css identifier="vitec-backend-preview" href="EXT:vitec/Resources/Public/Css/backend-preview.css"/>
<div class="vitec-preview">
<div class="vitec-preview__thumb-placeholder">&#8220;</div>
<div class="vitec-preview__body">
<div class="vitec-preview__label">VITEC · Customer Quotation</div>
<h3 class="vitec-preview__headline">
<f:if condition="{data.quote}">
<f:then>“{data.quote -> f:format.crop(maxCharacters: 90)}”</f:then>
<f:else><em style="color:#c00;">⚠ Quote missing</em></f:else>
</f:if>
</h3>
<f:if condition="{data.quote_name}">
<div class="vitec-preview__subline">— {data.quote_name}<f:if condition="{data.quote_position}">, {data.quote_position}</f:if></div>
</f:if>
</div>
<div class="vitec-preview__settings">
<f:if condition="{data.quote_logo.0}"><span class="vitec-badge">🖼 Logo</span></f:if>
</div>
</div>
</f:section>
</html>

View File

@@ -0,0 +1,3 @@
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
<!-- Headless mode: JSON is built by nb-headless-content-blocks -->
</html>

View File

@@ -0,0 +1,36 @@
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
<f:asset.css identifier="vitec-backend-preview" href="EXT:vitec/Resources/Public/Css/backend-preview.css"/>
<div class="vitec-preview">
<f:if condition="{imageUrl}">
<f:then>
<img src="{imageUrl}" class="vitec-preview__thumb" width="200" alt="Card preview"/>
</f:then>
<f:else>
<div class="vitec-preview__thumb-placeholder"></div>
</f:else>
</f:if>
<div class="vitec-preview__body">
<div class="vitec-preview__label">VITEC · Card — {typeLabel}</div>
<h3 class="vitec-preview__headline">
<f:if condition="{title}">
<f:then>{title}</f:then>
<f:else><em style="color:#c00;">⚠ No {typeLabel} selected</em></f:else>
</f:if>
</h3>
<f:if condition="{subtitle}">
<div class="vitec-preview__subline">{subtitle -> f:format.crop(maxCharacters: 90)}</div>
</f:if>
</div>
<div class="vitec-preview__settings">
<span class="vitec-badge">{typeLabel}</span>
<span class="vitec-badge">Layout: {layout}</span>
<f:if condition="{recordUid} == 0">
<span class="vitec-badge vitec-badge--warning">⚠ nothing selected</span>
</f:if>
</div>
</div>
</html>

View File

@@ -204,4 +204,44 @@
align-items: flex-start;
flex-wrap: wrap;
}
}
}
/* Small monitors: drop the body text — label, headline and badges stay. */
@media (max-width: 1400px) {
.vitec-preview__body-text {
display: none;
}
}
/* Real fix: react to the PREVIEW COLUMN width, not the monitor width.
The page-module body of a CE containing a vitec preview becomes a size
container (e.g. inside narrow b13 container columns). */
.t3-page-ce-body:has(.vitec-preview) {
container-type: inline-size;
}
/* Narrow column: stack the grid, drop the body text, badges in a row. */
@container (max-width: 700px) {
.vitec-preview {
grid-template-columns: 1fr;
}
.vitec-preview__body-text {
display: none;
}
.vitec-preview__settings {
flex-direction: row;
align-items: flex-start;
flex-wrap: wrap;
}
.vitec-preview__thumb,
.vitec-preview__thumb-placeholder {
max-width: 100%;
height: auto;
}
}
/* Very narrow column: only label, headline and badges survive. */
@container (max-width: 420px) {
.vitec-preview__subline {
display: none;
}
}

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32">
<rect x="3" y="7" width="12" height="8" rx="1.5" fill="#ff6a00"/>
<rect x="17" y="7" width="12" height="8" rx="1.5" fill="#9aa2ab"/>
<rect x="3" y="17" width="12" height="8" rx="1.5" fill="#9aa2ab"/>
<rect x="17" y="17" width="12" height="8" rx="1.5" fill="#9aa2ab"/>
</svg>

After

Width:  |  Height:  |  Size: 367 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32">
<path d="M16 3c-5 0-9 4-9 9 0 6.6 9 17 9 17s9-10.4 9-17c0-5-4-9-9-9z" fill="#ff6a00"/>
<circle cx="16" cy="12" r="4" fill="#fff"/>
</svg>

After

Width:  |  Height:  |  Size: 226 B

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32">
<rect x="5" y="4" width="22" height="24" rx="2" fill="#0a3d62"/>
<rect x="8" y="7" width="16" height="9" rx="1" fill="#ff6a00"/>
<rect x="8" y="19" width="16" height="2.5" rx="1" fill="#fff"/>
<rect x="8" y="23" width="10" height="2.5" rx="1" fill="#9aa2ab"/>
</svg>

After

Width:  |  Height:  |  Size: 359 B

View File

@@ -157,4 +157,40 @@ $GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeRegistry'][1750000000] = [
\Evomedien\Vitec\Controller\EventController::class => 'list'
]
);
ExtensionUtility::configurePlugin(
'Vitec',
'Locationlist',
[
\Evomedien\Vitec\Controller\LocationController::class => 'list'
],
[
\Evomedien\Vitec\Controller\LocationController::class => 'list'
]
);
ExtensionUtility::configurePlugin(
'Vitec',
'Customerlogos',
[
\Evomedien\Vitec\Controller\CustomerController::class => 'list'
],
[
\Evomedien\Vitec\Controller\CustomerController::class => 'list'
]
);
ExtensionUtility::configurePlugin(
'Vitec',
'Modelcard',
[
\Evomedien\Vitec\Controller\ModelcardController::class => 'list'
],
[
\Evomedien\Vitec\Controller\ModelcardController::class => 'list'
]
);
})();

View File

@@ -100,7 +100,7 @@ CREATE TABLE tx_vitec_domain_model_usecase (
card_image int(11) unsigned DEFAULT '0' NOT NULL,
customer_logo int(11) unsigned DEFAULT '0' NOT NULL,
teaser text,
market int(11) unsigned DEFAULT '0' NOT NULL,
markets int(11) unsigned DEFAULT '0' NOT NULL,
hero_bgimage int(11) unsigned DEFAULT '0' NOT NULL,
hero_small_image int(11) unsigned DEFAULT '0' NOT NULL,
@@ -243,3 +243,41 @@ CREATE TABLE tx_vitec_usecase_product_mm (
KEY uid_local (uid_local),
KEY uid_foreign (uid_foreign)
);
CREATE TABLE tx_vitec_usecase_market_mm (
uid_local int(11) unsigned DEFAULT '0' NOT NULL,
uid_foreign int(11) unsigned DEFAULT '0' NOT NULL,
sorting int(11) unsigned DEFAULT '0' NOT NULL,
sorting_foreign int(11) unsigned DEFAULT '0' NOT NULL,
KEY uid_local (uid_local),
KEY uid_foreign (uid_foreign)
);
CREATE TABLE tx_vitec_domain_model_location (
title varchar(255) DEFAULT '' NOT NULL,
slug varchar(255) DEFAULT '' NOT NULL,
country_code varchar(5) DEFAULT '' NOT NULL,
latitude decimal(11,7) DEFAULT '0.0000000' NOT NULL,
longitude decimal(11,7) DEFAULT '0.0000000' NOT NULL,
address_company varchar(255) DEFAULT '' NOT NULL,
street varchar(255) DEFAULT '' NOT NULL,
address_additional varchar(255) DEFAULT '' NOT NULL,
postal_code varchar(20) DEFAULT '' NOT NULL,
city varchar(255) DEFAULT '' NOT NULL,
region varchar(255) DEFAULT '' NOT NULL,
country varchar(255) DEFAULT '' NOT NULL,
phone varchar(100) DEFAULT '' NOT NULL,
fax varchar(100) DEFAULT '' NOT NULL,
email varchar(255) DEFAULT '' NOT NULL,
contact_link varchar(1024) DEFAULT '' NOT NULL,
legal_links text,
marker_label varchar(255) DEFAULT '' NOT NULL,
marker_color varchar(20) DEFAULT '#ff6633' NOT NULL,
marker_size decimal(3,2) DEFAULT '0.50' NOT NULL
);
CREATE TABLE tx_vitec_domain_model_customer (
title varchar(255) DEFAULT '' NOT NULL,
logo int(11) unsigned DEFAULT '0' NOT NULL,
emphasize_logo smallint(5) unsigned DEFAULT '0' NOT NULL
);

View File

@@ -397,7 +397,10 @@ FileETag None
# Add your own rules here.
#AuthUserFile "/usr/www/users/vitecevo/live/public/.htpasswd"
#AuthName "DEV"
#AuthType Basic
#require valid-user
AuthUserFile "/usr/www/users/vitecevo/live/public/.htpasswd"
AuthName "DEV"
AuthType Basic
<RequireAny>
Require ip 217.70.192.164
Require valid-user
</RequireAny>

View File

@@ -1,27 +1,27 @@
RewriteEngine On
RewriteBase /_frontend/
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
RewriteRule ^ index.html [L]
# ── Cache headers ────────────────────────────────────────────────────────────
# index.html: never cache — always fetch fresh so the browser picks up
# the latest hashed JS/CSS filenames after a deploy.
<Files "index.html">
Header set Cache-Control "no-store, no-cache, must-revalidate"
Header set Pragma "no-cache"
Header set Expires "0"
</Files>
# Hashed assets (JS, CSS, fonts, images inside /assets/):
# Vite appends a content hash to every filename, so these are immutable.
# Cache them for 1 year — a new deploy produces new filenames automatically.
<FilesMatch "\.(js|css|woff2?|ttf|eot|otf)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
RewriteEngine On
RewriteBase /_frontend/
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
RewriteRule ^ index.html [L]
# ── Cache headers ────────────────────────────────────────────────────────────
# index.html: never cache — always fetch fresh so the browser picks up
# the latest hashed JS/CSS filenames after a deploy.
<Files "index.html">
Header set Cache-Control "no-store, no-cache, must-revalidate"
Header set Pragma "no-cache"
Header set Expires "0"
</Files>
# Hashed assets (JS, CSS, fonts, images inside /assets/):
# Vite appends a content hash to every filename, so these are immutable.
# Cache them for 1 year — a new deploy produces new filenames automatically.
<FilesMatch "\.(js|css|woff2?|ttf|eot|otf)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
import{C as e,S as t,Z as n,_ as r,c as i,d as a,h as o,k as s,l as c,m as l,n as u,p as d,u as f,v as p,y as m}from"./index-7LYai_r-.js";var h=n();function g({element:n,className:g,nested:_,devInfoTags:v,uid:y,spaceBefore:b,spaceAfter:x}){let S=i(n),C=a(n),w=Number(c(n)??0),T=p(w),E=m(w),D=r(f(n)),O=D===`center`?`center`:D===`right`?`right`:`left`,k=D===`center`?`center`:D===`right`?`flex-end`:`flex-start`,A=d(n.content?.headerLink),j=o(n.content?.headerLink),M=l(n.content?.headerLink,`Mehr erfahren`);return w===100?null:(0,h.jsx)(u,{className:g,devInfoTags:v,nested:_,uid:y,spaceBefore:b,spaceAfter:x,children:(0,h.jsx)(`div`,{className:`header__inner`,children:(0,h.jsxs)(e,{spacing:.75,sx:{textAlign:O,alignItems:k},children:[S&&(0,h.jsx)(s,{variant:T,children:S}),C&&(0,h.jsx)(s,{variant:E,color:`text.secondary`,children:C}),A&&(0,h.jsx)(t,{href:A,target:j??`_self`,variant:`outlined`,size:`sm`,children:M})]})})})}export{g as VitecHeader};

View File

@@ -0,0 +1 @@
import{A as e,S as t,T as n,_ as r,b as i,d as a,f as o,g as s,m as c,ot as l,p as u,r as d,t as f,v as p,x as m}from"./index-CM0zIqrs.js";var h=l();function g({element:l,className:g,nested:_,devInfoTags:v,uid:y,spaceBefore:b,spaceAfter:x}){let S=a(l),C=c(l),w=Number(o(l)??0),T=m(w),E=t(w),D=i(u(l)),O=D===`center`?`center`:D===`right`?`right`:`left`,k=D===`center`?`center`:D===`right`?`flex-end`:`flex-start`,A=s(l.content?.headerLink),j=p(l.content?.headerLink),M=r(l.content?.headerLink,`Mehr erfahren`);return w===100?null:(0,h.jsx)(d,{className:g,devInfoTags:v,nested:_,uid:y,spaceBefore:b,spaceAfter:x,children:(0,h.jsx)(`div`,{className:`header__inner`,children:(0,h.jsxs)(n,{spacing:.75,sx:{textAlign:O,alignItems:k},children:[S&&(0,h.jsx)(e,{variant:T,children:S}),C&&(0,h.jsx)(e,{variant:E,sx:{opacity:.6},children:C}),A&&(0,h.jsx)(f,{href:A,target:j??`_self`,variant:`outlined`,size:`sm`,children:M})]})})})}export{g as VitecHeader};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
import{A as e,C as t,J as n,O as r,T as i,c as a,k as o,o as s,ot as c,r as l}from"./index-CM0zIqrs.js";var u=c();function d(e){return e.split(`?`)[0].split(`.`).pop()?.toLowerCase()??``}function f(e){return decodeURIComponent(e.split(`?`)[0].split(`/`).pop()??``).replace(/^csm_/,``).replace(/_[a-f0-9]{8,}(\.[^.]+)$/,`$1`)}function p(e){return[`pdf`,`doc`,`docx`,`xls`,`xlsx`,`ppt`,`pptx`,`zip`,`txt`,`csv`].includes(e)}function m({categories:e}){return e?.length?(0,u.jsx)(u.Fragment,{children:e.map(e=>(0,u.jsx)(o,{label:e.title,size:`small`,className:`vitec_newslist__chip`},e.uid))}):null}function h(){return(0,u.jsxs)(`svg`,{width:`14`,height:`14`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2.25`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,children:[(0,u.jsx)(`path`,{d:`M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4`}),(0,u.jsx)(`polyline`,{points:`7 10 12 15 17 10`}),(0,u.jsx)(`line`,{x1:`12`,y1:`15`,x2:`12`,y2:`3`})]})}function g({file:e}){let t=d(e.url),r=f(e.url);return(0,u.jsxs)(`a`,{href:n(e.url,a.base_url,a.router_mode),download:p(t)?r:void 0,target:`_blank`,rel:`noreferrer`,className:`vitec_newsdetail__file`,children:[(0,u.jsx)(`span`,{className:`vitec_newsdetail__file-ext`,children:t.toUpperCase()}),(0,u.jsx)(`span`,{className:`vitec_newsdetail__file-name`,children:r}),(0,u.jsx)(`span`,{className:`vitec_newsdetail__file-dl`,children:(0,u.jsx)(h,{})})]})}function _({element:n,className:a,nested:o,devInfoTags:c,uid:d,spaceBefore:f,spaceAfter:p}){let h=n.content?.news?.news;if(!h)return(0,u.jsx)(l,{className:[`vitec_newsdetail`,a].filter(Boolean).join(` `),devInfoTags:c,nested:o,uid:d,spaceBefore:f,spaceAfter:p,children:(0,u.jsx)(e,{variant:`body2`,color:`text.secondary`,children:`No article found.`})});let _=h.media?.[0]?.url,v=h.datetime?s(h.datetime):null,y=h.relatedFiles??[];return(0,u.jsxs)(l,{className:[`vitec_newsdetail`,a].filter(Boolean).join(` `),devInfoTags:c,nested:o,uid:d,spaceBefore:f,spaceAfter:p,children:[_&&(0,u.jsxs)(`div`,{className:`vitec_newsdetail__hero`,children:[(0,u.jsx)(`img`,{src:_,alt:``,className:`vitec_newsdetail__hero-img`,loading:`eager`}),(0,u.jsx)(`div`,{className:`vitec_newsdetail__hero-overlay`})]}),(0,u.jsxs)(`div`,{className:`vitec_newsdetail__body`,children:[(0,u.jsxs)(i,{direction:`row`,spacing:.75,flexWrap:`wrap`,useFlexGap:!0,alignItems:`center`,className:`vitec_newsdetail__meta`,children:[(0,u.jsx)(m,{categories:h.categories}),v&&(0,u.jsx)(`span`,{className:`vitec_newsdetail__date`,children:v}),h.author&&(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)(`span`,{className:`vitec_newsdetail__meta-sep`,"aria-hidden":!0,children:`·`}),(0,u.jsx)(`span`,{className:`vitec_newsdetail__author`,children:h.author})]})]}),(0,u.jsx)(`h1`,{className:`vitec_newsdetail__title`,children:h.title}),h.teaser&&(0,u.jsx)(`p`,{className:`vitec_newsdetail__teaser`,children:h.teaser}),h.bodytext&&(0,u.jsx)(r,{className:`vitec_newsdetail__bodytext`,sx:t,dangerouslySetInnerHTML:{__html:h.bodytext}}),y.length>0&&(0,u.jsxs)(`div`,{className:`vitec_newsdetail__files`,children:[(0,u.jsx)(`p`,{className:`vitec_newsdetail__files-label`,children:`Downloads`}),(0,u.jsx)(`div`,{className:`vitec_newsdetail__files-list`,children:y.map(e=>(0,u.jsx)(g,{file:e},e.uid))})]})]})]})}export{_ as NewsNewsDetail};

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
import{C as e,D as t,S as n,Z as r,_ as i,b as a,c as o,d as s,h as c,k as l,l as u,m as d,n as f,p,s as m,u as h,v as g,y as _}from"./index-7LYai_r-.js";var v=r();function y({element:r,className:y,nested:b,devInfoTags:x,uid:S,spaceBefore:C,spaceAfter:w}){let T=o(r),E=s(r),D=m(r),O=Number(u(r)??0),k=g(O),A=_(O),j=i(h(r)),M=j===`center`?`center`:j===`right`?`right`:`left`,N=j===`center`?`center`:j===`right`?`flex-end`:`flex-start`,P=r.appearance?.frameClass?.split(`,`).map(e=>e.trim()).includes(`vitec-full-width`)??!1,F=p(r.content?.headerLink),I=c(r.content?.headerLink),L=d(r.content?.headerLink,`Mehr erfahren`);return(0,v.jsx)(f,{className:y,devInfoTags:x,fullWidth:P,nested:b,uid:S,spaceBefore:C,spaceAfter:w,children:(0,v.jsx)(`div`,{className:`text__inner`,children:(0,v.jsxs)(e,{spacing:2,sx:{textAlign:M,alignItems:N},children:[(T||E)&&(0,v.jsxs)(e,{spacing:.5,children:[T&&(0,v.jsx)(l,{variant:k,children:T}),E&&(0,v.jsx)(l,{variant:A,color:`text.secondary`,children:E})]}),F&&(0,v.jsx)(n,{href:F,target:I??`_self`,variant:`outlined`,size:`sm`,children:L}),D&&(0,v.jsx)(t,{sx:{...a,textAlign:`left`},dangerouslySetInnerHTML:{__html:D}})]})})})}export{y as VitecText};

View File

@@ -0,0 +1 @@
import{A as e,C as t,O as n,S as r,T as i,_ as a,b as o,d as s,f as c,g as l,m as u,ot as d,p as f,r as p,t as m,u as h,v as g,x as _}from"./index-CM0zIqrs.js";var v=d();function y({element:d,className:y,nested:b,devInfoTags:x,uid:S,spaceBefore:C,spaceAfter:w}){let T=s(d),E=u(d),D=h(d),O=Number(c(d)??0),k=_(O),A=r(O),j=o(f(d)),M=j===`center`?`center`:j===`right`?`right`:`left`,N=j===`center`?`center`:j===`right`?`flex-end`:`flex-start`,P=d.appearance?.frameClass?.split(`,`).map(e=>e.trim()).includes(`vitec-full-width`)??!1,F=l(d.content?.headerLink),I=g(d.content?.headerLink),L=a(d.content?.headerLink,`Mehr erfahren`);return(0,v.jsx)(p,{className:y,devInfoTags:x,fullWidth:P,nested:b,uid:S,spaceBefore:C,spaceAfter:w,children:(0,v.jsx)(`div`,{className:`text__inner`,children:(0,v.jsxs)(i,{spacing:2,sx:{textAlign:M,alignItems:N},children:[(T||E)&&(0,v.jsxs)(i,{spacing:.5,children:[T&&(0,v.jsx)(e,{variant:k,children:T}),E&&(0,v.jsx)(e,{variant:A,color:`text.secondary`,children:E})]}),F&&(0,v.jsx)(m,{href:F,target:I??`_self`,variant:`outlined`,size:`sm`,children:L}),D&&(0,v.jsx)(n,{sx:{...t,textAlign:`left`},dangerouslySetInnerHTML:{__html:D}})]})})})}export{y as VitecText};

View File

@@ -1 +0,0 @@
import{C as e,D as t,S as n,Z as r,_ as i,b as a,c as o,d as s,h as c,i as l,k as u,l as d,m as f,n as p,p as m,s as h,t as g,u as _,v,w as y,y as b}from"./index-7LYai_r-.js";var x=r();function S({element:r,className:S,nested:C,devInfoTags:w,uid:T,spaceBefore:E,spaceAfter:D}){let O=o(r),k=s(r),A=h(r),j=Number(d(r)??0),M=v(j),N=b(j),P=i(_(r)),F=P===`center`?`center`:P===`right`?`right`:`left`,I=P===`center`?`center`:P===`right`?`flex-end`:`flex-start`,L=r.appearance?.frameClass?.split(`,`).map(e=>e.trim()).includes(`vitec-full-width`)??!1,R=m(r.content?.headerLink),z=c(r.content?.headerLink),B=f(r.content?.headerLink,`Mehr erfahren`),V=l(r.content?.gallery),H=r.content?.gallery?.position?.horizontal,U=V.length>0&&(H===`right`||H===`left`),W=H!==`left`,G=(r.content?.gallery?.width??480)>=600?5:4;return(0,x.jsx)(p,{className:S,devInfoTags:w,fullWidth:L,nested:C,uid:T,spaceBefore:E,spaceAfter:D,children:(0,x.jsx)(`div`,{className:`textpic__inner`,children:(0,x.jsxs)(y,{container:!0,spacing:3,alignItems:`start`,direction:W?`row`:`row-reverse`,children:[(0,x.jsx)(y,{size:{xs:12,md:U?12-G:12},children:(0,x.jsxs)(e,{spacing:2.5,children:[(0,x.jsxs)(e,{spacing:.5,sx:{width:`100%`,textAlign:F,alignItems:I},children:[(0,x.jsx)(u,{variant:M,children:O??`Untitled content`}),k&&(0,x.jsx)(u,{variant:N,color:`text.secondary`,children:k})]}),R&&(0,x.jsx)(n,{href:R,target:z??`_self`,variant:`outlined`,size:`sm`,alignStart:!0,children:B}),A&&(0,x.jsx)(t,{sx:{...a,textAlign:`left`},dangerouslySetInnerHTML:{__html:A}})]})}),V.length>0&&(0,x.jsx)(y,{size:{xs:12,md:U?G:12},children:(0,x.jsx)(g,{images:V})})]})})})}export{S as Textpic};

View File

@@ -0,0 +1 @@
import{A as e,C as t,E as n,O as r,S as i,T as a,_ as o,a as s,b as c,d as l,f as u,g as d,m as f,n as p,ot as m,p as h,r as g,t as _,u as v,v as y,x as b}from"./index-CM0zIqrs.js";var x=m();function S({element:m,className:S,nested:C,devInfoTags:w,uid:T,spaceBefore:E,spaceAfter:D}){let O=l(m),k=f(m),A=v(m),j=Number(u(m)??0),M=b(j),N=i(j),P=c(h(m)),F=P===`center`?`center`:P===`right`?`right`:`left`,I=P===`center`?`center`:P===`right`?`flex-end`:`flex-start`,L=m.appearance?.frameClass?.split(`,`).map(e=>e.trim()).includes(`vitec-full-width`)??!1,R=d(m.content?.headerLink),z=y(m.content?.headerLink),B=o(m.content?.headerLink,`Mehr erfahren`),V=s(m.content?.gallery),H=m.content?.gallery?.position?.horizontal,U=V.length>0&&(H===`right`||H===`left`),W=H!==`left`,G=(m.content?.gallery?.width??480)>=600?5:4;return(0,x.jsx)(g,{className:S,devInfoTags:w,fullWidth:L,nested:C,uid:T,spaceBefore:E,spaceAfter:D,children:(0,x.jsx)(`div`,{className:`textpic__inner`,children:(0,x.jsxs)(n,{container:!0,spacing:3,alignItems:`start`,direction:W?`row`:`row-reverse`,children:[(0,x.jsx)(n,{size:{xs:12,md:U?12-G:12},children:(0,x.jsxs)(a,{spacing:2.5,children:[(0,x.jsxs)(a,{spacing:.5,sx:{width:`100%`,textAlign:F,alignItems:I},children:[(0,x.jsx)(e,{variant:M,children:O??`Untitled content`}),k&&(0,x.jsx)(e,{variant:N,color:`text.secondary`,children:k})]}),R&&(0,x.jsx)(_,{href:R,target:z??`_self`,variant:`outlined`,size:`sm`,alignStart:!0,children:B}),A&&(0,x.jsx)(r,{sx:{...t,textAlign:`left`},dangerouslySetInnerHTML:{__html:A}})]})}),V.length>0&&(0,x.jsx)(n,{size:{xs:12,md:U?G:12},children:(0,x.jsx)(p,{images:V})})]})})})}export{S as Textpic};

View File

@@ -0,0 +1 @@
import{mt as e,ut as t}from"./index-CM0zIqrs.js";var n=e(t());function r(e){let{controlled:t,default:r,name:i,state:a=`value`}=e,{current:o}=n.useRef(t!==void 0),[s,c]=n.useState(r);return[o?t:s,n.useCallback(e=>{o||c(e)},[])]}var i=r;export{i as t};

View File

@@ -1 +0,0 @@
import{Q as e,tt as t}from"./index-7LYai_r-.js";var n=t(e());function r(e){let{controlled:t,default:r,name:i,state:a=`value`}=e,{current:o}=n.useRef(t!==void 0),[s,c]=n.useState(r);return[o?t:s,n.useCallback(e=>{o||c(e)},[])]}var i=r;export{i as t};

View File

@@ -0,0 +1 @@
import{A as e,C as t,O as n,T as r,_ as i,d as a,g as o,l as s,m as c,ot as l,r as u,t as d,u as f,v as p,y as m}from"./index-CM0zIqrs.js";var h=l();function g({element:l,className:g,nested:_,devInfoTags:v,uid:y,spaceBefore:b,spaceAfter:x}){let S=a(l),C=c(l),w=f(l),T=l.data?.eyebrow,E=l.data?.background_variant,D=l.data?.text_alignment??`left`,O=l.data?.layout_variant??`stacked`,k=l.data?.image_aspect_ratio??`auto`,A=l.data?.border_style??`none`,j=l.data?.shadow??`soft`,M=m(l.data?.button_style,`orange`),N=m(l.data?.secondary_button_style,`ghost`),P=(l.data?.icon?.length?l.data.icon:l.data?.image)??[],F=P[0]?.publicUrl,I=P[0]?.alt||P[0]?.title||S||``,L=o(l.data?.card_link),R=o(l.data?.primary_cta),z=p(l.data?.primary_cta),B=l.data?.primary_cta_label||i(l.data?.primary_cta,`Learn more`),V=o(l.data?.secondary_cta),H=p(l.data?.secondary_cta),U=l.data?.secondary_cta_label||i(l.data?.secondary_cta,`Learn more`),W=!!(R||V),G=O===`image_overlay`,K=O===`text_only`,q=[`graphite`,`midnight`,`orange`,`blue`,`dark`].includes(E??``),J=[`vitec_card__inner`,`vitec_card__inner--${O}`,q?`vitec_card__inner--dark-bg`:``,j===`none`?``:`vitec_card__inner--shadow-${j}`,A===`none`?``:`vitec_card__inner--border-${A}`].filter(Boolean).join(` `),Y=`vitec_card__media${k===`auto`?``:` vitec_card__media--${k}`}`,X=(0,h.jsxs)(`div`,{className:J,style:G&&F?{backgroundImage:`url(${F})`,backgroundSize:`cover`,backgroundPosition:`center`}:E&&E!==`none`?s(E):{},children:[F&&!K&&!G&&(0,h.jsx)(`div`,{className:Y,children:(0,h.jsx)(`img`,{className:`vitec_card__image`,src:F,alt:I,loading:`lazy`})}),(0,h.jsxs)(`div`,{className:`vitec_card__body`,style:{textAlign:D},children:[(0,h.jsxs)(r,{spacing:.75,children:[T&&(0,h.jsx)(e,{variant:`overline`,className:`vitec_card__eyebrow`,children:T}),S&&(0,h.jsx)(e,{variant:`h5`,className:`vitec_card__header`,children:S}),C&&(0,h.jsx)(e,{variant:`subtitle2`,color:`text.secondary`,className:`vitec_card__subheader`,children:C}),w&&(0,h.jsx)(n,{className:`vitec_card__text`,sx:t,dangerouslySetInnerHTML:{__html:w}})]}),W&&(0,h.jsx)(`div`,{className:`vitec_card__footer`,children:(0,h.jsxs)(r,{direction:`column`,spacing:.75,children:[R&&(0,h.jsx)(d,{href:R,target:z??`_self`,variant:M,children:B}),V&&(0,h.jsx)(d,{href:V,target:H??`_self`,variant:N,children:U})]})})]})]}),Z=!!L;return(0,h.jsx)(u,{className:g,devInfoTags:v,nested:_,uid:y,spaceBefore:b,spaceAfter:x,children:Z?Z&&!W?(0,h.jsx)(`a`,{className:`vitec_card__link-wrap`,href:L,"aria-label":S??void 0,children:X}):(0,h.jsx)(`div`,{className:`vitec_card__link-wrap vitec_card__link-wrap--div`,role:`link`,tabIndex:0,onClick:()=>{window.location.href=L},onKeyDown:e=>{(e.key===`Enter`||e.key===` `)&&(window.location.href=L)},children:X}):X})}export{g as VitecCard};

View File

@@ -1 +0,0 @@
import{C as e,D as t,S as n,Z as r,b as i,c as a,d as o,g as s,h as c,k as l,m as u,n as d,o as f,p,s as m}from"./index-7LYai_r-.js";var h=r();function g({element:r,className:g,nested:_,devInfoTags:v,uid:y,spaceBefore:b,spaceAfter:x}){let S=a(r),C=o(r),w=m(r),T=r.data?.eyebrow,E=r.data?.background_variant,D=r.data?.text_alignment??`left`,O=r.data?.layout_variant??`stacked`,k=r.data?.image_aspect_ratio??`auto`,A=r.data?.border_style??`none`,j=r.data?.shadow??`soft`,M=s(r.data?.button_style,`orange`),N=s(r.data?.secondary_button_style,`ghost`),P=(r.data?.icon?.length?r.data.icon:r.data?.image)??[],F=P[0]?.publicUrl,I=P[0]?.alt||P[0]?.title||S||``,L=p(r.data?.card_link),R=p(r.data?.primary_cta),z=c(r.data?.primary_cta),B=r.data?.primary_cta_label||u(r.data?.primary_cta,`Learn more`),V=p(r.data?.secondary_cta),H=c(r.data?.secondary_cta),U=r.data?.secondary_cta_label||u(r.data?.secondary_cta,`Learn more`),W=!!(R||V),G=O===`image_overlay`,K=O===`text_only`,q=[`graphite`,`midnight`,`orange`,`blue`,`dark`].includes(E??``),J=[`vitec_card__inner`,`vitec_card__inner--${O}`,q?`vitec_card__inner--dark-bg`:``,j===`none`?``:`vitec_card__inner--shadow-${j}`,A===`none`?``:`vitec_card__inner--border-${A}`].filter(Boolean).join(` `),Y=`vitec_card__media${k===`auto`?``:` vitec_card__media--${k}`}`,X=(0,h.jsxs)(`div`,{className:J,style:G&&F?{backgroundImage:`url(${F})`,backgroundSize:`cover`,backgroundPosition:`center`}:E&&E!==`none`?f(E):{},children:[F&&!K&&!G&&(0,h.jsx)(`div`,{className:Y,children:(0,h.jsx)(`img`,{className:`vitec_card__image`,src:F,alt:I,loading:`lazy`})}),(0,h.jsxs)(`div`,{className:`vitec_card__body`,style:{textAlign:D},children:[(0,h.jsxs)(e,{spacing:.75,children:[T&&(0,h.jsx)(l,{variant:`overline`,className:`vitec_card__eyebrow`,children:T}),S&&(0,h.jsx)(l,{variant:`h5`,className:`vitec_card__header`,children:S}),C&&(0,h.jsx)(l,{variant:`subtitle2`,color:`text.secondary`,className:`vitec_card__subheader`,children:C}),w&&(0,h.jsx)(t,{className:`vitec_card__text`,sx:i,dangerouslySetInnerHTML:{__html:w}})]}),W&&(0,h.jsx)(`div`,{className:`vitec_card__footer`,children:(0,h.jsxs)(e,{direction:`column`,spacing:.75,children:[R&&(0,h.jsx)(n,{href:R,target:z??`_self`,variant:M,children:B}),V&&(0,h.jsx)(n,{href:V,target:H??`_self`,variant:N,children:U})]})})]})]}),Z=!!L;return(0,h.jsx)(d,{className:g,devInfoTags:v,nested:_,uid:y,spaceBefore:b,spaceAfter:x,children:Z?Z&&!W?(0,h.jsx)(`a`,{className:`vitec_card__link-wrap`,href:L,"aria-label":S??void 0,children:X}):(0,h.jsx)(`div`,{className:`vitec_card__link-wrap vitec_card__link-wrap--div`,role:`link`,tabIndex:0,onClick:()=>{window.location.href=L},onKeyDown:e=>{(e.key===`Enter`||e.key===` `)&&(window.location.href=L)},children:X}):X})}export{g as VitecCard};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
import{C as e,O as t,Q as n,Z as r,_ as i,c as a,d as o,k as s,n as c,u as l,w as u,x as d}from"./index-7LYai_r-.js";var f=n(),p=r();function m({element:n,nested:r,devInfoTags:m,uid:h,renderElement:g,columnSpans:_,spaceBefore:v,spaceAfter:y}){let b=n.headerLayout??n.data?.header_layout??n.content?.headerLayout,x=b===100?void 0:a(n),S=b===100?void 0:o(n),C=i(l(n)),w=C===`center`?`center`:C===`right`?`right`:`left`,T=C===`center`?`center`:C===`right`?`flex-end`:`flex-start`,E=n.appearance?.frameClass?.split(`,`).map(e=>e.trim()).includes(`vitec-full-width`)??!1,D=d(),O=n.type??`vitec_cols`,k=n.data?.tx_vitec_gap??n.gap??`3`,A=`${Math.min(5,Math.max(1,parseInt(k,10)))}rem`;return(0,p.jsx)(c,{className:O,devInfoTags:m,fullWidth:E,nested:r,uid:h,spaceBefore:v,spaceAfter:y,children:(0,p.jsxs)(`div`,{className:`${O}__inner`,children:[(x||S)&&(0,p.jsxs)(e,{spacing:.5,sx:{width:`100%`,textAlign:w,alignItems:T},children:[x&&(0,p.jsx)(s,{variant:`h5`,children:x}),S&&(0,p.jsx)(s,{variant:`subtitle1`,color:`text.secondary`,children:S})]}),(0,p.jsx)(u,{container:!0,spacing:A,children:n.items?.map((r,i)=>{let a=_[i]??Math.max(1,Math.floor(12/Math.max(n.items?.length??1,1))),o=r.config?.align||`stretch`,s=r.config?.justify||`flex-start`;return(0,p.jsx)(u,{size:{xs:12,md:a},sx:{alignSelf:o},children:(0,p.jsxs)(e,{spacing:1.5,children:[D&&(0,p.jsx)(t,{label:`TYPO3 slot: colPos${r.config?.colPos??i}`,size:`small`,variant:`outlined`,sx:{width:`fit-content`,height:22,"& .MuiChip-label":{px:.75,fontSize:`0.68rem`}}}),(0,p.jsx)(e,{spacing:2,sx:{justifyContent:s},children:r.contentElements?.map(e=>{let t=`${e.id??e.type??`nested`}-${r.config?.colPos??i}`;return(0,p.jsx)(f.Fragment,{children:g(e,!0)},t)})})]})},`${n.id??n.type??`grid`}-${r.config?.colPos??i}`)})})]})})}export{m as VitecCols};

View File

@@ -0,0 +1 @@
import{A as e,E as t,O as n,T as r,b as i,d as a,k as o,l as s,m as c,ot as l,p as u,r as d,ut as f,w as p}from"./index-CM0zIqrs.js";var m=f(),h=l();function g({element:l,nested:f,devInfoTags:g,uid:_,className:v,renderElement:y,columnSpans:b,slotColPos:x,spaceBefore:S,spaceAfter:C}){let w=l.headerLayout??l.data?.header_layout??l.content?.headerLayout,T=w===100?void 0:a(l),E=w===100?void 0:c(l),D=i(u(l)),O=D===`center`?`center`:D===`right`?`right`:`left`,k=D===`center`?`center`:D===`right`?`flex-end`:`flex-start`,A=l.appearance?.frameClass?.split(`,`).map(e=>e.trim()).includes(`vitec-full-width`)??!1,j=p(),M=l.type??`vitec_cols`,N=l.tx_vitec_bg_variant,P=!!(N&&N!==`none`),F=P?s(N):{},I=l.data?.tx_vitec_gap??l.gap??`3`,L=`${Math.min(5,Math.max(1,parseInt(I,10)))}rem`,R=e=>({config:{colPos:e},contentElements:[]}),z=new Map((l.items??[]).map(e=>[e.config?.colPos??-1,e])),B;if(x&&x.length===b.length)B=x.map(e=>z.get(e)??R(e));else{let e=[...l.items??[]].sort((e,t)=>(e.config?.colPos??0)-(t.config?.colPos??0)),t=Math.max(0,b.length-e.length);B=[...e,...Array.from({length:t},(e,t)=>R(-(t+1)))]}return(0,h.jsx)(d,{className:v??M,devInfoTags:g,fullWidth:A,nested:f,uid:_,spaceBefore:S,spaceAfter:C,children:(0,h.jsx)(`div`,{className:`${M}__inner`,children:(0,h.jsxs)(n,{sx:{...F,...P?{borderRadius:3,px:{xs:2.5,md:4},py:{xs:3,md:3.5},"& p, & h1, & h2, & h3, & h4, & h5, & h6, & .MuiTypography-root":{color:`inherit`}}:{}},children:[(T||E)&&(0,h.jsxs)(r,{spacing:.5,sx:{width:`100%`,mb:2,textAlign:O,alignItems:k},children:[T&&(0,h.jsx)(e,{variant:`h5`,children:T}),E&&(0,h.jsx)(e,{variant:`subtitle1`,color:`text.secondary`,children:E})]}),(0,h.jsx)(t,{container:!0,spacing:L,children:B.map((e,n)=>{let i=b[n]??Math.max(1,Math.floor(12/Math.max(B.length,1))),a=e.config?.align||`stretch`,s=e.config?.justify||`flex-start`;return(0,h.jsx)(t,{size:{xs:12,md:i},sx:{alignSelf:a},children:(0,h.jsxs)(r,{spacing:1.5,children:[j&&(0,h.jsx)(o,{label:e.config?.colPos<0?`TYPO3 slot: empty`:`TYPO3 slot: colPos${e.config?.colPos??n}`,size:`small`,variant:`outlined`,sx:{width:`fit-content`,height:22,"& .MuiChip-label":{px:.75,fontSize:`0.68rem`}}}),(0,h.jsx)(r,{spacing:2,sx:{justifyContent:s},children:e.contentElements?.map(t=>{let r=`${t.id??t.type??`nested`}-${e.config?.colPos??n}`;return(0,h.jsx)(m.Fragment,{children:y(t,!0)},r)})})]})},`${l.id??l.type??`grid`}-${e.config?.colPos??n}`)})})]})})})}export{g as VitecCols};

View File

@@ -0,0 +1 @@
import{ot as e,r as t}from"./index-CM0zIqrs.js";var n=e();function r(e){if(e?.length)return e.map(e=>`${e.url} ${e.descriptor}`).join(`, `)}function i({item:e}){return e.type===`image`&&e.image?.url?(0,n.jsxs)(`div`,{className:`vitec_columns__item vitec_columns__item--image`,children:[(0,n.jsx)(`img`,{className:`vitec_columns__image`,src:e.image.url,srcSet:r(e.image.srcset),sizes:`(max-width: 899px) 100vw, 50vw`,alt:e.image.alternative||e.headline||``,loading:`lazy`}),e.headline&&(0,n.jsx)(`p`,{className:`vitec_columns__item-caption`,children:e.headline})]}):e.type===`text`?(0,n.jsxs)(`div`,{className:`vitec_columns__item vitec_columns__item--text`,children:[e.headline&&(0,n.jsx)(`h3`,{className:`vitec_columns__item-headline`,children:e.headline}),e.text&&(0,n.jsx)(`div`,{className:`vitec_columns__item-body`,dangerouslySetInnerHTML:{__html:e.text}}),e.link&&e.linkLabel&&(0,n.jsx)(`a`,{className:`vitec_columns__item-link`,href:e.link,children:e.linkLabel})]}):null}function a({element:e,className:r,nested:a,devInfoTags:o,uid:s,spaceBefore:c,spaceAfter:l}){let u=e,d=u.columns,f=u.layout??`cols_50_50`,p=d?.left??[],m=d?.right??[];return!p.length&&!m.length?null:(0,n.jsx)(t,{className:r,devInfoTags:o,nested:a,uid:s,spaceBefore:c,spaceAfter:l,children:(0,n.jsxs)(`div`,{className:`vitec_columns vitec_columns--${f.replace(/_/g,`-`)}`,children:[p.length>0&&(0,n.jsx)(`div`,{className:`vitec_columns__col vitec_columns__col--left`,children:p.map((e,t)=>(0,n.jsx)(i,{item:e},t))}),m.length>0&&(0,n.jsx)(`div`,{className:`vitec_columns__col vitec_columns__col--right`,children:m.map((e,t)=>(0,n.jsx)(i,{item:e},t))})]})})}export{a as VitecColumns};

View File

@@ -0,0 +1 @@
import{A as e,T as t,d as n,l as r,m as i,ot as a,r as o,ut as s}from"./index-CM0zIqrs.js";var c=s(),l=a();function u({element:a,nested:s,devInfoTags:u,uid:d,spaceBefore:f,spaceAfter:p,className:m,renderElement:h}){let g=n(a),_=i(a),v=a.tx_vitec_bg_variant,y=!!(v&&v!==`none`),b=r(v),x=[`vitec_container`,a.cssClass,m].filter(Boolean).join(` `),S=a.items?.flatMap(e=>e.contentElements??[])??[];return(0,l.jsx)(o,{className:x,devInfoTags:u,fullWidth:y&&!s,nested:s,uid:d,spaceBefore:f,spaceAfter:p,style:y?{background:b.background,color:b.color}:void 0,children:(0,l.jsx)(`div`,{className:`vitec_container__inner`,children:(0,l.jsxs)(`div`,{className:y?`vitec-content-box vitec_container__content`:`vitec_container__content`,children:[(g||_)&&(0,l.jsxs)(t,{spacing:.5,sx:{mb:2.5},children:[g&&(0,l.jsx)(e,{variant:`h4`,children:g}),_&&(0,l.jsx)(e,{variant:`subtitle1`,sx:y?{color:`rgba(var(--vitec-near-white), 0.65)`}:{color:`text.secondary`},children:_})]}),(0,l.jsx)(t,{spacing:2,children:S.map(e=>(0,l.jsx)(c.Fragment,{children:h(e,!0)},`${e.id??e.type}-${e.colPos}`))})]})})})}export{u as VitecContainer};

View File

@@ -1 +0,0 @@
import{C as e,D as t,Q as n,Z as r,c as i,d as a,k as o,n as s,o as c}from"./index-7LYai_r-.js";var l=n(),u=r();function d({element:n,nested:r,devInfoTags:d,uid:f,spaceBefore:p,spaceAfter:m,className:h,renderElement:g}){let _=i(n),v=a(n),y=n.tx_vitec_bg_variant,b=!!(y&&y!==`none`),x=b?c(y):{},S=[`vitec_container`,n.cssClass,h].filter(Boolean).join(` `),C=n.items?.flatMap(e=>e.contentElements??[])??[];return(0,u.jsx)(s,{className:S,devInfoTags:d,nested:r,uid:f,spaceBefore:p,spaceAfter:m,children:(0,u.jsx)(`div`,{className:`vitec_container__inner`,children:(0,u.jsxs)(t,{sx:{...x,...b?{borderRadius:3,px:{xs:2.5,md:4},py:{xs:3,md:3.5}}:{},"& p, & h1, & h2, & h3, & h4, & h5, & h6, & .MuiTypography-root":{color:`inherit`}},children:[(_||v)&&(0,u.jsxs)(e,{spacing:.5,sx:{mb:2.5},children:[_&&(0,u.jsx)(o,{variant:`h4`,children:_}),v&&(0,u.jsx)(o,{variant:`subtitle1`,color:`text.secondary`,children:v})]}),(0,u.jsx)(e,{spacing:2,children:C.map(e=>(0,u.jsx)(l.Fragment,{children:g(e,!0)},`${e.id??e.type}-${e.colPos}`))})]})})})}export{d as VitecContainer};

View File

@@ -1 +0,0 @@
import{C as e,D as t,S as n,Z as r,b as i,c as a,f as o,g as s,h as c,k as l,m as u,n as d,o as f,p,s as m,w as h}from"./index-7LYai_r-.js";var g=r();function _({element:r,className:_,nested:v,devInfoTags:y,uid:b,spaceBefore:x,spaceAfter:S}){let C=a(r),w=m(r),T=f(r.data?.background_variant),E=o(r.data?.background_image),D=o(r.data?.icon),O=p(r.data?.primary_cta),k=c(r.data?.primary_cta),A=p(r.data?.secondary_cta),j=c(r.data?.secondary_cta),M=r.data?.layout_variant??`stacked`,N=s(r.data?.button_style,`light`),P=s(r.data?.secondary_button_style,`ghost`),F=r.appearance?.frameClass?.split(`,`).map(e=>e.trim()).includes(`vitec-full-width`)??!1,I=M===`centered`,L=M===`split`,R=M===`compact`,z=M===`feature`,B=M===`icon_card`,V=R?{px:{xs:2,md:3},py:{xs:1.5,md:2}}:z?{px:{xs:3,md:5},py:{xs:4,md:5}}:{px:{xs:2.5,md:4},py:{xs:3,md:3.5}},H=R?120:z?320:200,U=z?`h3`:R?`h5`:`h4`,W=(0,g.jsxs)(e,{direction:L?`column`:{xs:`column`,sm:`row`},spacing:1.25,alignItems:I?`center`:void 0,children:[O&&(0,g.jsx)(n,{href:O,target:k??`_self`,variant:N,children:r.data?.primary_cta_label||u(r.data?.primary_cta,`Primary CTA`)}),A&&(0,g.jsx)(n,{href:A,target:j??`_self`,variant:P,children:r.data?.secondary_cta_label||u(r.data?.secondary_cta,`Secondary CTA`)})]});return(0,g.jsx)(d,{className:_,devInfoTags:y,fullWidth:F,nested:v,uid:b,spaceBefore:x,spaceAfter:S,children:(0,g.jsx)(`div`,{className:`vitec_ctabanner__inner vitec_ctabanner--${M}`,children:(0,g.jsxs)(t,{sx:{...T,position:`relative`,overflow:`hidden`,borderRadius:3,...V,minHeight:H},children:[E&&M!==`text_only`&&(0,g.jsx)(t,{sx:{position:`absolute`,inset:0,backgroundImage:`linear-gradient(90deg, rgba(var(--vitec-surface-dark), 0.9), rgba(var(--vitec-surface-dark), 0.45)), url(${E})`,backgroundSize:`cover`,backgroundPosition:`center`,opacity:.9}}),L?(0,g.jsxs)(h,{container:!0,spacing:4,alignItems:`center`,sx:{position:`relative`,zIndex:1},children:[(0,g.jsx)(h,{size:{xs:12,md:7},children:(0,g.jsxs)(e,{spacing:1.5,children:[C&&(0,g.jsx)(l,{variant:U,children:C}),w&&(0,g.jsx)(t,{sx:i,dangerouslySetInnerHTML:{__html:w}})]})}),(0,g.jsx)(h,{size:{xs:12,md:5},children:W})]}):(0,g.jsxs)(e,{spacing:R?1.5:2,sx:{position:`relative`,zIndex:1,maxWidth:I||z?760:640,textAlign:I?`center`:`left`,alignItems:I?`center`:`flex-start`,mx:I?`auto`:void 0},children:[B&&D&&(0,g.jsx)(t,{component:`img`,src:D,alt:``,sx:{height:48,width:`auto`,objectFit:`contain`}}),C&&(0,g.jsx)(l,{variant:U,children:C}),w&&(0,g.jsx)(t,{sx:i,dangerouslySetInnerHTML:{__html:w}}),W]})]})})})}export{_ as VitecCtaBanner};

View File

@@ -0,0 +1 @@
import{A as e,C as t,E as n,O as r,T as i,_ as a,d as o,g as s,h as c,l,m as u,ot as d,r as f,t as p,u as m,v as h,y as g}from"./index-CM0zIqrs.js";var _=d();function v({element:d,className:v,nested:y,devInfoTags:b,uid:x,spaceBefore:S,spaceAfter:C}){let w=o(d),T=u(d),E=m(d),D=l(d.data?.background_variant),O=c(d.data?.background_image),k=c(d.data?.icon),A=s(d.data?.primary_cta),j=h(d.data?.primary_cta),M=s(d.data?.secondary_cta),N=h(d.data?.secondary_cta),P=d.data?.layout_variant??`stacked`,F=g(d.data?.button_style,`light`),I=g(d.data?.secondary_button_style,`ghost`),L=d.appearance?.frameClass?.split(`,`).map(e=>e.trim()).includes(`vitec-full-width`)??!1,R=P===`centered`,z=P===`split`,B=P===`compact`,V=P===`feature`,H=P===`icon_card`,U=B?{px:{xs:2,md:3},py:{xs:1.5,md:2}}:V?{px:{xs:3,md:5},py:{xs:4,md:5}}:{px:{xs:2.5,md:4},py:{xs:3,md:3.5}},W=B?120:V?320:200,G=V?`h3`:B?`h5`:`h4`,K=(0,_.jsxs)(i,{direction:z?`column`:{xs:`column`,sm:`row`},spacing:1.25,alignItems:R?`center`:void 0,children:[A&&(0,_.jsx)(p,{href:A,target:j??`_self`,variant:F,children:d.data?.primary_cta_label||a(d.data?.primary_cta,`Primary CTA`)}),M&&(0,_.jsx)(p,{href:M,target:N??`_self`,variant:I,children:d.data?.secondary_cta_label||a(d.data?.secondary_cta,`Secondary CTA`)})]});return(0,_.jsx)(f,{className:v,devInfoTags:b,fullWidth:L,nested:y,uid:x,spaceBefore:S,spaceAfter:C,children:(0,_.jsx)(`div`,{className:`vitec_ctabanner__inner vitec_ctabanner--${P}`,children:(0,_.jsxs)(r,{sx:{...D,position:`relative`,overflow:`hidden`,borderRadius:3,...U,minHeight:W},children:[O&&P!==`text_only`&&(0,_.jsx)(r,{sx:{position:`absolute`,inset:0,backgroundImage:`linear-gradient(90deg, rgba(var(--vitec-surface-dark), 0.9), rgba(var(--vitec-surface-dark), 0.45)), url(${O})`,backgroundSize:`cover`,backgroundPosition:`center`,opacity:.9}}),z?(0,_.jsxs)(n,{container:!0,spacing:4,alignItems:`center`,sx:{position:`relative`,zIndex:1},children:[(0,_.jsx)(n,{size:{xs:12,md:7},children:(0,_.jsxs)(i,{spacing:1.5,children:[w&&(0,_.jsx)(e,{variant:G,children:w}),T&&(0,_.jsx)(e,{variant:`subtitle1`,sx:{opacity:.85},children:T}),E&&(0,_.jsx)(r,{sx:t,dangerouslySetInnerHTML:{__html:E}})]})}),(0,_.jsx)(n,{size:{xs:12,md:5},children:K})]}):(0,_.jsxs)(i,{spacing:B?1.5:2,sx:{position:`relative`,zIndex:1,maxWidth:R||V?760:640,textAlign:R?`center`:`left`,alignItems:R?`center`:`flex-start`,mx:R?`auto`:void 0},children:[H&&k&&(0,_.jsx)(r,{component:`img`,src:k,alt:``,sx:{height:48,width:`auto`,objectFit:`contain`}}),w&&(0,_.jsx)(e,{variant:G,children:w}),T&&(0,_.jsx)(e,{variant:`subtitle1`,sx:{opacity:.85},children:T}),E&&(0,_.jsx)(r,{sx:t,dangerouslySetInnerHTML:{__html:E}}),K]})]})})})}export{v as VitecCtaBanner};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
import{T as e,Z as t,n}from"./index-7LYai_r-.js";var r=t();function i({className:t,nested:i,devInfoTags:a,uid:o,spaceBefore:s,spaceAfter:c}){return(0,r.jsx)(n,{className:t,devInfoTags:a,nested:i,uid:o,spaceBefore:s,spaceAfter:c,children:(0,r.jsx)(`div`,{className:`vitec_divider__inner`,children:(0,r.jsx)(e,{})})})}export{i as VitecDivider};

View File

@@ -0,0 +1 @@
import{D as e,ot as t,r as n}from"./index-CM0zIqrs.js";var r=t();function i({className:t,nested:i,devInfoTags:a,uid:o,spaceBefore:s,spaceAfter:c}){return(0,r.jsx)(n,{className:t,devInfoTags:a,nested:i,uid:o,spaceBefore:s,spaceAfter:c,children:(0,r.jsx)(`div`,{className:`vitec_divider__inner`,children:(0,r.jsx)(e,{})})})}export{i as VitecDivider};

View File

@@ -0,0 +1 @@
import{A as e,J as t,O as n,T as r,c as i,d as a,ot as o,r as s}from"./index-CM0zIqrs.js";var c=o(),l=e=>e?e>=1024*1024?`${(e/(1024*1024)).toFixed(1)} MB`:`${Math.round(e/1024)} KB`:``,u={brochure:{background:`linear-gradient(135deg, rgba(var(--vitec-graphite), 1) 0%, rgba(var(--vitec-graphite-mid), 1) 100%)`,color:`#fff`},datasheet:{background:`linear-gradient(135deg, rgba(var(--vitec-brand), 1) 0%, rgba(var(--vitec-navy-deep), 1) 100%)`,color:`#fff`},neutral:{background:`rgba(var(--vitec-surface-light, 245 246 248), 1)`,color:`inherit`}},d={brochure:`Brochure`,datasheet:`Datasheet`,neutral:`Download`};function f(e){return e===`brochure`?`brochure`:e===`datasheet`?`datasheet`:`neutral`}function p({element:o,className:p,nested:h,devInfoTags:g,uid:_,spaceBefore:v,spaceAfter:y}){let b=o.content?.downloadcard,x=o.data?.downloadcard,S=b?.download??x?.download,C=f((x?.settings??b?.settings)?.layout),w=u[C],T=d[C],E=a(o)||S?.title,D=S?.file,O=D?.url?t(D.url,i.base_url,i.router_mode):void 0,k=D?.thumbnail?t(D.thumbnail,i.base_url,i.router_mode):void 0,A=l(D?.size),j=C!==`neutral`;return(0,c.jsx)(s,{className:p,devInfoTags:g,nested:h,uid:_,spaceBefore:v,spaceAfter:y,children:(0,c.jsxs)(n,{component:`a`,href:O??`#`,target:`_blank`,rel:`noreferrer noopener`,"aria-label":E??`Download`,className:`vitec_downloadcard vitec_downloadcard--${C}`,sx:{display:`flex`,textDecoration:`none`,color:`inherit`,borderRadius:`0.75rem`,overflow:`hidden`,boxShadow:`0 0.125rem 0.75rem rgba(0,0,0,0.08)`,minHeight:`9rem`,transition:`box-shadow 0.2s ease, transform 0.2s ease`,"&:hover":{boxShadow:`0 0.5rem 1.75rem rgba(0,0,0,0.15)`,transform:`translateY(-0.125rem)`}},children:[(0,c.jsx)(n,{sx:{width:`38%`,flexShrink:0,bgcolor:`#fff`,display:`flex`,alignItems:`center`,justifyContent:`center`,p:k?0:`1rem`,overflow:`hidden`},children:k?(0,c.jsx)(n,{component:`img`,src:k,alt:``,loading:`lazy`,sx:{width:`100%`,height:`100%`,objectFit:`contain`,objectPosition:`top center`,display:`block`,p:`0.75rem`}}):(0,c.jsx)(n,{sx:{color:`text.disabled`,opacity:.4},children:(0,c.jsxs)(`svg`,{width:`40`,height:`40`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`1.25`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,children:[(0,c.jsx)(`path`,{d:`M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z`}),(0,c.jsx)(`polyline`,{points:`14 2 14 8 20 8`})]})})}),(0,c.jsxs)(n,{sx:{flex:1,...w,display:`flex`,flexDirection:`column`,justifyContent:`space-between`,p:`1.25rem 1.25rem 1rem`,gap:`0.5rem`},children:[(0,c.jsxs)(r,{direction:`row`,alignItems:`center`,spacing:1,children:[(0,c.jsx)(e,{component:`span`,sx:{fontSize:`0.6875rem`,fontWeight:700,letterSpacing:`0.06em`,textTransform:`uppercase`,opacity:j?.85:.6,whiteSpace:`nowrap`,color:`inherit`},children:T}),(0,c.jsx)(n,{sx:{flex:1,height:`1px`,bgcolor:j?`rgba(255,255,255,0.3)`:`rgba(0,0,0,0.15)`}})]}),E&&(0,c.jsx)(e,{variant:`h6`,sx:{fontWeight:600,lineHeight:1.25,fontSize:`clamp(0.9375rem, 2vw, 1.25rem)`,color:`inherit`,flex:1},children:E}),(0,c.jsx)(r,{direction:`row`,alignItems:`center`,spacing:.625,sx:{mt:`auto`},children:C===`neutral`?(0,c.jsxs)(n,{component:`span`,sx:{display:`inline-flex`,alignItems:`center`,gap:`0.375rem`,fontSize:`0.8125rem`,fontWeight:600,px:`0.875rem`,py:`0.4375rem`,borderRadius:`0.375rem`,border:`1.5px solid currentColor`,color:`rgba(var(--vitec-brand), 1)`},children:[(0,c.jsx)(m,{}),`Download`,A&&(0,c.jsxs)(e,{component:`span`,variant:`caption`,sx:{opacity:.6,ml:.25},children:[`(`,A,`)`]})]}):(0,c.jsxs)(n,{component:`span`,sx:{display:`inline-flex`,alignItems:`center`,gap:`0.375rem`,fontSize:`0.8125rem`,fontWeight:600,color:j?`rgba(255,255,255,0.85)`:`inherit`,"&:hover":{color:j?`#fff`:`inherit`}},children:[(0,c.jsx)(m,{}),`Download`,A&&(0,c.jsxs)(e,{component:`span`,variant:`caption`,sx:{opacity:.6,ml:.25},children:[`(`,A,`)`]})]})})]})]})})}function m(){return(0,c.jsxs)(`svg`,{width:`13`,height:`13`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2.5`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,children:[(0,c.jsx)(`path`,{d:`M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4`}),(0,c.jsx)(`polyline`,{points:`7 10 12 15 17 10`}),(0,c.jsx)(`line`,{x1:`12`,y1:`15`,x2:`12`,y2:`3`})]})}export{p as VitecDownloadCard};

View File

@@ -1 +0,0 @@
import{C as e,D as t,V as n,Z as r,a as i,c as a,k as o,n as s}from"./index-7LYai_r-.js";var c=r(),l=e=>e?e>=1024*1024?`${(e/(1024*1024)).toFixed(1)} MB`:`${Math.round(e/1024)} KB`:``,u={brochure:{background:`linear-gradient(135deg, rgba(var(--vitec-orange), 1) 0%, rgba(var(--vitec-orange-dark), 1) 100%)`,color:`#fff`},datasheet:{background:`linear-gradient(135deg, rgba(var(--vitec-brand), 1) 0%, rgba(var(--vitec-navy-deep), 1) 100%)`,color:`#fff`},neutral:{background:`rgba(var(--vitec-surface-light, 245 246 248), 1)`,color:`inherit`}},d={brochure:`Brochure`,datasheet:`Datasheet`,neutral:`Download`};function f(e){return e===`brochure`?`brochure`:e===`datasheet`?`datasheet`:`neutral`}function p({element:r,className:p,nested:h,devInfoTags:g,uid:_,spaceBefore:v,spaceAfter:y}){let b=r.content?.downloadcard,x=r.data?.downloadcard,S=b?.download??x?.download,C=f((x?.settings??b?.settings)?.layout),w=u[C],T=d[C],E=a(r)||S?.title,D=S?.file,O=D?.url?n(D.url,i.base_url,i.router_mode):void 0,k=D?.thumbnail?n(D.thumbnail,i.base_url,i.router_mode):void 0,A=l(D?.size),j=C!==`neutral`;return(0,c.jsx)(s,{className:p,devInfoTags:g,nested:h,uid:_,spaceBefore:v,spaceAfter:y,children:(0,c.jsxs)(t,{component:`a`,href:O??`#`,target:`_blank`,rel:`noreferrer noopener`,"aria-label":E??`Download`,className:`vitec_downloadcard vitec_downloadcard--${C}`,sx:{display:`flex`,textDecoration:`none`,color:`inherit`,borderRadius:`0.75rem`,overflow:`hidden`,boxShadow:`0 0.125rem 0.75rem rgba(0,0,0,0.08)`,minHeight:`9rem`,transition:`box-shadow 0.2s ease, transform 0.2s ease`,"&:hover":{boxShadow:`0 0.5rem 1.75rem rgba(0,0,0,0.15)`,transform:`translateY(-0.125rem)`}},children:[(0,c.jsx)(t,{sx:{width:`38%`,flexShrink:0,bgcolor:`#fff`,display:`flex`,alignItems:`center`,justifyContent:`center`,p:k?0:`1rem`,overflow:`hidden`},children:k?(0,c.jsx)(t,{component:`img`,src:k,alt:``,loading:`lazy`,sx:{width:`100%`,height:`100%`,objectFit:`contain`,objectPosition:`top center`,display:`block`,p:`0.75rem`}}):(0,c.jsx)(t,{sx:{color:`text.disabled`,opacity:.4},children:(0,c.jsxs)(`svg`,{width:`40`,height:`40`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`1.25`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,children:[(0,c.jsx)(`path`,{d:`M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z`}),(0,c.jsx)(`polyline`,{points:`14 2 14 8 20 8`})]})})}),(0,c.jsxs)(t,{sx:{flex:1,...w,display:`flex`,flexDirection:`column`,justifyContent:`space-between`,p:`1.25rem 1.25rem 1rem`,gap:`0.5rem`},children:[(0,c.jsxs)(e,{direction:`row`,alignItems:`center`,spacing:1,children:[(0,c.jsx)(o,{component:`span`,sx:{fontSize:`0.6875rem`,fontWeight:700,letterSpacing:`0.06em`,textTransform:`uppercase`,opacity:j?.85:.6,whiteSpace:`nowrap`,color:`inherit`},children:T}),(0,c.jsx)(t,{sx:{flex:1,height:`1px`,bgcolor:j?`rgba(255,255,255,0.3)`:`rgba(0,0,0,0.15)`}})]}),E&&(0,c.jsx)(o,{variant:`h6`,sx:{fontWeight:600,lineHeight:1.25,fontSize:`clamp(0.9375rem, 2vw, 1.25rem)`,color:`inherit`,flex:1},children:E}),(0,c.jsx)(e,{direction:`row`,alignItems:`center`,spacing:.625,sx:{mt:`auto`},children:C===`neutral`?(0,c.jsxs)(t,{component:`span`,sx:{display:`inline-flex`,alignItems:`center`,gap:`0.375rem`,fontSize:`0.8125rem`,fontWeight:600,px:`0.875rem`,py:`0.4375rem`,borderRadius:`0.375rem`,border:`1.5px solid currentColor`,color:`rgba(var(--vitec-brand), 1)`},children:[(0,c.jsx)(m,{}),`Download`,A&&(0,c.jsxs)(o,{component:`span`,variant:`caption`,sx:{opacity:.6,ml:.25},children:[`(`,A,`)`]})]}):(0,c.jsxs)(t,{component:`span`,sx:{display:`inline-flex`,alignItems:`center`,gap:`0.375rem`,fontSize:`0.8125rem`,fontWeight:600,color:j?`rgba(255,255,255,0.85)`:`inherit`,"&:hover":{color:j?`#fff`:`inherit`}},children:[(0,c.jsx)(m,{}),`Download`,A&&(0,c.jsxs)(o,{component:`span`,variant:`caption`,sx:{opacity:.6,ml:.25},children:[`(`,A,`)`]})]})})]})]})})}function m(){return(0,c.jsxs)(`svg`,{width:`13`,height:`13`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2.5`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,children:[(0,c.jsx)(`path`,{d:`M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4`}),(0,c.jsx)(`polyline`,{points:`7 10 12 15 17 10`}),(0,c.jsx)(`line`,{x1:`12`,y1:`15`,x2:`12`,y2:`3`})]})}export{p as VitecDownloadCard};

View File

@@ -1 +0,0 @@
import{A as e,C as t,D as n,Q as r,V as i,Z as a,a as o,c as s,k as c,n as l,tt as u}from"./index-7LYai_r-.js";import{t as d}from"./jszip.min-B_jfdBDa.js";var f=r(),p=u(d(),1),m=a(),h=`linear-gradient(135deg, rgba(var(--vitec-brand), 1) 0%, rgba(var(--vitec-navy-deep), 1) 100%)`;async function g(e,t){let n=new p.default;await Promise.all(e.map(async e=>{let t=e.file?.url;if(!t)return;let r=i(t,o.base_url,o.router_mode),a=await fetch(r);if(!a.ok)return;let s=await a.blob();n.file(e.file?.name??`download_${e.uid}.pdf`,s)}));let r=await n.generateAsync({type:`blob`}),a=URL.createObjectURL(r),s=document.createElement(`a`);s.href=a,s.download=t,s.click(),URL.revokeObjectURL(a)}function _({download:e}){let t=e.file,r=t?.url?i(t.url,o.base_url,o.router_mode):void 0,a=e.title??t?.name??`Download`;return(0,m.jsxs)(n,{component:`a`,href:r??`#`,target:`_blank`,rel:`noreferrer noopener`,sx:{display:`flex`,alignItems:`baseline`,gap:`0.4rem`,color:`rgba(255,255,255,0.85)`,textDecoration:`none`,fontSize:`0.8125rem`,lineHeight:1.4,"&:hover":{color:`#fff`,textDecoration:`underline`}},children:[(0,m.jsx)(n,{component:`span`,sx:{flexShrink:0,mt:`0.2em`,color:`rgba(var(--vitec-orange), 1)`,fontSize:`0.5rem`},children:``}),(0,m.jsx)(`span`,{children:a})]})}function v({downloads:t,zipName:r}){let[i,a]=(0,f.useState)(`idle`);return(0,m.jsxs)(n,{component:`button`,onClick:async e=>{if(e.preventDefault(),i!==`loading`){a(`loading`);try{await g(t,r),a(`idle`)}catch{a(`error`),setTimeout(()=>a(`idle`),3e3)}}},disabled:i===`loading`,sx:{display:`inline-flex`,alignItems:`center`,gap:`0.4rem`,alignSelf:`flex-start`,mt:`auto`,fontSize:`0.8125rem`,fontWeight:600,color:i===`error`?`rgba(255,180,180,1)`:`#fff`,background:`none`,border:`1.5px solid`,borderColor:i===`error`?`rgba(255,120,120,0.6)`:`rgba(255,255,255,0.5)`,borderRadius:`0.375rem`,px:`0.875rem`,py:`0.4375rem`,cursor:i===`loading`?`default`:`pointer`,transition:`border-color 0.15s, background 0.15s`,"&:hover:not(:disabled)":{borderColor:`#fff`,bgcolor:`rgba(255,255,255,0.1)`}},children:[i===`loading`?(0,m.jsx)(e,{size:12,sx:{color:`inherit`}}):(0,m.jsxs)(`svg`,{width:`12`,height:`12`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2.5`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,children:[(0,m.jsx)(`path`,{d:`M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4`}),(0,m.jsx)(`polyline`,{points:`7 10 12 15 17 10`}),(0,m.jsx)(`line`,{x1:`12`,y1:`15`,x2:`12`,y2:`3`})]}),i===`error`?`Download failed`:`Download All Documents (ZIP)`]})}function y({element:e,className:r,nested:a,devInfoTags:u,uid:d,spaceBefore:f,spaceAfter:p}){let g=e.content?.downloadcardcollection??e.data?.downloadcardcollection,y=g?.downloads??[],b=s(e),x=g?.image,S=x&&`publicUrl`in x?x.publicUrl:void 0,C=y[0]?.file?.thumbnail,w=S??(C?i(C,o.base_url,o.router_mode):void 0),T=y.filter(e=>e.file?.url),E=`${(b??`documents`).toLowerCase().replace(/\s+/g,`-`)}.zip`;return y.length===0?null:(0,m.jsx)(l,{className:r,devInfoTags:u,nested:a,uid:d,spaceBefore:f,spaceAfter:p,children:(0,m.jsxs)(n,{className:`vitec_downloadcardcollection`,sx:{display:`flex`,borderRadius:`0.75rem`,overflow:`hidden`,boxShadow:`0 0.125rem 0.75rem rgba(0,0,0,0.08)`,minHeight:`10rem`},children:[(0,m.jsx)(n,{sx:{width:`38%`,flexShrink:0,bgcolor:`#fff`,display:`flex`,alignItems:`center`,justifyContent:`center`,overflow:`hidden`,p:w?0:`1.5rem`},children:w?(0,m.jsx)(n,{component:`img`,src:w,alt:b??`Downloads`,loading:`lazy`,sx:{width:`100%`,height:`100%`,objectFit:`contain`,objectPosition:`top center`,display:`block`,p:`0.75rem`}}):(0,m.jsx)(n,{sx:{color:`text.disabled`,opacity:.3},children:(0,m.jsxs)(`svg`,{width:`48`,height:`48`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`1`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,children:[(0,m.jsx)(`path`,{d:`M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z`}),(0,m.jsx)(`polyline`,{points:`14 2 14 8 20 8`}),(0,m.jsx)(`line`,{x1:`8`,y1:`13`,x2:`16`,y2:`13`}),(0,m.jsx)(`line`,{x1:`8`,y1:`17`,x2:`16`,y2:`17`}),(0,m.jsx)(`polyline`,{points:`10 9 9 9 8 9`})]})})}),(0,m.jsxs)(n,{sx:{flex:1,background:h,color:`#fff`,display:`flex`,flexDirection:`column`,p:`1.25rem 1.25rem 1rem`,gap:`0.75rem`},children:[(0,m.jsxs)(t,{direction:`row`,alignItems:`center`,spacing:1,children:[(0,m.jsx)(c,{component:`span`,sx:{fontSize:`0.6875rem`,fontWeight:700,letterSpacing:`0.06em`,textTransform:`uppercase`,opacity:.85,whiteSpace:`nowrap`,color:`inherit`},children:b??`Download Collection`}),(0,m.jsx)(n,{sx:{flex:1,height:`1px`,bgcolor:`rgba(255,255,255,0.3)`}})]}),(0,m.jsx)(t,{spacing:.375,sx:{flex:1},children:y.map(e=>(0,m.jsx)(_,{download:e},e.uid??e.slug??e.title))}),T.length>1&&(0,m.jsx)(v,{downloads:T,zipName:E})]})]})})}export{y as VitecDownloadCardCollection};

View File

@@ -0,0 +1 @@
import{A as e,J as t,M as n,O as r,T as i,c as a,d as o,mt as s,ot as c,r as l,ut as u}from"./index-CM0zIqrs.js";import{t as d}from"./jszip.min-DxLKr7hR.js";var f=u(),p=s(d(),1),m=c(),h=`linear-gradient(135deg, rgba(var(--vitec-brand), 1) 0%, rgba(var(--vitec-navy-deep), 1) 100%)`;async function g(e,n){let r=new p.default;await Promise.all(e.map(async e=>{let n=e.file?.url;if(!n)return;let i=t(n,a.base_url,a.router_mode),o=await fetch(i);if(!o.ok)return;let s=await o.blob();r.file(e.file?.name??`download_${e.uid}.pdf`,s)}));let i=await r.generateAsync({type:`blob`}),o=URL.createObjectURL(i),s=document.createElement(`a`);s.href=o,s.download=n,s.click(),URL.revokeObjectURL(o)}function _({download:e}){let n=e.file,i=n?.url?t(n.url,a.base_url,a.router_mode):void 0,o=e.title??n?.name??`Download`;return(0,m.jsxs)(r,{component:`a`,href:i??`#`,target:`_blank`,rel:`noreferrer noopener`,sx:{display:`flex`,alignItems:`baseline`,gap:`0.4rem`,color:`rgba(255,255,255,0.85)`,textDecoration:`none`,fontSize:`0.8125rem`,lineHeight:1.4,"&:hover":{color:`#fff`,textDecoration:`underline`}},children:[(0,m.jsx)(r,{component:`span`,sx:{flexShrink:0,mt:`0.2em`,color:`rgba(var(--vitec-orange), 1)`,fontSize:`0.5rem`},children:``}),(0,m.jsx)(`span`,{children:o})]})}function v({downloads:e,zipName:t}){let[i,a]=(0,f.useState)(`idle`);return(0,m.jsxs)(r,{component:`button`,onClick:async n=>{if(n.preventDefault(),i!==`loading`){a(`loading`);try{await g(e,t),a(`idle`)}catch{a(`error`),setTimeout(()=>a(`idle`),3e3)}}},disabled:i===`loading`,sx:{display:`inline-flex`,alignItems:`center`,gap:`0.4rem`,alignSelf:`flex-start`,mt:`auto`,fontSize:`0.8125rem`,fontWeight:600,color:i===`error`?`rgba(255,180,180,1)`:`#fff`,background:`none`,border:`1.5px solid`,borderColor:i===`error`?`rgba(255,120,120,0.6)`:`rgba(255,255,255,0.5)`,borderRadius:`0.375rem`,px:`0.875rem`,py:`0.4375rem`,cursor:i===`loading`?`default`:`pointer`,transition:`border-color 0.15s, background 0.15s`,"&:hover:not(:disabled)":{borderColor:`#fff`,bgcolor:`rgba(255,255,255,0.1)`}},children:[i===`loading`?(0,m.jsx)(n,{size:12,sx:{color:`inherit`}}):(0,m.jsxs)(`svg`,{width:`12`,height:`12`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2.5`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,children:[(0,m.jsx)(`path`,{d:`M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4`}),(0,m.jsx)(`polyline`,{points:`7 10 12 15 17 10`}),(0,m.jsx)(`line`,{x1:`12`,y1:`15`,x2:`12`,y2:`3`})]}),i===`error`?`Download failed`:`Download All Documents (ZIP)`]})}function y({element:n,className:s,nested:c,devInfoTags:u,uid:d,spaceBefore:f,spaceAfter:p}){let g=n.content?.downloadcardcollection??n.data?.downloadcardcollection,y=g?.downloads??[],b=o(n),x=g?.image,S=x&&`publicUrl`in x?x.publicUrl:void 0,C=y[0]?.file?.thumbnail,w=S??(C?t(C,a.base_url,a.router_mode):void 0),T=y.filter(e=>e.file?.url),E=`${(b??`documents`).toLowerCase().replace(/\s+/g,`-`)}.zip`;return y.length===0?null:(0,m.jsx)(l,{className:s,devInfoTags:u,nested:c,uid:d,spaceBefore:f,spaceAfter:p,children:(0,m.jsxs)(r,{className:`vitec_downloadcardcollection`,sx:{display:`flex`,borderRadius:`0.75rem`,overflow:`hidden`,boxShadow:`0 0.125rem 0.75rem rgba(0,0,0,0.08)`,minHeight:`10rem`},children:[(0,m.jsx)(r,{sx:{width:`38%`,flexShrink:0,bgcolor:`#fff`,display:`flex`,alignItems:`center`,justifyContent:`center`,overflow:`hidden`,p:w?0:`1.5rem`},children:w?(0,m.jsx)(r,{component:`img`,src:w,alt:b??`Downloads`,loading:`lazy`,sx:{width:`100%`,height:`100%`,objectFit:`contain`,objectPosition:`top center`,display:`block`,p:`0.75rem`}}):(0,m.jsx)(r,{sx:{color:`text.disabled`,opacity:.3},children:(0,m.jsxs)(`svg`,{width:`48`,height:`48`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`1`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,children:[(0,m.jsx)(`path`,{d:`M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z`}),(0,m.jsx)(`polyline`,{points:`14 2 14 8 20 8`}),(0,m.jsx)(`line`,{x1:`8`,y1:`13`,x2:`16`,y2:`13`}),(0,m.jsx)(`line`,{x1:`8`,y1:`17`,x2:`16`,y2:`17`}),(0,m.jsx)(`polyline`,{points:`10 9 9 9 8 9`})]})})}),(0,m.jsxs)(r,{sx:{flex:1,background:h,color:`#fff`,display:`flex`,flexDirection:`column`,p:`1.25rem 1.25rem 1rem`,gap:`0.75rem`},children:[(0,m.jsxs)(i,{direction:`row`,alignItems:`center`,spacing:1,children:[(0,m.jsx)(e,{component:`span`,sx:{fontSize:`0.6875rem`,fontWeight:700,letterSpacing:`0.06em`,textTransform:`uppercase`,opacity:.85,whiteSpace:`nowrap`,color:`inherit`},children:b??`Download Collection`}),(0,m.jsx)(r,{sx:{flex:1,height:`1px`,bgcolor:`rgba(255,255,255,0.3)`}})]}),(0,m.jsx)(i,{spacing:.375,sx:{flex:1},children:y.map(e=>(0,m.jsx)(_,{download:e},e.uid??e.slug??e.title))}),T.length>1&&(0,m.jsx)(v,{downloads:T,zipName:E})]})]})})}export{y as VitecDownloadCardCollection};

View File

@@ -1 +0,0 @@
import{C as e,E as t,O as n,Z as r,_ as i,c as a,d as o,k as s,n as c,u as l}from"./index-7LYai_r-.js";var u=r();function d(e){return e?Array.isArray(e)?e[0]??null:e:null}function f(e){return e.url??e.publicUrl}function p(e,t){return e.alternative??e.alt??e.title??t}function m(e){if(e.srcset?.length)return e.srcset.map(e=>`${e.url} ${e.descriptor}`).join(`, `)}function h(e){let t=new Date(e);return{day:t.toLocaleString(`en-US`,{day:`numeric`}),month:t.toLocaleString(`en-US`,{month:`short`}).toUpperCase()}}function g(e,t){if(!e)return null;let n=new Date(e),r=n.toLocaleString(`en-US`,{month:`short`,day:`numeric`,year:`numeric`});if(!t||e===t)return r;let i=new Date(t);return n.getMonth()===i.getMonth()&&n.getFullYear()===i.getFullYear()?`${n.toLocaleString(`en-US`,{month:`short`,day:`numeric`})} ${i.toLocaleString(`en-US`,{day:`numeric`,year:`numeric`})}`:`${r} ${i.toLocaleString(`en-US`,{month:`short`,day:`numeric`,year:`numeric`})}`}function _(e){return e?/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e.trim()):!1}function v({element:r,className:v,nested:y,devInfoTags:b,uid:x,spaceBefore:S,spaceAfter:C}){let w=a(r),T=o(r),E=i(l(r)),D=r.content?.eventlist?.events??[];return(0,u.jsx)(c,{className:[`vitec_eventlist`,v].filter(Boolean).join(` `),devInfoTags:b,nested:y,uid:x,spaceBefore:S,spaceAfter:C,children:(0,u.jsxs)(`div`,{className:`vitec_eventlist__inner`,children:[(w||T)&&(0,u.jsxs)(e,{spacing:.5,sx:{mb:2.5,textAlign:E,alignItems:E===`center`?`center`:E===`right`?`flex-end`:`flex-start`},children:[w&&(0,u.jsx)(s,{variant:`h4`,children:w}),T&&(0,u.jsx)(s,{variant:`subtitle1`,color:`text.secondary`,children:T})]}),D.length===0?(0,u.jsx)(s,{variant:`body2`,color:`text.secondary`,children:`No upcoming events.`}):(0,u.jsx)(`div`,{className:`vitec_eventlist__items`,children:D.map(r=>{let i=r.eventstart?h(r.eventstart):null,a=r.eventend&&r.eventstart!==r.eventend,o=g(r.eventstart,r.eventend),c=[r.venue,r.city,r.country].filter(Boolean).join(`, `),l=d(r.image),v=l?f(l):void 0,y=_(r.meetinglink)?`mailto:${r.meetinglink.trim()}`:r.meetinglink?r.meetinglink:void 0;return(0,u.jsxs)(`div`,{className:`vitec_eventlist__item`,children:[i&&(0,u.jsxs)(`div`,{className:`vitec_eventlist__date-badge`,"aria-label":o??void 0,children:[(0,u.jsx)(`span`,{className:`vitec_eventlist__date-day`,children:i.day}),(0,u.jsx)(`span`,{className:`vitec_eventlist__date-month`,children:i.month}),a&&(0,u.jsx)(`span`,{className:`vitec_eventlist__date-multiday`,children:`multi-day`})]}),(0,u.jsxs)(`div`,{className:`vitec_eventlist__content`,children:[(0,u.jsx)(s,{variant:`h6`,className:`vitec_eventlist__title`,children:r.title}),r.teaser&&(0,u.jsx)(s,{variant:`body2`,className:`vitec_eventlist__teaser`,children:r.teaser}),(o||c)&&(0,u.jsxs)(`div`,{className:`vitec_eventlist__meta`,children:[o&&(0,u.jsx)(`span`,{className:`vitec_eventlist__meta-date`,children:o}),o&&c&&(0,u.jsx)(`span`,{className:`vitec_eventlist__meta-sep`,"aria-hidden":!0,children:`·`}),c&&(0,u.jsx)(`span`,{className:`vitec_eventlist__meta-location`,children:c})]}),(0,u.jsxs)(e,{direction:`row`,spacing:1,flexWrap:`wrap`,useFlexGap:!0,alignItems:`center`,sx:{mt:1},children:[r.booth&&(0,u.jsx)(n,{label:r.booth,size:`small`,variant:`outlined`,className:`vitec_eventlist__chip vitec_eventlist__chip--booth`}),r.attendancemode===`online`&&(0,u.jsx)(n,{label:`Online`,size:`small`,variant:`outlined`,className:`vitec_eventlist__chip vitec_eventlist__chip--online`}),y&&(0,u.jsx)(t,{component:`a`,href:y,size:`small`,variant:`outlined`,className:`vitec_eventlist__btn vitec_eventlist__btn--contact`,children:`Contact`}),r.eventurl&&(0,u.jsx)(t,{component:`a`,href:r.eventurl,target:`_blank`,rel:`noreferrer`,size:`small`,variant:`contained`,className:`vitec_eventlist__btn vitec_eventlist__btn--more`,children:`Learn more`})]})]}),(0,u.jsx)(`div`,{className:`vitec_eventlist__logo-box${v?``:` vitec_eventlist__logo-box--empty`}`,children:v&&(0,u.jsx)(`img`,{src:v,srcSet:l?m(l):void 0,sizes:`128px`,alt:l?p(l,r.title):r.title,className:`vitec_eventlist__logo-img`,loading:`lazy`})})]},r.uid)})})]})})}export{v as VitecEventList};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
import{C as e,D as t,S as n,Z as r,b as i,c as a,d as o,f as s,g as c,h as l,k as u,m as d,n as f,o as p,p as m,s as h,w as g}from"./index-7LYai_r-.js";var _=r();function v({element:r,className:v,nested:y,devInfoTags:b,uid:x,spaceBefore:S,spaceAfter:C}){let w=a(r),T=o(r),E=h(r),D=p(r.data?.background_variant),O=m(r.data?.cta),k=l(r.data?.cta),A=c(r.data?.button_style,`light`),j=r.data?.hero_layout_variant??`sm`,M=r.data?.hero_video??[],N=r.data?.hero_image??[],P=M.length>0,F=N.length>0,I=(P||F)&&j!==`xs`,L=P,R=P?M[0]?.publicUrl:N[0]?.publicUrl,z=!P&&N[0]?N[0].alt||N[0].title||w||`Hero image`:``,B=(P?r.data?.hero_video_alignment:r.data?.hero_image_alignment)??`right`,V=I&&(B===`left`||B===`right`),H=I&&(B===`center`||B===`fullwidth`),U=!!r.data?.hero_video_autoplay,W=!!r.data?.hero_video_loop,G=!!r.data?.hero_video_muted||U,K=s(r.data?.hero_bgimage);return j===`xs`?(0,_.jsx)(f,{className:v,devInfoTags:b,fullWidth:!0,nested:y,uid:x,spaceBefore:S,spaceAfter:C,children:(0,_.jsx)(`div`,{className:`vitec_herosection__inner vitec_herosection__inner--xs`})}):(0,_.jsx)(f,{className:v,devInfoTags:b,fullWidth:!0,nested:y,uid:x,spaceBefore:S,spaceAfter:C,children:(0,_.jsx)(`div`,{className:`vitec_herosection__inner vitec_herosection__inner--${j}`,children:(0,_.jsxs)(t,{sx:{...D,...K?{backgroundImage:`url(${K})`,backgroundSize:`cover`,backgroundPosition:`center`}:{},borderRadius:3,px:{xs:2.5,md:4},py:{xs:3,md:4},overflow:`hidden`,display:`flex`,alignItems:`center`},children:[(0,_.jsxs)(g,{container:!0,spacing:4,alignItems:`center`,sx:{width:`100%`},children:[(0,_.jsx)(g,{size:{xs:12,md:V?7:12},sx:V&&B===`left`?{order:{md:2}}:{},children:(0,_.jsxs)(e,{spacing:2,sx:{maxWidth:V?`100%`:680},children:[r.data?.eyebrow&&j!==`sm`&&(0,_.jsx)(u,{variant:`overline`,sx:{letterSpacing:`0.14em`,opacity:.8},children:r.data.eyebrow}),w&&(0,_.jsx)(u,{variant:`h2`,children:w}),T&&(0,_.jsx)(u,{variant:`h6`,sx:{opacity:.85,fontWeight:500},children:T}),E&&(0,_.jsx)(t,{sx:{...i,"& a":{color:`inherit`},"& mark":{backgroundColor:`rgba(var(--vitec-highlight), 0.85)`}},dangerouslySetInnerHTML:{__html:E}}),O&&(0,_.jsx)(n,{href:O,target:k??`_self`,variant:A,alignStart:!0,children:r.data?.cta_label||d(r.data?.cta,`Learn more`)}),!!r.data?.show_logo_wall&&(0,_.jsx)(t,{sx:{mt:1,border:`1px dashed rgba(var(--vitec-white), 0.35)`,borderRadius:2,px:2,py:1.5,width:`fit-content`},children:(0,_.jsx)(u,{variant:`caption`,sx:{opacity:.85},children:`Logo wall placeholder enabled`})})]})}),V&&R&&(0,_.jsx)(g,{size:{xs:12,md:5},sx:B===`left`?{order:{md:1}}:{},children:L?(0,_.jsx)(`video`,{src:R,autoPlay:U,loop:W,muted:G,playsInline:!0,controls:!U,style:{width:`100%`,height:`auto`,display:`block`,borderRadius:`12px`,boxShadow:`0 20px 40px rgba(var(--vitec-surface-dark), 0.25)`}}):(0,_.jsx)(t,{component:`img`,src:R,alt:z,sx:{width:`100%`,height:`auto`,display:`block`,borderRadius:3,boxShadow:`0 20px 40px rgba(var(--vitec-surface-dark), 0.25)`}})})]}),H&&R&&(0,_.jsx)(t,{sx:{mt:3,mx:B===`center`?`auto`:void 0,maxWidth:B===`center`?720:`100%`},children:L?(0,_.jsx)(`video`,{src:R,autoPlay:U,loop:W,muted:G,playsInline:!0,controls:!U,style:{width:`100%`,height:`auto`,display:`block`,borderRadius:`12px`,boxShadow:`0 20px 40px rgba(var(--vitec-surface-dark), 0.25)`}}):(0,_.jsx)(t,{component:`img`,src:R,alt:z,sx:{width:`100%`,height:`auto`,display:`block`,borderRadius:3,boxShadow:`0 20px 40px rgba(var(--vitec-surface-dark), 0.25)`}})})]})})})}export{v as VitecHeroSection};

View File

@@ -0,0 +1 @@
import{A as e,E as t,O as n,T as r,_ as i,d as a,g as o,h as s,l as c,m as l,ot as u,r as d,t as f,u as p,v as m,y as h}from"./index-CM0zIqrs.js";var g=u();function _({element:u,className:_,nested:v,devInfoTags:y,uid:b,spaceBefore:x,spaceAfter:S}){let C=a(u),w=l(u),T=p(u),E=c(u.data?.background_variant),D=o(u.data?.cta),O=m(u.data?.cta),k=h(u.data?.button_style,`light`),A=u.data?.hero_layout_variant??`sm`,j=u.data?.hero_video??[],M=u.data?.hero_image??[],N=j.length>0,P=M.length>0,F=(N||P)&&A!==`xs`,I=N,L=N?j[0]?.publicUrl:M[0]?.publicUrl,R=!N&&M[0]?M[0].alt||M[0].title||C||`Hero image`:``,z=(N?u.data?.hero_video_alignment:u.data?.hero_image_alignment)??`right`,B=F&&(z===`left`||z===`right`),V=F&&(z===`center`||z===`fullwidth`),H=!!u.data?.hero_video_autoplay,U=!!u.data?.hero_video_loop,W=!!u.data?.hero_video_muted||H,G=s(u.data?.hero_bgimage),K=F&&L?I?(0,g.jsx)(`video`,{src:L,autoPlay:H,loop:U,muted:W,playsInline:!0,controls:!H,style:{width:`100%`,height:`auto`,display:`block`,borderRadius:`12px`,boxShadow:`0 20px 40px rgba(var(--vitec-surface-dark), 0.25)`}}):(0,g.jsx)(n,{component:`img`,src:L,alt:R,sx:{width:`100%`,height:`auto`,display:`block`,borderRadius:3,boxShadow:`0 20px 40px rgba(var(--vitec-surface-dark), 0.25)`}}):null;if(A===`xs`)return(0,g.jsx)(d,{className:_,devInfoTags:y,fullWidth:!0,nested:v,uid:b,spaceBefore:x,spaceAfter:S,children:(0,g.jsx)(`div`,{className:`vitec_herosection__inner vitec_herosection__inner--xs`})});let q=(u.data?.header_position??``)===`center`;return(0,g.jsx)(d,{className:_,devInfoTags:y,fullWidth:!0,nested:v,uid:b,spaceBefore:x,spaceAfter:S,children:(0,g.jsx)(`div`,{className:`vitec_herosection__inner vitec_herosection__inner--${A}`,children:(0,g.jsxs)(n,{className:`vitec_herosection__box`,sx:{...E,...G?{backgroundImage:`url(${G})`,backgroundSize:`cover`,backgroundPosition:`center`}:{}},children:[(0,g.jsxs)(t,{container:!0,spacing:4,alignItems:`center`,className:`vitec_herosection__grid`,children:[(0,g.jsx)(t,{size:{xs:12,md:B?7:12},className:B&&z===`left`?`vitec_herosection__text-col vitec_herosection__text-col--order-last`:`vitec_herosection__text-col`,children:(0,g.jsxs)(r,{spacing:2,className:[`vitec_herosection__content-stack`,B?`vitec_herosection__content-stack--side`:``,q?`vitec_herosection__content-stack--centered`:``].filter(Boolean).join(` `),children:[u.data?.eyebrow&&A!==`sm`&&(0,g.jsx)(e,{variant:`overline`,className:`vitec_herosection__eyebrow`,children:u.data.eyebrow}),C&&(0,g.jsx)(e,{variant:`h2`,children:C}),w&&(0,g.jsx)(e,{variant:`h6`,className:`vitec_herosection__subheader`,children:w}),T&&(0,g.jsx)(`div`,{className:`vitec_herosection__bodytext`,dangerouslySetInnerHTML:{__html:T}}),D&&(0,g.jsx)(f,{href:D,target:O??`_self`,variant:k,alignStart:!0,children:u.data?.cta_label||i(u.data?.cta,`Learn more`)}),!!u.data?.show_logo_wall&&(0,g.jsx)(`div`,{className:`vitec_herosection__logo-wall-placeholder`,children:(0,g.jsx)(e,{variant:`caption`,children:`Logo wall placeholder enabled`})})]})}),B&&K&&(0,g.jsx)(t,{size:{xs:12,md:5},className:z===`left`?`vitec_herosection__media-col vitec_herosection__media-col--order-first`:`vitec_herosection__media-col`,children:K})]}),V&&K&&(0,g.jsx)(`div`,{className:`vitec_herosection__stacked-media vitec_herosection__stacked-media--${z}`,children:K})]})})})}export{_ as VitecHeroSection};

View File

@@ -1 +0,0 @@
import{C as e,D as t,S as n,Z as r,_ as i,b as a,c as o,d as s,g as c,h as l,k as u,n as d,p as f,s as p,w as m}from"./index-7LYai_r-.js";var h=r();function g({element:r,className:g,nested:_,devInfoTags:v,uid:y,spaceBefore:b,spaceAfter:x}){let S=o(r),C=s(r),w=p(r),T=f(r.data?.button_link),E=l(r.data?.button_link),D=r.data?.button_text,O=c(r.data?.button_style),k=r.data?.intro_layout_variant??`variant_1`,A=i(r.data?.header_position)===`center`||k===`variant_1`,j=!!r.data?.fullwidth,M=r.data?.intro_video??[],N=r.data?.intro_image??[],P=M.length>0,F=N.length>0,I=P||F,L=P,R=P?M[0]?.publicUrl:N[0]?.publicUrl,z=!P&&N[0]&&(N[0].alt||N[0].title||S)||``,B=(P?r.data?.intro_video_alignment:r.data?.intro_image_alignment)??`right`,V=I&&(B===`left`||B===`right`),H=I&&(B===`center`||B===`fullwidth`),U=!!r.data?.intro_video_autoplay,W=!!r.data?.intro_video_loop,G=!!r.data?.intro_video_muted||U,K=(0,h.jsxs)(e,{spacing:2.5,sx:{textAlign:A&&!I?`center`:`left`,alignItems:A&&!I?`center`:`flex-start`,maxWidth:I?void 0:760,mx:A&&!I?`auto`:void 0},children:[S&&(0,h.jsx)(u,{variant:`h3`,children:S}),C&&(0,h.jsx)(u,{variant:`subtitle1`,color:`text.secondary`,children:C}),w&&(0,h.jsx)(t,{sx:a,dangerouslySetInnerHTML:{__html:w}}),T&&(0,h.jsx)(n,{href:T,target:E??`_self`,variant:O,children:D??`Mehr erfahren`})]}),q=I&&R&&(L?(0,h.jsx)(`video`,{src:R,autoPlay:U,loop:W,muted:G,playsInline:!0,controls:!U,style:{width:`100%`,height:`auto`,display:`block`,borderRadius:`12px`}}):(0,h.jsx)(t,{component:`img`,src:R,alt:z,sx:{width:`100%`,height:`auto`,display:`block`,borderRadius:2}}));return(0,h.jsx)(d,{className:g,devInfoTags:v,fullWidth:j,nested:_,uid:y,spaceBefore:b,spaceAfter:x,children:(0,h.jsx)(`div`,{className:`vitec_introparagraph__inner vitec_introparagraph--${k}`,children:V?(0,h.jsxs)(m,{container:!0,spacing:4,alignItems:`center`,children:[(0,h.jsx)(m,{size:{xs:12,md:6},sx:B===`left`?{order:{md:2}}:{},children:K}),(0,h.jsx)(m,{size:{xs:12,md:6},sx:B===`left`?{order:{md:1}}:{},children:q})]}):(0,h.jsxs)(h.Fragment,{children:[K,H&&q&&(0,h.jsx)(t,{sx:{mt:3,mx:B===`center`?`auto`:void 0,maxWidth:B===`center`?720:`100%`},children:q})]})})})}export{g as VitecIntroParagraph};

View File

@@ -0,0 +1 @@
import{A as e,C as t,E as n,O as r,T as i,b as a,d as o,g as s,m as c,ot as l,r as u,t as d,u as f,v as p,y as m}from"./index-CM0zIqrs.js";var h=l();function g({element:l,className:g,nested:_,devInfoTags:v,uid:y,spaceBefore:b,spaceAfter:x}){let S=o(l),C=c(l),w=f(l),T=s(l.data?.button_link),E=p(l.data?.button_link),D=l.data?.button_text,O=m(l.data?.button_style),k=l.data?.intro_layout_variant??`variant_1`,A=a(l.data?.header_position)===`center`||k===`variant_1`,j=!!l.data?.fullwidth,M=l.data?.intro_video??[],N=l.data?.intro_image??[],P=M.length>0,F=N.length>0,I=P||F,L=P,R=P?M[0]?.publicUrl:N[0]?.publicUrl,z=!P&&N[0]&&(N[0].alt||N[0].title||S)||``,B=(P?l.data?.intro_video_alignment:l.data?.intro_image_alignment)??`right`,V=I&&(B===`left`||B===`right`),H=I&&(B===`center`||B===`fullwidth`),U=!!l.data?.intro_video_autoplay,W=!!l.data?.intro_video_loop,G=!!l.data?.intro_video_muted||U,K=(0,h.jsxs)(i,{spacing:2.5,sx:{textAlign:A&&!I?`center`:`left`,alignItems:A&&!I?`center`:`flex-start`,maxWidth:I?void 0:760,mx:A&&!I?`auto`:void 0},children:[S&&(0,h.jsx)(e,{variant:`h3`,children:S}),C&&(0,h.jsx)(e,{variant:`subtitle1`,color:`text.secondary`,children:C}),w&&(0,h.jsx)(r,{sx:t,dangerouslySetInnerHTML:{__html:w}}),T&&(0,h.jsx)(d,{href:T,target:E??`_self`,variant:O,children:D??`Mehr erfahren`})]}),q=I&&R&&(L?(0,h.jsx)(`video`,{src:R,autoPlay:U,loop:W,muted:G,playsInline:!0,controls:!U,style:{width:`100%`,height:`auto`,display:`block`,borderRadius:`12px`}}):(0,h.jsx)(r,{component:`img`,src:R,alt:z,sx:{width:`100%`,height:`auto`,display:`block`,borderRadius:2}}));return(0,h.jsx)(u,{className:g,devInfoTags:v,fullWidth:j,nested:_,uid:y,spaceBefore:b,spaceAfter:x,children:(0,h.jsx)(`div`,{className:`vitec_introparagraph__inner vitec_introparagraph--${k}`,children:V?(0,h.jsxs)(n,{container:!0,spacing:4,alignItems:`center`,children:[(0,h.jsx)(n,{size:{xs:12,md:6},sx:B===`left`?{order:{md:2}}:{},children:K}),(0,h.jsx)(n,{size:{xs:12,md:6},sx:B===`left`?{order:{md:1}}:{},children:q})]}):(0,h.jsxs)(h.Fragment,{children:[K,H&&q&&(0,h.jsx)(r,{sx:{mt:3,mx:B===`center`?`auto`:void 0,maxWidth:B===`center`?720:`100%`},children:q})]})})})}export{g as VitecIntroParagraph};

View File

@@ -0,0 +1,2 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/vitec_locationmap-CO_jtYJV.js","assets/index-CM0zIqrs.js","assets/index-B6-rfpgz.css"])))=>i.map(i=>d[i]);
import{A as e,d as t,f as n,lt as r,ot as i,r as a,ut as o,x as s}from"./index-CM0zIqrs.js";var c=o(),l=i(),u=(0,c.lazy)(()=>r(()=>import(`./vitec_locationmap-CO_jtYJV.js`),__vite__mapDeps([0,1,2])).then(e=>({default:e.VitecGlobeMap}))),d=e=>e.toUpperCase().split(``).map(e=>String.fromCodePoint(e.charCodeAt(0)+127397)).join(``);function f(e){let t=e.address;if(!t)return[];let n=[];t.company&&n.push(t.company),t.street?n.push(t.street+(t.additional?`, `+t.additional:``)):t.additional&&n.push(t.additional);let r=[t.postalCode,t.city,t.region].filter(Boolean).join(` `);return r&&n.push(r),t.country&&n.push(t.country),n}function p(){return(0,l.jsx)(`svg`,{width:`13`,height:`13`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,children:(0,l.jsx)(`path`,{d:`M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.49 12 19.79 19.79 0 0 1 1.47 3.55 2 2 0 0 1 3.44 1.35h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L7.91 9a16 16 0 0 0 6.09 6.09l1.27-.78a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 16.92z`})})}function m(){return(0,l.jsxs)(`svg`,{width:`13`,height:`13`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,children:[(0,l.jsx)(`polyline`,{points:`22 17 13 17`}),(0,l.jsx)(`rect`,{x:`2`,y:`3`,width:`8`,height:`18`,rx:`1`}),(0,l.jsx)(`path`,{d:`M22 17l-5-5V7a2 2 0 0 0-2-2h-1`}),(0,l.jsx)(`line`,{x1:`6`,y1:`8`,x2:`6`,y2:`8`})]})}function h({loc:e}){let t=f(e),n=e.countryCode?d(e.countryCode):``,r=e.links?.contact??`/contact`;return(0,l.jsxs)(`div`,{className:`vitec_locationcard`,children:[(0,l.jsxs)(`div`,{className:`vitec_locationcard__header`,children:[n&&(0,l.jsx)(`span`,{className:`vitec_locationcard__flag`,"aria-hidden":`true`,children:n}),(0,l.jsx)(`span`,{className:`vitec_locationcard__name`,children:e.name})]}),(0,l.jsxs)(`div`,{className:`vitec_locationcard__body`,children:[t.length>0&&(0,l.jsx)(`address`,{className:`vitec_locationcard__address`,children:t.map((e,t)=>(0,l.jsx)(`span`,{children:e},t))}),(e.contact?.phone||e.contact?.fax)&&(0,l.jsxs)(`div`,{className:`vitec_locationcard__contact`,children:[e.contact.phone&&(0,l.jsxs)(`a`,{href:`tel:${e.contact.phone.replace(/\s/g,``)}`,className:`vitec_locationcard__contact-line`,children:[(0,l.jsx)(p,{}),e.contact.phone]}),e.contact.fax&&(0,l.jsxs)(`span`,{className:`vitec_locationcard__contact-line vitec_locationcard__contact-line--fax`,children:[(0,l.jsx)(m,{}),e.contact.fax]})]})]}),(0,l.jsx)(`div`,{className:`vitec_locationcard__footer`,children:(0,l.jsx)(`a`,{href:r,className:`vitec_locationcard__cta`,children:`Contact us`})})]})}function g({loc:e}){let t=f(e),n=e.countryCode?d(e.countryCode):``,r=e.links?.contact??`/contact`;return(0,l.jsxs)(`div`,{className:`vitec_locationrow`,children:[(0,l.jsxs)(`div`,{className:`vitec_locationrow__identity`,children:[n&&(0,l.jsx)(`span`,{className:`vitec_locationrow__flag`,"aria-hidden":`true`,children:n}),(0,l.jsx)(`span`,{className:`vitec_locationrow__name`,children:e.name})]}),t.length>0&&(0,l.jsx)(`address`,{className:`vitec_locationrow__address`,children:t.map((e,t)=>(0,l.jsx)(`span`,{children:e},t))}),(0,l.jsxs)(`div`,{className:`vitec_locationrow__right`,children:[(e.contact?.phone||e.contact?.fax)&&(0,l.jsxs)(`div`,{className:`vitec_locationrow__contact`,children:[e.contact.phone&&(0,l.jsxs)(`a`,{href:`tel:${e.contact.phone.replace(/\s/g,``)}`,className:`vitec_locationrow__contact-line`,children:[(0,l.jsx)(p,{}),e.contact.phone]}),e.contact.fax&&(0,l.jsxs)(`span`,{className:`vitec_locationrow__contact-line vitec_locationrow__contact-line--fax`,children:[(0,l.jsx)(m,{}),e.contact.fax]})]}),(0,l.jsx)(`a`,{href:r,className:`vitec_locationrow__cta`,children:`Contact →`})]})]})}function _({element:r,className:i,nested:o,devInfoTags:d,uid:f,spaceBefore:p,spaceAfter:m}){let _=t(r),v=Number(n(r)??0),y=s(v===0?2:v),b=r.content?.locations,x=b?.variant??`list`,S=(b?.locations??[]).filter(e=>e.active!==!1);return x===`map`?(0,l.jsx)(a,{className:i,devInfoTags:d,fullWidth:!0,nested:o,uid:f,spaceBefore:p,spaceAfter:m,children:(0,l.jsx)(c.Suspense,{fallback:(0,l.jsx)(`div`,{className:`vitec_locationmap__loading`}),children:(0,l.jsx)(u,{locations:S,mapText:b?.mapText})})}):(0,l.jsx)(a,{className:i,devInfoTags:d,nested:o,uid:f,spaceBefore:p,spaceAfter:m,children:(0,l.jsxs)(`div`,{className:`vitec_locationlist__inner`,children:[_&&(0,l.jsx)(e,{variant:y,className:`vitec_locationlist__heading`,children:_}),x===`grid`?(0,l.jsx)(`div`,{className:`vitec_locationlist__grid`,children:S.map(e=>(0,l.jsx)(h,{loc:e},e.id??e.slug))}):(0,l.jsx)(`div`,{className:`vitec_locationlist__list`,children:S.map(e=>(0,l.jsx)(g,{loc:e},e.id??e.slug))})]})})}export{_ as VitecLocationList};

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
import{A as e,C as t,E as n,O as r,T as i,k as a,ot as o,r as s}from"./index-CM0zIqrs.js";var c=o();function l({element:o,className:l,nested:u,devInfoTags:d,uid:f,spaceBefore:p,spaceAfter:m}){let h=o.content?.market?.market,g=o.appearance?.frameClass?.split(`,`).map(e=>e.trim()).includes(`vitec-full-width`)??!1,_=h?.image?.url,v=h?.image?.srcset?.map(e=>`${e.url} ${e.descriptor}`).join(`, `);return(0,c.jsx)(s,{className:l,devInfoTags:d,fullWidth:g,nested:u,uid:f,spaceBefore:p,spaceAfter:m,children:(0,c.jsx)(`div`,{className:`vitec_marketshow__inner`,children:(0,c.jsxs)(n,{container:!0,spacing:3,alignItems:`start`,children:[_&&(0,c.jsx)(n,{size:{xs:12,md:5},children:(0,c.jsx)(r,{component:`img`,src:_,srcSet:v,sizes:`(max-width: 900px) 100vw, 40vw`,alt:h?.title??`Market`,sx:{width:`100%`,height:`auto`,display:`block`,borderRadius:3}})}),(0,c.jsx)(n,{size:{xs:12,md:_?7:12},children:(0,c.jsxs)(i,{spacing:2,children:[(0,c.jsx)(e,{variant:`overline`,color:`text.secondary`,children:`Market`}),h?.title&&(0,c.jsx)(e,{variant:`h4`,children:h.title}),h?.subtitle&&(0,c.jsx)(e,{variant:`subtitle1`,color:`text.secondary`,children:h.subtitle}),h?.teaser&&(0,c.jsx)(e,{variant:`body1`,children:h.teaser}),h?.description&&(0,c.jsx)(r,{sx:t,dangerouslySetInnerHTML:{__html:h.description}}),h?.categories&&h.categories.length>0&&(0,c.jsx)(i,{direction:`row`,spacing:.75,flexWrap:`wrap`,useFlexGap:!0,children:h.categories.map(e=>(0,c.jsx)(a,{label:e.title??`Category`,size:`small`},e.uid??e.title))})]})})]})})})}export{l as VitecMarketShow};

View File

@@ -1 +0,0 @@
import{C as e,D as t,O as n,Z as r,b as i,k as a,n as o,w as s}from"./index-7LYai_r-.js";var c=r();function l({element:r,className:l,nested:u,devInfoTags:d,uid:f,spaceBefore:p,spaceAfter:m}){let h=r.content?.market?.market,g=r.appearance?.frameClass?.split(`,`).map(e=>e.trim()).includes(`vitec-full-width`)??!1,_=h?.image?.url,v=h?.image?.srcset?.map(e=>`${e.url} ${e.descriptor}`).join(`, `);return(0,c.jsx)(o,{className:l,devInfoTags:d,fullWidth:g,nested:u,uid:f,spaceBefore:p,spaceAfter:m,children:(0,c.jsx)(`div`,{className:`vitec_marketshow__inner`,children:(0,c.jsxs)(s,{container:!0,spacing:3,alignItems:`start`,children:[_&&(0,c.jsx)(s,{size:{xs:12,md:5},children:(0,c.jsx)(t,{component:`img`,src:_,srcSet:v,sizes:`(max-width: 900px) 100vw, 40vw`,alt:h?.title??`Market`,sx:{width:`100%`,height:`auto`,display:`block`,borderRadius:3}})}),(0,c.jsx)(s,{size:{xs:12,md:_?7:12},children:(0,c.jsxs)(e,{spacing:2,children:[(0,c.jsx)(a,{variant:`overline`,color:`text.secondary`,children:`Market`}),h?.title&&(0,c.jsx)(a,{variant:`h4`,children:h.title}),h?.subtitle&&(0,c.jsx)(a,{variant:`subtitle1`,color:`text.secondary`,children:h.subtitle}),h?.teaser&&(0,c.jsx)(a,{variant:`body1`,children:h.teaser}),h?.description&&(0,c.jsx)(t,{sx:i,dangerouslySetInnerHTML:{__html:h.description}}),h?.categories&&h.categories.length>0&&(0,c.jsx)(e,{direction:`row`,spacing:.75,flexWrap:`wrap`,useFlexGap:!0,children:h.categories.map(e=>(0,c.jsx)(n,{label:e.title??`Category`,size:`small`},e.uid??e.title))})]})})]})})})}export{l as VitecMarketShow};

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
import{C as e,D as t,N as n,O as r,S as i,V as a,Z as o,_ as s,a as c,c as l,d as u,f as d,k as f,n as p,u as m,w as h}from"./index-7LYai_r-.js";var g=o();function _({element:o,className:_,nested:v,devInfoTags:y,uid:b,spaceBefore:x,spaceAfter:S}){let C=l(o),w=u(o),T=s(m(o)),E=T===`center`?`center`:T===`right`?`right`:`left`,D=T===`center`?`center`:T===`right`?`flex-end`:`flex-start`,O=o.appearance?.frameClass?.split(`,`).map(e=>e.trim()).includes(`vitec-full-width`)??!1,k=o.content?.products?.products??[],A=o.content!=null;return(0,g.jsx)(p,{className:_,devInfoTags:y,fullWidth:O,nested:v,uid:b,spaceBefore:x,spaceAfter:S,children:(0,g.jsxs)(`div`,{className:`vitec_productlist__inner`,children:[(C||w)&&(0,g.jsxs)(e,{spacing:.5,sx:{width:`100%`,textAlign:E,alignItems:D},children:[C&&(0,g.jsx)(f,{variant:`h4`,children:C}),w&&(0,g.jsx)(f,{variant:`subtitle1`,color:`text.secondary`,children:w})]}),!A&&(0,g.jsxs)(f,{variant:`body2`,color:`text.disabled`,sx:{fontStyle:`italic`},children:[`No product data — plugin not configured or no products assigned (UID `,b??`?`,`).`]}),(0,g.jsx)(h,{container:!0,spacing:3,children:k.map(o=>{let s=d(o.images),l=a(o.link,c.base_url,c.router_mode);return(0,g.jsx)(h,{size:{xs:12,md:6,lg:4},children:(0,g.jsx)(n,{variant:`outlined`,sx:{height:`100%`,p:2.5,borderRadius:3},children:(0,g.jsxs)(e,{spacing:2,sx:{height:`100%`},children:[s&&(0,g.jsx)(t,{component:`img`,src:s,alt:o.title??`Product image`,sx:{width:`100%`,height:180,objectFit:`cover`,borderRadius:2}}),(0,g.jsxs)(e,{spacing:.75,children:[(0,g.jsx)(f,{variant:`h6`,children:o.title??`Product`}),o.subtitle&&(0,g.jsx)(f,{color:`text.secondary`,children:o.subtitle}),o.teaser&&(0,g.jsx)(f,{children:o.teaser})]}),o.categories&&o.categories.length>0&&(0,g.jsx)(e,{direction:`row`,spacing:.75,flexWrap:`wrap`,useFlexGap:!0,children:o.categories.map(e=>(0,g.jsx)(r,{label:e.title??`Category`,size:`small`},e.uid??e.title))}),(0,g.jsx)(t,{sx:{flexGrow:1}}),l&&(0,g.jsx)(i,{href:l,variant:`outlined`,size:`sm`,alignStart:!0,children:`View product`})]})})},o.uid??o.slug??o.title)})})]})})}export{_ as VitecProductList};

Some files were not shown because too many files have changed in this diff Show More