@designcrowd/library.brand-page 6.1.10 → 6.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-nsMF2NQ9.js → CreatedOnBrandCrowd-de-de-DToCz9Tp.js} +1 -1
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-BQI_UCR7.js → CreatedOnBrandCrowd-en-us-CrjiEGmM.js} +1 -1
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-DgNOoSPn.js → CreatedOnBrandCrowd-es-es-DgVVM-Df.js} +1 -1
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-JFRaxjvx.js → CreatedOnBrandCrowd-fr-ca-C_oKH8iu.js} +1 -1
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-JFRaxjvx.js → CreatedOnBrandCrowd-fr-fr-C_oKH8iu.js} +1 -1
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-DP6_-90i.js → CreatedOnBrandCrowd-pt-br-DRvHblvI.js} +1 -1
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-DP6_-90i.js → CreatedOnBrandCrowd-pt-pt-DRvHblvI.js} +1 -1
  8. package/lib/src/{CreatedOnDcom-de-de-07F2wjkk.js → CreatedOnDcom-de-de-uCIRFQYA.js} +1 -1
  9. package/lib/src/{CreatedOnDcom-en-us-DaV9sa6e.js → CreatedOnDcom-en-us-DfbhIoId.js} +1 -1
  10. package/lib/src/{CreatedOnDcom-es-es-73uOymvv.js → CreatedOnDcom-es-es-CipmOU1-.js} +1 -1
  11. package/lib/src/{CreatedOnDcom-fr-ca-Ci9Ome30.js → CreatedOnDcom-fr-ca-B5Cq9iJK.js} +1 -1
  12. package/lib/src/{CreatedOnDcom-fr-fr-DLHuMHfE.js → CreatedOnDcom-fr-fr-DIvycPcx.js} +1 -1
  13. package/lib/src/{CreatedOnDcom-pt-br-Fbt7tY-E.js → CreatedOnDcom-pt-br-BQ3EajZC.js} +1 -1
  14. package/lib/src/{CreatedOnDcom-pt-pt-V2jTB6of.js → CreatedOnDcom-pt-pt-DchaL_tP.js} +1 -1
  15. package/lib/src/brand-page/constants/index.d.ts +1 -0
  16. package/lib/src/brand-page/constants/index.js +1 -0
  17. package/lib/src/brand-page/constants/sections/header-display.constants.d.ts +2 -1
  18. package/lib/src/brand-page/constants/sections/header-display.constants.js +24 -24
  19. package/lib/src/brand-page/constants/sections/navigation-type.constants.d.ts +9 -0
  20. package/lib/src/brand-page/constants/sections/navigation-type.constants.js +33 -0
  21. package/lib/src/brand-page/models/index.d.ts +1 -1
  22. package/lib/src/brand-page/models/index.js +6 -6
  23. package/lib/src/brand-page/utils/container-text-color.util.d.ts +7 -4
  24. package/lib/src/brand-page/utils/container-text-color.util.js +11 -6
  25. package/lib/src/brand-page/utils/migration/migration-21.js +62 -0
  26. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +1 -0
  27. package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +1 -0
  28. package/lib/src/brand-page-maker/models/navigation-style-configs.d.ts +1 -0
  29. package/lib/src/brand-page-maker/models/navigation-style-configs.js +31 -7
  30. package/lib/src/{bundled-translations-C98dISr1.js → bundled-translations-BiFAlQdC.js} +21 -21
  31. package/lib/src/{bundled-translations.de-DE-D8ul_G4P.js → bundled-translations.de-DE-DuRWnOP-.js} +14 -14
  32. package/lib/src/{bundled-translations.es-ES-BVmPyfw9.js → bundled-translations.es-ES-CokHdZtW.js} +17 -17
  33. package/lib/src/{bundled-translations.fr-CA-DmzKPhfa.js → bundled-translations.fr-CA-CJcMmubA.js} +1 -1
  34. package/lib/src/{bundled-translations.fr-FR-CQRiASGY.js → bundled-translations.fr-FR-DYPup8fJ.js} +1 -1
  35. package/lib/src/{bundled-translations.pt-BR-guDO9Yfg.js → bundled-translations.pt-BR-GSsp1kuV.js} +1 -1
  36. package/lib/src/{bundled-translations.pt-PT-GFpLNo-u.js → bundled-translations.pt-PT-DbkY8UQ3.js} +9 -9
  37. package/lib/src/bundles/bundled-translations.de-DE.json +5 -0
  38. package/lib/src/bundles/bundled-translations.es-ES.json +5 -0
  39. package/lib/src/bundles/bundled-translations.fr-CA.json +5 -0
  40. package/lib/src/bundles/bundled-translations.fr-FR.json +5 -0
  41. package/lib/src/bundles/bundled-translations.json +5 -0
  42. package/lib/src/bundles/bundled-translations.pt-BR.json +5 -0
  43. package/lib/src/bundles/bundled-translations.pt-PT.json +5 -0
  44. package/lib/src/css/library.brand-page-brandCrowd.min.css +185 -63
  45. package/lib/src/css/library.brand-page-brandPage.min.css +190 -63
  46. package/lib/src/css/library.brand-page-designCom.min.css +185 -63
  47. package/lib/src/css/library.brand-page-preview.min.css +185 -63
  48. package/lib/src/index.mjs +1 -1
  49. package/lib/src/{lib-D2a1EmRb.js → lib-zpGnKGKw.js} +32747 -32191
  50. package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +1 -0
  51. package/lib/src/shared/content-settings/components/content-settings/ReplaceImageSettingsForContent.mixin.d.ts +1 -0
  52. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.d.ts +1 -0
  53. package/lib/src/shared/content-settings/components/payment-config-field/PaymentConfigField.mixin.d.ts +1 -0
  54. package/lib/src/shared/logo-settings/components/LogoSettings.mixin.d.ts +1 -0
  55. package/lib/src/shared/page-settings/components/new-page-modal/NewPageModal.mixin.d.ts +1 -0
  56. package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +1 -0
  57. package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +4 -1
  58. package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.js +15 -1
  59. package/lib/src/shared/page-settings/store/page-settings-store.d.ts +6 -0
  60. package/lib/src/shared/page-settings/store/page-settings-store.js +22 -1
  61. package/package.json +2 -2
  62. package/src/brand-page/constants/index.ts +1 -0
  63. package/src/brand-page/constants/sections/header-display.constants.ts +26 -25
  64. package/src/brand-page/constants/sections/navigation-type.constants.test.ts +53 -0
  65. package/src/brand-page/constants/sections/navigation-type.constants.ts +48 -0
  66. package/src/brand-page/models/footer-icon-color.test.ts +38 -102
  67. package/src/brand-page/models/index.ts +6 -9
  68. package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +6 -6
  69. package/src/brand-page/utils/__snapshots__/migration2.util.test.ts.snap +2 -6
  70. package/src/brand-page/utils/__snapshots__/model.util.test.ts.snap +4 -12
  71. package/src/brand-page/utils/container-text-color.util.test.ts +16 -0
  72. package/src/brand-page/utils/container-text-color.util.ts +15 -6
  73. package/src/brand-page/utils/migration/migration-21.test.ts +169 -0
  74. package/src/brand-page/utils/migration/migration-21.ts +75 -0
  75. package/src/brand-page-maker/models/i18n/models.de-DE.json +3 -0
  76. package/src/brand-page-maker/models/i18n/models.es-ES.json +3 -0
  77. package/src/brand-page-maker/models/i18n/models.fr-CA.json +3 -0
  78. package/src/brand-page-maker/models/i18n/models.fr-FR.json +3 -0
  79. package/src/brand-page-maker/models/i18n/models.json +3 -0
  80. package/src/brand-page-maker/models/i18n/models.pt-BR.json +3 -0
  81. package/src/brand-page-maker/models/i18n/models.pt-PT.json +3 -0
  82. package/src/brand-page-maker/models/navigation-style-configs.ts +32 -6
  83. package/src/bundles/bundled-translations.de-DE.json +5 -0
  84. package/src/bundles/bundled-translations.es-ES.json +5 -0
  85. package/src/bundles/bundled-translations.fr-CA.json +5 -0
  86. package/src/bundles/bundled-translations.fr-FR.json +5 -0
  87. package/src/bundles/bundled-translations.json +5 -0
  88. package/src/bundles/bundled-translations.pt-BR.json +5 -0
  89. package/src/bundles/bundled-translations.pt-PT.json +5 -0
  90. package/src/shared/navigation-style-settings/components/navigation-style-settings/NavigationStyleSettings.vue +0 -6
  91. package/src/shared/page-settings/components/header-settings-panel/HeaderSettingsPanel.vue +8 -0
  92. package/src/shared/page-settings/components/page-settings-section-styles/HeaderNavigationStyle.vue +95 -0
  93. package/src/shared/page-settings/components/page-settings-section-styles/HeaderNavigationType.vue +26 -14
  94. package/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.ts +33 -1
  95. package/src/shared/page-settings/components/page-settings-section-styles/i18n/page-settings-section.de-DE.json +10 -7
  96. package/src/shared/page-settings/components/page-settings-section-styles/i18n/page-settings-section.es-ES.json +10 -7
  97. package/src/shared/page-settings/components/page-settings-section-styles/i18n/page-settings-section.fr-CA.json +10 -7
  98. package/src/shared/page-settings/components/page-settings-section-styles/i18n/page-settings-section.fr-FR.json +10 -7
  99. package/src/shared/page-settings/components/page-settings-section-styles/i18n/page-settings-section.json +3 -0
  100. package/src/shared/page-settings/components/page-settings-section-styles/i18n/page-settings-section.pt-BR.json +10 -7
  101. package/src/shared/page-settings/components/page-settings-section-styles/i18n/page-settings-section.pt-PT.json +10 -7
  102. package/src/shared/page-settings/store/page-settings-store.test.ts +97 -0
  103. package/src/shared/page-settings/store/page-settings-store.ts +36 -1
