Repository: MozillaFoundation/foundation.mozilla.org
Pull request #13999: TP1-2499 Image Card with Title
Author: login: dlopezvsr
Created: 2025-05-21T22:44:34Z | Merged: 2025-05-23T17:01:00Z

# Description

This PR introduces the image Card with Title component, considering the variants of this component two blocks were created: `SingleImageBlock` and `DoubleImageBlock`. These two blocks were included under `sized_images` field as  a `StreamField`, added inside of `abstract_general_page.py` and `abstract_article_page.py`. A generic template for general page was also created for testing purposes. Also, simple `scss` style was added which needs to be updated later with the exact sizes and design.

### Variations considered:

- Double: Portrait - Landscape
- Double: Same type
- Single: Portrait
- Single: Landscape

Link to sample test page: https://mozilla-hub.atlassian.net/browse/TP1-2499


**Tests**
- Pull this branch and apply new migrations.
- Access to the CMS and create a General Page.
- You should see the  `sized_images` field with two options, Single and Double.
- Add the images and publish the page.
- Go to the live page and confirm single and double block is rendering correctly.


┆Issue is synchronized with this [Jira Story](https://mozilla-hub.atlassian.net/browse/TP1-2700)



Diff:
diff --git a/foundation_cms/articles/migrations/0002_articleadvicecolumn_sized_images_and_more.py b/foundation_cms/articles/migrations/0002_articleadvicecolumn_sized_images_and_more.py
new file mode 100644
index 00000000000..7a1f9c96e00
--- /dev/null
+++ b/foundation_cms/articles/migrations/0002_articleadvicecolumn_sized_images_and_more.py
@@ -0,0 +1,246 @@
+# Generated by Django 4.2.20 on 2025-05-21 22:47
+
+from django.db import migrations
+import wagtail.fields
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("articles", "0001_initial"),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="articleadvicecolumn",
+            name="sized_images",
+            field=wagtail.fields.StreamField(
+                [("single_image", 3), ("double_image", 8)],
+                blank=True,
+                block_lookup={
+                    0: ("wagtail.blocks.CharBlock", (), {"help_text": "Title for this image", "required": True}),
+                    1: ("wagtail.images.blocks.ImageBlock", [], {}),
+                    2: (
+                        "wagtail.blocks.ChoiceBlock",
+                        [],
+                        {
+                            "choices": [("portrait", "Portrait"), ("landscape", "Landscape")],
+                            "help_text": "Select the orientation of this image",
+                        },
+                    ),
+                    3: (
+                        "wagtail.blocks.StructBlock",
+                        [[("title", 0), ("image", 1), ("orientation", 2)]],
+                        {"label": "Single Image"},
+                    ),
+                    4: ("wagtail.blocks.CharBlock", (), {"help_text": "Title for first image", "required": True}),
+                    5: (
+                        "wagtail.blocks.ChoiceBlock",
+                        [],
+                        {
+                            "choices": [("portrait", "Portrait"), ("landscape", "Landscape")],
+                            "help_text": "Select the orientation of the first image",
+                        },
+                    ),
+                    6: ("wagtail.blocks.CharBlock", (), {"help_text": "Title for second image", "required": True}),
+                    7: (
+                        "wagtail.blocks.ChoiceBlock",
+                        [],
+                        {
+                            "choices": [("portrait", "Portrait"), ("landscape", "Landscape")],
+                            "help_text": "Select the orientation of the second image",
+                        },
+                    ),
+                    8: (
+                        "wagtail.blocks.StructBlock",
+                        [
+                            [
+                                ("first_title", 4),
+                                ("first_image", 1),
+                                ("first_orientation", 5),
+                                ("second_title", 6),
+                                ("second_image", 1),
+                                ("second_orientation", 7),
+                            ]
+                        ],
+                        {"label": "Double Image"},
+                    ),
+                },
+                help_text="Add images with different layout options",
+            ),
+        ),
+        migrations.AddField(
+            model_name="articlecollectionpage",
+            name="sized_images",
+            field=wagtail.fields.StreamField(
+                [("single_image", 3), ("double_image", 8)],
+                blank=True,
+                block_lookup={
+                    0: ("wagtail.blocks.CharBlock", (), {"help_text": "Title for this image", "required": True}),
+                    1: ("wagtail.images.blocks.ImageBlock", [], {}),
+                    2: (
+                        "wagtail.blocks.ChoiceBlock",
+                        [],
+                        {
+                            "choices": [("portrait", "Portrait"), ("landscape", "Landscape")],
+                            "help_text": "Select the orientation of this image",
+                        },
+                    ),
+                    3: (
+                        "wagtail.blocks.StructBlock",
+                        [[("title", 0), ("image", 1), ("orientation", 2)]],
+                        {"label": "Single Image"},
+                    ),
+                    4: ("wagtail.blocks.CharBlock", (), {"help_text": "Title for first image", "required": True}),
+                    5: (
+                        "wagtail.blocks.ChoiceBlock",
+                        [],
+                        {
+                            "choices": [("portrait", "Portrait"), ("landscape", "Landscape")],
+                            "help_text": "Select the orientation of the first image",
+                        },
+                    ),
+                    6: ("wagtail.blocks.CharBlock", (), {"help_text": "Title for second image", "required": True}),
+                    7: (
+                        "wagtail.blocks.ChoiceBlock",
+                        [],
+                        {
+                            "choices": [("portrait", "Portrait"), ("landscape", "Landscape")],
+                            "help_text": "Select the orientation of the second image",
+                        },
+                    ),
+                    8: (
+                        "wagtail.blocks.StructBlock",
+                        [
+                            [
+                                ("first_title", 4),
+                                ("first_image", 1),
+                                ("first_orientation", 5),
+                                ("second_title", 6),
+                                ("second_image", 1),
+                                ("second_orientation", 7),
+                            ]
+                        ],
+                        {"label": "Double Image"},
+                    ),
+                },
+                help_text="Add images with different layout options",
+            ),
+        ),
+        migrations.AddField(
+            model_name="articleeditorialfeature",
+            name="sized_images",
+            field=wagtail.fields.StreamField(
+                [("single_image", 3), ("double_image", 8)],
+                blank=True,
+                block_lookup={
+                    0: ("wagtail.blocks.CharBlock", (), {"help_text": "Title for this image", "required": True}),
+                    1: ("wagtail.images.blocks.ImageBlock", [], {}),
+                    2: (
+                        "wagtail.blocks.ChoiceBlock",
+                        [],
+                        {
+                            "choices": [("portrait", "Portrait"), ("landscape", "Landscape")],
+                            "help_text": "Select the orientation of this image",
+                        },
+                    ),
+                    3: (
+                        "wagtail.blocks.StructBlock",
+                        [[("title", 0), ("image", 1), ("orientation", 2)]],
+                        {"label": "Single Image"},
+                    ),
+                    4: ("wagtail.blocks.CharBlock", (), {"help_text": "Title for first image", "required": True}),
+                    5: (
+                        "wagtail.blocks.ChoiceBlock",
+                        [],
+                        {
+                            "choices": [("portrait", "Portrait"), ("landscape", "Landscape")],
+                            "help_text": "Select the orientation of the first image",
+                        },
+                    ),
+                    6: ("wagtail.blocks.CharBlock", (), {"help_text": "Title for second image", "required": True}),
+                    7: (
+                        "wagtail.blocks.ChoiceBlock",
+                        [],
+                        {
+                            "choices": [("portrait", "Portrait"), ("landscape", "Landscape")],
+                            "help_text": "Select the orientation of the second image",
+                        },
+                    ),
+                    8: (
+                        "wagtail.blocks.StructBlock",
+                        [
+                            [
+                                ("first_title", 4),
+                                ("first_image", 1),
+                                ("first_orientation", 5),
+                                ("second_title", 6),
+                                ("second_image", 1),
+                                ("second_orientation", 7),
+                            ]
+                        ],
+                        {"label": "Double Image"},
+                    ),
+                },
+                help_text="Add images with different layout options",
+            ),
+        ),
+        migrations.AddField(
+            model_name="articleproductreview",
+            name="sized_images",
+            field=wagtail.fields.StreamField(
+                [("single_image", 3), ("double_image", 8)],
+                blank=True,
+                block_lookup={
+                    0: ("wagtail.blocks.CharBlock", (), {"help_text": "Title for this image", "required": True}),
+                    1: ("wagtail.images.blocks.ImageBlock", [], {}),
+                    2: (
+                        "wagtail.blocks.ChoiceBlock",
+                        [],
+                        {
+                            "choices": [("portrait", "Portrait"), ("landscape", "Landscape")],
+                            "help_text": "Select the orientation of this image",
+                        },
+                    ),
+                    3: (
+                        "wagtail.blocks.StructBlock",
+                        [[("title", 0), ("image", 1), ("orientation", 2)]],
+                        {"label": "Single Image"},
+                    ),
+                    4: ("wagtail.blocks.CharBlock", (), {"help_text": "Title for first image", "required": True}),
+                    5: (
+                        "wagtail.blocks.ChoiceBlock",
+                        [],
+                        {
+                            "choices": [("portrait", "Portrait"), ("landscape", "Landscape")],
+                            "help_text": "Select the orientation of the first image",
+                        },
+                    ),
+                    6: ("wagtail.blocks.CharBlock", (), {"help_text": "Title for second image", "required": True}),
+                    7: (
+                        "wagtail.blocks.ChoiceBlock",
+                        [],
+                        {
+                            "choices": [("portrait", "Portrait"), ("landscape", "Landscape")],
+                            "help_text": "Select the orientation of the second image",
+                        },
+                    ),
+                    8: (
+                        "wagtail.blocks.StructBlock",
+                        [
+                            [
+                                ("first_title", 4),
+                                ("first_image", 1),
+                                ("first_orientation", 5),
+                                ("second_title", 6),
+                                ("second_image", 1),
+                                ("second_orientation", 7),
+                            ]
+                        ],
+                        {"label": "Double Image"},
+                    ),
+                },
+                help_text="Add images with different layout options",
+            ),
+        ),
+    ]
diff --git a/foundation_cms/articles/migrations/0004_merge_20250522_1659.py b/foundation_cms/articles/migrations/0004_merge_20250522_1659.py
new file mode 100644
index 00000000000..151ec23c859
--- /dev/null
+++ b/foundation_cms/articles/migrations/0004_merge_20250522_1659.py
@@ -0,0 +1,13 @@
+# Generated by Django 4.2.20 on 2025-05-22 16:59
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("articles", "0002_articleadvicecolumn_sized_images_and_more"),
+        ("articles", "0003_add_audioblock_to_abstractarticlepage_body"),
+    ]
+
+    operations = []
diff --git a/foundation_cms/articles/migrations/0005_merge_20250522_2223.py b/foundation_cms/articles/migrations/0005_merge_20250522_2223.py
new file mode 100644
index 00000000000..e0b16c8611d
--- /dev/null
+++ b/foundation_cms/articles/migrations/0005_merge_20250522_2223.py
@@ -0,0 +1,13 @@
+# Generated by Django 4.2.20 on 2025-05-22 22:23
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("articles", "0004_articleadvicecolumn_lede_text_and_more"),
+        ("articles", "0004_merge_20250522_1659"),
+    ]
+
+    operations = []
diff --git a/foundation_cms/articles/migrations/0006_remove_articleadvicecolumn_sized_images_and_more.py b/foundation_cms/articles/migrations/0006_remove_articleadvicecolumn_sized_images_and_more.py
new file mode 100644
index 00000000000..ad1a7960367
--- /dev/null
+++ b/foundation_cms/articles/migrations/0006_remove_articleadvicecolumn_sized_images_and_more.py
@@ -0,0 +1,426 @@
+# Generated by Django 4.2.20 on 2025-05-23 00:13
+
+from django.db import migrations
+import wagtail.fields
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("articles", "0005_merge_20250522_2223"),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name="articleadvicecolumn",
+            name="sized_images",
+        ),
+        migrations.RemoveField(
+            model_name="articlecollectionpage",
+            name="sized_images",
+        ),
+        migrations.RemoveField(
+            model_name="articleeditorialfeature",
+            name="sized_images",
+        ),
+        migrations.RemoveField(
+            model_name="articleproductreview",
+            name="sized_images",
+        ),
+        migrations.AlterField(
+            model_name="articleadvicecolumn",
+            name="body",
+            field=wagtail.fields.StreamField(
+                [
+                    ("rich_text", 0),
+                    ("image", 4),
+                    ("audio", 9),
+                    ("tabbed_content", 15),
+                    ("two_column_container_block", 19),
+                ],
+                blank=True,
+                block_lookup={
+                    0: ("wagtail.blocks.RichTextBlock", (), {}),
+                    1: (
+                        "wagtail.blocks.CharBlock",
+                        (),
+                        {"help_text": "Title/caption for this image", "required": True},
+                    ),
+                    2: ("wagtail.images.blocks.ImageBlock", [], {}),
+                    3: (
+                        "wagtail.blocks.ChoiceBlock",
+                        [],
+                        {
+                            "choices": [("portrait", "Portrait"), ("landscape", "Landscape")],
+                            "help_text": "Select the orientation of this image",
+                        },
+                    ),
+                    4: ("wagtail.blocks.StructBlock", [[("title", 1), ("image", 2), ("orientation", 3)]], {}),
+                    5: (
+                        "wagtail.images.blocks.ImageChooserBlock",
+                        (),
+                        {"help_text": "Optional image to display at the top of the block.", "required": False},
+                    ),
+                    6: (
+                        "wagtail.blocks.CharBlock",
+                        (),
+                        {"help_text": "Image description (for screen readers).", "required": False},
+                    ),
+                    7: ("wagtail.blocks.CharBlock", (), {}),
+                    8: (
+                        "wagtail.blocks.TextBlock",
+                        (),
+                        {"help_text": "Short description of the audio", "required": False},
+                    ),
+                    9: (
+                        "wagtail.blocks.StructBlock",
+                        [
+                            [
+                                ("image", 5),
+                                ("image_alt_text", 6),
+                                ("title", 7),
+                                ("description", 8),
+                                ("simplecast_embed_code", 7),
+                            ]
+                        ],
+                        {},
+                    ),
+                    10: (
+                        "wagtail.blocks.CharBlock",
+                        (),
+                        {"help_text": "Title shown on the tab button", "required": True},
+                    ),
+                    11: (
+                        "wagtail.blocks.CharBlock",
+                        (),
+                        {"help_text": "Subtitle shown under the title on the tab", "required": False},
+                    ),
+                    12: (
+                        "wagtail.blocks.StreamBlock",
+                        [[("rich_text", 0)]],
+                        {"help_text": "Only one content block per tab.", "max_num": 1, "min_num": 1, "required": True},
+                    ),
+                    13: ("wagtail.blocks.StructBlock", [[("title", 10), ("subtitle", 11), ("content", 12)]], {}),
+                    14: ("wagtail.blocks.ListBlock", (13,), {"label": "Tabs", "max_num": 8, "min_num": 1}),
+                    15: ("wagtail.blocks.StructBlock", [[("tabs", 14)]], {}),
+                    16: (
+                        "wagtail.blocks.CharBlock",
+                        (),
+                        {"help_text": "Optional title for the block", "required": False},
+                    ),
+                    17: (
+                        "wagtail.blocks.StreamBlock",
+                        [[("rich_text", 0), ("audio", 9), ("image", 4)]],
+                        {"label": "Left Column"},
+                    ),
+                    18: (
+                        "wagtail.blocks.StreamBlock",
+                        [[("rich_text", 0), ("audio", 9), ("image", 4)]],
+                        {"label": "Right Column"},
+                    ),
+                    19: (
+                        "wagtail.blocks.StructBlock",
+                        [[("title", 16), ("left_column", 17), ("right_column", 18)]],
+                        {},
+                    ),
+                },
+            ),
+        ),
+        migrations.AlterField(
+            model_name="articlecollectionpage",
+            name="body",
+            field=wagtail.fields.StreamField(
+                [
+                    ("rich_text", 0),
+                    ("image", 4),
+                    ("audio", 9),
+                    ("tabbed_content", 15),
+                    ("two_column_container_block", 19),
+                ],
+                blank=True,
+                block_lookup={
+                    0: ("wagtail.blocks.RichTextBlock", (), {}),
+                    1: (
+                        "wagtail.blocks.CharBlock",
+                        (),
+                        {"help_text": "Title/caption for this image", "required": True},
+                    ),
+                    2: ("wagtail.images.blocks.ImageBlock", [], {}),
+                    3: (
+                        "wagtail.blocks.ChoiceBlock",
+                        [],
+                        {
+                            "choices": [("portrait", "Portrait"), ("landscape", "Landscape")],
+                            "help_text": "Select the orientation of this image",
+                        },
+                    ),
+                    4: ("wagtail.blocks.StructBlock", [[("title", 1), ("image", 2), ("orientation", 3)]], {}),
+                    5: (
+                        "wagtail.images.blocks.ImageChooserBlock",
+                        (),
+                        {"help_text": "Optional image to display at the top of the block.", "required": False},
+                    ),
+                    6: (
+                        "wagtail.blocks.CharBlock",
+                        (),
+                        {"help_text": "Image description (for screen readers).", "required": False},
+                    ),
+                    7: ("wagtail.blocks.CharBlock", (), {}),
+                    8: (
+                        "wagtail.blocks.TextBlock",
+                        (),
+                        {"help_text": "Short description of the audio", "required": False},
+                    ),
+                    9: (
+                        "wagtail.blocks.StructBlock",
+                        [
+                            [
+                                ("image", 5),
+                                ("image_alt_text", 6),
+                                ("title", 7),
+                                ("description", 8),
+                                ("simplecast_embed_code", 7),
+                            ]
+                        ],
+                        {},
+                    ),
+                    10: (
+                        "wagtail.blocks.CharBlock",
+                        (),
+                        {"help_text": "Title shown on the tab button", "required": True},
+                    ),
+                    11: (
+                        "wagtail.blocks.CharBlock",
+                        (),
+                        {"help_text": "Subtitle shown under the title on the tab", "required": False},
+                    ),
+                    12: (
+                        "wagtail.blocks.StreamBlock",
+                        [[("rich_text", 0)]],
+                        {"help_text": "Only one content block per tab.", "max_num": 1, "min_num": 1, "required": True},
+                    ),
+                    13: ("wagtail.blocks.StructBlock", [[("title", 10), ("subtitle", 11), ("content", 12)]], {}),
+                    14: ("wagtail.blocks.ListBlock", (13,), {"label": "Tabs", "max_num": 8, "min_num": 1}),
+                    15: ("wagtail.blocks.StructBlock", [[("tabs", 14)]], {}),
+                    16: (
+                        "wagtail.blocks.CharBlock",
+                        (),
+                        {"help_text": "Optional title for the block", "required": False},
+                    ),
+                    17: (
+                        "wagtail.blocks.StreamBlock",
+                        [[("rich_text", 0), ("audio", 9), ("image", 4)]],
+                        {"label": "Left Column"},
+                    ),
+                    18: (
+                        "wagtail.blocks.StreamBlock",
+                        [[("rich_text", 0), ("audio", 9), ("image", 4)]],
+                        {"label": "Right Column"},
+                    ),
+                    19: (
+                        "wagtail.blocks.StructBlock",
+                        [[("title", 16), ("left_column", 17), ("right_column", 18)]],
+                        {},
+                    ),
+                },
+            ),
+        ),
+        migrations.AlterField(
+            model_name="articleeditorialfeature",
+            name="body",
+            field=wagtail.fields.StreamField(
+                [
+                    ("rich_text", 0),
+                    ("image", 4),
+                    ("audio", 9),
+                    ("tabbed_content", 15),
+                    ("two_column_container_block", 19),
+                ],
+                blank=True,
+                block_lookup={
+                    0: ("wagtail.blocks.RichTextBlock", (), {}),
+                    1: (
+                        "wagtail.blocks.CharBlock",
+                        (),
+                        {"help_text": "Title/caption for this image", "required": True},
+                    ),
+                    2: ("wagtail.images.blocks.ImageBlock", [], {}),
+                    3: (
+                        "wagtail.blocks.ChoiceBlock",
+                        [],
+                        {
+                            "choices": [("portrait", "Portrait"), ("landscape", "Landscape")],
+                            "help_text": "Select the orientation of this image",
+                        },
+                    ),
+                    4: ("wagtail.blocks.StructBlock", [[("title", 1), ("image", 2), ("orientation", 3)]], {}),
+                    5: (
+                        "wagtail.images.blocks.ImageChooserBlock",
+                        (),
+                        {"help_text": "Optional image to display at the top of the block.", "required": False},
+                    ),
+                    6: (
+                        "wagtail.blocks.CharBlock",
+                        (),
+                        {"help_text": "Image description (for screen readers).", "required": False},
+                    ),
+                    7: ("wagtail.blocks.CharBlock", (), {}),
+                    8: (
+                        "wagtail.blocks.TextBlock",
+                        (),
+                        {"help_text": "Short description of the audio", "required": False},
+                    ),
+                    9: (
+                        "wagtail.blocks.StructBlock",
+                        [
+                            [
+                                ("image", 5),
+                                ("image_alt_text", 6),
+                                ("title", 7),
+                                ("description", 8),
+                                ("simplecast_embed_code", 7),
+                            ]
+                        ],
+                        {},
+                    ),
+                    10: (
+                        "wagtail.blocks.CharBlock",
+                        (),
+                        {"help_text": "Title shown on the tab button", "required": True},
+                    ),
+                    11: (
+                        "wagtail.blocks.CharBlock",
+                        (),
+                        {"help_text": "Subtitle shown under the title on the tab", "required": False},
+                    ),
+                    12: (
+                        "wagtail.blocks.StreamBlock",
+                        [[("rich_text", 0)]],
+                        {"help_text": "Only one content block per tab.", "max_num": 1, "min_num": 1, "required": True},
+                    ),
+                    13: ("wagtail.blocks.StructBlock", [[("title", 10), ("subtitle", 11), ("content", 12)]], {}),
+                    14: ("wagtail.blocks.ListBlock", (13,), {"label": "Tabs", "max_num": 8, "min_num": 1}),
+                    15: ("wagtail.blocks.StructBlock", [[("tabs", 14)]], {}),
+                    16: (
+                        "wagtail.blocks.CharBlock",
+                        (),
+                        {"help_text": "Optional title for the block", "required": False},
+                    ),
+                    17: (
+                        "wagtail.blocks.StreamBlock",
+                        [[("rich_text", 0), ("audio", 9), ("image", 4)]],
+                        {"label": "Left Column"},
+                    ),
+                    18: (
+                        "wagtail.blocks.StreamBlock",
+                        [[("rich_text", 0), ("audio", 9), ("image", 4)]],
+                        {"label": "Right Column"},
+                    ),
+                    19: (
+                        "wagtail.blocks.StructBlock",
+                        [[("title", 16), ("left_column", 17), ("right_column", 18)]],
+                        {},
+                    ),
+                },
+            ),
+        ),
+        migrations.AlterField(
+            model_name="articleproductreview",
+            name="body",
+            field=wagtail.fields.StreamField(
+                [
+                    ("rich_text", 0),
+                    ("image", 4),
+                    ("audio", 9),
+                    ("tabbed_content", 15),
+                    ("two_column_container_block", 19),
+                ],
+                blank=True,
+                block_lookup={
+                    0: ("wagtail.blocks.RichTextBlock", (), {}),
+                    1: (
+                        "wagtail.blocks.CharBlock",
+                        (),
+                        {"help_text": "Title/caption for this image", "required": True},
+                    ),
+                    2: ("wagtail.images.blocks.ImageBlock", [], {}),
+                    3: (
+                        "wagtail.blocks.ChoiceBlock",
+                        [],
+                        {
+                            "choices": [("portrait", "Portrait"), ("landscape", "Landscape")],
+                            "help_text": "Select the orientation of this image",
+                        },
+                    ),
+                    4: ("wagtail.blocks.StructBlock", [[("title", 1), ("image", 2), ("orientation", 3)]], {}),
+                    5: (
+                        "wagtail.images.blocks.ImageChooserBlock",
+                        (),
+                        {"help_text": "Optional image to display at the top of the block.", "required": False},
+                    ),
+                    6: (
+                        "wagtail.blocks.CharBlock",
+                        (),
+                        {"help_text": "Image description (for screen readers).", "required": False},
+                    ),
+                    7: ("wagtail.blocks.CharBlock", (), {}),
+                    8: (
+                        "wagtail.blocks.TextBlock",
+                        (),
+                        {"help_text": "Short description of the audio", "required": False},
+                    ),
+                    9: (
+                        "wagtail.blocks.StructBlock",
+                        [
+                            [
+                                ("image", 5),
+                                ("image_alt_text", 6),
+                                ("title", 7),
+                                ("description", 8),
+                                ("simplecast_embed_code", 7),
+                            ]
+                        ],
+                        {},
+                    ),
+                    10: (
+                        "wagtail.blocks.CharBlock",
+                        (),
+                        {"help_text": "Title shown on the tab button", "required": True},
+                    ),
+                    11: (
+                        "wagtail.blocks.CharBlock",
+                        (),
+                        {"help_text": "Subtitle shown under the title on the tab", "required": False},
+                    ),
+                    12: (
+                        "wagtail.blocks.StreamBlock",
+                        [[("rich_text", 0)]],
+                        {"help_text": "Only one content block per tab.", "max_num": 1, "min_num": 1, "required": True},
+                    ),
+                    13: ("wagtail.blocks.StructBlock", [[("title", 10), ("subtitle", 11), ("content", 12)]], {}),
+                    14: ("wagtail.blocks.ListBlock", (13,), {"label": "Tabs", "max_num": 8, "min_num": 1}),
+                    15: ("wagtail.blocks.StructBlock", [[("tabs", 14)]], {}),
+                    16: (
+                        "wagtail.blocks.CharBlock",
+                        (),
+                        {"help_text": "Optional title for the block", "required": False},
+                    ),
+                    17: (
+                        "wagtail.blocks.StreamBlock",
+                        [[("rich_text", 0), ("audio", 9), ("image", 4)]],
+                        {"label": "Left Column"},
+                    ),
+                    18: (
+                        "wagtail.blocks.StreamBlock",
+                        [[("rich_text", 0), ("audio", 9), ("image", 4)]],
+                        {"label": "Right Column"},
+                    ),
+                    19: (
+                        "wagtail.blocks.StructBlock",
+                        [[("title", 16), ("left_column", 17), ("right_column", 18)]],
+                        {},
+                    ),
+                },
+            ),
+        ),
+    ]
diff --git a/foundation_cms/base/models/abstract_article_page.py b/foundation_cms/base/models/abstract_article_page.py
index be3ef50e174..c73ab614e4f 100644
--- a/foundation_cms/base/models/abstract_article_page.py
+++ b/foundation_cms/base/models/abstract_article_page.py
@@ -5,24 +5,14 @@
 from wagtail.fields import StreamField
 
 from foundation_cms.base.models.abstract_base_page import AbstractBasePage
