diff --git a/packages/vitec/Classes/Service/UsecaseSerializer.php b/packages/vitec/Classes/Service/UsecaseSerializer.php
index 1b669cf..63bb09c 100755
--- a/packages/vitec/Classes/Service/UsecaseSerializer.php
+++ b/packages/vitec/Classes/Service/UsecaseSerializer.php
@@ -51,6 +51,12 @@ final class UsecaseSerializer
'teaser' => (string)($u['teaser'] ?? ''),
'cardImage' => $this->image($uid, 'card_image', null, true),
'customerLogo' => $this->image($uid, 'customer_logo'),
+ // Testimonial data (2026-10-01): one quote per story, maintained
+ // on the record, consumed by testimonial cards (sub-market
+ // template). Empty strings when not yet maintained.
+ 'quote' => (string)($u['quote'] ?? ''),
+ 'quotePerson' => (string)($u['quote_person'] ?? ''),
+ 'quoteRole' => (string)($u['quote_role'] ?? ''),
'markets' => $this->relationMulti('tx_vitec_usecase_market_mm', 'tx_vitec_domain_model_market', $uid),
'categories' => $this->categories($uid),
'featured' => (bool)($u['featured'] ?? false),
diff --git a/packages/vitec/Classes/UserFunc/ProductFeatureJsonRenderer.php b/packages/vitec/Classes/UserFunc/ProductFeatureJsonRenderer.php
new file mode 100644
index 0000000..73fe9b2
--- /dev/null
+++ b/packages/vitec/Classes/UserFunc/ProductFeatureJsonRenderer.php
@@ -0,0 +1,58 @@
+cObj?->data ?? [];
+ $flexFormData = GeneralUtility::makeInstance(FlexFormService::class)
+ ->convertFlexFormContentToArray((string)($row['pi_flexform'] ?? ''));
+ $productUid = (int)($flexFormData['settings']['product'] ?? 0);
+ if ($productUid <= 0) {
+ return (string)json_encode(null);
+ }
+
+ $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+ ->getQueryBuilderForTable('tx_vitec_domain_model_product');
+ $product = $queryBuilder->select('*')->from('tx_vitec_domain_model_product')
+ ->where(
+ $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
+ $queryBuilder->expr()->eq('deleted', 0),
+ $queryBuilder->expr()->eq('hidden', 0),
+ $queryBuilder->expr()->eq('hideonwebsite', 0)
+ )
+ ->executeQuery()->fetchAssociative();
+ if (!$product) {
+ return (string)json_encode(null);
+ }
+
+ return (string)json_encode($this->serializeProduct($product), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
+ } catch (\Throwable) {
+ return (string)json_encode(null);
+ }
+ }
+}
diff --git a/packages/vitec/Classes/UserFunc/ProductListJsonRenderer.php b/packages/vitec/Classes/UserFunc/ProductListJsonRenderer.php
index bb0aacf..bb8714a 100755
--- a/packages/vitec/Classes/UserFunc/ProductListJsonRenderer.php
+++ b/packages/vitec/Classes/UserFunc/ProductListJsonRenderer.php
@@ -31,7 +31,9 @@ use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
*/
class ProductListJsonRenderer
{
- private ?ContentObjectRenderer $cObj = null;
+ // protected (not private) since 2026-10-01: ProductFeatureJsonRenderer
+ // extends this class to reuse serializeProduct() and needs the cObj.
+ protected ?ContentObjectRenderer $cObj = null;
/**
* TYPO3 v14 hands the ContentObjectRenderer over through this setter only -
@@ -119,6 +121,14 @@ class ProductListJsonRenderer
}
$debugMode = (bool)($settings['debug'] ?? false);
$allProducts = (bool)($settings['allproducts'] ?? false);
+ // Explicit picks (2026-10-01, built for the sub-market template's
+ // Products section): when set, EXACTLY these products in the
+ // editor's order - the category filter is ignored. The visibility
+ // filters below stay in force on purpose, so a hidden/legacy
+ // record never surfaces through a stale selection.
+ $selectedUids = array_filter(
+ array_map('intval', explode(',', (string)($settings['selectedproducts'] ?? '')))
+ );
// Extbase repositories don't work in UserFunc context — direct query.
$productQueryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
@@ -137,7 +147,11 @@ class ProductListJsonRenderer
$productQueryBuilder->expr()->eq('p.subproduct', 0)
);
- if (!empty($categoryUids) && !$allProducts) {
+ if ($selectedUids !== [] && !$allProducts) {
+ $productQuery->andWhere(
+ $productQueryBuilder->expr()->in('p.uid', $productQueryBuilder->createNamedParameter($selectedUids, Connection::PARAM_INT_ARRAY))
+ );
+ } elseif (!empty($categoryUids) && !$allProducts) {
$productQuery
->join(
'p',
@@ -159,7 +173,12 @@ class ProductListJsonRenderer
// sitemap. $categoryUids comes from expandWithDescendants in
// depth-first tree order; within one category the uid order is
// kept - the records were created in sitemap-V2 row order.
- if (!empty($categoryUids) && !$allProducts && $products !== []) {
+ if ($selectedUids !== [] && !$allProducts && $products !== []) {
+ // Editor's order, not storage order.
+ $rankBySelection = array_flip($selectedUids);
+ usort($products, static fn(array $a, array $b): int =>
+ ($rankBySelection[(int)$a['uid']] ?? PHP_INT_MAX) <=> ($rankBySelection[(int)$b['uid']] ?? PHP_INT_MAX));
+ } elseif (!empty($categoryUids) && !$allProducts && $products !== []) {
$rankByCategory = array_flip($categoryUids);
$mmQueryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getQueryBuilderForTable('sys_category_record_mm');
diff --git a/packages/vitec/Classes/View/BackendLayoutDataProvider.php b/packages/vitec/Classes/View/BackendLayoutDataProvider.php
index c21887d..812b6eb 100755
--- a/packages/vitec/Classes/View/BackendLayoutDataProvider.php
+++ b/packages/vitec/Classes/View/BackendLayoutDataProvider.php
@@ -27,6 +27,15 @@ final class BackendLayoutDataProvider implements DataProviderInterface
'hero' => 1,
'sidebar' => 2,
'preFooter' => 3,
+ // Sub-market template sections (spec 2026-10-01): one slot per Figma
+ // section. The headless JSON groups these as colPos110..colPos160 -
+ // that grouping IS the front-end contract for layout 21.
+ 'smHero' => 110,
+ 'smIntro' => 120,
+ 'smWorkflow' => 130,
+ 'smTestimonials' => 140,
+ 'smProducts' => 150,
+ 'smInsights' => 160,
];
/** Per-frontend-layout: title + ordered list of zones. */
@@ -52,6 +61,7 @@ final class BackendLayoutDataProvider implements DataProviderInterface
18 => ['title' => 'Content Page V3', 'zones' => ['hero', 'main', 'sidebar', 'preFooter']],
19 => ['title' => 'Events Overview', 'zones' => ['hero', 'main']],
20 => ['title' => 'Contact Page', 'zones' => ['hero', 'main', 'sidebar']],
+ 21 => ['title' => 'Sub-Market Page', 'zones' => ['smHero', 'smIntro', 'smWorkflow', 'smTestimonials', 'smProducts', 'smInsights']],
];
/** Display name for each zone. Shown as column header in the BE page module. */
@@ -60,6 +70,12 @@ final class BackendLayoutDataProvider implements DataProviderInterface
'hero' => 'Hero',
'sidebar' => 'Sidebar',
'preFooter' => 'Pre-Footer',
+ 'smHero' => '1 · Hero',
+ 'smIntro' => '2 · Intro',
+ 'smWorkflow' => '3 · Workflow (header + flip cards)',
+ 'smTestimonials' => '4 · Testimonials',
+ 'smProducts' => '5 · Products (featured + list)',
+ 'smInsights' => '6 · Industry Insights',
];
public function getIdentifier(): string
diff --git a/packages/vitec/Configuration/FlexForms/Productfeature.xml b/packages/vitec/Configuration/FlexForms/Productfeature.xml
new file mode 100644
index 0000000..6637bad
--- /dev/null
+++ b/packages/vitec/Configuration/FlexForms/Productfeature.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+ Featured Product
+ array
+
+
+ Product
+ The product shown as the big featured card. Image, title and teaser come from the record.
+
+ group
+ tx_vitec_domain_model_product
+ 1
+ 1
+
+
+
+
+
+
+
diff --git a/packages/vitec/Configuration/FlexForms/Productlist.xml b/packages/vitec/Configuration/FlexForms/Productlist.xml
index af4a7c0..205b152 100644
--- a/packages/vitec/Configuration/FlexForms/Productlist.xml
+++ b/packages/vitec/Configuration/FlexForms/Productlist.xml
@@ -30,6 +30,16 @@
+
+ Hand-picked products
+ When set, the list shows EXACTLY these products in this order - the category selection below is ignored. Hidden/legacy products stay suppressed. Used e.g. by the sub-market template's Products section.
+
+ group
+ tx_vitec_domain_model_product
+ 6
+ 50
+
+
Select Category
diff --git a/packages/vitec/Configuration/FlexForms/Usecaselist.xml b/packages/vitec/Configuration/FlexForms/Usecaselist.xml
index cf4dc0b..63a17b4 100644
--- a/packages/vitec/Configuration/FlexForms/Usecaselist.xml
+++ b/packages/vitec/Configuration/FlexForms/Usecaselist.xml
@@ -29,6 +29,10 @@
50 / 50
50-50
+
+ Testimonials (quote carousel, sub-market template)
+ testimonials
+
grid
diff --git a/packages/vitec/Configuration/Sets/Vitecset/setup.typoscript b/packages/vitec/Configuration/Sets/Vitecset/setup.typoscript
index fa086c3..0a88f38 100755
--- a/packages/vitec/Configuration/Sets/Vitecset/setup.typoscript
+++ b/packages/vitec/Configuration/Sets/Vitecset/setup.typoscript
@@ -67,6 +67,21 @@ tt_content {
}
}
+ # Featured product card (sub-market template section 5): one referenced
+ # product as a full product card under `productfeature` - same shape as
+ # the list items, serialization shared with ProductListJsonRenderer.
+ vitec_productfeature < lib.contentElementWithHeader
+ vitec_productfeature {
+ fields {
+ content {
+ fields {
+ productfeature = USER
+ productfeature.userFunc = Evomedien\Vitec\UserFunc\ProductFeatureJsonRenderer->render
+ }
+ }
+ }
+ }
+
# Product Finder - only the category hierarchy for the filter selects.
# Placed once above the product lists; the front end filters the products
# those lists already delivered, client side, via ?cat= and ?subcat=.
diff --git a/packages/vitec/Configuration/TCA/Overrides/pages.php b/packages/vitec/Configuration/TCA/Overrides/pages.php
index a9a5b04..94c2726 100755
--- a/packages/vitec/Configuration/TCA/Overrides/pages.php
+++ b/packages/vitec/Configuration/TCA/Overrides/pages.php
@@ -31,6 +31,7 @@ defined('TYPO3') or die();
['label' => $l . 'layout.18', 'value' => '18'],
['label' => $l . 'layout.19', 'value' => '19'],
['label' => $l . 'layout.20', 'value' => '20'],
+ ['label' => $l . 'layout.21', 'value' => '21'],
];
$GLOBALS['TCA']['pages']['columns']['layout']['config']['default'] = '0';
diff --git a/packages/vitec/Configuration/TCA/Overrides/tt_content.php b/packages/vitec/Configuration/TCA/Overrides/tt_content.php
index 6cf163a..f121e24 100644
--- a/packages/vitec/Configuration/TCA/Overrides/tt_content.php
+++ b/packages/vitec/Configuration/TCA/Overrides/tt_content.php
@@ -26,6 +26,7 @@ use TYPO3\CMS\Extbase\Utility\ExtensionUtility;
$registerPluginWithFlexForm('Productlist', 'Show Products by Category', 'FILE:EXT:vitec/Configuration/FlexForms/Productlist.xml', 'vitec-plugin-productlist');
$registerPluginWithFlexForm('Productfinder', 'Product Finder (filter data)', 'FILE:EXT:vitec/Configuration/FlexForms/Productfinder.xml', 'vitec-plugin-productlist');
+ $registerPluginWithFlexForm('Productfeature', 'Featured Product Card', 'FILE:EXT:vitec/Configuration/FlexForms/Productfeature.xml', 'vitec-plugin-productlist');
$registerPluginWithFlexForm('Simplecard', 'Simple Card', 'FILE:EXT:vitec/Configuration/FlexForms/Simplecard.xml', 'vitec-plugin-simplecard');
$registerPluginWithFlexForm('Productshow', 'Show Single Product', 'FILE:EXT:vitec/Configuration/FlexForms/Productshow.xml', 'vitec-plugin-productshow');
$registerPluginWithFlexForm('Usecaseshow', 'Single Success Story', 'FILE:EXT:vitec/Configuration/FlexForms/Usecase.xml', 'vitec-plugin-usecaseshow');
diff --git a/packages/vitec/Configuration/TCA/tx_vitec_domain_model_usecase.php b/packages/vitec/Configuration/TCA/tx_vitec_domain_model_usecase.php
index 72c6724..92e1083 100644
--- a/packages/vitec/Configuration/TCA/tx_vitec_domain_model_usecase.php
+++ b/packages/vitec/Configuration/TCA/tx_vitec_domain_model_usecase.php
@@ -29,6 +29,8 @@ return [
title, slug, detail_page, subtitle,
--div--;List view,
card_image, customer_logo, teaser,
+ --div--;Testimonial,
+ quote, quote_person, quote_role,
--div--;Detail · Hero,
hero_bgimage, hero_small_image, hero_video, hero_overlay_color, hero_overlay_opacity,
--div--;Detail · Content,
@@ -153,6 +155,26 @@ return [
'config' => ['type' => 'text', 'cols' => 40, 'rows' => 4, 'eval' => 'trim'],
],
+ // ------------------------------------------------------- Testimonial
+ // One quote per story, maintained editorially and reused wherever the
+ // story appears as a testimonial card (first consumer: the sub-market
+ // template, decision Olli 2026-10-01 - the customer's XLS only picks
+ // the story, never carries quote text).
+ 'quote' => [
+ 'label' => 'Quote',
+ 'description' => 'Shown on testimonial cards wherever this story is referenced. Plain text, no quotation marks - the front end adds them.',
+ 'config' => ['type' => 'text', 'cols' => 40, 'rows' => 4, 'eval' => 'trim'],
+ ],
+ 'quote_person' => [
+ 'label' => 'Quote: person',
+ 'config' => ['type' => 'input', 'size' => 30, 'max' => 255, 'eval' => 'trim'],
+ ],
+ 'quote_role' => [
+ 'label' => 'Quote: role / title',
+ 'description' => 'E.g. "Vice President of Technology, KCR" - shown below the name.',
+ 'config' => ['type' => 'input', 'size' => 30, 'max' => 255, 'eval' => 'trim'],
+ ],
+
// ------------------------------------------------------ Detail · Hero
'hero_bgimage' => [
'label' => 'Background Image',
diff --git a/packages/vitec/Configuration/page.tsconfig b/packages/vitec/Configuration/page.tsconfig
index 1894d47..2cfc528 100755
--- a/packages/vitec/Configuration/page.tsconfig
+++ b/packages/vitec/Configuration/page.tsconfig
@@ -16,6 +16,14 @@ mod {
CType = vitec_productlist
}
}
+ productfeature {
+ iconIdentifier = vitec-plugin-productlist
+ title = Featured Product Card
+ description = One product as the big featured card (sub-market template). All copy comes from the record.
+ tt_content_defValues {
+ CType = vitec_productfeature
+ }
+ }
productfinder {
iconIdentifier = vitec-plugin-productlist
title = Product Finder (filter data)
diff --git a/packages/vitec/ContentBlocks/ContentElements/market-hero/assets/icon.svg b/packages/vitec/ContentBlocks/ContentElements/market-hero/assets/icon.svg
new file mode 100644
index 0000000..ffd00da
--- /dev/null
+++ b/packages/vitec/ContentBlocks/ContentElements/market-hero/assets/icon.svg
@@ -0,0 +1,7 @@
+
+
+ ¶
+
+
+
+
diff --git a/packages/vitec/ContentBlocks/ContentElements/market-hero/config.yaml b/packages/vitec/ContentBlocks/ContentElements/market-hero/config.yaml
new file mode 100644
index 0000000..794b039
--- /dev/null
+++ b/packages/vitec/ContentBlocks/ContentElements/market-hero/config.yaml
@@ -0,0 +1,45 @@
+# Sub-market template section 1 (Figma "Sub-Market Page", spec 2026-10-01).
+# The H1 is NOT a field - it is the page title (dev names are canonical).
+name: vitec/market-hero
+group: vitec_custom_components
+prefixFields: true
+prefixType: vendor
+
+fields:
+ - identifier: subline
+ type: Text
+ max: 120
+
+ - identifier: cta_section
+ type: Palette
+ label: CTA button
+ fields:
+ - identifier: cta_label
+ type: Text
+ max: 40
+ - identifier: cta_link
+ type: Link
+
+ - identifier: bgimage
+ type: File
+ minitems: 0
+ maxitems: 1
+ allowed: common-image-types
+
+ - identifier: video_section
+ type: Palette
+ label: Video teaser (optional - empty url hides the card)
+ fields:
+ - identifier: video_title
+ type: Text
+ max: 80
+ - type: Linebreak
+ - identifier: video_url
+ type: Text
+ max: 500
+ - type: Linebreak
+ - identifier: video_cover
+ type: File
+ minitems: 0
+ maxitems: 1
+ allowed: common-image-types
diff --git a/packages/vitec/ContentBlocks/ContentElements/market-hero/language/labels.xlf b/packages/vitec/ContentBlocks/ContentElements/market-hero/language/labels.xlf
new file mode 100644
index 0000000..c8ff6b9
--- /dev/null
+++ b/packages/vitec/ContentBlocks/ContentElements/market-hero/language/labels.xlf
@@ -0,0 +1,21 @@
+
+
+
+
+ VITEC · Market Hero
+ Sub-market template, section 1: subline, CTA, background image, optional video teaser. The H1 is the page title.
+ Subline
+ Max ~90 characters visible (two lines in the design).
+ CTA label
+ Empty = front-end default "Get in touch".
+ CTA target
+ Empty = /contact.
+ Background image
+ 1920x600, dark enough for white text. Empty = dark gradient fallback.
+ Video title
+ Video URL
+ YouTube/Vimeo or file URL. Empty = no video card, hero runs full width.
+ Video cover image
+
+
+
diff --git a/packages/vitec/ContentBlocks/ContentElements/market-hero/templates/backend-preview.html b/packages/vitec/ContentBlocks/ContentElements/market-hero/templates/backend-preview.html
new file mode 100644
index 0000000..2475685
--- /dev/null
+++ b/packages/vitec/ContentBlocks/ContentElements/market-hero/templates/backend-preview.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+ Market Hero — {data.vitec_subline}
+ · 🎬 {data.vitec_video_title}
+
+
+
+
diff --git a/packages/vitec/ContentBlocks/ContentElements/market-hero/templates/frontend.html b/packages/vitec/ContentBlocks/ContentElements/market-hero/templates/frontend.html
new file mode 100644
index 0000000..da9c5f0
--- /dev/null
+++ b/packages/vitec/ContentBlocks/ContentElements/market-hero/templates/frontend.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/packages/vitec/ContentBlocks/ContentElements/market-insights/assets/icon.svg b/packages/vitec/ContentBlocks/ContentElements/market-insights/assets/icon.svg
new file mode 100644
index 0000000..ffd00da
--- /dev/null
+++ b/packages/vitec/ContentBlocks/ContentElements/market-insights/assets/icon.svg
@@ -0,0 +1,7 @@
+
+
+ ¶
+
+
+
+
diff --git a/packages/vitec/ContentBlocks/ContentElements/market-insights/config.yaml b/packages/vitec/ContentBlocks/ContentElements/market-insights/config.yaml
new file mode 100644
index 0000000..383df9c
--- /dev/null
+++ b/packages/vitec/ContentBlocks/ContentElements/market-insights/config.yaml
@@ -0,0 +1,50 @@
+# Sub-market template section 6 (spec 2026-10-01).
+name: vitec/market-insights
+group: vitec_custom_components
+prefixFields: true
+prefixType: vendor
+
+fields:
+ - identifier: text1
+ type: Textarea
+ rows: 4
+
+ - identifier: text2
+ type: Textarea
+ rows: 4
+
+ - identifier: cta_section
+ type: Palette
+ label: CTA (optional)
+ fields:
+ - identifier: cta_label
+ type: Text
+ max: 40
+ - identifier: cta_target
+ type: Text
+ max: 500
+
+ - identifier: video_section
+ type: Palette
+ label: Video card (optional - empty url hides the card)
+ fields:
+ - identifier: video_person
+ type: Text
+ max: 80
+ - identifier: video_role
+ type: Text
+ max: 120
+ - type: Linebreak
+ - identifier: video_title
+ type: Text
+ max: 120
+ - type: Linebreak
+ - identifier: video_url
+ type: Text
+ max: 500
+ - type: Linebreak
+ - identifier: video_cover
+ type: File
+ minitems: 0
+ maxitems: 1
+ allowed: common-image-types
diff --git a/packages/vitec/ContentBlocks/ContentElements/market-insights/language/labels.xlf b/packages/vitec/ContentBlocks/ContentElements/market-insights/language/labels.xlf
new file mode 100644
index 0000000..9cec40d
--- /dev/null
+++ b/packages/vitec/ContentBlocks/ContentElements/market-insights/language/labels.xlf
@@ -0,0 +1,23 @@
+
+
+
+
+ VITEC · Market Insights
+ Sub-market template, section 6: two paragraphs, optional CTA, optional video card with person.
+ Paragraph 1
+ Plain text, max ~250 characters.
+ Paragraph 2
+ Plain text, max ~250 characters.
+ CTA label
+ Empty = no button.
+ CTA target
+ Section anchor OR URL. Empty with a label set = /success-stories.
+ Person
+ Role / title
+ Video title
+ Video URL
+ Empty = no video card, text runs wider.
+ Cover image
+
+
+
diff --git a/packages/vitec/ContentBlocks/ContentElements/market-insights/templates/backend-preview.html b/packages/vitec/ContentBlocks/ContentElements/market-insights/templates/backend-preview.html
new file mode 100644
index 0000000..ac6a499
--- /dev/null
+++ b/packages/vitec/ContentBlocks/ContentElements/market-insights/templates/backend-preview.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+ Market Insights — {data.vitec_text1}
+ · 🎬 {data.vitec_video_person}
+
+
+
+
diff --git a/packages/vitec/ContentBlocks/ContentElements/market-insights/templates/frontend.html b/packages/vitec/ContentBlocks/ContentElements/market-insights/templates/frontend.html
new file mode 100644
index 0000000..da9c5f0
--- /dev/null
+++ b/packages/vitec/ContentBlocks/ContentElements/market-insights/templates/frontend.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/packages/vitec/ContentBlocks/ContentElements/market-intro/assets/icon.svg b/packages/vitec/ContentBlocks/ContentElements/market-intro/assets/icon.svg
new file mode 100644
index 0000000..ffd00da
--- /dev/null
+++ b/packages/vitec/ContentBlocks/ContentElements/market-intro/assets/icon.svg
@@ -0,0 +1,7 @@
+
+
+ ¶
+
+
+
+
diff --git a/packages/vitec/ContentBlocks/ContentElements/market-intro/config.yaml b/packages/vitec/ContentBlocks/ContentElements/market-intro/config.yaml
new file mode 100644
index 0000000..0fa2d33
--- /dev/null
+++ b/packages/vitec/ContentBlocks/ContentElements/market-intro/config.yaml
@@ -0,0 +1,33 @@
+# Sub-market template section 2 (spec 2026-10-01).
+name: vitec/market-intro
+group: vitec_custom_components
+prefixFields: true
+prefixType: vendor
+
+fields:
+ - identifier: intro_text
+ type: Textarea
+ rows: 6
+
+ - identifier: image
+ type: File
+ minitems: 0
+ maxitems: 1
+ allowed: common-image-types
+
+ - identifier: learnmore_section
+ type: Palette
+ label: Learn-more button (optional)
+ fields:
+ - identifier: learnmore_label
+ type: Text
+ max: 40
+ default: Learn more
+ - identifier: learnmore_target
+ type: Text
+ max: 500
+
+ - identifier: show_portfolio
+ type: Checkbox
+ renderType: checkboxToggle
+ default: 1
diff --git a/packages/vitec/ContentBlocks/ContentElements/market-intro/language/labels.xlf b/packages/vitec/ContentBlocks/ContentElements/market-intro/language/labels.xlf
new file mode 100644
index 0000000..16cd284
--- /dev/null
+++ b/packages/vitec/ContentBlocks/ContentElements/market-intro/language/labels.xlf
@@ -0,0 +1,18 @@
+
+
+
+
+ VITEC · Market Intro
+ Sub-market template, section 2: intro text, image right, optional learn-more button, portfolio button toggle.
+ Intro text
+ Plain text, max ~350 characters. No HTML.
+ Image (right)
+ 636x469 (about 4:3).
+ Label
+ Target
+ Section anchor (workflow, testimonials, products, insights) OR a URL. Empty = no button.
+ Show "Product Portfolio" button
+ Links to /products (fixed for now).
+
+
+
diff --git a/packages/vitec/ContentBlocks/ContentElements/market-intro/templates/backend-preview.html b/packages/vitec/ContentBlocks/ContentElements/market-intro/templates/backend-preview.html
new file mode 100644
index 0000000..0087d15
--- /dev/null
+++ b/packages/vitec/ContentBlocks/ContentElements/market-intro/templates/backend-preview.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+ Market Intro — {data.vitec_intro_text}
+
+
+
+
diff --git a/packages/vitec/ContentBlocks/ContentElements/market-intro/templates/frontend.html b/packages/vitec/ContentBlocks/ContentElements/market-intro/templates/frontend.html
new file mode 100644
index 0000000..da9c5f0
--- /dev/null
+++ b/packages/vitec/ContentBlocks/ContentElements/market-intro/templates/frontend.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/packages/vitec/Documentation/Headless-JSON-Architecture.md b/packages/vitec/Documentation/Headless-JSON-Architecture.md
index 8265b94..1c6dc72 100755
--- a/packages/vitec/Documentation/Headless-JSON-Architecture.md
+++ b/packages/vitec/Documentation/Headless-JSON-Architecture.md
@@ -3,9 +3,9 @@
| | |
|---|---|
| **Document identifier** | EVO‑VITEC‑HL‑001 |
-| **Version** | 1.19 |
+| **Version** | 1.20 |
| **Status** | Released |
-| **Date** | 2026‑09‑25 |
+| **Date** | 2026‑10‑01 |
| **Applies to** | `evomedien/vitec` on TYPO3 v14.3 (headless) |
| **Owner** | evomedien — VITEC relaunch |
@@ -32,6 +32,7 @@
| 1.17 | 2026‑09‑11 | **Interface change (additive).** The product list payload gained `successStories` (7.15.2.1): stories an editor attaches to a `sys_category` (tab *Options*, with its own layout), emitted in the list envelope beside `products`. Each card carries `forCategories` — the product categories it came from — so the front end can hide it with the same `?cat=`/`?subcat=` filter it applies to the products. Story links reuse the 7.5 rule, with the detail page taken from the element or the new site setting `vitec.storyDetailPid`. |
| 1.18 | 2026‑09‑24 | **Interface change (additive).** Products gained `solutions` — an MM relation to solutions, emitted as solution cards in both product payloads — and a **Resources tab**: child table `tx_vitec_product_resource` with documentation / support cards (editable title, text and icon around one link, resolved per 9.12; cards without a resolvable link are not emitted) plus a `datasheets` MM selection of download records rendered into the same envelope as `type: datasheet` cards, delivered detail‑only as the `resources` list envelope whose `layout` switches to `carousel` above three cards in total (7.15). The `solutions` relation accepts group‑level records: the eight groups exist as `is_group = 1` solutions (created by `vitec:create-solution-groups`, excluded from lists and from `vitec:align-solutions`) because the agency workbooks reference the group level. Editorial: the footer had been stuck at v1.15 since 1.16/1.17 (2026‑09‑11). |
| 1.19 | 2026‑09‑25 | **Interface change (additive).** The `vitec_card` Content Block gained the `layout_variant` value `flipcard` and a *Card Back* palette: `back_headline` (string, max 100) and `back_text` (richtext → HTML). The two fields are `displayCond`‑gated on the flipcard layout in the backend but serialised unconditionally (7.7) — the front end **shall** key the back face on `layout_variant`, never on field presence, because stored back values persist when an editor switches the layout away. Backend repair: `vitec_layout_variant` / `vitec_background_variant` are shared with `vitec_ctabanner`, and the index‑wise Content‑Blocks merge had dropped four card‑only select values (“Missing label”) — per‑type item lists now come from `columnsOverrides` (7.7); the Card‑Back palette lives in the Layout tab below the variant select. Editorial: the head‑matter version had been stuck at v1.17 since 1.18. |
+| 1.20 | 2026‑10‑01 | **Interface change (additive).** Sub-market page template: frontend layout 21 with one colPos group per design section (`colPos110`..`colPos160`, clause 7.17); new Content Blocks `vitec_markethero`, `vitec_marketintro`, `vitec_marketinsights`; new plugin `vitec_productfeature` (key `productfeature`, one product in the 7.15.2 item shape); story cards gained `quote`/`quotePerson`/`quoteRole`; `vitec_usecaselist` gained the layout value `testimonials`; `vitec_productlist` gained hand-picked product selection (editor order, category filter ignored). Field semantics: Template_Sub-Market_Spezifikation_2026-10-01.md. |
This document is drafted in the style of, and adopts the terminology conventions of,
ISO/IEC/IEEE 42010 (architecture description), ISO/IEC/IEEE 26514 (information for
@@ -1131,6 +1132,43 @@ content is extracted from the page’s `tt_content` rows via
`index.queue.pages.fields.content` because the headless JSON output carries no
`TYPO3SEARCH` markers.
+---
+
+### 7.17 Sub-market page template (frontend layout 21)
+
+A sub-market page is identified by `appearance.layout == "layout-21"`. Its
+content is NOT read from element order: the backend layout assigns one column
+per design section, and the JSON groups them as
+
+| Group | Section | Elements |
+|---|---|---|
+| `colPos110` | Hero | `vitec_markethero` |
+| `colPos120` | Intro | `vitec_marketintro` |
+| `colPos130` | Workflow | `vitec_introparagraph` + container with `vitec_card` flip cards |
+| `colPos140` | Testimonials | `vitec_usecaselist`, `layout: "testimonials"` |
+| `colPos150` | Products | `vitec_productfeature` + `vitec_productlist` (hand-picked) |
+| `colPos160` | Industry Insights | `vitec_marketinsights` |
+
+The three `vitec_market*` elements are Content Blocks: their fields arrive
+unprefixed under the element's `data` (7.7). Field reference and empty-value
+semantics ("empty url = card hidden" etc.) are specified in
+`Template_Sub-Market_Spezifikation_2026-10-01.md`; the per-field limits live
+in the backend field descriptions.
+
+Related interface additions (all additive):
+
+- **Story cards** (7.5 shape, everywhere they appear) carry `quote`,
+ `quotePerson`, `quoteRole` - maintained once per story, empty strings until
+ maintained. The testimonial card renders them; the `vitec_usecaselist`
+ FlexForm gained the `layout` value `testimonials` for this.
+- **`vitec_productfeature`** (payload key `productfeature`): exactly one
+ referenced product as a full product card in the 7.15.2 item shape, `null`
+ when unset or the product is not visible. Serialisation is shared with the
+ product list (one source of truth).
+- **`vitec_productlist`** gained the FlexForm field *Hand-picked products*:
+ when set, the list contains exactly those products in the editor's order;
+ the category selection is ignored, the visibility filters stay in force.
+
## 8 Content‑type catalogue
| CType | TS pattern | Renderer / Processor | Payload key | Kind |
diff --git a/packages/vitec/Resources/Private/Language/locallang_pages.xlf b/packages/vitec/Resources/Private/Language/locallang_pages.xlf
index a433fdb..e03315b 100755
--- a/packages/vitec/Resources/Private/Language/locallang_pages.xlf
+++ b/packages/vitec/Resources/Private/Language/locallang_pages.xlf
@@ -66,6 +66,9 @@
Contact Page
+
+ Sub-Market Page
+