# Sveltia CMS configuration for TheFamilyProclamation.org
# -----------------------------------------------------------------------------
# This drives the visual editor at /site-cms/. It is a thin layer over the
# repo-root _content/ folder — every collection below maps 1:1 onto files that
# the Astro Content Layer (site/src/content.config.ts) already indexes.
#
# Paths are relative to the REPOSITORY ROOT, not to site/.
#
# Engineer-owned files are deliberately NOT listed here, so they are invisible
# in the CMS: _content/taxonomy/family-proclamation.yml (1995 canonical text)
# and _content/taxonomy/audiences.yml (small + stable, lower-priority surface).
# Browse is derived from the article set (see src/lib/browse.ts): categories and
# topics surface only once they have published articles, so there is no separate
# section-grouping file to edit.
#
# Schema sync: the `fields` below mirror the Zod schemas in
# site/src/content.config.ts. Zod stays the hard build-time gate — if these
# drift, `astro build` fails loudly. Keep the two in step.
# -----------------------------------------------------------------------------

# Backend. Two ways to use this CMS:
#  - Local: on localhost Sveltia offers "Work with Local Repository" (File
#    System Access API), writing _content/ files straight to disk.
#  - Production: editors sign in with a GitHub personal access token via
#    "Sign In Using Access Token". See _content/AUTHORING.md.
# A one-click OAuth "Sign in with GitHub" flow (the cms-auth Lambda) is built
# and deployed, ready for the canonical-domain launch — enable it by adding
# `base_url` + `auth_endpoint` here. See docs/setup/sveltia-cms.md.
backend:
  name: github
  repo: thefamilyproclamation/the-family-proclamation
  branch: main
  # One-click "Sign in with GitHub" via the cms-auth Lambda (API stack). The
  # Lambda reads the GitHub OAuth App credentials from the CDK-declared secret
  # tfp-cms-oauth-credentials-dev — set its value out-of-band before this works.
  # See docs/setup/sveltia-cms.md.
  base_url: https://gaodxpoywb.execute-api.us-east-1.amazonaws.com
  auth_endpoint: dev/cms-auth/auth

# Media: content images live in the repo-root content tree under
# `_content/assets/img` (mirrored to `site/public/assets` at build time by
# scripts/sync-content-assets.mjs, so they resolve at `/assets/img/...`). The CMS
# media library reads/writes there; uploads land in the content tree, not under
# site/public. Non-image assets (PDFs) live alongside under `_content/assets/pdf`
# and are referenced as plain markdown links.
media_folder: _content/assets/img
public_folder: /assets/img

