From 46bdf13daf537c1d77ddb2c72c25fe2fb46a17b3 Mon Sep 17 00:00:00 2001 From: Damian Date: Thu, 28 May 2026 17:16:33 +0200 Subject: [PATCH 01/45] =?UTF-8?q?WIP=20variant/document-level=20=E2=80=94?= =?UTF-8?q?=20Step=201+2:=20plugin=20+=20all=209=20schemas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Install @sanity/document-internationalization (^6.2.1). - studio/sanity.config.ts: replace internationalizedArray({...}) with documentInternationalization({ supportedLanguages, schemaTypes, languageField }). - studio/config/sync/internationalizedArrayLanguages.ts renamed to supportedLanguages.ts (loader signature identical, plus a try/catch fallback that addresses the earlier robustness gap). - All 9 schemas migrated: page, home, errorSettings, siteSettings, siteNav, siteCookieBanner gain a hidden `language` field; module objects (text/carousel/contentRefs) get plain string/richText/ richTextMedia field types (no per-field i18n wrapper). Previews use plain string access; firstLocalizedLabel helper deleted. NOTE: this is an intermediate commit on the long-lived variant branch. typecheck/build are intentionally NOT green here — the queries, web types, fetch wrappers, components, structure items, and presentation resolver still assume field-level i18n and will be migrated in the following commits. --- pnpm-lock.yaml | 56 +++++++++++++++++++ ...rrayLanguages.ts => supportedLanguages.ts} | 24 +++++--- studio/package.json | 1 + studio/sanity.config.ts | 31 +++++++--- studio/schemas/documents/page.ts | 17 ++++-- .../schemas/objects/modules/moduleCarousel.ts | 7 +-- .../objects/modules/moduleContentRefs.ts | 8 ++- studio/schemas/objects/modules/moduleText.ts | 12 ++-- studio/schemas/settings/error.ts | 18 ++++-- studio/schemas/settings/siteCookieBanner.ts | 11 +++- studio/schemas/settings/siteNav.ts | 11 +++- studio/schemas/settings/siteSettings.ts | 11 +++- studio/schemas/singletons/home.ts | 12 +++- studio/utils/firstLocalizedLabel.ts | 22 -------- 14 files changed, 170 insertions(+), 71 deletions(-) rename studio/config/sync/{internationalizedArrayLanguages.ts => supportedLanguages.ts} (65%) delete mode 100644 studio/utils/firstLocalizedLabel.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index de39bde..2e52a07 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -33,6 +33,9 @@ importers: '@sanity/dashboard': specifier: ^5.0.1 version: 5.0.1(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.6(react@19.2.6))(react-is@19.2.6)(react@19.2.6)(sanity@5.27.0(@emotion/is-prop-valid@1.4.0)(@noble/hashes@2.2.0)(@oclif/core@4.11.4)(@sanity/cli-core@1.3.2(@noble/hashes@2.2.0)(@types/node@22.19.19)(lightningcss@1.32.0)(yaml@2.9.0))(@types/node@22.19.19)(@types/react@19.2.15)(immer@11.1.8)(jiti@2.7.0)(lightningcss@1.32.0)(prismjs@1.27.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(styled-components@6.4.2(css-to-react-native@3.2.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(typescript@6.0.3))(styled-components@6.4.2(css-to-react-native@3.2.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)) + '@sanity/document-internationalization': + specifier: ^6.2.1 + version: 6.2.1(@emotion/is-prop-valid@1.4.0)(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react-is@19.2.6)(react@19.2.6)(sanity-plugin-internationalized-array@5.1.3(ad74c337faf32969affba83ed387f611))(sanity@5.27.0(@emotion/is-prop-valid@1.4.0)(@noble/hashes@2.2.0)(@oclif/core@4.11.4)(@sanity/cli-core@1.3.2(@noble/hashes@2.2.0)(@types/node@22.19.19)(lightningcss@1.32.0)(yaml@2.9.0))(@types/node@22.19.19)(@types/react@19.2.15)(immer@11.1.8)(jiti@2.7.0)(lightningcss@1.32.0)(prismjs@1.27.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(styled-components@6.4.2(css-to-react-native@3.2.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(typescript@6.0.3))(styled-components@6.4.2(css-to-react-native@3.2.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)) '@sanity/icons': specifier: ^3.7.4 version: 3.7.4(react@19.2.6) @@ -2388,6 +2391,15 @@ packages: resolution: {integrity: sha512-/zFEmzg7da1hRS2OqOi+M1Ro/39WVvpLSf6ryv1tJ/dR4hoQMYoO4+QqChrjcKHcINI6Nvqs8cy9b/eTbqHEAw==} engines: {node: '>=20.19 <22 || >=22.12'} + '@sanity/document-internationalization@6.2.1': + resolution: {integrity: sha512-HcIVr/i1Sdw6ygidpO9HukRq0pqCVpYgDutZw6hnPg097Wvw7TnuypZcxu0DyhtfoIfOQ5OCan2dTZ8NzmYj+w==} + engines: {node: '>=20.19 <22 || >=22.12'} + peerDependencies: + react: ^19.2 + react-dom: ^19.2 + sanity: ^5 || ^6.0.0-0 + sanity-plugin-internationalized-array: ^5.0.0 + '@sanity/eventsource@5.0.2': resolution: {integrity: sha512-/B9PMkUvAlUrpRq0y+NzXgRv5lYCLxZNsBJD2WXVnqZYOfByL9oQBV7KiTaARuObp5hcQYuPfOAVjgXe3hrixA==} @@ -5094,6 +5106,15 @@ packages: react: ^18 || ^19 sanity: ^3 || ^5 + sanity-plugin-utils@1.8.0: + resolution: {integrity: sha512-fzRThaEO/UIK3PRf7W8UXAaxTOsaB53xl6CJGoAVtBRBHANEMOv9bAcgZyBoMyIVuj17N2SGpKCZSIcAgsQmYQ==} + engines: {node: '>=18'} + peerDependencies: + react: ^18 || ^19 + rxjs: ^7.8.1 + sanity: ^3.67.1 || ^4.0.0 || ^5.0.0 + styled-components: ^6.1 + sanity@5.27.0: resolution: {integrity: sha512-PEsEZP3gVi2YG5Xkgr/KjGteR5YRPFFrlfX9Sy9QV4kJRPa8pVHmrswba+VMHg5E9/Dd951TkTAQnUGrlINw0g==} engines: {node: '>=20.19 <22 || >=22.12'} @@ -8267,6 +8288,26 @@ snapshots: dependencies: '@sanity/diff-match-patch': 3.2.0 + '@sanity/document-internationalization@6.2.1(@emotion/is-prop-valid@1.4.0)(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react-is@19.2.6)(react@19.2.6)(sanity-plugin-internationalized-array@5.1.3(ad74c337faf32969affba83ed387f611))(sanity@5.27.0(@emotion/is-prop-valid@1.4.0)(@noble/hashes@2.2.0)(@oclif/core@4.11.4)(@sanity/cli-core@1.3.2(@noble/hashes@2.2.0)(@types/node@22.19.19)(lightningcss@1.32.0)(yaml@2.9.0))(@types/node@22.19.19)(@types/react@19.2.15)(immer@11.1.8)(jiti@2.7.0)(lightningcss@1.32.0)(prismjs@1.27.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(styled-components@6.4.2(css-to-react-native@3.2.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(typescript@6.0.3))(styled-components@6.4.2(css-to-react-native@3.2.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))': + dependencies: + '@sanity/icons': 3.7.4(react@19.2.6) + '@sanity/mutator': 5.27.0(@types/react@19.2.15) + '@sanity/ui': 3.2.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.6(react@19.2.6))(react-is@19.2.6)(react@19.2.6)(styled-components@6.4.2(css-to-react-native@3.2.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)) + '@sanity/util': 5.27.0(@types/react@19.2.15) + '@sanity/uuid': 3.0.2 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + rxjs: 7.8.2 + sanity: 5.27.0(@emotion/is-prop-valid@1.4.0)(@noble/hashes@2.2.0)(@oclif/core@4.11.4)(@sanity/cli-core@1.3.2(@noble/hashes@2.2.0)(@types/node@22.19.19)(lightningcss@1.32.0)(yaml@2.9.0))(@types/node@22.19.19)(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(immer@11.1.8)(jiti@2.7.0)(lightningcss@1.32.0)(prismjs@1.27.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(styled-components@6.4.2(css-to-react-native@3.2.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(typescript@6.0.3) + sanity-plugin-internationalized-array: 5.1.3(ad74c337faf32969affba83ed387f611) + sanity-plugin-utils: 1.8.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.6(react@19.2.6))(react-is@19.2.6)(react@19.2.6)(rxjs@7.8.2)(sanity@5.27.0(@emotion/is-prop-valid@1.4.0)(@noble/hashes@2.2.0)(@oclif/core@4.11.4)(@sanity/cli-core@1.3.2(@noble/hashes@2.2.0)(@types/node@22.19.19)(lightningcss@1.32.0)(yaml@2.9.0))(@types/node@22.19.19)(@types/react@19.2.15)(immer@11.1.8)(jiti@2.7.0)(lightningcss@1.32.0)(prismjs@1.27.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(styled-components@6.4.2(css-to-react-native@3.2.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(typescript@6.0.3))(styled-components@6.4.2(css-to-react-native@3.2.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)) + transitivePeerDependencies: + - '@emotion/is-prop-valid' + - '@types/react' + - react-is + - styled-components + - supports-color + '@sanity/eventsource@5.0.2': dependencies: '@types/event-source-polyfill': 1.0.5 @@ -11211,6 +11252,21 @@ snapshots: - styled-components - supports-color + sanity-plugin-utils@1.8.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.6(react@19.2.6))(react-is@19.2.6)(react@19.2.6)(rxjs@7.8.2)(sanity@5.27.0(@emotion/is-prop-valid@1.4.0)(@noble/hashes@2.2.0)(@oclif/core@4.11.4)(@sanity/cli-core@1.3.2(@noble/hashes@2.2.0)(@types/node@22.19.19)(lightningcss@1.32.0)(yaml@2.9.0))(@types/node@22.19.19)(@types/react@19.2.15)(immer@11.1.8)(jiti@2.7.0)(lightningcss@1.32.0)(prismjs@1.27.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(styled-components@6.4.2(css-to-react-native@3.2.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(typescript@6.0.3))(styled-components@6.4.2(css-to-react-native@3.2.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)): + dependencies: + '@sanity/icons': 3.7.4(react@19.2.6) + '@sanity/incompatible-plugin': 1.0.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@sanity/ui': 3.2.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.6(react@19.2.6))(react-is@19.2.6)(react@19.2.6)(styled-components@6.4.2(css-to-react-native@3.2.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)) + react: 19.2.6 + react-fast-compare: 3.2.2 + rxjs: 7.8.2 + sanity: 5.27.0(@emotion/is-prop-valid@1.4.0)(@noble/hashes@2.2.0)(@oclif/core@4.11.4)(@sanity/cli-core@1.3.2(@noble/hashes@2.2.0)(@types/node@22.19.19)(lightningcss@1.32.0)(yaml@2.9.0))(@types/node@22.19.19)(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(immer@11.1.8)(jiti@2.7.0)(lightningcss@1.32.0)(prismjs@1.27.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(styled-components@6.4.2(css-to-react-native@3.2.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(typescript@6.0.3) + styled-components: 6.4.2(css-to-react-native@3.2.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + transitivePeerDependencies: + - '@emotion/is-prop-valid' + - react-dom + - react-is + sanity@5.27.0(@emotion/is-prop-valid@1.4.0)(@noble/hashes@2.2.0)(@oclif/core@4.11.4)(@sanity/cli-core@1.3.2(@noble/hashes@2.2.0)(@types/node@22.19.19)(lightningcss@1.32.0)(yaml@2.9.0))(@types/node@22.19.19)(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(immer@11.1.8)(jiti@2.7.0)(lightningcss@1.32.0)(prismjs@1.27.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(styled-components@6.4.2(css-to-react-native@3.2.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(typescript@6.0.3): dependencies: '@algorithm.ts/lcs': 4.0.6 diff --git a/studio/config/sync/internationalizedArrayLanguages.ts b/studio/config/sync/supportedLanguages.ts similarity index 65% rename from studio/config/sync/internationalizedArrayLanguages.ts rename to studio/config/sync/supportedLanguages.ts index aa1ca46..ce915a7 100644 --- a/studio/config/sync/internationalizedArrayLanguages.ts +++ b/studio/config/sync/supportedLanguages.ts @@ -1,7 +1,7 @@ import type { SanityClient } from "sanity"; -/** Same projection as `web/sanity/queries/snippets/settings.ts` / locale codegen. */ -export const siteLanguageSettingsInternationalizedQuery = `*[_id == "siteLanguageSettings"][0]{availableLanguages[]{id,title},defaultLanguageId}`; +/** Same projection as `web/sanity/queries/snippets/settings.ts` (siteLanguageSettings). */ +export const siteLanguageSettingsLanguagesQuery = `*[_id == "siteLanguageSettings"][0]{availableLanguages[]{id,title},defaultLanguageId}`; type SiteLanguageSettingsDoc = { availableLanguages?: Array<{ id?: string; title?: string }> | null; @@ -44,14 +44,20 @@ function normalizeFromDoc(doc: SiteLanguageSettingsDoc): Array<{ } /** - * Languages for `sanity-plugin-internationalized-array`, loaded on each Studio session - * from the `siteLanguageSettings` singleton (drafts when the Studio client uses preview). + * Languages for `@sanity/document-internationalization`, loaded on each Studio session + * from the `siteLanguageSettings` singleton. Falls back to the minimal `en` list when + * the singleton is missing or malformed (Studio still boots). */ -export async function internationalizedArrayLanguagesFromClient( +export async function supportedLanguagesFromClient( client: SanityClient, ): Promise> { - const doc = await client.fetch( - siteLanguageSettingsInternationalizedQuery, - ); - return normalizeFromDoc(doc); + try { + const doc = await client.fetch( + siteLanguageSettingsLanguagesQuery, + ); + return normalizeFromDoc(doc); + } catch (err) { + console.error("[supportedLanguages] fetch failed:", err); + return [...FALLBACK_LANGUAGES]; + } } diff --git a/studio/package.json b/studio/package.json index 11468b7..1b32614 100644 --- a/studio/package.json +++ b/studio/package.json @@ -17,6 +17,7 @@ "@repo/sanity-dataset-resolve": "workspace:*", "@sanity/code-input": "^7.1.2", "@sanity/dashboard": "^5.0.1", + "@sanity/document-internationalization": "^6.2.1", "@sanity/icons": "^3.7.4", "@sanity/image-url": "^2.1.1", "@sanity/vision": "^5.27.0", diff --git a/studio/sanity.config.ts b/studio/sanity.config.ts index 99c61dc..c93b9ee 100644 --- a/studio/sanity.config.ts +++ b/studio/sanity.config.ts @@ -1,10 +1,10 @@ import { codeInput } from "@sanity/code-input"; import { dashboardTool, projectInfoWidget } from "@sanity/dashboard"; +import { documentInternationalization } from "@sanity/document-internationalization"; import { visionTool } from "@sanity/vision"; import { defineConfig } from "sanity"; import { presentationTool } from "sanity/presentation"; import { structureTool } from "sanity/structure"; -import { internationalizedArray } from "sanity-plugin-internationalized-array"; import { media } from "sanity-plugin-media"; import { muxInput } from "sanity-plugin-mux-input"; import { netlifyTool } from "sanity-plugin-netlify"; @@ -18,10 +18,25 @@ import { } from "./config/presentation/resolve"; import { filterSingletonDocumentActions } from "./config/singletons"; import { structure } from "./config/structure"; -import { internationalizedArrayLanguagesFromClient } from "./config/sync/internationalizedArrayLanguages"; import { studioDataset } from "./config/sync/studioDataset"; +import { supportedLanguagesFromClient } from "./config/sync/supportedLanguages"; import { schemaTypes } from "./schemas"; +/** + * Document types that carry a `language` field and have parallel-per-locale + * documents (see `@sanity/document-internationalization`). Add new translatable + * types here AND on the schema (`language` field + `translatable: true` in + * `singletons.ts` where it applies). + */ +const TRANSLATABLE_SCHEMA_TYPES = [ + "home", + "page", + "errorSettings", + "siteNav", + "siteSettings", + "siteCookieBanner", +]; + const projectId = process.env.SANITY_STUDIO_PROJECT_ID; const dataset = studioDataset; const previewOrigin = @@ -76,13 +91,11 @@ export default defineConfig({ media(), muxInput(), netlifyTool(), - internationalizedArray({ - languages: internationalizedArrayLanguagesFromClient, - /** Plugin only accepts a static list; runtime default comes from `siteLanguageSettings` in the fetch above. */ - defaultLanguages: [], - fieldTypes: ["string", "richText", "richTextMedia"], - /** Hide bulk “Add missing languages”; languages come only from Site languages (`siteLanguageSettings`). */ - buttonAddAll: false, + documentInternationalization({ + /** Loaded once per Studio session from `siteLanguageSettings`. */ + supportedLanguages: supportedLanguagesFromClient, + schemaTypes: TRANSLATABLE_SCHEMA_TYPES, + languageField: "language", }), ], schema: { diff --git a/studio/schemas/documents/page.ts b/studio/schemas/documents/page.ts index 62cd641..4de5cb3 100644 --- a/studio/schemas/documents/page.ts +++ b/studio/schemas/documents/page.ts @@ -21,10 +21,16 @@ export const page = defineType({ }, ], fields: [ + { + name: "language", + type: "string", + readOnly: true, + hidden: true, + }, { name: "title", title: "Title", - type: "internationalizedArrayString", + type: "string", group: "editorial", validation: (rule) => rule.required(), }, @@ -50,12 +56,15 @@ export const page = defineType({ ], preview: { select: { + title: "title", slug: "slug", + language: "language", }, - prepare(selection) { - const { slug } = selection; + prepare({ title, slug, language }) { + const path = slug?.current?.trim() ? `/${slug.current}` : "Page"; return { - title: slug?.current?.trim() ? `/${slug.current}` : "Page", + title: typeof title === "string" && title.trim() ? title : path, + subtitle: language ? `${path} · ${language}` : path, }; }, }, diff --git a/studio/schemas/objects/modules/moduleCarousel.ts b/studio/schemas/objects/modules/moduleCarousel.ts index 3ca518d..3618f6b 100644 --- a/studio/schemas/objects/modules/moduleCarousel.ts +++ b/studio/schemas/objects/modules/moduleCarousel.ts @@ -1,8 +1,6 @@ import { ImagesIcon, PlayIcon } from "@sanity/icons"; import { defineType, type PreviewValue } from "sanity"; -import { firstLocalizedLabel } from "../../../utils/firstLocalizedLabel"; - export const moduleCarousel = defineType({ name: "module.carousel", title: "Carousel", @@ -12,7 +10,7 @@ export const moduleCarousel = defineType({ { name: "heading", title: "Heading", - type: "internationalizedArrayString", + type: "string", }, { name: "imagesOnly", @@ -96,7 +94,8 @@ export const moduleCarousel = defineType({ } return { - title: firstLocalizedLabel(heading, "Carousel"), + title: + typeof heading === "string" && heading.trim() ? heading : "Carousel", subtitle: `${count} slide${count === 1 ? "" : "s"}`, media, }; diff --git a/studio/schemas/objects/modules/moduleContentRefs.ts b/studio/schemas/objects/modules/moduleContentRefs.ts index cab4ff9..eefd026 100644 --- a/studio/schemas/objects/modules/moduleContentRefs.ts +++ b/studio/schemas/objects/modules/moduleContentRefs.ts @@ -1,7 +1,6 @@ import { DocumentsIcon } from "@sanity/icons"; import { defineType } from "sanity"; -import { firstLocalizedLabel } from "../../../utils/firstLocalizedLabel"; import { PAGE_REFERENCE_FILTER, PAGE_REFERENCES, @@ -16,7 +15,7 @@ export const moduleContentRefs = defineType({ { name: "heading", title: "Heading", - type: "internationalizedArrayString", + type: "string", }, { name: "allowMultiple", @@ -80,7 +79,10 @@ export const moduleContentRefs = defineType({ allowMultiple: "allowMultiple", }, prepare({ heading, allowMultiple }) { - const title = firstLocalizedLabel(heading, "Content references"); + const title = + typeof heading === "string" && heading.trim() + ? heading + : "Content references"; const mode = allowMultiple === true ? "Multiple references" : "Single reference"; return { diff --git a/studio/schemas/objects/modules/moduleText.ts b/studio/schemas/objects/modules/moduleText.ts index 1ed4ceb..6868cf1 100644 --- a/studio/schemas/objects/modules/moduleText.ts +++ b/studio/schemas/objects/modules/moduleText.ts @@ -1,8 +1,6 @@ import { TextIcon } from "@sanity/icons"; import { defineType } from "sanity"; -import { firstLocalizedLabel } from "../../../utils/firstLocalizedLabel"; - export const moduleText = defineType({ name: "module.text", title: "Text", @@ -12,22 +10,22 @@ export const moduleText = defineType({ { name: "title", title: "Title", - type: "internationalizedArrayString", + type: "string", validation: (rule) => rule.required(), }, { name: "body", title: "Body", - type: "internationalizedArrayRichTextMedia", + type: "richTextMedia", }, ], preview: { select: { - titleEntries: "title", + title: "title", }, - prepare({ titleEntries }) { + prepare({ title }) { return { - title: firstLocalizedLabel(titleEntries, "Text"), + title: typeof title === "string" && title.trim() ? title : "Text", subtitle: "Text module", }; }, diff --git a/studio/schemas/settings/error.ts b/studio/schemas/settings/error.ts index c758807..36da94e 100644 --- a/studio/schemas/settings/error.ts +++ b/studio/schemas/settings/error.ts @@ -14,39 +14,47 @@ export const errorSettings = defineType({ }, ], fields: [ + { + name: "language", + type: "string", + readOnly: true, + hidden: true, + }, { name: "notFoundTitle", title: "404 — Title", - type: "internationalizedArrayString", + type: "string", group: "editorial", validation: (rule) => rule.required(), }, { name: "notFoundBody", title: "404 — Body", - type: "internationalizedArrayRichText", + type: "richText", description: "Basic rich text (no media modules).", group: "editorial", }, { name: "serverErrorTitle", title: "500 — Title", - type: "internationalizedArrayString", + type: "string", group: "editorial", validation: (rule) => rule.required(), }, { name: "serverErrorBody", title: "500 — Body", - type: "internationalizedArrayRichText", + type: "richText", description: "Basic rich text (no media modules).", group: "editorial", }, ], preview: { - prepare() { + select: { language: "language" }, + prepare({ language }) { return { title: "Error pages", + subtitle: language || undefined, }; }, }, diff --git a/studio/schemas/settings/siteCookieBanner.ts b/studio/schemas/settings/siteCookieBanner.ts index 1f48e79..0f90967 100644 --- a/studio/schemas/settings/siteCookieBanner.ts +++ b/studio/schemas/settings/siteCookieBanner.ts @@ -9,6 +9,12 @@ export const siteCookieBanner = defineType({ title: "Cookie Banner", icon: CodeBlockIcon, fields: [ + { + name: "language", + type: "string", + readOnly: true, + hidden: true, + }, { title: "Title", name: "title", @@ -101,10 +107,11 @@ export const siteCookieBanner = defineType({ }, ], preview: { - select: { title: "title" }, - prepare({ title }) { + select: { title: "title", language: "language" }, + prepare({ title, language }) { return { title: title ?? "Cookie Banner", + subtitle: language || undefined, }; }, }, diff --git a/studio/schemas/settings/siteNav.ts b/studio/schemas/settings/siteNav.ts index 89a23c5..f8736fc 100644 --- a/studio/schemas/settings/siteNav.ts +++ b/studio/schemas/settings/siteNav.ts @@ -8,6 +8,12 @@ export const siteNav = defineType({ title: "Navigation", icon: MenuIcon, fields: [ + { + name: "language", + type: "string", + readOnly: true, + hidden: true, + }, { title: "Title", name: "title", @@ -29,10 +35,11 @@ export const siteNav = defineType({ }, ], preview: { - select: { title: "title" }, - prepare({ title }) { + select: { title: "title", language: "language" }, + prepare({ title, language }) { return { title: title ?? "Navigation", + subtitle: language || undefined, }; }, }, diff --git a/studio/schemas/settings/siteSettings.ts b/studio/schemas/settings/siteSettings.ts index e565d4d..acd4d91 100644 --- a/studio/schemas/settings/siteSettings.ts +++ b/studio/schemas/settings/siteSettings.ts @@ -20,6 +20,12 @@ export const siteSettings = defineType({ }, ], fields: [ + { + name: "language", + type: "string", + readOnly: true, + hidden: true, + }, { title: "Site Title", name: "title", @@ -41,10 +47,11 @@ export const siteSettings = defineType({ }, ], preview: { - select: { title: "title" }, - prepare({ title }) { + select: { title: "title", language: "language" }, + prepare({ title, language }) { return { title: title || "Settings", + subtitle: language || undefined, }; }, }, diff --git a/studio/schemas/singletons/home.ts b/studio/schemas/singletons/home.ts index fa426ba..733ba23 100644 --- a/studio/schemas/singletons/home.ts +++ b/studio/schemas/singletons/home.ts @@ -21,10 +21,16 @@ export const home = defineType({ }, ], fields: [ + { + name: "language", + type: "string", + readOnly: true, + hidden: true, + }, { name: "title", title: "Title", - type: "internationalizedArrayString", + type: "string", group: "editorial", validation: (rule) => rule.required(), }, @@ -38,9 +44,11 @@ export const home = defineType({ }, ], preview: { - prepare() { + select: { language: "language" }, + prepare({ language }) { return { title: "Home", + subtitle: language || undefined, }; }, }, diff --git a/studio/utils/firstLocalizedLabel.ts b/studio/utils/firstLocalizedLabel.ts deleted file mode 100644 index edde59c..0000000 --- a/studio/utils/firstLocalizedLabel.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Returns the first non-empty value of an `internationalizedArrayString` field - * (array of `{ _key, value }` entries), trimmed — otherwise `fallback`. - * - * Used in schema `preview.prepare` blocks to derive a human label from a - * localized heading/title without committing to a specific locale. - */ -export function firstLocalizedLabel( - entries: unknown, - fallback: string, -): string { - if (!Array.isArray(entries)) { - return fallback; - } - const first = entries.find( - (entry: { value?: unknown }) => - typeof entry?.value === "string" && entry.value.trim().length > 0, - ); - return first && typeof first.value === "string" - ? first.value.trim() - : fallback; -} From 90c7264e69e211cb25e15eae66829c6b4eb2527a Mon Sep 17 00:00:00 2001 From: Damian Date: Thu, 28 May 2026 17:31:25 +0200 Subject: [PATCH 02/45] =?UTF-8?q?variant:=20Step=203+4=20=E2=80=94=20struc?= =?UTF-8?q?ture=20items,=20slug=20uniqueness,=20presentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Studio side of the document-level migration is now internally consistent (studio typecheck green; web side migrates in the next etappe). Structure items for the five singletons (home, siteSettings, siteNav, errorSettings, siteCookieBanner) switch from a fixed-id S.document() to S.documentTypeList() so the desk shows one row per language variant of each singleton; the plugin's Translations toolbar handles sibling switching inside each document. Slug uniqueness is now language-aware: `studio/utils/validateSlug.ts` exports `isUniqueLocaleAgnostic` which scopes uniqueness per (_type, language). `studio/schemas/documents/page.ts` wires it into the slug field's `options.isUnique`, so /about can exist in en AND de. Presentation: - `resolve.ts` `presentationMainDocuments` now registers four routes: `/` and `/:slug` (default-locale, no language constraint), plus `/:locale` and `/:locale/:slug` (`language == $locale`). Presentation prefers the most specific match, so per-locale routes win when the iframe URL carries a locale segment. - `locationsResolver.ts` SLUG_QUERY now selects `language` alongside `slug.current` and emits a `/{language}/{slug}` URL. The web proxy redirects default-locale prefixes to the canonical unprefixed URL. schema.json and sanity.types.gen.ts regenerated under the new schemas (52 types incl. `translation.metadata` from the plugin). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../config/presentation/locationsResolver.ts | 21 +- studio/config/presentation/resolve.ts | 20 +- .../config/structure/items/errorSettings.ts | 4 +- studio/config/structure/items/home.ts | 2 +- .../structure/items/siteCookieBanner.ts | 6 +- studio/config/structure/items/siteNav.ts | 2 +- studio/config/structure/items/siteSettings.ts | 2 +- studio/sanity.types.gen.ts | 419 +-- studio/schema.json | 2878 +++++++++-------- studio/schemas/documents/page.ts | 5 +- studio/utils/validateSlug.ts | 31 +- 11 files changed, 1800 insertions(+), 1590 deletions(-) diff --git a/studio/config/presentation/locationsResolver.ts b/studio/config/presentation/locationsResolver.ts index 42b96aa..5667f89 100644 --- a/studio/config/presentation/locationsResolver.ts +++ b/studio/config/presentation/locationsResolver.ts @@ -14,7 +14,18 @@ import { const SLUG_TYPE_SET = new Set(SLUG_BASED_DOCUMENT_TYPES); -const SLUG_QUERY = `*[_id in $ids][0]{ "slug": slug.current }`; +const SLUG_QUERY = `*[_id in $ids][0]{ "slug": slug.current, language }`; + +/** + * Builds the web URL for a slugged document. Always emits the language-prefixed + * path — the web app's `proxy.ts` redirects the default-locale prefix to the + * canonical unprefixed URL, so Presentation lands in the right place either way. + */ +function localizedPath(slug: string, language?: string | null): string { + const lang = typeof language === "string" ? language.trim() : ""; + if (!lang) return `/${slug}`; + return `/${lang}/${slug}`; +} /** * Types that need fully custom locations (no convention). @@ -73,12 +84,12 @@ export const presentationLocationsResolver: DocumentLocationResolver = ( return context.documentStore .listenQuery(SLUG_QUERY, { ids }, { perspective: "drafts" }) .pipe( - map((doc: { slug?: string } | null) => { - const raw = doc?.slug; - const slug = typeof raw === "string" ? raw.trim() : ""; + map((doc: { slug?: string; language?: string | null } | null) => { + const rawSlug = doc?.slug; + const slug = typeof rawSlug === "string" ? rawSlug.trim() : ""; if (slug) { - const path = `/${slug}`; + const path = localizedPath(slug, doc?.language); return { message: PRESENTATION_LOCATIONS_HEADER, locations: [ diff --git a/studio/config/presentation/resolve.ts b/studio/config/presentation/resolve.ts index a63135b..de42229 100644 --- a/studio/config/presentation/resolve.ts +++ b/studio/config/presentation/resolve.ts @@ -9,16 +9,34 @@ const slugTypeList = SLUG_BASED_DOCUMENT_TYPES.map((t) => `"${t}"`).join(","); /** * Which document opens when the Presentation iframe navigates to a route. - * Home at `/`; slugged types share `/:slug` (see `SLUG_BASED_DOCUMENT_TYPES`). + * + * The web app serves the default locale unprefixed (`/`, `/:slug`) and other + * locales under `/:locale/…`. We register both shapes for every routable type + * so editing any language variant lands on the right document: + * + * - `/` → default-locale `home` + * - `/:locale` → `home` filtered by `language` + * - `/:slug` → default-locale slugged doc + * - `/:locale/:slug` → slugged doc filtered by `language` + * + * Note: the default-locale routes do not constrain `language` because we don't + * know the default at config-load time (it lives in `siteLanguageSettings`). + * Presentation prefers the most specific match, so per-locale routes win when + * the URL carries a `:locale` segment. */ export const presentationMainDocuments = defineDocuments([ { route: "/", type: "home" }, + { route: "/:locale", filter: `_type == "home" && language == $locale` }, ...(SLUG_BASED_DOCUMENT_TYPES.length > 0 ? [ { route: "/:slug", filter: `_type in [${slugTypeList}] && slug.current == $slug`, }, + { + route: "/:locale/:slug", + filter: `_type in [${slugTypeList}] && slug.current == $slug && language == $locale`, + }, ] : []), ]); diff --git a/studio/config/structure/items/errorSettings.ts b/studio/config/structure/items/errorSettings.ts index e42169a..a3b7ae3 100644 --- a/studio/config/structure/items/errorSettings.ts +++ b/studio/config/structure/items/errorSettings.ts @@ -6,7 +6,5 @@ export function errorSettingsStructureItem(S: StructureBuilder) { .title("Error pages") .icon(ErrorOutlineIcon) .id("error-settings") - .child( - S.document().schemaType("errorSettings").documentId("errorSettings"), - ); + .child(S.documentTypeList("errorSettings").title("Error pages")); } diff --git a/studio/config/structure/items/home.ts b/studio/config/structure/items/home.ts index f62294f..6733572 100644 --- a/studio/config/structure/items/home.ts +++ b/studio/config/structure/items/home.ts @@ -6,5 +6,5 @@ export function homeStructureItem(S: StructureBuilder) { .title("Home") .icon(HomeIcon) .id("home") - .child(S.document().schemaType("home").documentId("home")); + .child(S.documentTypeList("home").title("Home")); } diff --git a/studio/config/structure/items/siteCookieBanner.ts b/studio/config/structure/items/siteCookieBanner.ts index 6b6cf79..94c1971 100644 --- a/studio/config/structure/items/siteCookieBanner.ts +++ b/studio/config/structure/items/siteCookieBanner.ts @@ -6,9 +6,5 @@ export function siteCookieBannerStructureItem(S: StructureBuilder) { .title("Cookie Banner") .icon(CodeBlockIcon) .id("site-cookie-banner") - .child( - S.document() - .schemaType("siteCookieBanner") - .documentId("siteCookieBanner"), - ); + .child(S.documentTypeList("siteCookieBanner").title("Cookie Banner")); } diff --git a/studio/config/structure/items/siteNav.ts b/studio/config/structure/items/siteNav.ts index 2c3760e..994cb7a 100644 --- a/studio/config/structure/items/siteNav.ts +++ b/studio/config/structure/items/siteNav.ts @@ -6,5 +6,5 @@ export function siteNavStructureItem(S: StructureBuilder) { .title("Navigation") .icon(MenuIcon) .id("site-nav") - .child(S.document().schemaType("siteNav").documentId("siteNav")); + .child(S.documentTypeList("siteNav").title("Navigation")); } diff --git a/studio/config/structure/items/siteSettings.ts b/studio/config/structure/items/siteSettings.ts index 8f04c7f..3254786 100644 --- a/studio/config/structure/items/siteSettings.ts +++ b/studio/config/structure/items/siteSettings.ts @@ -6,5 +6,5 @@ export function siteSettingsStructureItem(S: StructureBuilder) { .title("Settings") .icon(CogIcon) .id("site-settings") - .child(S.document().schemaType("siteSettings").documentId("siteSettings")); + .child(S.documentTypeList("siteSettings").title("Settings")); } diff --git a/studio/sanity.types.gen.ts b/studio/sanity.types.gen.ts index a325998..cf7d937 100644 --- a/studio/sanity.types.gen.ts +++ b/studio/sanity.types.gen.ts @@ -26,83 +26,6 @@ export type VideoSettings = { controls?: boolean; }; -export type SiteCookieBanner = { - _id: string; - _type: "siteCookieBanner"; - _createdAt: string; - _updatedAt: string; - _rev: string; - title?: string; - useCookieBanner?: boolean; - consentModal?: { - description?: string; - acceptAllBtn?: string; - acceptNecessaryBtn?: string; - showPreferencesBtn?: string; - }; - preferencesModal?: { - title?: string; - acceptAllBtn?: string; - acceptNecessaryBtn?: string; - savePreferencesBtn?: string; - sections?: Code; - }; -}; - -export type Code = { - _type: "code"; - language?: string; - filename?: string; - code?: string; - highlightedLines?: Array; -}; - -export type ErrorSettings = { - _id: string; - _type: "errorSettings"; - _createdAt: string; - _updatedAt: string; - _rev: string; - notFoundTitle: InternationalizedArrayString; - notFoundBody?: InternationalizedArrayRichText; - serverErrorTitle: InternationalizedArrayString; - serverErrorBody?: InternationalizedArrayRichText; -}; - -export type InternationalizedArrayRichText = Array< - { - _key: string; - } & InternationalizedArrayRichTextValue ->; - -export type InternationalizedArrayString = Array< - { - _key: string; - } & InternationalizedArrayStringValue ->; - -export type SiteNav = { - _id: string; - _type: "siteNav"; - _createdAt: string; - _updatedAt: string; - _rev: string; - title?: string; - mainMenu?: Array< - | ({ - _key: string; - } & Link) - | ({ - _key: string; - } & NavLanguageSwitch) - >; - footerMenu?: Array< - { - _key: string; - } & Link - >; -}; - export type SiteLanguageSettings = { _id: string; _type: "siteLanguageSettings"; @@ -119,59 +42,6 @@ export type SiteLanguageSettings = { defaultLanguageId: string; }; -export type SanityImageAssetReference = { - _ref: string; - _type: "reference"; - _weak?: boolean; - [internalGroqTypeReferenceTo]?: "sanity.imageAsset"; -}; - -export type SiteSettings = { - _id: string; - _type: "siteSettings"; - _createdAt: string; - _updatedAt: string; - _rev: string; - title: string; - favicon?: { - asset?: SanityImageAssetReference; - media?: unknown; - hotspot?: SanityImageHotspot; - crop?: SanityImageCrop; - _type: "image"; - }; - seo?: SeoFallback; -}; - -export type SeoFallback = { - _type: "seo.fallback"; - title?: string; - description?: string; - image?: { - asset?: SanityImageAssetReference; - media?: unknown; - hotspot?: SanityImageHotspot; - crop?: SanityImageCrop; - _type: "image"; - }; -}; - -export type SanityImageCrop = { - _type: "sanity.imageCrop"; - top: number; - bottom: number; - left: number; - right: number; -}; - -export type SanityImageHotspot = { - _type: "sanity.imageHotspot"; - x: number; - y: number; - height: number; - width: number; -}; - export type RichTextMedia = Array< | { children?: Array<{ @@ -220,8 +90,8 @@ export type RichText = Array<{ export type ModuleText = { _type: "module.text"; - title: InternationalizedArrayString; - body?: InternationalizedArrayRichTextMedia; + title: string; + body?: RichTextMedia; }; export type HomeReference = { @@ -240,7 +110,7 @@ export type PageReference = { export type ModuleContentRefs = { _type: "module.contentRefs"; - heading?: InternationalizedArrayString; + heading?: string; allowMultiple?: boolean; reference?: HomeReference | PageReference; references?: ArrayOf; @@ -253,9 +123,16 @@ export type ModuleMedia = { videoContent?: MediaVideo; }; +export type SanityImageAssetReference = { + _ref: string; + _type: "reference"; + _weak?: boolean; + [internalGroqTypeReferenceTo]?: "sanity.imageAsset"; +}; + export type ModuleCarousel = { _type: "module.carousel"; - heading?: InternationalizedArrayString; + heading?: string; imagesOnly?: boolean; slides?: Array<{ asset?: SanityImageAssetReference; @@ -313,13 +190,204 @@ export type Link = { func?: LinkFunctions; }; +export type SeoFallback = { + _type: "seo.fallback"; + title?: string; + description?: string; + image?: { + asset?: SanityImageAssetReference; + media?: unknown; + hotspot?: SanityImageHotspot; + crop?: SanityImageCrop; + _type: "image"; + }; +}; + +export type SeoPage = { + _type: "seo.page"; + title?: string; + description?: string; + image?: { + asset?: SanityImageAssetReference; + media?: unknown; + hotspot?: SanityImageHotspot; + crop?: SanityImageCrop; + _type: "image"; + }; +}; + +export type LinkFunctions = { + _type: "linkFunctions"; + key: "scroll-to" | "open-modal"; + params?: string; +}; + +export type TranslationMetadata = { + _id: string; + _type: "translation.metadata"; + _createdAt: string; + _updatedAt: string; + _rev: string; + translations?: InternationalizedArrayReference; + schemaTypes?: Array; +}; + +export type InternationalizedArrayReference = Array< + { + _key: string; + } & InternationalizedArrayReferenceValue +>; + +export type ErrorSettingsReference = { + _ref: string; + _type: "reference"; + _weak?: boolean; + [internalGroqTypeReferenceTo]?: "errorSettings"; +}; + +export type SiteNavReference = { + _ref: string; + _type: "reference"; + _weak?: boolean; + [internalGroqTypeReferenceTo]?: "siteNav"; +}; + +export type SiteSettingsReference = { + _ref: string; + _type: "reference"; + _weak?: boolean; + [internalGroqTypeReferenceTo]?: "siteSettings"; +}; + +export type SiteCookieBannerReference = { + _ref: string; + _type: "reference"; + _weak?: boolean; + [internalGroqTypeReferenceTo]?: "siteCookieBanner"; +}; + +export type InternationalizedArrayReferenceValue = { + _type: "internationalizedArrayReferenceValue"; + value?: + | HomeReference + | PageReference + | ErrorSettingsReference + | SiteNavReference + | SiteSettingsReference + | SiteCookieBannerReference; + language: string; +}; + +export type SiteCookieBanner = { + _id: string; + _type: "siteCookieBanner"; + _createdAt: string; + _updatedAt: string; + _rev: string; + language?: string; + title?: string; + useCookieBanner?: boolean; + consentModal?: { + description?: string; + acceptAllBtn?: string; + acceptNecessaryBtn?: string; + showPreferencesBtn?: string; + }; + preferencesModal?: { + title?: string; + acceptAllBtn?: string; + acceptNecessaryBtn?: string; + savePreferencesBtn?: string; + sections?: Code; + }; +}; + +export type Code = { + _type: "code"; + language?: string; + filename?: string; + code?: string; + highlightedLines?: Array; +}; + +export type SiteSettings = { + _id: string; + _type: "siteSettings"; + _createdAt: string; + _updatedAt: string; + _rev: string; + language?: string; + title: string; + favicon?: { + asset?: SanityImageAssetReference; + media?: unknown; + hotspot?: SanityImageHotspot; + crop?: SanityImageCrop; + _type: "image"; + }; + seo?: SeoFallback; +}; + +export type SanityImageCrop = { + _type: "sanity.imageCrop"; + top: number; + bottom: number; + left: number; + right: number; +}; + +export type SanityImageHotspot = { + _type: "sanity.imageHotspot"; + x: number; + y: number; + height: number; + width: number; +}; + +export type SiteNav = { + _id: string; + _type: "siteNav"; + _createdAt: string; + _updatedAt: string; + _rev: string; + language?: string; + title?: string; + mainMenu?: Array< + | ({ + _key: string; + } & Link) + | ({ + _key: string; + } & NavLanguageSwitch) + >; + footerMenu?: Array< + { + _key: string; + } & Link + >; +}; + +export type ErrorSettings = { + _id: string; + _type: "errorSettings"; + _createdAt: string; + _updatedAt: string; + _rev: string; + language?: string; + notFoundTitle: string; + notFoundBody?: RichText; + serverErrorTitle: string; + serverErrorBody?: RichText; +}; + export type Page = { _id: string; _type: "page"; _createdAt: string; _updatedAt: string; _rev: string; - title: InternationalizedArrayString; + language?: string; + title: string; slug: Slug; modules?: Array< | ({ @@ -338,19 +406,6 @@ export type Page = { seo?: SeoPage; }; -export type SeoPage = { - _type: "seo.page"; - title?: string; - description?: string; - image?: { - asset?: SanityImageAssetReference; - media?: unknown; - hotspot?: SanityImageHotspot; - crop?: SanityImageCrop; - _type: "image"; - }; -}; - export type Slug = { _type: "slug"; current: string; @@ -363,7 +418,8 @@ export type Home = { _createdAt: string; _updatedAt: string; _rev: string; - title: InternationalizedArrayString; + language?: string; + title: string; modules?: Array< | ({ _key: string; @@ -381,36 +437,6 @@ export type Home = { seo?: SeoPage; }; -export type InternationalizedArrayRichTextMedia = Array< - { - _key: string; - } & InternationalizedArrayRichTextMediaValue ->; - -export type LinkFunctions = { - _type: "linkFunctions"; - key: "scroll-to" | "open-modal"; - params?: string; -}; - -export type InternationalizedArrayRichTextMediaValue = { - _type: "internationalizedArrayRichTextMediaValue"; - value?: RichTextMedia; - language: string; -}; - -export type InternationalizedArrayRichTextValue = { - _type: "internationalizedArrayRichTextValue"; - value?: RichText; - language: string; -}; - -export type InternationalizedArrayStringValue = { - _type: "internationalizedArrayStringValue"; - value?: string; - language: string; -}; - export type MuxVideoAssetReference = { _ref: string; _type: "reference"; @@ -622,18 +648,7 @@ export type Geopoint = { export type AllSanitySchemaTypes = | VideoSettings - | SiteCookieBanner - | Code - | ErrorSettings - | InternationalizedArrayRichText - | InternationalizedArrayString - | SiteNav | SiteLanguageSettings - | SanityImageAssetReference - | SiteSettings - | SeoFallback - | SanityImageCrop - | SanityImageHotspot | RichTextMedia | RichText | ModuleText @@ -641,20 +656,32 @@ export type AllSanitySchemaTypes = | PageReference | ModuleContentRefs | ModuleMedia + | SanityImageAssetReference | ModuleCarousel | MediaVideo | MediaImage | NavLanguageSwitch | Link - | Page + | SeoFallback | SeoPage + | LinkFunctions + | TranslationMetadata + | InternationalizedArrayReference + | ErrorSettingsReference + | SiteNavReference + | SiteSettingsReference + | SiteCookieBannerReference + | InternationalizedArrayReferenceValue + | SiteCookieBanner + | Code + | SiteSettings + | SanityImageCrop + | SanityImageHotspot + | SiteNav + | ErrorSettings + | Page | Slug | Home - | InternationalizedArrayRichTextMedia - | LinkFunctions - | InternationalizedArrayRichTextMediaValue - | InternationalizedArrayRichTextValue - | InternationalizedArrayStringValue | MuxVideoAssetReference | MuxVideo | MuxVideoAsset diff --git a/studio/schema.json b/studio/schema.json index ce0f467..e893591 100644 --- a/studio/schema.json +++ b/studio/schema.json @@ -23,7 +23,7 @@ } }, { - "name": "siteCookieBanner", + "name": "siteLanguageSettings", "type": "document", "attributes": { "_id": { @@ -36,7 +36,7 @@ "type": "objectAttribute", "value": { "type": "string", - "value": "siteCookieBanner" + "value": "siteLanguageSettings" } }, "_createdAt": { @@ -64,322 +64,344 @@ }, "optional": true }, - "useCookieBanner": { - "type": "objectAttribute", - "value": { - "type": "boolean" - }, - "optional": true - }, - "consentModal": { - "type": "objectAttribute", - "value": { - "type": "object", - "attributes": { - "description": { - "type": "objectAttribute", - "value": { - "type": "string" - }, - "optional": true - }, - "acceptAllBtn": { - "type": "objectAttribute", - "value": { - "type": "string" - }, - "optional": true - }, - "acceptNecessaryBtn": { - "type": "objectAttribute", - "value": { - "type": "string" - }, - "optional": true - }, - "showPreferencesBtn": { - "type": "objectAttribute", - "value": { - "type": "string" - }, - "optional": true - } - } - }, - "optional": true - }, - "preferencesModal": { + "availableLanguages": { "type": "objectAttribute", "value": { - "type": "object", - "attributes": { - "title": { - "type": "objectAttribute", - "value": { - "type": "string" - }, - "optional": true - }, - "acceptAllBtn": { - "type": "objectAttribute", - "value": { - "type": "string" - }, - "optional": true - }, - "acceptNecessaryBtn": { - "type": "objectAttribute", - "value": { - "type": "string" + "type": "array", + "of": { + "type": "object", + "attributes": { + "id": { + "type": "objectAttribute", + "value": { + "type": "string" + }, + "optional": false }, - "optional": true - }, - "savePreferencesBtn": { - "type": "objectAttribute", - "value": { - "type": "string" + "title": { + "type": "objectAttribute", + "value": { + "type": "string" + }, + "optional": false }, - "optional": true + "_type": { + "type": "objectAttribute", + "value": { + "type": "string", + "value": "siteLanguage" + } + } }, - "sections": { - "type": "objectAttribute", - "value": { - "type": "inline", - "name": "code" - }, - "optional": true + "rest": { + "type": "object", + "attributes": { + "_key": { + "type": "objectAttribute", + "value": { + "type": "string" + } + } + } } } }, - "optional": true - } - } - }, - { - "name": "code", - "type": "type", - "value": { - "type": "object", - "attributes": { - "_type": { - "type": "objectAttribute", - "value": { - "type": "string", - "value": "code" - } - }, - "language": { - "type": "objectAttribute", - "value": { - "type": "string" - }, - "optional": true - }, - "filename": { - "type": "objectAttribute", - "value": { - "type": "string" - }, - "optional": true - }, - "code": { - "type": "objectAttribute", - "value": { - "type": "string" - }, - "optional": true - }, - "highlightedLines": { - "type": "objectAttribute", - "value": { - "type": "array", - "of": { - "type": "number" - } - }, - "optional": true - } - } - } - }, - { - "name": "errorSettings", - "type": "document", - "attributes": { - "_id": { - "type": "objectAttribute", - "value": { - "type": "string" - } - }, - "_type": { - "type": "objectAttribute", - "value": { - "type": "string", - "value": "errorSettings" - } - }, - "_createdAt": { - "type": "objectAttribute", - "value": { - "type": "string" - } - }, - "_updatedAt": { - "type": "objectAttribute", - "value": { - "type": "string" - } - }, - "_rev": { - "type": "objectAttribute", - "value": { - "type": "string" - } - }, - "notFoundTitle": { - "type": "objectAttribute", - "value": { - "type": "inline", - "name": "internationalizedArrayString" - }, "optional": false }, - "notFoundBody": { - "type": "objectAttribute", - "value": { - "type": "inline", - "name": "internationalizedArrayRichText" - }, - "optional": true - }, - "serverErrorTitle": { + "defaultLanguageId": { "type": "objectAttribute", "value": { - "type": "inline", - "name": "internationalizedArrayString" + "type": "string" }, "optional": false - }, - "serverErrorBody": { - "type": "objectAttribute", - "value": { - "type": "inline", - "name": "internationalizedArrayRichText" - }, - "optional": true - } - } - }, - { - "name": "internationalizedArrayRichText", - "type": "type", - "value": { - "type": "array", - "of": { - "type": "object", - "attributes": { - "_key": { - "type": "objectAttribute", - "value": { - "type": "string" - } - } - }, - "rest": { - "type": "inline", - "name": "internationalizedArrayRichTextValue" - } } } }, { - "name": "internationalizedArrayString", + "name": "richTextMedia", "type": "type", "value": { "type": "array", "of": { - "type": "object", - "attributes": { - "_key": { - "type": "objectAttribute", - "value": { - "type": "string" - } - } - }, - "rest": { - "type": "inline", - "name": "internationalizedArrayStringValue" - } + "type": "union", + "of": [ + { + "type": "object", + "attributes": { + "children": { + "type": "objectAttribute", + "value": { + "type": "array", + "of": { + "type": "object", + "attributes": { + "marks": { + "type": "objectAttribute", + "value": { + "type": "array", + "of": { + "type": "string" + } + }, + "optional": true + }, + "text": { + "type": "objectAttribute", + "value": { + "type": "string" + }, + "optional": true + }, + "_type": { + "type": "objectAttribute", + "value": { + "type": "string", + "value": "span" + } + } + }, + "rest": { + "type": "object", + "attributes": { + "_key": { + "type": "objectAttribute", + "value": { + "type": "string" + } + } + } + } + } + }, + "optional": true + }, + "style": { + "type": "objectAttribute", + "value": { + "type": "union", + "of": [ + { + "type": "string", + "value": "normal" + }, + { + "type": "string", + "value": "h2" + }, + { + "type": "string", + "value": "h3" + }, + { + "type": "string", + "value": "h4" + } + ] + }, + "optional": true + }, + "listItem": { + "type": "objectAttribute", + "value": { + "type": "union", + "of": [ + { + "type": "string", + "value": "bullet" + }, + { + "type": "string", + "value": "number" + } + ] + }, + "optional": true + }, + "markDefs": { + "type": "objectAttribute", + "value": { + "type": "array", + "of": { + "type": "object", + "attributes": { + "_key": { + "type": "objectAttribute", + "value": { + "type": "string" + } + } + }, + "rest": { + "type": "inline", + "name": "link" + } + } + }, + "optional": true + }, + "level": { + "type": "objectAttribute", + "value": { + "type": "number" + }, + "optional": true + }, + "_type": { + "type": "objectAttribute", + "value": { + "type": "string", + "value": "block" + } + } + }, + "rest": { + "type": "object", + "attributes": { + "_key": { + "type": "objectAttribute", + "value": { + "type": "string" + } + } + } + } + }, + { + "type": "object", + "attributes": { + "_key": { + "type": "objectAttribute", + "value": { + "type": "string" + } + } + }, + "rest": { + "type": "inline", + "name": "module.media" + } + }, + { + "type": "object", + "attributes": { + "_key": { + "type": "objectAttribute", + "value": { + "type": "string" + } + } + }, + "rest": { + "type": "inline", + "name": "module.carousel" + } + } + ] } } }, { - "name": "siteNav", - "type": "document", - "attributes": { - "_id": { - "type": "objectAttribute", - "value": { - "type": "string" - } - }, - "_type": { - "type": "objectAttribute", - "value": { - "type": "string", - "value": "siteNav" - } - }, - "_createdAt": { - "type": "objectAttribute", - "value": { - "type": "string" - } - }, - "_updatedAt": { - "type": "objectAttribute", - "value": { - "type": "string" - } - }, - "_rev": { - "type": "objectAttribute", - "value": { - "type": "string" - } - }, - "title": { - "type": "objectAttribute", - "value": { - "type": "string" - }, - "optional": true - }, - "mainMenu": { - "type": "objectAttribute", - "value": { - "type": "array", - "of": { - "type": "union", - "of": [ - { + "name": "richText", + "type": "type", + "value": { + "type": "array", + "of": { + "type": "object", + "attributes": { + "children": { + "type": "objectAttribute", + "value": { + "type": "array", + "of": { "type": "object", "attributes": { - "_key": { + "marks": { + "type": "objectAttribute", + "value": { + "type": "array", + "of": { + "type": "string" + } + }, + "optional": true + }, + "text": { "type": "objectAttribute", "value": { "type": "string" + }, + "optional": true + }, + "_type": { + "type": "objectAttribute", + "value": { + "type": "string", + "value": "span" + } + } + }, + "rest": { + "type": "object", + "attributes": { + "_key": { + "type": "objectAttribute", + "value": { + "type": "string" + } } } + } + } + }, + "optional": true + }, + "style": { + "type": "objectAttribute", + "value": { + "type": "union", + "of": [ + { + "type": "string", + "value": "normal" }, - "rest": { - "type": "inline", - "name": "link" + { + "type": "string", + "value": "h2" + }, + { + "type": "string", + "value": "h3" + }, + { + "type": "string", + "value": "h4" } - }, - { + ] + }, + "optional": true + }, + "listItem": { + "type": "objectAttribute", + "value": { + "type": "union", + "of": [ + { + "type": "string", + "value": "bullet" + }, + { + "type": "string", + "value": "number" + } + ] + }, + "optional": true + }, + "markDefs": { + "type": "objectAttribute", + "value": { + "type": "array", + "of": { "type": "object", "attributes": { "_key": { @@ -391,130 +413,245 @@ }, "rest": { "type": "inline", - "name": "nav.languageSwitch" + "name": "link" } } - ] + }, + "optional": true + }, + "level": { + "type": "objectAttribute", + "value": { + "type": "number" + }, + "optional": true + }, + "_type": { + "type": "objectAttribute", + "value": { + "type": "string", + "value": "block" + } } }, - "optional": true - }, - "footerMenu": { - "type": "objectAttribute", - "value": { - "type": "array", - "of": { - "type": "object", - "attributes": { - "_key": { - "type": "objectAttribute", - "value": { - "type": "string" - } + "rest": { + "type": "object", + "attributes": { + "_key": { + "type": "objectAttribute", + "value": { + "type": "string" } - }, - "rest": { - "type": "inline", - "name": "link" } } - }, - "optional": true + } } } }, { - "name": "siteLanguageSettings", - "type": "document", - "attributes": { - "_id": { - "type": "objectAttribute", - "value": { - "type": "string" - } - }, - "_type": { - "type": "objectAttribute", - "value": { - "type": "string", - "value": "siteLanguageSettings" - } - }, - "_createdAt": { - "type": "objectAttribute", - "value": { - "type": "string" - } - }, - "_updatedAt": { - "type": "objectAttribute", - "value": { - "type": "string" + "name": "module.text", + "type": "type", + "value": { + "type": "object", + "attributes": { + "_type": { + "type": "objectAttribute", + "value": { + "type": "string", + "value": "module.text" + } + }, + "title": { + "type": "objectAttribute", + "value": { + "type": "string" + }, + "optional": false + }, + "body": { + "type": "objectAttribute", + "value": { + "type": "inline", + "name": "richTextMedia" + }, + "optional": true } - }, - "_rev": { - "type": "objectAttribute", - "value": { - "type": "string" + } + } + }, + { + "type": "type", + "name": "home.reference", + "value": { + "type": "object", + "attributes": { + "_ref": { + "type": "objectAttribute", + "value": { + "type": "string" + } + }, + "_type": { + "type": "objectAttribute", + "value": { + "type": "string", + "value": "reference" + } + }, + "_weak": { + "type": "objectAttribute", + "value": { + "type": "boolean" + }, + "optional": true } }, - "title": { - "type": "objectAttribute", - "value": { - "type": "string" + "dereferencesTo": "home" + } + }, + { + "type": "type", + "name": "page.reference", + "value": { + "type": "object", + "attributes": { + "_ref": { + "type": "objectAttribute", + "value": { + "type": "string" + } }, - "optional": true + "_type": { + "type": "objectAttribute", + "value": { + "type": "string", + "value": "reference" + } + }, + "_weak": { + "type": "objectAttribute", + "value": { + "type": "boolean" + }, + "optional": true + } }, - "availableLanguages": { - "type": "objectAttribute", - "value": { - "type": "array", - "of": { - "type": "object", - "attributes": { - "id": { - "type": "objectAttribute", - "value": { - "type": "string" - }, - "optional": false - }, - "title": { - "type": "objectAttribute", - "value": { - "type": "string" - }, - "optional": false + "dereferencesTo": "page" + } + }, + { + "name": "module.contentRefs", + "type": "type", + "value": { + "type": "object", + "attributes": { + "_type": { + "type": "objectAttribute", + "value": { + "type": "string", + "value": "module.contentRefs" + } + }, + "heading": { + "type": "objectAttribute", + "value": { + "type": "string" + }, + "optional": true + }, + "allowMultiple": { + "type": "objectAttribute", + "value": { + "type": "boolean" + }, + "optional": true + }, + "reference": { + "type": "objectAttribute", + "value": { + "type": "union", + "of": [ + { + "type": "inline", + "name": "home.reference" }, - "_type": { - "type": "objectAttribute", - "value": { - "type": "string", - "value": "siteLanguage" - } + { + "type": "inline", + "name": "page.reference" } - }, - "rest": { - "type": "object", - "attributes": { - "_key": { - "type": "objectAttribute", - "value": { - "type": "string" - } + ] + }, + "optional": true + }, + "references": { + "type": "objectAttribute", + "value": { + "type": "array", + "of": { + "type": "union", + "of": [ + { + "type": "inline", + "name": "home.reference" + }, + { + "type": "inline", + "name": "page.reference" } - } + ] } + }, + "optional": true + } + } + } + }, + { + "name": "module.media", + "type": "type", + "value": { + "type": "object", + "attributes": { + "_type": { + "type": "objectAttribute", + "value": { + "type": "string", + "value": "module.media" } }, - "optional": false - }, - "defaultLanguageId": { - "type": "objectAttribute", - "value": { - "type": "string" + "type": { + "type": "objectAttribute", + "value": { + "type": "union", + "of": [ + { + "type": "string", + "value": "image" + }, + { + "type": "string", + "value": "video" + } + ] + }, + "optional": false }, - "optional": false + "imageContent": { + "type": "objectAttribute", + "value": { + "type": "inline", + "name": "media.image" + }, + "optional": true + }, + "videoContent": { + "type": "objectAttribute", + "value": { + "type": "inline", + "name": "media.video" + }, + "optional": true + } } } }, @@ -549,106 +686,120 @@ } }, { - "name": "siteSettings", - "type": "document", - "attributes": { - "_id": { - "type": "objectAttribute", - "value": { - "type": "string" - } - }, - "_type": { - "type": "objectAttribute", - "value": { - "type": "string", - "value": "siteSettings" - } - }, - "_createdAt": { - "type": "objectAttribute", - "value": { - "type": "string" - } - }, - "_updatedAt": { - "type": "objectAttribute", - "value": { - "type": "string" - } - }, - "_rev": { - "type": "objectAttribute", - "value": { - "type": "string" - } - }, - "title": { - "type": "objectAttribute", - "value": { - "type": "string" + "name": "module.carousel", + "type": "type", + "value": { + "type": "object", + "attributes": { + "_type": { + "type": "objectAttribute", + "value": { + "type": "string", + "value": "module.carousel" + } }, - "optional": false - }, - "favicon": { - "type": "objectAttribute", - "value": { - "type": "object", - "attributes": { - "asset": { - "type": "objectAttribute", - "value": { - "type": "inline", - "name": "sanity.imageAsset.reference" - }, - "optional": true - }, - "media": { - "type": "objectAttribute", - "value": { - "type": "unknown" - }, - "optional": true - }, - "hotspot": { - "type": "objectAttribute", - "value": { - "type": "inline", - "name": "sanity.imageHotspot" - }, - "optional": true - }, - "crop": { - "type": "objectAttribute", - "value": { - "type": "inline", - "name": "sanity.imageCrop" + "heading": { + "type": "objectAttribute", + "value": { + "type": "string" + }, + "optional": true + }, + "imagesOnly": { + "type": "objectAttribute", + "value": { + "type": "boolean" + }, + "optional": true + }, + "slides": { + "type": "objectAttribute", + "value": { + "type": "array", + "of": { + "type": "object", + "attributes": { + "asset": { + "type": "objectAttribute", + "value": { + "type": "inline", + "name": "sanity.imageAsset.reference" + }, + "optional": true + }, + "media": { + "type": "objectAttribute", + "value": { + "type": "unknown" + }, + "optional": true + }, + "hotspot": { + "type": "objectAttribute", + "value": { + "type": "inline", + "name": "sanity.imageHotspot" + }, + "optional": true + }, + "crop": { + "type": "objectAttribute", + "value": { + "type": "inline", + "name": "sanity.imageCrop" + }, + "optional": true + }, + "_type": { + "type": "objectAttribute", + "value": { + "type": "string", + "value": "image" + } + } }, - "optional": true - }, - "_type": { - "type": "objectAttribute", - "value": { - "type": "string", - "value": "image" + "rest": { + "type": "object", + "attributes": { + "_key": { + "type": "objectAttribute", + "value": { + "type": "string" + } + } + } } } - } - }, - "optional": true - }, - "seo": { - "type": "objectAttribute", - "value": { - "type": "inline", - "name": "seo.fallback" + }, + "optional": true }, - "optional": true + "slidesMedia": { + "type": "objectAttribute", + "value": { + "type": "array", + "of": { + "type": "object", + "attributes": { + "_key": { + "type": "objectAttribute", + "value": { + "type": "string" + } + } + }, + "rest": { + "type": "inline", + "name": "module.media" + } + } + }, + "optional": true + } } } }, { - "name": "seo.fallback", + "name": "media.video", "type": "type", "value": { "type": "object", @@ -657,24 +808,18 @@ "type": "objectAttribute", "value": { "type": "string", - "value": "seo.fallback" + "value": "media.video" } }, - "title": { - "type": "objectAttribute", - "value": { - "type": "string" - }, - "optional": true - }, - "description": { + "video": { "type": "objectAttribute", "value": { - "type": "string" + "type": "inline", + "name": "mux.video" }, "optional": true }, - "image": { + "poster": { "type": "objectAttribute", "value": { "type": "object", @@ -720,12 +865,27 @@ } }, "optional": true + }, + "videoSettings": { + "type": "objectAttribute", + "value": { + "type": "inline", + "name": "videoSettings" + }, + "optional": true + }, + "caption": { + "type": "objectAttribute", + "value": { + "type": "string" + }, + "optional": true } } } }, { - "name": "sanity.imageCrop", + "name": "media.image", "type": "type", "value": { "type": "object", @@ -734,42 +894,91 @@ "type": "objectAttribute", "value": { "type": "string", - "value": "sanity.imageCrop" + "value": "media.image" } }, - "top": { + "image": { "type": "objectAttribute", "value": { - "type": "number" + "type": "object", + "attributes": { + "asset": { + "type": "objectAttribute", + "value": { + "type": "inline", + "name": "sanity.imageAsset.reference" + }, + "optional": true + }, + "media": { + "type": "objectAttribute", + "value": { + "type": "unknown" + }, + "optional": true + }, + "hotspot": { + "type": "objectAttribute", + "value": { + "type": "inline", + "name": "sanity.imageHotspot" + }, + "optional": true + }, + "crop": { + "type": "objectAttribute", + "value": { + "type": "inline", + "name": "sanity.imageCrop" + }, + "optional": true + }, + "_type": { + "type": "objectAttribute", + "value": { + "type": "string", + "value": "image" + } + } + } }, - "optional": false + "optional": true }, - "bottom": { + "caption": { "type": "objectAttribute", "value": { - "type": "number" + "type": "string" }, - "optional": false - }, - "left": { + "optional": true + } + } + } + }, + { + "name": "nav.languageSwitch", + "type": "type", + "value": { + "type": "object", + "attributes": { + "_type": { "type": "objectAttribute", "value": { - "type": "number" - }, - "optional": false + "type": "string", + "value": "nav.languageSwitch" + } }, - "right": { + "blockKind": { "type": "objectAttribute", "value": { - "type": "number" + "type": "string" }, - "optional": false + "optional": true } } } }, { - "name": "sanity.imageHotspot", + "name": "link", "type": "type", "value": { "type": "object", @@ -778,166 +987,139 @@ "type": "objectAttribute", "value": { "type": "string", - "value": "sanity.imageHotspot" + "value": "link" } }, - "x": { + "type": { "type": "objectAttribute", "value": { - "type": "number" + "type": "union", + "of": [ + { + "type": "string", + "value": "internal" + }, + { + "type": "string", + "value": "external" + }, + { + "type": "string", + "value": "function" + } + ] }, "optional": false }, - "y": { + "title": { "type": "objectAttribute", "value": { - "type": "number" + "type": "string" }, - "optional": false + "optional": true }, - "height": { + "reference": { "type": "objectAttribute", "value": { - "type": "number" + "type": "union", + "of": [ + { + "type": "inline", + "name": "home.reference" + }, + { + "type": "inline", + "name": "page.reference" + } + ] }, - "optional": false + "optional": true }, - "width": { + "url": { "type": "objectAttribute", "value": { - "type": "number" + "type": "string" }, - "optional": false + "optional": true + }, + "blank": { + "type": "objectAttribute", + "value": { + "type": "boolean" + }, + "optional": true + }, + "func": { + "type": "objectAttribute", + "value": { + "type": "inline", + "name": "linkFunctions" + }, + "optional": true } } } }, { - "name": "richTextMedia", + "name": "seo.fallback", "type": "type", "value": { - "type": "array", - "of": { - "type": "union", - "of": [ - { + "type": "object", + "attributes": { + "_type": { + "type": "objectAttribute", + "value": { + "type": "string", + "value": "seo.fallback" + } + }, + "title": { + "type": "objectAttribute", + "value": { + "type": "string" + }, + "optional": true + }, + "description": { + "type": "objectAttribute", + "value": { + "type": "string" + }, + "optional": true + }, + "image": { + "type": "objectAttribute", + "value": { "type": "object", "attributes": { - "children": { - "type": "objectAttribute", - "value": { - "type": "array", - "of": { - "type": "object", - "attributes": { - "marks": { - "type": "objectAttribute", - "value": { - "type": "array", - "of": { - "type": "string" - } - }, - "optional": true - }, - "text": { - "type": "objectAttribute", - "value": { - "type": "string" - }, - "optional": true - }, - "_type": { - "type": "objectAttribute", - "value": { - "type": "string", - "value": "span" - } - } - }, - "rest": { - "type": "object", - "attributes": { - "_key": { - "type": "objectAttribute", - "value": { - "type": "string" - } - } - } - } - } - }, - "optional": true - }, - "style": { + "asset": { "type": "objectAttribute", "value": { - "type": "union", - "of": [ - { - "type": "string", - "value": "normal" - }, - { - "type": "string", - "value": "h2" - }, - { - "type": "string", - "value": "h3" - }, - { - "type": "string", - "value": "h4" - } - ] + "type": "inline", + "name": "sanity.imageAsset.reference" }, "optional": true }, - "listItem": { + "media": { "type": "objectAttribute", "value": { - "type": "union", - "of": [ - { - "type": "string", - "value": "bullet" - }, - { - "type": "string", - "value": "number" - } - ] + "type": "unknown" }, "optional": true }, - "markDefs": { + "hotspot": { "type": "objectAttribute", "value": { - "type": "array", - "of": { - "type": "object", - "attributes": { - "_key": { - "type": "objectAttribute", - "value": { - "type": "string" - } - } - }, - "rest": { - "type": "inline", - "name": "link" - } - } + "type": "inline", + "name": "sanity.imageHotspot" }, "optional": true }, - "level": { + "crop": { "type": "objectAttribute", "value": { - "type": "number" + "type": "inline", + "name": "sanity.imageCrop" }, "optional": true }, @@ -945,206 +1127,95 @@ "type": "objectAttribute", "value": { "type": "string", - "value": "block" - } - } - }, - "rest": { - "type": "object", - "attributes": { - "_key": { - "type": "objectAttribute", - "value": { - "type": "string" - } - } - } - } - }, - { - "type": "object", - "attributes": { - "_key": { - "type": "objectAttribute", - "value": { - "type": "string" + "value": "image" } } - }, - "rest": { - "type": "inline", - "name": "module.media" } }, - { - "type": "object", - "attributes": { - "_key": { - "type": "objectAttribute", - "value": { - "type": "string" - } - } - }, - "rest": { - "type": "inline", - "name": "module.carousel" - } - } - ] + "optional": true + } } } }, { - "name": "richText", + "name": "seo.page", "type": "type", "value": { - "type": "array", - "of": { - "type": "object", - "attributes": { - "children": { - "type": "objectAttribute", - "value": { - "type": "array", - "of": { - "type": "object", - "attributes": { - "marks": { - "type": "objectAttribute", - "value": { - "type": "array", - "of": { - "type": "string" - } - }, - "optional": true - }, - "text": { - "type": "objectAttribute", - "value": { - "type": "string" - }, - "optional": true - }, - "_type": { - "type": "objectAttribute", - "value": { - "type": "string", - "value": "span" - } - } - }, - "rest": { - "type": "object", - "attributes": { - "_key": { - "type": "objectAttribute", - "value": { - "type": "string" - } - } - } - } - } - }, - "optional": true + "type": "object", + "attributes": { + "_type": { + "type": "objectAttribute", + "value": { + "type": "string", + "value": "seo.page" + } + }, + "title": { + "type": "objectAttribute", + "value": { + "type": "string" }, - "style": { - "type": "objectAttribute", - "value": { - "type": "union", - "of": [ - { - "type": "string", - "value": "normal" + "optional": true + }, + "description": { + "type": "objectAttribute", + "value": { + "type": "string" + }, + "optional": true + }, + "image": { + "type": "objectAttribute", + "value": { + "type": "object", + "attributes": { + "asset": { + "type": "objectAttribute", + "value": { + "type": "inline", + "name": "sanity.imageAsset.reference" }, - { - "type": "string", - "value": "h2" + "optional": true + }, + "media": { + "type": "objectAttribute", + "value": { + "type": "unknown" }, - { - "type": "string", - "value": "h3" + "optional": true + }, + "hotspot": { + "type": "objectAttribute", + "value": { + "type": "inline", + "name": "sanity.imageHotspot" }, - { - "type": "string", - "value": "h4" - } - ] - }, - "optional": true - }, - "listItem": { - "type": "objectAttribute", - "value": { - "type": "union", - "of": [ - { - "type": "string", - "value": "bullet" + "optional": true + }, + "crop": { + "type": "objectAttribute", + "value": { + "type": "inline", + "name": "sanity.imageCrop" }, - { + "optional": true + }, + "_type": { + "type": "objectAttribute", + "value": { "type": "string", - "value": "number" - } - ] - }, - "optional": true - }, - "markDefs": { - "type": "objectAttribute", - "value": { - "type": "array", - "of": { - "type": "object", - "attributes": { - "_key": { - "type": "objectAttribute", - "value": { - "type": "string" - } - } - }, - "rest": { - "type": "inline", - "name": "link" + "value": "image" } } - }, - "optional": true - }, - "level": { - "type": "objectAttribute", - "value": { - "type": "number" - }, - "optional": true - }, - "_type": { - "type": "objectAttribute", - "value": { - "type": "string", - "value": "block" - } - } - }, - "rest": { - "type": "object", - "attributes": { - "_key": { - "type": "objectAttribute", - "value": { - "type": "string" - } } - } + }, + "optional": true } } } }, { - "name": "module.text", + "name": "linkFunctions", "type": "type", "value": { "type": "object", @@ -1153,22 +1224,30 @@ "type": "objectAttribute", "value": { "type": "string", - "value": "module.text" + "value": "linkFunctions" } }, - "title": { + "key": { "type": "objectAttribute", "value": { - "type": "inline", - "name": "internationalizedArrayString" + "type": "union", + "of": [ + { + "type": "string", + "value": "scroll-to" + }, + { + "type": "string", + "value": "open-modal" + } + ] }, "optional": false }, - "body": { + "params": { "type": "objectAttribute", "value": { - "type": "inline", - "name": "internationalizedArrayRichTextMedia" + "type": "string" }, "optional": true } @@ -1176,8 +1255,85 @@ } }, { + "name": "translation.metadata", + "type": "document", + "attributes": { + "_id": { + "type": "objectAttribute", + "value": { + "type": "string" + } + }, + "_type": { + "type": "objectAttribute", + "value": { + "type": "string", + "value": "translation.metadata" + } + }, + "_createdAt": { + "type": "objectAttribute", + "value": { + "type": "string" + } + }, + "_updatedAt": { + "type": "objectAttribute", + "value": { + "type": "string" + } + }, + "_rev": { + "type": "objectAttribute", + "value": { + "type": "string" + } + }, + "translations": { + "type": "objectAttribute", + "value": { + "type": "inline", + "name": "internationalizedArrayReference" + }, + "optional": true + }, + "schemaTypes": { + "type": "objectAttribute", + "value": { + "type": "array", + "of": { + "type": "string" + } + }, + "optional": true + } + } + }, + { + "name": "internationalizedArrayReference", "type": "type", - "name": "home.reference", + "value": { + "type": "array", + "of": { + "type": "object", + "attributes": { + "_key": { + "type": "objectAttribute", + "value": { + "type": "string" + } + } + }, + "rest": { + "type": "inline", + "name": "internationalizedArrayReferenceValue" + } + } + } + }, + { + "type": "type", + "name": "errorSettings.reference", "value": { "type": "object", "attributes": { @@ -1202,12 +1358,12 @@ "optional": true } }, - "dereferencesTo": "home" + "dereferencesTo": "errorSettings" } }, { "type": "type", - "name": "page.reference", + "name": "siteNav.reference", "value": { "type": "object", "attributes": { @@ -1232,128 +1388,71 @@ "optional": true } }, - "dereferencesTo": "page" + "dereferencesTo": "siteNav" } }, { - "name": "module.contentRefs", "type": "type", + "name": "siteSettings.reference", "value": { "type": "object", "attributes": { - "_type": { + "_ref": { "type": "objectAttribute", "value": { - "type": "string", - "value": "module.contentRefs" + "type": "string" } - }, - "heading": { - "type": "objectAttribute", - "value": { - "type": "inline", - "name": "internationalizedArrayString" - }, - "optional": true - }, - "allowMultiple": { - "type": "objectAttribute", - "value": { - "type": "boolean" - }, - "optional": true - }, - "reference": { - "type": "objectAttribute", - "value": { - "type": "union", - "of": [ - { - "type": "inline", - "name": "home.reference" - }, - { - "type": "inline", - "name": "page.reference" - } - ] - }, - "optional": true - }, - "references": { - "type": "objectAttribute", - "value": { - "type": "array", - "of": { - "type": "union", - "of": [ - { - "type": "inline", - "name": "home.reference" - }, - { - "type": "inline", - "name": "page.reference" - } - ] - } + }, + "_type": { + "type": "objectAttribute", + "value": { + "type": "string", + "value": "reference" + } + }, + "_weak": { + "type": "objectAttribute", + "value": { + "type": "boolean" }, "optional": true } - } + }, + "dereferencesTo": "siteSettings" } }, { - "name": "module.media", "type": "type", + "name": "siteCookieBanner.reference", "value": { "type": "object", "attributes": { - "_type": { + "_ref": { "type": "objectAttribute", "value": { - "type": "string", - "value": "module.media" + "type": "string" } }, - "type": { - "type": "objectAttribute", - "value": { - "type": "union", - "of": [ - { - "type": "string", - "value": "image" - }, - { - "type": "string", - "value": "video" - } - ] - }, - "optional": false - }, - "imageContent": { + "_type": { "type": "objectAttribute", "value": { - "type": "inline", - "name": "media.image" - }, - "optional": true + "type": "string", + "value": "reference" + } }, - "videoContent": { + "_weak": { "type": "objectAttribute", "value": { - "type": "inline", - "name": "media.video" + "type": "boolean" }, "optional": true } - } + }, + "dereferencesTo": "siteCookieBanner" } }, { - "name": "module.carousel", + "name": "internationalizedArrayReferenceValue", "type": "type", "value": { "type": "object", @@ -1362,112 +1461,194 @@ "type": "objectAttribute", "value": { "type": "string", - "value": "module.carousel" + "value": "internationalizedArrayReferenceValue" } }, - "heading": { + "value": { "type": "objectAttribute", "value": { - "type": "inline", - "name": "internationalizedArrayString" + "type": "union", + "of": [ + { + "type": "inline", + "name": "home.reference" + }, + { + "type": "inline", + "name": "page.reference" + }, + { + "type": "inline", + "name": "errorSettings.reference" + }, + { + "type": "inline", + "name": "siteNav.reference" + }, + { + "type": "inline", + "name": "siteSettings.reference" + }, + { + "type": "inline", + "name": "siteCookieBanner.reference" + } + ] }, "optional": true }, - "imagesOnly": { + "language": { "type": "objectAttribute", "value": { - "type": "boolean" + "type": "string" }, - "optional": true + "optional": false + } + } + } + }, + { + "name": "siteCookieBanner", + "type": "document", + "attributes": { + "_id": { + "type": "objectAttribute", + "value": { + "type": "string" + } + }, + "_type": { + "type": "objectAttribute", + "value": { + "type": "string", + "value": "siteCookieBanner" + } + }, + "_createdAt": { + "type": "objectAttribute", + "value": { + "type": "string" + } + }, + "_updatedAt": { + "type": "objectAttribute", + "value": { + "type": "string" + } + }, + "_rev": { + "type": "objectAttribute", + "value": { + "type": "string" + } + }, + "language": { + "type": "objectAttribute", + "value": { + "type": "string" }, - "slides": { - "type": "objectAttribute", - "value": { - "type": "array", - "of": { - "type": "object", - "attributes": { - "asset": { - "type": "objectAttribute", - "value": { - "type": "inline", - "name": "sanity.imageAsset.reference" - }, - "optional": true - }, - "media": { - "type": "objectAttribute", - "value": { - "type": "unknown" - }, - "optional": true - }, - "hotspot": { - "type": "objectAttribute", - "value": { - "type": "inline", - "name": "sanity.imageHotspot" - }, - "optional": true - }, - "crop": { - "type": "objectAttribute", - "value": { - "type": "inline", - "name": "sanity.imageCrop" - }, - "optional": true - }, - "_type": { - "type": "objectAttribute", - "value": { - "type": "string", - "value": "image" - } - } + "optional": true + }, + "title": { + "type": "objectAttribute", + "value": { + "type": "string" + }, + "optional": true + }, + "useCookieBanner": { + "type": "objectAttribute", + "value": { + "type": "boolean" + }, + "optional": true + }, + "consentModal": { + "type": "objectAttribute", + "value": { + "type": "object", + "attributes": { + "description": { + "type": "objectAttribute", + "value": { + "type": "string" }, - "rest": { - "type": "object", - "attributes": { - "_key": { - "type": "objectAttribute", - "value": { - "type": "string" - } - } - } - } + "optional": true + }, + "acceptAllBtn": { + "type": "objectAttribute", + "value": { + "type": "string" + }, + "optional": true + }, + "acceptNecessaryBtn": { + "type": "objectAttribute", + "value": { + "type": "string" + }, + "optional": true + }, + "showPreferencesBtn": { + "type": "objectAttribute", + "value": { + "type": "string" + }, + "optional": true } - }, - "optional": true + } }, - "slidesMedia": { - "type": "objectAttribute", - "value": { - "type": "array", - "of": { - "type": "object", - "attributes": { - "_key": { - "type": "objectAttribute", - "value": { - "type": "string" - } - } + "optional": true + }, + "preferencesModal": { + "type": "objectAttribute", + "value": { + "type": "object", + "attributes": { + "title": { + "type": "objectAttribute", + "value": { + "type": "string" }, - "rest": { + "optional": true + }, + "acceptAllBtn": { + "type": "objectAttribute", + "value": { + "type": "string" + }, + "optional": true + }, + "acceptNecessaryBtn": { + "type": "objectAttribute", + "value": { + "type": "string" + }, + "optional": true + }, + "savePreferencesBtn": { + "type": "objectAttribute", + "value": { + "type": "string" + }, + "optional": true + }, + "sections": { + "type": "objectAttribute", + "value": { "type": "inline", - "name": "module.media" - } + "name": "code" + }, + "optional": true } - }, - "optional": true - } + } + }, + "optional": true } } }, { - "name": "media.video", + "name": "code", "type": "type", "value": { "type": "object", @@ -1476,76 +1657,37 @@ "type": "objectAttribute", "value": { "type": "string", - "value": "media.video" + "value": "code" } }, - "video": { + "language": { "type": "objectAttribute", "value": { - "type": "inline", - "name": "mux.video" + "type": "string" }, "optional": true }, - "poster": { + "filename": { "type": "objectAttribute", "value": { - "type": "object", - "attributes": { - "asset": { - "type": "objectAttribute", - "value": { - "type": "inline", - "name": "sanity.imageAsset.reference" - }, - "optional": true - }, - "media": { - "type": "objectAttribute", - "value": { - "type": "unknown" - }, - "optional": true - }, - "hotspot": { - "type": "objectAttribute", - "value": { - "type": "inline", - "name": "sanity.imageHotspot" - }, - "optional": true - }, - "crop": { - "type": "objectAttribute", - "value": { - "type": "inline", - "name": "sanity.imageCrop" - }, - "optional": true - }, - "_type": { - "type": "objectAttribute", - "value": { - "type": "string", - "value": "image" - } - } - } + "type": "string" }, "optional": true }, - "videoSettings": { + "code": { "type": "objectAttribute", "value": { - "type": "inline", - "name": "videoSettings" + "type": "string" }, "optional": true }, - "caption": { + "highlightedLines": { "type": "objectAttribute", "value": { - "type": "string" + "type": "array", + "of": { + "type": "number" + } }, "optional": true } @@ -1553,77 +1695,113 @@ } }, { - "name": "media.image", - "type": "type", - "value": { - "type": "object", - "attributes": { - "_type": { - "type": "objectAttribute", - "value": { - "type": "string", - "value": "media.image" - } + "name": "siteSettings", + "type": "document", + "attributes": { + "_id": { + "type": "objectAttribute", + "value": { + "type": "string" + } + }, + "_type": { + "type": "objectAttribute", + "value": { + "type": "string", + "value": "siteSettings" + } + }, + "_createdAt": { + "type": "objectAttribute", + "value": { + "type": "string" + } + }, + "_updatedAt": { + "type": "objectAttribute", + "value": { + "type": "string" + } + }, + "_rev": { + "type": "objectAttribute", + "value": { + "type": "string" + } + }, + "language": { + "type": "objectAttribute", + "value": { + "type": "string" }, - "image": { - "type": "objectAttribute", - "value": { - "type": "object", - "attributes": { - "asset": { - "type": "objectAttribute", - "value": { - "type": "inline", - "name": "sanity.imageAsset.reference" - }, - "optional": true + "optional": true + }, + "title": { + "type": "objectAttribute", + "value": { + "type": "string" + }, + "optional": false + }, + "favicon": { + "type": "objectAttribute", + "value": { + "type": "object", + "attributes": { + "asset": { + "type": "objectAttribute", + "value": { + "type": "inline", + "name": "sanity.imageAsset.reference" }, - "media": { - "type": "objectAttribute", - "value": { - "type": "unknown" - }, - "optional": true + "optional": true + }, + "media": { + "type": "objectAttribute", + "value": { + "type": "unknown" }, - "hotspot": { - "type": "objectAttribute", - "value": { - "type": "inline", - "name": "sanity.imageHotspot" - }, - "optional": true + "optional": true + }, + "hotspot": { + "type": "objectAttribute", + "value": { + "type": "inline", + "name": "sanity.imageHotspot" }, - "crop": { - "type": "objectAttribute", - "value": { - "type": "inline", - "name": "sanity.imageCrop" - }, - "optional": true + "optional": true + }, + "crop": { + "type": "objectAttribute", + "value": { + "type": "inline", + "name": "sanity.imageCrop" }, - "_type": { - "type": "objectAttribute", - "value": { - "type": "string", - "value": "image" - } + "optional": true + }, + "_type": { + "type": "objectAttribute", + "value": { + "type": "string", + "value": "image" } } - }, - "optional": true + } }, - "caption": { - "type": "objectAttribute", - "value": { - "type": "string" - }, - "optional": true - } + "optional": true + }, + "seo": { + "type": "objectAttribute", + "value": { + "type": "inline", + "name": "seo.fallback" + }, + "optional": true } } }, { - "name": "nav.languageSwitch", + "name": "sanity.imageCrop", "type": "type", "value": { "type": "object", @@ -1632,21 +1810,42 @@ "type": "objectAttribute", "value": { "type": "string", - "value": "nav.languageSwitch" + "value": "sanity.imageCrop" } }, - "blockKind": { + "top": { "type": "objectAttribute", "value": { - "type": "string" + "type": "number" }, - "optional": true + "optional": false + }, + "bottom": { + "type": "objectAttribute", + "value": { + "type": "number" + }, + "optional": false + }, + "left": { + "type": "objectAttribute", + "value": { + "type": "number" + }, + "optional": false + }, + "right": { + "type": "objectAttribute", + "value": { + "type": "number" + }, + "optional": false } } } }, { - "name": "link", + "name": "sanity.imageHotspot", "type": "type", "value": { "type": "object", @@ -1655,76 +1854,226 @@ "type": "objectAttribute", "value": { "type": "string", - "value": "link" + "value": "sanity.imageHotspot" } }, - "type": { + "x": { "type": "objectAttribute", "value": { - "type": "union", - "of": [ - { - "type": "string", - "value": "internal" - }, - { - "type": "string", - "value": "external" - }, - { - "type": "string", - "value": "function" - } - ] + "type": "number" }, "optional": false }, - "title": { + "y": { "type": "objectAttribute", "value": { - "type": "string" + "type": "number" }, - "optional": true + "optional": false }, - "reference": { + "height": { + "type": "objectAttribute", + "value": { + "type": "number" + }, + "optional": false + }, + "width": { "type": "objectAttribute", "value": { + "type": "number" + }, + "optional": false + } + } + } + }, + { + "name": "siteNav", + "type": "document", + "attributes": { + "_id": { + "type": "objectAttribute", + "value": { + "type": "string" + } + }, + "_type": { + "type": "objectAttribute", + "value": { + "type": "string", + "value": "siteNav" + } + }, + "_createdAt": { + "type": "objectAttribute", + "value": { + "type": "string" + } + }, + "_updatedAt": { + "type": "objectAttribute", + "value": { + "type": "string" + } + }, + "_rev": { + "type": "objectAttribute", + "value": { + "type": "string" + } + }, + "language": { + "type": "objectAttribute", + "value": { + "type": "string" + }, + "optional": true + }, + "title": { + "type": "objectAttribute", + "value": { + "type": "string" + }, + "optional": true + }, + "mainMenu": { + "type": "objectAttribute", + "value": { + "type": "array", + "of": { "type": "union", "of": [ { - "type": "inline", - "name": "home.reference" + "type": "object", + "attributes": { + "_key": { + "type": "objectAttribute", + "value": { + "type": "string" + } + } + }, + "rest": { + "type": "inline", + "name": "link" + } }, { - "type": "inline", - "name": "page.reference" + "type": "object", + "attributes": { + "_key": { + "type": "objectAttribute", + "value": { + "type": "string" + } + } + }, + "rest": { + "type": "inline", + "name": "nav.languageSwitch" + } } ] - }, - "optional": true + } }, - "url": { - "type": "objectAttribute", - "value": { - "type": "string" - }, - "optional": true + "optional": true + }, + "footerMenu": { + "type": "objectAttribute", + "value": { + "type": "array", + "of": { + "type": "object", + "attributes": { + "_key": { + "type": "objectAttribute", + "value": { + "type": "string" + } + } + }, + "rest": { + "type": "inline", + "name": "link" + } + } }, - "blank": { - "type": "objectAttribute", - "value": { - "type": "boolean" - }, - "optional": true + "optional": true + } + } + }, + { + "name": "errorSettings", + "type": "document", + "attributes": { + "_id": { + "type": "objectAttribute", + "value": { + "type": "string" + } + }, + "_type": { + "type": "objectAttribute", + "value": { + "type": "string", + "value": "errorSettings" + } + }, + "_createdAt": { + "type": "objectAttribute", + "value": { + "type": "string" + } + }, + "_updatedAt": { + "type": "objectAttribute", + "value": { + "type": "string" + } + }, + "_rev": { + "type": "objectAttribute", + "value": { + "type": "string" + } + }, + "language": { + "type": "objectAttribute", + "value": { + "type": "string" + }, + "optional": true + }, + "notFoundTitle": { + "type": "objectAttribute", + "value": { + "type": "string" + }, + "optional": false + }, + "notFoundBody": { + "type": "objectAttribute", + "value": { + "type": "inline", + "name": "richText" + }, + "optional": true + }, + "serverErrorTitle": { + "type": "objectAttribute", + "value": { + "type": "string" }, - "func": { - "type": "objectAttribute", - "value": { - "type": "inline", - "name": "linkFunctions" - }, - "optional": true - } + "optional": false + }, + "serverErrorBody": { + "type": "objectAttribute", + "value": { + "type": "inline", + "name": "richText" + }, + "optional": true } } }, @@ -1763,11 +2112,17 @@ "type": "string" } }, + "language": { + "type": "objectAttribute", + "value": { + "type": "string" + }, + "optional": true + }, "title": { "type": "objectAttribute", "value": { - "type": "inline", - "name": "internationalizedArrayString" + "type": "string" }, "optional": false }, @@ -1861,83 +2216,6 @@ } } }, - { - "name": "seo.page", - "type": "type", - "value": { - "type": "object", - "attributes": { - "_type": { - "type": "objectAttribute", - "value": { - "type": "string", - "value": "seo.page" - } - }, - "title": { - "type": "objectAttribute", - "value": { - "type": "string" - }, - "optional": true - }, - "description": { - "type": "objectAttribute", - "value": { - "type": "string" - }, - "optional": true - }, - "image": { - "type": "objectAttribute", - "value": { - "type": "object", - "attributes": { - "asset": { - "type": "objectAttribute", - "value": { - "type": "inline", - "name": "sanity.imageAsset.reference" - }, - "optional": true - }, - "media": { - "type": "objectAttribute", - "value": { - "type": "unknown" - }, - "optional": true - }, - "hotspot": { - "type": "objectAttribute", - "value": { - "type": "inline", - "name": "sanity.imageHotspot" - }, - "optional": true - }, - "crop": { - "type": "objectAttribute", - "value": { - "type": "inline", - "name": "sanity.imageCrop" - }, - "optional": true - }, - "_type": { - "type": "objectAttribute", - "value": { - "type": "string", - "value": "image" - } - } - } - }, - "optional": true - } - } - } - }, { "name": "slug", "type": "type", @@ -2003,11 +2281,17 @@ "type": "string" } }, + "language": { + "type": "objectAttribute", + "value": { + "type": "string" + }, + "optional": true + }, "title": { "type": "objectAttribute", "value": { - "type": "inline", - "name": "internationalizedArrayString" + "type": "string" }, "optional": false }, @@ -2093,160 +2377,6 @@ } } }, - { - "name": "internationalizedArrayRichTextMedia", - "type": "type", - "value": { - "type": "array", - "of": { - "type": "object", - "attributes": { - "_key": { - "type": "objectAttribute", - "value": { - "type": "string" - } - } - }, - "rest": { - "type": "inline", - "name": "internationalizedArrayRichTextMediaValue" - } - } - } - }, - { - "name": "linkFunctions", - "type": "type", - "value": { - "type": "object", - "attributes": { - "_type": { - "type": "objectAttribute", - "value": { - "type": "string", - "value": "linkFunctions" - } - }, - "key": { - "type": "objectAttribute", - "value": { - "type": "union", - "of": [ - { - "type": "string", - "value": "scroll-to" - }, - { - "type": "string", - "value": "open-modal" - } - ] - }, - "optional": false - }, - "params": { - "type": "objectAttribute", - "value": { - "type": "string" - }, - "optional": true - } - } - } - }, - { - "name": "internationalizedArrayRichTextMediaValue", - "type": "type", - "value": { - "type": "object", - "attributes": { - "_type": { - "type": "objectAttribute", - "value": { - "type": "string", - "value": "internationalizedArrayRichTextMediaValue" - } - }, - "value": { - "type": "objectAttribute", - "value": { - "type": "inline", - "name": "richTextMedia" - }, - "optional": true - }, - "language": { - "type": "objectAttribute", - "value": { - "type": "string" - }, - "optional": false - } - } - } - }, - { - "name": "internationalizedArrayRichTextValue", - "type": "type", - "value": { - "type": "object", - "attributes": { - "_type": { - "type": "objectAttribute", - "value": { - "type": "string", - "value": "internationalizedArrayRichTextValue" - } - }, - "value": { - "type": "objectAttribute", - "value": { - "type": "inline", - "name": "richText" - }, - "optional": true - }, - "language": { - "type": "objectAttribute", - "value": { - "type": "string" - }, - "optional": false - } - } - } - }, - { - "name": "internationalizedArrayStringValue", - "type": "type", - "value": { - "type": "object", - "attributes": { - "_type": { - "type": "objectAttribute", - "value": { - "type": "string", - "value": "internationalizedArrayStringValue" - } - }, - "value": { - "type": "objectAttribute", - "value": { - "type": "string" - }, - "optional": true - }, - "language": { - "type": "objectAttribute", - "value": { - "type": "string" - }, - "optional": false - } - } - } - }, { "type": "type", "name": "mux.videoAsset.reference", diff --git a/studio/schemas/documents/page.ts b/studio/schemas/documents/page.ts index 4de5cb3..238fa50 100644 --- a/studio/schemas/documents/page.ts +++ b/studio/schemas/documents/page.ts @@ -1,6 +1,6 @@ import { DocumentTextIcon, SearchIcon, TextIcon } from "@sanity/icons"; import { defineType } from "sanity"; -import { validateSlug } from "../../utils/validateSlug"; +import { isUniqueLocaleAgnostic, validateSlug } from "../../utils/validateSlug"; import { modulesArrayField } from "../fields/modulesArrayField"; export const page = defineType({ @@ -38,10 +38,11 @@ export const page = defineType({ name: "slug", title: "Path", description: - "URL path for this page (e.g. yoursite.com/my-path). Use lowercase letters, numbers, and hyphens.", + "URL path for this page (e.g. yoursite.com/my-path). Use lowercase letters, numbers, and hyphens. The same slug may be reused on different language variants.", type: "slug", options: { maxLength: 96, + isUnique: isUniqueLocaleAgnostic, }, validation: validateSlug, group: "editorial", diff --git a/studio/utils/validateSlug.ts b/studio/utils/validateSlug.ts index d81f9f5..6740a58 100644 --- a/studio/utils/validateSlug.ts +++ b/studio/utils/validateSlug.ts @@ -1,7 +1,36 @@ -import type { Rule, SlugValue } from "sanity"; +import type { Rule, SlugIsUniqueValidator, SlugValue } from "sanity"; const slugPattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/; +/** + * Slug uniqueness scoped per `_type` AND `language`, so the same slug can + * coexist on different language variants of the same document — required + * under document-level translation (`@sanity/document-internationalization`). + */ +export const isUniqueLocaleAgnostic: SlugIsUniqueValidator = async ( + slug, + context, +) => { + const { document, getClient } = context; + if (!document) return true; + const id = document._id.replace(/^drafts\./, ""); + const client = getClient({ apiVersion: "2024-01-01" }); + const params = { + draft: `drafts.${id}`, + published: id, + slug, + type: document._type, + language: (document as { language?: string | null }).language ?? null, + }; + const query = `count(*[ + _type == $type && + !(_id in [$draft, $published]) && + slug.current == $slug && + language == $language + ]) == 0`; + return client.fetch(query, params); +}; + export function validateSlug(rule: Rule) { return rule.required().custom((value: SlugValue | undefined) => { const current = value?.current?.trim(); From 0c5799e1a331cc1b89aa8de335167fbcfd05f6f2 Mon Sep 17 00:00:00 2001 From: Damian Date: Thu, 28 May 2026 17:44:54 +0200 Subject: [PATCH 03/45] =?UTF-8?q?variant:=20Step=205=E2=80=939=20=E2=80=94?= =?UTF-8?q?=20full=20web=20migration=20to=20document-level=20translation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Web is now consistent with the Studio's document-level model: QUERIES - `pages/home.ts` and `pages/page.ts` filter by `language == $locale` and project plain `title` (no field-level array wrapper). Both wrap in `defineQuery` — the typegen-blocking `richTextMediaQuery` recursion is gone, so the full pipeline can be typed now. - `snippets/settings.ts`: every settings/nav query takes `$locale` and filters at the document level; `internationalizedRichTextArrayField` helper deleted; error-body fields project plain `richText` blocks. - `snippets/sitemap.ts`: rows carry the document's `language` so each language variant is emitted as a separate URL. - `components/text/richTextMedia.ts`: rewrite from the recursive `buildRichTextMediaQuery(depth)` function to a static depth-2 inline literal (typegen-friendly, idempotent). - `components/modules/text.ts`: flatten body — no `{language, value}` wrapper. TYPES - `web/sanity/utils/sanityLocalizedText.ts` deleted; the utils barrel drops its re-exports of `parseLocalizedText`, `pickLocalizedString`, `pickLocalizedPortableTextBlocks`, `resolveLocalizedPortableTextDeep` and the `Intl*Entry` shapes. - Hand-written types in `web/sanity/types/{pages,errorSettings,modules/*}` switch from `Intl*Entry[]` to plain `string`/`PortableTextBlock[]`, and gain a top-level `language` field where the doc carries one. - `web/sanity/sanity.types.gen.ts` regenerates with 7 typed queries (HomeQueryResult and PageBySlugQueryResult now resolve cleanly — the full-pipeline bonus promised in the plan). FETCH WRAPPERS / ROUTES - `fetchSanityData.ts`: every per-document wrapper takes `locale: string` (`fetchHomeDocument`, `fetchPageBySlug`, `fetchErrorSettings`, `fetchSiteSettingsTitle`, `fetchSettingsSeoFallback`, `fetchSiteNavMenus`). `fetchSiteLanguageSettings` stays unparameterised — it is the locale registry itself. - `cachedSanityQuery.ts`: `cachedPageDocumentBySlug(slug, locale)` and `cachedHomeDocument(locale)`; cache keys/tags become `page-{slug}-{locale}` / `home-{locale}`. - `/api/revalidate/route.ts`: payload validation accepts `language`; the emitted tags include it for `home` and `page`. - Route pages (`[locale]/page.tsx`, `[locale]/[slug]/page.tsx`, `not-found.tsx`, `sitemap.ts`) thread `locale` through every fetch. - `generateStaticParams` for `[slug]` now uses each page document's declared `language` instead of the previous cartesian locale × slug product — only the slugs that actually exist for a language are pre- rendered. COMPONENTS - `ModuleText`, `ModulesRenderer`, `LocaleNotFoundContent`, `resolveSanityMetadata` access `module.title`/`data.title` directly; no more `pickLocalized*` calls. The `siteLocale` prop drops off the module renderer chain. VERIFICATION - `pnpm -r run typecheck`: green on studio AND web. - `pnpm --filter studio run generate` + `--filter web run generate`: both idempotent against the committed artifacts. - `pnpm --filter studio run build` and `pnpm --filter web run build`: green. - `pnpm lint`: clean (only the pre-existing `useOptionalChain` warning on `RichTextMedia.tsx`). The branch is now functionally complete. Issue #18 remains open as the tracking issue for the variant. Co-Authored-By: Claude Opus 4.7 (1M context) --- web/sanity/cachedSanityQuery.ts | 80 +- web/sanity/fetchSanityData.ts | 64 +- web/sanity/queries/components/modules/text.ts | 11 +- .../queries/components/text/richTextMedia.ts | 54 +- web/sanity/queries/pages/home.ts | 8 +- web/sanity/queries/pages/page.ts | 8 +- web/sanity/queries/snippets/settings.ts | 59 +- web/sanity/queries/snippets/sitemap.ts | 21 +- web/sanity/sanity.types.gen.ts | 2541 +++++++++++++++-- web/sanity/seo/resolveSanityMetadata.ts | 10 +- web/sanity/types/errorSettings.ts | 12 +- web/sanity/types/modules/carousel.ts | 3 +- web/sanity/types/modules/contentRefs.ts | 6 +- web/sanity/types/modules/text.ts | 6 +- web/sanity/types/pages.ts | 7 +- web/sanity/utils/index.ts | 9 - web/sanity/utils/sanityLocalizedText.ts | 311 -- .../app/[locale]/LocaleNotFoundContent.tsx | 18 +- web/src/app/[locale]/[slug]/page.tsx | 44 +- web/src/app/[locale]/layout.tsx | 8 +- web/src/app/[locale]/not-found.tsx | 17 +- web/src/app/[locale]/page.tsx | 22 +- web/src/app/api/revalidate/route.ts | 19 +- web/src/app/sitemap.ts | 51 +- web/src/components/modules/ModuleText.tsx | 21 +- .../components/modules/ModulesRenderer.tsx | 43 +- 26 files changed, 2579 insertions(+), 874 deletions(-) delete mode 100644 web/sanity/utils/sanityLocalizedText.ts diff --git a/web/sanity/cachedSanityQuery.ts b/web/sanity/cachedSanityQuery.ts index b25ba6e..dddc77a 100644 --- a/web/sanity/cachedSanityQuery.ts +++ b/web/sanity/cachedSanityQuery.ts @@ -40,9 +40,9 @@ export const cachedSanityQuery = cache(async (query: string) => { // ── Tag constants (kept in sync with `/api/revalidate`) ───────────────────── export const SANITY_CACHE_TAGS = { - home: "home", + home: (locale: string) => `home-${locale}`, pages: "pages", - pageSlug: (slug: string) => `page-${slug}`, + pageSlug: (slug: string, locale: string) => `page-${slug}-${locale}`, sitemap: "site-pages", siteLanguageSettings: "site-language-settings", } as const; @@ -50,46 +50,52 @@ export const SANITY_CACHE_TAGS = { // ── Page-by-slug ──────────────────────────────────────────────────────────── /** - * `pageBySlugQuery` with `$slug` — one fetch per slug per request. + * `pageBySlugQuery` with `$slug` and `$locale` — one fetch per slug+locale per request. * * Combines React `cache()` (per-request) with `unstable_cache` (cross-request). - * Revalidates via tag `page-{slug}` or time-based after `SANITY_DOCUMENT_CACHE_REVALIDATE_SECONDS`. + * Revalidates via tag `page-{slug}-{locale}` or time-based after + * `SANITY_DOCUMENT_CACHE_REVALIDATE_SECONDS`. */ -export const cachedPageDocumentBySlug = cache(async (slug: string) => { - if (!isSanityConfigured) return { data: null as PageDocument | null }; - const fetchPage = unstable_cache( - async () => - client.fetch(pageBySlugQuery, { - slug, - }), - [`page-${slug}`], - { - revalidate: SANITY_DOCUMENT_CACHE_REVALIDATE_SECONDS, - tags: [`page-${slug}`, SANITY_CACHE_TAGS.pages], - }, - ); - const data = await fetchPage(); - return { data }; -}); - -// ── Home singleton ────────────────────────────────────────────────────────── - -const fetchHomeDocumentCached = unstable_cache( - async () => client.fetch(homeQuery), - ["home-document"], - { - revalidate: SANITY_DOCUMENT_CACHE_REVALIDATE_SECONDS, - tags: [SANITY_CACHE_TAGS.home], +export const cachedPageDocumentBySlug = cache( + async (slug: string, locale: string) => { + if (!isSanityConfigured) return { data: null as PageDocument | null }; + const tag = SANITY_CACHE_TAGS.pageSlug(slug, locale); + const fetchPage = unstable_cache( + async () => + client.fetch(pageBySlugQuery, { + slug, + locale, + }), + [tag], + { + revalidate: SANITY_DOCUMENT_CACHE_REVALIDATE_SECONDS, + tags: [tag, SANITY_CACHE_TAGS.pages], + }, + ); + const data = await fetchPage(); + return { data }; }, ); +// ── Home singleton (per language) ────────────────────────────────────────── + /** - * Home singleton — cross-request cached with tag `home`. - * Call from `generateMetadata` and the page: one Sanity request per request (React `cache` dedupe). + * Home singleton — one document per language. Cross-request cached with tag + * `home-{locale}`. React `cache` dedupes within a request when `generateMetadata` + * and the page component both request the same locale. */ -export const cachedHomeDocument = cache(async () => { +export const cachedHomeDocument = cache(async (locale: string) => { if (!isSanityConfigured) return { data: null as HomeDocument | null }; - const data = await fetchHomeDocumentCached(); + const tag = SANITY_CACHE_TAGS.home(locale); + const fetchHome = unstable_cache( + async () => client.fetch(homeQuery, { locale }), + [tag], + { + revalidate: SANITY_DOCUMENT_CACHE_REVALIDATE_SECONDS, + tags: [tag], + }, + ); + const data = await fetchHome(); return { data }; }); @@ -102,11 +108,7 @@ const fetchSitemapPagesCached = unstable_cache( ["sitemap-pages"], { revalidate: 3600, - tags: [ - SANITY_CACHE_TAGS.sitemap, - SANITY_CACHE_TAGS.pages, - SANITY_CACHE_TAGS.home, - ], + tags: [SANITY_CACHE_TAGS.sitemap, SANITY_CACHE_TAGS.pages], }, ); @@ -117,7 +119,7 @@ export const cachedSitemapPages = cache(async (): Promise => { return fetchSitemapPagesCached(); }); -// ── `generateStaticParams` slug list ──────────────────────────────────────── +// ── `generateStaticParams` slug list (all languages) ─────────────────────── const fetchPageSlugsCached = unstable_cache( async () => client.fetch(pageSlugsQuery), diff --git a/web/sanity/fetchSanityData.ts b/web/sanity/fetchSanityData.ts index e882480..fd2a87a 100644 --- a/web/sanity/fetchSanityData.ts +++ b/web/sanity/fetchSanityData.ts @@ -27,22 +27,25 @@ type LiveFetchOptions = { }; /** Dedupes home fetches; pass `{ stega: false }` in `generateMetadata`. */ -export const fetchHomeDocument = cache(async (options?: LiveFetchOptions) => { - if (!isSanityConfigured) return null; - const { data } = await sanityFetch({ - query: homeQuery, - ...options, - }); - return data as HomeDocument | null; -}); +export const fetchHomeDocument = cache( + async (locale: string, options?: LiveFetchOptions) => { + if (!isSanityConfigured) return null; + const { data } = await sanityFetch({ + query: homeQuery, + params: { locale }, + ...options, + }); + return data as HomeDocument | null; + }, +); /** Dedupes page fetches; pass `{ stega: false }` in `generateMetadata`. */ export const fetchPageBySlug = cache( - async (slug: string, options?: LiveFetchOptions) => { + async (slug: string, locale: string, options?: LiveFetchOptions) => { if (!isSanityConfigured) return null; const { data } = await sanityFetch({ query: pageBySlugQuery, - params: { slug }, + params: { slug, locale }, ...options, }); return data as PageDocument | null; @@ -54,10 +57,11 @@ export const fetchPageBySlug = cache( * Falls back to `"Site"` when Sanity is off or the document/title is missing. */ export const fetchSiteSettingsTitle = cache( - async (options?: LiveFetchOptions): Promise => { + async (locale: string, options?: LiveFetchOptions): Promise => { if (!isSanityConfigured) return "Site"; const { data } = await sanityFetch({ query: siteSettingsTitleQuery, + params: { locale }, ...options, }); const row = data as SiteSettingsTitleQueryResult; @@ -71,10 +75,11 @@ export const fetchSiteSettingsTitle = cache( * (React `cache`) instead of joining on every home/page GROQ query. */ export const fetchSettingsSeoFallback = cache( - async (options?: LiveFetchOptions): Promise => { + async (locale: string, options?: LiveFetchOptions): Promise => { if (!isSanityConfigured) return null; const { data } = await sanityFetch({ query: siteSettingsSeoFallbackQuery, + params: { locale }, ...options, }); return data as PageSeo; @@ -82,10 +87,11 @@ export const fetchSettingsSeoFallback = cache( ); /** `siteNav` main/footer menus with resolved links; no embedded modules. */ -export const fetchSiteNavMenus = cache(async () => { +export const fetchSiteNavMenus = cache(async (locale: string) => { if (!isSanityConfigured) return null; const { data } = await sanityFetch({ query: siteNavMenusQuery, + params: { locale }, }); return data as SiteNavMenusDocument | null; }); @@ -99,15 +105,16 @@ function draftClientForSiteLanguageSettings(token: string) { } /** - * Document id: `siteLanguageSettings` — locales, default, labels for routing + i18n resolution. + * Document id: `siteLanguageSettings` — the global locale registry. Drives routing + * (default + available locales) and is NOT itself per-language. * - * Uses `client.fetch` (not `sanityFetch`) so `generateStaticParams` can run at build time - * without `draftMode()`. The published path is wrapped in `unstable_cache` (tag + * Uses `client.fetch` (not `sanityFetch`) so `generateStaticParams` can run at build + * time without `draftMode()`. The published path is wrapped in `unstable_cache` (tag * `site-language-settings`) so cross-request reads are deduped — Sanity webhooks for * `siteLanguageSettings` invalidate the tag through `/api/revalidate`. * - * With `SANITY_API_READ_TOKEN`, uses **drafts** perspective so unpublished language changes - * show in dev. Token-mode skips `unstable_cache` (drafts must always be fresh). + * With `SANITY_API_READ_TOKEN`, uses **drafts** perspective so unpublished language + * changes show in dev. Token-mode skips `unstable_cache` (drafts must always be fresh). */ export const fetchSiteLanguageSettings = cache( async (_options?: LiveFetchOptions): Promise => { @@ -123,12 +130,15 @@ export const fetchSiteLanguageSettings = cache( }, ); -/** Document id: `errorSettings` — 404 / 500 copy from Studio. */ -export const fetchErrorSettings = cache(async (options?: LiveFetchOptions) => { - if (!isSanityConfigured) return null; - const { data } = await sanityFetch({ - query: errorSettingsQuery, - ...options, - }); - return data as ErrorSettingsDocument | null; -}); +/** Document type: `errorSettings` — 404 / 500 copy from Studio (one per language). */ +export const fetchErrorSettings = cache( + async (locale: string, options?: LiveFetchOptions) => { + if (!isSanityConfigured) return null; + const { data } = await sanityFetch({ + query: errorSettingsQuery, + params: { locale }, + ...options, + }); + return data as ErrorSettingsDocument | null; + }, +); diff --git a/web/sanity/queries/components/modules/text.ts b/web/sanity/queries/components/modules/text.ts index 6312815..8e7f37c 100644 --- a/web/sanity/queries/components/modules/text.ts +++ b/web/sanity/queries/components/modules/text.ts @@ -1,16 +1,9 @@ import { richTextMediaQuery } from "../text/richTextMedia"; -/** - * `module.text` (`objects/modules/moduleText.ts`): `title` = i18n strings; `body` = `internationalizedArrayRichTextMedia`. - */ +/** `module.text` (`objects/modules/moduleText.ts`): plain `title` + `body` (richTextMedia). */ export const moduleTextQuery = `_type == "module.text" => { title, body[]{ - _key, - _type, - language, - value[]{ - ${richTextMediaQuery} - } + ${richTextMediaQuery} } }`; diff --git a/web/sanity/queries/components/text/richTextMedia.ts b/web/sanity/queries/components/text/richTextMedia.ts index 9463ff8..e7cdd83 100644 --- a/web/sanity/queries/components/text/richTextMedia.ts +++ b/web/sanity/queries/components/text/richTextMedia.ts @@ -3,14 +3,8 @@ import { moduleCarouselInnerFields } from "../modules/carousel"; import { moduleContentRefsInnerFields } from "../modules/contentRefs"; import { moduleMediaInnerFields } from "../modules/media"; -/** - * Portable Text `value[]` for `internationalizedArrayRichTextMedia` / `richTextMedia` - * (`objects/editors/richTextMedia.ts`): blocks, `module.media`, `module.carousel`, - * `module.contentRefs`, `module.text` (nested `body` up to `depth` levels). - */ -function buildRichTextMediaQuery(depth: number): string { - if (depth <= 0) { - return ` +/** Base case: plain blocks with resolved `link` marks (no embedded modules). */ +const blockAndLinks = ` ..., _type == "block" => { ..., @@ -22,19 +16,15 @@ function buildRichTextMediaQuery(depth: number): string { } } `; - } - return ` - ..., - _type == "block" => { - ..., - markDefs[]{ - ..., - _type == "link" => { - ${linkQuery} - } - } - }, +/** + * Portable Text `value[]` for schema `richTextMedia` (`objects/editors/richTextMedia.ts`): + * blocks, `module.media`, `module.carousel`, `module.contentRefs`, and `module.text` + * with one level of nesting (`module.text` inside `module.text`); deeper nestings fall + * back to blocks + links only. The flat shape keeps Sanity Typegen happy. + */ +export const richTextMediaQuery = ` + ${blockAndLinks}, _type == "module.media" => { ${moduleMediaInnerFields} }, @@ -47,16 +37,22 @@ function buildRichTextMediaQuery(depth: number): string { _type == "module.text" => { title, body[]{ - _key, - _type, - language, - value[]{ - ${buildRichTextMediaQuery(depth - 1)} + ${blockAndLinks}, + _type == "module.media" => { + ${moduleMediaInnerFields} + }, + _type == "module.carousel" => { + ${moduleCarouselInnerFields} + }, + _type == "module.contentRefs" => { + ${moduleContentRefsInnerFields} + }, + _type == "module.text" => { + title, + body[]{ + ${blockAndLinks} + } } } } `; -} - -/** Default nesting depth for `module.text` inside rich text (each level adds one `body[]` → `value[]`). */ -export const richTextMediaQuery = buildRichTextMediaQuery(3); diff --git a/web/sanity/queries/pages/home.ts b/web/sanity/queries/pages/home.ts index db36e49..53843e2 100644 --- a/web/sanity/queries/pages/home.ts +++ b/web/sanity/queries/pages/home.ts @@ -1,9 +1,13 @@ +import { defineQuery } from "next-sanity"; + import { modulesQuery } from "../components/modules"; import { pageSeoQuery } from "../snippets/seo"; -export const homeQuery = `*[_id == "home"][0]{ +export const homeQuery = + defineQuery(`*[_type == "home" && language == $locale][0]{ _id, title, + language, ${modulesQuery}, ${pageSeoQuery} -}`; +}`); diff --git a/web/sanity/queries/pages/page.ts b/web/sanity/queries/pages/page.ts index 16ab22c..50e0c19 100644 --- a/web/sanity/queries/pages/page.ts +++ b/web/sanity/queries/pages/page.ts @@ -1,10 +1,14 @@ +import { defineQuery } from "next-sanity"; + import { modulesQuery } from "../components/modules"; import { pageSeoQuery } from "../snippets/seo"; -export const pageBySlugQuery = `*[_type == "page" && slug.current == $slug][0]{ +export const pageBySlugQuery = + defineQuery(`*[_type == "page" && slug.current == $slug && language == $locale][0]{ _id, title, slug, + language, ${modulesQuery}, ${pageSeoQuery} -}`; +}`); diff --git a/web/sanity/queries/snippets/settings.ts b/web/sanity/queries/snippets/settings.ts index 945a06f..2250ef0 100644 --- a/web/sanity/queries/snippets/settings.ts +++ b/web/sanity/queries/snippets/settings.ts @@ -6,25 +6,18 @@ import { imageQuery } from "./media"; import { pageSeoQuery } from "./seo"; /** - * `internationalizedArrayRichText` field: array of { language, value: portable text }. - * Resolves link annotations like module text bodies. + * Portable Text `body[]` projection for plain `richText` fields (no embedded + * modules) — resolves `link` annotations. */ -function internationalizedRichTextArrayField(fieldName: string): string { - return `${fieldName}[]{ - _key, - _type, - language, - value[]{ +const richTextBody = `[]{ + ..., + markDefs[]{ ..., - markDefs[]{ - ..., - _type == "link" => { - ${linkQuery} - } + _type == "link" => { + ${linkQuery} } } }`; -} /** Main menu: links plus optional `nav.languageSwitch` blocks (order preserved). */ const navMainMenuQuery = `mainMenu[]{ @@ -54,27 +47,30 @@ export const navMenusQuery = ` "footerMenu": ${navFooterMenuQuery} `; -/** Document id: `siteNav` (see studio structure). */ -export const siteNavQuery = `*[_id == "siteNav"][0]{ +/** Document type: `siteNav` (one per language; see studio structure). */ +export const siteNavQuery = `*[_type == "siteNav" && language == $locale][0]{ _id, title, + language, ${navMenusQuery}, ${modulesQuery} }`; -/** Same resolved menus as `siteNavQuery` without `modules[]` (lighter layout fetch). */ /** + * Same resolved menus as `siteNavQuery` without `modules[]` (lighter layout fetch). + * * Not wrapped in `defineQuery`: Sanity Typegen cannot resolve the `link` * projection here and mis-types `mainMenu`/`footerMenu` as `null`. The * hand-written `SiteNavMenusDocument` (sanity/types/nav.ts) is authoritative. */ -export const siteNavMenusQuery = `*[_id == "siteNav"][0]{ +export const siteNavMenusQuery = `*[_type == "siteNav" && language == $locale][0]{ _id, title, + language, ${navMenusQuery} }`; -/** Document id: `siteLanguageSettings` — drives Next routes + `internationalizedArray` codegen in Studio. */ +/** Document id: `siteLanguageSettings` — the global locale registry. */ export const siteLanguageSettingsQuery = defineQuery(`*[_id == "siteLanguageSettings"][0]{ _id, @@ -84,40 +80,43 @@ export const siteLanguageSettingsQuery = /** Minimal fetch for `app/[locale]/layout.tsx` `generateMetadata` (tab title template). */ export const siteSettingsTitleQuery = defineQuery( - `*[_id == "siteSettings"][0]{title}`, + `*[_type == "siteSettings" && language == $locale][0]{title}`, ); /** Site-wide SEO fallback for route `generateMetadata` (deduped via `fetchSettingsSeoFallback`). */ export const siteSettingsSeoFallbackQuery = - defineQuery(`*[_id == "siteSettings"][0]{ + defineQuery(`*[_type == "siteSettings" && language == $locale][0]{ "title": seo.title, "description": seo.description, "imageUrl": seo.image.asset->url }`); -/** Document id: `siteSettings`. */ -export const siteSettingsQuery = `*[_id == "siteSettings"][0]{ +/** Document type: `siteSettings` (one per language). */ +export const siteSettingsQuery = `*[_type == "siteSettings" && language == $locale][0]{ _id, title, + language, "favicon": favicon${imageQuery}, ${modulesQuery}, ${pageSeoQuery} }`; -/** Document id: `errorSettings`. */ -export const errorSettingsQuery = `*[_id == "errorSettings"][0]{ +/** Document type: `errorSettings` (one per language). */ +export const errorSettingsQuery = `*[_type == "errorSettings" && language == $locale][0]{ _id, + language, notFoundTitle, - ${internationalizedRichTextArrayField("notFoundBody")}, + "notFoundBody": notFoundBody${richTextBody}, serverErrorTitle, - ${internationalizedRichTextArrayField("serverErrorBody")}, + "serverErrorBody": serverErrorBody${richTextBody}, ${modulesQuery} }`; -/** Document id: `siteCookieBanner`. */ -export const siteCookieBannerQuery = `*[_id == "siteCookieBanner"][0]{ +/** Document type: `siteCookieBanner` (one per language). */ +export const siteCookieBannerQuery = `*[_type == "siteCookieBanner" && language == $locale][0]{ _id, title, + language, useCookieBanner, consentModal, preferencesModal, @@ -126,7 +125,7 @@ export const siteCookieBannerQuery = `*[_id == "siteCookieBanner"][0]{ /** * Single fetch for app shell: settings, nav, errors, cookie banner. - * Document ids match Studio Documents: siteSettings, siteNav, errorSettings, siteCookieBanner. + * Document types match Studio Documents: siteSettings, siteNav, errorSettings, siteCookieBanner. * * NOTE: not consumed by any current route — provided as a convenience aggregate * (and the reason `siteSettingsQuery`, `siteNavQuery`, `siteCookieBannerQuery` diff --git a/web/sanity/queries/snippets/sitemap.ts b/web/sanity/queries/snippets/sitemap.ts index 4a98d27..5159d78 100644 --- a/web/sanity/queries/snippets/sitemap.ts +++ b/web/sanity/queries/snippets/sitemap.ts @@ -1,26 +1,33 @@ import { defineQuery } from "next-sanity"; /** - * Minimal list of routable pages — for `generateStaticParams` and simple slug lists. - * (Home lives at `/`, not under `[slug]`, so it is not included here.) + * Minimal list of routable page slugs across all languages — for + * `generateStaticParams` and simple slug lists. Home is excluded (it lives + * at `/` / `/:locale`, not under `[slug]`). */ export const pageSlugsQuery = defineQuery(`*[_type == "page" && defined(slug.current)]{ - "slug": slug.current + "slug": slug.current, + language }`); /** - * All public URL entries for a sitemap: slug-based `page` documents plus site-root - * singletons (e.g. `home`). Settings singletons are omitted — they are not public routes. + * All public URL entries for a sitemap across all languages: slug-based + * `page` documents plus site-root singletons (e.g. `home`). Settings + * singletons are omitted — they are not public routes. * - * - `path`: URL path from site root (`/` for home, `/{slug}` for pages). - * Extend the filter when you add new routable singletons (see Studio `SITE_ROOT_DOCUMENT_TYPES`). + * - `path`: URL path from site root (`/` for home, `/{slug}` for pages), + * without the locale prefix (the route layer adds it per `language`). + * - `language`: the document's locale (one row per language variant). + * + * Extend the filter when you add new routable singletons. */ export const sitemapPagesQuery = defineQuery(`*[_type == "home" || (_type == "page" && defined(slug.current))]{ _id, _type, _updatedAt, + language, "slug": select(_type == "home" => null, slug.current), "path": select(_type == "home" => "/", "/" + slug.current) }`); diff --git a/web/sanity/sanity.types.gen.ts b/web/sanity/sanity.types.gen.ts index 480e418..d26185d 100644 --- a/web/sanity/sanity.types.gen.ts +++ b/web/sanity/sanity.types.gen.ts @@ -26,83 +26,6 @@ export type VideoSettings = { controls?: boolean; }; -export type SiteCookieBanner = { - _id: string; - _type: "siteCookieBanner"; - _createdAt: string; - _updatedAt: string; - _rev: string; - title?: string; - useCookieBanner?: boolean; - consentModal?: { - description?: string; - acceptAllBtn?: string; - acceptNecessaryBtn?: string; - showPreferencesBtn?: string; - }; - preferencesModal?: { - title?: string; - acceptAllBtn?: string; - acceptNecessaryBtn?: string; - savePreferencesBtn?: string; - sections?: Code; - }; -}; - -export type Code = { - _type: "code"; - language?: string; - filename?: string; - code?: string; - highlightedLines?: Array; -}; - -export type ErrorSettings = { - _id: string; - _type: "errorSettings"; - _createdAt: string; - _updatedAt: string; - _rev: string; - notFoundTitle: InternationalizedArrayString; - notFoundBody?: InternationalizedArrayRichText; - serverErrorTitle: InternationalizedArrayString; - serverErrorBody?: InternationalizedArrayRichText; -}; - -export type InternationalizedArrayRichText = Array< - { - _key: string; - } & InternationalizedArrayRichTextValue ->; - -export type InternationalizedArrayString = Array< - { - _key: string; - } & InternationalizedArrayStringValue ->; - -export type SiteNav = { - _id: string; - _type: "siteNav"; - _createdAt: string; - _updatedAt: string; - _rev: string; - title?: string; - mainMenu?: Array< - | ({ - _key: string; - } & Link) - | ({ - _key: string; - } & NavLanguageSwitch) - >; - footerMenu?: Array< - { - _key: string; - } & Link - >; -}; - export type SiteLanguageSettings = { _id: string; _type: "siteLanguageSettings"; @@ -119,59 +42,6 @@ export type SiteLanguageSettings = { defaultLanguageId: string; }; -export type SanityImageAssetReference = { - _ref: string; - _type: "reference"; - _weak?: boolean; - [internalGroqTypeReferenceTo]?: "sanity.imageAsset"; -}; - -export type SiteSettings = { - _id: string; - _type: "siteSettings"; - _createdAt: string; - _updatedAt: string; - _rev: string; - title: string; - favicon?: { - asset?: SanityImageAssetReference; - media?: unknown; - hotspot?: SanityImageHotspot; - crop?: SanityImageCrop; - _type: "image"; - }; - seo?: SeoFallback; -}; - -export type SeoFallback = { - _type: "seo.fallback"; - title?: string; - description?: string; - image?: { - asset?: SanityImageAssetReference; - media?: unknown; - hotspot?: SanityImageHotspot; - crop?: SanityImageCrop; - _type: "image"; - }; -}; - -export type SanityImageCrop = { - _type: "sanity.imageCrop"; - top: number; - bottom: number; - left: number; - right: number; -}; - -export type SanityImageHotspot = { - _type: "sanity.imageHotspot"; - x: number; - y: number; - height: number; - width: number; -}; - export type RichTextMedia = Array< | { children?: Array<{ @@ -220,8 +90,8 @@ export type RichText = Array<{ export type ModuleText = { _type: "module.text"; - title: InternationalizedArrayString; - body?: InternationalizedArrayRichTextMedia; + title: string; + body?: RichTextMedia; }; export type HomeReference = { @@ -240,7 +110,7 @@ export type PageReference = { export type ModuleContentRefs = { _type: "module.contentRefs"; - heading?: InternationalizedArrayString; + heading?: string; allowMultiple?: boolean; reference?: HomeReference | PageReference; references?: ArrayOf; @@ -253,9 +123,16 @@ export type ModuleMedia = { videoContent?: MediaVideo; }; +export type SanityImageAssetReference = { + _ref: string; + _type: "reference"; + _weak?: boolean; + [internalGroqTypeReferenceTo]?: "sanity.imageAsset"; +}; + export type ModuleCarousel = { _type: "module.carousel"; - heading?: InternationalizedArrayString; + heading?: string; imagesOnly?: boolean; slides?: Array<{ asset?: SanityImageAssetReference; @@ -313,13 +190,204 @@ export type Link = { func?: LinkFunctions; }; +export type SeoFallback = { + _type: "seo.fallback"; + title?: string; + description?: string; + image?: { + asset?: SanityImageAssetReference; + media?: unknown; + hotspot?: SanityImageHotspot; + crop?: SanityImageCrop; + _type: "image"; + }; +}; + +export type SeoPage = { + _type: "seo.page"; + title?: string; + description?: string; + image?: { + asset?: SanityImageAssetReference; + media?: unknown; + hotspot?: SanityImageHotspot; + crop?: SanityImageCrop; + _type: "image"; + }; +}; + +export type LinkFunctions = { + _type: "linkFunctions"; + key: "scroll-to" | "open-modal"; + params?: string; +}; + +export type TranslationMetadata = { + _id: string; + _type: "translation.metadata"; + _createdAt: string; + _updatedAt: string; + _rev: string; + translations?: InternationalizedArrayReference; + schemaTypes?: Array; +}; + +export type InternationalizedArrayReference = Array< + { + _key: string; + } & InternationalizedArrayReferenceValue +>; + +export type ErrorSettingsReference = { + _ref: string; + _type: "reference"; + _weak?: boolean; + [internalGroqTypeReferenceTo]?: "errorSettings"; +}; + +export type SiteNavReference = { + _ref: string; + _type: "reference"; + _weak?: boolean; + [internalGroqTypeReferenceTo]?: "siteNav"; +}; + +export type SiteSettingsReference = { + _ref: string; + _type: "reference"; + _weak?: boolean; + [internalGroqTypeReferenceTo]?: "siteSettings"; +}; + +export type SiteCookieBannerReference = { + _ref: string; + _type: "reference"; + _weak?: boolean; + [internalGroqTypeReferenceTo]?: "siteCookieBanner"; +}; + +export type InternationalizedArrayReferenceValue = { + _type: "internationalizedArrayReferenceValue"; + value?: + | HomeReference + | PageReference + | ErrorSettingsReference + | SiteNavReference + | SiteSettingsReference + | SiteCookieBannerReference; + language: string; +}; + +export type SiteCookieBanner = { + _id: string; + _type: "siteCookieBanner"; + _createdAt: string; + _updatedAt: string; + _rev: string; + language?: string; + title?: string; + useCookieBanner?: boolean; + consentModal?: { + description?: string; + acceptAllBtn?: string; + acceptNecessaryBtn?: string; + showPreferencesBtn?: string; + }; + preferencesModal?: { + title?: string; + acceptAllBtn?: string; + acceptNecessaryBtn?: string; + savePreferencesBtn?: string; + sections?: Code; + }; +}; + +export type Code = { + _type: "code"; + language?: string; + filename?: string; + code?: string; + highlightedLines?: Array; +}; + +export type SiteSettings = { + _id: string; + _type: "siteSettings"; + _createdAt: string; + _updatedAt: string; + _rev: string; + language?: string; + title: string; + favicon?: { + asset?: SanityImageAssetReference; + media?: unknown; + hotspot?: SanityImageHotspot; + crop?: SanityImageCrop; + _type: "image"; + }; + seo?: SeoFallback; +}; + +export type SanityImageCrop = { + _type: "sanity.imageCrop"; + top: number; + bottom: number; + left: number; + right: number; +}; + +export type SanityImageHotspot = { + _type: "sanity.imageHotspot"; + x: number; + y: number; + height: number; + width: number; +}; + +export type SiteNav = { + _id: string; + _type: "siteNav"; + _createdAt: string; + _updatedAt: string; + _rev: string; + language?: string; + title?: string; + mainMenu?: Array< + | ({ + _key: string; + } & Link) + | ({ + _key: string; + } & NavLanguageSwitch) + >; + footerMenu?: Array< + { + _key: string; + } & Link + >; +}; + +export type ErrorSettings = { + _id: string; + _type: "errorSettings"; + _createdAt: string; + _updatedAt: string; + _rev: string; + language?: string; + notFoundTitle: string; + notFoundBody?: RichText; + serverErrorTitle: string; + serverErrorBody?: RichText; +}; + export type Page = { _id: string; _type: "page"; _createdAt: string; _updatedAt: string; _rev: string; - title: InternationalizedArrayString; + language?: string; + title: string; slug: Slug; modules?: Array< | ({ @@ -338,19 +406,6 @@ export type Page = { seo?: SeoPage; }; -export type SeoPage = { - _type: "seo.page"; - title?: string; - description?: string; - image?: { - asset?: SanityImageAssetReference; - media?: unknown; - hotspot?: SanityImageHotspot; - crop?: SanityImageCrop; - _type: "image"; - }; -}; - export type Slug = { _type: "slug"; current: string; @@ -363,7 +418,8 @@ export type Home = { _createdAt: string; _updatedAt: string; _rev: string; - title: InternationalizedArrayString; + language?: string; + title: string; modules?: Array< | ({ _key: string; @@ -381,36 +437,6 @@ export type Home = { seo?: SeoPage; }; -export type InternationalizedArrayRichTextMedia = Array< - { - _key: string; - } & InternationalizedArrayRichTextMediaValue ->; - -export type LinkFunctions = { - _type: "linkFunctions"; - key: "scroll-to" | "open-modal"; - params?: string; -}; - -export type InternationalizedArrayRichTextMediaValue = { - _type: "internationalizedArrayRichTextMediaValue"; - value?: RichTextMedia; - language: string; -}; - -export type InternationalizedArrayRichTextValue = { - _type: "internationalizedArrayRichTextValue"; - value?: RichText; - language: string; -}; - -export type InternationalizedArrayStringValue = { - _type: "internationalizedArrayStringValue"; - value?: string; - language: string; -}; - export type MuxVideoAssetReference = { _ref: string; _type: "reference"; @@ -622,18 +648,7 @@ export type Geopoint = { export type AllSanitySchemaTypes = | VideoSettings - | SiteCookieBanner - | Code - | ErrorSettings - | InternationalizedArrayRichText - | InternationalizedArrayString - | SiteNav | SiteLanguageSettings - | SanityImageAssetReference - | SiteSettings - | SeoFallback - | SanityImageCrop - | SanityImageHotspot | RichTextMedia | RichText | ModuleText @@ -641,20 +656,32 @@ export type AllSanitySchemaTypes = | PageReference | ModuleContentRefs | ModuleMedia + | SanityImageAssetReference | ModuleCarousel | MediaVideo | MediaImage | NavLanguageSwitch | Link - | Page + | SeoFallback | SeoPage + | LinkFunctions + | TranslationMetadata + | InternationalizedArrayReference + | ErrorSettingsReference + | SiteNavReference + | SiteSettingsReference + | SiteCookieBannerReference + | InternationalizedArrayReferenceValue + | SiteCookieBanner + | Code + | SiteSettings + | SanityImageCrop + | SanityImageHotspot + | SiteNav + | ErrorSettings + | Page | Slug | Home - | InternationalizedArrayRichTextMedia - | LinkFunctions - | InternationalizedArrayRichTextMediaValue - | InternationalizedArrayRichTextValue - | InternationalizedArrayStringValue | MuxVideoAssetReference | MuxVideo | MuxVideoAsset @@ -673,6 +700,2073 @@ export type AllSanitySchemaTypes = | SanityImageAsset | Geopoint; +// Source: sanity/queries/pages/home.ts +// Variable: homeQuery +// Query: *[_type == "home" && language == $locale][0]{ _id, title, language, modules[]{ _key, _type, _type == "module.text" => { title, body[]{ ..., _type == "block" => { ..., markDefs[]{ ..., _type == "link" => { ..., type == "internal" => { "linkType": "linkInternal", "title": coalesce(title, reference->title), "route": select( reference->_type == "home" => "page", reference->_type == "page" => "slug", "page" ), "slug": reference->slug.current, "resolvedReference": reference->{ _id, _type, title, "slug": slug.current } }, type == "external" => { ..., "linkType": "linkExternal", "href": url, "title": coalesce(title, url), blank }, type == "function" => { ..., "linkType": "linkFunction", "func": func { key, params } } } } } , _type == "module.media" => { type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) }, _type == "module.carousel" => { heading, imagesOnly, "slides": slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "slidesMedia": slidesMedia[]{ _key, _type, type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) }, "resolvedSlides": select( imagesOnly == true => slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, slidesMedia[]{ _key, _type, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) } ) }, _type == "module.contentRefs" => { heading, allowMultiple, "reference": reference->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) }, "references": references[]->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) } }, _type == "module.text" => { title, body[]{ ..., _type == "block" => { ..., markDefs[]{ ..., _type == "link" => { ..., type == "internal" => { "linkType": "linkInternal", "title": coalesce(title, reference->title), "route": select( reference->_type == "home" => "page", reference->_type == "page" => "slug", "page" ), "slug": reference->slug.current, "resolvedReference": reference->{ _id, _type, title, "slug": slug.current } }, type == "external" => { ..., "linkType": "linkExternal", "href": url, "title": coalesce(title, url), blank }, type == "function" => { ..., "linkType": "linkFunction", "func": func { key, params } } } } } , _type == "module.media" => { type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) }, _type == "module.carousel" => { heading, imagesOnly, "slides": slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "slidesMedia": slidesMedia[]{ _key, _type, type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) }, "resolvedSlides": select( imagesOnly == true => slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, slidesMedia[]{ _key, _type, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) } ) }, _type == "module.contentRefs" => { heading, allowMultiple, "reference": reference->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) }, "references": references[]->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) } }, _type == "module.text" => { title, body[]{ ..., _type == "block" => { ..., markDefs[]{ ..., _type == "link" => { ..., type == "internal" => { "linkType": "linkInternal", "title": coalesce(title, reference->title), "route": select( reference->_type == "home" => "page", reference->_type == "page" => "slug", "page" ), "slug": reference->slug.current, "resolvedReference": reference->{ _id, _type, title, "slug": slug.current } }, type == "external" => { ..., "linkType": "linkExternal", "href": url, "title": coalesce(title, url), blank }, type == "function" => { ..., "linkType": "linkFunction", "func": func { key, params } } } } } } } } } }}, _type == "module.media" => { type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } )}, _type == "module.carousel" => { heading, imagesOnly, "slides": slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "slidesMedia": slidesMedia[]{ _key, _type, type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) }, "resolvedSlides": select( imagesOnly == true => slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, slidesMedia[]{ _key, _type, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) } )}, _type == "module.contentRefs" => { heading, allowMultiple, "reference": reference->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) }, "references": references[]->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) }}}, seo { title, description, "imageUrl": image.asset->url}} +export type HomeQueryResult = { + _id: string; + title: string; + language: string | null; + modules: Array< + | { + _key: string; + _type: "module.carousel"; + heading: string | null; + imagesOnly: boolean | null; + slides: Array<{ + _key: string; + _type: "image"; + media: { + kind: "image"; + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + }; + }> | null; + slidesMedia: Array<{ + _key: string; + _type: "module.media"; + type: "image" | "video"; + imageContent: { + caption: string | null; + media: { + kind: "image"; + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + } | null; + videoContent: { + caption: string | null; + videoSettings: VideoSettings | null; + media: + | { + kind: "image"; + crop: null; + hotspot: null; + alt: null; + asset: { + _id: string; + url: null; + metadata: null; + } | null; + } + | { + kind: "video"; + playbackId: string | null; + duration: number | null; + asset: { + playbackId: string | null; + data: MuxAssetData | null; + } | null; + } + | null; + poster: { + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + } | null; + resolvedMedia: + | { + kind: "image"; + caption: string | null; + media: { + kind: "image"; + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + } + | { + kind: "video"; + caption: string | null; + videoSettings: VideoSettings | null; + media: + | { + kind: "image"; + crop: null; + hotspot: null; + alt: null; + asset: { + _id: string; + url: null; + metadata: null; + } | null; + } + | { + kind: "video"; + playbackId: string | null; + duration: number | null; + asset: { + playbackId: string | null; + data: MuxAssetData | null; + } | null; + } + | null; + poster: { + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + }; + }> | null; + resolvedSlides: + | Array<{ + _key: string; + _type: "image"; + media: { + kind: "image"; + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + }; + }> + | Array<{ + _key: string; + _type: "module.media"; + resolvedMedia: + | { + kind: "image"; + caption: string | null; + media: { + kind: "image"; + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + } + | { + kind: "video"; + caption: string | null; + videoSettings: VideoSettings | null; + media: + | { + kind: "image"; + crop: null; + hotspot: null; + alt: null; + asset: { + _id: string; + url: null; + metadata: null; + } | null; + } + | { + kind: "video"; + playbackId: string | null; + duration: number | null; + asset: { + playbackId: string | null; + data: MuxAssetData | null; + } | null; + } + | null; + poster: { + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + }; + }> + | null; + } + | { + _key: string; + _type: "module.contentRefs"; + heading: string | null; + allowMultiple: boolean | null; + reference: + | { + _id: string; + _type: "home"; + title: string; + slug: null; + route: "index"; + } + | { + _id: string; + _type: "page"; + title: string; + slug: string; + route: "slug"; + } + | null; + references: Array< + | { + _id: string; + _type: "home"; + title: string; + slug: null; + route: "index"; + } + | { + _id: string; + _type: "page"; + title: string; + slug: string; + route: "slug"; + } + > | null; + } + | { + _key: string; + _type: "module.media"; + type: "image" | "video"; + imageContent: { + caption: string | null; + media: { + kind: "image"; + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + } | null; + videoContent: { + caption: string | null; + videoSettings: VideoSettings | null; + media: + | { + kind: "image"; + crop: null; + hotspot: null; + alt: null; + asset: { + _id: string; + url: null; + metadata: null; + } | null; + } + | { + kind: "video"; + playbackId: string | null; + duration: number | null; + asset: { + playbackId: string | null; + data: MuxAssetData | null; + } | null; + } + | null; + poster: { + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + } | null; + resolvedMedia: + | { + kind: "image"; + caption: string | null; + media: { + kind: "image"; + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + } + | { + kind: "video"; + caption: string | null; + videoSettings: VideoSettings | null; + media: + | { + kind: "image"; + crop: null; + hotspot: null; + alt: null; + asset: { + _id: string; + url: null; + metadata: null; + } | null; + } + | { + kind: "video"; + playbackId: string | null; + duration: number | null; + asset: { + playbackId: string | null; + data: MuxAssetData | null; + } | null; + } + | null; + poster: { + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + }; + } + | { + _key: string; + _type: "module.text"; + title: string; + body: Array< + | { + children?: Array<{ + marks?: Array; + text?: string; + _type: "span"; + _key: string; + }>; + style?: "h2" | "h3" | "h4" | "normal"; + listItem?: "bullet" | "number"; + markDefs: Array< + | { + _key: string; + _type: "link"; + type: "external" | "function" | "internal"; + title: string | null; + reference?: HomeReference | PageReference; + url?: string; + blank?: boolean; + func?: LinkFunctions; + linkType: "linkInternal"; + route: "page" | "slug"; + slug: string | null; + resolvedReference: + | { + _id: string; + _type: "home"; + title: string; + slug: null; + } + | { + _id: string; + _type: "page"; + title: string; + slug: string; + } + | null; + } + | { + _key: string; + _type: "link"; + type: "external" | "function" | "internal"; + title?: string; + reference?: HomeReference | PageReference; + url?: string; + blank?: boolean; + func?: LinkFunctions; + } + | { + _key: string; + _type: "link"; + type: "external" | "function" | "internal"; + title?: string; + reference?: HomeReference | PageReference; + url?: string; + blank?: boolean; + func: { + key: "open-modal" | "scroll-to"; + params: string | null; + } | null; + linkType: "linkFunction"; + route: "page" | "slug"; + slug: string | null; + resolvedReference: + | { + _id: string; + _type: "home"; + title: string; + slug: null; + } + | { + _id: string; + _type: "page"; + title: string; + slug: string; + } + | null; + href: string | null; + } + | { + _key: string; + _type: "link"; + type: "external" | "function" | "internal"; + title?: string; + reference?: HomeReference | PageReference; + url?: string; + blank?: boolean; + func: { + key: "open-modal" | "scroll-to"; + params: string | null; + } | null; + linkType: "linkFunction"; + href: string | null; + } + | { + _key: string; + _type: "link"; + type: "external" | "function" | "internal"; + title?: string; + reference?: HomeReference | PageReference; + url?: string; + blank?: boolean; + func: { + key: "open-modal" | "scroll-to"; + params: string | null; + } | null; + linkType: "linkFunction"; + route: "page" | "slug"; + slug: string | null; + resolvedReference: + | { + _id: string; + _type: "home"; + title: string; + slug: null; + } + | { + _id: string; + _type: "page"; + title: string; + slug: string; + } + | null; + } + | { + _key: string; + _type: "link"; + type: "external" | "function" | "internal"; + title?: string; + reference?: HomeReference | PageReference; + url?: string; + blank?: boolean; + func: { + key: "open-modal" | "scroll-to"; + params: string | null; + } | null; + linkType: "linkFunction"; + } + | { + _key: string; + _type: "link"; + type: "external" | "function" | "internal"; + title: string | null; + reference?: HomeReference | PageReference; + url?: string; + blank: boolean | null; + func?: LinkFunctions; + linkType: "linkExternal"; + route: "page" | "slug"; + slug: string | null; + resolvedReference: + | { + _id: string; + _type: "home"; + title: string; + slug: null; + } + | { + _id: string; + _type: "page"; + title: string; + slug: string; + } + | null; + href: string | null; + } + | { + _key: string; + _type: "link"; + type: "external" | "function" | "internal"; + title: string | null; + reference?: HomeReference | PageReference; + url?: string; + blank: boolean | null; + func?: LinkFunctions; + linkType: "linkExternal"; + href: string | null; + } + > | null; + level?: number; + _type: "block"; + _key: string; + } + | { + _key: string; + _type: "module.carousel"; + heading: string | null; + imagesOnly: boolean | null; + slides: Array<{ + _key: string; + _type: "image"; + media: { + kind: "image"; + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + }; + }> | null; + slidesMedia: Array<{ + _key: string; + _type: "module.media"; + type: "image" | "video"; + imageContent: { + caption: string | null; + media: { + kind: "image"; + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + } | null; + videoContent: { + caption: string | null; + videoSettings: VideoSettings | null; + media: + | { + kind: "image"; + crop: null; + hotspot: null; + alt: null; + asset: { + _id: string; + url: null; + metadata: null; + } | null; + } + | { + kind: "video"; + playbackId: string | null; + duration: number | null; + asset: { + playbackId: string | null; + data: MuxAssetData | null; + } | null; + } + | null; + poster: { + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + } | null; + resolvedMedia: + | { + kind: "image"; + caption: string | null; + media: { + kind: "image"; + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + } + | { + kind: "video"; + caption: string | null; + videoSettings: VideoSettings | null; + media: + | { + kind: "image"; + crop: null; + hotspot: null; + alt: null; + asset: { + _id: string; + url: null; + metadata: null; + } | null; + } + | { + kind: "video"; + playbackId: string | null; + duration: number | null; + asset: { + playbackId: string | null; + data: MuxAssetData | null; + } | null; + } + | null; + poster: { + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + }; + }> | null; + resolvedSlides: + | Array<{ + _key: string; + _type: "image"; + media: { + kind: "image"; + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + }; + }> + | Array<{ + _key: string; + _type: "module.media"; + resolvedMedia: + | { + kind: "image"; + caption: string | null; + media: { + kind: "image"; + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + } + | { + kind: "video"; + caption: string | null; + videoSettings: VideoSettings | null; + media: + | { + kind: "image"; + crop: null; + hotspot: null; + alt: null; + asset: { + _id: string; + url: null; + metadata: null; + } | null; + } + | { + kind: "video"; + playbackId: string | null; + duration: number | null; + asset: { + playbackId: string | null; + data: MuxAssetData | null; + } | null; + } + | null; + poster: { + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + }; + }> + | null; + } + | { + _key: string; + _type: "module.media"; + type: "image" | "video"; + imageContent: { + caption: string | null; + media: { + kind: "image"; + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + } | null; + videoContent: { + caption: string | null; + videoSettings: VideoSettings | null; + media: + | { + kind: "image"; + crop: null; + hotspot: null; + alt: null; + asset: { + _id: string; + url: null; + metadata: null; + } | null; + } + | { + kind: "video"; + playbackId: string | null; + duration: number | null; + asset: { + playbackId: string | null; + data: MuxAssetData | null; + } | null; + } + | null; + poster: { + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + } | null; + resolvedMedia: + | { + kind: "image"; + caption: string | null; + media: { + kind: "image"; + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + } + | { + kind: "video"; + caption: string | null; + videoSettings: VideoSettings | null; + media: + | { + kind: "image"; + crop: null; + hotspot: null; + alt: null; + asset: { + _id: string; + url: null; + metadata: null; + } | null; + } + | { + kind: "video"; + playbackId: string | null; + duration: number | null; + asset: { + playbackId: string | null; + data: MuxAssetData | null; + } | null; + } + | null; + poster: { + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + }; + } + > | null; + } + > | null; + seo: { + title: string | null; + description: string | null; + imageUrl: string | null; + } | null; +} | null; + +// Source: sanity/queries/pages/page.ts +// Variable: pageBySlugQuery +// Query: *[_type == "page" && slug.current == $slug && language == $locale][0]{ _id, title, slug, language, modules[]{ _key, _type, _type == "module.text" => { title, body[]{ ..., _type == "block" => { ..., markDefs[]{ ..., _type == "link" => { ..., type == "internal" => { "linkType": "linkInternal", "title": coalesce(title, reference->title), "route": select( reference->_type == "home" => "page", reference->_type == "page" => "slug", "page" ), "slug": reference->slug.current, "resolvedReference": reference->{ _id, _type, title, "slug": slug.current } }, type == "external" => { ..., "linkType": "linkExternal", "href": url, "title": coalesce(title, url), blank }, type == "function" => { ..., "linkType": "linkFunction", "func": func { key, params } } } } } , _type == "module.media" => { type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) }, _type == "module.carousel" => { heading, imagesOnly, "slides": slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "slidesMedia": slidesMedia[]{ _key, _type, type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) }, "resolvedSlides": select( imagesOnly == true => slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, slidesMedia[]{ _key, _type, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) } ) }, _type == "module.contentRefs" => { heading, allowMultiple, "reference": reference->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) }, "references": references[]->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) } }, _type == "module.text" => { title, body[]{ ..., _type == "block" => { ..., markDefs[]{ ..., _type == "link" => { ..., type == "internal" => { "linkType": "linkInternal", "title": coalesce(title, reference->title), "route": select( reference->_type == "home" => "page", reference->_type == "page" => "slug", "page" ), "slug": reference->slug.current, "resolvedReference": reference->{ _id, _type, title, "slug": slug.current } }, type == "external" => { ..., "linkType": "linkExternal", "href": url, "title": coalesce(title, url), blank }, type == "function" => { ..., "linkType": "linkFunction", "func": func { key, params } } } } } , _type == "module.media" => { type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) }, _type == "module.carousel" => { heading, imagesOnly, "slides": slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "slidesMedia": slidesMedia[]{ _key, _type, type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) }, "resolvedSlides": select( imagesOnly == true => slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, slidesMedia[]{ _key, _type, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) } ) }, _type == "module.contentRefs" => { heading, allowMultiple, "reference": reference->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) }, "references": references[]->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) } }, _type == "module.text" => { title, body[]{ ..., _type == "block" => { ..., markDefs[]{ ..., _type == "link" => { ..., type == "internal" => { "linkType": "linkInternal", "title": coalesce(title, reference->title), "route": select( reference->_type == "home" => "page", reference->_type == "page" => "slug", "page" ), "slug": reference->slug.current, "resolvedReference": reference->{ _id, _type, title, "slug": slug.current } }, type == "external" => { ..., "linkType": "linkExternal", "href": url, "title": coalesce(title, url), blank }, type == "function" => { ..., "linkType": "linkFunction", "func": func { key, params } } } } } } } } } }}, _type == "module.media" => { type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } )}, _type == "module.carousel" => { heading, imagesOnly, "slides": slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "slidesMedia": slidesMedia[]{ _key, _type, type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) }, "resolvedSlides": select( imagesOnly == true => slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, slidesMedia[]{ _key, _type, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) } )}, _type == "module.contentRefs" => { heading, allowMultiple, "reference": reference->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) }, "references": references[]->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) }}}, seo { title, description, "imageUrl": image.asset->url}} +export type PageBySlugQueryResult = { + _id: string; + title: string; + slug: Slug; + language: string | null; + modules: Array< + | { + _key: string; + _type: "module.carousel"; + heading: string | null; + imagesOnly: boolean | null; + slides: Array<{ + _key: string; + _type: "image"; + media: { + kind: "image"; + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + }; + }> | null; + slidesMedia: Array<{ + _key: string; + _type: "module.media"; + type: "image" | "video"; + imageContent: { + caption: string | null; + media: { + kind: "image"; + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + } | null; + videoContent: { + caption: string | null; + videoSettings: VideoSettings | null; + media: + | { + kind: "image"; + crop: null; + hotspot: null; + alt: null; + asset: { + _id: string; + url: null; + metadata: null; + } | null; + } + | { + kind: "video"; + playbackId: string | null; + duration: number | null; + asset: { + playbackId: string | null; + data: MuxAssetData | null; + } | null; + } + | null; + poster: { + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + } | null; + resolvedMedia: + | { + kind: "image"; + caption: string | null; + media: { + kind: "image"; + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + } + | { + kind: "video"; + caption: string | null; + videoSettings: VideoSettings | null; + media: + | { + kind: "image"; + crop: null; + hotspot: null; + alt: null; + asset: { + _id: string; + url: null; + metadata: null; + } | null; + } + | { + kind: "video"; + playbackId: string | null; + duration: number | null; + asset: { + playbackId: string | null; + data: MuxAssetData | null; + } | null; + } + | null; + poster: { + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + }; + }> | null; + resolvedSlides: + | Array<{ + _key: string; + _type: "image"; + media: { + kind: "image"; + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + }; + }> + | Array<{ + _key: string; + _type: "module.media"; + resolvedMedia: + | { + kind: "image"; + caption: string | null; + media: { + kind: "image"; + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + } + | { + kind: "video"; + caption: string | null; + videoSettings: VideoSettings | null; + media: + | { + kind: "image"; + crop: null; + hotspot: null; + alt: null; + asset: { + _id: string; + url: null; + metadata: null; + } | null; + } + | { + kind: "video"; + playbackId: string | null; + duration: number | null; + asset: { + playbackId: string | null; + data: MuxAssetData | null; + } | null; + } + | null; + poster: { + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + }; + }> + | null; + } + | { + _key: string; + _type: "module.contentRefs"; + heading: string | null; + allowMultiple: boolean | null; + reference: + | { + _id: string; + _type: "home"; + title: string; + slug: null; + route: "index"; + } + | { + _id: string; + _type: "page"; + title: string; + slug: string; + route: "slug"; + } + | null; + references: Array< + | { + _id: string; + _type: "home"; + title: string; + slug: null; + route: "index"; + } + | { + _id: string; + _type: "page"; + title: string; + slug: string; + route: "slug"; + } + > | null; + } + | { + _key: string; + _type: "module.media"; + type: "image" | "video"; + imageContent: { + caption: string | null; + media: { + kind: "image"; + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + } | null; + videoContent: { + caption: string | null; + videoSettings: VideoSettings | null; + media: + | { + kind: "image"; + crop: null; + hotspot: null; + alt: null; + asset: { + _id: string; + url: null; + metadata: null; + } | null; + } + | { + kind: "video"; + playbackId: string | null; + duration: number | null; + asset: { + playbackId: string | null; + data: MuxAssetData | null; + } | null; + } + | null; + poster: { + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + } | null; + resolvedMedia: + | { + kind: "image"; + caption: string | null; + media: { + kind: "image"; + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + } + | { + kind: "video"; + caption: string | null; + videoSettings: VideoSettings | null; + media: + | { + kind: "image"; + crop: null; + hotspot: null; + alt: null; + asset: { + _id: string; + url: null; + metadata: null; + } | null; + } + | { + kind: "video"; + playbackId: string | null; + duration: number | null; + asset: { + playbackId: string | null; + data: MuxAssetData | null; + } | null; + } + | null; + poster: { + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + }; + } + | { + _key: string; + _type: "module.text"; + title: string; + body: Array< + | { + children?: Array<{ + marks?: Array; + text?: string; + _type: "span"; + _key: string; + }>; + style?: "h2" | "h3" | "h4" | "normal"; + listItem?: "bullet" | "number"; + markDefs: Array< + | { + _key: string; + _type: "link"; + type: "external" | "function" | "internal"; + title: string | null; + reference?: HomeReference | PageReference; + url?: string; + blank?: boolean; + func?: LinkFunctions; + linkType: "linkInternal"; + route: "page" | "slug"; + slug: string | null; + resolvedReference: + | { + _id: string; + _type: "home"; + title: string; + slug: null; + } + | { + _id: string; + _type: "page"; + title: string; + slug: string; + } + | null; + } + | { + _key: string; + _type: "link"; + type: "external" | "function" | "internal"; + title?: string; + reference?: HomeReference | PageReference; + url?: string; + blank?: boolean; + func?: LinkFunctions; + } + | { + _key: string; + _type: "link"; + type: "external" | "function" | "internal"; + title?: string; + reference?: HomeReference | PageReference; + url?: string; + blank?: boolean; + func: { + key: "open-modal" | "scroll-to"; + params: string | null; + } | null; + linkType: "linkFunction"; + route: "page" | "slug"; + slug: string | null; + resolvedReference: + | { + _id: string; + _type: "home"; + title: string; + slug: null; + } + | { + _id: string; + _type: "page"; + title: string; + slug: string; + } + | null; + href: string | null; + } + | { + _key: string; + _type: "link"; + type: "external" | "function" | "internal"; + title?: string; + reference?: HomeReference | PageReference; + url?: string; + blank?: boolean; + func: { + key: "open-modal" | "scroll-to"; + params: string | null; + } | null; + linkType: "linkFunction"; + href: string | null; + } + | { + _key: string; + _type: "link"; + type: "external" | "function" | "internal"; + title?: string; + reference?: HomeReference | PageReference; + url?: string; + blank?: boolean; + func: { + key: "open-modal" | "scroll-to"; + params: string | null; + } | null; + linkType: "linkFunction"; + route: "page" | "slug"; + slug: string | null; + resolvedReference: + | { + _id: string; + _type: "home"; + title: string; + slug: null; + } + | { + _id: string; + _type: "page"; + title: string; + slug: string; + } + | null; + } + | { + _key: string; + _type: "link"; + type: "external" | "function" | "internal"; + title?: string; + reference?: HomeReference | PageReference; + url?: string; + blank?: boolean; + func: { + key: "open-modal" | "scroll-to"; + params: string | null; + } | null; + linkType: "linkFunction"; + } + | { + _key: string; + _type: "link"; + type: "external" | "function" | "internal"; + title: string | null; + reference?: HomeReference | PageReference; + url?: string; + blank: boolean | null; + func?: LinkFunctions; + linkType: "linkExternal"; + route: "page" | "slug"; + slug: string | null; + resolvedReference: + | { + _id: string; + _type: "home"; + title: string; + slug: null; + } + | { + _id: string; + _type: "page"; + title: string; + slug: string; + } + | null; + href: string | null; + } + | { + _key: string; + _type: "link"; + type: "external" | "function" | "internal"; + title: string | null; + reference?: HomeReference | PageReference; + url?: string; + blank: boolean | null; + func?: LinkFunctions; + linkType: "linkExternal"; + href: string | null; + } + > | null; + level?: number; + _type: "block"; + _key: string; + } + | { + _key: string; + _type: "module.carousel"; + heading: string | null; + imagesOnly: boolean | null; + slides: Array<{ + _key: string; + _type: "image"; + media: { + kind: "image"; + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + }; + }> | null; + slidesMedia: Array<{ + _key: string; + _type: "module.media"; + type: "image" | "video"; + imageContent: { + caption: string | null; + media: { + kind: "image"; + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + } | null; + videoContent: { + caption: string | null; + videoSettings: VideoSettings | null; + media: + | { + kind: "image"; + crop: null; + hotspot: null; + alt: null; + asset: { + _id: string; + url: null; + metadata: null; + } | null; + } + | { + kind: "video"; + playbackId: string | null; + duration: number | null; + asset: { + playbackId: string | null; + data: MuxAssetData | null; + } | null; + } + | null; + poster: { + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + } | null; + resolvedMedia: + | { + kind: "image"; + caption: string | null; + media: { + kind: "image"; + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + } + | { + kind: "video"; + caption: string | null; + videoSettings: VideoSettings | null; + media: + | { + kind: "image"; + crop: null; + hotspot: null; + alt: null; + asset: { + _id: string; + url: null; + metadata: null; + } | null; + } + | { + kind: "video"; + playbackId: string | null; + duration: number | null; + asset: { + playbackId: string | null; + data: MuxAssetData | null; + } | null; + } + | null; + poster: { + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + }; + }> | null; + resolvedSlides: + | Array<{ + _key: string; + _type: "image"; + media: { + kind: "image"; + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + }; + }> + | Array<{ + _key: string; + _type: "module.media"; + resolvedMedia: + | { + kind: "image"; + caption: string | null; + media: { + kind: "image"; + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + } + | { + kind: "video"; + caption: string | null; + videoSettings: VideoSettings | null; + media: + | { + kind: "image"; + crop: null; + hotspot: null; + alt: null; + asset: { + _id: string; + url: null; + metadata: null; + } | null; + } + | { + kind: "video"; + playbackId: string | null; + duration: number | null; + asset: { + playbackId: string | null; + data: MuxAssetData | null; + } | null; + } + | null; + poster: { + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + }; + }> + | null; + } + | { + _key: string; + _type: "module.media"; + type: "image" | "video"; + imageContent: { + caption: string | null; + media: { + kind: "image"; + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + } | null; + videoContent: { + caption: string | null; + videoSettings: VideoSettings | null; + media: + | { + kind: "image"; + crop: null; + hotspot: null; + alt: null; + asset: { + _id: string; + url: null; + metadata: null; + } | null; + } + | { + kind: "video"; + playbackId: string | null; + duration: number | null; + asset: { + playbackId: string | null; + data: MuxAssetData | null; + } | null; + } + | null; + poster: { + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + } | null; + resolvedMedia: + | { + kind: "image"; + caption: string | null; + media: { + kind: "image"; + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + } + | { + kind: "video"; + caption: string | null; + videoSettings: VideoSettings | null; + media: + | { + kind: "image"; + crop: null; + hotspot: null; + alt: null; + asset: { + _id: string; + url: null; + metadata: null; + } | null; + } + | { + kind: "video"; + playbackId: string | null; + duration: number | null; + asset: { + playbackId: string | null; + data: MuxAssetData | null; + } | null; + } + | null; + poster: { + crop: SanityImageCrop | null; + hotspot: SanityImageHotspot | null; + alt: string | null; + asset: { + _id: string; + url: string; + metadata: { + dimensions: { + width: number; + height: number; + aspectRatio: number; + } | null; + lqip: string | null; + } | null; + } | null; + } | null; + }; + } + > | null; + } + > | null; + seo: { + title: string | null; + description: string | null; + imageUrl: string | null; + } | null; +} | null; + // Source: sanity/queries/snippets/settings.ts // Variable: siteLanguageSettingsQuery // Query: *[_id == "siteLanguageSettings"][0]{ _id, availableLanguages[]{id, title}, defaultLanguageId} @@ -694,53 +2788,37 @@ export type SiteLanguageSettingsQueryResult = // Source: sanity/queries/snippets/settings.ts // Variable: siteSettingsTitleQuery -// Query: *[_id == "siteSettings"][0]{title} -export type SiteSettingsTitleQueryResult = - | { - title: InternationalizedArrayString; - } - | { - title: null; - } - | { - title: string; - } - | { - title: string | null; - } - | null; +// Query: *[_type == "siteSettings" && language == $locale][0]{title} +export type SiteSettingsTitleQueryResult = { + title: string; +} | null; // Source: sanity/queries/snippets/settings.ts // Variable: siteSettingsSeoFallbackQuery -// Query: *[_id == "siteSettings"][0]{ "title": seo.title, "description": seo.description, "imageUrl": seo.image.asset->url} -export type SiteSettingsSeoFallbackQueryResult = - | { - title: null; - description: null; - imageUrl: null; - } - | { - title: string | null; - description: string | null; - imageUrl: string | null; - } - | null; +// Query: *[_type == "siteSettings" && language == $locale][0]{ "title": seo.title, "description": seo.description, "imageUrl": seo.image.asset->url} +export type SiteSettingsSeoFallbackQueryResult = { + title: string | null; + description: string | null; + imageUrl: string | null; +} | null; // Source: sanity/queries/snippets/sitemap.ts // Variable: pageSlugsQuery -// Query: *[_type == "page" && defined(slug.current)]{ "slug": slug.current} +// Query: *[_type == "page" && defined(slug.current)]{ "slug": slug.current, language} export type PageSlugsQueryResult = Array<{ slug: string; + language: string | null; }>; // Source: sanity/queries/snippets/sitemap.ts // Variable: sitemapPagesQuery -// Query: *[_type == "home" || (_type == "page" && defined(slug.current))]{ _id, _type, _updatedAt, "slug": select(_type == "home" => null, slug.current), "path": select(_type == "home" => "/", "/" + slug.current)} +// Query: *[_type == "home" || (_type == "page" && defined(slug.current))]{ _id, _type, _updatedAt, language, "slug": select(_type == "home" => null, slug.current), "path": select(_type == "home" => "/", "/" + slug.current)} export type SitemapPagesQueryResult = Array< | { _id: string; _type: "home"; _updatedAt: string; + language: string | null; slug: null; path: "/"; } @@ -748,6 +2826,7 @@ export type SitemapPagesQueryResult = Array< _id: string; _type: "page"; _updatedAt: string; + language: string | null; slug: string; path: string; } @@ -757,10 +2836,12 @@ export type SitemapPagesQueryResult = Array< import "@sanity/client"; declare module "@sanity/client" { interface SanityQueries { + '*[_type == "home" && language == $locale][0]{\n _id,\n title,\n language,\n modules[]{\n _key,\n _type,\n _type == "module.text" => {\n title,\n body[]{\n \n \n ...,\n _type == "block" => {\n ...,\n markDefs[]{\n ...,\n _type == "link" => {\n \n ...,\n type == "internal" => {\n "linkType": "linkInternal",\n "title": coalesce(title, reference->title),\n "route": select(\n reference->_type == "home" => "page",\n reference->_type == "page" => "slug",\n "page"\n ),\n "slug": reference->slug.current,\n "resolvedReference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current\n }\n },\n type == "external" => {\n ...,\n "linkType": "linkExternal",\n "href": url,\n "title": coalesce(title, url),\n blank\n },\n type == "function" => {\n ...,\n "linkType": "linkFunction",\n "func": func {\n key,\n params\n }\n }\n\n }\n }\n }\n ,\n _type == "module.media" => {\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n },\n _type == "module.carousel" => {\n \n heading,\n imagesOnly,\n "slides": slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n "slidesMedia": slidesMedia[]{\n _key,\n _type,\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n },\n "resolvedSlides": select(\n imagesOnly == true => slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n slidesMedia[]{\n _key,\n _type,\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n }\n )\n\n },\n _type == "module.contentRefs" => {\n \n heading,\n allowMultiple,\n "reference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n },\n "references": references[]->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n }\n\n },\n _type == "module.text" => {\n title,\n body[]{\n \n ...,\n _type == "block" => {\n ...,\n markDefs[]{\n ...,\n _type == "link" => {\n \n ...,\n type == "internal" => {\n "linkType": "linkInternal",\n "title": coalesce(title, reference->title),\n "route": select(\n reference->_type == "home" => "page",\n reference->_type == "page" => "slug",\n "page"\n ),\n "slug": reference->slug.current,\n "resolvedReference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current\n }\n },\n type == "external" => {\n ...,\n "linkType": "linkExternal",\n "href": url,\n "title": coalesce(title, url),\n blank\n },\n type == "function" => {\n ...,\n "linkType": "linkFunction",\n "func": func {\n key,\n params\n }\n }\n\n }\n }\n }\n ,\n _type == "module.media" => {\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n },\n _type == "module.carousel" => {\n \n heading,\n imagesOnly,\n "slides": slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n "slidesMedia": slidesMedia[]{\n _key,\n _type,\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n },\n "resolvedSlides": select(\n imagesOnly == true => slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n slidesMedia[]{\n _key,\n _type,\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n }\n )\n\n },\n _type == "module.contentRefs" => {\n \n heading,\n allowMultiple,\n "reference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n },\n "references": references[]->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n }\n\n },\n _type == "module.text" => {\n title,\n body[]{\n \n ...,\n _type == "block" => {\n ...,\n markDefs[]{\n ...,\n _type == "link" => {\n \n ...,\n type == "internal" => {\n "linkType": "linkInternal",\n "title": coalesce(title, reference->title),\n "route": select(\n reference->_type == "home" => "page",\n reference->_type == "page" => "slug",\n "page"\n ),\n "slug": reference->slug.current,\n "resolvedReference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current\n }\n },\n type == "external" => {\n ...,\n "linkType": "linkExternal",\n "href": url,\n "title": coalesce(title, url),\n blank\n },\n type == "function" => {\n ...,\n "linkType": "linkFunction",\n "func": func {\n key,\n params\n }\n }\n\n }\n }\n }\n \n }\n }\n }\n }\n \n }\n},\n _type == "module.media" => {\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n},\n _type == "module.carousel" => {\n \n heading,\n imagesOnly,\n "slides": slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n "slidesMedia": slidesMedia[]{\n _key,\n _type,\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n },\n "resolvedSlides": select(\n imagesOnly == true => slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n slidesMedia[]{\n _key,\n _type,\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n }\n )\n\n},\n _type == "module.contentRefs" => {\n \n heading,\n allowMultiple,\n "reference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n },\n "references": references[]->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n }\n\n}\n},\n seo {\n title,\n description,\n "imageUrl": image.asset->url\n}\n}': HomeQueryResult; + '*[_type == "page" && slug.current == $slug && language == $locale][0]{\n _id,\n title,\n slug,\n language,\n modules[]{\n _key,\n _type,\n _type == "module.text" => {\n title,\n body[]{\n \n \n ...,\n _type == "block" => {\n ...,\n markDefs[]{\n ...,\n _type == "link" => {\n \n ...,\n type == "internal" => {\n "linkType": "linkInternal",\n "title": coalesce(title, reference->title),\n "route": select(\n reference->_type == "home" => "page",\n reference->_type == "page" => "slug",\n "page"\n ),\n "slug": reference->slug.current,\n "resolvedReference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current\n }\n },\n type == "external" => {\n ...,\n "linkType": "linkExternal",\n "href": url,\n "title": coalesce(title, url),\n blank\n },\n type == "function" => {\n ...,\n "linkType": "linkFunction",\n "func": func {\n key,\n params\n }\n }\n\n }\n }\n }\n ,\n _type == "module.media" => {\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n },\n _type == "module.carousel" => {\n \n heading,\n imagesOnly,\n "slides": slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n "slidesMedia": slidesMedia[]{\n _key,\n _type,\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n },\n "resolvedSlides": select(\n imagesOnly == true => slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n slidesMedia[]{\n _key,\n _type,\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n }\n )\n\n },\n _type == "module.contentRefs" => {\n \n heading,\n allowMultiple,\n "reference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n },\n "references": references[]->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n }\n\n },\n _type == "module.text" => {\n title,\n body[]{\n \n ...,\n _type == "block" => {\n ...,\n markDefs[]{\n ...,\n _type == "link" => {\n \n ...,\n type == "internal" => {\n "linkType": "linkInternal",\n "title": coalesce(title, reference->title),\n "route": select(\n reference->_type == "home" => "page",\n reference->_type == "page" => "slug",\n "page"\n ),\n "slug": reference->slug.current,\n "resolvedReference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current\n }\n },\n type == "external" => {\n ...,\n "linkType": "linkExternal",\n "href": url,\n "title": coalesce(title, url),\n blank\n },\n type == "function" => {\n ...,\n "linkType": "linkFunction",\n "func": func {\n key,\n params\n }\n }\n\n }\n }\n }\n ,\n _type == "module.media" => {\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n },\n _type == "module.carousel" => {\n \n heading,\n imagesOnly,\n "slides": slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n "slidesMedia": slidesMedia[]{\n _key,\n _type,\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n },\n "resolvedSlides": select(\n imagesOnly == true => slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n slidesMedia[]{\n _key,\n _type,\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n }\n )\n\n },\n _type == "module.contentRefs" => {\n \n heading,\n allowMultiple,\n "reference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n },\n "references": references[]->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n }\n\n },\n _type == "module.text" => {\n title,\n body[]{\n \n ...,\n _type == "block" => {\n ...,\n markDefs[]{\n ...,\n _type == "link" => {\n \n ...,\n type == "internal" => {\n "linkType": "linkInternal",\n "title": coalesce(title, reference->title),\n "route": select(\n reference->_type == "home" => "page",\n reference->_type == "page" => "slug",\n "page"\n ),\n "slug": reference->slug.current,\n "resolvedReference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current\n }\n },\n type == "external" => {\n ...,\n "linkType": "linkExternal",\n "href": url,\n "title": coalesce(title, url),\n blank\n },\n type == "function" => {\n ...,\n "linkType": "linkFunction",\n "func": func {\n key,\n params\n }\n }\n\n }\n }\n }\n \n }\n }\n }\n }\n \n }\n},\n _type == "module.media" => {\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n},\n _type == "module.carousel" => {\n \n heading,\n imagesOnly,\n "slides": slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n "slidesMedia": slidesMedia[]{\n _key,\n _type,\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n },\n "resolvedSlides": select(\n imagesOnly == true => slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n slidesMedia[]{\n _key,\n _type,\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n }\n )\n\n},\n _type == "module.contentRefs" => {\n \n heading,\n allowMultiple,\n "reference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n },\n "references": references[]->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n }\n\n}\n},\n seo {\n title,\n description,\n "imageUrl": image.asset->url\n}\n}': PageBySlugQueryResult; '*[_id == "siteLanguageSettings"][0]{\n _id,\n availableLanguages[]{id, title},\n defaultLanguageId\n}': SiteLanguageSettingsQueryResult; - '*[_id == "siteSettings"][0]{title}': SiteSettingsTitleQueryResult; - '*[_id == "siteSettings"][0]{\n "title": seo.title,\n "description": seo.description,\n "imageUrl": seo.image.asset->url\n}': SiteSettingsSeoFallbackQueryResult; - '*[_type == "page" && defined(slug.current)]{\n "slug": slug.current\n}': PageSlugsQueryResult; - '*[_type == "home" || (_type == "page" && defined(slug.current))]{\n _id,\n _type,\n _updatedAt,\n "slug": select(_type == "home" => null, slug.current),\n "path": select(_type == "home" => "/", "/" + slug.current)\n}': SitemapPagesQueryResult; + '*[_type == "siteSettings" && language == $locale][0]{title}': SiteSettingsTitleQueryResult; + '*[_type == "siteSettings" && language == $locale][0]{\n "title": seo.title,\n "description": seo.description,\n "imageUrl": seo.image.asset->url\n}': SiteSettingsSeoFallbackQueryResult; + '*[_type == "page" && defined(slug.current)]{\n "slug": slug.current,\n language\n}': PageSlugsQueryResult; + '*[_type == "home" || (_type == "page" && defined(slug.current))]{\n _id,\n _type,\n _updatedAt,\n language,\n "slug": select(_type == "home" => null, slug.current),\n "path": select(_type == "home" => "/", "/" + slug.current)\n}': SitemapPagesQueryResult; } } diff --git a/web/sanity/seo/resolveSanityMetadata.ts b/web/sanity/seo/resolveSanityMetadata.ts index 1a8e589..60c3623 100644 --- a/web/sanity/seo/resolveSanityMetadata.ts +++ b/web/sanity/seo/resolveSanityMetadata.ts @@ -4,7 +4,6 @@ import type { SiteLocaleConfig } from "@/src/i18n/fallbackSiteLocales"; import { createLanguagePathUtils } from "@/src/i18n/siteLocalePathUtils"; import type { HomeDocument, PageDocument, PageSeo } from "../types/pages"; -import { pickLocalizedString } from "../utils/sanityLocalizedText"; const SITE_BASE_URL = ( process.env.NEXT_PUBLIC_SITE_URL || "https://example.com" @@ -142,9 +141,10 @@ export type MetadataFromSanityPageDataInput = { }; /** - * `pickLocalizedString` + `resolveSanityMetadata` for route documents (home singleton or - * `page` by slug). Pass `settingsSeo` from `fetchSettingsSeoFallback` so empty page-level - * SEO falls back to site settings. + * `resolveSanityMetadata` for route documents (home singleton or `page` by slug). + * Pass `settingsSeo` from `fetchSettingsSeoFallback` so empty page-level SEO + * falls back to site settings. Under document-level translation `data.title` is + * already in the active locale (no resolver needed). */ export function metadataFromSanityPageData({ data, @@ -155,7 +155,7 @@ export function metadataFromSanityPageData({ path, siteBrandTitle, }: MetadataFromSanityPageDataInput): Metadata { - const heading = pickLocalizedString(data.title, locale, siteLocale); + const heading = data.title?.trim() ?? ""; const brand = typeof siteBrandTitle === "string" ? siteBrandTitle.trim() : ""; return resolveSanityMetadata({ seo: data.seo, diff --git a/web/sanity/types/errorSettings.ts b/web/sanity/types/errorSettings.ts index 7d02fd4..9b6cf5a 100644 --- a/web/sanity/types/errorSettings.ts +++ b/web/sanity/types/errorSettings.ts @@ -1,11 +1,13 @@ -import type { IntlRichTextEntry, IntlStringEntry } from "../utils"; +import type { PortableTextBlock } from "@portabletext/types"; + import type { ContentModule } from "./modules"; export type ErrorSettingsDocument = { _id: string; - notFoundTitle?: IntlStringEntry[] | null; - notFoundBody?: IntlRichTextEntry[] | null; - serverErrorTitle?: IntlStringEntry[] | null; - serverErrorBody?: IntlRichTextEntry[] | null; + language?: string | null; + notFoundTitle?: string | null; + notFoundBody?: PortableTextBlock[] | null; + serverErrorTitle?: string | null; + serverErrorBody?: PortableTextBlock[] | null; modules?: ContentModule[] | null; }; diff --git a/web/sanity/types/modules/carousel.ts b/web/sanity/types/modules/carousel.ts index 7acdfb8..79559b8 100644 --- a/web/sanity/types/modules/carousel.ts +++ b/web/sanity/types/modules/carousel.ts @@ -1,10 +1,9 @@ -import type { IntlStringEntry } from "@/sanity/utils"; import type { ModuleMediaData, ResolvedMediaPayload } from "./media"; export type ModuleCarouselData = { _type: "module.carousel"; _key?: string; - heading?: IntlStringEntry[] | null; + heading?: string | null; imagesOnly?: boolean | null; /** Image slides: each slide has `media` from `mediaQuery` (kind + payload). */ slides?: Array<{ diff --git a/web/sanity/types/modules/contentRefs.ts b/web/sanity/types/modules/contentRefs.ts index c306d8c..5d848b1 100644 --- a/web/sanity/types/modules/contentRefs.ts +++ b/web/sanity/types/modules/contentRefs.ts @@ -1,16 +1,14 @@ -import type { IntlStringEntry } from "@/sanity/utils"; - export type ModuleContentRefTarget = { _id?: string; _type?: "home" | "page" | string; - title?: IntlStringEntry[] | null; + title?: string | null; slug?: string | null; } | null; export type ModuleContentRefsData = { _type: "module.contentRefs"; _key?: string; - heading?: IntlStringEntry[] | null; + heading?: string | null; allowMultiple?: boolean | null; reference?: ModuleContentRefTarget; references?: Array | null; diff --git a/web/sanity/types/modules/text.ts b/web/sanity/types/modules/text.ts index 002ba07..e1077b4 100644 --- a/web/sanity/types/modules/text.ts +++ b/web/sanity/types/modules/text.ts @@ -1,8 +1,8 @@ -import type { IntlRichTextEntry, IntlStringEntry } from "@/sanity/utils"; +import type { PortableTextBlock } from "@portabletext/types"; export type ModuleTextData = { _type: "module.text"; _key?: string; - title?: IntlStringEntry[] | null; - body?: IntlRichTextEntry[] | null; + title?: string | null; + body?: PortableTextBlock[] | null; }; diff --git a/web/sanity/types/pages.ts b/web/sanity/types/pages.ts index dbcb277..b4511fd 100644 --- a/web/sanity/types/pages.ts +++ b/web/sanity/types/pages.ts @@ -3,7 +3,6 @@ * Import in app route pages next to fetchHomeDocument / fetchPageBySlug (`fetchSanityData`) — * types live here; fetches stay deduped per request via React cache. */ -import type { IntlStringEntry } from "../utils"; import type { ContentModule } from "./modules"; /** Resolved seo / seo.fallback projection from GROQ (snippets/seo.ts). */ @@ -15,14 +14,16 @@ export type PageSeo = { export type HomeDocument = { _id: string; - title?: IntlStringEntry[] | null; + language?: string | null; + title?: string | null; modules?: ContentModule[] | null; seo?: PageSeo; }; export type PageDocument = { _id: string; - title?: IntlStringEntry[] | null; + language?: string | null; + title?: string | null; slug?: { current?: string | null } | null; modules?: ContentModule[] | null; seo?: PageSeo; diff --git a/web/sanity/utils/index.ts b/web/sanity/utils/index.ts index fe3f469..94e77e6 100644 --- a/web/sanity/utils/index.ts +++ b/web/sanity/utils/index.ts @@ -1,11 +1,2 @@ export * from "./sanityImageBuilder"; -export { - type IntlRichTextEntry, - type IntlStringEntry, - type IntlTextEntry, - parseLocalizedText, - pickLocalizedPortableTextBlocks, - pickLocalizedString, - resolveLocalizedPortableTextDeep, -} from "./sanityLocalizedText"; export * from "./sanityModuleLabel"; diff --git a/web/sanity/utils/sanityLocalizedText.ts b/web/sanity/utils/sanityLocalizedText.ts deleted file mode 100644 index b47b450..0000000 --- a/web/sanity/utils/sanityLocalizedText.ts +++ /dev/null @@ -1,311 +0,0 @@ -/** - * Resolves `sanity-plugin-internationalized-array` fields for a requested locale. - * Fallback order: exact language tag → base tag → other configured site locales → any entry with content. - * Pass `siteLocale` from `fetchSiteLanguageSettings()` when resolving; otherwise built-in fallback (en/de) is used. - */ -import type { PortableTextBlock } from "@portabletext/types"; - -import { - FALLBACK_SITE_LOCALE_CONFIG, - type SiteLocaleConfig, -} from "@/src/i18n/fallbackSiteLocales"; - -type SiteLocaleSlice = Pick; - -function effectiveSiteLocale( - siteLocale?: SiteLocaleSlice | null, -): SiteLocaleSlice { - if ( - siteLocale && - siteLocale.localeIds.length > 0 && - siteLocale.defaultLocale.trim() - ) { - return siteLocale; - } - return { - localeIds: FALLBACK_SITE_LOCALE_CONFIG.localeIds, - defaultLocale: FALLBACK_SITE_LOCALE_CONFIG.defaultLocale, - }; -} - -type LocalizedEntryValue = string | PortableTextBlock[] | null | undefined; - -type LocalizedEntryBase = { - _key?: string; - language?: string; - value?: TValue; -}; - -export type IntlStringEntry = LocalizedEntryBase; -export type IntlRichTextEntry = LocalizedEntryBase; -export type IntlTextEntry = LocalizedEntryBase; - -function getLocaleCandidates(locale: string): string[] { - const normalized = locale.trim(); - if (!normalized) { - return []; - } - - const base = normalized.split("-")[0]; - if (base && base !== normalized) { - return [normalized, base]; - } - return [normalized]; -} - -function isNonEmptyString(value: unknown): value is string { - return typeof value === "string" && value.trim().length > 0; -} - -function isNonEmptyPortableText(value: unknown): value is PortableTextBlock[] { - return Array.isArray(value) && value.length > 0; -} - -function hasUsableValue(value: LocalizedEntryValue): boolean { - return isNonEmptyString(value) || isNonEmptyPortableText(value); -} - -function pickPreferredEntry( - entries: IntlTextEntry[], - localeCandidates: string[], -): IntlTextEntry | undefined { - for (const candidateTag of localeCandidates) { - const matched = entries.find( - (entry) => - (entry.language === candidateTag || entry._key === candidateTag) && - hasUsableValue(entry.value), - ); - if (matched) { - return matched; - } - } - return undefined; -} - -/** Any entry with content (last resort if no configured locales match). */ -function pickFallbackEntry( - entries: IntlTextEntry[], -): IntlTextEntry | undefined { - return entries.find((entry) => hasUsableValue(entry.value)); -} - -function getLocaleFallbackChain( - locale: string, - site: SiteLocaleSlice, -): string[] { - const normalized = locale.trim(); - const base = normalized.split("-")[0] || site.defaultLocale; - const chain: string[] = []; - if (normalized && normalized !== base) { - chain.push(normalized); - } - if (!chain.includes(base)) { - chain.push(base); - } - for (const siteLocaleId of site.localeIds) { - if (siteLocaleId !== base && !chain.includes(siteLocaleId)) { - chain.push(siteLocaleId); - } - } - return chain; -} - -function coerceResolvedValue( - value: LocalizedEntryValue, -): string | PortableTextBlock[] | undefined { - if (isNonEmptyString(value)) { - return value.trim(); - } - if (isNonEmptyPortableText(value)) { - return value; - } - return undefined; -} - -function resolveLocalizedEntries( - entries: IntlTextEntry[] | null | undefined, - locale: string, - site: SiteLocaleSlice, -): string | PortableTextBlock[] | undefined { - if (!Array.isArray(entries) || entries.length === 0) { - return undefined; - } - - for (const localeSegment of getLocaleFallbackChain(locale, site)) { - const candidates = getLocaleCandidates(localeSegment); - const preferred = pickPreferredEntry(entries, candidates); - if (preferred) { - return coerceResolvedValue(preferred.value); - } - } - - const fallback = pickFallbackEntry(entries); - return coerceResolvedValue(fallback?.value); -} - -/** - * Detects `sanity-plugin-internationalized-array` entries (`language` / `_key` + `value`). - * Do not confuse with arbitrary object arrays: each element must have `value`. - */ -function looksLikeIntlEntryArray(value: unknown): value is IntlTextEntry[] { - if (!Array.isArray(value) || value.length === 0) { - return false; - } - return value.every( - (item) => - item != null && - typeof item === "object" && - "value" in item && - ("language" in item || "_key" in item), - ); -} - -function deepResolveLocalizedTree( - value: unknown, - locale: string, - site: SiteLocaleSlice, -): unknown { - if (value == null) { - return value; - } - - if (looksLikeIntlEntryArray(value)) { - const resolved = resolveLocalizedEntries(value, locale, site); - if (resolved === undefined) { - return undefined; - } - if (typeof resolved === "string") { - return resolved; - } - if (Array.isArray(resolved)) { - return resolved.map((item) => - deepResolveLocalizedTree(item, locale, site), - ); - } - return resolved; - } - - if (Array.isArray(value)) { - return value.map((item) => deepResolveLocalizedTree(item, locale, site)); - } - - if (typeof value === "object") { - const out: Record = {}; - for (const [propertyKey, propertyValue] of Object.entries( - value as object, - )) { - out[propertyKey] = deepResolveLocalizedTree(propertyValue, locale, site); - } - return out; - } - - return value; -} - -/** - * Picks the locale for `internationalizedArrayRichText*` and resolves all embedded i18n - * fields in blocks, mark defs, and modules. - */ -export function resolveLocalizedPortableTextDeep( - entries: IntlRichTextEntry[] | null | undefined, - locale: string, - siteLocale?: SiteLocaleSlice | null, -): PortableTextBlock[] { - const site = effectiveSiteLocale(siteLocale); - const raw = resolveLocalizedEntries(entries, locale, site); - if (!Array.isArray(raw) || raw.length === 0) { - return []; - } - return deepResolveLocalizedTree(raw, locale, site) as PortableTextBlock[]; -} - -export type ParseLocalizedTextOptions = { - /** `internationalizedArray*` field value from Sanity */ - entries: IntlTextEntry[] | null | undefined; - locale?: string; - /** - * - `auto` (default): string or Portable Text blocks, depending on the field - * - `string`: only plain string (rich text resolves to `undefined`) - * - `blocks`: only blocks (plain string resolves to `[]`) - */ - as?: "auto" | "string" | "blocks"; - /** From `fetchSiteLanguageSettings()` — drives fallback order. */ - siteLocale?: SiteLocaleSlice | null; -}; - -export function parseLocalizedText( - options: Omit & { as?: "auto" }, -): string | PortableTextBlock[] | undefined; -export function parseLocalizedText( - options: ParseLocalizedTextOptions & { as: "string" }, -): string | undefined; -export function parseLocalizedText( - options: ParseLocalizedTextOptions & { as: "blocks" }, -): PortableTextBlock[]; -export function parseLocalizedText({ - entries, - locale, - as = "auto", - siteLocale, -}: ParseLocalizedTextOptions): - | string - | PortableTextBlock[] - | undefined - | PortableTextBlock[] { - const site = effectiveSiteLocale(siteLocale); - const resolvedLocale = - locale?.trim() || - site.defaultLocale || - FALLBACK_SITE_LOCALE_CONFIG.defaultLocale; - const raw = resolveLocalizedEntries(entries, resolvedLocale, site); - - if (as === "string") { - return typeof raw === "string" ? raw : undefined; - } - - if (as === "blocks") { - if (!Array.isArray(raw) || raw.length === 0) { - return []; - } - return deepResolveLocalizedTree( - raw, - resolvedLocale, - site, - ) as PortableTextBlock[]; - } - - if (typeof raw === "string") { - return raw; - } - if (Array.isArray(raw) && raw.length > 0) { - return deepResolveLocalizedTree( - raw, - resolvedLocale, - site, - ) as PortableTextBlock[]; - } - return raw; -} - -/** Convenience for i18n string fields (`internationalizedArrayString`). */ -export function pickLocalizedString( - entries: IntlStringEntry[] | null | undefined, - locale?: string, - siteLocale?: SiteLocaleSlice | null, -): string | undefined { - const site = effectiveSiteLocale(siteLocale); - const loc = - locale?.trim() || - site.defaultLocale || - FALLBACK_SITE_LOCALE_CONFIG.defaultLocale; - return parseLocalizedText({ entries, locale: loc, as: "string", siteLocale }); -} - -/** Convenience for `internationalizedArrayRichText*` / `richTextMedia` bodies. */ -export function pickLocalizedPortableTextBlocks( - entries: IntlRichTextEntry[] | null | undefined, - locale: string, - siteLocale?: SiteLocaleSlice | null, -): PortableTextBlock[] { - return resolveLocalizedPortableTextDeep(entries, locale, siteLocale); -} diff --git a/web/src/app/[locale]/LocaleNotFoundContent.tsx b/web/src/app/[locale]/LocaleNotFoundContent.tsx index 18fb559..2d79631 100644 --- a/web/src/app/[locale]/LocaleNotFoundContent.tsx +++ b/web/src/app/[locale]/LocaleNotFoundContent.tsx @@ -4,10 +4,6 @@ import Link from "next/link"; import { usePathname } from "next/navigation"; import type { ErrorSettingsDocument } from "@/sanity/types/errorSettings"; -import { - pickLocalizedPortableTextBlocks, - pickLocalizedString, -} from "@/sanity/utils/sanityLocalizedText"; import { RichTextMedia } from "@/src/components/text/RichTextMedia"; import { useLanguage } from "@/src/contexts/LanguageContext"; @@ -17,23 +13,17 @@ type Props = { export function LocaleNotFoundContent({ errorSettings }: Props) { const pathname = usePathname() ?? "/"; - const { localeFromPathname, siteLocale, localePath } = useLanguage(); + const { localeFromPathname, localePath } = useLanguage(); const locale = localeFromPathname(pathname); - const title = - pickLocalizedString(errorSettings?.notFoundTitle, locale, siteLocale) ?? - "Page not found"; - const body = pickLocalizedPortableTextBlocks( - errorSettings?.notFoundBody, - locale, - siteLocale, - ); + const title = errorSettings?.notFoundTitle?.trim() || "Page not found"; + const body = errorSettings?.notFoundBody ?? []; return (

{title}

- {body?.length ? ( + {body.length ? ( ) : (

diff --git a/web/src/app/[locale]/[slug]/page.tsx b/web/src/app/[locale]/[slug]/page.tsx index 0fb0082..6cbeb78 100644 --- a/web/src/app/[locale]/[slug]/page.tsx +++ b/web/src/app/[locale]/[slug]/page.tsx @@ -22,25 +22,16 @@ type PageProps = { export const revalidate = 60 satisfies SanityDocumentCacheRevalidateSeconds; export async function generateStaticParams() { - const [rows, siteLocale] = await Promise.all([ - cachedPageSlugs(), - fetchSiteLanguageSettings({ stega: false }), - ]); - const list = rows ?? []; - const slugs = list - .map((row) => row.slug) + const rows = await cachedPageSlugs(); + return (rows ?? []) .filter( - (s: string | undefined): s is string => - typeof s === "string" && s.length > 0, - ); - - const out: { locale: string; slug: string }[] = []; - for (const locale of siteLocale.localeIds) { - for (const slug of slugs) { - out.push({ locale, slug }); - } - } - return out; + (row): row is { slug: string; language: string } => + typeof row.slug === "string" && + row.slug.length > 0 && + typeof row.language === "string" && + row.language.length > 0, + ) + .map((row) => ({ locale: row.language, slug: row.slug })); } export async function generateMetadata({ @@ -48,10 +39,10 @@ export async function generateMetadata({ }: PageProps): Promise { const { slug, locale } = await params; const [data, siteLocale, siteBrand, settingsSeo] = await Promise.all([ - fetchPageBySlug(slug, { stega: false }), + fetchPageBySlug(slug, locale, { stega: false }), fetchSiteLanguageSettings({ stega: false }), - fetchSiteSettingsTitle({ stega: false }), - fetchSettingsSeoFallback({ stega: false }), + fetchSiteSettingsTitle(locale, { stega: false }), + fetchSettingsSeoFallback(locale, { stega: false }), ]); if (!data) { return { @@ -73,10 +64,7 @@ export async function generateMetadata({ export default async function Page({ params }: PageProps) { const { slug, locale } = await params; - const [data, siteLocale] = await Promise.all([ - fetchPageBySlug(slug), - fetchSiteLanguageSettings(), - ]); + const data = await fetchPageBySlug(slug, locale); if (!data) { notFound(); @@ -88,11 +76,7 @@ export default async function Page({ params }: PageProps) { {data.modules?.length ? (

Modules

- +
) : null}
diff --git a/web/src/app/[locale]/layout.tsx b/web/src/app/[locale]/layout.tsx index e2bef11..5e00c19 100644 --- a/web/src/app/[locale]/layout.tsx +++ b/web/src/app/[locale]/layout.tsx @@ -22,14 +22,12 @@ export const dynamicParams = true; export async function generateMetadata({ params }: Props): Promise { const { locale: raw } = await params; - const [siteLocale, siteTitle] = await Promise.all([ - fetchSiteLanguageSettings({ stega: false }), - fetchSiteSettingsTitle({ stega: false }), - ]); + const siteLocale = await fetchSiteLanguageSettings({ stega: false }); const pathUtils = createLanguagePathUtils(siteLocale); if (!pathUtils.isAppLocale(raw)) { notFound(); } + const siteTitle = await fetchSiteSettingsTitle(raw, { stega: false }); const suffix = siteTitle.trim() || "Site"; return { title: { @@ -48,7 +46,7 @@ export default async function LocaleLayout({ children, params }: Props) { notFound(); } const locale = raw; - const siteNav = await fetchSiteNavMenus(); + const siteNav = await fetchSiteNavMenus(locale); return ( diff --git a/web/src/app/[locale]/not-found.tsx b/web/src/app/[locale]/not-found.tsx index 6d2fb45..a59e791 100644 --- a/web/src/app/[locale]/not-found.tsx +++ b/web/src/app/[locale]/not-found.tsx @@ -1,13 +1,20 @@ -import { fetchErrorSettings } from "@/sanity/fetchSanityData"; +import { + fetchErrorSettings, + fetchSiteLanguageSettings, +} from "@/sanity/fetchSanityData"; import { LocaleNotFoundContent } from "./LocaleNotFoundContent"; /** - * Server: cached Sanity settings only. Locale comes from the URL on the client - * (`LocaleNotFoundContent`) so we never call `headers()` here — that would mark - * the whole `[locale]` segment as dynamic and disable SSG for `/[locale]`. + * Server: cached Sanity settings only. The actual URL locale is resolved + * client-side in `LocaleNotFoundContent` (`usePathname`) so we never call + * `headers()` here — that would mark the whole `[locale]` segment as dynamic + * and disable SSG for `/[locale]`. The server fetch uses the site default + * locale; if it doesn't match the URL the client can still render the + * generic fallback copy. */ export default async function NotFound() { - const errorSettings = await fetchErrorSettings(); + const siteLocale = await fetchSiteLanguageSettings(); + const errorSettings = await fetchErrorSettings(siteLocale.defaultLocale); return ; } diff --git a/web/src/app/[locale]/page.tsx b/web/src/app/[locale]/page.tsx index 33913de..e407745 100644 --- a/web/src/app/[locale]/page.tsx +++ b/web/src/app/[locale]/page.tsx @@ -29,10 +29,10 @@ export async function generateMetadata({ }: PageProps): Promise { const { locale } = await params; const [data, siteLocale, siteBrand, settingsSeo] = await Promise.all([ - fetchHomeDocument({ stega: false }), + fetchHomeDocument(locale, { stega: false }), fetchSiteLanguageSettings({ stega: false }), - fetchSiteSettingsTitle({ stega: false }), - fetchSettingsSeoFallback({ stega: false }), + fetchSiteSettingsTitle(locale, { stega: false }), + fetchSettingsSeoFallback(locale, { stega: false }), ]); if (!data) { return { @@ -53,19 +53,15 @@ export async function generateMetadata({ export default async function Home({ params }: PageProps) { const { locale } = await params; - const [data, siteLocale] = await Promise.all([ - fetchHomeDocument(), - fetchSiteLanguageSettings(), - ]); + const data = await fetchHomeDocument(locale); if (!data) { return (

- Home singleton is not in the dataset yet. Create it in Sanity Studio - (document id home - ). + No home document for {locale} yet. Create + one in Sanity Studio.

@@ -78,11 +74,7 @@ export default async function Home({ params }: PageProps) { {data.modules?.length ? (

Modules

- +
) : null} diff --git a/web/src/app/api/revalidate/route.ts b/web/src/app/api/revalidate/route.ts index c789323..5e39deb 100644 --- a/web/src/app/api/revalidate/route.ts +++ b/web/src/app/api/revalidate/route.ts @@ -23,6 +23,7 @@ type SanityWebhookPayload = { _id?: string; _type?: string; slug?: { current?: string } | null; + language?: string | null; }; // ── Defense-in-depth: simple in-memory token bucket per IP ─────────────────── @@ -66,6 +67,13 @@ function isValidPayload(value: unknown): value is SanityWebhookPayload { return false; } } + if ( + v.language !== undefined && + v.language !== null && + typeof v.language !== "string" + ) { + return false; + } return true; } @@ -110,20 +118,23 @@ function verifySignature( function getTagsForDocument(payload: SanityWebhookPayload): string[] { const tags: string[] = []; - const { _type, slug } = payload; + const { _type, slug, language } = payload; + const lang = + typeof language === "string" && language.trim() ? language.trim() : null; if (_type && !ALLOWED_DOCUMENT_TYPES.has(_type as never)) { return tags; } if (_type === "home") { - tags.push("home", "site-pages"); + if (lang) tags.push(`home-${lang}`); + tags.push("site-pages"); } if (_type === "page") { tags.push("pages", "site-pages"); - if (slug?.current) { - tags.push(`page-${slug.current}`); + if (slug?.current && lang) { + tags.push(`page-${slug.current}-${lang}`); } } diff --git a/web/src/app/sitemap.ts b/web/src/app/sitemap.ts index c6355e5..7787c96 100644 --- a/web/src/app/sitemap.ts +++ b/web/src/app/sitemap.ts @@ -8,12 +8,19 @@ const BASE_URL = ( ).replace(/\/$/, ""); /** - * Refresh on tag invalidation (`pages`, `home`, `site-language-settings`, `site-pages`) - * via `/api/revalidate`. The 1 h fail-safe revalidate ensures editors who skip the - * webhook still get fresh sitemaps within reasonable time. + * Refresh on tag invalidation (`pages`, `site-pages`, `site-language-settings`) + * via `/api/revalidate`. The 1 h fail-safe revalidate ensures editors who skip + * the webhook still get fresh sitemaps within reasonable time. */ export const revalidate = 3600; +/** + * One sitemap entry per language variant of a routable document. With + * document-level translation each language is its own document — slugs may + * differ between languages, and the cross-locale relationship lives in + * `translation.metadata` (not queried here). Hreflang alternates are + * therefore omitted; add them in a follow-up if you need them. + */ export default async function sitemap(): Promise { const [pages, siteLocale] = await Promise.all([ cachedSitemapPages(), @@ -24,31 +31,19 @@ export default async function sitemap(): Promise { const entries: MetadataRoute.Sitemap = []; for (const page of pages) { - for (const locale of siteLocale.localeIds) { - const pathname = pathUtils.localePath(page.path, locale); - const url = `${BASE_URL}${pathname === "/" ? "" : pathname}` || BASE_URL; - - const languages: Record = {}; - for (const altLocale of siteLocale.localeIds) { - const altPath = pathUtils.localePath(page.path, altLocale); - languages[altLocale] = - `${BASE_URL}${altPath === "/" ? "" : altPath}` || BASE_URL; - } - const xDefaultPath = pathUtils.localePath( - page.path, - siteLocale.defaultLocale, - ); - languages["x-default"] = - `${BASE_URL}${xDefaultPath === "/" ? "" : xDefaultPath}` || BASE_URL; - - entries.push({ - url, - lastModified: page._updatedAt, - changeFrequency: page._type === "home" ? "daily" : "weekly", - priority: page._type === "home" ? 1.0 : 0.8, - alternates: { languages }, - }); - } + const language = + typeof page.language === "string" && page.language.trim() + ? page.language.trim() + : siteLocale.defaultLocale; + const pathname = pathUtils.localePath(page.path, language); + const url = `${BASE_URL}${pathname === "/" ? "" : pathname}` || BASE_URL; + + entries.push({ + url, + lastModified: page._updatedAt, + changeFrequency: page._type === "home" ? "daily" : "weekly", + priority: page._type === "home" ? 1.0 : 0.8, + }); } return entries; diff --git a/web/src/components/modules/ModuleText.tsx b/web/src/components/modules/ModuleText.tsx index 28aeafc..3487dff 100644 --- a/web/src/components/modules/ModuleText.tsx +++ b/web/src/components/modules/ModuleText.tsx @@ -1,28 +1,13 @@ import type { ModuleTextData } from "@/sanity/types/modules"; -import { - pickLocalizedPortableTextBlocks, - pickLocalizedString, -} from "@/sanity/utils/sanityLocalizedText"; -import type { SiteLocaleConfig } from "@/src/i18n/fallbackSiteLocales"; import { RichTextMedia } from "../text/RichTextMedia"; -// ─── Types ─────────────────────────────────────────────────────────────────── - type Props = { module: ModuleTextData; - locale: string; - siteLocale: Pick; }; -// ─── Component ─────────────────────────────────────────────────────────────── - -export function ModuleText({ module, locale, siteLocale }: Props) { - const title = pickLocalizedString(module.title, locale, siteLocale); - const blocks = pickLocalizedPortableTextBlocks( - module.body, - locale, - siteLocale, - ); +export function ModuleText({ module }: Props) { + const title = module.title?.trim() ?? ""; + const blocks = module.body ?? []; return (
diff --git a/web/src/components/modules/ModulesRenderer.tsx b/web/src/components/modules/ModulesRenderer.tsx index f57d102..b0a8eef 100644 --- a/web/src/components/modules/ModulesRenderer.tsx +++ b/web/src/components/modules/ModulesRenderer.tsx @@ -5,40 +5,24 @@ import type { ModuleMediaData, ModuleTextData, } from "@/sanity/types/modules"; -import { pickLocalizedString } from "@/sanity/utils/sanityLocalizedText"; import { getSanityModuleLabel } from "@/sanity/utils/sanityModuleLabel"; -import type { SiteLocaleConfig } from "@/src/i18n/fallbackSiteLocales"; import { ModuleMedia } from "./ModuleMedia"; import { ModuleText } from "./ModuleText"; -// ─── Types ─────────────────────────────────────────────────────────────────── - type Props = { modules: ContentModule[]; - locale: string; - siteLocale: Pick; }; const IS_DEV = process.env.NODE_ENV === "development"; -// ─── Sub-components ────────────────────────────────────────────────────────── - /** * Carousel placeholder — schema + GROQ exist, no production renderer yet. * Renders a labeled placeholder in dev only; nothing in production so empty * Studio modules don't surface visible warnings to end users. */ -function ModuleCarouselPlaceholder({ - module, - locale, - siteLocale, -}: { - module: ModuleCarouselData; - locale: string; - siteLocale: Pick; -}) { +function ModuleCarouselPlaceholder({ module }: { module: ModuleCarouselData }) { if (!IS_DEV) return null; - const heading = pickLocalizedString(module.heading, locale, siteLocale); + const heading = module.heading?.trim() ?? ""; const slideCount = module.resolvedSlides?.length ?? module.slidesMedia?.length ?? @@ -57,15 +41,11 @@ function ModuleCarouselPlaceholder({ function ModuleContentRefsPlaceholder({ module, - locale, - siteLocale, }: { module: ModuleContentRefsData; - locale: string; - siteLocale: Pick; }) { if (!IS_DEV) return null; - const heading = pickLocalizedString(module.heading, locale, siteLocale); + const heading = module.heading?.trim() ?? ""; const refCount = module.allowMultiple ? (module.references?.length ?? 0) : module.reference @@ -100,23 +80,14 @@ function UnknownModule({ moduleType }: { moduleType: string | undefined }) { ); } -// ─── Component ─────────────────────────────────────────────────────────────── - /** Renders the document `modules[]` stack (one UI block per `module.*` type). */ -export function ModulesRenderer({ modules, locale, siteLocale }: Props) { +export function ModulesRenderer({ modules }: Props) { return (
{modules.map((mod, index) => { const key = mod._key ?? `${mod._type ?? "module"}-${index}`; if (mod._type === "module.text") { - return ( - - ); + return ; } if (mod._type === "module.media") { return ; @@ -126,8 +97,6 @@ export function ModulesRenderer({ modules, locale, siteLocale }: Props) { ); } @@ -136,8 +105,6 @@ export function ModulesRenderer({ modules, locale, siteLocale }: Props) { ); } From b86135add29f9b32d6e26d669c6843e9abdad3d9 Mon Sep 17 00:00:00 2001 From: Marcell Lanczos Date: Thu, 28 May 2026 18:07:07 +0200 Subject: [PATCH 04/45] feat(MediaImage): enable client-side rendering by adding "use client" directive --- web/src/components/media/MediaImage.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/src/components/media/MediaImage.tsx b/web/src/components/media/MediaImage.tsx index 706e17d..e74524e 100644 --- a/web/src/components/media/MediaImage.tsx +++ b/web/src/components/media/MediaImage.tsx @@ -1,3 +1,5 @@ +"use client"; + import clsx from "clsx"; import type { CSSProperties } from "react"; From 9afe8b7f47a0950e844878acffcb117b54ef7f6f Mon Sep 17 00:00:00 2001 From: Marcell Lanczos Date: Thu, 28 May 2026 18:30:18 +0200 Subject: [PATCH 05/45] refactor: remove fallback to NEXT_PUBLIC_SANITY_PROJECT_ID in dataset resolution Updated the dataset resolution logic to exclusively use SANITY_STUDIO_PROJECT_ID, removing the fallback to NEXT_PUBLIC_SANITY_PROJECT_ID in both the resolveStudioDatasetAsync function and getSanityStudioProjectId function. This change simplifies the project ID retrieval process. --- packages/sanity-dataset-resolve/src/index.ts | 3 +- web/sanity/resolveStudioDataset.ts | 1 - web/src/components/media/MediaImage.tsx | 36 ++++++++++---------- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/packages/sanity-dataset-resolve/src/index.ts b/packages/sanity-dataset-resolve/src/index.ts index b42e3c9..69c2d4e 100644 --- a/packages/sanity-dataset-resolve/src/index.ts +++ b/packages/sanity-dataset-resolve/src/index.ts @@ -132,8 +132,7 @@ export async function resolveStudioDatasetAsync( } const projectId = - env.SANITY_STUDIO_PROJECT_ID?.trim() || - env.NEXT_PUBLIC_SANITY_PROJECT_ID?.trim(); + env.SANITY_STUDIO_PROJECT_ID?.trim(); const devName = env.SANITY_STUDIO_DATASET_DEVELOPMENT?.trim() ?? "development"; const prodName = env.SANITY_STUDIO_DATASET_PRODUCTION?.trim() ?? "production"; diff --git a/web/sanity/resolveStudioDataset.ts b/web/sanity/resolveStudioDataset.ts index cf9caea..11fe3b2 100644 --- a/web/sanity/resolveStudioDataset.ts +++ b/web/sanity/resolveStudioDataset.ts @@ -15,7 +15,6 @@ export function getSanityStudioProjectId( ): string { return ( env.SANITY_STUDIO_PROJECT_ID?.trim() || - env.NEXT_PUBLIC_SANITY_PROJECT_ID?.trim() || "" ); } diff --git a/web/src/components/media/MediaImage.tsx b/web/src/components/media/MediaImage.tsx index e74524e..37364f1 100644 --- a/web/src/components/media/MediaImage.tsx +++ b/web/src/components/media/MediaImage.tsx @@ -1,5 +1,3 @@ -"use client"; - import clsx from "clsx"; import type { CSSProperties } from "react"; @@ -78,7 +76,8 @@ function buildSrcSet(image: SanityImageField, maxWidth: number): string { * Sanity-driven image: **native ``** with deterministic Sanity CDN URLs (same SSR / client), * responsive `srcset` + `sizes` so the browser picks the right variant per container / viewport. * - * Avoids `next/image` optimizer `src` / `srcSet` hydration drift. + * Server Component (no `"use client"`) so URLs are built once on the server and the + * layout boot script may add `.img-loaded` without React hydration conflicts. */ export function MediaImage({ imagePayload, @@ -129,21 +128,22 @@ export function MediaImage({ : { aspectRatio: `${cropped.width} / ${cropped.height}` } } > - {/* biome-ignore lint/performance/noImgElement: deterministic Sanity URLs; next/image caused hydration mismatches */} - {imageAltFromField(image, + {/* biome-ignore lint/performance/noImgElement: deterministic Sanity URLs; next/image caused hydration mismatches */} + {imageAltFromField(image,
); } From 3a69f5944b76bc961cb2b5e7a9b95508780b759a Mon Sep 17 00:00:00 2001 From: Marcell Lanczos Date: Thu, 28 May 2026 18:36:59 +0200 Subject: [PATCH 06/45] refactor: streamline project ID retrieval in dataset resolution Consolidated the project ID retrieval logic in both the resolveStudioDatasetAsync and getSanityStudioProjectId functions by removing unnecessary line breaks. This enhances code readability without altering functionality. --- packages/sanity-dataset-resolve/src/index.ts | 3 +-- web/sanity/resolveStudioDataset.ts | 5 +---- web/src/components/media/MediaImage.tsx | 3 ++- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/sanity-dataset-resolve/src/index.ts b/packages/sanity-dataset-resolve/src/index.ts index 69c2d4e..6a727fe 100644 --- a/packages/sanity-dataset-resolve/src/index.ts +++ b/packages/sanity-dataset-resolve/src/index.ts @@ -131,8 +131,7 @@ export async function resolveStudioDatasetAsync( return explicit; } - const projectId = - env.SANITY_STUDIO_PROJECT_ID?.trim(); + const projectId = env.SANITY_STUDIO_PROJECT_ID?.trim(); const devName = env.SANITY_STUDIO_DATASET_DEVELOPMENT?.trim() ?? "development"; const prodName = env.SANITY_STUDIO_DATASET_PRODUCTION?.trim() ?? "production"; diff --git a/web/sanity/resolveStudioDataset.ts b/web/sanity/resolveStudioDataset.ts index 11fe3b2..98cde25 100644 --- a/web/sanity/resolveStudioDataset.ts +++ b/web/sanity/resolveStudioDataset.ts @@ -13,8 +13,5 @@ export { resolveStudioDatasetAsync }; export function getSanityStudioProjectId( env: NodeJS.ProcessEnv = process.env, ): string { - return ( - env.SANITY_STUDIO_PROJECT_ID?.trim() || - "" - ); + return env.SANITY_STUDIO_PROJECT_ID?.trim() || ""; } diff --git a/web/src/components/media/MediaImage.tsx b/web/src/components/media/MediaImage.tsx index 37364f1..fd313e3 100644 --- a/web/src/components/media/MediaImage.tsx +++ b/web/src/components/media/MediaImage.tsx @@ -1,3 +1,5 @@ +"use client"; + import clsx from "clsx"; import type { CSSProperties } from "react"; @@ -142,7 +144,6 @@ export function MediaImage({ {...(!priority && { "data-lazy": "" })} className="absolute inset-0 block h-full w-full max-w-none" style={imgStyle} - suppressHydrationWarning />
); From e8baa5534ab396a0af9564dc83a19d7bfd89e58a Mon Sep 17 00:00:00 2001 From: Marcell Lanczos Date: Thu, 28 May 2026 18:37:13 +0200 Subject: [PATCH 07/45] refactor(MediaImage): remove unnecessary line breaks for improved readability Streamlined the JSX structure in the MediaImage component by eliminating redundant line breaks, enhancing code clarity without affecting functionality. --- web/src/components/media/MediaImage.tsx | 30 ++++++++++++------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/web/src/components/media/MediaImage.tsx b/web/src/components/media/MediaImage.tsx index fd313e3..b6962c4 100644 --- a/web/src/components/media/MediaImage.tsx +++ b/web/src/components/media/MediaImage.tsx @@ -130,21 +130,21 @@ export function MediaImage({ : { aspectRatio: `${cropped.width} / ${cropped.height}` } } > - {/* biome-ignore lint/performance/noImgElement: deterministic Sanity URLs; next/image caused hydration mismatches */} - {imageAltFromField(image, + {/* biome-ignore lint/performance/noImgElement: deterministic Sanity URLs; next/image caused hydration mismatches */} + {imageAltFromField(image, ); } From c6b4efa89eabb473f3e7083517ce77bbd87b03d9 Mon Sep 17 00:00:00 2001 From: Damian Date: Thu, 28 May 2026 18:36:58 +0200 Subject: [PATCH 08/45] feat(carousel): implement module.carousel with embla (loop, dots, thumbs, autoplay) Adds CMS-driven carousel behavior fields (loop, showThumbnails, showNavDots, autoplay, autoplayDelayMs) and a production ModuleCarousel renderer using embla-carousel-react + embla-carousel-autoplay. Replaces the dev-only placeholder in ModulesRenderer and supports embeds in RichTextMedia. Co-authored-by: Cursor --- pnpm-lock.yaml | 40 +++ studio/sanity.types.gen.ts | 5 + studio/schema.json | 35 +++ studio/schemas/README.md | 2 +- .../schemas/objects/modules/moduleCarousel.ts | 50 ++++ web/package.json | 2 + .../queries/components/modules/carousel.ts | 5 + web/sanity/sanity.types.gen.ts | 33 ++- web/sanity/types/modules/carousel.ts | 5 + web/src/components/carousel/CarouselSlide.tsx | 88 ++++++ .../components/carousel/CarouselViewport.tsx | 267 ++++++++++++++++++ .../components/carousel/ModuleCarousel.tsx | 126 +++++++++ web/src/components/carousel/index.ts | 1 + .../components/modules/ModulesRenderer.tsx | 30 +- web/src/components/text/RichTextMedia.tsx | 12 +- 15 files changed, 666 insertions(+), 35 deletions(-) create mode 100644 web/src/components/carousel/CarouselSlide.tsx create mode 100644 web/src/components/carousel/CarouselViewport.tsx create mode 100644 web/src/components/carousel/ModuleCarousel.tsx create mode 100644 web/src/components/carousel/index.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2e52a07..8ff975d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -106,6 +106,12 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + embla-carousel-autoplay: + specifier: ^8.6.0 + version: 8.6.0(embla-carousel@8.6.0) + embla-carousel-react: + specifier: ^8.6.0 + version: 8.6.0(react@19.2.6) hls.js: specifier: ^1.6.16 version: 1.6.16 @@ -3490,6 +3496,24 @@ packages: electron-to-chromium@1.5.362: resolution: {integrity: sha512-PUY2DrLvkjkUuWqq+KPL2iWshrJsZOcIojzRQ7eXFacc9dWga7MGMJAa15VbiejSZB1PAXaRLAiKgruHP8LB1w==} + embla-carousel-autoplay@8.6.0: + resolution: {integrity: sha512-OBu5G3nwaSXkZCo1A6LTaFMZ8EpkYbwIaH+bPqdBnDGQ2fh4+NbzjXjs2SktoPNKCtflfVMc75njaDHOYXcrsA==} + peerDependencies: + embla-carousel: 8.6.0 + + embla-carousel-react@8.6.0: + resolution: {integrity: sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA==} + peerDependencies: + react: ^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + + embla-carousel-reactive-utils@8.6.0: + resolution: {integrity: sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A==} + peerDependencies: + embla-carousel: 8.6.0 + + embla-carousel@8.6.0: + resolution: {integrity: sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==} + emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} @@ -9550,6 +9574,22 @@ snapshots: electron-to-chromium@1.5.362: {} + embla-carousel-autoplay@8.6.0(embla-carousel@8.6.0): + dependencies: + embla-carousel: 8.6.0 + + embla-carousel-react@8.6.0(react@19.2.6): + dependencies: + embla-carousel: 8.6.0 + embla-carousel-reactive-utils: 8.6.0(embla-carousel@8.6.0) + react: 19.2.6 + + embla-carousel-reactive-utils@8.6.0(embla-carousel@8.6.0): + dependencies: + embla-carousel: 8.6.0 + + embla-carousel@8.6.0: {} + emoji-regex@10.6.0: {} emoji-regex@8.0.0: {} diff --git a/studio/sanity.types.gen.ts b/studio/sanity.types.gen.ts index cf7d937..bafd883 100644 --- a/studio/sanity.types.gen.ts +++ b/studio/sanity.types.gen.ts @@ -147,6 +147,11 @@ export type ModuleCarousel = { _key: string; } & ModuleMedia >; + loop?: boolean; + showThumbnails?: boolean; + showNavDots?: boolean; + autoplay?: boolean; + autoplayDelayMs?: number; }; export type MediaVideo = { diff --git a/studio/schema.json b/studio/schema.json index e893591..5217745 100644 --- a/studio/schema.json +++ b/studio/schema.json @@ -794,6 +794,41 @@ } }, "optional": true + }, + "loop": { + "type": "objectAttribute", + "value": { + "type": "boolean" + }, + "optional": true + }, + "showThumbnails": { + "type": "objectAttribute", + "value": { + "type": "boolean" + }, + "optional": true + }, + "showNavDots": { + "type": "objectAttribute", + "value": { + "type": "boolean" + }, + "optional": true + }, + "autoplay": { + "type": "objectAttribute", + "value": { + "type": "boolean" + }, + "optional": true + }, + "autoplayDelayMs": { + "type": "objectAttribute", + "value": { + "type": "number" + }, + "optional": true } } } diff --git a/studio/schemas/README.md b/studio/schemas/README.md index d9c8b64..8ac8ef3 100644 --- a/studio/schemas/README.md +++ b/studio/schemas/README.md @@ -101,7 +101,7 @@ You do **not** need a Desk structure item for a module (modules are not document | Module | Role | |--------|------| | `module.media` | Image or Mux video via nested **`imageContent`** (`media.image`) / **`videoContent`** (`media.video`) depending on `type`. | -| `module.carousel` | Optional heading; **`imagesOnly`** (default `true`) uses **`slides`** (images only); when `false`, use **`slidesMedia`** (array of `module.media`). | +| `module.carousel` | Optional heading; **`imagesOnly`** (default `true`) uses **`slides`** (images only); when `false`, use **`slidesMedia`** (array of `module.media`). Behavior fieldset: **`loop`**, **`showThumbnails`**, **`showNavDots`**, **`autoplay`** + **`autoplayDelayMs`**. | | `module.contentRefs` | Optional heading; **`allowMultiple`** toggles single **`reference`** vs **`references`** array to **`PAGE_REFERENCES`** (same as internal links). | | `module.text` | Title + rich text body. | diff --git a/studio/schemas/objects/modules/moduleCarousel.ts b/studio/schemas/objects/modules/moduleCarousel.ts index 3618f6b..9a05f15 100644 --- a/studio/schemas/objects/modules/moduleCarousel.ts +++ b/studio/schemas/objects/modules/moduleCarousel.ts @@ -6,6 +6,13 @@ export const moduleCarousel = defineType({ title: "Carousel", type: "object", icon: ImagesIcon, + fieldsets: [ + { + name: "behavior", + title: "Carousel behavior", + options: { collapsible: true, collapsed: false }, + }, + ], fields: [ { name: "heading", @@ -50,6 +57,49 @@ export const moduleCarousel = defineType({ return true; }), }, + { + name: "loop", + title: "Loop", + description: "Wrap from the last slide back to the first.", + type: "boolean", + initialValue: false, + fieldset: "behavior", + }, + { + name: "showThumbnails", + title: "Show thumbnails", + description: "Display a thumbnail strip below the carousel.", + type: "boolean", + initialValue: false, + fieldset: "behavior", + }, + { + name: "showNavDots", + title: "Navigation dots", + description: "Show pagination dots under the carousel.", + type: "boolean", + initialValue: true, + fieldset: "behavior", + }, + { + name: "autoplay", + title: "Autoplay slides", + description: + "Automatically advance to the next slide. Independent of per-video autoplay.", + type: "boolean", + initialValue: false, + fieldset: "behavior", + }, + { + name: "autoplayDelayMs", + title: "Autoplay delay (ms)", + description: "Time between slide changes when autoplay is enabled.", + type: "number", + initialValue: 5000, + hidden: ({ parent }) => parent?.autoplay !== true, + validation: (rule) => rule.min(1000).integer(), + fieldset: "behavior", + }, ], preview: { select: { diff --git a/web/package.json b/web/package.json index fd15180..750dee1 100644 --- a/web/package.json +++ b/web/package.json @@ -19,6 +19,8 @@ "@sanity/client": "^7.22.0", "@sanity/image-url": "^2.1.1", "clsx": "^2.1.1", + "embla-carousel-autoplay": "^8.6.0", + "embla-carousel-react": "^8.6.0", "hls.js": "^1.6.16", "next": "16.2.6", "next-sanity": "^13.0.4", diff --git a/web/sanity/queries/components/modules/carousel.ts b/web/sanity/queries/components/modules/carousel.ts index 01d9b33..8fabcb2 100644 --- a/web/sanity/queries/components/modules/carousel.ts +++ b/web/sanity/queries/components/modules/carousel.ts @@ -8,6 +8,11 @@ import { moduleMediaInnerFields, moduleMediaResolvedMediaQuery } from "./media"; export const moduleCarouselInnerFields = ` heading, imagesOnly, + loop, + showThumbnails, + showNavDots, + autoplay, + autoplayDelayMs, "slides": slides[]{ _key, _type, diff --git a/web/sanity/sanity.types.gen.ts b/web/sanity/sanity.types.gen.ts index d26185d..92c56e5 100644 --- a/web/sanity/sanity.types.gen.ts +++ b/web/sanity/sanity.types.gen.ts @@ -147,6 +147,11 @@ export type ModuleCarousel = { _key: string; } & ModuleMedia >; + loop?: boolean; + showThumbnails?: boolean; + showNavDots?: boolean; + autoplay?: boolean; + autoplayDelayMs?: number; }; export type MediaVideo = { @@ -702,7 +707,7 @@ export type AllSanitySchemaTypes = // Source: sanity/queries/pages/home.ts // Variable: homeQuery -// Query: *[_type == "home" && language == $locale][0]{ _id, title, language, modules[]{ _key, _type, _type == "module.text" => { title, body[]{ ..., _type == "block" => { ..., markDefs[]{ ..., _type == "link" => { ..., type == "internal" => { "linkType": "linkInternal", "title": coalesce(title, reference->title), "route": select( reference->_type == "home" => "page", reference->_type == "page" => "slug", "page" ), "slug": reference->slug.current, "resolvedReference": reference->{ _id, _type, title, "slug": slug.current } }, type == "external" => { ..., "linkType": "linkExternal", "href": url, "title": coalesce(title, url), blank }, type == "function" => { ..., "linkType": "linkFunction", "func": func { key, params } } } } } , _type == "module.media" => { type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) }, _type == "module.carousel" => { heading, imagesOnly, "slides": slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "slidesMedia": slidesMedia[]{ _key, _type, type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) }, "resolvedSlides": select( imagesOnly == true => slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, slidesMedia[]{ _key, _type, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) } ) }, _type == "module.contentRefs" => { heading, allowMultiple, "reference": reference->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) }, "references": references[]->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) } }, _type == "module.text" => { title, body[]{ ..., _type == "block" => { ..., markDefs[]{ ..., _type == "link" => { ..., type == "internal" => { "linkType": "linkInternal", "title": coalesce(title, reference->title), "route": select( reference->_type == "home" => "page", reference->_type == "page" => "slug", "page" ), "slug": reference->slug.current, "resolvedReference": reference->{ _id, _type, title, "slug": slug.current } }, type == "external" => { ..., "linkType": "linkExternal", "href": url, "title": coalesce(title, url), blank }, type == "function" => { ..., "linkType": "linkFunction", "func": func { key, params } } } } } , _type == "module.media" => { type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) }, _type == "module.carousel" => { heading, imagesOnly, "slides": slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "slidesMedia": slidesMedia[]{ _key, _type, type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) }, "resolvedSlides": select( imagesOnly == true => slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, slidesMedia[]{ _key, _type, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) } ) }, _type == "module.contentRefs" => { heading, allowMultiple, "reference": reference->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) }, "references": references[]->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) } }, _type == "module.text" => { title, body[]{ ..., _type == "block" => { ..., markDefs[]{ ..., _type == "link" => { ..., type == "internal" => { "linkType": "linkInternal", "title": coalesce(title, reference->title), "route": select( reference->_type == "home" => "page", reference->_type == "page" => "slug", "page" ), "slug": reference->slug.current, "resolvedReference": reference->{ _id, _type, title, "slug": slug.current } }, type == "external" => { ..., "linkType": "linkExternal", "href": url, "title": coalesce(title, url), blank }, type == "function" => { ..., "linkType": "linkFunction", "func": func { key, params } } } } } } } } } }}, _type == "module.media" => { type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } )}, _type == "module.carousel" => { heading, imagesOnly, "slides": slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "slidesMedia": slidesMedia[]{ _key, _type, type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) }, "resolvedSlides": select( imagesOnly == true => slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, slidesMedia[]{ _key, _type, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) } )}, _type == "module.contentRefs" => { heading, allowMultiple, "reference": reference->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) }, "references": references[]->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) }}}, seo { title, description, "imageUrl": image.asset->url}} +// Query: *[_type == "home" && language == $locale][0]{ _id, title, language, modules[]{ _key, _type, _type == "module.text" => { title, body[]{ ..., _type == "block" => { ..., markDefs[]{ ..., _type == "link" => { ..., type == "internal" => { "linkType": "linkInternal", "title": coalesce(title, reference->title), "route": select( reference->_type == "home" => "page", reference->_type == "page" => "slug", "page" ), "slug": reference->slug.current, "resolvedReference": reference->{ _id, _type, title, "slug": slug.current } }, type == "external" => { ..., "linkType": "linkExternal", "href": url, "title": coalesce(title, url), blank }, type == "function" => { ..., "linkType": "linkFunction", "func": func { key, params } } } } } , _type == "module.media" => { type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) }, _type == "module.carousel" => { heading, imagesOnly, loop, showThumbnails, showNavDots, autoplay, autoplayDelayMs, "slides": slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "slidesMedia": slidesMedia[]{ _key, _type, type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) }, "resolvedSlides": select( imagesOnly == true => slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, slidesMedia[]{ _key, _type, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) } ) }, _type == "module.contentRefs" => { heading, allowMultiple, "reference": reference->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) }, "references": references[]->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) } }, _type == "module.text" => { title, body[]{ ..., _type == "block" => { ..., markDefs[]{ ..., _type == "link" => { ..., type == "internal" => { "linkType": "linkInternal", "title": coalesce(title, reference->title), "route": select( reference->_type == "home" => "page", reference->_type == "page" => "slug", "page" ), "slug": reference->slug.current, "resolvedReference": reference->{ _id, _type, title, "slug": slug.current } }, type == "external" => { ..., "linkType": "linkExternal", "href": url, "title": coalesce(title, url), blank }, type == "function" => { ..., "linkType": "linkFunction", "func": func { key, params } } } } } , _type == "module.media" => { type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) }, _type == "module.carousel" => { heading, imagesOnly, loop, showThumbnails, showNavDots, autoplay, autoplayDelayMs, "slides": slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "slidesMedia": slidesMedia[]{ _key, _type, type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) }, "resolvedSlides": select( imagesOnly == true => slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, slidesMedia[]{ _key, _type, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) } ) }, _type == "module.contentRefs" => { heading, allowMultiple, "reference": reference->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) }, "references": references[]->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) } }, _type == "module.text" => { title, body[]{ ..., _type == "block" => { ..., markDefs[]{ ..., _type == "link" => { ..., type == "internal" => { "linkType": "linkInternal", "title": coalesce(title, reference->title), "route": select( reference->_type == "home" => "page", reference->_type == "page" => "slug", "page" ), "slug": reference->slug.current, "resolvedReference": reference->{ _id, _type, title, "slug": slug.current } }, type == "external" => { ..., "linkType": "linkExternal", "href": url, "title": coalesce(title, url), blank }, type == "function" => { ..., "linkType": "linkFunction", "func": func { key, params } } } } } } } } } }}, _type == "module.media" => { type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } )}, _type == "module.carousel" => { heading, imagesOnly, loop, showThumbnails, showNavDots, autoplay, autoplayDelayMs, "slides": slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "slidesMedia": slidesMedia[]{ _key, _type, type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) }, "resolvedSlides": select( imagesOnly == true => slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, slidesMedia[]{ _key, _type, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) } )}, _type == "module.contentRefs" => { heading, allowMultiple, "reference": reference->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) }, "references": references[]->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) }}}, seo { title, description, "imageUrl": image.asset->url}} export type HomeQueryResult = { _id: string; title: string; @@ -713,6 +718,11 @@ export type HomeQueryResult = { _type: "module.carousel"; heading: string | null; imagesOnly: boolean | null; + loop: boolean | null; + showThumbnails: boolean | null; + showNavDots: boolean | null; + autoplay: boolean | null; + autoplayDelayMs: number | null; slides: Array<{ _key: string; _type: "image"; @@ -1491,6 +1501,11 @@ export type HomeQueryResult = { } | null; }; }> | null; + loop: boolean | null; + showThumbnails: boolean | null; + showNavDots: boolean | null; + autoplay: boolean | null; + autoplayDelayMs: number | null; resolvedSlides: | Array<{ _key: string; @@ -1735,7 +1750,7 @@ export type HomeQueryResult = { // Source: sanity/queries/pages/page.ts // Variable: pageBySlugQuery -// Query: *[_type == "page" && slug.current == $slug && language == $locale][0]{ _id, title, slug, language, modules[]{ _key, _type, _type == "module.text" => { title, body[]{ ..., _type == "block" => { ..., markDefs[]{ ..., _type == "link" => { ..., type == "internal" => { "linkType": "linkInternal", "title": coalesce(title, reference->title), "route": select( reference->_type == "home" => "page", reference->_type == "page" => "slug", "page" ), "slug": reference->slug.current, "resolvedReference": reference->{ _id, _type, title, "slug": slug.current } }, type == "external" => { ..., "linkType": "linkExternal", "href": url, "title": coalesce(title, url), blank }, type == "function" => { ..., "linkType": "linkFunction", "func": func { key, params } } } } } , _type == "module.media" => { type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) }, _type == "module.carousel" => { heading, imagesOnly, "slides": slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "slidesMedia": slidesMedia[]{ _key, _type, type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) }, "resolvedSlides": select( imagesOnly == true => slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, slidesMedia[]{ _key, _type, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) } ) }, _type == "module.contentRefs" => { heading, allowMultiple, "reference": reference->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) }, "references": references[]->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) } }, _type == "module.text" => { title, body[]{ ..., _type == "block" => { ..., markDefs[]{ ..., _type == "link" => { ..., type == "internal" => { "linkType": "linkInternal", "title": coalesce(title, reference->title), "route": select( reference->_type == "home" => "page", reference->_type == "page" => "slug", "page" ), "slug": reference->slug.current, "resolvedReference": reference->{ _id, _type, title, "slug": slug.current } }, type == "external" => { ..., "linkType": "linkExternal", "href": url, "title": coalesce(title, url), blank }, type == "function" => { ..., "linkType": "linkFunction", "func": func { key, params } } } } } , _type == "module.media" => { type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) }, _type == "module.carousel" => { heading, imagesOnly, "slides": slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "slidesMedia": slidesMedia[]{ _key, _type, type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) }, "resolvedSlides": select( imagesOnly == true => slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, slidesMedia[]{ _key, _type, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) } ) }, _type == "module.contentRefs" => { heading, allowMultiple, "reference": reference->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) }, "references": references[]->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) } }, _type == "module.text" => { title, body[]{ ..., _type == "block" => { ..., markDefs[]{ ..., _type == "link" => { ..., type == "internal" => { "linkType": "linkInternal", "title": coalesce(title, reference->title), "route": select( reference->_type == "home" => "page", reference->_type == "page" => "slug", "page" ), "slug": reference->slug.current, "resolvedReference": reference->{ _id, _type, title, "slug": slug.current } }, type == "external" => { ..., "linkType": "linkExternal", "href": url, "title": coalesce(title, url), blank }, type == "function" => { ..., "linkType": "linkFunction", "func": func { key, params } } } } } } } } } }}, _type == "module.media" => { type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } )}, _type == "module.carousel" => { heading, imagesOnly, "slides": slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "slidesMedia": slidesMedia[]{ _key, _type, type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) }, "resolvedSlides": select( imagesOnly == true => slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, slidesMedia[]{ _key, _type, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) } )}, _type == "module.contentRefs" => { heading, allowMultiple, "reference": reference->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) }, "references": references[]->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) }}}, seo { title, description, "imageUrl": image.asset->url}} +// Query: *[_type == "page" && slug.current == $slug && language == $locale][0]{ _id, title, slug, language, modules[]{ _key, _type, _type == "module.text" => { title, body[]{ ..., _type == "block" => { ..., markDefs[]{ ..., _type == "link" => { ..., type == "internal" => { "linkType": "linkInternal", "title": coalesce(title, reference->title), "route": select( reference->_type == "home" => "page", reference->_type == "page" => "slug", "page" ), "slug": reference->slug.current, "resolvedReference": reference->{ _id, _type, title, "slug": slug.current } }, type == "external" => { ..., "linkType": "linkExternal", "href": url, "title": coalesce(title, url), blank }, type == "function" => { ..., "linkType": "linkFunction", "func": func { key, params } } } } } , _type == "module.media" => { type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) }, _type == "module.carousel" => { heading, imagesOnly, loop, showThumbnails, showNavDots, autoplay, autoplayDelayMs, "slides": slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "slidesMedia": slidesMedia[]{ _key, _type, type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) }, "resolvedSlides": select( imagesOnly == true => slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, slidesMedia[]{ _key, _type, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) } ) }, _type == "module.contentRefs" => { heading, allowMultiple, "reference": reference->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) }, "references": references[]->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) } }, _type == "module.text" => { title, body[]{ ..., _type == "block" => { ..., markDefs[]{ ..., _type == "link" => { ..., type == "internal" => { "linkType": "linkInternal", "title": coalesce(title, reference->title), "route": select( reference->_type == "home" => "page", reference->_type == "page" => "slug", "page" ), "slug": reference->slug.current, "resolvedReference": reference->{ _id, _type, title, "slug": slug.current } }, type == "external" => { ..., "linkType": "linkExternal", "href": url, "title": coalesce(title, url), blank }, type == "function" => { ..., "linkType": "linkFunction", "func": func { key, params } } } } } , _type == "module.media" => { type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) }, _type == "module.carousel" => { heading, imagesOnly, loop, showThumbnails, showNavDots, autoplay, autoplayDelayMs, "slides": slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "slidesMedia": slidesMedia[]{ _key, _type, type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) }, "resolvedSlides": select( imagesOnly == true => slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, slidesMedia[]{ _key, _type, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) } ) }, _type == "module.contentRefs" => { heading, allowMultiple, "reference": reference->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) }, "references": references[]->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) } }, _type == "module.text" => { title, body[]{ ..., _type == "block" => { ..., markDefs[]{ ..., _type == "link" => { ..., type == "internal" => { "linkType": "linkInternal", "title": coalesce(title, reference->title), "route": select( reference->_type == "home" => "page", reference->_type == "page" => "slug", "page" ), "slug": reference->slug.current, "resolvedReference": reference->{ _id, _type, title, "slug": slug.current } }, type == "external" => { ..., "linkType": "linkExternal", "href": url, "title": coalesce(title, url), blank }, type == "function" => { ..., "linkType": "linkFunction", "func": func { key, params } } } } } } } } } }}, _type == "module.media" => { type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } )}, _type == "module.carousel" => { heading, imagesOnly, loop, showThumbnails, showNavDots, autoplay, autoplayDelayMs, "slides": slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "slidesMedia": slidesMedia[]{ _key, _type, type, imageContent{ caption, "media": image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } }, videoContent{ caption, videoSettings, "media": video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) }, "resolvedSlides": select( imagesOnly == true => slides[]{ _key, _type, "media": select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, slidesMedia[]{ _key, _type, "resolvedMedia": select( type == "video" => { "kind": "video", "caption": videoContent.caption, "videoSettings": videoContent.videoSettings, "media": videoContent.video{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) }, "poster": videoContent.poster{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }, { "kind": "image", "caption": imageContent.caption, "media": imageContent.image{ ...select( defined(asset->playbackId) || defined(asset->data.playbackId) => { "kind": "video", ...{ "playbackId": coalesce( asset->playbackId, asset->data.playbackId, asset->data.playback_ids[0].id ), "duration": asset->data.duration, "asset": asset->{ playbackId, data }} }, { "kind": "image", ...{ crop, hotspot, "alt": asset->altText, "asset": asset->{ _id, url, metadata{ dimensions{ width, height, aspectRatio }, lqip } }} }) } } ) } )}, _type == "module.contentRefs" => { heading, allowMultiple, "reference": reference->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) }, "references": references[]->{ _id, _type, title, "slug": slug.current, "route": select( _type == "home" => "index", _type == "page" => "slug", "index" ) }}}, seo { title, description, "imageUrl": image.asset->url}} export type PageBySlugQueryResult = { _id: string; title: string; @@ -1747,6 +1762,11 @@ export type PageBySlugQueryResult = { _type: "module.carousel"; heading: string | null; imagesOnly: boolean | null; + loop: boolean | null; + showThumbnails: boolean | null; + showNavDots: boolean | null; + autoplay: boolean | null; + autoplayDelayMs: number | null; slides: Array<{ _key: string; _type: "image"; @@ -2525,6 +2545,11 @@ export type PageBySlugQueryResult = { } | null; }; }> | null; + loop: boolean | null; + showThumbnails: boolean | null; + showNavDots: boolean | null; + autoplay: boolean | null; + autoplayDelayMs: number | null; resolvedSlides: | Array<{ _key: string; @@ -2836,8 +2861,8 @@ export type SitemapPagesQueryResult = Array< import "@sanity/client"; declare module "@sanity/client" { interface SanityQueries { - '*[_type == "home" && language == $locale][0]{\n _id,\n title,\n language,\n modules[]{\n _key,\n _type,\n _type == "module.text" => {\n title,\n body[]{\n \n \n ...,\n _type == "block" => {\n ...,\n markDefs[]{\n ...,\n _type == "link" => {\n \n ...,\n type == "internal" => {\n "linkType": "linkInternal",\n "title": coalesce(title, reference->title),\n "route": select(\n reference->_type == "home" => "page",\n reference->_type == "page" => "slug",\n "page"\n ),\n "slug": reference->slug.current,\n "resolvedReference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current\n }\n },\n type == "external" => {\n ...,\n "linkType": "linkExternal",\n "href": url,\n "title": coalesce(title, url),\n blank\n },\n type == "function" => {\n ...,\n "linkType": "linkFunction",\n "func": func {\n key,\n params\n }\n }\n\n }\n }\n }\n ,\n _type == "module.media" => {\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n },\n _type == "module.carousel" => {\n \n heading,\n imagesOnly,\n "slides": slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n "slidesMedia": slidesMedia[]{\n _key,\n _type,\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n },\n "resolvedSlides": select(\n imagesOnly == true => slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n slidesMedia[]{\n _key,\n _type,\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n }\n )\n\n },\n _type == "module.contentRefs" => {\n \n heading,\n allowMultiple,\n "reference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n },\n "references": references[]->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n }\n\n },\n _type == "module.text" => {\n title,\n body[]{\n \n ...,\n _type == "block" => {\n ...,\n markDefs[]{\n ...,\n _type == "link" => {\n \n ...,\n type == "internal" => {\n "linkType": "linkInternal",\n "title": coalesce(title, reference->title),\n "route": select(\n reference->_type == "home" => "page",\n reference->_type == "page" => "slug",\n "page"\n ),\n "slug": reference->slug.current,\n "resolvedReference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current\n }\n },\n type == "external" => {\n ...,\n "linkType": "linkExternal",\n "href": url,\n "title": coalesce(title, url),\n blank\n },\n type == "function" => {\n ...,\n "linkType": "linkFunction",\n "func": func {\n key,\n params\n }\n }\n\n }\n }\n }\n ,\n _type == "module.media" => {\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n },\n _type == "module.carousel" => {\n \n heading,\n imagesOnly,\n "slides": slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n "slidesMedia": slidesMedia[]{\n _key,\n _type,\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n },\n "resolvedSlides": select(\n imagesOnly == true => slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n slidesMedia[]{\n _key,\n _type,\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n }\n )\n\n },\n _type == "module.contentRefs" => {\n \n heading,\n allowMultiple,\n "reference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n },\n "references": references[]->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n }\n\n },\n _type == "module.text" => {\n title,\n body[]{\n \n ...,\n _type == "block" => {\n ...,\n markDefs[]{\n ...,\n _type == "link" => {\n \n ...,\n type == "internal" => {\n "linkType": "linkInternal",\n "title": coalesce(title, reference->title),\n "route": select(\n reference->_type == "home" => "page",\n reference->_type == "page" => "slug",\n "page"\n ),\n "slug": reference->slug.current,\n "resolvedReference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current\n }\n },\n type == "external" => {\n ...,\n "linkType": "linkExternal",\n "href": url,\n "title": coalesce(title, url),\n blank\n },\n type == "function" => {\n ...,\n "linkType": "linkFunction",\n "func": func {\n key,\n params\n }\n }\n\n }\n }\n }\n \n }\n }\n }\n }\n \n }\n},\n _type == "module.media" => {\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n},\n _type == "module.carousel" => {\n \n heading,\n imagesOnly,\n "slides": slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n "slidesMedia": slidesMedia[]{\n _key,\n _type,\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n },\n "resolvedSlides": select(\n imagesOnly == true => slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n slidesMedia[]{\n _key,\n _type,\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n }\n )\n\n},\n _type == "module.contentRefs" => {\n \n heading,\n allowMultiple,\n "reference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n },\n "references": references[]->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n }\n\n}\n},\n seo {\n title,\n description,\n "imageUrl": image.asset->url\n}\n}': HomeQueryResult; - '*[_type == "page" && slug.current == $slug && language == $locale][0]{\n _id,\n title,\n slug,\n language,\n modules[]{\n _key,\n _type,\n _type == "module.text" => {\n title,\n body[]{\n \n \n ...,\n _type == "block" => {\n ...,\n markDefs[]{\n ...,\n _type == "link" => {\n \n ...,\n type == "internal" => {\n "linkType": "linkInternal",\n "title": coalesce(title, reference->title),\n "route": select(\n reference->_type == "home" => "page",\n reference->_type == "page" => "slug",\n "page"\n ),\n "slug": reference->slug.current,\n "resolvedReference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current\n }\n },\n type == "external" => {\n ...,\n "linkType": "linkExternal",\n "href": url,\n "title": coalesce(title, url),\n blank\n },\n type == "function" => {\n ...,\n "linkType": "linkFunction",\n "func": func {\n key,\n params\n }\n }\n\n }\n }\n }\n ,\n _type == "module.media" => {\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n },\n _type == "module.carousel" => {\n \n heading,\n imagesOnly,\n "slides": slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n "slidesMedia": slidesMedia[]{\n _key,\n _type,\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n },\n "resolvedSlides": select(\n imagesOnly == true => slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n slidesMedia[]{\n _key,\n _type,\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n }\n )\n\n },\n _type == "module.contentRefs" => {\n \n heading,\n allowMultiple,\n "reference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n },\n "references": references[]->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n }\n\n },\n _type == "module.text" => {\n title,\n body[]{\n \n ...,\n _type == "block" => {\n ...,\n markDefs[]{\n ...,\n _type == "link" => {\n \n ...,\n type == "internal" => {\n "linkType": "linkInternal",\n "title": coalesce(title, reference->title),\n "route": select(\n reference->_type == "home" => "page",\n reference->_type == "page" => "slug",\n "page"\n ),\n "slug": reference->slug.current,\n "resolvedReference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current\n }\n },\n type == "external" => {\n ...,\n "linkType": "linkExternal",\n "href": url,\n "title": coalesce(title, url),\n blank\n },\n type == "function" => {\n ...,\n "linkType": "linkFunction",\n "func": func {\n key,\n params\n }\n }\n\n }\n }\n }\n ,\n _type == "module.media" => {\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n },\n _type == "module.carousel" => {\n \n heading,\n imagesOnly,\n "slides": slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n "slidesMedia": slidesMedia[]{\n _key,\n _type,\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n },\n "resolvedSlides": select(\n imagesOnly == true => slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n slidesMedia[]{\n _key,\n _type,\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n }\n )\n\n },\n _type == "module.contentRefs" => {\n \n heading,\n allowMultiple,\n "reference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n },\n "references": references[]->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n }\n\n },\n _type == "module.text" => {\n title,\n body[]{\n \n ...,\n _type == "block" => {\n ...,\n markDefs[]{\n ...,\n _type == "link" => {\n \n ...,\n type == "internal" => {\n "linkType": "linkInternal",\n "title": coalesce(title, reference->title),\n "route": select(\n reference->_type == "home" => "page",\n reference->_type == "page" => "slug",\n "page"\n ),\n "slug": reference->slug.current,\n "resolvedReference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current\n }\n },\n type == "external" => {\n ...,\n "linkType": "linkExternal",\n "href": url,\n "title": coalesce(title, url),\n blank\n },\n type == "function" => {\n ...,\n "linkType": "linkFunction",\n "func": func {\n key,\n params\n }\n }\n\n }\n }\n }\n \n }\n }\n }\n }\n \n }\n},\n _type == "module.media" => {\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n},\n _type == "module.carousel" => {\n \n heading,\n imagesOnly,\n "slides": slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n "slidesMedia": slidesMedia[]{\n _key,\n _type,\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n },\n "resolvedSlides": select(\n imagesOnly == true => slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n slidesMedia[]{\n _key,\n _type,\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n }\n )\n\n},\n _type == "module.contentRefs" => {\n \n heading,\n allowMultiple,\n "reference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n },\n "references": references[]->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n }\n\n}\n},\n seo {\n title,\n description,\n "imageUrl": image.asset->url\n}\n}': PageBySlugQueryResult; + '*[_type == "home" && language == $locale][0]{\n _id,\n title,\n language,\n modules[]{\n _key,\n _type,\n _type == "module.text" => {\n title,\n body[]{\n \n \n ...,\n _type == "block" => {\n ...,\n markDefs[]{\n ...,\n _type == "link" => {\n \n ...,\n type == "internal" => {\n "linkType": "linkInternal",\n "title": coalesce(title, reference->title),\n "route": select(\n reference->_type == "home" => "page",\n reference->_type == "page" => "slug",\n "page"\n ),\n "slug": reference->slug.current,\n "resolvedReference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current\n }\n },\n type == "external" => {\n ...,\n "linkType": "linkExternal",\n "href": url,\n "title": coalesce(title, url),\n blank\n },\n type == "function" => {\n ...,\n "linkType": "linkFunction",\n "func": func {\n key,\n params\n }\n }\n\n }\n }\n }\n ,\n _type == "module.media" => {\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n },\n _type == "module.carousel" => {\n \n heading,\n imagesOnly,\n loop,\n showThumbnails,\n showNavDots,\n autoplay,\n autoplayDelayMs,\n "slides": slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n "slidesMedia": slidesMedia[]{\n _key,\n _type,\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n },\n "resolvedSlides": select(\n imagesOnly == true => slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n slidesMedia[]{\n _key,\n _type,\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n }\n )\n\n },\n _type == "module.contentRefs" => {\n \n heading,\n allowMultiple,\n "reference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n },\n "references": references[]->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n }\n\n },\n _type == "module.text" => {\n title,\n body[]{\n \n ...,\n _type == "block" => {\n ...,\n markDefs[]{\n ...,\n _type == "link" => {\n \n ...,\n type == "internal" => {\n "linkType": "linkInternal",\n "title": coalesce(title, reference->title),\n "route": select(\n reference->_type == "home" => "page",\n reference->_type == "page" => "slug",\n "page"\n ),\n "slug": reference->slug.current,\n "resolvedReference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current\n }\n },\n type == "external" => {\n ...,\n "linkType": "linkExternal",\n "href": url,\n "title": coalesce(title, url),\n blank\n },\n type == "function" => {\n ...,\n "linkType": "linkFunction",\n "func": func {\n key,\n params\n }\n }\n\n }\n }\n }\n ,\n _type == "module.media" => {\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n },\n _type == "module.carousel" => {\n \n heading,\n imagesOnly,\n loop,\n showThumbnails,\n showNavDots,\n autoplay,\n autoplayDelayMs,\n "slides": slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n "slidesMedia": slidesMedia[]{\n _key,\n _type,\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n },\n "resolvedSlides": select(\n imagesOnly == true => slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n slidesMedia[]{\n _key,\n _type,\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n }\n )\n\n },\n _type == "module.contentRefs" => {\n \n heading,\n allowMultiple,\n "reference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n },\n "references": references[]->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n }\n\n },\n _type == "module.text" => {\n title,\n body[]{\n \n ...,\n _type == "block" => {\n ...,\n markDefs[]{\n ...,\n _type == "link" => {\n \n ...,\n type == "internal" => {\n "linkType": "linkInternal",\n "title": coalesce(title, reference->title),\n "route": select(\n reference->_type == "home" => "page",\n reference->_type == "page" => "slug",\n "page"\n ),\n "slug": reference->slug.current,\n "resolvedReference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current\n }\n },\n type == "external" => {\n ...,\n "linkType": "linkExternal",\n "href": url,\n "title": coalesce(title, url),\n blank\n },\n type == "function" => {\n ...,\n "linkType": "linkFunction",\n "func": func {\n key,\n params\n }\n }\n\n }\n }\n }\n \n }\n }\n }\n }\n \n }\n},\n _type == "module.media" => {\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n},\n _type == "module.carousel" => {\n \n heading,\n imagesOnly,\n loop,\n showThumbnails,\n showNavDots,\n autoplay,\n autoplayDelayMs,\n "slides": slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n "slidesMedia": slidesMedia[]{\n _key,\n _type,\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n },\n "resolvedSlides": select(\n imagesOnly == true => slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n slidesMedia[]{\n _key,\n _type,\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n }\n )\n\n},\n _type == "module.contentRefs" => {\n \n heading,\n allowMultiple,\n "reference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n },\n "references": references[]->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n }\n\n}\n},\n seo {\n title,\n description,\n "imageUrl": image.asset->url\n}\n}': HomeQueryResult; + '*[_type == "page" && slug.current == $slug && language == $locale][0]{\n _id,\n title,\n slug,\n language,\n modules[]{\n _key,\n _type,\n _type == "module.text" => {\n title,\n body[]{\n \n \n ...,\n _type == "block" => {\n ...,\n markDefs[]{\n ...,\n _type == "link" => {\n \n ...,\n type == "internal" => {\n "linkType": "linkInternal",\n "title": coalesce(title, reference->title),\n "route": select(\n reference->_type == "home" => "page",\n reference->_type == "page" => "slug",\n "page"\n ),\n "slug": reference->slug.current,\n "resolvedReference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current\n }\n },\n type == "external" => {\n ...,\n "linkType": "linkExternal",\n "href": url,\n "title": coalesce(title, url),\n blank\n },\n type == "function" => {\n ...,\n "linkType": "linkFunction",\n "func": func {\n key,\n params\n }\n }\n\n }\n }\n }\n ,\n _type == "module.media" => {\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n },\n _type == "module.carousel" => {\n \n heading,\n imagesOnly,\n loop,\n showThumbnails,\n showNavDots,\n autoplay,\n autoplayDelayMs,\n "slides": slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n "slidesMedia": slidesMedia[]{\n _key,\n _type,\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n },\n "resolvedSlides": select(\n imagesOnly == true => slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n slidesMedia[]{\n _key,\n _type,\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n }\n )\n\n },\n _type == "module.contentRefs" => {\n \n heading,\n allowMultiple,\n "reference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n },\n "references": references[]->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n }\n\n },\n _type == "module.text" => {\n title,\n body[]{\n \n ...,\n _type == "block" => {\n ...,\n markDefs[]{\n ...,\n _type == "link" => {\n \n ...,\n type == "internal" => {\n "linkType": "linkInternal",\n "title": coalesce(title, reference->title),\n "route": select(\n reference->_type == "home" => "page",\n reference->_type == "page" => "slug",\n "page"\n ),\n "slug": reference->slug.current,\n "resolvedReference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current\n }\n },\n type == "external" => {\n ...,\n "linkType": "linkExternal",\n "href": url,\n "title": coalesce(title, url),\n blank\n },\n type == "function" => {\n ...,\n "linkType": "linkFunction",\n "func": func {\n key,\n params\n }\n }\n\n }\n }\n }\n ,\n _type == "module.media" => {\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n },\n _type == "module.carousel" => {\n \n heading,\n imagesOnly,\n loop,\n showThumbnails,\n showNavDots,\n autoplay,\n autoplayDelayMs,\n "slides": slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n "slidesMedia": slidesMedia[]{\n _key,\n _type,\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n },\n "resolvedSlides": select(\n imagesOnly == true => slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n slidesMedia[]{\n _key,\n _type,\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n }\n )\n\n },\n _type == "module.contentRefs" => {\n \n heading,\n allowMultiple,\n "reference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n },\n "references": references[]->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n }\n\n },\n _type == "module.text" => {\n title,\n body[]{\n \n ...,\n _type == "block" => {\n ...,\n markDefs[]{\n ...,\n _type == "link" => {\n \n ...,\n type == "internal" => {\n "linkType": "linkInternal",\n "title": coalesce(title, reference->title),\n "route": select(\n reference->_type == "home" => "page",\n reference->_type == "page" => "slug",\n "page"\n ),\n "slug": reference->slug.current,\n "resolvedReference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current\n }\n },\n type == "external" => {\n ...,\n "linkType": "linkExternal",\n "href": url,\n "title": coalesce(title, url),\n blank\n },\n type == "function" => {\n ...,\n "linkType": "linkFunction",\n "func": func {\n key,\n params\n }\n }\n\n }\n }\n }\n \n }\n }\n }\n }\n \n }\n},\n _type == "module.media" => {\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n},\n _type == "module.carousel" => {\n \n heading,\n imagesOnly,\n loop,\n showThumbnails,\n showNavDots,\n autoplay,\n autoplayDelayMs,\n "slides": slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n "slidesMedia": slidesMedia[]{\n _key,\n _type,\n \n type,\n imageContent{\n caption,\n "media": image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n },\n videoContent{\n caption,\n videoSettings,\n "media": video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n\n },\n "resolvedSlides": select(\n imagesOnly == true => slides[]{\n _key,\n _type,\n "media": select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n)\n },\n slidesMedia[]{\n _key,\n _type,\n "resolvedMedia": \n select(\n type == "video" => {\n "kind": "video",\n "caption": videoContent.caption,\n "videoSettings": videoContent.videoSettings,\n "media": videoContent.video{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) },\n "poster": videoContent.poster{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n },\n {\n "kind": "image",\n "caption": imageContent.caption,\n "media": imageContent.image{ ...select(\n defined(asset->playbackId) || defined(asset->data.playbackId) => {\n "kind": "video",\n ...{\n "playbackId": coalesce(\n asset->playbackId,\n asset->data.playbackId,\n asset->data.playback_ids[0].id\n ),\n "duration": asset->data.duration,\n "asset": asset->{\n playbackId,\n data\n }\n}\n },\n {\n "kind": "image",\n ...{\n crop,\n hotspot,\n "alt": asset->altText,\n "asset": asset->{\n _id,\n url,\n metadata{\n dimensions{ width, height, aspectRatio },\n lqip\n }\n }\n}\n }\n) }\n }\n )\n\n }\n )\n\n},\n _type == "module.contentRefs" => {\n \n heading,\n allowMultiple,\n "reference": reference->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n },\n "references": references[]->{\n _id,\n _type,\n title,\n "slug": slug.current,\n "route": select(\n _type == "home" => "index",\n _type == "page" => "slug",\n "index"\n )\n }\n\n}\n},\n seo {\n title,\n description,\n "imageUrl": image.asset->url\n}\n}': PageBySlugQueryResult; '*[_id == "siteLanguageSettings"][0]{\n _id,\n availableLanguages[]{id, title},\n defaultLanguageId\n}': SiteLanguageSettingsQueryResult; '*[_type == "siteSettings" && language == $locale][0]{title}': SiteSettingsTitleQueryResult; '*[_type == "siteSettings" && language == $locale][0]{\n "title": seo.title,\n "description": seo.description,\n "imageUrl": seo.image.asset->url\n}': SiteSettingsSeoFallbackQueryResult; diff --git a/web/sanity/types/modules/carousel.ts b/web/sanity/types/modules/carousel.ts index 79559b8..6f5d6dc 100644 --- a/web/sanity/types/modules/carousel.ts +++ b/web/sanity/types/modules/carousel.ts @@ -5,6 +5,11 @@ export type ModuleCarouselData = { _key?: string; heading?: string | null; imagesOnly?: boolean | null; + loop?: boolean | null; + showThumbnails?: boolean | null; + showNavDots?: boolean | null; + autoplay?: boolean | null; + autoplayDelayMs?: number | null; /** Image slides: each slide has `media` from `mediaQuery` (kind + payload). */ slides?: Array<{ _key?: string; diff --git a/web/src/components/carousel/CarouselSlide.tsx b/web/src/components/carousel/CarouselSlide.tsx new file mode 100644 index 0000000..197afe8 --- /dev/null +++ b/web/src/components/carousel/CarouselSlide.tsx @@ -0,0 +1,88 @@ +"use client"; + +import { MediaImage, MediaVideo, MediaVideoLoop } from "@/src/components/media"; + +export type NormalizedSlide = { + key: string; + kind: "image" | "video"; + media: unknown; + poster?: unknown; + caption?: string | null; + videoSettings?: { + autoplay?: boolean | null; + controls?: boolean | null; + } | null; +}; + +type Props = { + slide: NormalizedSlide; + isActive: boolean; +}; + +/** `autoplay && !controls` → silent loop (no MuxPlayer chrome). Mirrors `ModuleMedia`. */ +function isLoopIntent(settings: NormalizedSlide["videoSettings"]): boolean { + return Boolean(settings?.autoplay) && settings?.controls === false; +} + +/** + * The shared media components apply `min-h-[100dvh]` when `fillParent` is set (designed + * for hero sections). Inside a fixed-aspect carousel viewport that would blow out the + * layout, so we override it back to `0`. + */ +const FIT_TO_PARENT = "!min-h-0"; + +/** + * Renders a single carousel slide. Inactive full-player videos render the poster only, + * so audio/decoder work is scoped to the active slide. + */ +export function CarouselSlide({ slide, isActive }: Props) { + if (slide.kind === "image") { + return ( + + ); + } + + if (isLoopIntent(slide.videoSettings)) { + return ( + + ); + } + + if (!isActive) { + return ( + + ); + } + + return ( + + ); +} diff --git a/web/src/components/carousel/CarouselViewport.tsx b/web/src/components/carousel/CarouselViewport.tsx new file mode 100644 index 0000000..9b2c041 --- /dev/null +++ b/web/src/components/carousel/CarouselViewport.tsx @@ -0,0 +1,267 @@ +"use client"; + +import clsx from "clsx"; +import Autoplay from "embla-carousel-autoplay"; +import useEmblaCarousel from "embla-carousel-react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; + +import { + resolveSanityImageFieldForUrl, + urlForFetchedImage, +} from "@/sanity/utils/sanityImageBuilder"; +import { + extractMuxPlaybackId, + muxThumbnailTimeSec, + muxThumbnailUrl, +} from "@/src/utils/muxPlayback"; + +import { CarouselSlide, type NormalizedSlide } from "./CarouselSlide"; + +type CarouselOptions = { + loop: boolean; + showThumbnails: boolean; + showNavDots: boolean; + autoplay: boolean; + autoplayDelayMs: number; +}; + +type Props = { + slides: NormalizedSlide[]; + options: CarouselOptions; +}; + +const THUMBNAIL_WIDTH_PX = 160; + +function thumbnailUrlForSlide(slide: NormalizedSlide): string | null { + const imageSource = slide.kind === "image" ? slide.media : slide.poster; + const img = resolveSanityImageFieldForUrl(imageSource); + if (img) { + return urlForFetchedImage(img, THUMBNAIL_WIDTH_PX); + } + if (slide.kind === "video") { + const playbackId = extractMuxPlaybackId(slide.media); + if (playbackId) { + return muxThumbnailUrl(playbackId, muxThumbnailTimeSec(slide.media), { + width: THUMBNAIL_WIDTH_PX, + }); + } + } + return null; +} + +/** + * Embla viewport with optional autoplay, prev/next, dots, and a synced thumbnail strip. + * Reduced-motion users get a still carousel: autoplay is suspended but interaction works. + */ +export function CarouselViewport({ slides, options }: Props) { + const [reducedMotion, setReducedMotion] = useState(false); + + useEffect(() => { + if (typeof window === "undefined" || !window.matchMedia) return; + const mq = window.matchMedia("(prefers-reduced-motion: reduce)"); + const sync = () => setReducedMotion(mq.matches); + sync(); + mq.addEventListener("change", sync); + return () => mq.removeEventListener("change", sync); + }, []); + + const autoplayPlugin = useRef( + options.autoplay + ? Autoplay({ + delay: Math.max(1000, options.autoplayDelayMs), + stopOnInteraction: true, + stopOnMouseEnter: true, + }) + : null, + ); + + const [emblaRef, emblaApi] = useEmblaCarousel( + { loop: options.loop, align: "start" }, + autoplayPlugin.current ? [autoplayPlugin.current] : [], + ); + const [thumbsRef, thumbsApi] = useEmblaCarousel({ + containScroll: "keepSnaps", + dragFree: true, + }); + + const [selectedIndex, setSelectedIndex] = useState(0); + const [snapCount, setSnapCount] = useState(0); + const [canScrollPrev, setCanScrollPrev] = useState(false); + const [canScrollNext, setCanScrollNext] = useState(false); + + const onSelect = useCallback(() => { + if (!emblaApi) return; + const idx = emblaApi.selectedScrollSnap(); + setSelectedIndex(idx); + setCanScrollPrev(emblaApi.canScrollPrev()); + setCanScrollNext(emblaApi.canScrollNext()); + thumbsApi?.scrollTo(idx); + }, [emblaApi, thumbsApi]); + + useEffect(() => { + if (!emblaApi) return; + setSnapCount(emblaApi.scrollSnapList().length); + onSelect(); + emblaApi.on("select", onSelect); + emblaApi.on("reInit", onSelect); + return () => { + emblaApi.off("select", onSelect); + emblaApi.off("reInit", onSelect); + }; + }, [emblaApi, onSelect]); + + useEffect(() => { + if (!options.autoplay) return; + const plugin = autoplayPlugin.current; + if (!plugin) return; + if (reducedMotion) { + plugin.stop(); + } + }, [reducedMotion, options.autoplay]); + + const scrollPrev = useCallback(() => emblaApi?.scrollPrev(), [emblaApi]); + const scrollNext = useCallback(() => emblaApi?.scrollNext(), [emblaApi]); + const scrollTo = useCallback( + (index: number) => emblaApi?.scrollTo(index), + [emblaApi], + ); + + const thumbnails = useMemo( + () => + options.showThumbnails + ? slides.map((slide) => thumbnailUrlForSlide(slide)) + : null, + [slides, options.showThumbnails], + ); + + if (slides.length === 0) return null; + + return ( +
+
+
+
+ {slides.map((slide, index) => ( +
+ +
+ ))} +
+
+ + {snapCount > 1 ? ( + <> + + + + ) : null} +
+ + {options.showNavDots && snapCount > 1 ? ( +
+ {slides.slice(0, snapCount).map((slide, index) => { + const isActive = index === selectedIndex; + return ( +
+ ) : null} + + {options.showThumbnails && thumbnails ? ( +
+
+ {slides.map((slide, index) => { + const url = thumbnails[index]; + const isActive = index === selectedIndex; + return ( + + ); + })} +
+
+ ) : null} +
+ ); +} diff --git a/web/src/components/carousel/ModuleCarousel.tsx b/web/src/components/carousel/ModuleCarousel.tsx new file mode 100644 index 0000000..d4940be --- /dev/null +++ b/web/src/components/carousel/ModuleCarousel.tsx @@ -0,0 +1,126 @@ +"use client"; + +import type { + ModuleCarouselData, + ModuleMediaData, + ResolvedMediaPayload, +} from "@/sanity/types/modules"; + +import type { NormalizedSlide } from "./CarouselSlide"; +import { CarouselViewport } from "./CarouselViewport"; + +type Props = { + module: ModuleCarouselData; +}; + +const DEFAULT_AUTOPLAY_DELAY_MS = 5000; + +function payloadKind( + payload: ResolvedMediaPayload | null | undefined, +): "image" | "video" | null { + if (!payload) return null; + if (payload.kind === "image" || payload.kind === "video") return payload.kind; + return null; +} + +/** Normalize the GROQ `resolvedSlides` into a single shape `CarouselSlide` understands. */ +function normalizeSlides(module: ModuleCarouselData): NormalizedSlide[] { + const imagesOnly = module.imagesOnly !== false; + + if (imagesOnly) { + const source = module.resolvedSlides ?? module.slides ?? []; + return source + .map((slide, index): NormalizedSlide | null => { + const media = slide.media ?? null; + const kind = payloadKind(media) ?? "image"; + if (!media) return null; + return { + key: slide._key ?? `slide-${index}`, + kind, + media, + }; + }) + .filter((s): s is NormalizedSlide => s !== null); + } + + const source = module.resolvedSlides; + if (source && source.length > 0) { + return source + .map((slide, index): NormalizedSlide | null => { + const resolved = slide.resolvedMedia; + if (!resolved) return null; + const kind = payloadKind(resolved.media) ?? resolved.kind ?? null; + if (!kind || !resolved.media) return null; + return { + key: slide._key ?? `slide-${index}`, + kind, + media: resolved.media, + poster: resolved.poster ?? undefined, + caption: resolved.caption ?? null, + videoSettings: resolved.videoSettings ?? null, + }; + }) + .filter((s): s is NormalizedSlide => s !== null); + } + + const fallback = module.slidesMedia ?? []; + return fallback + .map((slide: ModuleMediaData, index): NormalizedSlide | null => { + if (slide.type === "image") { + const imagePayload = + slide.imageContent?.media ?? slide.imageContent?.image ?? null; + if (!imagePayload) return null; + return { + key: slide._key ?? `slide-${index}`, + kind: "image", + media: imagePayload, + caption: slide.imageContent?.caption ?? null, + }; + } + if (slide.type === "video" && slide.videoContent) { + const muxField = + slide.videoContent.media ?? slide.videoContent.video ?? null; + if (!muxField) return null; + return { + key: slide._key ?? `slide-${index}`, + kind: "video", + media: muxField, + poster: slide.videoContent.poster ?? undefined, + caption: slide.videoContent.caption ?? null, + videoSettings: slide.videoContent.videoSettings ?? null, + }; + } + return null; + }) + .filter((s): s is NormalizedSlide => s !== null); +} + +/** + * `module.carousel` renderer. Behavior fields (`loop`, `showThumbnails`, `showNavDots`, + * `autoplay`, `autoplayDelayMs`) come from Sanity and drive the embla viewport. + */ +export function ModuleCarousel({ module }: Props) { + const slides = normalizeSlides(module); + if (slides.length === 0) return null; + + const heading = module.heading?.trim() ?? ""; + + return ( +
+ {heading ?

{heading}

: null} + +
+ ); +} diff --git a/web/src/components/carousel/index.ts b/web/src/components/carousel/index.ts new file mode 100644 index 0000000..6e9c058 --- /dev/null +++ b/web/src/components/carousel/index.ts @@ -0,0 +1 @@ +export { ModuleCarousel } from "./ModuleCarousel"; diff --git a/web/src/components/modules/ModulesRenderer.tsx b/web/src/components/modules/ModulesRenderer.tsx index b0a8eef..c4a5630 100644 --- a/web/src/components/modules/ModulesRenderer.tsx +++ b/web/src/components/modules/ModulesRenderer.tsx @@ -6,6 +6,7 @@ import type { ModuleTextData, } from "@/sanity/types/modules"; import { getSanityModuleLabel } from "@/sanity/utils/sanityModuleLabel"; +import { ModuleCarousel } from "@/src/components/carousel"; import { ModuleMedia } from "./ModuleMedia"; import { ModuleText } from "./ModuleText"; @@ -15,30 +16,6 @@ type Props = { const IS_DEV = process.env.NODE_ENV === "development"; -/** - * Carousel placeholder — schema + GROQ exist, no production renderer yet. - * Renders a labeled placeholder in dev only; nothing in production so empty - * Studio modules don't surface visible warnings to end users. - */ -function ModuleCarouselPlaceholder({ module }: { module: ModuleCarouselData }) { - if (!IS_DEV) return null; - const heading = module.heading?.trim() ?? ""; - const slideCount = - module.resolvedSlides?.length ?? - module.slidesMedia?.length ?? - module.slides?.length ?? - 0; - return ( -
- module.carousel - {heading ? heading: {heading} : null} - - slides: {slideCount} (no frontend renderer yet) - -
- ); -} - function ModuleContentRefsPlaceholder({ module, }: { @@ -94,10 +71,7 @@ export function ModulesRenderer({ modules }: Props) { } if (mod._type === "module.carousel") { return ( - + ); } if (mod._type === "module.contentRefs") { diff --git a/web/src/components/text/RichTextMedia.tsx b/web/src/components/text/RichTextMedia.tsx index 2a8a176..19c3242 100644 --- a/web/src/components/text/RichTextMedia.tsx +++ b/web/src/components/text/RichTextMedia.tsx @@ -3,7 +3,11 @@ import type { PortableTextBlock } from "@portabletext/types"; import clsx from "clsx"; import type { ReactNode } from "react"; -import type { ModuleMediaData } from "@/sanity/types/modules"; +import type { + ModuleCarouselData, + ModuleMediaData, +} from "@/sanity/types/modules"; +import { ModuleCarousel } from "@/src/components/carousel"; import { ModuleMedia } from "@/src/components/modules/ModuleMedia"; /** @@ -132,7 +136,11 @@ function portableTextComponents(): Partial { ), - "module.carousel": () => null, + "module.carousel": ({ value }) => ( +
+ +
+ ), }, }; } From fae67db4705654918913dc9fd8e0549333f908e3 Mon Sep 17 00:00:00 2001 From: Damian Date: Thu, 28 May 2026 18:42:02 +0200 Subject: [PATCH 09/45] fix(layout): defer img-loaded class to next animation frame to avoid hydration mismatch The boot script synchronously added `.img-loaded` to images already complete in the browser cache, racing React 19 streaming hydration and producing a "tree hydrated but attributes didn't match" warning on every page that ships a non-priority Sanity image. Wrapping the class mutation in `requestAnimationFrame` lets React reconcile the SSR markup first while still keeping the fade-in transition imperceptible. Co-authored-by: Cursor --- web/src/app/layout.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/web/src/app/layout.tsx b/web/src/app/layout.tsx index 70acc68..62f5e16 100644 --- a/web/src/app/layout.tsx +++ b/web/src/app/layout.tsx @@ -104,10 +104,15 @@ const bootScript = `(function(){ }catch(e){} document.documentElement.classList.add('js-enabled'); function handle(img){ - if(img.complete&&img.naturalWidth>0){img.classList.add('img-loaded');} + function add(){img.classList.add('img-loaded');} + /* Defer the className mutation past the current task so React 19 streaming + hydration reconciles the SSR markup before we touch it — otherwise images + served from cache produce a "tree hydrated but attributes didn't match" + warning (className diff: 'img-loaded' was added by this script). */ + if(img.complete&&img.naturalWidth>0){requestAnimationFrame(add);} else{ - img.addEventListener('load',function(){img.classList.add('img-loaded');},{once:true}); - img.addEventListener('error',function(){img.classList.add('img-loaded');},{once:true}); + img.addEventListener('load',add,{once:true}); + img.addEventListener('error',add,{once:true}); } } function scan(){document.querySelectorAll('img[data-lazy]').forEach(handle);} From c1f45dcc5f5567b165b10d7f8ab49d6ef36321cc Mon Sep 17 00:00:00 2001 From: Damian Date: Thu, 28 May 2026 18:58:19 +0200 Subject: [PATCH 10/45] fix(sanity): use NEXT_PUBLIC_* only for sync project id and dataset to fix hydration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `syncSanityProjectId` previously read `SANITY_STUDIO_PROJECT_ID`, which Next does not inline into the client bundle. SSR therefore built transformed Sanity image URLs (`?w=…&auto=format&q=85`) while the hydrating client had no project id and fell back to bare `image.asset.url`, producing a hydration mismatch on every Sanity image. Read `NEXT_PUBLIC_SANITY_PROJECT_ID` exclusively (and drop the equivalent server-only `SANITY_STUDIO_DATASET` branch from `syncDataset`) so server and client always compute identical URLs. Document the new env var requirement in `.env.example`. Co-authored-by: Cursor --- web/.env.example | 4 ++++ web/sanity/sanitySyncConfig.ts | 21 ++++++++++++--------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/web/.env.example b/web/.env.example index b2fa4a8..b6802e0 100644 --- a/web/.env.example +++ b/web/.env.example @@ -3,6 +3,10 @@ # Sanity — project (required; same id as `studio/.env`) SANITY_STUDIO_PROJECT_ID= +# Same project id, exposed to the **browser** (Next inlines `NEXT_PUBLIC_*` only) so +# Client Components can build matching Sanity image URLs. Without this, SSR and CSR +# generate different image URLs and React reports a hydration mismatch. +NEXT_PUBLIC_SANITY_PROJECT_ID= # --- Dataset override (optional; `@repo/sanity-dataset-resolve` / `sanity/sanityEnv.ts`) --- # Default: no vars — order is inferred (e.g. dev-first locally, prod-first on Vercel production). diff --git a/web/sanity/sanitySyncConfig.ts b/web/sanity/sanitySyncConfig.ts index 2e349b5..7a0b040 100644 --- a/web/sanity/sanitySyncConfig.ts +++ b/web/sanity/sanitySyncConfig.ts @@ -1,5 +1,3 @@ -import { getSanityStudioProjectId } from "./resolveStudioDataset"; - /** * **Synchronous** `projectId` + `dataset` for code that may run in the **browser** (e.g. * `sanityImageBuilder` used under Client Components). No top-level `await`. @@ -7,20 +5,25 @@ import { getSanityStudioProjectId } from "./resolveStudioDataset"; * The GROQ `client` in `client.ts` still uses async resolution in `sanityEnv.ts` — keep env * aligned so image URLs match API queries (set `SANITY_STUDIO_DATASET` or pin both places). * - * In client bundles, Next only inlines **`NEXT_PUBLIC_*`** — prefer - * **`NEXT_PUBLIC_SANITY_DATASET`** if image URLs must match a non-default dataset in the browser. + * In client bundles, Next only inlines **`NEXT_PUBLIC_*`**. We deliberately read **only** + * `NEXT_PUBLIC_SANITY_PROJECT_ID` / `NEXT_PUBLIC_SANITY_DATASET` here so server and client + * always compute identical URLs — otherwise SSR transforms (`?w=…&auto=format&q=85`) but + * the hydrating client falls back to bare `asset.url`, producing hydration mismatches on + * every Sanity image. If the public vars are unset, both sides degrade to bare `asset.url` + * (no transformations) but stay consistent. */ -export const syncSanityProjectId: string = getSanityStudioProjectId(); +export const syncSanityProjectId: string = + process.env.NEXT_PUBLIC_SANITY_PROJECT_ID?.trim() || ""; function syncDataset(): string { const fromPublic = process.env.NEXT_PUBLIC_SANITY_DATASET?.trim(); if (fromPublic) { return fromPublic; } - const fromStudio = process.env.SANITY_STUDIO_DATASET?.trim(); - if (fromStudio) { - return fromStudio; - } + /* `SANITY_STUDIO_DATASET` deliberately omitted: it's not inlined into client + bundles, so reading it here would diverge from the client and cause image + URL mismatches. `NODE_ENV` is inlined by Next, so this final branch matches + on both sides. Set `NEXT_PUBLIC_SANITY_DATASET` for non-default datasets. */ return process.env.NODE_ENV === "production" ? "production" : "development"; } From ff5e628edfb99effca9334970b04842b08dfd671 Mon Sep 17 00:00:00 2001 From: Damian Date: Thu, 28 May 2026 19:10:21 +0200 Subject: [PATCH 11/45] fix(media): manage img-loaded class via React state to fix hydration mismatch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adding the `img-loaded` class from the inline boot script raced React 19's streaming hydration on cached images and produced "tree hydrated but attributes didn't match" warnings. `requestAnimationFrame` was not enough to push the DOM mutation past hydration reliably. Move the lazy fade-in inside `MediaImage`: a `useRef` + `useEffect` flips a `loaded` state once the image's `load` event fires (or immediately if the image is already complete) and React renders the class — no more DOM / hydration race. Strip the image-handling section from the boot script — theme + `js-enabled` stay so the CSS opacity gate keeps working. Co-authored-by: Cursor --- web/src/app/layout.tsx | 33 ++++------------------ web/src/components/media/MediaImage.tsx | 37 ++++++++++++++++++++++--- 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/web/src/app/layout.tsx b/web/src/app/layout.tsx index 62f5e16..f890aa3 100644 --- a/web/src/app/layout.tsx +++ b/web/src/app/layout.tsx @@ -89,9 +89,12 @@ export const metadata: Metadata = { * `@media (prefers-color-scheme: dark)` rule in `variables/colors.css` * takes over — works without JavaScript. * 2. Adds `js-enabled` to so CSS can safely start images at opacity:0. - * 3. Wires up the lazy-image fade-in: when an img[data-lazy] finishes loading, - * adds `.img-loaded` → CSS transition kicks in (0.2 s ease-in-out). - * 4. MutationObserver covers images injected after initial paint (client nav). + * + * The lazy-image `img-loaded` class is added by `MediaImage` itself via + * `useEffect` — managing it from this inline script raced React 19 streaming + * hydration on cached images and produced "tree hydrated but attributes didn't + * match" warnings. Doing it through React's render flow keeps SSR and the + * first client paint identical. * * Without JS the images remain fully visible (no opacity applied) — graceful degradation. */ @@ -103,30 +106,6 @@ const bootScript = `(function(){ } }catch(e){} document.documentElement.classList.add('js-enabled'); - function handle(img){ - function add(){img.classList.add('img-loaded');} - /* Defer the className mutation past the current task so React 19 streaming - hydration reconciles the SSR markup before we touch it — otherwise images - served from cache produce a "tree hydrated but attributes didn't match" - warning (className diff: 'img-loaded' was added by this script). */ - if(img.complete&&img.naturalWidth>0){requestAnimationFrame(add);} - else{ - img.addEventListener('load',add,{once:true}); - img.addEventListener('error',add,{once:true}); - } - } - function scan(){document.querySelectorAll('img[data-lazy]').forEach(handle);} - if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',scan);} - else{scan();} - new MutationObserver(function(recs){ - recs.forEach(function(r){ - r.addedNodes.forEach(function(n){ - if(n.nodeType!==1)return; - if(n.matches&&n.matches('img[data-lazy]'))handle(n); - n.querySelectorAll&&n.querySelectorAll('img[data-lazy]').forEach(handle); - }); - }); - }).observe(document.documentElement,{childList:true,subtree:true}); })();`; /** diff --git a/web/src/components/media/MediaImage.tsx b/web/src/components/media/MediaImage.tsx index b6962c4..b340951 100644 --- a/web/src/components/media/MediaImage.tsx +++ b/web/src/components/media/MediaImage.tsx @@ -1,7 +1,7 @@ "use client"; import clsx from "clsx"; -import type { CSSProperties } from "react"; +import { type CSSProperties, useEffect, useRef, useState } from "react"; import type { SanityImageField } from "@/sanity/types/modules"; import { @@ -78,8 +78,10 @@ function buildSrcSet(image: SanityImageField, maxWidth: number): string { * Sanity-driven image: **native ``** with deterministic Sanity CDN URLs (same SSR / client), * responsive `srcset` + `sizes` so the browser picks the right variant per container / viewport. * - * Server Component (no `"use client"`) so URLs are built once on the server and the - * layout boot script may add `.img-loaded` without React hydration conflicts. + * The `img-loaded` class is React-managed (via `useState` + `useEffect`) so the SSR markup + * stays stable through hydration — adding the class from an inline boot script raced React + * 19's streaming hydration on cached images and produced "tree hydrated but attributes + * didn't match" warnings. */ export function MediaImage({ imagePayload, @@ -92,6 +94,29 @@ export function MediaImage({ sizes = "100vw", }: MediaImageProps) { const image = resolveSanityImageFieldForUrl(imagePayload); + const imgRef = useRef(null); + /* Priority images skip the fade — they render visible immediately. Non-priority + images start at `loaded=false` (matches SSR) and flip to `true` from the load + handler in `useEffect` (or right away if cached). */ + const [loaded, setLoaded] = useState(priority); + + useEffect(() => { + if (priority) return; + const el = imgRef.current; + if (!el) return; + if (el.complete && el.naturalWidth > 0) { + setLoaded(true); + return; + } + const onSettled = () => setLoaded(true); + el.addEventListener("load", onSettled, { once: true }); + el.addEventListener("error", onSettled, { once: true }); + return () => { + el.removeEventListener("load", onSettled); + el.removeEventListener("error", onSettled); + }; + }, [priority]); + if (!image) return null; const cropped = getCroppedImageDisplayDimensions(image); @@ -132,6 +157,7 @@ export function MediaImage({ > {/* biome-ignore lint/performance/noImgElement: deterministic Sanity URLs; next/image caused hydration mismatches */} From 1d4ddd72a2f273aeed37b685b10682027654e6df Mon Sep 17 00:00:00 2001 From: Marcell Lanczos Date: Thu, 28 May 2026 19:14:06 +0200 Subject: [PATCH 12/45] feat(sanity): enhance siteNav queries for locale support Updated `siteNavQuery` and `siteNavMenusQuery` to utilize a new `siteNavByLocale` query, which retrieves the active locale's `siteNav` document while falling back to a legacy document if no locale-specific document exists. This change improves internationalization handling in the navigation structure. --- web/sanity/queries/snippets/settings.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/web/sanity/queries/snippets/settings.ts b/web/sanity/queries/snippets/settings.ts index 2250ef0..f9394ce 100644 --- a/web/sanity/queries/snippets/settings.ts +++ b/web/sanity/queries/snippets/settings.ts @@ -47,8 +47,17 @@ export const navMenusQuery = ` "footerMenu": ${navFooterMenuQuery} `; +/** + * `siteNav` for the active locale. Falls back to a legacy document with no + * `language` (pre–document-level i18n) when no locale-specific doc exists. + */ +const siteNavByLocale = `coalesce( + *[_type == "siteNav" && language == $locale][0], + *[_type == "siteNav" && !defined(language)][0] +)`; + /** Document type: `siteNav` (one per language; see studio structure). */ -export const siteNavQuery = `*[_type == "siteNav" && language == $locale][0]{ +export const siteNavQuery = `${siteNavByLocale}{ _id, title, language, @@ -63,7 +72,7 @@ export const siteNavQuery = `*[_type == "siteNav" && language == $locale][0]{ * projection here and mis-types `mainMenu`/`footerMenu` as `null`. The * hand-written `SiteNavMenusDocument` (sanity/types/nav.ts) is authoritative. */ -export const siteNavMenusQuery = `*[_type == "siteNav" && language == $locale][0]{ +export const siteNavMenusQuery = `${siteNavByLocale}{ _id, title, language, From d3f1704b8c75eb5037e8d7bc94d4ad2dfb6fff28 Mon Sep 17 00:00:00 2001 From: Damian Date: Thu, 28 May 2026 19:19:44 +0200 Subject: [PATCH 13/45] refactor(web): expose SANITY_STUDIO_* to client via next.config env map MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `web/.env` previously needed both `SANITY_STUDIO_PROJECT_ID` (server / Studio convention) and `NEXT_PUBLIC_SANITY_PROJECT_ID` (so Client Components could build matching Sanity image URLs). Same value, two names — confusing and error-prone (forgetting one half of the pair causes a hydration mismatch). Use Next's `next.config.ts` `env` map to inline `SANITY_STUDIO_PROJECT_ID` and `SANITY_STUDIO_DATASET` into the client bundle. `sanitySyncConfig.ts` now reads the Studio-named vars directly. `.env.example` drops the public aliases. One env var, one name, identical SSR / CSR Sanity URLs. Co-authored-by: Cursor --- web/.env.example | 15 +++++++-------- web/next.config.ts | 14 ++++++++++++++ web/sanity/sanitySyncConfig.ts | 27 +++++++++++++-------------- 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/web/.env.example b/web/.env.example index b6802e0..7d3a2df 100644 --- a/web/.env.example +++ b/web/.env.example @@ -1,12 +1,12 @@ # Next.js (web) — copy to `.env.local` # https://nextjs.org/docs/app/building-your-application/configuring-environment-variables -# Sanity — project (required; same id as `studio/.env`) +# Sanity — project (required; same id as `studio/.env`). +# `next.config.ts` inlines `SANITY_STUDIO_*` into the **client** bundle (Next normally +# only inlines `NEXT_PUBLIC_*`) so Client Components and the server resolve to the same +# project id and dataset — otherwise SSR + CSR produce different Sanity image URLs and +# React reports a hydration mismatch. No `NEXT_PUBLIC_SANITY_PROJECT_ID` needed. SANITY_STUDIO_PROJECT_ID= -# Same project id, exposed to the **browser** (Next inlines `NEXT_PUBLIC_*` only) so -# Client Components can build matching Sanity image URLs. Without this, SSR and CSR -# generate different image URLs and React reports a hydration mismatch. -NEXT_PUBLIC_SANITY_PROJECT_ID= # --- Dataset override (optional; `@repo/sanity-dataset-resolve` / `sanity/sanityEnv.ts`) --- # Default: no vars — order is inferred (e.g. dev-first locally, prod-first on Vercel production). @@ -17,10 +17,9 @@ NEXT_PUBLIC_SANITY_PROJECT_ID= # - Any other value (e.g. `development`) — try a dataset with that name first, then dev/prod fallbacks. # SANITY_STUDIO_DEPLOYMENT_TARGET=development # -# Hard pin (skips auto-selection and `SANITY_STUDIO_DEPLOYMENT_TARGET`): +# Hard pin (skips auto-selection and `SANITY_STUDIO_DEPLOYMENT_TARGET`). +# `next.config.ts` exposes this to the browser too — set it once. # SANITY_STUDIO_DATASET= -# Same dataset name for **browser** image URLs (Client Components / Portable Text media): -# NEXT_PUBLIC_SANITY_DATASET= # Sanity — live preview & Visual Editing (Presentation, Draft Mode, Stega) # Also used for **Site languages** (`siteLanguageSettings`): with a token, Next reads draft/unpublished diff --git a/web/next.config.ts b/web/next.config.ts index 72dd515..3836062 100644 --- a/web/next.config.ts +++ b/web/next.config.ts @@ -5,6 +5,20 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { transpilePackages: ["@repo/sanity-dataset-resolve"], + /** + * Inline `SANITY_STUDIO_*` into the **client** bundle. + * + * Next normally only exposes `NEXT_PUBLIC_*` variables to the browser. We keep a single + * env-var name across `web/` and `studio/` (`SANITY_STUDIO_PROJECT_ID`, `SANITY_STUDIO_DATASET`) + * by listing them here — `next.config`'s `env` map gets statically replaced at build time + * exactly like `NEXT_PUBLIC_*`. Without this, Client Components (image-URL builder etc.) + * would see `undefined` and SSR / CSR would diverge → hydration mismatch on Sanity images. + */ + env: { + SANITY_STUDIO_PROJECT_ID: process.env.SANITY_STUDIO_PROJECT_ID ?? "", + SANITY_STUDIO_DATASET: process.env.SANITY_STUDIO_DATASET ?? "", + }, + /** Drop the `X-Powered-By: Next.js` header — small fingerprinting reduction. */ poweredByHeader: false, diff --git a/web/sanity/sanitySyncConfig.ts b/web/sanity/sanitySyncConfig.ts index 7a0b040..71fa906 100644 --- a/web/sanity/sanitySyncConfig.ts +++ b/web/sanity/sanitySyncConfig.ts @@ -5,25 +5,24 @@ * The GROQ `client` in `client.ts` still uses async resolution in `sanityEnv.ts` — keep env * aligned so image URLs match API queries (set `SANITY_STUDIO_DATASET` or pin both places). * - * In client bundles, Next only inlines **`NEXT_PUBLIC_*`**. We deliberately read **only** - * `NEXT_PUBLIC_SANITY_PROJECT_ID` / `NEXT_PUBLIC_SANITY_DATASET` here so server and client - * always compute identical URLs — otherwise SSR transforms (`?w=…&auto=format&q=85`) but - * the hydrating client falls back to bare `asset.url`, producing hydration mismatches on - * every Sanity image. If the public vars are unset, both sides degrade to bare `asset.url` - * (no transformations) but stay consistent. + * Reads `SANITY_STUDIO_PROJECT_ID` / `SANITY_STUDIO_DATASET` directly. These are inlined + * into the **client** bundle by `next.config.ts`'s `env` map (Next normally only inlines + * `NEXT_PUBLIC_*`) so server and client always compute identical URLs — otherwise SSR + * transforms (`?w=…&auto=format&q=85`) but the hydrating client falls back to bare + * `asset.url`, producing hydration mismatches on every Sanity image. */ export const syncSanityProjectId: string = - process.env.NEXT_PUBLIC_SANITY_PROJECT_ID?.trim() || ""; + process.env.SANITY_STUDIO_PROJECT_ID?.trim() || ""; function syncDataset(): string { - const fromPublic = process.env.NEXT_PUBLIC_SANITY_DATASET?.trim(); - if (fromPublic) { - return fromPublic; + const explicit = process.env.SANITY_STUDIO_DATASET?.trim(); + if (explicit) { + return explicit; } - /* `SANITY_STUDIO_DATASET` deliberately omitted: it's not inlined into client - bundles, so reading it here would diverge from the client and cause image - URL mismatches. `NODE_ENV` is inlined by Next, so this final branch matches - on both sides. Set `NEXT_PUBLIC_SANITY_DATASET` for non-default datasets. */ + /* No explicit dataset → fall back to a name that's identical on server and client. + `NODE_ENV` is inlined by Next, so this branch matches on both sides. Set + `SANITY_STUDIO_DATASET` (which `next.config.ts` exposes to the browser) for + non-default datasets. */ return process.env.NODE_ENV === "production" ? "production" : "development"; } From b56489f279e91ca36f2add77aa54237be0366f7d Mon Sep 17 00:00:00 2001 From: Damian Date: Thu, 28 May 2026 19:26:33 +0200 Subject: [PATCH 14/45] fix(media): reveal images that failed to load instead of leaving them at opacity:0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `useEffect` only set `loaded=true` synchronously when both `el.complete` and `naturalWidth > 0` were true. For images that errored before React's passive effect attached its listeners, the load/error events had already fired and were lost — `loaded` never flipped, the `img-loaded` class was never added and the broken image stayed invisible (opacity:0) forever. Drop the `naturalWidth > 0` guard. Both successful and failed loads need to reveal the `` (the alt text matters for failed ones); the running listeners still cover the in-flight case. Co-authored-by: Cursor --- web/src/components/media/MediaImage.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/web/src/components/media/MediaImage.tsx b/web/src/components/media/MediaImage.tsx index b340951..28c859f 100644 --- a/web/src/components/media/MediaImage.tsx +++ b/web/src/components/media/MediaImage.tsx @@ -104,7 +104,10 @@ export function MediaImage({ if (priority) return; const el = imgRef.current; if (!el) return; - if (el.complete && el.naturalWidth > 0) { + /* `complete` is also true for failed loads — reveal the element either way, + otherwise broken images stay at opacity:0 forever (load/error events have + already fired before this effect attaches its listeners). */ + if (el.complete) { setLoaded(true); return; } From 9b8a32a9a7f05192cb45315296b686357c3b31e6 Mon Sep 17 00:00:00 2001 From: Damian Date: Thu, 28 May 2026 19:36:40 +0200 Subject: [PATCH 15/45] feat(sanity): wire Presentation overlay onto rendered modules via data-sanity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a `dataAttr` helper that composes `next-sanity`'s `createDataAttribute` with the resolved `projectId` / `dataset` and the Studio URL, so callers only need to pass the field path (`{ id, type, path }`). Used in `ModulesRenderer` to wrap each module with `
` — Presentation tool can now reverse-map a rendered module to its `modules[_key=="…"]` GROQ slot and jump the Studio cursor into the corresponding field. Skips the attribute when a module has no `_key` (legacy data) — without a stable key the overlay would target the wrong array slot. Co-authored-by: Cursor --- web/sanity/utils/dataAttr.ts | 31 ++++++++++ web/sanity/utils/index.ts | 1 + web/src/app/[locale]/[slug]/page.tsx | 6 +- web/src/app/[locale]/page.tsx | 6 +- .../components/modules/ModulesRenderer.tsx | 62 ++++++++++++------- 5 files changed, 83 insertions(+), 23 deletions(-) create mode 100644 web/sanity/utils/dataAttr.ts diff --git a/web/sanity/utils/dataAttr.ts b/web/sanity/utils/dataAttr.ts new file mode 100644 index 0000000..4924723 --- /dev/null +++ b/web/sanity/utils/dataAttr.ts @@ -0,0 +1,31 @@ +import { + type CreateDataAttributeProps, + createDataAttribute, +} from "next-sanity"; + +import { dataset, projectId } from "../sanityEnv"; + +const studioUrl = + process.env.NEXT_PUBLIC_SANITY_STUDIO_URL ?? "http://localhost:3333"; + +type DataAttrConfig = CreateDataAttributeProps & + Required>; + +/** + * Builds a `data-sanity` attribute value so the Presentation tool overlay can + * map a rendered element back to its document + field path. Wrap editable + * surfaces with `data-sanity={dataAttr({ id, type, path })}` — clicking the + * element in Presentation jumps the Studio cursor straight into that field. + * + * `id` is the document `_id`, `type` is the document `_type`, `path` is the + * dot-notated GROQ path to the field (e.g. `"modules[_key==\"abc\"].heading"`). + */ +export function dataAttr(config: DataAttrConfig): string { + return createDataAttribute({ + projectId, + dataset, + baseUrl: studioUrl, + }) + .combine(config) + .toString(); +} diff --git a/web/sanity/utils/index.ts b/web/sanity/utils/index.ts index 94e77e6..641ace8 100644 --- a/web/sanity/utils/index.ts +++ b/web/sanity/utils/index.ts @@ -1,2 +1,3 @@ +export { dataAttr } from "./dataAttr"; export * from "./sanityImageBuilder"; export * from "./sanityModuleLabel"; diff --git a/web/src/app/[locale]/[slug]/page.tsx b/web/src/app/[locale]/[slug]/page.tsx index 6cbeb78..49875f6 100644 --- a/web/src/app/[locale]/[slug]/page.tsx +++ b/web/src/app/[locale]/[slug]/page.tsx @@ -76,7 +76,11 @@ export default async function Page({ params }: PageProps) { {data.modules?.length ? (

Modules

- +
) : null} diff --git a/web/src/app/[locale]/page.tsx b/web/src/app/[locale]/page.tsx index e407745..0fc4d30 100644 --- a/web/src/app/[locale]/page.tsx +++ b/web/src/app/[locale]/page.tsx @@ -74,7 +74,11 @@ export default async function Home({ params }: PageProps) { {data.modules?.length ? (

Modules

- +
) : null} diff --git a/web/src/components/modules/ModulesRenderer.tsx b/web/src/components/modules/ModulesRenderer.tsx index c4a5630..32162ce 100644 --- a/web/src/components/modules/ModulesRenderer.tsx +++ b/web/src/components/modules/ModulesRenderer.tsx @@ -5,6 +5,7 @@ import type { ModuleMediaData, ModuleTextData, } from "@/sanity/types/modules"; +import { dataAttr } from "@/sanity/utils/dataAttr"; import { getSanityModuleLabel } from "@/sanity/utils/sanityModuleLabel"; import { ModuleCarousel } from "@/src/components/carousel"; import { ModuleMedia } from "./ModuleMedia"; @@ -12,6 +13,11 @@ import { ModuleText } from "./ModuleText"; type Props = { modules: ContentModule[]; + /** Document `_id` of the page rendering these modules. Used to mark each + * module as Presentation-tool clickable via `data-sanity`. */ + documentId: string; + /** Document `_type` of the page rendering these modules. */ + documentType: string; }; const IS_DEV = process.env.NODE_ENV === "development"; @@ -58,31 +64,45 @@ function UnknownModule({ moduleType }: { moduleType: string | undefined }) { } /** Renders the document `modules[]` stack (one UI block per `module.*` type). */ -export function ModulesRenderer({ modules }: Props) { +export function ModulesRenderer({ modules, documentId, documentType }: Props) { return (
{modules.map((mod, index) => { const key = mod._key ?? `${mod._type ?? "module"}-${index}`; - if (mod._type === "module.text") { - return ; - } - if (mod._type === "module.media") { - return ; - } - if (mod._type === "module.carousel") { - return ( - - ); - } - if (mod._type === "module.contentRefs") { - return ( - - ); - } - return ; + // Only modules with a stable `_key` can be reverse-mapped to a + // GROQ path. Without `_key` (legacy data) the Presentation + // overlay would target the wrong array slot, so we skip it. + const sanityAttr = mod._key + ? dataAttr({ + id: documentId, + type: documentType, + path: `modules[_key=="${mod._key}"]`, + }) + : undefined; + const child = (() => { + if (mod._type === "module.text") { + return ; + } + if (mod._type === "module.media") { + return ; + } + if (mod._type === "module.carousel") { + return ; + } + if (mod._type === "module.contentRefs") { + return ( + + ); + } + return ; + })(); + return ( +
+ {child} +
+ ); })}
); From 84c1d21de1727d5311efa42cc764712ec29ffa8a Mon Sep 17 00:00:00 2001 From: Damian Date: Thu, 28 May 2026 19:37:14 +0200 Subject: [PATCH 16/45] chore(layout): log SanityLive connection errors to console.error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Silent failures of the SanityLive socket made it hard to tell whether live content was syncing during draft mode. Pass an `onError` handler that emits the error plus the request context (`includeDrafts`, `waitFor`) to `console.error` — surfaces issues in DevTools without changing behavior. Co-authored-by: Cursor --- web/src/app/layout.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/web/src/app/layout.tsx b/web/src/app/layout.tsx index f890aa3..9310a20 100644 --- a/web/src/app/layout.tsx +++ b/web/src/app/layout.tsx @@ -151,7 +151,17 @@ export default async function RootLayout({ {children} - {shouldMountSanityLive ? : null} + {shouldMountSanityLive ? ( + { + console.error("[SanityLive] live connection error", { + error, + includeDrafts: context.includeDrafts, + waitFor: context.waitFor, + }); + }} + /> + ) : null} {isDraft ? ( <> From f9d502ae40f94f493ac5dacff249a19dbac23bf3 Mon Sep 17 00:00:00 2001 From: Marcell Lanczos Date: Thu, 28 May 2026 19:53:12 +0200 Subject: [PATCH 17/45] feat(layout): integrate DocumentBootScript for theme management Replaced the inline boot script with a new DocumentBootScript component that sets the theme based on localStorage and adds a 'js-enabled' class to the element. This change enhances the structure and maintainability of the layout while ensuring the theme is applied before the first paint. Updated related comments in animations.css to reflect this change. --- web/src/app/layout.tsx | 35 ++----------------- web/src/assets/styles/animations.css | 2 +- .../components/theme/DocumentBootScript.tsx | 34 ++++++++++++++++++ 3 files changed, 38 insertions(+), 33 deletions(-) create mode 100644 web/src/components/theme/DocumentBootScript.tsx diff --git a/web/src/app/layout.tsx b/web/src/app/layout.tsx index 9310a20..7b829b8 100644 --- a/web/src/app/layout.tsx +++ b/web/src/app/layout.tsx @@ -4,6 +4,7 @@ import { VisualEditing } from "next-sanity/visual-editing"; // import localFont from "next/font/local"; import { SanityLive } from "@/sanity/live"; import { DisableDraftMode } from "@/src/components/sanity/DisableDraftMode"; +import { DocumentBootScript } from "@/src/components/theme/DocumentBootScript"; import { ThemeProvider } from "@/src/contexts/ThemeContext"; import { FALLBACK_SITE_LOCALE_CONFIG } from "@/src/i18n/fallbackSiteLocales"; import "../assets/styles/tokens.css"; @@ -80,34 +81,6 @@ export const metadata: Metadata = { }, }; -/** - * Tiny blocking inline script — runs synchronously before hydration. - * - * 1. Applies the stored theme override (`localStorage["color-scheme"]`) as - * `data-theme="light" | "dark"` on **before** the first paint. For - * `"system"` or a missing value the attribute stays unset, so the - * `@media (prefers-color-scheme: dark)` rule in `variables/colors.css` - * takes over — works without JavaScript. - * 2. Adds `js-enabled` to so CSS can safely start images at opacity:0. - * - * The lazy-image `img-loaded` class is added by `MediaImage` itself via - * `useEffect` — managing it from this inline script raced React 19 streaming - * hydration on cached images and produced "tree hydrated but attributes didn't - * match" warnings. Doing it through React's render flow keeps SSR and the - * first client paint identical. - * - * Without JS the images remain fully visible (no opacity applied) — graceful degradation. - */ -const bootScript = `(function(){ - try{ - var t=localStorage.getItem('color-scheme'); - if(t==='light'||t==='dark'){ - document.documentElement.setAttribute('data-theme',t); - } - }catch(e){} - document.documentElement.classList.add('js-enabled'); -})();`; - /** * Root shell only — avoid `headers()` here (keeps static routes static where possible). * `draftMode()` only toggles Visual Editing UI; locale chrome lives in `app/[locale]/layout.tsx`. @@ -132,10 +105,6 @@ export default async function RootLayout({ suppressHydrationWarning > - {/* Theme class + lazy-image fade-in — must run before first paint */} - {/* biome-ignore lint/security/noDangerouslySetInnerHtml: controlled inline script, no user input */} -