-from foundation_cms.blocks.audio_block import AudioBlock
 
 
 class AbstractArticlePage(AbstractBasePage):
-    body = StreamField(
-        [
-            ("audio", AudioBlock()),
-            ("rich_text", RichTextBlock()),
-        ],
-        use_json_field=True,
-        blank=True,
-    )
 
     lede_text = models.TextField(blank=True, help_text="Optional introductory lede text (plain text only).")
 
     content_panels = AbstractBasePage.content_panels + [
         FieldPanel("lede_text"),
-        FieldPanel("body"),
     ]
 
     class Meta:
diff --git a/foundation_cms/base/models/abstract_base_page.py b/foundation_cms/base/models/abstract_base_page.py
index b8a1c0b16a7..5339d34396c 100644
--- a/foundation_cms/base/models/abstract_base_page.py
+++ b/foundation_cms/base/models/abstract_base_page.py
@@ -10,6 +10,8 @@
 from wagtailmetadata.models import MetadataPageMixin
 
 from foundation_cms.blocks import TabbedContentContainerBlock, TwoColumnContainerBlock
+from foundation_cms.blocks.image_block import CustomImageBlock
+from foundation_cms.blocks.audio_block import AudioBlock
 
 @register_snippet
 class Author(models.Model):
@@ -52,6 +54,8 @@ class AbstractBasePage(MetadataPageMixin, Page):
     body = StreamField(
         [
             ("rich_text", RichTextBlock()),
+            ("image", CustomImageBlock()),
+            ("audio", AudioBlock()),
             ('tabbed_content', TabbedContentContainerBlock()),
             ("two_column_container_block", TwoColumnContainerBlock()),
         ],
diff --git a/foundation_cms/base/models/abstract_general_page.py b/foundation_cms/base/models/abstract_general_page.py
index a81c68c27b5..0beee5e1bf8 100644
--- a/foundation_cms/base/models/abstract_general_page.py
+++ b/foundation_cms/base/models/abstract_general_page.py
@@ -1,9 +1,13 @@
 from wagtail.admin.panels import FieldPanel
-from wagtail.fields import StreamField
 
 from foundation_cms.base.models.abstract_base_page import AbstractBasePage
 
 
 class AbstractGeneralPage(AbstractBasePage):
+
+    content_panels = AbstractBasePage.content_panels + [
+        FieldPanel("body"),
+        ]
+
     class Meta:
         abstract = True
diff --git a/foundation_cms/blocks/__init__.py b/foundation_cms/blocks/__init__.py
index f88fc6d25e2..e290cff58cc 100644
--- a/foundation_cms/blocks/__init__.py
+++ b/foundation_cms/blocks/__init__.py
@@ -1,10 +1,14 @@
 from .tabbed_content_container_block import TabbedContentContainerBlock
 from .tabbed_content_tab_block import TabbedContentTabBlock
 from .two_column_container_block import TwoColumnContainerBlock
+from .image_block import CustomImageBlock
+from .audio_block import AudioBlock
 
 # Add "unused" import to _all_ for flake8 linting
 __all__ = [
     "TabbedContentContainerBlock",
     "TabbedContentTabBlock",
     "TwoColumnContainerBlock",
+    "CustomImageBlock",
+    "AudioBlock",
 ]
diff --git a/foundation_cms/blocks/image_block.py b/foundation_cms/blocks/image_block.py
new file mode 100644
index 00000000000..366fb167c6d
--- /dev/null
+++ b/foundation_cms/blocks/image_block.py
@@ -0,0 +1,23 @@
+from wagtail.blocks import CharBlock, ChoiceBlock
+from wagtail.images.blocks import ImageBlock
+from foundation_cms.base.models.base_block import BaseBlock
+
+class CustomImageBlock(BaseBlock):
+    """
+    A reusable image block with title and orientation options
+    """
+    title = CharBlock(required=True, help_text="Title/caption for this image")
+    image = ImageBlock(required=True)
+    orientation = ChoiceBlock(
+        choices=[
+            ('portrait', 'Portrait'),
+            ('landscape', 'Landscape'),
+        ],
+        default='landscape',
+        help_text='Select the orientation of this image'
+    )
+    
+    class Meta:
+        icon = "image"
+        template = "patterns/blocks/image_block.html"
+        label = "Image" 
\ No newline at end of file
diff --git a/foundation_cms/blocks/two_column_container_block.py b/foundation_cms/blocks/two_column_container_block.py
index c1d6f637c79..edfdc79a32c 100644
--- a/foundation_cms/blocks/two_column_container_block.py
+++ b/foundation_cms/blocks/two_column_container_block.py
@@ -1,9 +1,11 @@
 from wagtail.blocks import StructBlock, StreamBlock, RichTextBlock, CharBlock
 from .audio_block import AudioBlock # Just as an example second block
+from .image_block import CustomImageBlock
 
 class ColumnStreamBlock(StreamBlock):
     rich_text = RichTextBlock()
     audio = AudioBlock()
+    image = CustomImageBlock()
 
 class TwoColumnContainerBlock(StructBlock):
     title = CharBlock(required=False, help_text="Optional title for the block")
diff --git a/foundation_cms/core/migrations/0003_generalpage_sized_images.py b/foundation_cms/core/migrations/0003_generalpage_sized_images.py
new file mode 100644
index 00000000000..ac651fdf66b
--- /dev/null
+++ b/foundation_cms/core/migrations/0003_generalpage_sized_images.py
@@ -0,0 +1,72 @@
+# Generated by Django 4.2.20 on 2025-05-21 22:47
+
+from django.db import migrations
+import wagtail.fields
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("core", "0002_alter_generalpage_body_alter_homepage_body"),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="generalpage",
+            name="sized_images",
+            field=wagtail.fields.StreamField(
+                [("single_image", 3), ("double_image", 8)],
+                blank=True,
+                block_lookup={
+                    0: ("wagtail.blocks.CharBlock", (), {"help_text": "Title for this image", "required": True}),
+                    1: ("wagtail.images.blocks.ImageBlock", [], {}),
+                    2: (
+                        "wagtail.blocks.ChoiceBlock",
+                        [],
+                        {
+                            "choices": [("portrait", "Portrait"), ("landscape", "Landscape")],
+                            "help_text": "Select the orientation of this image",
+                        },
+                    ),
+                    3: (
+                        "wagtail.blocks.StructBlock",
+                        [[("title", 0), ("image", 1), ("orientation", 2)]],
+                        {"label": "Single Image"},
+                    ),
+                    4: ("wagtail.blocks.CharBlock", (), {"help_text": "Title for first image", "required": True}),
+                    5: (
+                        "wagtail.blocks.ChoiceBlock",
+                        [],
+                        {
+                            "choices": [("portrait", "Portrait"), ("landscape", "Landscape")],
+                            "help_text": "Select the orientation of the first image",
+                        },
+                    ),
+                    6: ("wagtail.blocks.CharBlock", (), {"help_text": "Title for second image", "required": True}),
+                    7: (
+                        "wagtail.blocks.ChoiceBlock",
+                        [],
+                        {
+                            "choices": [("portrait", "Portrait"), ("landscape", "Landscape")],
+                            "help_text": "Select the orientation of the second image",
+                        },
+                    ),
+                    8: (
+                        "wagtail.blocks.StructBlock",
+                        [
+                            [
+                                ("first_title", 4),
+                                ("first_image", 1),
+                                ("first_orientation", 5),
+                                ("second_title", 6),
+                                ("second_image", 1),
+                                ("second_orientation", 7),
+                            ]
+                        ],
+                        {"label": "Double Image"},
+                    ),
+                },
+                help_text="Add images with different layout options",
+            ),
+        ),
+    ]
diff --git a/foundation_cms/core/migrations/0004_merge_20250522_1659.py b/foundation_cms/core/migrations/0004_merge_20250522_1659.py
new file mode 100644
index 00000000000..8242bb6029a
--- /dev/null
+++ b/foundation_cms/core/migrations/0004_merge_20250522_1659.py
@@ -0,0 +1,13 @@
+# Generated by Django 4.2.20 on 2025-05-22 16:59
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("core", "0003_add_theme_to_core_pages"),
+        ("core", "0003_generalpage_sized_images"),
+    ]
+
+    operations = []
diff --git a/foundation_cms/core/migrations/0006_merge_20250522_2222.py b/foundation_cms/core/migrations/0006_merge_20250522_2222.py
new file mode 100644
index 00000000000..8de3c4b46ed
--- /dev/null
+++ b/foundation_cms/core/migrations/0006_merge_20250522_2222.py
@@ -0,0 +1,13 @@
+# Generated by Django 4.2.20 on 2025-05-22 22:22
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("core", "0004_merge_20250522_1659"),
+        ("core", "0005_alter_generalpage_body_alter_homepage_body"),
+    ]
+
+    operations = []
diff --git a/foundation_cms/core/migrations/0007_remove_generalpage_sized_images_and_more.py b/foundation_cms/core/migrations/0007_remove_generalpage_sized_images_and_more.py
new file mode 100644
index 00000000000..5dd6ec7824e
--- /dev/null
+++ b/foundation_cms/core/migrations/0007_remove_generalpage_sized_images_and_more.py
@@ -0,0 +1,216 @@
+# Generated by Django 4.2.20 on 2025-05-23 00:13
+
+from django.db import migrations
+import wagtail.fields
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("core", "0006_merge_20250522_2222"),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name="generalpage",
+            name="sized_images",
+        ),
+        migrations.AlterField(
+            model_name="generalpage",
+            name="body",
+            field=wagtail.fields.StreamField(
+                [
+                    ("rich_text", 0),
+                    ("image", 4),
+                    ("audio", 9),
+                    ("tabbed_content", 15),
+                    ("two_column_container_block", 19),
+                ],
+                blank=True,
+                block_lookup={
+                    0: ("wagtail.blocks.RichTextBlock", (), {}),
+                    1: (
+                        "wagtail.blocks.CharBlock",
+                        (),
+                        {"help_text": "Title/caption for this image", "required": True},
+                    ),
+                    2: ("wagtail.images.blocks.ImageBlock", [], {}),
+                    3: (
+                        "wagtail.blocks.ChoiceBlock",
+                        [],
+                        {
+                            "choices": [("portrait", "Portrait"), ("landscape", "Landscape")],
+                            "help_text": "Select the orientation of this image",
+                        },
+                    ),
+                    4: ("wagtail.blocks.StructBlock", [[("title", 1), ("image", 2), ("orientation", 3)]], {}),
+                    5: (
+                        "wagtail.images.blocks.ImageChooserBlock",
+                        (),
+                        {"help_text": "Optional image to display at the top of the block.", "required": False},
+                    ),
+                    6: (
+                        "wagtail.blocks.CharBlock",
+                        (),
+                        {"help_text": "Image description (for screen readers).", "required": False},
+                    ),
+                    7: ("wagtail.blocks.CharBlock", (), {}),
+                    8: (
+                        "wagtail.blocks.TextBlock",
+                        (),
+                        {"help_text": "Short description of the audio", "required": False},
+                    ),
+                    9: (
+                        "wagtail.blocks.StructBlock",
+                        [
+                            [
+                                ("image", 5),
+                                ("image_alt_text", 6),
+                                ("title", 7),
+                                ("description", 8),
+                                ("simplecast_embed_code", 7),
+                            ]
+                        ],
+                        {},
+                    ),
+                    10: (
+                        "wagtail.blocks.CharBlock",
+                        (),
+                        {"help_text": "Title shown on the tab button", "required": True},
+                    ),
+                    11: (
+                        "wagtail.blocks.CharBlock",
+                        (),
+                        {"help_text": "Subtitle shown under the title on the tab", "required": False},
+                    ),
+                    12: (
+                        "wagtail.blocks.StreamBlock",
+                        [[("rich_text", 0)]],
+                        {"help_text": "Only one content block per tab.", "max_num": 1, "min_num": 1, "required": True},
+                    ),
+                    13: ("wagtail.blocks.StructBlock", [[("title", 10), ("subtitle", 11), ("content", 12)]], {}),
+                    14: ("wagtail.blocks.ListBlock", (13,), {"label": "Tabs", "max_num": 8, "min_num": 1}),
+                    15: ("wagtail.blocks.StructBlock", [[("tabs", 14)]], {}),
+                    16: (
+                        "wagtail.blocks.CharBlock",
+                        (),
+                        {"help_text": "Optional title for the block", "required": False},
+                    ),
+                    17: (
+                        "wagtail.blocks.StreamBlock",
+                        [[("rich_text", 0), ("audio", 9), ("image", 4)]],
+                        {"label": "Left Column"},
+                    ),
+                    18: (
+                        "wagtail.blocks.StreamBlock",
+                        [[("rich_text", 0), ("audio", 9), ("image", 4)]],
+                        {"label": "Right Column"},
+                    ),
+                    19: (
+                        "wagtail.blocks.StructBlock",
+                        [[("title", 16), ("left_column", 17), ("right_column", 18)]],
+                        {},
+                    ),
+                },
+            ),
+        ),
+        migrations.AlterField(
+            model_name="homepage",
+            name="body",
+            field=wagtail.fields.StreamField(
+                [
+                    ("rich_text", 0),
+                    ("image", 4),
+                    ("audio", 9),
+                    ("tabbed_content", 15),
+                    ("two_column_container_block", 19),
+                ],
+                blank=True,
+                block_lookup={
+                    0: ("wagtail.blocks.RichTextBlock", (), {}),
+                    1: (
+                        "wagtail.blocks.CharBlock",
+                        (),
+                        {"help_text": "Title/caption for this image", "required": True},
+                    ),
+                    2: ("wagtail.images.blocks.ImageBlock", [], {}),
+                    3: (
+                        "wagtail.blocks.ChoiceBlock",
+                        [],
+                        {
+                            "choices": [("portrait", "Portrait"), ("landscape", "Landscape")],
+                            "help_text": "Select the orientation of this image",
+                        },
+                    ),
+                    4: ("wagtail.blocks.StructBlock", [[("title", 1), ("image", 2), ("orientation", 3)]], {}),
+                    5: (
+                        "wagtail.images.blocks.ImageChooserBlock",
+                        (),
+                        {"help_text": "Optional image to display at the top of the block.", "required": False},
+                    ),
+                    6: (
+                        "wagtail.blocks.CharBlock",
+                        (),
+                        {"help_text": "Image description (for screen readers).", "required": False},
+                    ),
+                    7: ("wagtail.blocks.CharBlock", (), {}),
+                    8: (
+                        "wagtail.blocks.TextBlock",
+                        (),
+                        {"help_text": "Short description of the audio", "required": False},
+                    ),
+                    9: (
+                        "wagtail.blocks.StructBlock",
+                        [
+                            [
+                                ("image", 5),
+                                ("image_alt_text", 6),
+                                ("title", 7),
+                                ("description", 8),
+                                ("simplecast_embed_code", 7),
+                            ]
+                        ],
+                        {},
+                    ),
+                    10: (
+                        "wagtail.blocks.CharBlock",
+                        (),
+                        {"help_text": "Title shown on the tab button", "required": True},
+                    ),
+                    11: (
+                        "wagtail.blocks.CharBlock",
+                        (),
+                        {"help_text": "Subtitle shown under the title on the tab", "required": False},
+                    ),
+                    12: (
+                        "wagtail.blocks.StreamBlock",
+                        [[("rich_text", 0)]],
+                        {"help_text": "Only one content block per tab.", "max_num": 1, "min_num": 1, "required": True},
+                    ),
+                    13: ("wagtail.blocks.StructBlock", [[("title", 10), ("subtitle", 11), ("content", 12)]], {}),
+                    14: ("wagtail.blocks.ListBlock", (13,), {"label": "Tabs", "max_num": 8, "min_num": 1}),
+                    15: ("wagtail.blocks.StructBlock", [[("tabs", 14)]], {}),
+                    16: (
+                        "wagtail.blocks.CharBlock",
+                        (),
+                        {"help_text": "Optional title for the block", "required": False},
+                    ),
+                    17: (
+                        "wagtail.blocks.StreamBlock",
+                        [[("rich_text", 0), ("audio", 9), ("image", 4)]],
+                        {"label": "Left Column"},
+                    ),
+                    18: (
+                        "wagtail.blocks.StreamBlock",
+                        [[("rich_text", 0), ("audio", 9), ("image", 4)]],
+                        {"label": "Right Column"},
+                    ),
+                    19: (
+                        "wagtail.blocks.StructBlock",
+                        [[("title", 16), ("left_column", 17), ("right_column", 18)]],
+                        {},
+                    ),
+                },
+            ),
+        ),
+    ]
diff --git a/foundation_cms/core/models/general_page.py b/foundation_cms/core/models/general_page.py
index c4877be9c9b..c098987688a 100644
--- a/foundation_cms/core/models/general_page.py
+++ b/foundation_cms/core/models/general_page.py
@@ -6,6 +6,9 @@
 
 
 class GeneralPage(AbstractGeneralPage):
