diff --git a/CLAUDE.md b/CLAUDE.md index 63f4683575..c8b06e087b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -186,7 +186,7 @@ Data-driven page at `/clients-feedback/`. Key dirs: `src/data/clients-feedback/` Data-driven page at `/partners/distributors/`. Import distributor data from `@data/partners` — it exports the derived selectors the page renders from (`OFFERED_COUNTRIES`, `REGION_OFFERED_COUNTRIES`, `getCoverage`). Distributor-scoped: hardware partners live in `@data/partners/hardware-partners` and are imported directly. -A distributor either lists the countries it covers or sets `countries: 'region-wide'` to cover every country in its `regions`, expanded from `REGION_MEMBERSHIP` in `src/data/partners/regions.ts`. That table and the countries distributors name must stay in step, so adding a country to a distributor means classifying it there too. `distributors.ts` asserts this as it loads (via `coverage.ts`), so any import path — the barrel or the data file directly — fails the build until you do. +A distributor either lists the countries it covers or sets `countries: 'region-wide'` to cover every country in its `regions`, expanded from `REGION_MEMBERSHIP` in `src/data/partners/regions.ts`. That table and the countries distributors name must stay in step, so adding a country to a distributor means classifying it there too — and declaring every region the country falls under, because the finder only offers a region's own countries in its dropdown and a card only matches regions it declares. `distributors.ts` asserts both as it loads (via `coverage.ts`), so any import path — the barrel or the data file directly — fails the build until you do. ## Redirects diff --git a/public/images/case-studies/cuba-iot-platform-1.webp b/public/images/case-studies/cuba-iot-platform-1.webp new file mode 100644 index 0000000000..2e83db6768 Binary files /dev/null and b/public/images/case-studies/cuba-iot-platform-1.webp differ diff --git a/public/images/case-studies/cuba-iot-platform-2.webp b/public/images/case-studies/cuba-iot-platform-2.webp new file mode 100644 index 0000000000..9ec646462a Binary files /dev/null and b/public/images/case-studies/cuba-iot-platform-2.webp differ diff --git a/public/images/case-studies/cuba-iot-platform-3.webp b/public/images/case-studies/cuba-iot-platform-3.webp new file mode 100644 index 0000000000..a419bd346c Binary files /dev/null and b/public/images/case-studies/cuba-iot-platform-3.webp differ diff --git a/public/images/case-studies/cuba-iot-platform-4.webp b/public/images/case-studies/cuba-iot-platform-4.webp new file mode 100644 index 0000000000..3b52a9d201 Binary files /dev/null and b/public/images/case-studies/cuba-iot-platform-4.webp differ diff --git a/public/images/case-studies/cuba-iot-platform-5.webp b/public/images/case-studies/cuba-iot-platform-5.webp new file mode 100644 index 0000000000..e848dfb8bc Binary files /dev/null and b/public/images/case-studies/cuba-iot-platform-5.webp differ diff --git a/public/images/case-studies/cuba-iot-platform-logo.png b/public/images/case-studies/cuba-iot-platform-logo.png new file mode 100644 index 0000000000..c756eaa55f Binary files /dev/null and b/public/images/case-studies/cuba-iot-platform-logo.png differ diff --git a/public/images/case-studies/cuba-iot-platform.webp b/public/images/case-studies/cuba-iot-platform.webp new file mode 100644 index 0000000000..9ea1ba592b Binary files /dev/null and b/public/images/case-studies/cuba-iot-platform.webp differ diff --git a/src/components/CaseStudy/ContactCTA.astro b/src/components/CaseStudy/ContactCTA.astro index 7f0f2d0a9b..a0f13c8406 100644 --- a/src/components/CaseStudy/ContactCTA.astro +++ b/src/components/CaseStudy/ContactCTA.astro @@ -4,9 +4,11 @@ interface Props { companyLogoAlt: string; companyLogoWidth?: number; companyLogoHeight?: number; + /** Logo already ships light-on-dark artwork — skip the white-flatten filter. */ + companyLogoIsLightOnDark?: boolean; } -const { companyLogo, companyLogoAlt, companyLogoWidth, companyLogoHeight } = Astro.props; +const { companyLogo, companyLogoAlt, companyLogoWidth, companyLogoHeight, companyLogoIsLightOnDark } = Astro.props; ---
@@ -14,7 +16,13 @@ const { companyLogo, companyLogoAlt, companyLogoWidth, companyLogoHeight } = Ast
ThingsBoard logo - {companyLogoAlt} + {companyLogoAlt}
@@ -135,6 +143,10 @@ const { companyLogo, companyLogoAlt, companyLogoWidth, companyLogoHeight } = Ast z-index: 1; filter: brightness(0) invert(1); + &.cs-logo-unfiltered { + filter: none; + } + @include media-down(md) { max-height: 50px; max-width: 200px; diff --git a/src/data/case-studies/cuba-iot-platform.ts b/src/data/case-studies/cuba-iot-platform.ts new file mode 100644 index 0000000000..a42f7895bc --- /dev/null +++ b/src/data/case-studies/cuba-iot-platform.ts @@ -0,0 +1,182 @@ +import type { CaseStudyData } from './types'; + +export const data: CaseStudyData = { + title: 'Backup power IoT in Kazakhstan: how CUBA IoT Platform monitors diesel generators with ThingsBoard', + pageTitle: 'How CUBA IoT Platform Monitors Diesel Generators', + description: + 'CUBA IoT Platform connects diesel generator controllers to ThingsBoard via CUBA RePort gateways: fleet dashboards, alarms, fuel and readiness monitoring.', + pageSlug: 'cuba-iot-platform', + breadcrumb: 'CUBA IoT Platform — Facility Management', + categories: ['Facility management'], + logoIsLightOnDark: true, + + hero: { + category: 'FACILITY MANAGEMENT', + heading: 'Backup power IoT in Kazakhstan: how CUBA IoT Platform monitors diesel generators with ThingsBoard', + paragraphs: [ + 'CUBA IoT Platform is a Kazakhstan-based IoT company and local ThingsBoard integrator. The company delivers online monitoring, real-time device control, data visualization, analytics, automation scenarios, and turnkey integration for distributed infrastructure. CUBA combines platform implementation, local engineering support, and proprietary hardware to help customers monitor equipment, reduce operational risk, and respond to events faster.', + 'For backup power operators, diesel generator units are critical but often geographically distributed assets. A generator may remain idle for long periods, but it must start immediately when the main power supply fails. CUBA’s diesel generator monitoring solution connects generator controllers through CUBA RePort gateways and uses ThingsBoard dashboards, alarms, telemetry history, and rule chains to give operators continuous visibility into generator readiness.', + ], + logo: '/images/case-studies/cuba-iot-platform-logo.png', + logoAlt: 'CUBA IoT Platform logo', + backgroundImage: '/images/case-studies/cuba-iot-platform.webp', + }, + + statistics: [ + { value: 100, suffix: '+', label: 'monitored generator nodes in production' }, + { value: 74, suffix: '+', label: 'telemetry and alarm parameters per generator profile' }, + { value: 4, suffix: '+', label: 'supported generator controller/OEM families' }, + ], + + quote: { + text: 'With ThingsBoard and CUBA RePort gateways, diesel generators are no longer rarely inspected backup assets. Operators can see low fuel, discharged batteries, abnormal temperatures, fault states, missed test runs, and maintenance needs before they become outages.', + author: 'Dmitry Shkunov', + role: 'CTO at Skif Trade LLP (SMALL, SPAR supermarkets)', + }, + + problem: { + description: + 'Organizations relying on diesel generators needed reliable visibility into generator readiness, actual runtime, alarms, fuel level, and maintenance status. Manual inspection could miss critical issues such as discharged batteries, low fuel, incorrect operating mode, heater failure, cooling problems, or unreported controller alarms. CUBA addressed these challenges by connecting generator controllers to ThingsBoard and converting raw telemetry into dashboards, alarms, historical trends, and operational notifications.', + challenges: [ + 'Limited visibility into geographically distributed diesel generators and their actual readiness', + 'Manual checks did not reliably detect low fuel, battery voltage issues, incorrect operating modes, or controller fault states', + 'Service teams needed maintenance planning based on engine hours, start counts, and real operating history instead of fixed assumptions', + 'Operators needed immediate notifications when a generator failed, fuel changed unexpectedly, or a scheduled test run was missed', + ], + results: [ + 'Continuous monitoring of fuel level, battery voltage, oil pressure, coolant temperature, generator/mains electrical parameters, runtime, start count, operating mode, and alarms', + 'Centralized fleet table with sorting by site, generator type, operating status, fuel level, alarm count, coolant temperature, and control mode', + 'Rule-based alarm activation and instant notifications through Telegram, SMS, and email', + 'Historical trends for outage investigation, fuel refill tracking, generator start/stop analysis, maintenance planning, and operational reporting', + ], + }, + + power: { + companyName: 'CUBA IoT Platform', + blocks: [ + { + title: 'Gateway-based data acquisition', + text: 'CUBA RePort polls diesel generator controllers over Modbus RTU or Modbus TCP and transfers telemetry to the platform through MQTT/HTTP. The gateway supports Ethernet, GSM LTE, Wi-Fi, RS-485, UART, NTP time synchronization, built-in web configuration, DIN-rail mounting, and backup autonomy of at least 20 minutes. This makes the solution suitable for distributed sites where wired connectivity may be unavailable or unreliable.', + image: '/images/case-studies/cuba-iot-platform-1.webp', + imageAlt: 'CUBA RePort gateway that connects diesel generator controllers to ThingsBoard', + }, + { + title: 'Unified asset model and dashboards', + text: 'ThingsBoard is configured around generator assets, sites, telemetry streams, alarms, and operator dashboards. Dispatchers can start from a fleet-level table, sort assets by status or alarm count, and drill down to a detailed generator page. The detailed panel consolidates engine state, generator electrical parameters, mains parameters, breaker states, runtime, and control mode.', + image: '/images/case-studies/cuba-iot-platform-2.webp', + imageAlt: 'CUBA RePort generator monitoring dashboard with engine, generator, and mains panels', + }, + { + title: 'Rule Engine alarms and operational notifications', + text: 'Rule chains process controller alarms, fuel level, battery voltage, coolant temperature, oil pressure, operating mode, and generator/mains state. Notifications are routed to responsible users through Telegram, SMS, and email. Each event can include the site, generator, source gateway, parameter, value, and timestamp, reducing the need for manual dashboard checks.', + image: '/images/case-studies/cuba-iot-platform-3.webp', + imageAlt: 'Generator and mains telemetry charts in a ThingsBoard dashboard', + }, + { + title: 'Historical analytics and service workflows', + text: 'Time-series widgets show current and historical data for any selected period. Operators can analyze mains outages, generator starts, fuel refills, engine stops, voltage, current, power, oil pressure, coolant temperature, and battery behavior. This supports readiness proof, outage investigation, fuel theft detection, and maintenance planning based on actual runtime.', + image: '/images/case-studies/cuba-iot-platform-4.webp', + imageAlt: 'Historical charts of fuel level, coolant temperature, oil pressure, and power', + }, + { + title: 'ThingsBoard implementation', + text: 'CUBA deployed ThingsBoard as the central monitoring layer for diesel generator assets. CUBA RePort gateways poll generator controllers over industrial protocols and publish structured telemetry to the platform. ThingsBoard stores telemetry, manages assets and dashboards, processes alarm rules, and sends notifications to responsible users.', + image: '/images/case-studies/cuba-iot-platform-5.webp', + imageAlt: 'Mains and generator current and power charts in ThingsBoard', + }, + ], + }, + + help: { + industryName: 'backup power operations improve reliability with IoT', + blocks: [ + { + title: 'Early detection of generator issues', + text: 'ThingsBoard helps detect problems before they lead to generator failure. Operators receive alerts when battery voltage drops, fuel level becomes too low, coolant temperature rises, oil pressure changes, or the controller reports an alarm.', + images: [ + { + src: 'https://img.thingsboard.io/case-studies/sensors-widget.webp', + alt: 'ThingsBoard sensors widget', + title: 'ThingsBoard sensors widget', + }, + { + src: 'https://img.thingsboard.io/case-studies/entities-table.webp', + alt: 'ThingsBoard entities table widget', + title: 'ThingsBoard entities table widget', + }, + ], + }, + { + title: 'Reliable start readiness monitoring', + text: 'The platform continuously shows whether each generator is ready to start during a power outage. This helps identify common hidden risks such as discharged batteries, failed preheating, fuel shortage, manual mode, or active controller faults.', + images: [ + { + src: 'https://img.thingsboard.io/case-studies/tables.webp', + alt: 'ThingsBoard tables widget', + title: 'ThingsBoard tables widget', + }, + { + src: 'https://img.thingsboard.io/case-studies/bar-chart.webp', + alt: 'ThingsBoard bar chart widget', + title: 'ThingsBoard bar chart widget', + }, + ], + }, + { + title: 'Event-based maintenance control', + text: 'Runtime hours, number of starts, alarms, and operating history are collected automatically. Service teams can plan maintenance based on real equipment usage and react faster when abnormal operation is detected.', + images: [ + { + src: 'https://img.thingsboard.io/case-studies/line-chart.webp', + alt: 'ThingsBoard line chart widget', + title: 'ThingsBoard line chart widget', + }, + { + src: 'https://img.thingsboard.io/case-studies/alarms-table.webp', + alt: 'ThingsBoard alarms table widget', + title: 'ThingsBoard alarms table widget', + }, + ], + }, + { + title: 'Fuel level and usage visibility', + text: 'Fuel data is available remotely for every monitored generator. This helps detect sudden drops, refueling events, abnormal consumption, and possible fuel theft.', + images: [ + { + src: 'https://img.thingsboard.io/case-studies/pie-chart.webp', + alt: 'ThingsBoard pie chart widget', + title: 'ThingsBoard pie chart widget', + }, + { + src: 'https://img.thingsboard.io/case-studies/battery-level.webp', + alt: 'ThingsBoard battery level widget', + title: 'ThingsBoard battery level widget', + }, + ], + }, + { + title: 'Instant notifications for responsible teams', + text: 'ThingsBoard sends alerts through configured notification channels when critical events occur. This reduces response time and allows service teams to act before the backup power system becomes unavailable.', + images: [ + { + src: 'https://img.thingsboard.io/case-studies/alarms-table-2.webp', + alt: 'ThingsBoard alarms table widget', + title: 'ThingsBoard alarms table widget', + }, + { + src: 'https://img.thingsboard.io/case-studies/notification-widget.webp', + alt: 'ThingsBoard notification widget', + title: 'ThingsBoard notification widget', + }, + ], + }, + ], + }, + + contact: { + companyLogo: '/images/case-studies/cuba-iot-platform-logo.png', + companyLogoAlt: 'CUBA IoT Platform logo', + companyLogoWidth: 240, + companyLogoHeight: 91, + }, +}; diff --git a/src/data/case-studies/index.ts b/src/data/case-studies/index.ts index 47a26fff18..8ce61d62a0 100644 --- a/src/data/case-studies/index.ts +++ b/src/data/case-studies/index.ts @@ -19,6 +19,7 @@ export const caseStudyCategories = [ // 2. Add the slug to this array in the position you want it displayed. // 3. (Optional) If the card needs a custom logo height, add an entry to // `cardLogoHeights` below. +// 4. (Optional) Logo already light-on-dark? Set `logoIsLightOnDark: true` in the data file. export const caseStudyOrder: string[] = [ 'ibt-systems', 'acte-technology', @@ -29,6 +30,8 @@ export const caseStudyOrder: string[] = [ 'onedata', 'lumen', 'solandtec', + // Intentionally out of newest-first order: first card of catalog page 2. + 'cuba-iot-platform', 'tjk-solutions', 'energenix', 'energroup', @@ -61,7 +64,8 @@ export const caseStudyOrder: string[] = [ // Card-only logo height overrides. The hero on the detail page may want a // different intrinsic size than the catalog card, so we keep the card override -// here rather than on the data itself. +// here rather than on the data itself. Sizing is per surface; the logo's +// treatment (`logoIsLightOnDark`) spans surfaces, so that one lives on the data. const cardLogoHeights: Record = { ariot: 75, iioote: 35, @@ -120,6 +124,7 @@ function toCard(d: CaseStudyData): CaseStudyCard { logo: d.hero.logo, logoAlt: d.hero.logoAlt, logoHeight: cardLogoHeights[d.pageSlug], + logoIsLightOnDark: d.logoIsLightOnDark, backgroundImage: d.hero.backgroundImage, }; } diff --git a/src/data/case-studies/types.ts b/src/data/case-studies/types.ts index 0bce58482c..6e06fe94cb 100644 --- a/src/data/case-studies/types.ts +++ b/src/data/case-studies/types.ts @@ -50,6 +50,12 @@ export interface CaseStudyData { pageSlug: string; breadcrumb: string; categories: string[]; + /** + * The company logo already ships light-on-dark artwork (e.g. a shaded + * white-on-transparent mark or a tinted SVG). Surfaces that normally + * white-flatten logos (catalog card, contact CTA) render it as-is instead. + */ + logoIsLightOnDark?: boolean; hero: { category: string; @@ -126,5 +132,7 @@ export interface CaseStudyCard { logo: string; logoAlt: string; logoHeight?: number; + /** See CaseStudyData.logoIsLightOnDark. */ + logoIsLightOnDark?: boolean; backgroundImage: string; } diff --git a/src/data/partners/coverage.ts b/src/data/partners/coverage.ts index 9e523f3725..c1cf38cb0b 100644 --- a/src/data/partners/coverage.ts +++ b/src/data/partners/coverage.ts @@ -18,10 +18,19 @@ export function getCoverage(d: Distributor): string[] { return Array.from(new Set(d.regions.flatMap((r) => REGION_MEMBERSHIP[r]))).sort(); } +/** Regions the table files a country under. */ +function regionsOf(country: string, membership: Record): Region[] { + return (Object.keys(membership) as Region[]).filter((r) => membership[r].includes(country)); +} + /** - * The region table and the distributor data must describe the same countries. - * A named country the table doesn't classify silently narrows region-wide - * coverage; a classified country nobody covers is dead weight that hides typos. + * The region table and the distributor data must describe the same countries, + * and every distributor must declare every region its named countries fall + * under. A named country the table doesn't classify silently narrows + * region-wide coverage; a classified country nobody covers is dead weight that + * hides typos; a named country in an undeclared region can't be reached through + * that region's filter, since the dropdown offers only the region's own + * countries and a card only matches regions it declares. */ function findCoverageErrors(distributors: Distributor[], membership: Record): string[] { const named = new Set(distributors.flatMap(getNamedCountries)); @@ -35,6 +44,19 @@ function findCoverageErrors(distributors: Distributor[], membership: Record { + // Unclassified countries are already reported above. + const outside = getNamedCountries(d).filter( + (c) => classified.has(c) && regionsOf(c, membership).some((r) => !d.regions.includes(r)) + ); + return outside.length > 0 ? [`${d.name} (${outside.join(', ')})`] : []; + }); + if (undeclared.length > 0) { + errors.push( + `countries in a region their distributor doesn't declare — add that region to the entry in src/data/partners/distributors.ts, or fix the country's classification in src/data/partners/regions.ts: ${undeclared.join('; ')}` + ); + } + // Compares names, not coverage: a region-wide distributor covers these without naming them. const stray = [...classified].filter((c) => !named.has(c)).sort(); if (stray.length > 0) { @@ -52,10 +74,31 @@ function findCoverageErrors(distributors: Distributor[], membership: Record): string[] { + return distributors.flatMap((d) => { + const countries = getNamedCountries(d); + if (countries.length === 0) return []; + const dangling = d.regions.filter((r) => !countries.some((c) => membership[r].includes(c))); + return dangling.length > 0 ? [`${d.name} → ${dangling.join(', ')}`] : []; + }); +} + /** Throws if the dataset and the region table disagree. Called by `distributors.ts`. */ export function assertDistributorData(distributors: Distributor[], membership: Record): void { const errors = findCoverageErrors(distributors, membership); if (errors.length > 0) { throw new Error(`[distributors] ${errors.join(' | ')}`); } + const dangling = findDanglingRegions(distributors, membership); + if (dangling.length > 0) { + console.warn( + `[distributors] regions declared without any named country in them (card vanishes once a country is picked): ${dangling.join('; ')}` + ); + } } diff --git a/src/data/partners/distributors.ts b/src/data/partners/distributors.ts index 24c36f29c6..9a95f7405c 100644 --- a/src/data/partners/distributors.ts +++ b/src/data/partners/distributors.ts @@ -236,7 +236,7 @@ export const DISTRIBUTORS: Distributor[] = [ }, { name: "HADEX", - regions: ["Asia"], + regions: ["Asia","Middle East"], countries: ["Syria"], email: "havalbajary@gmail.com", website: "hadex.nicepage.io", @@ -656,7 +656,7 @@ export const DISTRIBUTORS: Distributor[] = [ }, { name: "Vehi.kz LLP", - regions: ["Asia"], + regions: ["Asia","Europe"], countries: ["Kazakhstan","Uzbekistan","Turkmenistan","Tajikistan","Kyrgyzstan","Armenia","Azerbaijan","Moldova","Belarus"], email: "partner@vehi.kz", website: "vehi.kz", @@ -710,6 +710,20 @@ export const DISTRIBUTORS: Distributor[] = [ email: "delee@n2m.co.kr", website: "n2m.co.kr", }, + { + name: "Okiff", + regions: ["North America"], + countries: ["Canada"], + email: "thingsboard@okiff.com", + website: "okiff.com", + }, + { + name: "GREENNET", + regions: ["Europe","Asia"], + countries: ["Ukraine","Poland","Georgia"], + email: "info@greennet.ua", + website: "greennet.ua", + }, ]; // Throws if this data and REGION_MEMBERSHIP disagree — see ./coverage.ts. diff --git a/src/data/partners/index.ts b/src/data/partners/index.ts index 39698019b6..087ddb53ba 100644 --- a/src/data/partners/index.ts +++ b/src/data/partners/index.ts @@ -14,17 +14,20 @@ export { DISTRIBUTORS, REGION_MEMBERSHIP, REGIONS }; export { getCoverage } from './coverage.ts'; export type { Distributor, Region }; +// Alphabetical for readers, so "Åland Islands" files under A rather than after Z. +const byName = new Intl.Collator('en').compare; + /** Countries the finder offers as filter options — the ones distributors name. */ -export const OFFERED_COUNTRIES: string[] = Array.from(new Set(DISTRIBUTORS.flatMap(getNamedCountries))).sort(); +export const OFFERED_COUNTRIES: string[] = Array.from(new Set(DISTRIBUTORS.flatMap(getNamedCountries))).sort(byName); /** - * Countries each region offers in its dropdown — names distributors list, not - * their coverage, so a region-wide entry adds no options of its own. + * Countries each region offers in its dropdown — the region's own countries that + * a distributor declaring the region names. Region-wide entries name nothing, so + * they add no options; a multi-region distributor's other countries stay out. */ const regionOffered = {} as Record; for (const region of REGIONS) { - regionOffered[region] = Array.from( - new Set(DISTRIBUTORS.filter((d) => d.regions.includes(region)).flatMap(getNamedCountries)) - ).sort(); + const named = new Set(DISTRIBUTORS.filter((d) => d.regions.includes(region)).flatMap(getNamedCountries)); + regionOffered[region] = REGION_MEMBERSHIP[region].filter((c) => named.has(c)).sort(byName); } export const REGION_OFFERED_COUNTRIES: Record = regionOffered; diff --git a/src/data/partners/regions.ts b/src/data/partners/regions.ts index 3127319d43..7636c4c190 100644 --- a/src/data/partners/regions.ts +++ b/src/data/partners/regions.ts @@ -27,6 +27,9 @@ export type Region = (typeof REGIONS)[number]; * - Transcontinental countries appear under every region they span, so Turkey * counts as Europe and Middle East, and Georgia as Europe and Asia. * - Caribbean and Central American territories count as North America. + * - A distributor must declare every region its named countries fall under: + * the finder offers a region only its own countries and a card only matches + * regions it declares, so `coverage.ts` fails the build otherwise. */ export const REGION_MEMBERSHIP: Record = { Africa: [ diff --git a/src/pages/case-studies/[slug].astro b/src/pages/case-studies/[slug].astro index 1b4f74b5e1..0061a98386 100644 --- a/src/pages/case-studies/[slug].astro +++ b/src/pages/case-studies/[slug].astro @@ -1,17 +1,17 @@ --- -import type { CaseStudyData } from '../../data/case-studies/types'; -import CaseStudyLayout from '../../layouts/CaseStudyLayout.astro'; -import CaseStudyDetailHero from '../../components/CaseStudy/CaseStudyDetailHero.astro'; -import StatisticsSection from '../../components/CaseStudy/StatisticsSection.astro'; -import QuoteSection from '../../components/CaseStudy/QuoteSection.astro'; -import ProblemSection from '../../components/CaseStudy/ProblemSection.astro'; -import PowerSection from '../../components/CaseStudy/PowerSection.astro'; -import FullWidthImage from '../../components/CaseStudy/FullWidthImage.astro'; -import ContactUsBanner from '../../components/UseCase/ContactUsBanner.astro'; +import type { CaseStudyData } from '@data/case-studies/types'; +import CaseStudyLayout from '@layouts/CaseStudyLayout.astro'; +import CaseStudyDetailHero from '@components/CaseStudy/CaseStudyDetailHero.astro'; +import StatisticsSection from '@components/CaseStudy/StatisticsSection.astro'; +import QuoteSection from '@components/CaseStudy/QuoteSection.astro'; +import ProblemSection from '@components/CaseStudy/ProblemSection.astro'; +import PowerSection from '@components/CaseStudy/PowerSection.astro'; +import FullWidthImage from '@components/CaseStudy/FullWidthImage.astro'; +import ContactUsBanner from '@components/UseCase/ContactUsBanner.astro'; import { caseStudyBySlug, caseStudyContactBanner, caseStudyServicesBanner } from '@data/case-studies'; -import HelpSection from '../../components/CaseStudy/HelpSection.astro'; -import AwardSection from '../../components/CaseStudy/AwardSection.astro'; -import ContactCTA from '../../components/CaseStudy/ContactCTA.astro'; +import HelpSection from '@components/CaseStudy/HelpSection.astro'; +import AwardSection from '@components/CaseStudy/AwardSection.astro'; +import ContactCTA from '@components/CaseStudy/ContactCTA.astro'; export async function getStaticPaths() { return Object.values(caseStudyBySlug).map((data) => ({ @@ -152,6 +152,7 @@ const jsonLd = { companyLogoAlt={data.contact.companyLogoAlt} companyLogoWidth={data.contact.companyLogoWidth} companyLogoHeight={data.contact.companyLogoHeight} + companyLogoIsLightOnDark={data.logoIsLightOnDark} /> diff --git a/src/pages/case-studies/index.astro b/src/pages/case-studies/index.astro index f0f738d562..8efd7db9c6 100644 --- a/src/pages/case-studies/index.astro +++ b/src/pages/case-studies/index.astro @@ -1,10 +1,11 @@ --- import BaseLayout from '@layouts/BaseLayout.astro'; -import NButton from '../../components/Landing/NButton.astro'; +import NButton from '@components/Landing/NButton.astro'; import Pagination from '@components/Pagination/Pagination.astro'; import { Icon } from 'astro-icon/components'; import { caseStudyCards, caseStudyCategories, featuredCard } from '@data/case-studies'; +// Shared with the client script through data-page-size on the grid. const PAGE_SIZE = 9; // Build filter values with counts @@ -79,7 +80,7 @@ const filterValues = caseStudyCategories.map((cat) => ({ -