content: drop repeated reference headings and land case CTAs on anchors
Service notes now say why the public case fits, the four case CTAs open the matching project fragment, and the Innofocus run-time figure is a case metric as well as a home tile. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -37,11 +37,15 @@ describe('content claims and attribution', () => {
|
|||||||
expect(Object.keys(site.cases)).toEqual([...CASE_STUDY_IDS]);
|
expect(Object.keys(site.cases)).toEqual([...CASE_STUDY_IDS]);
|
||||||
expect(CASE_STUDY_IDS).toEqual(['innofocus', 'roesterei', 'myspa', 'hdi']);
|
expect(CASE_STUDY_IDS).toEqual(['innofocus', 'roesterei', 'myspa', 'hdi']);
|
||||||
|
|
||||||
const runtimeMetrics = allMetrics(site).filter(
|
const matchesRuntime = (metric: MetricCopy) =>
|
||||||
(metric) => /8/.test(metric.value) && /90/.test(metric.value),
|
/8/.test(metric.value) && /90/.test(metric.value);
|
||||||
);
|
const runtimeMetrics = allMetrics(site).filter(matchesRuntime);
|
||||||
expect(runtimeMetrics).toHaveLength(1);
|
expect(runtimeMetrics.every((metric) => metric.caseId === 'innofocus')).toBe(true);
|
||||||
expect(runtimeMetrics[0]?.caseId).toBe('innofocus');
|
expect(site.home.metrics.filter(matchesRuntime)).toHaveLength(1);
|
||||||
|
expect(site.cases.innofocus.metrics.filter(matchesRuntime)).toHaveLength(1);
|
||||||
|
for (const caseId of CASE_STUDY_IDS.filter((id) => id !== 'innofocus')) {
|
||||||
|
expect(site.cases[caseId].metrics.filter(matchesRuntime)).toHaveLength(0);
|
||||||
|
}
|
||||||
|
|
||||||
const leadershipMetrics = allMetrics(site).filter(
|
const leadershipMetrics = allMetrics(site).filter(
|
||||||
(metric) =>
|
(metric) =>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export interface CtaCopy {
|
|||||||
readonly routeId?: RouteId;
|
readonly routeId?: RouteId;
|
||||||
readonly href?: string;
|
readonly href?: string;
|
||||||
readonly external?: boolean;
|
readonly external?: boolean;
|
||||||
|
readonly fragment?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SectionCopy extends CopyBlock {
|
export interface SectionCopy extends CopyBlock {
|
||||||
|
|||||||
@@ -24,7 +24,15 @@ export const CASES_DE: Record<'innofocus' | 'roesterei' | 'myspa' | 'hdi', CaseS
|
|||||||
'Die Laufzeit der Migration liegt bei etwa 90 Minuten statt zuvor rund acht Stunden, bei größerem Funktionsumfang und höherer Datenqualität.',
|
'Die Laufzeit der Migration liegt bei etwa 90 Minuten statt zuvor rund acht Stunden, bei größerem Funktionsumfang und höherer Datenqualität.',
|
||||||
'Die Zielstruktur ist normalisiert und für die prüfende Stelle dokumentiert.',
|
'Die Zielstruktur ist normalisiert und für die prüfende Stelle dokumentiert.',
|
||||||
],
|
],
|
||||||
metrics: [],
|
metrics: [
|
||||||
|
{
|
||||||
|
id: 'innofocus-migration-runtime',
|
||||||
|
value: '8 h → ca. 90 min',
|
||||||
|
label: 'Laufzeit der Datenmigration bei Innofocus / reifen.com',
|
||||||
|
note: 'bei größerem Funktionsumfang und höherer Datenqualität',
|
||||||
|
caseId: 'innofocus',
|
||||||
|
},
|
||||||
|
],
|
||||||
stack: [
|
stack: [
|
||||||
'Microsoft SQL Server',
|
'Microsoft SQL Server',
|
||||||
'T-SQL',
|
'T-SQL',
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export const SERVICES_SOFTWARE_DE: ServicePageCopy = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
ctas: [
|
ctas: [
|
||||||
{ label: 'Fall Innofocus / reifen.com', routeId: 'projects' },
|
{ label: 'Fall Innofocus / reifen.com', routeId: 'projects', fragment: 'innofocus' },
|
||||||
{ label: 'Projekt anfragen', routeId: 'contact' },
|
{ label: 'Projekt anfragen', routeId: 'contact' },
|
||||||
],
|
],
|
||||||
sequence: {
|
sequence: {
|
||||||
@@ -116,7 +116,7 @@ export const SERVICES_SOFTWARE_DE: ServicePageCopy = {
|
|||||||
referenceCaseId: 'innofocus',
|
referenceCaseId: 'innofocus',
|
||||||
referenceHeadline: 'Passender öffentlicher Fall',
|
referenceHeadline: 'Passender öffentlicher Fall',
|
||||||
referenceNote:
|
referenceNote:
|
||||||
'Passender öffentlicher Fall: die ERP-Datenmigration für Innofocus / reifen.com, mit T-SQL, .NET, Angular und Keycloak.',
|
'Die ERP-Datenmigration für Innofocus / reifen.com gehört in diesen Bereich, weil sie Produkt- und Datenarbeit mit T-SQL, .NET, Angular und Keycloak ist.',
|
||||||
inquiry: {
|
inquiry: {
|
||||||
id: 'inquiry',
|
id: 'inquiry',
|
||||||
headline: 'Anfrage',
|
headline: 'Anfrage',
|
||||||
@@ -163,7 +163,7 @@ export const SERVICES_HARDWARE_DE: ServicePageCopy = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
ctas: [
|
ctas: [
|
||||||
{ label: 'Fall Rösterei Tangermünde', routeId: 'projects' },
|
{ label: 'Fall Rösterei Tangermünde', routeId: 'projects', fragment: 'roesterei' },
|
||||||
{ label: 'Projekt anfragen', routeId: 'contact' },
|
{ label: 'Projekt anfragen', routeId: 'contact' },
|
||||||
],
|
],
|
||||||
sequence: {
|
sequence: {
|
||||||
@@ -198,7 +198,7 @@ export const SERVICES_HARDWARE_DE: ServicePageCopy = {
|
|||||||
referenceCaseId: 'roesterei',
|
referenceCaseId: 'roesterei',
|
||||||
referenceHeadline: 'Passender öffentlicher Fall',
|
referenceHeadline: 'Passender öffentlicher Fall',
|
||||||
referenceNote:
|
referenceNote:
|
||||||
'Passender öffentlicher Fall: die Gesamtverantwortung für die Technik der Rösterei Tangermünde, vom Netz bis zum Shop.',
|
'Die Gesamtverantwortung für die Technik der Rösterei Tangermünde gehört in diesen Bereich, weil sie vom Netz bis zum Shop reicht.',
|
||||||
inquiry: {
|
inquiry: {
|
||||||
id: 'inquiry',
|
id: 'inquiry',
|
||||||
headline: 'Anfrage',
|
headline: 'Anfrage',
|
||||||
@@ -244,7 +244,7 @@ export const SERVICES_CLUSTERS_DE: ServicePageCopy = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
ctas: [
|
ctas: [
|
||||||
{ label: 'Fall HDI Specialty', routeId: 'projects' },
|
{ label: 'Fall HDI Specialty', routeId: 'projects', fragment: 'hdi' },
|
||||||
{ label: 'Projekt anfragen', routeId: 'contact' },
|
{ label: 'Projekt anfragen', routeId: 'contact' },
|
||||||
],
|
],
|
||||||
sequence: {
|
sequence: {
|
||||||
@@ -279,7 +279,7 @@ export const SERVICES_CLUSTERS_DE: ServicePageCopy = {
|
|||||||
referenceCaseId: 'hdi',
|
referenceCaseId: 'hdi',
|
||||||
referenceHeadline: 'Passender öffentlicher Fall',
|
referenceHeadline: 'Passender öffentlicher Fall',
|
||||||
referenceNote:
|
referenceNote:
|
||||||
'Passender öffentlicher Fall: die Migration der Umgebungen von HDI Specialty von Azure App Services auf Azure AKS.',
|
'Die Migration der Umgebungen von HDI Specialty von Azure App Services auf Azure AKS gehört in diesen Bereich, weil sie den Weg auf ein Cluster zeigt.',
|
||||||
inquiry: {
|
inquiry: {
|
||||||
id: 'inquiry',
|
id: 'inquiry',
|
||||||
headline: 'Anfrage',
|
headline: 'Anfrage',
|
||||||
@@ -325,7 +325,7 @@ export const SERVICES_AI_DE: ServicePageCopy = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
ctas: [
|
ctas: [
|
||||||
{ label: 'Fall Rösterei Tangermünde', routeId: 'projects' },
|
{ label: 'Fall Rösterei Tangermünde', routeId: 'projects', fragment: 'roesterei' },
|
||||||
{ label: 'Projekt anfragen', routeId: 'contact' },
|
{ label: 'Projekt anfragen', routeId: 'contact' },
|
||||||
],
|
],
|
||||||
sequence: {
|
sequence: {
|
||||||
@@ -360,7 +360,7 @@ export const SERVICES_AI_DE: ServicePageCopy = {
|
|||||||
referenceCaseId: 'roesterei',
|
referenceCaseId: 'roesterei',
|
||||||
referenceHeadline: 'Passender öffentlicher Fall',
|
referenceHeadline: 'Passender öffentlicher Fall',
|
||||||
referenceNote:
|
referenceNote:
|
||||||
'Passender öffentlicher Fall: die internen KI-Werkzeuge der Rösterei Tangermünde für Bewertungen und Shop-Verwaltung.',
|
'Die internen KI-Werkzeuge der Rösterei Tangermünde für Bewertungen und Shop-Verwaltung gehören in diesen Bereich, weil sie die umgesetzte KI-Arbeit sind.',
|
||||||
inquiry: {
|
inquiry: {
|
||||||
id: 'inquiry',
|
id: 'inquiry',
|
||||||
headline: 'Anfrage',
|
headline: 'Anfrage',
|
||||||
|
|||||||
@@ -24,7 +24,15 @@ export const CASES_EN: Record<'innofocus' | 'roesterei' | 'myspa' | 'hdi', CaseS
|
|||||||
'Migration run time is about 90 minutes instead of around eight hours, with a larger feature scope and higher data quality.',
|
'Migration run time is about 90 minutes instead of around eight hours, with a larger feature scope and higher data quality.',
|
||||||
'The target structure is normalised and documented for the auditor.',
|
'The target structure is normalised and documented for the auditor.',
|
||||||
],
|
],
|
||||||
metrics: [],
|
metrics: [
|
||||||
|
{
|
||||||
|
id: 'innofocus-migration-runtime',
|
||||||
|
value: '8 h → about 90 min',
|
||||||
|
label: 'data-migration run time at Innofocus / reifen.com',
|
||||||
|
note: 'with a larger feature scope and higher data quality',
|
||||||
|
caseId: 'innofocus',
|
||||||
|
},
|
||||||
|
],
|
||||||
stack: [
|
stack: [
|
||||||
'Microsoft SQL Server',
|
'Microsoft SQL Server',
|
||||||
'T-SQL',
|
'T-SQL',
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export const SERVICES_SOFTWARE_EN: ServicePageCopy = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
ctas: [
|
ctas: [
|
||||||
{ label: 'Innofocus / reifen.com case', routeId: 'projects' },
|
{ label: 'Innofocus / reifen.com case', routeId: 'projects', fragment: 'innofocus' },
|
||||||
{ label: 'Start a project enquiry', routeId: 'contact' },
|
{ label: 'Start a project enquiry', routeId: 'contact' },
|
||||||
],
|
],
|
||||||
sequence: {
|
sequence: {
|
||||||
@@ -116,7 +116,7 @@ export const SERVICES_SOFTWARE_EN: ServicePageCopy = {
|
|||||||
referenceCaseId: 'innofocus',
|
referenceCaseId: 'innofocus',
|
||||||
referenceHeadline: 'A fitting public case',
|
referenceHeadline: 'A fitting public case',
|
||||||
referenceNote:
|
referenceNote:
|
||||||
'Fitting public case: the ERP data migration for Innofocus / reifen.com, with T-SQL, .NET, Angular and Keycloak.',
|
'The ERP data migration for Innofocus / reifen.com belongs here because it is product and data work with T-SQL, .NET, Angular and Keycloak.',
|
||||||
inquiry: {
|
inquiry: {
|
||||||
id: 'inquiry',
|
id: 'inquiry',
|
||||||
headline: 'Enquiry',
|
headline: 'Enquiry',
|
||||||
@@ -163,7 +163,7 @@ export const SERVICES_HARDWARE_EN: ServicePageCopy = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
ctas: [
|
ctas: [
|
||||||
{ label: 'Rösterei Tangermünde case', routeId: 'projects' },
|
{ label: 'Rösterei Tangermünde case', routeId: 'projects', fragment: 'roesterei' },
|
||||||
{ label: 'Start a project enquiry', routeId: 'contact' },
|
{ label: 'Start a project enquiry', routeId: 'contact' },
|
||||||
],
|
],
|
||||||
sequence: {
|
sequence: {
|
||||||
@@ -198,7 +198,7 @@ export const SERVICES_HARDWARE_EN: ServicePageCopy = {
|
|||||||
referenceCaseId: 'roesterei',
|
referenceCaseId: 'roesterei',
|
||||||
referenceHeadline: 'A fitting public case',
|
referenceHeadline: 'A fitting public case',
|
||||||
referenceNote:
|
referenceNote:
|
||||||
'Fitting public case: full technical responsibility for Rösterei Tangermünde, from the network to the shop.',
|
'Full technical responsibility for Rösterei Tangermünde belongs here because it runs from the network to the shop.',
|
||||||
inquiry: {
|
inquiry: {
|
||||||
id: 'inquiry',
|
id: 'inquiry',
|
||||||
headline: 'Enquiry',
|
headline: 'Enquiry',
|
||||||
@@ -244,7 +244,7 @@ export const SERVICES_CLUSTERS_EN: ServicePageCopy = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
ctas: [
|
ctas: [
|
||||||
{ label: 'HDI Specialty case', routeId: 'projects' },
|
{ label: 'HDI Specialty case', routeId: 'projects', fragment: 'hdi' },
|
||||||
{ label: 'Start a project enquiry', routeId: 'contact' },
|
{ label: 'Start a project enquiry', routeId: 'contact' },
|
||||||
],
|
],
|
||||||
sequence: {
|
sequence: {
|
||||||
@@ -279,7 +279,7 @@ export const SERVICES_CLUSTERS_EN: ServicePageCopy = {
|
|||||||
referenceCaseId: 'hdi',
|
referenceCaseId: 'hdi',
|
||||||
referenceHeadline: 'A fitting public case',
|
referenceHeadline: 'A fitting public case',
|
||||||
referenceNote:
|
referenceNote:
|
||||||
'Fitting public case: the migration of the HDI Specialty environments from Azure App Services to Azure AKS.',
|
'The migration of the HDI Specialty environments from Azure App Services to Azure AKS belongs here because it is the move onto a cluster.',
|
||||||
inquiry: {
|
inquiry: {
|
||||||
id: 'inquiry',
|
id: 'inquiry',
|
||||||
headline: 'Enquiry',
|
headline: 'Enquiry',
|
||||||
@@ -325,7 +325,7 @@ export const SERVICES_AI_EN: ServicePageCopy = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
ctas: [
|
ctas: [
|
||||||
{ label: 'Rösterei Tangermünde case', routeId: 'projects' },
|
{ label: 'Rösterei Tangermünde case', routeId: 'projects', fragment: 'roesterei' },
|
||||||
{ label: 'Start a project enquiry', routeId: 'contact' },
|
{ label: 'Start a project enquiry', routeId: 'contact' },
|
||||||
],
|
],
|
||||||
sequence: {
|
sequence: {
|
||||||
@@ -360,7 +360,7 @@ export const SERVICES_AI_EN: ServicePageCopy = {
|
|||||||
referenceCaseId: 'roesterei',
|
referenceCaseId: 'roesterei',
|
||||||
referenceHeadline: 'A fitting public case',
|
referenceHeadline: 'A fitting public case',
|
||||||
referenceNote:
|
referenceNote:
|
||||||
'Fitting public case: the in-house AI tools at Rösterei Tangermünde for reviews and shop management.',
|
'The in-house AI tools at Rösterei Tangermünde for reviews and shop management belong here because they are the delivered AI work.',
|
||||||
inquiry: {
|
inquiry: {
|
||||||
id: 'inquiry',
|
id: 'inquiry',
|
||||||
headline: 'Enquiry',
|
headline: 'Enquiry',
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import { SITE_CONTENT } from '../core/content/content.token';
|
|||||||
import { SITE_CONTENT_DATA } from '../core/content/site-content';
|
import { SITE_CONTENT_DATA } from '../core/content/site-content';
|
||||||
import { SITE_CONFIG } from '../core/content/site-config';
|
import { SITE_CONFIG } from '../core/content/site-config';
|
||||||
import { CASE_STUDY_IDS } from '../core/content/content.contracts';
|
import { CASE_STUDY_IDS } from '../core/content/content.contracts';
|
||||||
|
import { APP_LOCALES } from '../core/i18n/locale';
|
||||||
|
import { routePath } from '../core/routing/route-paths';
|
||||||
|
|
||||||
function headingLevel(element: Element): number {
|
function headingLevel(element: Element): number {
|
||||||
return Number(element.tagName.slice(1));
|
return Number(element.tagName.slice(1));
|
||||||
@@ -95,4 +97,22 @@ describe('page rendering, semantics and accessibility', () => {
|
|||||||
|
|
||||||
expect(SITE_CONTENT_DATA.de.pages.notFound.hero.headline).toBeTruthy();
|
expect(SITE_CONTENT_DATA.de.pages.notFound.hero.headline).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('sends the software service case CTA to the innofocus project anchor', async () => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [provideRouter(routes), { provide: SITE_CONTENT, useValue: SITE_CONTENT_DATA }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const harness = await RouterTestingHarness.create();
|
||||||
|
|
||||||
|
for (const locale of APP_LOCALES) {
|
||||||
|
await harness.navigateByUrl(routePath('servicesSoftware', locale));
|
||||||
|
const root = harness.routeNativeElement as HTMLElement;
|
||||||
|
const expected = `${routePath('projects', locale)}#innofocus`;
|
||||||
|
const hrefs = [...root.querySelectorAll('a')].map(
|
||||||
|
(anchor) => anchor.getAttribute('href') ?? '',
|
||||||
|
);
|
||||||
|
expect(hrefs.some((href) => href.endsWith(expected))).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<p class="cta-row cluster">
|
<p class="cta-row cluster">
|
||||||
@for (cta of ctas(); track $index) {
|
@for (cta of ctas(); track $index) {
|
||||||
@if (cta.routeId) {
|
@if (cta.routeId) {
|
||||||
<a [routerLink]="link(cta)">{{ cta.label }}</a>
|
<a [routerLink]="link(cta)" [fragment]="cta.fragment ?? undefined">{{ cta.label }}</a>
|
||||||
} @else if (cta.href) {
|
} @else if (cta.href) {
|
||||||
<a
|
<a
|
||||||
[href]="cta.href"
|
[href]="cta.href"
|
||||||
|
|||||||
Reference in New Issue
Block a user