+    # Specify the correct template path
+    template = "patterns/pages/core/general_page.html"
+    
     hero_headline = models.CharField(
         max_length=120,
         help_text="Hero story headline",
@@ -41,7 +44,6 @@ class GeneralPage(AbstractGeneralPage):
         FieldPanel("header"),
         FieldPanel("button_title"),
         FieldPanel("button_url"),
-        FieldPanel("body"),
     ]
 
     translatable_fields = [
diff --git a/foundation_cms/static/scss/components/images.scss b/foundation_cms/static/scss/components/images.scss
new file mode 100644
index 00000000000..048addcc693
--- /dev/null
+++ b/foundation_cms/static/scss/components/images.scss
@@ -0,0 +1,28 @@
+// Image block styles
+.image-block {
+  margin-bottom: 2rem;
+}
+
+.image-block-container {
+  margin-bottom: 1rem;
+}
+
+.image-block-portrait .image-block-image {
+  width: 60%;
+  height: 400px;
+  object-fit: cover;
+  margin: 0 auto;
+  display: block;
+}
+
+.image-block-landscape .image-block-image {
+  width: 80%;
+  height: 300px;
+  object-fit: cover;
+  margin: 0 auto;
+  display: block;
+}
+
+.image-block-title {
+  text-align: center;
+}
diff --git a/foundation_cms/static/scss/redesign_main.scss b/foundation_cms/static/scss/redesign_main.scss
index 9bcb6f661e2..6ec3b8c91b3 100644
--- a/foundation_cms/static/scss/redesign_main.scss
+++ b/foundation_cms/static/scss/redesign_main.scss
@@ -3,6 +3,7 @@
 @import "../scss/fonts";
 @import "../scss/components/primary_nav";
 @import "../scss/components/footer";
+@import "../scss/components/images";
 
 // Foundation Modules
 @include foundation-global-styles;
diff --git a/foundation_cms/templates/patterns/blocks/image_block.html b/foundation_cms/templates/patterns/blocks/image_block.html
new file mode 100644
index 00000000000..768d4b0c2f5
--- /dev/null
+++ b/foundation_cms/templates/patterns/blocks/image_block.html
@@ -0,0 +1,10 @@
+{% load wagtailimages_tags %}
+<div class="image-block image-block-{{ value.orientation }}">
+    <div class="image-block-container">
+        {% image value.image original as img %}
+        <img src="{{ img.url }}"
+             alt="{{ value.title }}"
+             class="image-block-image">
+    </div>
+    {% if value.title %}<p class="image-block-title">{{ value.title }}</p>{% endif %}
+</div>
diff --git a/foundation_cms/templates/patterns/pages/core/general_page.html b/foundation_cms/templates/patterns/pages/core/general_page.html
new file mode 100644
index 00000000000..d762c869064
--- /dev/null
+++ b/foundation_cms/templates/patterns/pages/core/general_page.html
@@ -0,0 +1,11 @@
+{% extends "base.html" %}
+{% load wagtailcore_tags wagtailimages_tags %}
+{% block content %}
+    <div class="general-page">
+        <div class="general-page__body">
+            <div class="container">
+                {{ page.body }}
+            </div>
+        </div>
+    </div>
+{% endblock %}


Reviews:
user: login: robdivincenzo
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-23T15:17:04Z
user: login: robdivincenzo
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-23T15:20:42Z
user: login: robdivincenzo
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-23T15:21:30Z
user: login: robdivincenzo
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-23T15:23:04Z
user: login: robdivincenzo
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-23T15:24:29Z
user: login: robdivincenzo
body: Awesome work @dlopezvsr and approved! Smooth editing experience 🚀

Some small tweaks because dev patterns changed a little bit. Once these are in good to merge!!

state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-23T15:25:13Z

Review comments:
diff_hunk: @@ -24,3 +24,7 @@ def listing_page(request):
     }
 
     return render(request, "patterns/pages/core/listing_page.html", context)