@@ -1,142 +1,78 @@
1
1
  import { describe, test, expect } from 'vitest';
2
- import {
3
- BrandPageModel,
4
- PageFooterDesktop,
5
- PageFooterMobile,
6
- type Theme,
7
- } from './index';
2
+ import { BrandPageModel, PageFooterDesktop, PageFooterMobile } from './index';
8
3
  import {
9
4
  migrateBrandPageModel,
10
5
  GetLatestModelVersion,
11
6
  } from '../utils/migration.util';
12
- import { STYLE_THEME_KEYS } from '../constants';
13
-
14
- // BP-5886 (colour-v2): the footer social-icon colour must be wired to the same
15
- // logo/global colour source as every other surface. It is seeded from the theme
16
- // at construction (like the header's nav hamburger icon), then resolved /
17
- // referencified downstream. See docs/plans/feat-bp-5886-*.
18
- describe('footer icon colour is wired to the theme', () => {
19
- // The footer background is transparent, so the icons sit on the page
20
- // background (logo-background / secondaryColor); they must therefore take the
21
- // contrasting logo-text colour (primaryColor) to stay legible.
22
- const theme: Theme = {
23
- primaryColor: STYLE_THEME_KEYS.LogoTextColor,
24
- secondaryColor: STYLE_THEME_KEYS.LogoBackgroundColor,
25
- };
26
7
 
27
- describe('PageFooter* constructor seeding', () => {
28
- test('seeds iconStyle.fill from theme.primaryColor (logo text) when a theme is passed', () => {
29
- expect(new PageFooterDesktop({}, theme).iconStyle).toEqual({
30
- fill: STYLE_THEME_KEYS.LogoTextColor,
31
- });
32
- expect(new PageFooterMobile({}, theme).iconStyle).toEqual({
33
- fill: STYLE_THEME_KEYS.LogoTextColor,
34
- });
35
- });
36
-
37
- test('stays empty when no theme is passed (non-theme call sites unchanged)', () => {
8
+ // The footer social icons carry no fill in their SVGs, so an unstyled footer must not
9
+ // inherit a site text colour (which need not read on the footer band). The constructor
10
+ // leaves the icon unset; the fallback is decided downstream — migration-21 bakes an
11
+ // explicit black, and a not-yet-migrated model renders the SVG default. An explicit
12
+ // persisted colour always wins.
13
+ describe('footer icon colour fallback', () => {
14
+ describe('PageFooter* constructor', () => {
15
+ test('leaves iconStyle unset (no colour seed)', () => {
38
16
  expect(new PageFooterDesktop().iconStyle).toEqual({});
39
17
  expect(new PageFooterMobile().iconStyle).toEqual({});
40
18
  });
41
19
 
42
- test('repaints an empty persisted iconStyle', () => {
43
- const footer = new PageFooterDesktop({ iconStyle: {} }, theme);
44
-
45
- expect(footer.iconStyle).toEqual({
46
- fill: STYLE_THEME_KEYS.LogoTextColor,
47
- });
48
- });
49
-
50
20
  test('preserves an explicit persisted icon colour', () => {
51
- const footer = new PageFooterDesktop(
52
- { iconStyle: { fill: '#abcdef' } },
53
- theme,
54
- );
55
-
56
- expect(footer.iconStyle.fill).toBe('#abcdef');
21
+ expect(
22
+ new PageFooterDesktop({ iconStyle: { fill: '#abcdef' } }).iconStyle
23
+ .fill,
24
+ ).toBe('#abcdef');
57
25
  });
58
26
  });
59
27
 
60
- describe('BrandPageModel wiring', () => {
61
- test('a fresh model seeds both footer viewports with the logo-text colour', () => {
62
- // theme.primaryColor is derived from styles.textStyles.style.color, which
63
- // defaults to LogoTextColor (TextStyles.GetDefaultTextStyle).
64
- const model = new BrandPageModel({});
65
-
66
- expect(model.pageFooterDesktop.iconStyle.fill).toBe(
67
- STYLE_THEME_KEYS.LogoTextColor,
68
- );
69
- expect(model.pageFooterMobile.iconStyle.fill).toBe(
70
- STYLE_THEME_KEYS.LogoTextColor,
71
- );
72
- });
73
- });
74
-
75
- describe('end-to-end through migration', () => {
76
- test('the seeded footer icon token is referencified to a palette reference', async () => {
28
+ describe('through migration', () => {
29
+ test('an unset footer icon is baked to a literal black fallback', async () => {
77
30
  // Fresh model is v=0, so it runs the full migration chain incl. migration-21.
78
31
  const { brandPageModel } = await migrateBrandPageModel(
79
32
  new BrandPageModel({}),
80
33
  { isUserDraft: false, isServerRender: false },
81
34
  );
82
35
 
83
- const fill = brandPageModel.pageFooterDesktop.iconStyle.fill;
84
-
85
- // It was seeded (not empty) and got referencified off the raw token to a
86
- // well-formed palette reference.
87
- expect(fill).not.toBe(STYLE_THEME_KEYS.LogoBackgroundColor);
88
- expect(fill).toMatch(/^var\(--palette-\d+\)$/);
36
+ expect(brandPageModel.pageFooterDesktop.iconStyle.fill).toBe('#000000');
37
+ expect(brandPageModel.pageFooterMobile.iconStyle.fill).toBe('#000000');
89
38
  });
90
39
 
91
- test('an already-migrated page seeds a render-ready value and needs no re-migration', async () => {
92
- // An existing colour-v2 page is at the latest version with an already-
93
- // referencified text colour. The footer icon inherits that value via
94
- // theme.primaryColor, so it is render-ready without migration-21 re-running.
40
+ test('an explicit footer icon colour is preserved (referencified, not blacked out)', async () => {
95
41
  const persisted = {
96
- v: GetLatestModelVersion(),
97
- styles: {
98
- textStyles: { style: { color: 'var(--palette-0)' } },
99
- },
100
- pageFooterDesktop: { iconStyle: {} },
42
+ styles: {},
43
+ pages: [],
44
+ pageFooterDesktop: { iconStyle: { fill: '#abcdef' } },
101
45
  pageFooterMobile: { iconStyle: {} },
102
46
  };
103
47
 
104
- const model = new BrandPageModel(persisted);
105
-
106
- expect(model.pageFooterDesktop.iconStyle.fill).toBe('var(--palette-0)');
107
-
108
- const { brandPageModel } = await migrateBrandPageModel(model, {
109
- isUserDraft: false,
110
- isServerRender: false,
111
- });
48
+ const { brandPageModel } = await migrateBrandPageModel(
49
+ new BrandPageModel(persisted),
50
+ { isUserDraft: false, isServerRender: false },
51
+ );
112
52
 
113
- // Version gate skips migration-21, so the seeded palette ref is untouched.
114
- expect(brandPageModel.pageFooterDesktop.iconStyle.fill).toBe(
115
- 'var(--palette-0)',
53
+ // The explicit colour rode pass B into a palette reference; the empty one took black.
54
+ expect(brandPageModel.pageFooterDesktop.iconStyle.fill).toMatch(
55
+ /^var\(--palette-/,
116
56
  );
57
+ expect(brandPageModel.pageFooterMobile.iconStyle.fill).toBe('#000000');
117
58
  });
118
59
 
119
- test('a legacy footerStyles model seeds the icon before migration-01 re-wraps it', async () => {
120
- // Pre-split models carry deprecated headerStyles/footerStyles; migration-01
121
- // converts footerStyles → pageFooter*. The legacy footer must also be
122
- // theme-seeded (like the legacy header nav) so the seed rides the re-wrap
123
- // and migration-21 referencifies it — otherwise legacy sites keep empty
124
- // footer icons after full migration.
125
- const legacy = {
60
+ test('an already-migrated page is left untouched (version gate skips the bake)', async () => {
61
+ const persisted = {
62
+ v: GetLatestModelVersion(),
126
63
  styles: {},
127
- pages: [],
128
- headerStyles: { navigationStyles: { displayType: 'tabs' } },
129
- footerStyles: { iconStyle: {} },
64
+ pageFooterDesktop: { iconStyle: {} },
65
+ pageFooterMobile: { iconStyle: {} },
130
66
  };
131
67
 
132
68
  const { brandPageModel } = await migrateBrandPageModel(
133
- new BrandPageModel(legacy),
69
+ new BrandPageModel(persisted),
134
70
  { isUserDraft: false, isServerRender: false },
135
71
  );
136
72
 
137
- expect(brandPageModel.pageFooterDesktop.iconStyle.fill).toMatch(
138
- /^var\(--palette-\d+\)$/,
139
- );
73
+ // migration-21 does not re-run, so the empty icon is left for the SVG default.
74
+ expect(brandPageModel.pageFooterDesktop.iconStyle).toEqual({});
75
+ expect(brandPageModel.pageFooterMobile.iconStyle).toEqual({});
140
76
  });
141
77
  });
142
78
  });
@@ -849,7 +849,7 @@ export abstract class AbstractPageFooterStyles {
849
849
 
850
850
  protected constructor(
851
851
  options: AbstractPageFooterStylesType = {},
852
- theme?: Theme,
852
+ _theme?: Theme,
853
853
  ) {
854
854
  if (typeof options.viewport === 'undefined') {
855
855
  throw new Error('"viewport" is required');
@@ -920,14 +920,11 @@ export abstract class AbstractPageFooterStyles {
920
920
  imageLink: '',
921
921
  },
922
922
  ];
923
- // Seed the social-icon colour from the theme's PRIMARY (logo text) colour: the
924
- // footer background is transparent, so icons sit on the page background — the
925
- // logo-background colour — and need the contrasting one to stay legible.
926
- // Object.assign so an explicit persisted colour still wins.
927
- this.iconStyle = Object.assign(
928
- theme?.primaryColor ? { fill: theme.primaryColor } : {},
929
- options.iconStyle || {},
930
- );
923
+ // No colour seed: the social icons carry no fill of their own, so an unstyled footer
924
+ // leaves them unset and the fallback is decided downstream — migration-21 bakes an
925
+ // explicit black, a not-yet-migrated model renders the SVG default. Seeding a site
926
+ // text colour here need not read against the footer band.
927
+ this.iconStyle = options.iconStyle ? { ...options.iconStyle } : {};
931
928
  this.textFontFamily = options.textFontFamily;
932
929
  }
933
930
  }
@@ -41,7 +41,7 @@ exports[`migration.util > Banner Contents - text Align and Font Family should no
41
41
  "copyrightText": "© 2026 by [Company Name]. All Rights Reserved.",
42
42
  "displayStyle": "centered-menu",
43
43
  "iconStyle": {
44
- "fill": "var(--palette-0)",
44
+ "fill": "#000000",
45
45
  },
46
46
  "isLogoOptional": true,
47
47
  "isLogoVisible": true,
@@ -101,7 +101,7 @@ exports[`migration.util > Banner Contents - text Align and Font Family should no
101
101
  "copyrightText": "© 2026 by [Company Name]. All Rights Reserved.",
102
102
  "displayStyle": "centered-menu",
103
103
  "iconStyle": {
104
- "fill": "var(--palette-0)",
104
+ "fill": "#000000",
105
105
  },
106
106
  "isLogoOptional": true,
107
107
  "isLogoVisible": true,
@@ -534,7 +534,7 @@ exports[`migration.util > migrateBrandPageModel should return brand page model w
534
534
  "copyrightText": "© 2026 by [Company Name]. All Rights Reserved.",
535
535
  "displayStyle": "centered-menu",
536
536
  "iconStyle": {
537
- "fill": "var(--palette-0)",
537
+ "fill": "#000000",
538
538
  },
539
539
  "isLogoOptional": true,
540
540
  "isLogoVisible": true,
@@ -594,7 +594,7 @@ exports[`migration.util > migrateBrandPageModel should return brand page model w
594
594
  "copyrightText": "© 2026 by [Company Name]. All Rights Reserved.",
595
595
  "displayStyle": "centered-menu",
596
596
  "iconStyle": {
597
- "fill": "var(--palette-0)",
597
+ "fill": "#000000",
598
598
  },
599
599
  "isLogoOptional": true,
600
600
  "isLogoVisible": true,
@@ -1011,7 +1011,7 @@ exports[`migration.util > migrateBrandPageModel should return brand page model w
1011
1011
  "copyrightText": "© 2026 by [Company Name]. All Rights Reserved.",
1012
1012
  "displayStyle": "centered-menu",
1013
1013
  "iconStyle": {
1014
- "fill": "var(--palette-0)",
1014
+ "fill": "#000000",
1015
1015
  },
1016
1016
  "isLogoOptional": true,
1017
1017
  "isLogoVisible": true,
@@ -1071,7 +1071,7 @@ exports[`migration.util > migrateBrandPageModel should return brand page model w
1071
1071
  "copyrightText": "© 2026 by [Company Name]. All Rights Reserved.",
1072
1072
  "displayStyle": "centered-menu",
1073
1073
  "iconStyle": {
1074
- "fill": "var(--palette-0)",
1074
+ "fill": "#000000",
1075
1075
  },
1076
1076
  "isLogoOptional": true,
1077
1077
  "isLogoVisible": true,
@@ -26,9 +26,7 @@ exports[`migration02.ts > migration02 should return converted letterspacing 1`]
26
26
  },
27
27
  "copyrightText": "© 2026 by [Company Name]. All Rights Reserved.",
28
28
  "displayStyle": "centered-menu",
29
- "iconStyle": {
30
- "fill": "logo-text-color",
31
- },
29
+ "iconStyle": {},
32
30
  "isLogoOptional": true,
33
31
  "isLogoVisible": true,
34
32
  "logoDraftToken": "",
@@ -87,9 +85,7 @@ exports[`migration02.ts > migration02 should return converted letterspacing 1`]
87
85
  },
88
86
  "copyrightText": "© 2026 by [Company Name]. All Rights Reserved.",
89
87
  "displayStyle": "centered-menu",
90
- "iconStyle": {
91
- "fill": "logo-text-color",
92
- },
88
+ "iconStyle": {},
93
89
  "isLogoOptional": true,
94
90
  "isLogoVisible": true,
95
91
  "logoDraftToken": "",
@@ -25,9 +25,7 @@ BrandPageModel {
25
25
  },
26
26
  "copyrightText": "© 2026 by [Company Name]. All Rights Reserved.",
27
27
  "displayStyle": "centered-menu",
28
- "iconStyle": {
29
- "fill": "logo-text-color",
30
- },
28
+ "iconStyle": {},
31
29
  "isLogoOptional": true,
32
30
  "isLogoVisible": true,
33
31
  "logoDraftToken": "",
@@ -86,9 +84,7 @@ BrandPageModel {
86
84
  },
87
85
  "copyrightText": "© 2026 by [Company Name]. All Rights Reserved.",
88
86
  "displayStyle": "centered-menu",
89
- "iconStyle": {
90
- "fill": "logo-text-color",
91
- },
87
+ "iconStyle": {},
92
88
  "isLogoOptional": true,
93
89
  "isLogoVisible": true,
94
90
  "logoDraftToken": "",
@@ -486,9 +482,7 @@ BrandPageModel {
486
482
  },
487
483
  "copyrightText": "© 2026 by [Company Name]. All Rights Reserved.",
488
484
  "displayStyle": "centered-menu",
489
- "iconStyle": {
490
- "fill": "logo-text-color",
491
- },
485
+ "iconStyle": {},
492
486
  "isLogoOptional": true,
493
487
  "isLogoVisible": true,
494
488
  "logoDraftToken": "",
@@ -547,9 +541,7 @@ BrandPageModel {
547
541
  },
548
542
  "copyrightText": "© 2026 by [Company Name]. All Rights Reserved.",
549
543
  "displayStyle": "centered-menu",
550
- "iconStyle": {
551
- "fill": "logo-text-color",
552
- },
544
+ "iconStyle": {},
553
545
  "isLogoOptional": true,
554
546
  "isLogoVisible": true,
555
547
  "logoDraftToken": "",
@@ -323,6 +323,22 @@ describe('rteFrameFallbackColour', () => {
323
323
  ).toBeUndefined();
324
324
  });
325
325
 
326
+ // The maker's default text/profile background is the literal `'transparent'`, which the
327
+ // PRE-V2 renderer framed (its gate was `!isHexTransparent`, and that only excludes an
328
+ // alpha-00 hex) — so the RTE inherited the button colour. The bake must reproduce it, or
329
+ // those sites silently recolour from the button colour to the root paint.
330
+ test.each(['text', 'profile'] as const)(
331
+ 'returns the button text colour for a %s section with a literal `transparent` background',
332
+ (type) => {
333
+ expect(
334
+ rteFrameFallbackColour(
335
+ framed(type, { backgroundColor: 'transparent' }),
336
+ stylesWith({ backgroundColor: BUTTON_FILL, color: BUTTON_TEXT }),
337
+ ),
338
+ ).toBe(BUTTON_TEXT);
339
+ },
340
+ );
341
+
326
342
  // --- container-bound blocks: always framed, whatever the card colour ---
327
343
  test('mailing-list on a CUSTOM (non-button-fill) card: still the button text colour', () => {
328
344
  expect(
@@ -17,7 +17,11 @@ import type {
17
17
  } from '../models';
18
18
  import { BRAND_PAGE_TYPES } from '../models';
19
19
  import type { ContentTextStylePropertyKey } from '../constants';
20
- import { backgroundColorFromStyle, isTransparentColour } from './color.util';
20
+ import {
21
+ backgroundColorFromStyle,
22
+ isTransparentColour,
23
+ isHexTransparent,
24
+ } from './color.util';
21
25
  import {
22
26
  resolveColorReference,
23
27
  bestContrastBrandRole,
@@ -253,10 +257,13 @@ const OWN_BACKGROUND_FRAME_TYPES: ReadonlySet<Content['type']> = new Set([
253
257
  * than silently recolouring published sites to the linked global — the exact trap ADR 0014
254
258
  * names, here applied to the migration bake (ADR 0015).
255
259
  *
256
- * Two frame families, mirroring the renderer:
257
- * - **text / profile** — framed when the content has its own SOLID background (gate mirrors
258
- * the view-model's container gate: a `background`/`backgroundColor` present and not an
259
- * alpha-`00` hex).
260
+ * Two frame families:
261
+ * - **text / profile** — framed when the content has its own painted background. The gate
262
+ * deliberately mirrors the PRE-V2 renderer (`!isHexTransparent`), NOT the current
263
+ * view-model gate (`!isTransparentColour`): the legacy renderer framed the literal
264
+ * `'transparent'` (the maker's default background) too — only an alpha-`00` hex was
265
+ * unframed — so its empty-mark RTE inherited the button colour. This bake must reproduce
266
+ * that, or every default text/profile section silently recolours to the root paint.
260
267
  * - **the container-bound blocks** (mailing-list, request-payment, support-me, non-brandSite
261
268
  * contact-form) — always framed; `containerSurfaceForContent` is the existing oracle for
262
269
  * "does this block paint a frame" (and encodes the brandSite contact-form carve-out), and
@@ -286,7 +293,9 @@ export const rteFrameFallbackColour = (
286
293
  ).backgroundColor;
287
294
  const reference = background?.background || background?.backgroundColor;
288
295
 
289
- return reference && !isTransparentColour(reference)
296
+ // `isHexTransparent`, not `isTransparentColour`: the pre-V2 renderer framed the literal
297
+ // `'transparent'` (only an alpha-00 hex was unframed), so the bake must too — see docblock.
298
+ return reference && !isHexTransparent(reference)
290
299
  ? buttonTextColour
291
300
  : undefined;
292
301
  }
@@ -2206,6 +2206,107 @@ describe('migration21 — step FINAL+1: bake resolved colour into inherit-only R
2206
2206
  );
2207
2207
  });
2208
2208
 
2209
+ // The maker's DEFAULT text/profile background is the literal `'transparent'`. The pre-V2
2210
+ // renderer framed it (its gate was `!isHexTransparent`, which excludes only an alpha-00
2211
+ // hex), so the empty-mark RTE inherited the button colour — not the root paint. The bake
2212
+ // must reproduce that or every default text section silently recolours (BP-5886 regression).
2213
+ test('brandPage text section with a LITERAL `transparent` background: bakes to the frame button-text colour, not the paragraph global', async () => {
2214
+ const model = {
2215
+ styles: bakeStyles(),
2216
+ brandPageType: 'default',
2217
+ pages: [
2218
+ {
2219
+ contents: [
2220
+ {
2221
+ type: 'text',
2222
+ backgroundColor: { backgroundColor: 'transparent' },
2223
+ text: rteDoc(''),
2224
+ textStyle: { textStyleType: 'paragraphs' },
2225
+ },
2226
+ ],
2227
+ },
2228
+ ],
2229
+ };
2230
+
2231
+ const { brandPageModel } = await migration21(
2232
+ createMigrationData(model),
2233
+ OPTIONS_NO_LOGO,
2234
+ );
2235
+
2236
+ const content = brandPageModel.pages[0].contents[0] as any;
2237
+
2238
+ expect(rteMarks(content.text)[0].attrs.color).toBe(
2239
+ brandPageModel.styles.buttonStyles.style.color,
2240
+ );
2241
+ expect(rteMarks(content.text)[0].attrs.color).not.toBe(
2242
+ paragraphColour(brandPageModel),
2243
+ );
2244
+ });
2245
+
2246
+ // The exact user-reported shape: an UNLINKED text style (no `textStyleType`) whose mark is
2247
+ // `color: ''`, on the default `transparent` section. Without the frame it drops to the root
2248
+ // paint; with it, the empty mark bakes to the button colour it showed pre-V2.
2249
+ test('brandContact text section, UNLINKED style + empty `transparent` background: empty mark bakes to the button colour', async () => {
2250
+ const model = {
2251
+ styles: bakeStyles(),
2252
+ brandPageType: 'brandContact',
2253
+ pages: [
2254
+ {
2255
+ contents: [
2256
+ {
2257
+ type: 'text',
2258
+ backgroundColor: { backgroundColor: 'transparent' },
2259
+ text: rteDoc(''),
2260
+ textStyle: { fontFamily: 'Lato' }, // no textStyleType, no color
2261
+ },
2262
+ ],
2263
+ },
2264
+ ],
2265
+ };
2266
+
2267
+ const { brandPageModel } = await migration21(
2268
+ createMigrationData(model),
2269
+ OPTIONS_NO_LOGO,
2270
+ );
2271
+
2272
+ const content = brandPageModel.pages[0].contents[0] as any;
2273
+
2274
+ expect(rteMarks(content.text)[0].attrs.color).toBe(
2275
+ brandPageModel.styles.buttonStyles.style.color,
2276
+ );
2277
+ });
2278
+
2279
+ test('profile with a LITERAL `transparent` background: uncoloured title bakes to the frame button-text colour', async () => {
2280
+ const model = {
2281
+ styles: bakeStyles(),
2282
+ brandPageType: 'default',
2283
+ pages: [
2284
+ {
2285
+ contents: [
2286
+ {
2287
+ type: 'profile',
2288
+ backgroundColor: { backgroundColor: 'transparent' },
2289
+ title: rteDoc(''),
2290
+ titleStyle: { textStyleType: 'headings' },
2291
+ },
2292
+ ],
2293
+ },
2294
+ ],
2295
+ };
2296
+
2297
+ const { brandPageModel } = await migration21(
2298
+ createMigrationData(model),
2299
+ OPTIONS_NO_LOGO,
2300
+ );
2301
+
2302
+ const content = brandPageModel.pages[0].contents[0] as any;
2303
+
2304
+ expect(titleMarkColour(content)).toBe(
2305
+ brandPageModel.styles.buttonStyles.style.color,
2306
+ );
2307
+ expect(titleMarkColour(content)).not.toBe(headingColour(brandPageModel));
2308
+ });
2309
+
2209
2310
  test('profile on a SOLID background: uncoloured title/description bake to the frame button-text colour', async () => {
2210
2311
  const model = {
2211
2312
  styles: bakeStyles(),
@@ -2594,3 +2695,71 @@ describe('migration21 — step FINAL+1: bake resolved colour into inherit-only R
2594
2695
  );
2595
2696
  });
2596
2697
  });
2698
+
2699
+ describe('migration21 — icon fills (social-links + footer)', () => {
2700
+ test('social-links: an unset iconFillStyles.fill is baked from the referencified root text colour', async () => {
2701
+ const model = {
2702
+ styles: { textStyles: { style: { color: '#123456' } }, colors: [] },
2703
+ pages: [{ contents: [{ type: 'social-links', iconFillStyles: {} }] }],
2704
+ };
2705
+
2706
+ const { brandPageModel } = await migration21(
2707
+ createMigrationData(model),
2708
+ OPTIONS,
2709
+ );
2710
+
2711
+ const rootRef = refFor(brandPageModel, '#123456');
2712
+
2713
+ expect(rootRef).toMatch(/^var\(--palette-/);
2714
+ expect(
2715
+ (brandPageModel.pages[0].contents[0] as any).iconFillStyles.fill,
2716
+ ).toBe(rootRef);
2717
+ });
2718
+
2719
+ test('social-links: an explicit iconFillStyles.fill is preserved, not overwritten', async () => {
2720
+ const model = {
2721
+ styles: { textStyles: { style: { color: '#123456' } }, colors: [] },
2722
+ pages: [
2723
+ {
2724
+ contents: [
2725
+ { type: 'social-links', iconFillStyles: { fill: '#abcdef' } },
2726
+ ],
2727
+ },
2728
+ ],
2729
+ };
2730
+
2731
+ const { brandPageModel } = await migration21(
2732
+ createMigrationData(model),
2733
+ OPTIONS,
2734
+ );
2735
+
2736
+ // Its own colour rode pass B into a reference; the root ref did not replace it.
2737
+ expect(
2738
+ (brandPageModel.pages[0].contents[0] as any).iconFillStyles.fill,
2739
+ ).toBe(refFor(brandPageModel, '#abcdef'));
2740
+ });
2741
+
2742
+ test('footer: an unset iconStyle.fill is baked to a literal black; an explicit one is kept', async () => {
2743
+ const model = {
2744
+ styles: { textStyles: { style: { color: '#123456' } }, colors: [] },
2745
+ pages: [],
2746
+ pageFooterDesktop: { iconStyle: {} },
2747
+ pageFooterMobile: { iconStyle: { fill: '#654321' } },
2748
+ };
2749
+
2750
+ const { brandPageModel } = await migration21(
2751
+ createMigrationData(model),
2752
+ OPTIONS,
2753
+ );
2754
+
2755
+ // Literal, not a palette ref: painted after pass B, so it mints no custom.
2756
+ expect(brandPageModel.pageFooterDesktop.iconStyle.fill).toBe('#000000');
2757
+ expect(
2758
+ brandPageModel.styles.colors.some((c: any) => c.value === '#000000'),
2759
+ ).toBe(false);
2760
+ // The explicit footer colour was referencified, not blacked out.
2761
+ expect(brandPageModel.pageFooterMobile.iconStyle.fill).toBe(
2762
+ refFor(brandPageModel, '#654321'),
2763
+ );
2764
+ });
2765
+ });
@@ -491,6 +491,75 @@ export const moveRootTextColourIntoGlobalStyles = (
491
491
  delete (textStyles as unknown as Record<string, unknown>)['style'];
492
492
  };
493
493
 
494
+ /* -------------------------------------------------------------------------- *
495
+ * Step FINAL−: bake the social-icon fills the palette walk leaves unset
496
+ * -------------------------------------------------------------------------- */
497
+
498
+ /**
499
+ * The footer social icons carry no `fill` in their SVGs, so an unset footer icon painted
500
+ * the SVG default (black). Baked as a LITERAL, not a palette ref — a fixed fallback for
501
+ * that previously-implicit default, so it does not mint a palette custom or follow palette
502
+ * edits. Painted after pass B, so referencify never sees it.
503
+ */
504
+ const FOOTER_ICON_FALLBACK_COLOUR = '#000000';
505
+
506
+ /**
507
+ * Bake the two social-icon fills that neither the palette walk nor a resolver sets, so
508
+ * each persists an explicit colour instead of a render-time seed that changed under V2:
509
+ *
510
+ * - **social-links content** (`iconFillStyles.fill`) → the site's ROOT text colour, the
511
+ * colour the icon followed before the palette split (`styles.textStyles.style.color`).
512
+ * Read here — already referencified by pass B — BEFORE
513
+ * `moveRootTextColourIntoGlobalStyles` retires it, which is why this runs at Step FINAL−.
514
+ * - **footer** (`pageFooter{Desktop,Mobile}.iconStyle.fill`) → a literal black
515
+ * (`FOOTER_ICON_FALLBACK_COLOUR`).
516
+ *
517
+ * Blank-only: an explicit persisted colour is never touched (`Object`-spread with the fill
518
+ * appended only when absent). A re-run finds every fill set, so it is a no-op.
519
+ */
520
+ const bakeIconColours = (brandPageModel: BrandPageModel): void => {
521
+ const rootTextColour = legacyRootTextColour(brandPageModel);
522
+
523
+ if (!isBlankColorValue(rootTextColour)) {
524
+ for (const page of brandPageModel.pages ?? []) {
525
+ for (const content of page.contents ?? []) {
526
+ if (content.type !== 'social-links') {
527
+ continue;
528
+ }
529
+
530
+ const fields = content as unknown as Record<string, unknown>;
531
+ const iconFillStyles = (fields.iconFillStyles ?? {}) as Record<
532
+ string,
533
+ unknown
534
+ >;
535
+
536
+ if (isBlankColorValue(iconFillStyles.fill as string | undefined)) {
537
+ fields.iconFillStyles = { ...iconFillStyles, fill: rootTextColour };
538
+ }
539
+ }
540
+ }
541
+ }
542
+
543
+ for (const footer of [
544
+ brandPageModel.pageFooterDesktop,
545
+ brandPageModel.pageFooterMobile,
546
+ ]) {
547
+ if (!footer) {
548
+ continue;
549
+ }
550
+
551
+ const footerRecord = footer as unknown as Record<string, unknown>;
552
+ const iconStyle = (footerRecord.iconStyle ?? {}) as Record<string, unknown>;
553
+
554
+ if (isBlankColorValue(iconStyle.fill as string | undefined)) {
555
+ footerRecord.iconStyle = {
556
+ ...iconStyle,
557
+ fill: FOOTER_ICON_FALLBACK_COLOUR,
558
+ };
559
+ }
560
+ }
561
+ };
562
+
494
563
  /* -------------------------------------------------------------------------- *
495
564
  * Step 0.1: retire styles.logoStyles.backgroundStyle onto the logo content
496
565
  * -------------------------------------------------------------------------- */
@@ -974,6 +1043,12 @@ export const migration21 = async (
974
1043
 
975
1044
  brandPageModel.styles.colors = builder.toColorItems();
976
1045
 
1046
+ // ---- Step FINAL−: bake icon fills before the root colour is retired ----
1047
+ // Social-links icons take the now-referencified root text colour they followed before
1048
+ // the palette split; footer icons take a literal black fallback. Reads the root ref, so
1049
+ // it MUST precede its retirement below.
1050
+ bakeIconColours(brandPageModel);
1051
+
977
1052
  // ---- Step FINAL: retire the root text colour (order matters — see docblock) ----
978
1053
  moveRootTextColourIntoGlobalStyles(brandPageModel);
979
1054