# Show DRAFT vs PUBLISHED plainly in entry lists.
collections:
  # ---------------------------------------------------------------------------
  - name: answers
    label: Answers
    label_singular: Answer
    description: >-
      The answer to the question someone actually typed - complete in itself, short enough to send to a friend. Aim for 350-600 words. Where there is more to say, name the essay it was drawn from and the page links to it.
    folder: _content/answers
    create: true
    extension: md
    format: frontmatter
    identifier_field: title
    summary: '{{title}}  ·  {{status}}'
    sortable_fields: [title, status, delicateness]
    fields:
      - { name: title, label: Title, widget: string }
      - name: topics
        label: Topics
        hint: One or more topics this belongs to. Surfaces on each topic's page.
        widget: relation
        collection: topics
        value_field: id
        search_fields: [display_name, id]
        display_fields: [display_name]
        multiple: true
        min: 1
        # Archived topics are hidden from the public site — `getPublicTagMap()`
        # drops them and Browse then drops the piece along with them, silently.
        # Without this filter every archived topic stayed pickable, which is how
        # 116 topic slots drifted back onto retired tags after the June
        # consolidation and quietly took those pages out of Browse.
        filters:
          - field: archived
            values: [false]
      - name: status
        label: Status
        widget: select
        default: DRAFT
        options: [DRAFT, PUBLISHED]
      - { name: aiGenerated, label: 'AI-generated (uncheck after human review)', widget: boolean, default: true }
      - name: delicateness
        label: Delicateness (1 = routine, 5 = highly sensitive)
        widget: number
        value_type: int
        min: 1
        max: 5
        default: 3
      - name: chatQuestions
        label: Chat questions that route here
        hint: Question phrasings that should surface this article in chat. One per line. (The title is always matched too.)
        widget: list
        required: false
        default: []
        field: { name: question, label: Question, widget: string }
      - name: summary
        label: Summary
        widget: text
        required: false
        hint: "What chat says when this article answers a question (~150–250 words, complete in itself, markdown links OK). Leave blank to let chat use the article's opening."
      - name: askPrompts
        label: '"Try asking" prompts'
        hint: Questions shown at the foot of the article; each deep-links into chat with the question pre-filled.
        widget: list
        required: false
        default: []
        field: { name: prompt, label: Prompt, widget: string }
      - name: related
        label: Related page IDs
        hint: Filenames (without .md) of related pages. They can be on any shelf - an answer may point at a story.
        widget: list
        required: false
        default: []
        field: { name: id, label: Article ID, widget: string }
      - name: sources
        label: 'Sources (title + absolute URL)'
        widget: list
        required: false
        fields:
          - { name: title, label: Title, widget: string }
          - name: url
            label: URL
            widget: string
            pattern: ['^https?:\/\/[^\s]+$', 'A full web address starting with http:// or https:// — no spaces']
      - name: research
        label: Research (supporting studies)
        hint: Renders a "What the research shows" section. Peer-reviewed / institutional findings with a short headline stat.
        widget: list
        required: false
        default: []
        fields:
          - { name: stat, label: Headline stat (optional), widget: string, required: false }
          - { name: finding, label: Finding (plain language), widget: text }
          - { name: citation, label: Citation (authors, venue, year), widget: string }
          - name: url
            label: URL
            widget: string
            pattern: ['^https?:\/\/[^\s]+$', 'A full web address starting with http:// or https:// — no spaces']
      - name: stories
        label: Real stories
        hint: Renders a "Real stories" section. Lived-experience accounts of real people showing the benefit of this article's principle. Each links out to the full account.
        widget: list
        required: false
        default: []
        fields:
          - { name: person, label: Who the story is about, widget: string }
          - { name: summary, label: Summary (what happens + the benefit), widget: text }
          - { name: quote, label: Pull-quote (optional), widget: text, required: false }
          - { name: source, label: Source (publication), widget: string }
          - name: url
            label: URL
            widget: string
            pattern: ['^https?:\/\/[^\s]+$', 'A full web address starting with http:// or https:// — no spaces']
      - name: updatedAt
        label: Last updated
        widget: datetime
        required: false
        date_format: 'yyyy-MM-dd'
        time_format: false
      - name: image
        label: Hero image (optional)
        hint: >-
          Optional banner shown at the top of the page. Leave blank to use
          the shared default. Upload or pick a landscape image (final art should
          be a JPG/WebP).
        widget: image
        required: false
      - name: imageAlt
        label: Hero image — alt text
        widget: string
        required: false
        hint: >-
          What a screen reader reads aloud. Describe the picture that is actually
          there — write it from the photo, not from its filename. Leave it empty
          only when the image is decorative and the heading beside it already says
          everything. Never write a note to yourself here; it is read out word for
          word.
      - name: essay
        label: Drawn from this essay (optional)
        hint: The filename (without .md) of the essay this answer was distilled from. The answer then closes with a link to it, and the essay lists this answer in return.
        widget: string
        required: false
      # Carried over from the WordPress import. These belong to the piece, not
      # to the shelf it used to sit on, so they follow it: a story keeps the day
      # it was told, and `originalUrl` is the only record of where it lived
      # before. Declared here because Sveltia writes back only the fields it
      # knows about — an undeclared field is silently deleted on the first save.
      - { name: date, label: Date first published, widget: datetime, required: false }
      - { name: author, label: Author, widget: string, required: false }
      - name: excerpt
        label: Excerpt (shown on the index card)
        widget: text
        required: false
      - name: categories
        label: Categories (legacy WordPress slugs - provenance only)
        widget: list
        required: false
        default: []
        field: { name: category, label: Category, widget: string }
      - name: originalUrl
        label: Original WordPress URL
        widget: string
        required: false
        pattern: ['^$|^https?:\/\/[^\s]+$', 'Leave empty, or give a full web address starting with http:// or https:// - no spaces']
      - name: body
        label: Body (Markdown)
        widget: markdown

  # ---------------------------------------------------------------------------
  - name: essays
    label: Essays
    label_singular: Essay
    description: >-
      The whole case on one question - scriptures, scholarship and stories laid out - for a reader who has decided to sit down. Aim for 1,200-2,500 words. List the short answers distilled from it so a reader in a hurry has somewhere to go.
    folder: _content/essays
    create: true
    extension: md
    format: frontmatter
    identifier_field: title
    summary: '{{title}}  ·  {{status}}'
    sortable_fields: [title, status, delicateness]
    fields:
      - { name: title, label: Title, widget: string }
      - name: topics
        label: Topics
        hint: One or more topics this belongs to. Surfaces on each topic's page.
        widget: relation
        collection: topics
        value_field: id
        search_fields: [display_name, id]
        display_fields: [display_name]
        multiple: true
        min: 1
        # Archived topics are hidden from the public site — `getPublicTagMap()`
        # drops them and Browse then drops the piece along with them, silently.
        # Without this filter every archived topic stayed pickable, which is how
        # 116 topic slots drifted back onto retired tags after the June
        # consolidation and quietly took those pages out of Browse.
        filters:
          - field: archived
            values: [false]
      - name: status
        label: Status
        widget: select
        default: DRAFT
        options: [DRAFT, PUBLISHED]
      - { name: aiGenerated, label: 'AI-generated (uncheck after human review)', widget: boolean, default: true }
      - name: delicateness
        label: Delicateness (1 = routine, 5 = highly sensitive)
        widget: number
        value_type: int
        min: 1
        max: 5
        default: 3
      - name: chatQuestions
        label: Chat questions that route here
        hint: Question phrasings that should surface this article in chat. One per line. (The title is always matched too.)
        widget: list
        required: false
        default: []
        field: { name: question, label: Question, widget: string }
      - name: summary
        label: Summary
        widget: text
        required: false
        hint: "What chat says when this article answers a question (~150–250 words, complete in itself, markdown links OK). Leave blank to let chat use the article's opening."
      - name: askPrompts
        label: '"Try asking" prompts'
        hint: Questions shown at the foot of the article; each deep-links into chat with the question pre-filled.
        widget: list
        required: false
        default: []
        field: { name: prompt, label: Prompt, widget: string }
      - name: related
        label: Related page IDs
        hint: Filenames (without .md) of related pages. They can be on any shelf - an answer may point at a story.
        widget: list
        required: false
        default: []
        field: { name: id, label: Article ID, widget: string }
      - name: sources
        label: 'Sources (title + absolute URL)'
        widget: list
        required: false
        fields:
          - { name: title, label: Title, widget: string }
          - name: url
            label: URL
            widget: string
            pattern: ['^https?:\/\/[^\s]+$', 'A full web address starting with http:// or https:// — no spaces']
      - name: research
        label: Research (supporting studies)
        hint: Renders a "What the research shows" section. Peer-reviewed / institutional findings with a short headline stat.
        widget: list
        required: false
        default: []
        fields:
          - { name: stat, label: Headline stat (optional), widget: string, required: false }
          - { name: finding, label: Finding (plain language), widget: text }
          - { name: citation, label: Citation (authors, venue, year), widget: string }
          - name: url
            label: URL
            widget: string
            pattern: ['^https?:\/\/[^\s]+$', 'A full web address starting with http:// or https:// — no spaces']
      - name: stories
        label: Real stories
        hint: Renders a "Real stories" section. Lived-experience accounts of real people showing the benefit of this article's principle. Each links out to the full account.
        widget: list
        required: false
        default: []
        fields:
          - { name: person, label: Who the story is about, widget: string }
          - { name: summary, label: Summary (what happens + the benefit), widget: text }
          - { name: quote, label: Pull-quote (optional), widget: text, required: false }
          - { name: source, label: Source (publication), widget: string }
          - name: url
            label: URL
            widget: string
            pattern: ['^https?:\/\/[^\s]+$', 'A full web address starting with http:// or https:// — no spaces']
      - name: updatedAt
        label: Last updated
        widget: datetime
        required: false
        date_format: 'yyyy-MM-dd'
        time_format: false
      - name: image
        label: Hero image (optional)
        hint: >-
          Optional banner shown at the top of the page. Leave blank to use
          the shared default. Upload or pick a landscape image (final art should
          be a JPG/WebP).
        widget: image
        required: false
      - name: imageAlt
        label: Hero image — alt text
        widget: string
        required: false
        hint: >-
          What a screen reader reads aloud. Describe the picture that is actually
          there — write it from the photo, not from its filename. Leave it empty
          only when the image is decorative and the heading beside it already says
          everything. Never write a note to yourself here; it is read out word for
          word.
      - name: answers
        label: Short answers drawn from this essay
        hint: Filenames (without .md) of the answers distilled from this essay.
        widget: list
        required: false
        default: []
        field: { name: id, label: Answer ID, widget: string }
      # Carried over from the WordPress import. These belong to the piece, not
      # to the shelf it used to sit on, so they follow it: a story keeps the day
      # it was told, and `originalUrl` is the only record of where it lived
      # before. Declared here because Sveltia writes back only the fields it
      # knows about — an undeclared field is silently deleted on the first save.
      - { name: date, label: Date first published, widget: datetime, required: false }
      - { name: author, label: Author, widget: string, required: false }
      - name: excerpt
        label: Excerpt (shown on the index card)
        widget: text
        required: false
      - name: categories
        label: Categories (legacy WordPress slugs - provenance only)
        widget: list
        required: false
        default: []
        field: { name: category, label: Category, widget: string }
      - name: originalUrl
        label: Original WordPress URL
        widget: string
        required: false
        pattern: ['^$|^https?:\/\/[^\s]+$', 'Leave empty, or give a full web address starting with http:// or https:// - no spaces']
      - name: body
        label: Body (Markdown)
        widget: markdown

  # ---------------------------------------------------------------------------
  - name: stories
    label: Stories
    label_singular: Story
    description: >-
      One real, named person and what happened to them. Any length. A story is not an argument with an anecdote in it - if the person is an illustration rather than the subject, it belongs on Essays.
    folder: _content/stories
    create: true
    extension: md
    format: frontmatter
    identifier_field: title
    summary: '{{title}}  ·  {{status}}'
    sortable_fields: [title, status, delicateness]
    fields:
      - { name: title, label: Title, widget: string }
      - name: topics
        label: Topics
        hint: One or more topics this belongs to. Surfaces on each topic's page.
        widget: relation
        collection: topics
        value_field: id
        search_fields: [display_name, id]
        display_fields: [display_name]
        multiple: true
        min: 1
        # Archived topics are hidden from the public site — `getPublicTagMap()`
        # drops them and Browse then drops the piece along with them, silently.
        # Without this filter every archived topic stayed pickable, which is how
        # 116 topic slots drifted back onto retired tags after the June
        # consolidation and quietly took those pages out of Browse.
        filters:
          - field: archived
            values: [false]
      - name: status
        label: Status
        widget: select
        default: DRAFT
        options: [DRAFT, PUBLISHED]
      - { name: aiGenerated, label: 'AI-generated (uncheck after human review)', widget: boolean, default: true }
      - name: delicateness
        label: Delicateness (1 = routine, 5 = highly sensitive)
        widget: number
        value_type: int
        min: 1
        max: 5
        default: 3
      - name: chatQuestions
        label: Chat questions that route here
        hint: Question phrasings that should surface this article in chat. One per line. (The title is always matched too.)
        widget: list
        required: false
        default: []
        field: { name: question, label: Question, widget: string }
      - name: summary
        label: Summary
        widget: text
        required: false
        hint: "What chat says when this article answers a question (~150–250 words, complete in itself, markdown links OK). Leave blank to let chat use the article's opening."
      - name: askPrompts
        label: '"Try asking" prompts'
        hint: Questions shown at the foot of the article; each deep-links into chat with the question pre-filled.
        widget: list
        required: false
        default: []
        field: { name: prompt, label: Prompt, widget: string }
      - name: related
        label: Related page IDs
        hint: Filenames (without .md) of related pages. They can be on any shelf - an answer may point at a story.
        widget: list
        required: false
        default: []
        field: { name: id, label: Article ID, widget: string }
      - name: sources
        label: 'Sources (title + absolute URL)'
        widget: list
        required: false
        fields:
          - { name: title, label: Title, widget: string }
          - name: url
            label: URL
            widget: string
            pattern: ['^https?:\/\/[^\s]+$', 'A full web address starting with http:// or https:// — no spaces']
      - name: research
        label: Research (supporting studies)
        hint: Renders a "What the research shows" section. Peer-reviewed / institutional findings with a short headline stat.
        widget: list
        required: false
        default: []
        fields:
          - { name: stat, label: Headline stat (optional), widget: string, required: false }
          - { name: finding, label: Finding (plain language), widget: text }
          - { name: citation, label: Citation (authors, venue, year), widget: string }
          - name: url
            label: URL
            widget: string
            pattern: ['^https?:\/\/[^\s]+$', 'A full web address starting with http:// or https:// — no spaces']
      - name: stories
        label: Real stories
        hint: Renders a "Real stories" section. Lived-experience accounts of real people showing the benefit of this article's principle. Each links out to the full account.
        widget: list
        required: false
        default: []
        fields:
          - { name: person, label: Who the story is about, widget: string }
          - { name: summary, label: Summary (what happens + the benefit), widget: text }
          - { name: quote, label: Pull-quote (optional), widget: text, required: false }
          - { name: source, label: Source (publication), widget: string }
          - name: url
            label: URL
            widget: string
            pattern: ['^https?:\/\/[^\s]+$', 'A full web address starting with http:// or https:// — no spaces']
      - name: updatedAt
        label: Last updated
        widget: datetime
        required: false
        date_format: 'yyyy-MM-dd'
        time_format: false
      - name: image
        label: Hero image (optional)
        hint: >-
          Optional banner shown at the top of the page. Leave blank to use
          the shared default. Upload or pick a landscape image (final art should
          be a JPG/WebP).
        widget: image
        required: false
      - name: imageAlt
        label: Hero image — alt text
        widget: string
        required: false
        hint: >-
          What a screen reader reads aloud. Describe the picture that is actually
          there — write it from the photo, not from its filename. Leave it empty
          only when the image is decorative and the heading beside it already says
          everything. Never write a note to yourself here; it is read out word for
          word.
      # Carried over from the WordPress import. These belong to the piece, not
      # to the shelf it used to sit on, so they follow it: a story keeps the day
      # it was told, and `originalUrl` is the only record of where it lived
      # before. Declared here because Sveltia writes back only the fields it
      # knows about — an undeclared field is silently deleted on the first save.
      - { name: date, label: Date first published, widget: datetime, required: false }
      - { name: author, label: Author, widget: string, required: false }
      - name: excerpt
        label: Excerpt (shown on the index card)
        widget: text
        required: false
      - name: categories
        label: Categories (legacy WordPress slugs - provenance only)
        widget: list
        required: false
        default: []
        field: { name: category, label: Category, widget: string }
      - name: originalUrl
        label: Original WordPress URL
        widget: string
        required: false
        pattern: ['^$|^https?:\/\/[^\s]+$', 'Leave empty, or give a full web address starting with http:// or https:// - no spaces']
      - name: body
        label: Body (Markdown)
        widget: markdown

  # ---------------------------------------------------------------------------
  - name: podcast
    label: Podcast
    label_singular: Episode
    description: >-
      Every Raising Family episode with its notes. Where a post carried an episode's transcript it lives in that episode's body under a Transcript heading, not as a page of its own.
    folder: _content/podcast
    create: true
    extension: md
    format: frontmatter
    identifier_field: title
    summary: '{{title}}  ·  {{status}}'
    sortable_fields: [title, status, delicateness]
    fields:
      - { name: title, label: Title, widget: string }
      - name: topics
        label: Topics
        hint: One or more topics this belongs to. Surfaces on each topic's page.
        widget: relation
        collection: topics
        value_field: id
        search_fields: [display_name, id]
        display_fields: [display_name]
        multiple: true
        min: 1
        # Archived topics are hidden from the public site — `getPublicTagMap()`
        # drops them and Browse then drops the piece along with them, silently.
        # Without this filter every archived topic stayed pickable, which is how
        # 116 topic slots drifted back onto retired tags after the June
        # consolidation and quietly took those pages out of Browse.
        filters:
          - field: archived
            values: [false]
      - name: status
        label: Status
        widget: select
        default: DRAFT
        options: [DRAFT, PUBLISHED]
      - { name: aiGenerated, label: 'AI-generated (uncheck after human review)', widget: boolean, default: true }
      - name: delicateness
        label: Delicateness (1 = routine, 5 = highly sensitive)
        widget: number
        value_type: int
        min: 1
        max: 5
        default: 3
      - name: chatQuestions
        label: Chat questions that route here
        hint: Question phrasings that should surface this article in chat. One per line. (The title is always matched too.)
        widget: list
        required: false
        default: []
        field: { name: question, label: Question, widget: string }
      - name: summary
        label: Summary
        widget: text
        required: false
        hint: "What chat says when this article answers a question (~150–250 words, complete in itself, markdown links OK). Leave blank to let chat use the article's opening."
      - name: askPrompts
        label: '"Try asking" prompts'
        hint: Questions shown at the foot of the article; each deep-links into chat with the question pre-filled.
        widget: list
        required: false
        default: []
        field: { name: prompt, label: Prompt, widget: string }
      - name: related
        label: Related page IDs
        hint: Filenames (without .md) of related pages. They can be on any shelf - an answer may point at a story.
        widget: list
        required: false
        default: []
        field: { name: id, label: Article ID, widget: string }
      - name: sources
        label: 'Sources (title + absolute URL)'
        widget: list
        required: false
        fields:
          - { name: title, label: Title, widget: string }
          - name: url
            label: URL
            widget: string
            pattern: ['^https?:\/\/[^\s]+$', 'A full web address starting with http:// or https:// — no spaces']
      - name: research
        label: Research (supporting studies)
        hint: Renders a "What the research shows" section. Peer-reviewed / institutional findings with a short headline stat.
        widget: list
        required: false
        default: []
        fields:
          - { name: stat, label: Headline stat (optional), widget: string, required: false }
          - { name: finding, label: Finding (plain language), widget: text }
          - { name: citation, label: Citation (authors, venue, year), widget: string }
          - name: url
            label: URL
            widget: string
            pattern: ['^https?:\/\/[^\s]+$', 'A full web address starting with http:// or https:// — no spaces']
      - name: stories
        label: Real stories
        hint: Renders a "Real stories" section. Lived-experience accounts of real people showing the benefit of this article's principle. Each links out to the full account.
        widget: list
        required: false
        default: []
        fields:
          - { name: person, label: Who the story is about, widget: string }
          - { name: summary, label: Summary (what happens + the benefit), widget: text }
          - { name: quote, label: Pull-quote (optional), widget: text, required: false }
          - { name: source, label: Source (publication), widget: string }
          - name: url
            label: URL
            widget: string
            pattern: ['^https?:\/\/[^\s]+$', 'A full web address starting with http:// or https:// — no spaces']
      - name: updatedAt
        label: Last updated
        widget: datetime
        required: false
        date_format: 'yyyy-MM-dd'
        time_format: false
      - name: image
        label: Hero image (optional)
        hint: >-
          Optional banner shown at the top of the page. Leave blank to use
          the shared default. Upload or pick a landscape image (final art should
          be a JPG/WebP).
        widget: image
        required: false
      - name: imageAlt
        label: Hero image — alt text
        widget: string
        required: false
        hint: >-
          What a screen reader reads aloud. Describe the picture that is actually
          there — write it from the photo, not from its filename. Leave it empty
          only when the image is decorative and the heading beside it already says
          everything. Never write a note to yourself here; it is read out word for
          word.
      - name: episode
        label: Episode number (e.g. S2E04)
        widget: string
        required: false
      # Carried over from the WordPress import. These belong to the piece, not
      # to the shelf it used to sit on, so they follow it: a story keeps the day
      # it was told, and `originalUrl` is the only record of where it lived
      # before. Declared here because Sveltia writes back only the fields it
      # knows about — an undeclared field is silently deleted on the first save.
      - { name: date, label: Date first published, widget: datetime, required: false }
      - { name: author, label: Author, widget: string, required: false }
      - name: excerpt
        label: Excerpt (shown on the index card)
        widget: text
        required: false
      - name: categories
        label: Categories (legacy WordPress slugs - provenance only)
        widget: list
        required: false
        default: []
        field: { name: category, label: Category, widget: string }
      - name: originalUrl
        label: Original WordPress URL
        widget: string
        required: false
        pattern: ['^$|^https?:\/\/[^\s]+$', 'Leave empty, or give a full web address starting with http:// or https:// - no spaces']
      - name: body
        label: Body (Markdown)
        widget: markdown

  # ---------------------------------------------------------------------------
  - name: come_follow_me
    label: Come, Follow Me
    label_singular: Reading
    description: >-
      Where the family shows up in the block someone is studying this week. Filed
      by curriculum book and scripture block, never by date - the cycle runs four
      years, so a reading written for Isaiah in 2022 is exactly what a reader
      studying Isaiah wants now. Changing `book` or `block` MOVES the page.
    folder: _content/come-follow-me
    path: '{{fields.book}}/{{fields.block}}'
    create: true
    extension: md
    format: frontmatter
    identifier_field: title
    summary: '{{book}}  ·  {{block}}  ·  {{title}}'
    sortable_fields: [book, week, title, status]
    fields:
      - { name: title, label: Title, widget: string }
      - name: book
        label: Curriculum book
        widget: select
        options:
          - { label: Old Testament, value: old-testament }
          - { label: New Testament, value: new-testament }
          - { label: Book of Mormon, value: book-of-mormon }
          - { label: Doctrine and Covenants, value: doctrine-and-covenants }
      - name: block
        label: Scripture block (this is the web address)
        hint: >-
          Lowercase, hyphenated, and named after the scripture - `isaiah-1-12`,
          `2-nephi-6-10`. This is half the page's address, so changing it moves
          the page. Where the block is genuinely unknown, leave what is here
          rather than guessing a week: a wrong week is worse than an honest one.
        widget: string
        pattern: ['^[a-z0-9]+(-[a-z0-9]+)*$', 'Lowercase letters, numbers and hyphens only']
      - name: curriculumYear
        label: Curriculum year
        hint: Which year of the four-year cycle this was written for.
        widget: number
        value_type: int
        min: 2000
        max: 2100
      - name: week
        label: Week (for ordering only, not the address)
        widget: number
        value_type: int
        required: false
        min: 1
        max: 53
      - name: topics
        label: Topics
        hint: One or more topics this belongs to. Surfaces on each topic's page.
        widget: relation
        collection: topics
        value_field: id
        search_fields: [display_name, id]
        display_fields: [display_name]
        multiple: true
        min: 1
        filters:
          - field: archived
            values: [false]
      - name: status
        label: Status
        widget: select
        default: DRAFT
        options: [DRAFT, PUBLISHED]
      - { name: date, label: Date first published, widget: datetime, required: false }
      - { name: author, label: Author, widget: string, required: false }
      - name: excerpt
        label: Excerpt
        widget: text
        required: false
      - { name: aiGenerated, label: 'AI-generated (uncheck after human review)', widget: boolean, default: false }
      - name: chatQuestions
        label: Chat questions that route here
        widget: list
        required: false
        default: []
        field: { name: question, label: Question, widget: string }
      - name: askPrompts
        label: '"Try asking" prompts'
        widget: list
        required: false
        default: []
        field: { name: prompt, label: Prompt, widget: string }
      - name: related
        label: Related page IDs
        hint: Filenames (without .md) of related pages, on any shelf.
        widget: list
        required: false
        default: []
        field: { name: id, label: Page ID, widget: string }
      - { name: image, label: Hero image (optional), widget: image, required: false }
      - name: imageAlt
        label: Hero image - alt text
        widget: string
        required: false
        hint: >-
          What a screen reader reads aloud. Describe the picture that is actually
          there. Leave it empty only when the image is decorative.
      - name: body
        label: Body (Markdown)
        widget: markdown

  # ---------------------------------------------------------------------------
  # ---------------------------------------------------------------------------
  - name: lessons
    label: Lessons
    label_singular: Lesson
    description: >-
      Teacher-facing lesson plans (one Markdown file each). The whole lesson —
      opening quote, objective, materials, and the "Read Aloud" sections — lives
      in the body. Assign each to one or more topics; it surfaces on the matching
      Topics pages. Set status to PUBLISHED only when ready to go live.
    folder: _content/lessons
    create: true
    extension: md
    format: frontmatter
    identifier_field: title
    summary: '{{order}}. {{title}}  ·  {{status}}'
    sortable_fields: [order, title, status]
    fields:
      - { name: title, label: Title, widget: string }
      - name: order
        label: Order (position on the Lessons page)
        widget: number
        value_type: int
        min: 1
      - name: topics
        label: Topics
        hint: One or more topics this lesson belongs to. Surfaces on each topic's page.
        widget: relation
        collection: topics
        value_field: id
        search_fields: [display_name, id]
        display_fields: [display_name]
        multiple: true
        min: 1
        # Archived topics are hidden from the public site — `getPublicTagMap()`
        # drops them and Browse then drops the piece along with them, silently.
        # Without this filter every archived topic stayed pickable, which is how
        # 116 topic slots drifted back onto retired tags after the June
        # consolidation and quietly took those pages out of Browse.
        filters:
          - field: archived
            values: [false]
      - name: status
        label: Status
        widget: select
        default: DRAFT
        options: [DRAFT, PUBLISHED]
      - name: summary
        label: Summary (shown on the Lessons index card)
        widget: text
        required: false
      - { name: image, label: Hero image (banner + index card header), widget: image, required: false }
      - name: imageAlt
        label: Hero image — alt text
        widget: string
        required: false
        hint: >-
          What a screen reader reads aloud. Describe the picture that is actually
          there — write it from the photo, not from its filename. Leave it empty
          only when the image is decorative and the heading beside it already says
          everything. Never write a note to yourself here; it is read out word for
          word.
      - name: chatQuestions
        label: Chat questions that route here
        hint: Question phrasings that should surface this lesson in chat. One per line. (The title is always matched too.)
        widget: list
        required: false
        default: []
        field: { name: question, label: Question, widget: string }
      - name: askPrompts
        label: '"Try asking" prompts'
        hint: Questions shown at the foot of the lesson; each deep-links into chat with the question pre-filled.
        widget: list
        required: false
        default: []
        field: { name: prompt, label: Prompt, widget: string }
      - name: body
        label: Lesson body (Markdown)
        widget: markdown

  # ---------------------------------------------------------------------------
  - name: blog
    label: Blog
    label_singular: Blog post
    description: >-
      What we are thinking about right now - news reactions, talk coverage,
      research releases, announcements. The one shelf where the date is the
      point. If a post would still be worth reading in four years, it belongs
      on Essays; if it is about one named person, on Stories.
    folder: _content/blog
    create: true
    extension: md
    format: frontmatter
    identifier_field: title
    summary: '{{date}}  ·  {{title}}  ·  {{status}}'
    sortable_fields: [date, title, status]
    fields:
      - { name: title, label: Title, widget: string }
      - { name: date, label: Date published, widget: datetime }
      - name: topics
        label: Topics
        hint: One or more topics this belongs to. Surfaces on each topic's page.
        widget: relation
        collection: topics
        value_field: id
        search_fields: [display_name, id]
        display_fields: [display_name]
        multiple: true
        min: 1
        filters:
          - field: archived
            values: [false]
      - name: askPrompts
        label: '"Try asking" prompts'
        hint: Questions shown at the foot of the post; each deep-links into chat with the question pre-filled.
        widget: list
        required: false
        default: []
        field: { name: prompt, label: Prompt, widget: string }
      - name: related
        label: Related page IDs
        hint: Filenames (without .md) of related pages, on any shelf.
        widget: list
        required: false
        default: []
        field: { name: id, label: Page ID, widget: string }
      - { name: author, label: Author, widget: string, default: Relatable Team }
      - name: excerpt
        label: Excerpt (shown on the Blog index card)
        widget: text
        required: false
      - name: categories
        label: Categories (legacy WordPress slugs - provenance only)
        widget: list
        required: false
        default: []
        field: { name: category, label: Category, widget: string }
      - { name: image, label: Featured image, widget: image, required: false }
      - name: imageAlt
        label: Featured image — alt text
        widget: string
        required: false
        hint: >-
          What a screen reader reads aloud. Describe the picture that is actually
          there — write it from the photo, not from its filename. Leave it empty
          only when the image is decorative and the heading beside it already says
          everything. Never write a note to yourself here; it is read out word for
          word.
      - name: originalUrl
        label: Original WordPress URL
        widget: string
        required: false
        pattern: ['^$|^https?:\/\/[^\s]+$', 'Leave empty, or give a full web address starting with http:// or https:// — no spaces']
      - name: aiGenerated
        label: AI-generated (shows a review banner)
        widget: boolean
        default: false
      - name: status
        label: Status
        widget: select
        default: DRAFT
        options: [DRAFT, PUBLISHED]
      - name: body
        label: Post body (Markdown)
        widget: markdown

  # ---------------------------------------------------------------------------
  - name: topics
    label: Taxonomy Topics
    label_singular: Topic
    description: >-
      The corpus topic taxonomy — used by the scoring lambda to assign topic
      relevance to every ingested document, and by the site for /topics pages.
      Renaming a topic is cosmetic. Adding, splitting, or merging topics
      changes scoring; archived topics are retained so historical scores still
      resolve. The site build regenerates /content-api/taxonomy.json from
      these files; the scoring lambda picks up the new taxonomy on its TTL
      after Matthew publishes the site.
    folder: _content/taxonomy/topics
    create: true
    extension: md
    format: frontmatter
    identifier_field: id
    summary: '{{display_name}}  ·  {{category}}  ·  {{archived | ternary("archived", "active")}}'
    sortable_fields: [id, display_name, category, archived]
    fields:
      - name: id
        label: ID (lowercase-kebab)
        hint: >-
          The stable taxonomy key. Used by scoring rows and by every API.
          Do not change after a topic has been scored against the corpus.
        widget: string
      - { name: display_name, label: Display name, widget: string }
      - name: category
        label: Category
        widget: relation
        collection: categories
        file: categories
        value_field: 'categories.*.id'
        display_fields: ['categories.*.display_name']
        search_fields: ['categories.*.display_name', 'categories.*.id']
      - name: archived
        label: Archived
        hint: >-
          Hide from the public site and from new scoring runs, while keeping
          the ID resolvable for historical scoring rows. Prefer this over
          deleting a topic.
        widget: boolean
        default: false
      - name: body
        label: Description (Markdown)
        hint: >-
          Long-form description of what this topic covers. Used as the topic
          prototype for LLM scoring and as the topic-page intro on the site.
        widget: markdown

  # ---------------------------------------------------------------------------
  - name: categories
    label: Taxonomy Categories
    label_singular: Category
    description: >-
      Top-level grouping that organises the topic list. Editorial / browse-only;
      not consumed by scoring.
    files:
      - name: categories
        label: Categories
        file: _content/taxonomy/categories.yml
        fields:
          - name: categories
            label: Categories
            widget: list
            summary: '{{display_name}}  ·  {{id}}'
            fields:
              - { name: id, label: ID (lowercase-kebab), widget: string }
              - { name: display_name, label: Display name, widget: string }
              - { name: description, label: Description, widget: text }

  # ---------------------------------------------------------------------------
  - name: proclamation
    label: Proclamation Paragraphs
    label_singular: Paragraph
    description: >-
      The nine paragraphs of the Proclamation — structured metadata plus the
      editorial commentary body. The set of nine is fixed; new entries are
      disabled.
    folder: _content/proclamation
    create: false
    delete: false
    extension: md
    format: frontmatter
    identifier_field: title
    summary: 'Paragraph {{paragraphNumber}}  ·  {{title}}  ·  {{status}}'
    sortable_fields: [paragraphNumber, title, status]
    fields:
      - name: paragraphNumber
        label: Paragraph number
        widget: number
        value_type: int
        min: 1
        max: 9
      - { name: title, label: Title, widget: string }
      - { name: summary, label: Summary, widget: text }
      - name: accentColor
        label: Accent color
        widget: color
        enableAlpha: false
        allowInput: true
      - name: status
        label: Status
        widget: select
        default: DRAFT
        options: [DRAFT, PUBLISHED]
      - name: sentenceIds
        label: Sentence IDs
        hint: Canonical sentence keys (s1, s2, ...) this paragraph covers.
        widget: list
        field: { name: id, label: Sentence ID, widget: string }
      - name: topics
        label: Topics
        hint: Taxonomy tag IDs associated with this paragraph.
        widget: list
        required: false
        default: []
        field: { name: topic, label: Topic ID, widget: string }
      - name: scriptures
        label: Scripture references
        widget: list
        required: false
        default: []
        fields:
          - { name: reference, label: Reference, widget: string }
          - name: url
            label: URL
            widget: string
            required: false
            pattern: ['^$|^https?:\/\/[^\s]+$', 'Leave empty, or give a full web address starting with http:// or https:// — no spaces']
      - name: videos
        label: Videos
        widget: list
        required: false
        default: []
        fields:
          - { name: youtubeId, label: YouTube ID, widget: string }
          - { name: title, label: Title, widget: string }
          - { name: caption, label: Caption, widget: string, required: false }
      - name: body
        label: Commentary (Markdown)
        widget: markdown

  # ---------------------------------------------------------------------------
  - name: proclamation_explore
    label: Proclamation — Explore (per phrase)
    description: >-
      Per-phrase resources for the line-by-line Explore experience. A long
      sentence is split into phrases, each with its own entry (`s19a`, `s19b`,
      …); a sentence that is not split has a single entry ending in `a`. The
      files are generated from the canonical text — do not create or delete them
      here. Build each phrase as an ordered list of resource blocks: drag to
      reorder, and add as many as you like in whatever order fits the line. Set
      status to PUBLISHED when the phrase is ready.
    folder: _content/proclamation-explore
    create: false
    delete: false
    extension: yml
    format: yaml
    identifier_field: id
    summary: '{{fields.id}}  ·  {{fields.status}}  ·  {{fields.text}}'
    sortable_fields: [id, status]
    fields:
      - name: id
        label: Phrase ID
        widget: string
        readonly: true
        hint: >-
          Which phrase this file belongs to. The site matches the two by this
          code, so it is shown for your bearings and cannot be changed here.
      - name: text
        label: Phrase (reference only — do not edit)
        widget: string
        required: false
      - name: status
        label: Status
        widget: select
        default: DRAFT
        options: [DRAFT, PUBLISHED]
      - name: blocks
        label: Resource blocks (drag to reorder)
        widget: list
        required: false
        default: []
        types:
          - name: commentary
            label: Commentary
            widget: object
            summary: 'Commentary'
            fields:
              - { name: body, label: Commentary (Markdown), widget: markdown }
          - name: pullQuote
            label: Pull-quote
            widget: object
            summary: '“{{fields.text}}”'
            fields:
              - { name: text, label: Quote, widget: text }
              - { name: attribution, label: Attribution, widget: string }
              - { name: source, label: Source, widget: string, required: false }
          - name: watch
            label: Watch (video)
            widget: object
            summary: 'Watch ({{fields.layout}})'
            fields:
              - name: layout
                label: Layout
                widget: select
                default: rail
                options: [featured, rail]
                hint: '"featured" shows one large player; "rail" shows a swipeable row.'
              - name: clips
                label: Clips
                widget: list
                fields:
                  - { name: youtubeId, label: YouTube ID, widget: string }
                  - { name: caption, label: Caption, widget: string, required: false }
          - name: ask
            label: Ask (chat prompts)
            widget: object
            summary: 'Ask ({{fields.prompts.length}})'
            fields:
              - name: prompts
                label: Prompts
                hint: Each becomes a tappable pill that opens chat with the question asked.
                widget: list
                field: { name: prompt, label: Prompt, widget: string }
          - name: scriptures
            label: Scriptures
            widget: object
            summary: 'Scriptures'
            fields:
              - name: refs
                label: References
                widget: list
                fields:
                  - { name: reference, label: Reference, widget: string }
                  - name: url
                    label: URL
                    widget: string
                    required: false
                    pattern: ['^$|^https?:\/\/[^\s]+$', 'Leave empty, or give a full web address starting with http:// or https:// — no spaces']
          - name: articles
            label: Further reading
            widget: object
            summary: 'Further reading'
            fields:
              - name: slugs
                label: Pages
                hint: >-
                  Filenames (without .md) of pages to link from this line. They can
                  be on any shelf - an answer, an essay, a story. This is typed
                  rather than picked because a picker can only offer one shelf, and
                  restricting it to one is how the stories became unlinkable. A name
                  that matches nothing fails the build with a message naming it.
                widget: list
                field: { name: id, label: Page ID, widget: string }
          - name: research
            label: Research (supporting studies)
            widget: object
            summary: 'Research ({{fields.studies.length}})'
            fields:
              - name: studies
                label: Studies
                hint: Peer-reviewed / institutional social-science findings that support this line. Each links out to the source.
                widget: list
                fields:
                  - { name: stat, label: Headline stat (optional), widget: string, required: false }
                  - { name: finding, label: Finding (plain language), widget: text }
                  - { name: citation, label: Citation (authors, venue, year), widget: string }
                  - name: url
                    label: URL
                    widget: string
                    pattern: ['^https?:\/\/[^\s]+$', 'A full web address starting with http:// or https:// — no spaces']
          - name: stories
            label: Real stories
            widget: object
            summary: 'Stories ({{fields.items.length}})'
            fields:
              - name: items
                label: Stories
                hint: Lived-experience accounts of real people showing the benefit of living this line's principle. Each links out to the full account.
                widget: list
                fields:
                  - { name: person, label: Who the story is about, widget: string }
                  - { name: summary, label: Summary (what happens + the benefit), widget: text }
                  - { name: quote, label: Pull-quote (optional), widget: text, required: false }
                  - { name: source, label: Source (publication), widget: string }
                  - name: url
                    label: URL
                    widget: string
                    pattern: ['^https?:\/\/[^\s]+$', 'A full web address starting with http:// or https:// — no spaces']

  # ---------------------------------------------------------------------------
  # Proclamation — Study (per phrase). The deepest content on the site: 1,184
  # scriptures, talks, stories and studies across 38 files, one per phrase.
  #
  # Three things shape this collection.
  #
  # An item's `kind` decides which fields are legal, and the Zod schema enforces
  # it: a quote keeps its words in `quotation`, a commentary in `body`, and the
  # build rejects the crossover. So `kind` is the list's `typeKey` and an editor
  # picks the kind first — the boxes that appear are the only ones that kind can
  # have, and the box that would break the build is never on screen.
  #
  # The four resource lists (scriptures / scholarship / stories / resources)
  # take identical items, so they share one definition through the YAML anchor
  # `&enrichment_items` declared on Scriptures.
  #
  # And what the CMS does NOT declare, it deletes. Sveltia writes back only the
  # fields in this config, so every field the files actually use is here, and
  # `id` and `text` are present-but-read-only rather than absent — `id` is the
  # key the build joins on and `text` is a copy of the canonical 1995 sentence,
  # which lives in _content/taxonomy/family-proclamation.yml.
  #
  # ONE RULE THIS CONFIG CANNOT EXPRESS. Sveltia 0.162 has no conditional
  # `required`, so three of the schema's rules can only be stated in a hint and
  # caught by the build: an image needs `image_alt`; a quote needs its words
  # unless it is marked lost; a quote needs a citation or a scripture reference
  # unless it is marked as needing sourcing.
  - name: proclamation_enrichment
    label: Proclamation — Study (per phrase)
    description: >-
      Everything behind the little markers on the Proclamation. Open a marker on
      the reading page and this is what opens with it: the scriptures, prophetic
      teachings, stories and research gathered for that one phrase. There is one
      entry per phrase and they are already made — pick the phrase you want and
      add to it. Group what you add under a section with a heading ("Adoption",
      "Fidelity"); the first section usually has no heading and is the phrase's
      main body. Set the status to Published when a phrase is ready to be seen.
    folder: _content/proclamation-enrichment
    create: false
    delete: false
    extension: yml
    format: yaml
    identifier_field: id
    summary: '{{fields.id}}  ·  {{fields.status}}  ·  {{fields.text}}'
    sortable_fields: [id, status]
    fields:
      - name: id
        label: Phrase ID
        widget: string
        readonly: true
        hint: >-
          Which phrase this file belongs to. The site matches the two by this
          code, so it is shown for your bearings and cannot be changed here.
      - name: text
        label: The phrase itself
        widget: string
        required: false
        readonly: true
        hint: >-
          A copy of the line from the Proclamation, so you can see what you are
          adding to. The wording that actually appears on the page comes from
          the 1995 text, not from here.
      - name: status
        label: Status
        widget: select
        default: DRAFT
        options: [DRAFT, PUBLISHED]
        hint: Draft entries are kept but not shown to visitors.
      - name: sections
        label: Sections (drag to reorder)
        label_singular: Section
        widget: list
        collapsed: true
        summary: '{{fields.title}}'
        hint: >-
          A section groups the resources for one aspect of the phrase. The first
          one usually has no heading and holds the phrase's main material; give
          later ones a heading naming what they are about.
        fields:
          - name: title
            label: Section heading
            widget: string
            required: false
            hint: >-
              Leave empty for the opening section. Otherwise a short topic, in
              the reader's words — "Adoption", "When a marriage ends".
          - name: scriptures
            label: Scriptures and prophetic teachings
            label_singular: Entry
            widget: list
            required: false
            default: []
            collapsed: true
            typeKey: kind
            hint: >-
              Revealed word: scripture, and the teachings of prophets and
              apostles. Pick what each entry is, then fill in the boxes it shows.
            types: &enrichment_items
              - name: quote
                label: Quote or scripture
                widget: object
                summary: '“{{fields.quotation}}” — {{fields.reference}}{{fields.citation}}'
                fields:
                  - name: quotation
                    label: The words themselves
                    widget: text
                    required: false
                    hint: >-
                      Exactly what was said or written, word for word. Leave a
                      blank line between paragraphs and each becomes its own
                      paragraph. The only reason to leave this empty is that the
                      wording is genuinely lost — tick "wording lost" below if so,
                      or the site will refuse the file.
                  - name: reference
                    label: Scripture reference
                    widget: string
                    required: false
                    hint: >-
                      For scripture only, in the short form — "Gen. 2:24",
                      "1 Ne. 13:24". Shown as a small label under the quote. A
                      scripture needs nothing in the box below; a talk does.
                  - name: citation
                    label: Who said it, and where
                    widget: string
                    required: false
                    hint: >-
                      "Elder Jeffrey R. Holland, 'Sanctify Yourselves', Ensign
                      Nov. 2000". Shown under the quote. Every quote needs either
                      this or a scripture reference above; if you cannot find
                      either, tick "needs sourcing" so it is not forgotten.
                  - name: url
                    label: Link to the source
                    widget: string
                    required: false
                    hint: Where a reader can go and read the whole thing.
                  - name: image
                    label: Picture or chart (optional)
                    widget: image
                    required: false
                    media_folder: /_content/assets/img/study
                    public_folder: /assets/img/study
                    hint: >-
                      A chart, a table or a photograph shown with this entry.
                      Most entries have none.
                  - name: image_alt
                    label: What the picture shows
                    widget: string
                    required: false
                    hint: >-
                      Required whenever there is a picture — the site refuses the
                      file without it. This is what someone using a screen reader
                      hears instead of the picture, so describe what it SHOWS. For
                      a chart, give the finding and the numbers, not the words
                      "a chart".
                  - name: featured
                    label: Show on the Explore card too
                    widget: boolean
                    default: false
                    hint: >-
                      Featured entries are the ones that appear on the lighter
                      Explore card, not only on the full Study page.
                  - name: todo
                    label: Needs sourcing
                    widget: boolean
                    default: false
                    hint: >-
                      Tick when the words are right but the citation or the link
                      is missing. The page shows a small marker so someone can
                      finish it later, rather than the entry being dropped.
                  - name: quotationLost
                    label: Wording lost
                    widget: boolean
                    default: false
                    hint: >-
                      Only for the handful of entries where we know the source
                      and can link to it, but the quoted words themselves are
                      gone and cannot be recovered. Never tick this to skip
                      typing a quotation you have.
              - name: commentary
                label: Our own commentary
                widget: object
                summary: '{{fields.body}}'
                fields:
                  - name: body
                    label: Commentary
                    widget: text
                    hint: >-
                      Prose written for this site — an explanation, a bridge
                      between two sources, a bit of context. Not a quotation from
                      anyone; those are entries of their own. Leave a blank line
                      between paragraphs.
                  - name: citation
                    label: Source line (optional)
                    widget: string
                    required: false
                    hint: >-
                      Use when the commentary leans on something in particular
                      and you want to say what.
                  - name: url
                    label: Link (optional)
                    widget: string
                    required: false
                  - name: image
                    label: Picture or chart (optional)
                    widget: image
                    required: false
                    media_folder: /_content/assets/img/study
                    public_folder: /assets/img/study
                  - name: image_alt
                    label: What the picture shows
                    widget: string
                    required: false
                    hint: >-
                      Required whenever there is a picture — the site refuses the
                      file without it. What someone using a screen reader hears
                      instead of the picture: describe what it shows.
                  - name: featured
                    label: Show on the Explore card too
                    widget: boolean
                    default: false
                  - name: todo
                    label: Needs sourcing
                    widget: boolean
                    default: false
              - name: video
                label: Video
                widget: object
                summary: '{{fields.body}}'
                fields:
                  - name: body
                    label: Video title
                    widget: string
                    hint: What the video is called, shown above the player.
                  - name: youtubeId
                    label: YouTube ID
                    widget: string
                    required: false
                    hint: >-
                      Just the id, not the whole address — the part after
                      "watch?v=". For youtu.be/VFvOjokh5VY that is VFvOjokh5VY.
                  - name: sourceUrl
                    label: Old video address (only if it is not on YouTube yet)
                    widget: string
                    required: false
                    hint: >-
                      A few videos still live on the old site's player. Putting
                      the address here keeps them playable and puts them on the
                      list to be moved to YouTube. Leave empty if you filled in
                      the YouTube id above.
                  - name: featured
                    label: Show on the Explore card too
                    widget: boolean
                    default: false
                  - name: todo
                    label: Needs sourcing
                    widget: boolean
                    default: false
          - name: scholarship
            label: Scholarship and research
            label_singular: Entry
            widget: list
            required: false
            default: []
            collapsed: true
            typeKey: kind
            hint: >-
              Peer-reviewed and institutional research — marriage, parenting,
              child development, psychology, sociology. A companion to revealed
              doctrine, never a substitute for it.
            types: *enrichment_items
          - name: stories
            label: Stories
            label_singular: Entry
            widget: list
            required: false
            default: []
            collapsed: true
            typeKey: kind
            hint: >-
              First-person accounts of people living this line — what it looked
              like for them, in their own words wherever possible.
            types: *enrichment_items
          - name: resources
            label: Other resources
            label_singular: Entry
            widget: list
            required: false
            default: []
            collapsed: true
            typeKey: kind
            hint: >-
              Anything trustworthy that belongs here and is none of the three
              above — a handbook section, a Church web page, a video, a talk that
              is not a quotation.
            types: *enrichment_items

  # ---------------------------------------------------------------------------
  # Daily insights: the dated lines behind the home-page "Today's insight" card,
  # stored one file per month in _content/daily-insights/ (they used to be a
  # 366-row block inside home.yml, which made that page unusable to edit).
  #
  # The date list is FIXED. Each month's list sets allow_add / allow_remove /
  # allow_duplicate / allow_reorder to false and marks the date read-only, so an
  # editor can change a day's quote but can never add, delete or reshuffle days.
  # Rows are collapsed with a "MM-DD — quote" summary, so a month reads as a
  # scannable calendar. The twelve months share one field definition via the
  # YAML anchor `&insight_items` declared on January.
  - name: daily_insights
    label: Daily Insights
    description: >-
      One line per calendar date for the home-page insight card. Pick a month,
      then edit that day's quote, attribution and link. Dates are fixed — they
      cannot be added or removed here.
    files:
      - name: january
        label: January
        file: _content/daily-insights/01-january.yml
        fields:
          - { name: month, widget: hidden, default: '01' }
          - { name: label, widget: hidden, default: January }
          - &insight_items
            name: items
            label: Insight lines
            label_singular: Insight line
            widget: list
            collapsed: true
            summary: '{{fields.date}} — {{fields.text}}'
            allow_add: false
            allow_remove: false
            allow_duplicate: false
            allow_reorder: false
            hint: >-
              One row per calendar date, shown on the home page on that date.
              Edit the quote, attribution and link; the date itself is fixed.
            fields:
              - { name: date, label: Date (MM-DD), widget: string, readonly: true }
              - { name: text, label: Insight text, widget: text }
              - { name: attribution, label: Attribution (who/where), widget: string, required: false }
              - { name: link, label: Link URL, widget: string, required: false }
              - { name: link_label, label: Link label, widget: string, required: false }
      - name: february
        label: February
        file: _content/daily-insights/02-february.yml
        fields:
          - { name: month, widget: hidden, default: '02' }
          - { name: label, widget: hidden, default: February }
          - *insight_items
      - name: march
        label: March
        file: _content/daily-insights/03-march.yml
        fields:
          - { name: month, widget: hidden, default: '03' }
          - { name: label, widget: hidden, default: March }
          - *insight_items
      - name: april
        label: April
        file: _content/daily-insights/04-april.yml
        fields:
          - { name: month, widget: hidden, default: '04' }
          - { name: label, widget: hidden, default: April }
          - *insight_items
      - name: may
        label: May
        file: _content/daily-insights/05-may.yml
        fields:
          - { name: month, widget: hidden, default: '05' }
          - { name: label, widget: hidden, default: May }
          - *insight_items
      - name: june
        label: June
        file: _content/daily-insights/06-june.yml
        fields:
          - { name: month, widget: hidden, default: '06' }
          - { name: label, widget: hidden, default: June }
          - *insight_items
      - name: july
        label: July
        file: _content/daily-insights/07-july.yml
        fields:
          - { name: month, widget: hidden, default: '07' }
          - { name: label, widget: hidden, default: July }
          - *insight_items
      - name: august
        label: August
        file: _content/daily-insights/08-august.yml
        fields:
          - { name: month, widget: hidden, default: '08' }
          - { name: label, widget: hidden, default: August }
          - *insight_items
      - name: september
        label: September
        file: _content/daily-insights/09-september.yml
        fields:
          - { name: month, widget: hidden, default: '09' }
          - { name: label, widget: hidden, default: September }
          - *insight_items
      - name: october
        label: October
        file: _content/daily-insights/10-october.yml
        fields:
          - { name: month, widget: hidden, default: '10' }
          - { name: label, widget: hidden, default: October }
          - *insight_items
      - name: november
        label: November
        file: _content/daily-insights/11-november.yml
        fields:
          - { name: month, widget: hidden, default: '11' }
          - { name: label, widget: hidden, default: November }
          - *insight_items
      - name: december
        label: December
        file: _content/daily-insights/12-december.yml
        fields:
          - { name: month, widget: hidden, default: '12' }
          - { name: label, widget: hidden, default: December }
          - *insight_items

  # ---------------------------------------------------------------------------
  # Design-heavy pages: each is one "string library" file. Layout and styling
  # stay in code; only the copy below is editable.
  - name: pages
    label: Site Pages
    description: Editable copy for the standalone pages and global site chrome.
    files:
      - name: home
        label: Home Page
        file: _content/pages/home.yml
        fields:
          - name: hero
            label: Hero
            widget: object
            fields:
              - { name: headline, label: Headline (shown above the chat bar), widget: string, required: false }
              - { name: banner_src, label: Banner image, widget: image, required: false }
              - { name: banner_alt, label: Banner alt text, widget: text }
              - name: chat_typewriter
                label: Chat questions (typewriter)
                widget: list
                hint: >-
                  Looping placeholder questions for the home hero chat box and
                  the /chat empty state (both share this list). The order is
                  shuffled on every page load. Keep each one short, plain, and a
                  single question — no compound "and" questions.
                field: { name: question, label: Question, widget: string }
              - name: typewriter_timing
                label: Typewriter speed (milliseconds)
                widget: object
                hint: Animation timing in milliseconds. Lower = faster.
                fields:
                  - { name: typeMs, label: Type speed (per character), widget: number, value_type: int, default: 45 }
                  - { name: deleteMs, label: Delete speed (per character), widget: number, value_type: int, default: 22 }
                  - { name: holdMs, label: Hold after a finished question, widget: number, value_type: int, default: 2700 }
                  - { name: nextMs, label: Pause before the next question, widget: number, value_type: int, default: 220 }
                  - { name: startDelayMs, label: Delay before animation starts, widget: number, value_type: int, default: 700 }
              - { name: chat_action, label: Chat form action (URL), widget: string }
              - { name: chat_field, label: Chat query parameter name, widget: string, default: q }
              - name: try_badges
                label: Topic chip links (home hero only)
                widget: list
                fields:
                  - { name: label, label: Badge label, widget: string }
                  - { name: query, label: Chat query, widget: string }
          - name: tiles
            label: Explore tiles
            widget: list
            fields:
              - { name: href, label: Link, widget: string }
              - { name: icon, label: Icon slug, widget: string, hint: "One of: ask, proclamation, topics, media" }
              - { name: title, label: Title, widget: string }
              - { name: description, label: Description, widget: text }
          - name: trust
            label: Trust band
            widget: object
            fields:
              - { name: text, label: Text, widget: text }
              - { name: link_label, label: Link label, widget: string }
              - { name: link_href, label: Link URL, widget: string }
          - name: daily_insight
            label: Daily insight widget
            widget: object
            hint: >-
              A small home-page card showing one line per calendar day (same for
              every visitor on a given day, advances the next day). The lines
              themselves are edited under "Daily Insights" in the sidebar — this
              is only the on/off switch and the little label above the line.
            fields:
              - { name: enabled, label: Show the widget, widget: boolean, default: true }
              - { name: eyebrow, label: Small label above the line, widget: string, default: "Today's insight" }

      - name: proclamation
        label: Proclamation Page
        file: _content/pages/proclamation.yml
        fields:
          - name: hero
            label: Hero
            widget: object
            fields:
              - { name: eyebrow, label: Eyebrow, widget: string }
              - { name: title, label: Title, widget: string }
              - { name: intro, label: Intro (optional — hero reads best title-only), widget: text, required: false }
              - { name: image, label: Hero background image, widget: image, required: false }
              - name: image_alt
                label: Hero image — alt text
                widget: string
                required: false
                hint: >-
                  What a screen reader reads aloud. Describe the picture that is actually
                  there — write it from the photo, not from its filename. Leave it empty
                  only when the image is decorative and the heading beside it already says
                  everything. Never write a note to yourself here; it is read out word for
                  word.
              - name: focus
                label: Which part of the picture to keep
                widget: string
                default: center
                required: false
                pattern:
                  ['^(top|bottom|left|right|center|\d{1,3}%)( +(top|bottom|left|right|center|\d{1,3}%))?$', 'Write one or two of top / bottom / left / right / center, or a percentage — like "top" or "50% 30%"']
                hint: >-
                  Which part of the picture survives when the hero is cropped.
                  A hero is only ever as tall as the design allows, so on a wide
                  screen the picture is trimmed to fit — and it is trimmed off
                  the top and the bottom equally unless this says otherwise,
                  which is how faces near the top of a photo lose the tops of
                  their heads. Write "top" to keep the top of the picture,
                  "bottom" for the bottom, "center" (the default) for the
                  middle, or a percentage for something in between: "50% 30%"
                  means keep the point 30% of the way down.
          - name: explore
            label: '"Explore line by line" button'
            widget: object
            fields:
              - { name: label, label: Button label, widget: string }
          - name: study
            label: Study pages
            widget: object
            fields:
              - name: quotation_lost_note
                label: Note shown when a quotation's wording is missing
                widget: text
                hint: >-
                  A few study entries name a real source and link to it, but the
                  quoted wording itself was lost before we inherited the content.
                  This sentence appears in place of the quotation so readers are
                  not left looking at an empty quote box. Reword it however you
                  would say it.
          - name: reading
            label: Reading surface — how readers find the line markers
            widget: object
            hint: >-
              Every line of the proclamation ends with a small marker that opens
              scriptures, talks and questions about that line. The marker is
              deliberately quiet, so these words are how a reader learns it is
              there.
            fields:
              - name: legend
                label: Legend (shown under the attribution, above the text)
                widget: text
          - name: document
            label: Full-text attribution
            widget: object
            fields:
              - { name: authorship, label: Authorship line (signing body), widget: text }
              - { name: closing, label: Closing attribution, widget: text }
          - name: scriptures
            label: '"Keep it with your scriptures" (PDF)'
            widget: object
            fields:
              - { name: heading, label: Heading, widget: string }
              - { name: body, label: Body, widget: text }
              - { name: label, label: Button label, widget: string }
              - { name: href, label: File URL, widget: string }

      - name: media
        label: Media Page
        file: _content/pages/media.yml
        fields:
          - name: hero
            label: Hero
            widget: object
            fields:
              - { name: eyebrow, label: Eyebrow, widget: string }
              - { name: title, label: Title, widget: string }
              - { name: intro, label: Intro, widget: text }
              - name: background
                label: Hero band
                widget: select
                default: photo
                options:
                  - { label: Photo, value: photo }
                  - { label: Brand gradient (no photo), value: gradient }
                hint: >-
                  What sits behind the words at the top of this page. "Photo" shows
                  the picture below, dimmed so white text stays readable. "Brand
                  gradient" hides it and leaves the plain green band. The picture is
                  kept either way — it is still what shows when someone shares this
                  page on Facebook or in a text message — so switch back any time.
              - { name: image, label: Hero background image, widget: image, required: false }
              - name: image_alt
                label: Hero image — alt text
                widget: string
                required: false
                hint: >-
                  What a screen reader reads aloud. Describe the picture that is actually
                  there — write it from the photo, not from its filename. Leave it empty
                  only when the image is decorative and the heading beside it already says
                  everything. Never write a note to yourself here; it is read out word for
                  word.
              - name: focus
                label: Which part of the picture to keep
                widget: string
                default: center
                required: false
                pattern:
                  ['^(top|bottom|left|right|center|\d{1,3}%)( +(top|bottom|left|right|center|\d{1,3}%))?$', 'Write one or two of top / bottom / left / right / center, or a percentage — like "top" or "50% 30%"']
                hint: >-
                  Which part of the picture survives when the hero is cropped.
                  A hero is only ever as tall as the design allows, so on a wide
                  screen the picture is trimmed to fit — and it is trimmed off
                  the top and the bottom equally unless this says otherwise,
                  which is how faces near the top of a photo lose the tops of
                  their heads. Write "top" to keep the top of the picture,
                  "bottom" for the bottom, "center" (the default) for the
                  middle, or a percentage for something in between: "50% 30%"
                  means keep the point 30% of the way down.
          - name: aside
            label: Sidebar
            widget: object
            fields:
              - { name: listen_label, label: Listen link label, widget: string }
              - { name: rss_label, label: RSS link label, widget: string }
              - { name: note, label: Note, widget: text }

      - name: lessons
        label: Lessons Page
        file: _content/pages/lessons.yml
        fields:
          - name: hero
            label: Hero
            widget: object
            fields:
              - { name: eyebrow, label: Eyebrow, widget: string }
              - { name: title, label: Title, widget: string }
              - { name: intro, label: Intro, widget: text }
              - name: background
                label: Hero band
                widget: select
                default: photo
                options:
                  - { label: Photo, value: photo }
                  - { label: Brand gradient (no photo), value: gradient }
                hint: >-
                  What sits behind the words at the top of this page. "Photo" shows
                  the picture below, dimmed so white text stays readable. "Brand
                  gradient" hides it and leaves the plain green band. The picture is
                  kept either way — it is still what shows when someone shares this
                  page on Facebook or in a text message — so switch back any time.
              - { name: image, label: Hero background image, widget: image, required: false }
              - name: image_alt
                label: Hero image — alt text
                widget: string
                required: false
                hint: >-
                  What a screen reader reads aloud. Describe the picture that is actually
                  there — write it from the photo, not from its filename. Leave it empty
                  only when the image is decorative and the heading beside it already says
                  everything. Never write a note to yourself here; it is read out word for
                  word.
              - name: focus
                label: Which part of the picture to keep
                widget: string
                default: center
                required: false
                pattern:
                  ['^(top|bottom|left|right|center|\d{1,3}%)( +(top|bottom|left|right|center|\d{1,3}%))?$', 'Write one or two of top / bottom / left / right / center, or a percentage — like "top" or "50% 30%"']
                hint: >-
                  Which part of the picture survives when the hero is cropped.
                  A hero is only ever as tall as the design allows, so on a wide
                  screen the picture is trimmed to fit — and it is trimmed off
                  the top and the bottom equally unless this says otherwise,
                  which is how faces near the top of a photo lose the tops of
                  their heads. Write "top" to keep the top of the picture,
                  "bottom" for the bottom, "center" (the default) for the
                  middle, or a percentage for something in between: "50% 30%"
                  means keep the point 30% of the way down.
          - name: includes
            label: '"Every lesson includes" cards'
            widget: list
            fields:
              - { name: icon, label: Icon, widget: select, options: [BookOpen, Megaphone, BarChart3, FileText] }
              - { name: title, label: Title, widget: string }
              - { name: body, label: Body, widget: text }

      - name: about
        label: About Page
        file: _content/pages/about.md
        fields:
          - name: hero
            label: Hero
            widget: object
            fields:
              - { name: eyebrow, label: Eyebrow, widget: string }
              - { name: title, label: Title, widget: string }
              - { name: intro, label: Intro, widget: text }
              - name: background
                label: Hero band
                widget: select
                default: photo
                options:
                  - { label: Photo, value: photo }
                  - { label: Brand gradient (no photo), value: gradient }
                hint: >-
                  What sits behind the words at the top of this page. "Photo" shows
                  the picture below, dimmed so white text stays readable. "Brand
                  gradient" hides it and leaves the plain green band. The picture is
                  kept either way — it is still what shows when someone shares this
                  page on Facebook or in a text message — so switch back any time.
              - { name: image, label: Hero background image, widget: image, required: false }
              - name: image_alt
                label: Hero image — alt text
                widget: string
                required: false
                hint: >-
                  What a screen reader reads aloud. Describe the picture that is actually
                  there — write it from the photo, not from its filename. Leave it empty
                  only when the image is decorative and the heading beside it already says
                  everything. Never write a note to yourself here; it is read out word for
                  word.
              - name: focus
                label: Which part of the picture to keep
                widget: string
                default: center
                required: false
                pattern:
                  ['^(top|bottom|left|right|center|\d{1,3}%)( +(top|bottom|left|right|center|\d{1,3}%))?$', 'Write one or two of top / bottom / left / right / center, or a percentage — like "top" or "50% 30%"']
                hint: >-
                  Which part of the picture survives when the hero is cropped.
                  A hero is only ever as tall as the design allows, so on a wide
                  screen the picture is trimmed to fit — and it is trimmed off
                  the top and the bottom equally unless this says otherwise,
                  which is how faces near the top of a photo lose the tops of
                  their heads. Write "top" to keep the top of the picture,
                  "bottom" for the bottom, "center" (the default) for the
                  middle, or a percentage for something in between: "50% 30%"
                  means keep the point 30% of the way down.
          - { name: body, label: Page body (Markdown), widget: markdown }

      - name: methodology
        label: Methodology Page
        file: _content/pages/methodology.yml
        description: >-
          About → Methodology. What we read, how we rank it when sources
          disagree, how a question becomes an answer, and what we deliberately
          do not do. The About page summarises the 3S Teaching Method in a
          sentence and links here for the detail, so this page is the one to
          change when the method changes.
        fields:
          - name: hero
            label: Hero
            widget: object
            fields:
              - { name: eyebrow, label: Eyebrow, widget: string }
              - { name: title, label: Title, widget: string }
              - { name: intro, label: Intro, widget: text }
              - name: background
                label: Hero band
                widget: select
                default: photo
                options:
                  - { label: Photo, value: photo }
                  - { label: Brand gradient (no photo), value: gradient }
                hint: >-
                  What sits behind the words at the top of this page. "Photo" shows
                  the picture below, dimmed so white text stays readable. "Brand
                  gradient" hides it and leaves the plain green band. The picture is
                  kept either way — it is still what shows when someone shares this
                  page on Facebook or in a text message — so switch back any time.
              - { name: image, label: Hero background image, widget: image, required: false }
              - name: image_alt
                label: Hero image — alt text
                widget: string
                required: false
                hint: >-
                  What a screen reader reads aloud. Describe the picture that is actually
                  there — write it from the photo, not from its filename. Leave it empty
                  only when the image is decorative and the heading beside it already says
                  everything. Never write a note to yourself here; it is read out word for
                  word.
              - name: focus
                label: Which part of the picture to keep
                widget: string
                default: center
                required: false
                pattern:
                  ['^(top|bottom|left|right|center|\d{1,3}%)( +(top|bottom|left|right|center|\d{1,3}%))?$', 'Write one or two of top / bottom / left / right / center, or a percentage — like "top" or "50% 30%"']
                hint: >-
                  Which part of the picture survives when the hero is cropped.
                  A hero is only ever as tall as the design allows, so on a wide
                  screen the picture is trimmed to fit — and it is trimmed off
                  the top and the bottom equally unless this says otherwise,
                  which is how faces near the top of a photo lose the tops of
                  their heads. Write "top" to keep the top of the picture,
                  "bottom" for the bottom, "center" (the default) for the
                  middle, or a percentage for something in between: "50% 30%"
                  means keep the point 30% of the way down.
          - name: pillars
            label: The three pillars
            widget: object
            fields:
              - { name: heading, label: Heading, widget: string }
              - { name: intro, label: Intro line, widget: text }
              - name: items
                label: Pillars
                label_singular: Pillar
                widget: list
                summary: '{{fields.title}}'
                hint: >-
                  The three S's — Scriptures, Stories, Scholarship — the same
                  three named on the About page and in the site tagline. Keep
                  them in that order. Which source wins when they conflict
                  belongs in the "When the three disagree" callout below, not in
                  these three — the icons deliberately do not rank them.
                fields:
                  - { name: title, label: Title, widget: string }
                  - { name: icon, label: Icon, widget: select, options: [BookOpen, MessageSquareQuote, GraduationCap] }
                  - { name: body, label: Body, widget: text }
              - name: disagree
                label: When the three disagree (callout)
                widget: object
                fields:
                  - { name: heading, label: Heading, widget: string }
                  - { name: body, label: Body, widget: text }
          - name: corpus
            label: The corpus
            widget: object
            fields:
              - { name: heading, label: Heading, widget: string }
              - { name: body, label: Body, widget: text }
              - name: stats
                label: Stat tiles
                label_singular: Stat
                widget: list
                summary: '{{fields.label}} — {{fields.figure}}{{fields.value}}'
                hint: >-
                  Each tile shows either a number the site measures for itself
                  or a value you type; "What this tile shows" decides which.
                  Prefer a measured one: the typed
                  figures on this page drifted badly, saying "50+" publishers
                  against a registry of 90 and "250K+" sources while the About
                  and home pages said 650,000. Whichever you choose, make the
                  label say exactly what was counted.
                fields:
                  - name: figure
                    label: What this tile shows
                    widget: select
                    default: typed
                    options:
                      - { label: 'Documents in the search index (counted for you)', value: documents }
                      - { label: 'Publications we read from (counted for you)', value: publications }
                      - { label: 'A value I type below', value: typed }
                    hint: >-
                      The two counted options are recounted every night from the
                      database — documents, not sources, because a source
                      publishes many; and the publications behind the feeds, so
                      BYU Speeches is one whatever the campus. Pick "a value I
                      type" for a tile that is not a count, like "Daily", and
                      fill in the box below.
                  - name: value
                    label: The value you type
                    widget: string
                    required: false
                    hint: >-
                      Only used when you picked "A value I type below". Leave it
                      empty for a counted tile. A typed tile with nothing in
                      this box simply does not appear on the page.
                  - { name: label, label: Label, widget: string }
                  - name: round_down_to
                    label: State it to the nearest
                    widget: number
                    default: 1
                    value_type: int
                    min: 1
                    hint: >-
                      Only for a counted tile. 1 states the exact number. 10
                      rounds down to the nearest ten, so a registry of 91 reads
                      "90" — pair it with a "+" below and the tile stays true as
                      the number grows, instead of being an exact figure someone
                      has to remember to update.
                  - name: suffix
                    label: Mark after the number
                    widget: string
                    required: false
                    default: ''
                    hint: >-
                      Usually "+", and only when you have rounded down above. It
                      says "at least this many". Leave empty for an exact count.
              - name: note
                label: Closing note
                widget: text
                hint: >-
                  The paragraphs that close this section, under the tiles. Leave
                  a blank line between paragraphs and each one is set as its own
                  paragraph on the page. Say the library is large if you like,
                  but do not say how large — the tiles directly above count that
                  for themselves and are recounted every night, so a figure
                  typed here becomes a second number that drifts away from them.
          - name: answer
            label: How an answer is made
            widget: object
            fields:
              - { name: heading, label: Heading, widget: string }
              - name: steps
                label: Steps
                label_singular: Step
                widget: list
                summary: '{{fields.title}}'
                hint: Shown as a numbered list, so the order is the sequence.
                fields:
                  - { name: title, label: Step name, widget: string }
                  - { name: body, label: Body, widget: text }
          - name: limits
            label: What we deliberately do not do
            widget: object
            fields:
              - { name: heading, label: Heading, widget: string }
              - name: items
                label: Limits
                label_singular: Limit
                widget: list
                summary: '{{fields.title}}'
                fields:
                  - { name: title, label: Title, widget: string }
                  - { name: body, label: Body, widget: text }

      - name: vision
        label: Vision — the leader/donor brief
        file: _content/pages/vision.yml
        description: >-
          The page we hand a church leader, a professor or a funder when the site
          cannot be demonstrated in person. NOT in the navigation and NOT in
          search results — it is reached only by someone who was given the link,
          and that is deliberate. Live at /vision. Three sections, in the order
          somebody decides: the problem, the solution, and why the solution has
          to take this shape.
        fields:
          - name: hero
            label: Hero
            widget: object
            fields:
              - { name: eyebrow, label: Eyebrow, widget: string }
              - { name: title, label: Title, widget: string }
              - { name: intro, label: Intro, widget: text }
              - name: background
                label: Hero band
                widget: select
                default: photo
                options:
                  - { label: Photo, value: photo }
                  - { label: Brand gradient (no photo), value: gradient }
                hint: >-
                  What sits behind the words at the top. "Photo" shows the picture
                  below, dimmed so white text stays readable. The picture is kept
                  either way — it is still what shows when someone shares this
                  page in an email — so switch back any time.
              - { name: image, label: Hero background image, widget: image, required: false }
              - name: image_alt
                label: Hero image — alt text
                widget: string
                required: false
                hint: >-
                  What a screen reader reads aloud. Write it from the photo, not
                  from its filename.
              - name: focus
                label: Which part of the picture to keep
                widget: string
                default: center
                required: false
                pattern:
                  ['^(top|bottom|left|right|center|\d{1,3}%)( +(top|bottom|left|right|center|\d{1,3}%))?$', 'Write one or two of top / bottom / left / right / center, or a percentage — like "top" or "50% 30%"']
                hint: >-
                  Which part survives when the hero is cropped. "top", "bottom",
                  "center", or a percentage: "50% 30%" keeps the point 30% down.
          - name: problem
            label: 01 — The problem
            widget: object
            fields:
              - { name: eyebrow, label: Section label, widget: string }
              - { name: heading, label: Heading, widget: string }
              - { name: lede, label: Opening, widget: text, hint: Blank lines separate paragraphs. }
              - name: numbers
                label: The headline numbers
                widget: list
                max: 4
                hint: >-
                  Set very large, and the first thing a skimming reader takes
                  from the page. EVERY one must carry a source a reader can click
                  and check — this page is read by BYU faculty, and a number that
                  turns out to be soft costs more than the number was worth.
                  Figures about THIS SITE (documents, sources, articles) are not
                  typed anywhere: the page counts them itself when it is built.
                fields:
                  - name: figure
                    label: The number (optional)
                    widget: string
                    required: false
                    hint: >-
                      Short enough to read across a room — "3 in 4", "46%".
                      Avoid arrows and other symbols; the heading font has no
                      glyph for them and they print as an empty box. Leave it
                      empty when the study has not reported a number yet; the
                      sentence then takes the weight instead.
                  - { name: label, label: What it means, widget: text }
                  - name: quote
                    label: Quotation (optional)
                    widget: text
                    required: false
                    hint: Quote exactly, or leave empty. Never approximate someone's words.
                  - { name: quote_attribution, label: Who said it, widget: string, required: false }
                  - name: chart
                    label: The picture beside it
                    widget: object
                    hint: >-
                      Drawn from these numbers, not from a photograph, so the
                      picture and the figure cannot end up saying different
                      things. Whatever you put here is what a reader who follows
                      the source link should find.
                    fields:
                      - name: kind
                        label: Which picture
                        widget: select
                        options:
                          - { label: People — fills some of a group, value: proportion }
                          - { label: Line over time, value: trend }
                          - { label: People under question marks — no number claimed, value: questions }
                          - { label: A hundred people, some faded away, value: grid }
                      - name: caption
                        label: Caption under the picture (optional)
                        widget: string
                        required: false
                        hint: Leave empty where the picture explains itself. A caption under an obvious graphic is noise.
                      - name: retained
                        label: 'Hundred people: how many are still here'
                        widget: number
                        value_type: int
                        required: false
                        min: 0
                        max: 100
                        hint: The rest are drawn faded and red.
                      - name: min
                        label: 'Line: bottom of the scale (optional)'
                        widget: number
                        value_type: int
                        required: false
                        min: 0
                        max: 100
                        hint: >-
                          Leave both empty and the scale fits the readings, which
                          shows the movement. Set them when a reader needs to see
                          the LEVEL too — the axis ends are printed either way,
                          so the picture cannot flatter the numbers silently.
                      - name: max
                        label: 'Line: top of the scale (optional)'
                        widget: number
                        value_type: int
                        required: false
                        min: 0
                        max: 100
                      - name: marker
                        label: 'Line: dashed reference line (optional)'
                        widget: number
                        value_type: int
                        required: false
                        min: 0
                        max: 100
                        hint: Draws a dashed line across the chart, for a level the series is visibly approaching.
                      - name: people
                        label: Which figures to draw
                        widget: select
                        default: mixed
                        required: false
                        options:
                          - { label: Mixed — alternating, value: mixed }
                          - { label: All female, value: female }
                        hint: >-
                          "Mixed" alternates so a crowd is not silently all male.
                          Use "all female" only where the finding is about women.
                      - name: of
                        label: 'People: how many in the group'
                        widget: number
                        value_type: int
                        required: false
                        min: 2
                        max: 10
                      - name: filled
                        label: 'People: how many are filled in'
                        widget: number
                        value_type: int
                        required: false
                        min: 0
                        max: 10
                      - name: series
                        label: 'Line: two or more lines to compare'
                        widget: list
                        required: false
                        hint: >-
                          Use this INSTEAD of the readings below when you are
                          comparing two groups. The first line is drawn in the
                          brand colour, the second dashed in red, and each is
                          named at its own end rather than every point being
                          labelled, which would collide.
                        fields:
                          - { name: label, label: What this line is, widget: string }
                          - name: points
                            label: The readings, in order
                            widget: list
                            fields:
                              - { name: label, label: When, widget: string }
                              - { name: value, label: Percent, widget: number, value_type: int, min: 0, max: 100 }
                              - name: display
                                label: Print this instead (optional)
                                widget: string
                                required: false
                      - name: points
                        label: 'Line: the readings, in order'
                        widget: list
                        required: false
                        hint: >-
                          Percentages, plotted against a fixed 0-100 scale so the
                          slope a reader sees is the real one. Three or four
                          readings is plenty.
                        fields:
                          - { name: label, label: When, widget: string }
                          - { name: value, label: Percent, widget: number, value_type: int, min: 0, max: 100 }
                          - name: display
                            label: Print this instead (optional)
                            widget: string
                            required: false
                            hint: >-
                              Use it where the source hedges. BYU Studies says
                              "just under 50%", so the point prints that rather
                              than a 49% nobody reported. The percent above still
                              places the dot; this only changes the words.
                  - { name: source_title, label: Source — how to cite it, widget: string }
                  - { name: source_url, label: Source — link, widget: string }
              - { name: reasons_heading, label: Reasons heading, widget: string }
              - name: reasons
                label: Why the conversation is not happening
                widget: list
                fields:
                  - { name: title, label: Title, widget: string }
                  - { name: body, label: Body, widget: text }
          - name: solution
            label: 02 — The solution
            widget: object
            fields:
              - { name: eyebrow, label: Section label, widget: string }
              - { name: heading, label: Heading, widget: string }
              - { name: body, label: Body, widget: text, hint: Blank lines separate paragraphs. }
              - { name: outward_heading, label: 'Sends people onward — heading', widget: string }
              - { name: outward_body, label: 'Sends people onward — body', widget: text }
              - name: outward_excerpts
                label: 'Sends people onward — which instructions to quote'
                widget: list
                hint: >-
                  The words are NOT written here. They are read out of the live
                  chat instructions when the page is built, so this section
                  cannot drift into a promise the system stopped making. Pick the
                  instruction; reword it in the prompt and the line disappears.
                field:
                  name: excerpt
                  label: Instruction
                  widget: select
                  options:
                    - { label: Christ comes first, value: christ_first }
                    - { label: Prophets outrank scholars, value: prophets_rank_first }
                    - { label: No invented quotations, value: no_fabrication }
                    - { label: Never contradicts the Brethren, value: never_contradict }
                    - { label: Must admit when it does not know, value: admit_ignorance }
                    - { label: Points the reader to God, not to itself, value: points_outward }
                    - { label: Ends with a real next step, value: real_next_step }
              - { name: demo_heading, label: 'Phone — heading', widget: string }
              - { name: demo_intro, label: 'Phone — intro', widget: text }
              - name: exchanges
                label: What the phone plays
                widget: list
                max: 3
                hint: >-
                  Each replays a real published Answer exactly as the chat serves
                  it. You choose WHICH answer, not what it says — the words are
                  read from the article when the page is built, so the phone can
                  never show something the chat would not actually say.
                fields:
                  - { name: label, label: Who is asking, widget: string }
                  - name: answer_slug
                    label: Which Answer to replay
                    widget: string
                    hint: >-
                      The file name of an article on the Answers shelf without
                      ".md" — for example what-if-i-feel-like-im-in-the-wrong-body.
                      It must have chat questions filled in, or the site build stops.
                  - name: device
                    label: Which handset
                    widget: select
                    default: iphone
                    options:
                      - { label: iPhone, value: iphone }
                      - { label: Android, value: android }
                    hint: >-
                      Vary it between exchanges. Two people asking from two
                      different phones reads as two people; the same phone twice
                      reads as a slideshow.
                  - name: question
                    label: Which phrasing to show (optional)
                    widget: string
                    required: false
                    hint: >-
                      Leave empty for the article's first chat question. To show
                      another, copy it EXACTLY from that article's "Chat
                      questions" or "Ask prompts" — anything else stops the build,
                      because a question we made up and then answered is not a
                      demonstration of anything.
              - { name: demo_note, label: Note under the phone, widget: text }
              - { name: replay_label, label: Replay button label, widget: string }
              - { name: cta_label, label: Button label, widget: string }
              - { name: cta_href, label: Button link, widget: string }
              - { name: chips_label, label: Suggested questions label, widget: string }
              - name: chips
                label: Suggested questions
                widget: list
                max: 4
                hint: >-
                  Each opens the chat with the question already typed. Pick ones
                  the site answers well — a reader who clicks one is forming their
                  whole opinion from what comes back.
                field: { name: question, label: Question, widget: string }
              - { name: scale_heading, label: 'Library — heading', widget: string }
              - { name: scale_body, label: 'Library — body', widget: text }
              - { name: sources_label, label: Sources list label, widget: string }
              - name: sources
                label: Named sources
                widget: list
                hint: >-
                  Publications worth naming to this audience, not the full list —
                  the count beside it is measured from the source registry.
                field: { name: source, label: Source, widget: string }
              - { name: methodology_label, label: Methodology link text, widget: string }
              - { name: methodology_href, label: Methodology link, widget: string }
              - { name: proclamation_heading, label: 'Proclamation — heading', widget: string }
              - { name: proclamation_body, label: 'Proclamation — body', widget: text }
              - name: links
                label: Links into the Proclamation surfaces
                widget: list
                fields:
                  - { name: label, label: Label, widget: string }
                  - { name: href, label: Link, widget: string }
                  - { name: body, label: One-line description, widget: string }
          - name: approach
            label: 03 — Why this approach
            widget: object
            fields:
              - { name: eyebrow, label: Section label, widget: string }
              - { name: heading, label: Heading, widget: string }
              - { name: body, label: Body, widget: text, hint: Blank lines separate paragraphs. }
              - { name: quote, label: Quotation, widget: text }
              - { name: quote_attribution, label: Who said it, widget: string }
              - { name: quote_source_title, label: Source — how to cite it, widget: string }
              - { name: quote_source_url, label: Source — link, widget: string }
              - name: exchange
                label: The question he was asked
                widget: object
                hint: >-
                  A real question put to the speaker and his real answer, quoted
                  exactly. Do not tidy his words — the fact that a leader had to
                  ask permission out loud, and was told yes out loud, is the
                  point. Mark any trim with … and never trim in a way that
                  changes what he meant.
                fields:
                  - { name: heading, label: Label above it, widget: string }
                  - { name: question, label: The question, widget: text }
                  - { name: answer, label: His answer, widget: text }
                  - name: note
                    label: Our note underneath
                    widget: text
                    hint: Where the exchange came from, and anything a reader needs to make sense of it.
              - { name: gap_note, label: The gap we sit in, widget: text }
              - { name: criteria_heading, label: 'Trust — heading', widget: string }
              - { name: criteria_intro, label: 'Trust — intro', widget: text }
              - name: criteria
                label: What we show them
                widget: list
                hint: >-
                  You write the heading for each. The quotation underneath is NOT
                  written here — it is read out of the live chat instructions when
                  the page is built. That is the whole point of this section: it
                  prints what we actually tell the AI, so it cannot drift into a
                  promise we stopped making. Reword the instructions and the line
                  disappears from this page.
                fields:
                  - name: excerpt
                    label: Which instruction to quote
                    widget: select
                    options:
                      - { label: Christ comes first, value: christ_first }
                      - { label: Prophets outrank scholars, value: prophets_rank_first }
                      - { label: No invented quotations, value: no_fabrication }
                      - { label: Never contradicts the Brethren, value: never_contradict }
                      - { label: Must admit when it does not know, value: admit_ignorance }
                      - { label: Points the reader to God, not to itself, value: points_outward }
                      - { label: Ends with a real next step, value: real_next_step }
                  - { name: title, label: Your heading for it, widget: string }
              - { name: criteria_note, label: Note under the criteria, widget: text }
              - { name: people_label, label: 'Contributors — label', widget: string }
              - { name: people_body, label: 'Contributors — body', widget: text }
              - { name: people_href, label: 'Contributors — link', widget: string }
          - name: serves
            label: Who it is for
            widget: object
            fields:
              - { name: heading, label: Heading, widget: string }
              - name: items
                label: Audiences
                widget: list
                fields:
                  - { name: title, label: Title, widget: string }
                  - name: icon
                    label: Icon
                    widget: string
                    hint: >-
                      A name from the site's icon set — MessageCircle,
                      MessageSquareQuote, ClipboardList, GraduationCap. A name
                      that is not in the set stops the build.
                  - { name: body, label: Body, widget: text }
          - name: who
            label: Who is behind it
            widget: object
            fields:
              - { name: heading, label: Heading, widget: string }
              - { name: body, label: Body, widget: text, hint: Blank lines separate paragraphs. }
              - name: beta_note
                label: The beta note
                widget: text
                hint: >-
                  Said once, quietly, near the end. It exists so the reader does
                  not have to be the one to point out the site is unfinished —
                  with this audience, naming your own limits first earns the room.
          - name: ask
            label: The ask
            widget: object
            fields:
              - { name: heading, label: Heading, widget: string }
              - name: doors
                label: What we are asking for
                widget: list
                hint: One per audience. Keep each ask concrete enough to act on today.
                fields:
                  - { name: title, label: Title, widget: string }
                  - { name: body, label: Body, widget: text }
                  - { name: label, label: Link text, widget: string }
                  - { name: href, label: Link, widget: string }
          - name: close
            label: The closing line
            widget: object
            fields:
              - { name: heading, label: Label above it, widget: string }
              - { name: body, label: The line, widget: text }
      - name: contributors
        label: Contributors Page
        file: _content/pages/contributors.yml
        description: >-
          The advisors, contributors and peer reviewers listed under About →
          Contributors. This page is intentionally hidden from Google and other
          search engines; it is only reachable from the site's own navigation.
        fields:
          - name: hero
            label: Hero
            widget: object
            fields:
              - { name: eyebrow, label: Eyebrow, widget: string }
              - { name: title, label: Title, widget: string }
              - { name: intro, label: Intro, widget: text }
              - name: background
                label: Hero band
                widget: select
                default: photo
                options:
                  - { label: Photo, value: photo }
                  - { label: Brand gradient (no photo), value: gradient }
                hint: >-
                  What sits behind the words at the top of this page. "Photo" shows
                  the picture below, dimmed so white text stays readable. "Brand
                  gradient" hides it and leaves the plain green band. The picture is
                  kept either way — it is still what shows when someone shares this
                  page on Facebook or in a text message — so switch back any time.
              - { name: image, label: Hero background image, widget: image, required: false }
              - name: image_alt
                label: Hero image — alt text
                widget: string
                required: false
                hint: >-
                  What a screen reader reads aloud. Describe the picture that is actually
                  there — write it from the photo, not from its filename. Leave it empty
                  only when the image is decorative and the heading beside it already says
                  everything. Never write a note to yourself here; it is read out word for
                  word.
              - name: focus
                label: Which part of the picture to keep
                widget: string
                default: center
                required: false
                pattern:
                  ['^(top|bottom|left|right|center|\d{1,3}%)( +(top|bottom|left|right|center|\d{1,3}%))?$', 'Write one or two of top / bottom / left / right / center, or a percentage — like "top" or "50% 30%"']
                hint: >-
                  Which part of the picture survives when the hero is cropped.
                  A hero is only ever as tall as the design allows, so on a wide
                  screen the picture is trimmed to fit — and it is trimmed off
                  the top and the bottom equally unless this says otherwise,
                  which is how faces near the top of a photo lose the tops of
                  their heads. Write "top" to keep the top of the picture,
                  "bottom" for the bottom, "center" (the default) for the
                  middle, or a percentage for something in between: "50% 30%"
                  means keep the point 30% of the way down.
          - { name: intro, label: Mission line (shown above the roster), widget: text }
          - name: people
            label: Contributors
            label_singular: Contributor
            widget: list
            summary: '{{fields.name}}'
            hint: >-
              Listed in the order below — currently alphabetical by surname. The
              bio appears under the person's name, so start it with "He" or
              "She" rather than repeating the name.
            fields:
              - { name: name, label: Name, widget: string }
              - name: credential
                label: Post-nominal (optional)
                hint: Shown after the name, e.g. PhD.
                widget: string
                required: false
              - { name: bio, label: Bio, widget: text }
              - name: photo
                label: Portrait
                hint: >-
                  A real portrait once we have the person's permission to use
                  one. Until then pick the placeholder silhouette that person
                  would choose — /assets/img/avatars/placeholder-female.png,
                  placeholder-male.png, or placeholder-neutral.png. Everyone
                  starts on neutral; please do not guess on someone's behalf.
                widget: image
          - name: advisory_board
            label: Young Adult Advisory Board
            widget: object
            fields:
              - { name: heading, label: Heading, widget: string }
              - name: members
                label: Members
                widget: list
                field: { name: member, label: Name, widget: string }

      - name: contact
        label: Contact Page
        file: _content/pages/contact.yml
        description: >-
          The contact form and everything around it. Important: the email
          address below is only what the page prints for visitors who would
          rather write to us directly. Where form submissions actually get sent
          is a setting in Netlify (Forms → Notifications), not here — changing
          one does not change the other.
        fields:
          - name: header
            label: Page header
            widget: object
            fields:
              - { name: eyebrow, label: Eyebrow, widget: string }
              - { name: title, label: Title, widget: string }
              - { name: intro, label: Intro, widget: text }
          - name: form
            label: The form
            widget: object
            fields:
              - { name: heading, label: Form heading, widget: string }
              - { name: name_label, label: '"Name" label', widget: string }
              - { name: email_label, label: '"Email" label', widget: string }
              - { name: email_hint, label: '"Email" hint', widget: string }
              - { name: reason_label, label: '"Reason" label', widget: string }
              - name: reason_placeholder
                label: '"Reason" — unchosen state'
                hint: What the dropdown reads before the visitor picks something.
                widget: string
              - name: reasons
                label: Reasons for getting in touch
                widget: list
                min: 2
                field: { name: reason, label: Reason, widget: string }
                hint: >-
                  Whichever one the visitor picks is copied into the
                  notification email, so write each so it still makes sense to
                  someone reading it there with none of this page around it.
              - { name: message_label, label: '"Message" label', widget: string }
              - { name: message_hint, label: '"Message" hint', widget: string }
              - { name: submit_label, label: Send button, widget: string }
              - name: sending_label
                label: Send button while sending
                hint: >-
                  Shown on the button while the message is on its way. Keep it
                  short — it replaces the button's own words.
                widget: string
              - name: error_message
                label: Message shown when sending fails
                widget: text
                hint: >-
                  Shown in place of the thank-you page when the message does not
                  go through. Their message stays in the box, so say so — the
                  worry is that they have lost what they wrote.
              - name: fallback_note
                label: Note under the form
                hint: >-
                  Shown under the form. Do NOT put an email address here: the
                  site no longer publishes one, and the reply goes to whatever
                  address the visitor types in above.
          - name: crisis
            label: Crisis note
            widget: object
            fields:
              - { name: heading, label: Heading, widget: string }
              - { name: body, label: Body, widget: text }
          - name: thanks
            label: After sending (the thank-you page)
            widget: object
            fields:
              - { name: eyebrow, label: Eyebrow, widget: string }
              - { name: title, label: Title, widget: string }
              - { name: body, label: Body, widget: text }
              - { name: back_label, label: Button label, widget: string }
              - { name: back_href, label: Button link, widget: string }

      - name: scripture-insert
        label: Scripture Insert Page
        file: _content/pages/scripture-insert.yml
        fields:
          - name: hero
            label: Hero
            widget: object
            fields:
              - { name: eyebrow, label: Eyebrow, widget: string }
              - { name: title, label: Title, widget: string }
              - { name: intro, label: Intro, widget: text }
              - name: background
                label: Hero band
                widget: select
                default: photo
                options:
                  - { label: Photo, value: photo }
                  - { label: Brand gradient (no photo), value: gradient }
                hint: >-
                  What sits behind the words at the top of this page. "Photo" shows
                  the picture below, dimmed so white text stays readable. "Brand
                  gradient" hides it and leaves the plain green band. The picture is
                  kept either way — it is still what shows when someone shares this
                  page on Facebook or in a text message — so switch back any time.
              - { name: image, label: Hero background image, widget: image, required: false }
              - name: image_alt
                label: Hero image — alt text
                widget: string
                required: false
                hint: >-
                  What a screen reader reads aloud. Describe the picture that is actually
                  there — write it from the photo, not from its filename. Leave it empty
                  only when the image is decorative and the heading beside it already says
                  everything. Never write a note to yourself here; it is read out word for
                  word.
              - name: focus
                label: Which part of the picture to keep
                widget: string
                default: center
                required: false
                pattern:
                  ['^(top|bottom|left|right|center|\d{1,3}%)( +(top|bottom|left|right|center|\d{1,3}%))?$', 'Write one or two of top / bottom / left / right / center, or a percentage — like "top" or "50% 30%"']
                hint: >-
                  Which part of the picture survives when the hero is cropped.
                  A hero is only ever as tall as the design allows, so on a wide
                  screen the picture is trimmed to fit — and it is trimmed off
                  the top and the bottom equally unless this says otherwise,
                  which is how faces near the top of a photo lose the tops of
                  their heads. Write "top" to keep the top of the picture,
                  "bottom" for the bottom, "center" (the default) for the
                  middle, or a percentage for something in between: "50% 30%"
                  means keep the point 30% of the way down.
          - { name: embed_title, label: Heading above the PDF, widget: string }
          - name: fallback
            label: Fallback card (shown when the browser won't display the PDF in-page)
            widget: object
            fields:
              - { name: heading, label: Heading, widget: string }
              - { name: body, label: Body, widget: text }
          - { name: download_label, label: Download button label, widget: string }

      - name: history
        label: Proclamation History Page
        file: _content/pages/history.md
        fields:
          - name: hero
            label: Hero
            widget: object
            fields:
              - { name: eyebrow, label: Eyebrow, widget: string }
              - { name: title, label: Title, widget: string }
              - { name: intro, label: Intro, widget: text }
              - name: background
                label: Hero band
                widget: select
                default: photo
                options:
                  - { label: Photo, value: photo }
                  - { label: Brand gradient (no photo), value: gradient }
                hint: >-
                  What sits behind the words at the top of this page. "Photo" shows
                  the picture below, dimmed so white text stays readable. "Brand
                  gradient" hides it and leaves the plain green band. The picture is
                  kept either way — it is still what shows when someone shares this
                  page on Facebook or in a text message — so switch back any time.
              - { name: image, label: Hero background image, widget: image, required: false }
              - name: image_alt
                label: Hero image — alt text
                widget: string
                required: false
                hint: >-
                  What a screen reader reads aloud. Describe the picture that is actually
                  there — write it from the photo, not from its filename. Leave it empty
                  only when the image is decorative and the heading beside it already says
                  everything. Never write a note to yourself here; it is read out word for
                  word.
              - name: focus
                label: Which part of the picture to keep
                widget: string
                default: center
                required: false
                pattern:
                  ['^(top|bottom|left|right|center|\d{1,3}%)( +(top|bottom|left|right|center|\d{1,3}%))?$', 'Write one or two of top / bottom / left / right / center, or a percentage — like "top" or "50% 30%"']
                hint: >-
                  Which part of the picture survives when the hero is cropped.
                  A hero is only ever as tall as the design allows, so on a wide
                  screen the picture is trimmed to fit — and it is trimmed off
                  the top and the bottom equally unless this says otherwise,
                  which is how faces near the top of a photo lose the tops of
                  their heads. Write "top" to keep the top of the picture,
                  "bottom" for the bottom, "center" (the default) for the
                  middle, or a percentage for something in between: "50% 30%"
                  means keep the point 30% of the way down.
          - { name: body, label: Intro (Markdown, shown above the timeline), widget: markdown }
          - name: firsthand
            label: In Their Own Words
            widget: object
            hint: >-
              How the Proclamation came about, told by the apostles who drafted
              it. This was a blog post until the collections split, where it was
              the one piece of the archive that belonged to a page rather than a
              shelf. Rendered after the timeline.
            fields:
              - { name: heading, label: Heading, widget: string }
              - { name: intro, label: Intro, widget: text }
              - { name: body, label: Body (Markdown), widget: markdown }
          - name: closing
            label: Closing section (below the timeline)
            widget: object
            fields:
              - { name: heading, label: Heading, widget: string }
              - { name: body, label: Body (Markdown), widget: text }
              - { name: ask_label, label: Ask-in-chat link label, widget: string }
              - { name: ask_href, label: Ask-in-chat link URL, widget: string }
          - name: further_study
            label: Sources & further study band
            widget: object
            fields:
              - { name: heading, label: Heading, widget: string }
              - { name: intro, label: Intro line, widget: text, required: false }
              - name: groups
                label: Groups
                widget: list
                fields:
                  - { name: title, label: Group title, widget: string }
                  - name: items
                    label: Items
                    widget: list
                    fields:
                      - { name: label, label: Label, widget: string }
                      - name: url
                        label: URL
                        widget: string
                        required: false
                        pattern: ['^$|^https?:\/\/[^\s]+$', 'Leave empty, or give a full web address starting with http:// or https:// — no spaces']
                      - { name: note, label: Note (shown under the link), widget: text, required: false }

      - name: history_timeline
        label: Proclamation History — Timeline
        file: _content/proclamation-history/timeline.yml
        fields:
          - name: eras
            label: Eras
            widget: list
            fields:
              - name: id
                label: 'Anchor id (lowercase-with-dashes, stable once published)'
                widget: string
                pattern: ['^[a-z0-9-]+$', 'Lowercase letters, numbers and dashes only — no spaces or capitals']
              - { name: title, label: Era title, widget: string }
              - { name: span, label: Year span (e.g. "1990–1994"), widget: string }
              - { name: summary, label: Era summary (one or two sentences), widget: text }
              - name: events
                label: Events (keep in date order)
                widget: list
                fields:
                  - name: id
                    label: 'Anchor id (lowercase-with-dashes, stable once published)'
                    widget: string
                    pattern: ['^[a-z0-9-]+$', 'Lowercase letters, numbers and dashes only — no spaces or capitals']
                  - { name: date_label, label: 'Date as shown (e.g. "January 9, 1995" or "Early 1900s")', widget: string }
                  - { name: title, label: Title, widget: string }
                  - { name: lede, label: Summary (1–2 sentences, always visible), widget: text }
                  - { name: body, label: 'Full story (Markdown, shown under "Read more"; quotes must be verbatim from the cited sources)', widget: markdown }
                  - name: sources
                    label: Source footnotes
                    widget: list
                    required: false
                    fields:
                      - { name: label, label: Citation, widget: string }
                      - name: url
                        label: URL
                        widget: string
                        required: false
                        pattern: ['^$|^https?:\/\/[^\s]+$', 'Leave empty, or give a full web address starting with http:// or https:// — no spaces']

      - name: proclamation_references
        label: Proclamation References — Page Copy
        file: _content/pages/proclamation-references.md
        fields:
          - name: hero
            label: Hero
            widget: object
            fields:
              - { name: eyebrow, label: Eyebrow, widget: string }
              - { name: title, label: Title, widget: string }
              - { name: intro, label: Intro, widget: text }
              - name: background
                label: Hero band
                widget: select
                default: photo
                options:
                  - { label: Photo, value: photo }
                  - { label: Brand gradient (no photo), value: gradient }
                hint: >-
                  What sits behind the words at the top of this page. "Photo" shows
                  the picture below, dimmed so white text stays readable. "Brand
                  gradient" hides it and leaves the plain green band. The picture is
                  kept either way — it is still what shows when someone shares this
                  page on Facebook or in a text message — so switch back any time.
              - { name: image, label: Hero background image, widget: image, required: false }
              - name: image_alt
                label: Hero image — alt text
                widget: string
                required: false
                hint: >-
                  What a screen reader reads aloud. Describe the picture that is actually
                  there — write it from the photo, not from its filename. Leave it empty
                  only when the image is decorative and the heading beside it already says
                  everything. Never write a note to yourself here; it is read out word for
                  word.
              - name: focus
                label: Which part of the picture to keep
                widget: string
                default: center
                required: false
                pattern:
                  ['^(top|bottom|left|right|center|\d{1,3}%)( +(top|bottom|left|right|center|\d{1,3}%))?$', 'Write one or two of top / bottom / left / right / center, or a percentage — like "top" or "50% 30%"']
                hint: >-
                  Which part of the picture survives when the hero is cropped.
                  A hero is only ever as tall as the design allows, so on a wide
                  screen the picture is trimmed to fit — and it is trimmed off
                  the top and the bottom equally unless this says otherwise,
                  which is how faces near the top of a photo lose the tops of
                  their heads. Write "top" to keep the top of the picture,
                  "bottom" for the bottom, "center" (the default) for the
                  middle, or a percentage for something in between: "50% 30%"
                  means keep the point 30% of the way down.
          - name: method
            label: '"What counts as a reference" block'
            widget: object
            fields:
              - { name: heading, label: Heading, widget: string }
              - { name: body, label: 'Explanation (blank line between paragraphs)', widget: text }
              - { name: data_label, label: Dataset link label, widget: string }
              - { name: data_href, label: Dataset link URL, widget: string }
          - name: chart
            label: Chart
            widget: object
            fields:
              - { name: heading, label: Heading (screen readers), widget: string }
              - { name: caption, label: Caption shown under the chart, widget: text }
          - name: filters
            label: Filter bar labels
            widget: object
            fields:
              - { name: search_label, label: Search label, widget: string }
              - { name: search_placeholder, label: Search placeholder, widget: string }
              - { name: position_label, label: Position filter label, widget: string }
              - { name: type_label, label: Type filter label, widget: string }
              - { name: speaker_label, label: Speaker filter label, widget: string }
              - { name: year_label, label: Year filter label, widget: string }
              - { name: sort_label, label: Sort label, widget: string }
              - { name: clear_label, label: '"Clear filters" label', widget: string }
              - { name: empty_message, label: Message when nothing matches, widget: string }
              - { name: results_heading, label: Results heading noun (after the count), widget: string }
              - { name: counted_through_label, label: 'Counted-through prefix (e.g. "Counted through")', widget: string }
              - { name: show_more_label, label: '"Show more" button label', widget: string }
              - { name: page_size, label: Entries shown before "Show more", widget: number, value_type: int, min: 10, max: 500 }
          - name: closing
            label: Closing block
            widget: object
            fields:
              - { name: heading, label: Heading, widget: string }
              - { name: body, label: Body, widget: text }

      # The catalog itself (_content/proclamation-references/entries.yml) is
      # GENERATED by scripts/proclamation-references/build.ts and is not exposed
      # here — editing it in the CMS would be overwritten by the next run.
      # Additions and take-downs go through these two files instead.
      - name: proclamation_references_manual
        label: Proclamation References — Manual Additions
        file: _content/proclamation-references/manual-entries.yml
        fields:
          - name: entries
            label: Hand-added entries (books, interviews — anything not in the corpus)
            widget: list
            required: false
            fields:
              - name: id
                label: 'Unique id (lowercase-with-dashes)'
                widget: string
                pattern: ['^[a-z0-9-]+$', 'Lowercase letters, numbers and dashes only']
              - name: date
                label: 'Date (YYYY-MM-DD)'
                widget: string
                required: false
                pattern: ['^$|^\d{4}-\d{2}-\d{2}$', 'Leave empty for an undated entry, or write it as 1995-09-23']
              - { name: speaker, label: Speaker name, widget: string }
              - { name: speakerId, label: 'Speaker id (lowercase-with-dashes, e.g. dallin-h-oaks)', widget: string }
              - { name: office, label: 'Office at the time (e.g. "Of the Quorum of the Twelve Apostles")', widget: string, required: false }
              - name: authorityClass
                label: Authority
                widget: select
                options:
                  - first-presidency
                  - twelve
                  - presiding-bishopric
                  - seventy
                  - general-authority-other
                  - auxiliary-general
                  - area-seventy
                  - ces-byu-officer
                  - institutional
              - name: type
                label: Type
                widget: select
                options:
                  - conference-talk
                  - devotional-address
                  - womens-conference
                  - magazine-article
                  - church-news
                  - book-chapter
                  - journal-article
                  - manual
                  - broadcast
                  - podcast-interview
                  - book
                  - official-statement
                  - gospel-topic
                  - news-article
                  - other
              - { name: venue, label: 'Venue (e.g. "Deseret Book", "Church News Podcast")', widget: string }
              - { name: title, label: Title, widget: string }
              - name: url
                label: URL
                widget: string
                pattern: ['^https?:\/\/[^\s]+$', 'A full web address starting with http:// or https:// — no spaces']
              - name: referenceKind
                label: How the Proclamation was used
                widget: select
                options: [cited, quoted, named, quoted-unattributed]
              - { name: excerpt, label: 'Excerpt (verbatim from the source)', widget: text }
              - { name: mentionCount, label: Mentions, widget: number, value_type: int, min: 0, required: false }
              - name: confidence
                label: Confidence (1 for hand-checked)
                widget: number
                value_type: float
                min: 0
                max: 1
                required: false
                hint: A number between 0 and 1. Not a percentage — "100% sure" is 1, not 100.

      - name: proclamation_references_removed
        label: Proclamation References — Take-downs
        file: _content/proclamation-references/removed-entries.yml
        fields:
          - name: removed
            label: 'Entries to suppress (the catalog is regenerated, so removing a row from entries.yml alone will not stick)'
            widget: list
            required: false
            fields:
              - { name: url, label: URL of the entry to remove, widget: string }
              - { name: reason, label: Why it does not belong, widget: text, required: false }

      - name: donate
        label: Donate Page
        file: _content/pages/donate.md
        fields:
          - name: hero
            label: Hero
            widget: object
            fields:
              - { name: eyebrow, label: Eyebrow, widget: string }
              - { name: title, label: Title, widget: string }
              - { name: intro, label: Intro, widget: text }
              - name: background
                label: Hero band
                widget: select
                default: photo
                options:
                  - { label: Photo, value: photo }
                  - { label: Brand gradient (no photo), value: gradient }
                hint: >-
                  What sits behind the words at the top of this page. "Photo" shows
                  the picture below, dimmed so white text stays readable. "Brand
                  gradient" hides it and leaves the plain green band. The picture is
                  kept either way — it is still what shows when someone shares this
                  page on Facebook or in a text message — so switch back any time.
              - { name: image, label: Hero background image, widget: image, required: false }
              - name: image_alt
                label: Hero image — alt text
                widget: string
                required: false
                hint: >-
                  What a screen reader reads aloud. Describe the picture that is actually
                  there — write it from the photo, not from its filename. Leave it empty
                  only when the image is decorative and the heading beside it already says
                  everything. Never write a note to yourself here; it is read out word for
                  word.
              - name: focus
                label: Which part of the picture to keep
                widget: string
                default: center
                required: false
                pattern:
                  ['^(top|bottom|left|right|center|\d{1,3}%)( +(top|bottom|left|right|center|\d{1,3}%))?$', 'Write one or two of top / bottom / left / right / center, or a percentage — like "top" or "50% 30%"']
                hint: >-
                  Which part of the picture survives when the hero is cropped.
                  A hero is only ever as tall as the design allows, so on a wide
                  screen the picture is trimmed to fit — and it is trimmed off
                  the top and the bottom equally unless this says otherwise,
                  which is how faces near the top of a photo lose the tops of
                  their heads. Write "top" to keep the top of the picture,
                  "bottom" for the bottom, "center" (the default) for the
                  middle, or a percentage for something in between: "50% 30%"
                  means keep the point 30% of the way down.
          - name: give
            label: PayPal block
            widget: object
            fields:
              - { name: heading, label: Heading, widget: string }
              - { name: blurb, label: Blurb, widget: text }
              - { name: button_label, label: Button label, widget: string }
              - { name: fineprint, label: Fine print, widget: text }
          - { name: body, label: Page body (Markdown), widget: markdown }

      - name: answers
        label: Answers Page
        file: _content/pages/answers.yml
        fields:
          - name: hero
            label: Hero
            widget: object
            fields:
              - { name: eyebrow, label: Eyebrow, widget: string }
              - { name: title, label: Title, widget: string }
              - { name: intro, label: Intro, widget: text }
              - name: background
                label: Hero band
                widget: select
                default: photo
                options:
                  - { label: Photo, value: photo }
                  - { label: Brand gradient (no photo), value: gradient }
                hint: >-
                  What sits behind the words at the top of this page. "Photo" shows
                  the picture below, dimmed so white text stays readable. "Brand
                  gradient" hides it and leaves the plain green band. The picture is
                  kept either way — it is still what shows when someone shares this
                  page on Facebook or in a text message — so switch back any time.
              - { name: image, label: Hero background image, widget: image, required: false }
              - name: image_alt
                label: Hero image — alt text
                widget: string
                required: false
                hint: >-
                  What a screen reader reads aloud. Describe the picture that is actually
                  there — write it from the photo, not from its filename. Leave it empty
                  only when the image is decorative and the heading beside it already says
                  everything. Never write a note to yourself here; it is read out word for
                  word.
              - name: focus
                label: Which part of the picture to keep
                widget: string
                default: center
                required: false
                pattern:
                  ['^(top|bottom|left|right|center|\d{1,3}%)( +(top|bottom|left|right|center|\d{1,3}%))?$', 'Write one or two of top / bottom / left / right / center, or a percentage — like "top" or "50% 30%"']
                hint: >-
                  Which part of the picture survives when the hero is cropped.
                  A hero is only ever as tall as the design allows, so on a wide
                  screen the picture is trimmed to fit — and it is trimmed off
                  the top and the bottom equally unless this says otherwise,
                  which is how faces near the top of a photo lose the tops of
                  their heads. Write "top" to keep the top of the picture,
                  "bottom" for the bottom, "center" (the default) for the
                  middle, or a percentage for something in between: "50% 30%"
                  means keep the point 30% of the way down.
          - name: shelf
            label: Wording on an individual page
            widget: object
            fields:
              - { name: label, label: Label above the title, widget: string }
              - { name: back_label, label: Back-link text, widget: string }
              - { name: draft_note, label: What a draft on this shelf says, widget: text }
              - { name: noun, label: One of these is called, widget: string }
              - { name: noun_plural, label: Several are called, widget: string }
              - { name: search_placeholder, label: Search box placeholder, widget: string }
          - name: counterpart
            label: Answer/Essay cross-link wording
            widget: object
            fields:
              - { name: heading, label: Heading, widget: string }
              - { name: note, label: Note, widget: text }
      - name: essays
        label: Essays Page
        file: _content/pages/essays.yml
        fields:
          - name: hero
            label: Hero
            widget: object
            fields:
              - { name: eyebrow, label: Eyebrow, widget: string }
              - { name: title, label: Title, widget: string }
              - { name: intro, label: Intro, widget: text }
              - name: background
                label: Hero band
                widget: select
                default: photo
                options:
                  - { label: Photo, value: photo }
                  - { label: Brand gradient (no photo), value: gradient }
                hint: >-
                  What sits behind the words at the top of this page. "Photo" shows
                  the picture below, dimmed so white text stays readable. "Brand
                  gradient" hides it and leaves the plain green band. The picture is
                  kept either way — it is still what shows when someone shares this
                  page on Facebook or in a text message — so switch back any time.
              - { name: image, label: Hero background image, widget: image, required: false }
              - name: image_alt
                label: Hero image — alt text
                widget: string
                required: false
                hint: >-
                  What a screen reader reads aloud. Describe the picture that is actually
                  there — write it from the photo, not from its filename. Leave it empty
                  only when the image is decorative and the heading beside it already says
                  everything. Never write a note to yourself here; it is read out word for
                  word.
              - name: focus
                label: Which part of the picture to keep
                widget: string
                default: center
                required: false
                pattern:
                  ['^(top|bottom|left|right|center|\d{1,3}%)( +(top|bottom|left|right|center|\d{1,3}%))?$', 'Write one or two of top / bottom / left / right / center, or a percentage — like "top" or "50% 30%"']
                hint: >-
                  Which part of the picture survives when the hero is cropped.
                  A hero is only ever as tall as the design allows, so on a wide
                  screen the picture is trimmed to fit — and it is trimmed off
                  the top and the bottom equally unless this says otherwise,
                  which is how faces near the top of a photo lose the tops of
                  their heads. Write "top" to keep the top of the picture,
                  "bottom" for the bottom, "center" (the default) for the
                  middle, or a percentage for something in between: "50% 30%"
                  means keep the point 30% of the way down.
          - name: shelf
            label: Wording on an individual page
            widget: object
            fields:
              - { name: label, label: Label above the title, widget: string }
              - { name: back_label, label: Back-link text, widget: string }
              - { name: draft_note, label: What a draft on this shelf says, widget: text }
              - { name: noun, label: One of these is called, widget: string }
              - { name: noun_plural, label: Several are called, widget: string }
              - { name: search_placeholder, label: Search box placeholder, widget: string }
          - name: counterpart
            label: Answer/Essay cross-link wording
            widget: object
            fields:
              - { name: heading, label: Heading, widget: string }
              - { name: note, label: Note, widget: text }
      - name: stories
        label: Stories Page
        file: _content/pages/stories.yml
        fields:
          - name: hero
            label: Hero
            widget: object
            fields:
              - { name: eyebrow, label: Eyebrow, widget: string }
              - { name: title, label: Title, widget: string }
              - { name: intro, label: Intro, widget: text }
              - name: background
                label: Hero band
                widget: select
                default: photo
                options:
                  - { label: Photo, value: photo }
                  - { label: Brand gradient (no photo), value: gradient }
                hint: >-
                  What sits behind the words at the top of this page. "Photo" shows
                  the picture below, dimmed so white text stays readable. "Brand
                  gradient" hides it and leaves the plain green band. The picture is
                  kept either way — it is still what shows when someone shares this
                  page on Facebook or in a text message — so switch back any time.
              - { name: image, label: Hero background image, widget: image, required: false }
              - name: image_alt
                label: Hero image — alt text
                widget: string
                required: false
                hint: >-
                  What a screen reader reads aloud. Describe the picture that is actually
                  there — write it from the photo, not from its filename. Leave it empty
                  only when the image is decorative and the heading beside it already says
                  everything. Never write a note to yourself here; it is read out word for
                  word.
              - name: focus
                label: Which part of the picture to keep
                widget: string
                default: center
                required: false
                pattern:
                  ['^(top|bottom|left|right|center|\d{1,3}%)( +(top|bottom|left|right|center|\d{1,3}%))?$', 'Write one or two of top / bottom / left / right / center, or a percentage — like "top" or "50% 30%"']
                hint: >-
                  Which part of the picture survives when the hero is cropped.
                  A hero is only ever as tall as the design allows, so on a wide
                  screen the picture is trimmed to fit — and it is trimmed off
                  the top and the bottom equally unless this says otherwise,
                  which is how faces near the top of a photo lose the tops of
                  their heads. Write "top" to keep the top of the picture,
                  "bottom" for the bottom, "center" (the default) for the
                  middle, or a percentage for something in between: "50% 30%"
                  means keep the point 30% of the way down.
          - name: shelf
            label: Wording on an individual page
            widget: object
            fields:
              - { name: label, label: Label above the title, widget: string }
              - { name: back_label, label: Back-link text, widget: string }
              - { name: draft_note, label: What a draft on this shelf says, widget: text }
              - { name: noun, label: One of these is called, widget: string }
              - { name: noun_plural, label: Several are called, widget: string }
              - { name: search_placeholder, label: Search box placeholder, widget: string }
      - name: podcast_page
        label: Podcast Page
        file: _content/pages/podcast.yml
        fields:
          - name: hero
            label: Hero
            widget: object
            fields:
              - { name: eyebrow, label: Eyebrow, widget: string }
              - { name: title, label: Title, widget: string }
              - { name: intro, label: Intro, widget: text }
              - name: background
                label: Hero band
                widget: select
                default: photo
                options:
                  - { label: Photo, value: photo }
                  - { label: Brand gradient (no photo), value: gradient }
                hint: >-
                  What sits behind the words at the top of this page. "Photo" shows
                  the picture below, dimmed so white text stays readable. "Brand
                  gradient" hides it and leaves the plain green band. The picture is
                  kept either way — it is still what shows when someone shares this
                  page on Facebook or in a text message — so switch back any time.
              - { name: image, label: Hero background image, widget: image, required: false }
              - name: image_alt
                label: Hero image — alt text
                widget: string
                required: false
                hint: >-
                  What a screen reader reads aloud. Describe the picture that is actually
                  there — write it from the photo, not from its filename. Leave it empty
                  only when the image is decorative and the heading beside it already says
                  everything. Never write a note to yourself here; it is read out word for
                  word.
              - name: focus
                label: Which part of the picture to keep
                widget: string
                default: center
                required: false
                pattern:
                  ['^(top|bottom|left|right|center|\d{1,3}%)( +(top|bottom|left|right|center|\d{1,3}%))?$', 'Write one or two of top / bottom / left / right / center, or a percentage — like "top" or "50% 30%"']
                hint: >-
                  Which part of the picture survives when the hero is cropped.
                  A hero is only ever as tall as the design allows, so on a wide
                  screen the picture is trimmed to fit — and it is trimmed off
                  the top and the bottom equally unless this says otherwise,
                  which is how faces near the top of a photo lose the tops of
                  their heads. Write "top" to keep the top of the picture,
                  "bottom" for the bottom, "center" (the default) for the
                  middle, or a percentage for something in between: "50% 30%"
                  means keep the point 30% of the way down.
          - name: shelf
            label: Wording on an individual page
            widget: object
            fields:
              - { name: label, label: Label above the title, widget: string }
              - { name: back_label, label: Back-link text, widget: string }
              - { name: draft_note, label: What a draft on this shelf says, widget: text }
              - { name: noun, label: One of these is called, widget: string }
              - { name: noun_plural, label: Several are called, widget: string }
              - { name: search_placeholder, label: Search box placeholder, widget: string }
      - name: come_follow_me_page
        label: Come, Follow Me Page
        file: _content/pages/come-follow-me.yml
        fields:
          - name: hero
            label: Hero
            widget: object
            fields:
              - { name: eyebrow, label: Eyebrow, widget: string }
              - { name: title, label: Title, widget: string }
              - { name: intro, label: Intro, widget: text }
              - name: background
                label: Hero band
                widget: select
                default: photo
                options:
                  - { label: Photo, value: photo }
                  - { label: Brand gradient (no photo), value: gradient }
                hint: >-
                  What sits behind the words at the top of this page. "Photo" shows
                  the picture below, dimmed so white text stays readable. "Brand
                  gradient" hides it and leaves the plain green band. The picture is
                  kept either way — it is still what shows when someone shares this
                  page on Facebook or in a text message — so switch back any time.
              - { name: image, label: Hero background image, widget: image, required: false }
              - name: image_alt
                label: Hero image — alt text
                widget: string
                required: false
                hint: >-
                  What a screen reader reads aloud. Describe the picture that is actually
                  there — write it from the photo, not from its filename. Leave it empty
                  only when the image is decorative and the heading beside it already says
                  everything. Never write a note to yourself here; it is read out word for
                  word.
              - name: focus
                label: Which part of the picture to keep
                widget: string
                default: center
                required: false
                pattern:
                  ['^(top|bottom|left|right|center|\d{1,3}%)( +(top|bottom|left|right|center|\d{1,3}%))?$', 'Write one or two of top / bottom / left / right / center, or a percentage — like "top" or "50% 30%"']
                hint: >-
                  Which part of the picture survives when the hero is cropped.
                  A hero is only ever as tall as the design allows, so on a wide
                  screen the picture is trimmed to fit — and it is trimmed off
                  the top and the bottom equally unless this says otherwise,
                  which is how faces near the top of a photo lose the tops of
                  their heads. Write "top" to keep the top of the picture,
                  "bottom" for the bottom, "center" (the default) for the
                  middle, or a percentage for something in between: "50% 30%"
                  means keep the point 30% of the way down.
          - name: shelf
            label: Wording on an individual page
            widget: object
            fields:
              - { name: label, label: Label above the title, widget: string }
              - { name: back_label, label: Back-link text, widget: string }
              - { name: draft_note, label: What a draft on this shelf says, widget: text }
              - { name: noun, label: One of these is called, widget: string }
              - { name: noun_plural, label: Several are called, widget: string }
              - { name: search_placeholder, label: Search box placeholder, widget: string }
          - name: books
            label: Curriculum books
            hint: The four books, in the order the cycle runs. Ids must match the `book` field on each reading - renaming a label is safe, changing an id is not.
            widget: list
            fields:
              - name: id
                label: Id
                widget: select
                options: [old-testament, new-testament, book-of-mormon, doctrine-and-covenants]
              - { name: label, label: Label, widget: string }
              - { name: blurb, label: Blurb, widget: string }
          - name: cycle
            label: Which book is being studied this year
            widget: object
            hint: >-
              Decides which tab opens first when someone lands on Come, Follow
              Me. The curriculum repeats every four years, so instead of a list
              of years this stores one starting year and the order the books
              come round in - and it stays right without being edited each
              December. Leave it alone unless the Church changes the rotation.
            fields:
              - name: anchor_year
                label: Starting year
                widget: number
                value_type: int
                hint: The year the first book in the list below was studied. 2021 was the Doctrine and Covenants.
              - name: order
                label: The order the books come round in
                widget: list
                hint: >-
                  All four, in the order the cycle runs - which is not the order
                  the tabs are shown in above. Starts with the book named by the
                  year on the left.
                field:
                  name: book
                  label: Book
                  widget: select
                  options: [old-testament, new-testament, book-of-mormon, doctrine-and-covenants]
      - name: blog
        label: Blog Page
        file: _content/pages/blog.yml
        fields:
          - name: hero
            label: Hero
            widget: object
            fields:
              - { name: eyebrow, label: Eyebrow, widget: string }
              - { name: title, label: Title, widget: string }
              - { name: intro, label: Intro, widget: text }
              - name: background
                label: Hero band
                widget: select
                default: photo
                options:
                  - { label: Photo, value: photo }
                  - { label: Brand gradient (no photo), value: gradient }
                hint: >-
                  What sits behind the words at the top of this page. "Photo" shows
                  the picture below, dimmed so white text stays readable. "Brand
                  gradient" hides it and leaves the plain green band. The picture is
                  kept either way — it is still what shows when someone shares this
                  page on Facebook or in a text message — so switch back any time.
              - { name: image, label: Hero background image, widget: image, required: false }
              - name: image_alt
                label: Hero image — alt text
                widget: string
                required: false
                hint: >-
                  What a screen reader reads aloud. Describe the picture that is actually
                  there — write it from the photo, not from its filename. Leave it empty
                  only when the image is decorative and the heading beside it already says
                  everything. Never write a note to yourself here; it is read out word for
                  word.
              - name: focus
                label: Which part of the picture to keep
                widget: string
                default: center
                required: false
                pattern:
                  ['^(top|bottom|left|right|center|\d{1,3}%)( +(top|bottom|left|right|center|\d{1,3}%))?$', 'Write one or two of top / bottom / left / right / center, or a percentage — like "top" or "50% 30%"']
                hint: >-
                  Which part of the picture survives when the hero is cropped.
                  A hero is only ever as tall as the design allows, so on a wide
                  screen the picture is trimmed to fit — and it is trimmed off
                  the top and the bottom equally unless this says otherwise,
                  which is how faces near the top of a photo lose the tops of
                  their heads. Write "top" to keep the top of the picture,
                  "bottom" for the bottom, "center" (the default) for the
                  middle, or a percentage for something in between: "50% 30%"
                  means keep the point 30% of the way down.

          - name: shelf
            label: Wording on an individual page
            widget: object
            fields:
              - { name: label, label: Label above the title, widget: string }
              - { name: back_label, label: Back-link text, widget: string }
              - { name: draft_note, label: What a draft on this shelf says, widget: text }
              - { name: noun, label: One of these is called, widget: string }
              - { name: noun_plural, label: Several are called, widget: string }
              - { name: search_placeholder, label: Search box placeholder, widget: string }
      - name: topics
        label: Topic Pages (shared hero)
        file: _content/pages/topics.yml
        fields:
          - name: hero
            label: Shared hero image
            widget: object
            hint: One background image reused across every /topics page.
            fields:
              - { name: image, label: Hero background image, widget: image, required: false }
              - name: image_alt
                label: Hero image — alt text
                widget: string
                required: false
                hint: >-
                  What a screen reader reads aloud. Describe the picture that is actually
                  there — write it from the photo, not from its filename. Leave it empty
                  only when the image is decorative and the heading beside it already says
                  everything. Never write a note to yourself here; it is read out word for
                  word.
              - name: focus
                label: Which part of the picture to keep
                widget: string
                default: center
                required: false
                pattern:
                  ['^(top|bottom|left|right|center|\d{1,3}%)( +(top|bottom|left|right|center|\d{1,3}%))?$', 'Write one or two of top / bottom / left / right / center, or a percentage — like "top" or "50% 30%"']
                hint: >-
                  Which part of the picture survives when the hero is cropped.
                  A hero is only ever as tall as the design allows, so on a wide
                  screen the picture is trimmed to fit — and it is trimmed off
                  the top and the bottom equally unless this says otherwise,
                  which is how faces near the top of a photo lose the tops of
                  their heads. Write "top" to keep the top of the picture,
                  "bottom" for the bottom, "center" (the default) for the
                  middle, or a percentage for something in between: "50% 30%"
                  means keep the point 30% of the way down.

      - name: chat
        label: Chat Page (background)
        file: _content/pages/chat.yml
        fields:
          - name: background
            label: Full-screen background
            widget: object
            fields:
              - { name: enabled, label: Show background image, widget: boolean, default: true }
              - { name: image, label: Background image (landscape), widget: image, required: false }
              - { name: image_mobile, label: Background image (portrait / mobile), widget: image, required: false }
              - name: image_alt
                label: Background alt text
                widget: string
                required: false
                hint: >-
                  What a screen reader reads aloud. Describe the picture that is actually
                  there — write it from the photo, not from its filename. Leave it empty
                  only when the image is decorative and the heading beside it already says
                  everything. Never write a note to yourself here; it is read out word for
                  word.
              - name: scrim_opacity
                label: Scrim strength (0 = photo full, 1 = solid white)
                hint: A soft white wash over the photo so the dark chat text stays readable.
                widget: number
                value_type: float
                min: 0
                max: 1
                default: 0.82
              - { name: blur, label: Blur the background, widget: boolean, default: false }
          - name: ui
            label: Chat interface copy
            widget: object
            fields:
              - name: disclaimer
                label: Disclaimer
                widget: text
                hint: Standing note under the ask box. Shown under the composer on desktop and in the opening screen on phones.
              - name: mobile_placeholder
                label: Ask-box placeholder (phones)
                widget: string
                hint: Short prompt shown in the ask box on phones, where the animated example questions are too long to fit on one line.
              - name: followup_placeholder
                label: Ask-box placeholder (after an answer)
                widget: string
              - name: jump_to_latest_label
                label: "\"Jump to latest\" button label"
                widget: string
                hint: Shown when the reader has scrolled away from the answer being written.
              - name: input_max_chars
                label: Longest question allowed (characters)
                hint: >-
                  The ask box stops accepting characters here. Must not exceed
                  what the server accepts (2000) or long questions will be
                  refused after they are sent.
                widget: number
                value_type: int
                min: 100
                default: 2000
              - name: input_counter_threshold
                label: Show "characters left" when this many remain
                widget: number
                value_type: int
                min: 0
                default: 100
              - name: input_counter_label
                label: "\"Characters left\" note"
                hint: '{n} is replaced with the number of characters still allowed.'
                widget: string
                default: '{n} characters left'
              - name: input_over_limit_message
                label: Note shown when the ask box is full
                widget: string
              - name: error_generic
                label: Message shown when a question cannot be sent
                hint: >-
                  Covers network trouble and rejected requests. Keep it warm —
                  the reader did nothing wrong.
                widget: text
              - name: turnstile_prompt
                label: Note above the "are you a person?" box
                hint: >-
                  Most readers never see this — the check usually runs silently.
                  It appears only when Cloudflare asks someone to tick a box
                  before their question is sent, so keep it to one calm line.
                widget: text
          - name: status
            label: "\"Working on it\" progress text"
            widget: object
            hint: >-
              Shown while an answer is being prepared. The lines appear one at a
              time, in order, on a steady beat until the answer starts printing,
              starting over if the wait runs long. They are reassurance, not a
              running commentary on the search, so keep each one true of the
              whole wait.
            fields:
              - name: cycle_ms
                label: Seconds each line stays on screen (milliseconds)
                widget: number
                value_type: int
                min: 500
                default: 3000
              - name: lines
                label: Lines to rotate through
                widget: list

      - name: site
        label: Site Chrome (brand & footer)
        file: _content/ui/site.yml
        fields:
          - { name: brand_name, label: Brand name, widget: string }
          - { name: tagline, label: Tagline, widget: string }
          - { name: footer_text, label: Footer text, widget: text }
          - name: hero_aspect
            label: Hero image shape (whole site)
            hint: >-
              How tall every hero image is, written as width / height. "16 / 9"
              is the shape most of our art is already in. A slimmer band is
              16 / 6; a deeper one is 4 / 3. This governs articles, lessons,
              blog posts, topic pages, the Proclamation and the section landing
              pages — the home page keeps its own look. On the pages where the
              heading sits on top of the image, this is a minimum: the band
              grows if the words need more room rather than cutting them off.
              Photos are cropped from the centre to fit, so changing this
              re-crops every blog post — check a few tall photos after.
            widget: string
            pattern:
              ['^\s*\d+(\.\d+)?\s*/\s*\d+(\.\d+)?\s*$', 'Write it as two numbers with a slash, like 16 / 9']
          - name: hero_max_height_photo
            label: Tallest a hero with a photo may be (% of the screen)
            widget: number
            value_type: int
            min: 1
            max: 100
            default: 45
            hint: >-
              The shape above is a ratio, so on a wide screen it just keeps
              getting taller — it once filled 83% of an ordinary laptop screen
              with a banner. This is the ceiling. It is measured against the
              browser window, so a short laptop screen gets a shorter banner
              than a tall desktop from the same number. Lowering it can never
              cut a heading off: a band that holds its title inside the image
              still grows to fit the words. It does nothing on a phone, where
              the words already set the height.
          - name: hero_max_height_gradient
            label: Tallest a hero with no photo may be (% of the screen)
            widget: number
            value_type: int
            min: 1
            max: 100
            default: 32
            hint: >-
              The same ceiling for the pages whose banner is the plain green
              gradient rather than a picture. Lower than the photo one on
              purpose: a photograph is something to look at, an empty band of
              colour past a certain height is just a wall.
          - name: nav
            label: Navigation
            hint: >-
              Engineer-owned. Fixing a label is safe; adding/removing an item or
              changing where it points changes site routing and needs a matching
              code change. An item is either a link (set Link) or a dropdown
              group (leave Link blank and add Dropdown items).
            widget: list
            summary: '{{label}}'
            fields:
              - { name: label, label: Label, widget: string }
              - { name: href, label: Link, widget: string, required: false }
              - { name: cta, label: Styled as button?, widget: boolean, required: false, default: false }
              - name: adminOnly
                label: Only show to signed-in admins
                widget: boolean
                required: false
                default: false
                hint: >-
                  Hides this item from ordinary visitors; only someone signed
                  in to the admin console sees it. This field has to stay in
                  this list even though nobody edits it: the CMS writes back
                  only the fields listed here, so leaving it out meant that
                  saving anything on this page — the brand name, the footer —
                  silently deleted it and put the Admin link in the navigation
                  for the whole world, on a build that passed.
              - name: children
                label: Dropdown items
                widget: list
                required: false
                summary: '{{label}}  →  {{href}}'
                fields:
                  - { name: label, label: Label, widget: string }
                  - { name: href, label: Link, widget: string }
                  - name: adminOnly
                    label: Only show to signed-in admins
                    widget: boolean
                    required: false
                    default: false
                    hint: >-
                      As above — listed so that saving this page cannot delete it.
                  - name: external
                    label: Goes to another website
                    widget: boolean
                    required: false
                    default: false
                    hint: >-
                      Tick for a link that leaves this site — YouTube, Instagram.
                      It gets a small arrow so a reader knows before they click,
                      and opens in a new tab so they keep their place here.
          - name: voice
            label: Voice output (spoken answers)
            widget: object
            required: false
            fields:
              - { name: enabled, label: Enable spoken answers, widget: boolean, default: true }
              - name: voice_name
                label: Voice
                widget: select
                default: Kore
                options: [Zephyr, Puck, Charon, Kore, Fenrir, Aoede, Leda, Orus, Autonoe, Callirrhoe]
              - {
                  name: auto_play_on_voice_input,
                  label: Auto-speak when asked by voice,
                  widget: boolean,
                  default: true,
                }
              - {
                  name: max_spoken_seconds,
                  label: Max spoken seconds (0 = unlimited),
                  widget: number,
                  default: 180,
                  value_type: int,
                  min: 0,
                }
          - name: search
            label: Sitewide search
            widget: object
            required: false
            hint: >-
              Copy for the full-text site search. The index itself is built from
              page content automatically at deploy time.
            fields:
              - { name: button_label, label: Search button label, widget: string, default: Search }
              - { name: placeholder, label: Search box placeholder, widget: string }
              - { name: no_results, label: "No-results message", widget: string }
              - { name: hint, label: Search button tooltip / aria-label, widget: string }
          - name: launch
            label: Launch screen & updates
            widget: object
            required: false
            hint: >-
              The animated splash shown on a cold open while the app checks for a
              newer version, plus the mid-session "new version" banner.
            fields:
              - { name: enabled, label: Show launch screen on cold open, widget: boolean, default: true }
              - { name: message, label: Launch caption, widget: string, default: 'Loading…' }
              - {
                  name: min_display_ms,
                  label: Minimum launch-screen time (ms),
                  widget: number,
                  default: 500,
                  value_type: int,
                  min: 0,
                }
              - {
                  name: update_timeout_ms,
                  label: Update-check timeout (ms),
                  widget: number,
                  default: 1500,
                  value_type: int,
                  min: 0,
                }
              - {
                  name: update_check_interval_ms,
                  label: Re-check interval for open tabs (ms),
                  widget: number,
                  default: 60000,
                  value_type: int,
                  min: 0,
                }
              - { name: update_banner_text, label: Update banner text, widget: string, default: 'A new version of the site is available.' }
              - { name: update_banner_action, label: Update banner button, widget: string, default: 'Reload' }

      - name: system-prompt
        label: AI System Prompt
        file: _content/config/system-prompt.yml
        fields:
          - name: answer_prompt
            label: Answer system prompt
            widget: text
            hint: >-
              The full instruction the AI follows when answering questions and
              writing articles. Changes go live on the next site deploy. Test
              edits side-by-side in the System Prompt Playground
              (/admin/playground) before saving here.

      - name: chat-scope
        label: What the AI Will Answer
        file: _content/config/chat-scope.yml
        fields:
          - name: refusal_message
            label: Reply to an off-topic question
            hint: >-
              Sent when a question has nothing to do with the family, the
              Proclamation, or the gospel. Warm, brief, and it should invite a
              question we can actually answer.
            widget: text
          - name: embedding_floor
            label: How close a question must be to our subjects (0–1)
            hint: >-
              Lower = more questions get answered (and a few off-topic ones slip
              through). Higher = stricter, and some sincere questions get turned
              away. 0.4 is deliberately generous. Prefer adding subjects below
              over raising this.
            widget: number
            value_type: float
            min: 0
            max: 1
            default: 0.4
          - name: anchors
            label: Subjects we answer about
            hint: >-
              Short topical phrases, not questions or sentences — an incoming
              question is compared against this list. Adding phrases is the safe
              way to widen what the assistant will take on.
            widget: list

      - name: ranking-config
        label: Search Ranking
        file: _content/config/ranking-config.yml
        fields:
          - name: weight_cosine
            label: Weight — topical match
            hint: How much the chunk's actual match to the question counts.
            widget: number
            default: 0.5
            value_type: float
            min: 0
          - name: weight_doctrinal
            label: Weight — doctrinal alignment
            hint: How much the source's doctrinal-alignment score counts.
            widget: number
            default: 0.2
            value_type: float
            min: 0
          - name: weight_reliability
            label: Weight — reliability
            hint: How much the source's reliability score counts.
            widget: number
            default: 0.15
            value_type: float
            min: 0
          - name: weight_audience
            label: Weight — audience fit
            hint: How much fit to the inferred reader counts.
            widget: number
            default: 0.15
            value_type: float
            min: 0
          - name: min_doctrinal_alignment
            label: Minimum doctrinal alignment
            hint: >-
              A passage below this score (0-1) is dropped before ranking,
              regardless of relevance.
            widget: number
            default: 0.4
            value_type: float
            min: 0
            max: 1
          - name: normalize_scores
            label: Normalize topical-match score
            hint: >-
              Rescales the raw match score before combining it with the
              weights above. Needed for the weight above to matter in hybrid
              search. "none" is today's production behavior.
            widget: select
            default: none
            options: [none, minmax, zscore]
          - name: authority_boost_weight
            label: Authority boost
            hint: >-
              Extra credit for scripture/prophetic/apostolic sources. 0 = off
              (today's production behavior).
            widget: number
            default: 0
            value_type: float
            min: 0
          - name: max_docs
            label: Max documents per answer
            widget: number
            default: 8
            value_type: int
            min: 1
          - name: max_seeds
            label: Max top-ranked chunks considered
            widget: number
            default: 16
            value_type: int
            min: 1
          - name: reserved_authority_docs
            label: Reserved authoritative-source seats
            hint: >-
              Extra seats, ON TOP OF "max documents per answer", guaranteed
              for the best-available scripture/General-Conference-tier
              source(s) — even if the scoring formula above wouldn't have
              ranked it into the main cutoff. 0 = off.
            widget: number
            default: 0
            value_type: int
            min: 0

      - name: spend
        label: AWS Spend Page
        file: _content/config/spend.yml
        fields:
          - name: history_months
            label: Months of history to compare
            hint: >-
              How many months the "this month against the last few" chart shows,
              counting the month running now. Up to 13; nothing is fetched
              again when you raise it.
            widget: number
            default: 6
            value_type: int
            min: 2
            max: 13
          - name: service_alert_usd
            label: Flag a service costing more than ($)
            hint: >-
              Any AWS service past this figure in the current month is called
              out on the page. It changes nothing about what is charged or what
              alerts fire — it only decides what gets a warning label.
            widget: number
            default: 50
            value_type: float
            min: 0
          - name: highlight_services
            label: Services to always show
            hint: >-
              Listed even in a month they cost nothing, so a service that has
              run away before stays visible. Use the exact AWS service name,
              e.g. "Amazon Simple Storage Service".
            widget: list
          - name: budget_warn_percent
            label: Flag a budget past (% of its limit)
            hint: >-
              A budget at or above this share of its limit is marked on the
              page. 80 matches the alert the budgets themselves send to Slack.
            widget: number
            default: 80
            value_type: int
            min: 1
            max: 100
          - name: stack_tag_activated_on
            label: Date the per-stack cost tag started collecting
            hint: >-
              AWS does not label spend it has already recorded, so the page
              shows "no data for this period" before this date instead of a
              chart of zeros. Only change this if the tag is ever switched off
              and on again.
            widget: datetime
            date_format: YYYY-MM-DD
            time_format: false
            picker_utc: true
          - name: cost_basis_note
            label: What kind of figure this page shows
            hint: >-
              Printed under the headline number. Says which AWS cost basis the
              page reports, so the figure can be lined up against the AWS
              console rather than guessed at.
            widget: text
          - name: settlement_lag_days
            label: Days AWS is still adjusting
            hint: >-
              The most recent days always read low because AWS has not finished
              counting them. The page draws those days hollow and says they will
              rise, instead of presenting them as a drop in spending. Raise this
              if figures keep climbing after the page has called them final.
            widget: number
            default: 2
            value_type: int
            min: 0
            max: 7