+
+def general_page(request):
+    
+    return render(request, "patterns/pages/core/general_page.html")
path: foundation_cms/core/views.py
user: login: robdivincenzo
body: ```suggestion
```
Only required for something custom like the listing page, but GeneralPage inherits from wagtail's `Page` model via the `AbstractBasePage` model 👍
created_at: 2025-05-23T15:17:04Z
author_association: COLLABORATOR
diff_hunk: @@ -139,6 +139,7 @@ def csrf_response(request):
 urlpatterns += i18n_patterns(
     path("search/", search_views.search, name="search"),
     path("listing_page/", core_views.listing_page, name="listing_page"),
+    path("general_page/", core_views.general_page, name="general_page"),
path: foundation_cms/urls.py
user: login: robdivincenzo
body: ```suggestion
```
Only required for something custom like the listing page, but GeneralPage inherits from wagtail's `Page` model via the `AbstractBasePage` model 👍
created_at: 2025-05-23T15:20:42Z
author_association: COLLABORATOR
diff_hunk: @@ -3,6 +3,7 @@
 @import "../scss/fonts";
 @import "../scss/components/primary_nav";
 @import "../scss/components/footer";
+@use "../scss/components/images.scss";
path: foundation_cms/static/scss/redesign_main.scss
user: login: robdivincenzo
body: ```suggestion
@import "../scss/components/images.scss";
```
@mmmavis just had a PR where this is changed to `@import`, so figure we should catch that now!
created_at: 2025-05-23T15:21:29Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,22 @@
+from wagtail.blocks import StructBlock, CharBlock, ChoiceBlock
+from wagtail.images.blocks import ImageBlock
+
+class CustomImageBlock(StructBlock):
path: foundation_cms/blocks/image_block.py
user: login: robdivincenzo
body: ```suggestion
from wagtail.blocks import CharBlock, ChoiceBlock
from wagtail.images.blocks import ImageBlock
from foundation_cms.base.models.base_block import BaseBlock

class CustomImageBlock(BaseBlock):
```
New pattern is to inherit from the `BaseBlock`. I have a PR out there do retroactively update mine but let's catch this one now!
created_at: 2025-05-23T15:23:04Z
author_association: COLLABORATOR
path: foundation_cms/templates/patterns/blocks/image_block.html
user: login: robdivincenzo
body: Once the `image_block.py` template_name is updated, this will need to live in `/patterns/blocks/themes/default/image_block.html`
created_at: 2025-05-23T15:24:28Z
author_association: COLLABORATOR

PR comments:
user: login: robdivincenzo
created_at: 2025-05-22T19:57:58Z
body: Looking good @dlopezvsr 🔥🔥🔥! This is giving me a couple ideas.

1. Instead of creating new `sized_images` fields, what if we added it as a [streamfield block option in the body field](https://github.com/MozillaFoundation/foundation.mozilla.org/blob/59a2af93de1a646724b847376a22786d360ccec3/foundation_cms/base/models/abstract_base_page.py#L52-L59) on the `AbstractBasePage` model so that a user can place it anywhere in the body on any page?
2. I also wonder if we can consolidate this down to just a single ImageBlock and re-use that in a bunch of places.
-- Single Image Portrait Block = your single `ImageBlock`
-- Single Image Landscape Block = your single `ImageBlock`
-- Double Image Block = Update the options in the [TwoColumnContainerBlock](https://github.com/MozillaFoundation/foundation.mozilla.org/pull/14001) (technically `ColumnStreamBlock`) with your new `ImageBlock`, and the editor can select `ImageBlock` for both columns.
--- Portrait/Landscape variants(s) handled on the ImageBlock level like you have.

If we can do it like that, it would cut down our need to maintain a separate two column layout. And possibly streamline the editor experience. Do you have any thoughts on this?
author_association: COLLABORATOR
user: login: dlopezvsr
created_at: 2025-05-23T14:36:46Z
body: > Looking good @dlopezvsr 🔥🔥🔥! This is giving me a couple ideas.
> 
>     1. Instead of creating new `sized_images` fields, what if we added it as a [streamfield block option in the body field](https://github.com/MozillaFoundation/foundation.mozilla.org/blob/59a2af93de1a646724b847376a22786d360ccec3/foundation_cms/base/models/abstract_base_page.py#L52-L59) on the `AbstractBasePage` model so that a user can place it anywhere in the body on any page?
> 
>     2. I also wonder if we can consolidate this down to just a single ImageBlock and re-use that in a bunch of places.
>        -- Single Image Portrait Block = your single `ImageBlock`
>        -- Single Image Landscape Block = your single `ImageBlock`
>        -- Double Image Block = Update the options in the [TwoColumnContainerBlock](https://github.com/MozillaFoundation/foundation.mozilla.org/pull/14001) (technically `ColumnStreamBlock`) with your new `ImageBlock`, and the editor can select `ImageBlock` for both columns.
>        --- Portrait/Landscape variants(s) handled on the ImageBlock level like you have.
> 
> 
> If we can do it like that, it would cut down our need to maintain a separate two column layout. And possibly streamline the editor experience. Do you have any thoughts on this?

Hey @robdivincenzo thanks so much for your comments. I found them very useful and accurate!

I just finished applying these changes, let me know what you think:
- I removed the previous duplicated blocks I had created, and left only one block: `CustomImageBlock`
- I included this block inside of the `StreamField` body to make it more flexible and reusable.
- And finally included it inside `ColumnStreamBlock`

Let me know what you think 👍 
author_association: CONTRIBUTOR