@styleflow.app/flowpress-astro-core 0.2.0-beta.0 → 0.2.0-beta.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,64 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 0.2.0-beta.11
6
+
7
+ - Rendered empty Gutenberg and FlowPress text blocks with an internal
8
+ ` `, preserving their typographic line height without visible
9
+ characters or synthetic break elements.
10
+
11
+ ## 0.2.0-beta.10
12
+
13
+ - Preserved intentionally empty core Gutenberg paragraphs as visible line
14
+ breaks, including paragraphs resolved through FlowPress templates.
15
+ - Exported safe recursive collection of CMS-authored block class names for
16
+ Tailwind `@source inline()` generation.
17
+
18
+ ## 0.2.0-beta.9
19
+
20
+ - Preserved repeated Gutenberg RichText spacing across `<br>` sequences,
21
+ legacy empty paragraphs and literal double/triple newlines.
22
+
23
+ ## 0.2.0-beta.8
24
+
25
+ - Applied Gutenberg `anchor` and `className` attributes to each rendered
26
+ block root, merging custom classes with the required FlowPress renderer
27
+ classes and preserving transparent template blocks when neither is set.
28
+
29
+ ## 0.2.0-beta.5
30
+
31
+ - Split Gutenberg rendering into dedicated hero, responsive layout, text,
32
+ list/template and CF7 modules.
33
+ - Added semantic foreground/modifier propagation and core text block
34
+ rendering.
35
+ - Added responsive 12-column inheritance, token-based partial containers,
36
+ hero media/scrim rendering and reduced-motion handling.
37
+ - Added repeated query-item template rendering with the previous plain-list
38
+ fallback.
39
+ - Added an accessible Turnstile/CF7 browser form compatible with static and
40
+ server builds.
41
+ - Added Astro component checking to the release gate.
42
+
43
+ ## 0.2.0-beta.4
44
+
45
+ - Updated the exact StyleFlow Astro dependency to `0.3.0-beta.2`.
46
+ - Validated the npm Trusted Publisher path with a new OIDC-only publication.
47
+
48
+ ## 0.2.0-beta.3
49
+
50
+ - Updated the exact StyleFlow Astro dependency to `0.3.0-beta.1`.
51
+ - Made the optional PHP parity test compatible with standalone CI checkouts;
52
+ the TypeScript hierarchy tests remain mandatory.
53
+
54
+ ## 0.2.0-beta.2
55
+
56
+ - Made Gutenberg color axes inherit by omission until authors explicitly set
57
+ a valid tone or intensity.
58
+ - Applied explicit color axes to heading, button, and quote renderers.
59
+ - Embedded source contents in generated sourcemaps for clean Astro/Vite
60
+ diagnostics when the package is installed from npm.
61
+
3
62
  ## 0.2.0-beta.0
4
63
 
5
64
  - Made the repository buildable outside the original monorepo.
package/README.md CHANGED
@@ -9,7 +9,7 @@ The package must not import from an application or project plugin.
9
9
  ## Install
10
10
 
11
11
  ```bash
12
- pnpm add @styleflow.app/flowpress-astro-core@0.2.0-beta.0
12
+ pnpm add @styleflow.app/flowpress-astro-core@0.2.0-beta.11
13
13
  ```
14
14
 
15
15
  Prereleases are pinned exactly. The package expects Astro 7 or newer and the
@@ -30,6 +30,20 @@ For local Cloudflare development, prefer an HTTP loopback endpoint dedicated
30
30
  to server-to-server traffic. Keep the public HTTPS backend URL separate for
31
31
  canonical URL rewriting.
32
32
 
33
+ `GutenbergRenderer.astro` renders the responsive hero/grid/column contract,
34
+ StyleFlow text axes and resolved content/query templates. CF7 schemas
35
+ serialized by WordPress become accessible client forms that submit directly
36
+ to the protected FlowPress REST endpoint, so the same component works in
37
+ static and server output modes. Gutenberg `anchor` and `className` attributes
38
+ are applied to the rendered block root as HTML `id` and `class`. Rich Text
39
+ preserves intentional double, triple and longer line breaks, including content
40
+ saved by older editor versions as empty paragraphs. Empty textual blocks render
41
+ an internal `&nbsp;`, preserving their typographic line height without adding
42
+ visible characters. The exported
43
+ `collectFlowpressClassNames()` helper recursively extracts safe class tokens
44
+ from rendered CMS blocks so consumers can generate Tailwind
45
+ `@source inline()` directives before compiling their frontend CSS.
46
+
33
47
  ## Release
34
48
 
35
49
  ```bash
@@ -45,3 +59,10 @@ release and in CI.
45
59
  The first publish requires npm organization access and 2FA. Subsequent
46
60
  publishes use `.github/workflows/publish.yml` with npm trusted publishing
47
61
  (OIDC); no long-lived publish token belongs in this repository.
62
+
63
+ The TypeScript template-hierarchy suite is self-contained. Its additional PHP
64
+ parity test runs when the sibling `flowpress-wordpress-core` checkout is
65
+ available locally and is skipped in a standalone package checkout.
66
+
67
+ Release activity reports are indexed in
68
+ [`docs/activities/README.md`](docs/activities/README.md).
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  import type { SfSeo } from "../types.js";
3
+ import S from "@styleflow.app/astro/S.astro";
3
4
 
4
5
  interface Props {
5
6
  lang?: string;
@@ -44,6 +45,8 @@ const jsonLd = seo?.jsonLd ? JSON.stringify(seo.jsonLd) : null;
44
45
  <slot name="head" />
45
46
  </head>
46
47
  <body>
47
- <slot />
48
+ <S.main tone="neutral" intensity={1} surface="default" layoutRole="none" density="md">
49
+ <slot />
50
+ </S.main>
48
51
  </body>
49
52
  </html>
@@ -7,28 +7,38 @@ import type { FlowpressTemplateProps } from "../types.js";
7
7
  const { route, content, styleflowContract } = Astro.props as FlowpressTemplateProps;
8
8
  const title = content?.title || route.seo?.title || "Contenuto";
9
9
  const isNotFound = route.queryType === "404" || route.status === 404;
10
+ const hasGutembergBlocks = Array.isArray(content?.contentBlocks) && content.contentBlocks.length > 0 ? true : false;
11
+ //TODO SET GLOBAL LOCAL ON ENV
10
12
  ---
11
13
 
12
14
  <BaseLayout lang={route.locale || "it"} seo={route.seo}>
13
- <S.main tone="neutral" intensity={1} surface="default" layoutRole="container" density="md">
14
- <S.section tone="neutral" intensity={1} surface="default" layoutRole="section" density="md">
15
- <S.h1 ty="heading" v="1" w="strong">{isNotFound ? "Pagina non trovata" : title}</S.h1>
16
- {content?.excerpt && <S.p ty="body" v="md" w="default">{content.excerpt}</S.p>}
17
- {Array.isArray(content?.contentBlocks) && content.contentBlocks.length > 0 ? (
18
- <GutenbergRenderer
19
- blocks={content.contentBlocks as import("../types.js").FlowpressBlock[]}
20
- contract={styleflowContract}
21
- />
22
- ) : content?.content && (
23
- <S.div
24
- tone="neutral"
25
- intensity={1}
26
- surface="default"
27
- layoutRole="stack"
28
- density="md"
29
- set:html={String(content.content)}
30
- />
31
- )}
32
- </S.section>
33
- </S.main>
15
+
16
+ {hasGutembergBlocks ? (
17
+ <GutenbergRenderer
18
+ blocks={content?.contentBlocks as import("../types.js").FlowpressBlock[]}
19
+ contract={styleflowContract}
20
+ />
21
+ ) : (
22
+ <S.section tone="neutral" intensity={1} surface="default" layoutRole="section" density="md">
23
+ <S.h1 ty="heading" v="1" w="strong">{isNotFound ? "Pagina non trovata" : title}</S.h1>
24
+ {content?.excerpt && <S.p ty="body" v="md" w="default">{content.excerpt}</S.p>}
25
+ {Array.isArray(content?.contentBlocks) && content.contentBlocks.length > 0 ? (
26
+ <GutenbergRenderer
27
+ blocks={content.contentBlocks as import("../types.js").FlowpressBlock[]}
28
+ contract={styleflowContract}
29
+ />
30
+ ) : content?.content && (
31
+ <S.div
32
+ tone="neutral"
33
+ intensity={1}
34
+ surface="default"
35
+ layoutRole="stack"
36
+ density="md"
37
+ set:html={String(content.content)}
38
+ />
39
+ )}
40
+ </S.section>
41
+ )}
42
+
43
+
34
44
  </BaseLayout>
@@ -1,10 +1,20 @@
1
1
  ---
2
2
  import S from "@styleflow.app/astro/S.astro";
3
- import {
4
- resolveDynamicStyleflowProps,
5
- type StyleflowContract
6
- } from "../styleflow.js";
3
+ import type { StyleflowContract } from "../styleflow.js";
7
4
  import type { FlowpressBlock } from "../types.js";
5
+ import ColumnBlock from "./blocks/ColumnBlock.astro";
6
+ import FormBlock from "./blocks/FormBlock.astro";
7
+ import GridBlock from "./blocks/GridBlock.astro";
8
+ import HeroBlock from "./blocks/HeroBlock.astro";
9
+ import ListItemFallback from "./blocks/ListItemFallback.astro";
10
+ import TextBlock from "./blocks/TextBlock.astro";
11
+ import {
12
+ blockHtmlAttributes,
13
+ linkValue,
14
+ objectValue,
15
+ semanticProps,
16
+ textValue
17
+ } from "./blocks/helpers.js";
8
18
 
9
19
  interface Props {
10
20
  blocks: FlowpressBlock[];
@@ -13,59 +23,6 @@ interface Props {
13
23
 
14
24
  const { blocks, contract } = Astro.props;
15
25
 
16
- const defaults = {
17
- tone: "neutral",
18
- intensity: 1,
19
- surface: "default",
20
- layoutRole: "stack",
21
- density: "md"
22
- };
23
-
24
- function styleflowProps(attributes: Record<string, unknown>) {
25
- if (!contract) return defaults;
26
- return resolveDynamicStyleflowProps(attributes, defaults, contract);
27
- }
28
-
29
- function dynamicProps(
30
- attributes: Record<string, unknown>,
31
- fallback: Record<string, unknown>
32
- ) {
33
- if (!contract) return fallback;
34
- return resolveDynamicStyleflowProps(attributes, fallback, contract);
35
- }
36
-
37
- function text(block: FlowpressBlock, key = "text"): string {
38
- return String(block.attributes[key] ?? "");
39
- }
40
-
41
- function objectValue(
42
- block: FlowpressBlock,
43
- key: string
44
- ): Record<string, unknown> {
45
- const value = block.attributes[key];
46
- return value && typeof value === "object" && !Array.isArray(value)
47
- ? value as Record<string, unknown>
48
- : {};
49
- }
50
-
51
- function linkValue(block: FlowpressBlock, key = "link"): string {
52
- const value = block.attributes[key];
53
- const href = typeof value === "string"
54
- ? value
55
- : String(objectValue(block, key).url ?? "");
56
- if (/^(?:\/(?!\/)|#|https?:\/\/|mailto:|tel:)/i.test(href)) {
57
- return href;
58
- }
59
- return "";
60
- }
61
-
62
- function itemImage(item: Record<string, unknown>): Record<string, unknown> {
63
- const value = item.image;
64
- return value && typeof value === "object" && !Array.isArray(value)
65
- ? value as Record<string, unknown>
66
- : {};
67
- }
68
-
69
26
  function youtubeEmbedUrl(value: unknown): string | null {
70
27
  try {
71
28
  const url = new URL(String(value ?? ""));
@@ -87,50 +44,64 @@ function youtubeEmbedUrl(value: unknown): string | null {
87
44
  {blocks.map((block) => {
88
45
  const name = block.name.replace(/^sf\//, "");
89
46
  const children = block.innerBlocks ?? [];
90
- const semantic = styleflowProps(block.attributes);
47
+ const semantic = semanticProps(block.attributes, contract);
48
+ const htmlAttributes = blockHtmlAttributes(block.attributes);
91
49
 
92
- if (name === "heading") {
93
- const level = Math.min(6, Math.max(1, Number(block.attributes.level ?? 2)));
94
- const headingTag = `h${level}` as "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
95
- const typography = dynamicProps(
96
- {
97
- ty: "heading",
98
- v: String(block.attributes.variant ?? level),
99
- w: String(block.attributes.weight ?? "strong")
100
- },
101
- { ty: "heading", v: String(level), w: "strong" }
50
+ if (name === "hero") {
51
+ return (
52
+ <HeroBlock block={block} contract={contract}>
53
+ <Astro.self blocks={children} contract={contract} />
54
+ </HeroBlock>
102
55
  );
56
+ }
57
+
58
+ if (name === "grid") {
103
59
  return (
104
- <S
105
- as={headingTag}
106
- {...typography}
107
- set:html={text(block)}
108
- />
60
+ <GridBlock block={block} contract={contract}>
61
+ <Astro.self blocks={children} contract={contract} />
62
+ </GridBlock>
109
63
  );
110
64
  }
111
65
 
112
- if (name === "rich-text" || name === "core/paragraph") {
113
- const html = String(block.attributes.content ?? block.innerHtml ?? "");
114
- return <S as="div" {...semantic} set:html={html} />;
66
+ if (name === "column") {
67
+ return (
68
+ <ColumnBlock block={block} contract={contract}>
69
+ <Astro.self blocks={children} contract={contract} />
70
+ </ColumnBlock>
71
+ );
72
+ }
73
+
74
+ if ([
75
+ "heading",
76
+ "rich-text",
77
+ "quote",
78
+ "core/paragraph",
79
+ "core/heading",
80
+ "core/list",
81
+ "core/list-item",
82
+ "core/quote"
83
+ ].includes(name)) {
84
+ return (
85
+ <TextBlock block={block} contract={contract} hasChildren={children.length > 0}>
86
+ <Astro.self blocks={children} contract={contract} />
87
+ </TextBlock>
88
+ );
115
89
  }
116
90
 
117
91
  if (name === "button") {
118
92
  const href = linkValue(block, "href") || "#";
119
- const interaction = dynamicProps(
120
- {
121
- interactivePriority: String(
122
- block.attributes.interactivePriority ?? "primary"
123
- )
124
- },
125
- { interactivePriority: "primary" }
126
- );
93
+ const link = objectValue(block, "href");
94
+ const opensInNewTab = Boolean(link.opensInNewTab);
127
95
  return (
128
96
  <S
129
97
  as="a"
130
98
  href={href}
131
- {...interaction}
99
+ target={opensInNewTab ? "_blank" : undefined}
100
+ rel={opensInNewTab ? "noopener noreferrer" : undefined}
101
+ {...semantic}
102
+ {...htmlAttributes}
132
103
  >
133
- {text(block, "label")}
104
+ {textValue(block, "label")}
134
105
  </S>
135
106
  );
136
107
  }
@@ -144,7 +115,7 @@ function youtubeEmbedUrl(value: unknown): string | null {
144
115
  } | undefined;
145
116
  const src = image?.url ?? image?.sourceUrl;
146
117
  return src ? (
147
- <S as="div" {...semantic}>
118
+ <S as="div" {...semantic} {...htmlAttributes}>
148
119
  <img
149
120
  src={src}
150
121
  alt={String(block.attributes.alt ?? image?.alt ?? image?.altText ?? "")}
@@ -160,7 +131,7 @@ function youtubeEmbedUrl(value: unknown): string | null {
160
131
  ? block.attributes.images as Array<Record<string, unknown>>
161
132
  : [];
162
133
  return (
163
- <S as="div" {...semantic}>
134
+ <S as="div" {...semantic} {...htmlAttributes}>
164
135
  {images.map((image) => image.url ? (
165
136
  <img
166
137
  src={String(image.url)}
@@ -177,7 +148,7 @@ function youtubeEmbedUrl(value: unknown): string | null {
177
148
  const video = objectValue(block, "video");
178
149
  const poster = objectValue(block, "poster");
179
150
  return video.url ? (
180
- <S as="div" {...semantic}>
151
+ <S as="div" {...semantic} {...htmlAttributes}>
181
152
  <video
182
153
  src={String(video.url)}
183
154
  poster={poster.url ? String(poster.url) : undefined}
@@ -193,7 +164,7 @@ function youtubeEmbedUrl(value: unknown): string | null {
193
164
  if (name === "video-youtube") {
194
165
  const embed = youtubeEmbedUrl(block.attributes.url);
195
166
  return embed ? (
196
- <S as="div" {...semantic}>
167
+ <S as="div" {...semantic} {...htmlAttributes}>
197
168
  <iframe
198
169
  src={embed}
199
170
  title={String(block.attributes.title ?? "YouTube video")}
@@ -205,24 +176,19 @@ function youtubeEmbedUrl(value: unknown): string | null {
205
176
  ) : null;
206
177
  }
207
178
 
208
- if (name === "quote") {
179
+ if (name === "chip") {
209
180
  return (
210
- <blockquote>
211
- <S as="p" set:html={text(block, "quote")} />
212
- {block.attributes.citation && <cite>{String(block.attributes.citation)}</cite>}
213
- </blockquote>
181
+ <S as="span" {...semantic} {...htmlAttributes}>
182
+ {textValue(block, "label")}
183
+ </S>
214
184
  );
215
185
  }
216
186
 
217
- if (name === "chip") {
218
- return <S as="span" {...semantic}>{text(block, "label")}</S>;
219
- }
220
-
221
187
  if (name === "card") {
222
188
  const media = objectValue(block, "image");
223
189
  const href = linkValue(block);
224
190
  return (
225
- <S as="article" {...semantic}>
191
+ <S as="article" {...semantic} {...htmlAttributes}>
226
192
  {media.url && (
227
193
  <img src={String(media.url)} alt={String(media.alt ?? "")} loading="lazy" />
228
194
  )}
@@ -237,34 +203,41 @@ function youtubeEmbedUrl(value: unknown): string | null {
237
203
  const items = Array.isArray(block.attributes.resolvedItems)
238
204
  ? block.attributes.resolvedItems as Array<Record<string, unknown>>
239
205
  : [];
206
+ const resolvedBlocks = Array.isArray(block.attributes.resolvedBlocks)
207
+ ? block.attributes.resolvedBlocks as FlowpressBlock[][]
208
+ : [];
240
209
  return (
241
- <S as="ul" {...semantic}>
242
- {items.map((item) => (
210
+ <S as="ul" {...semantic} {...htmlAttributes}>
211
+ {items.map((item, index) => (
243
212
  <S as="li">
244
- {itemImage(item).url && (
245
- <img
246
- src={String(itemImage(item).url)}
247
- alt={String(itemImage(item).alt ?? "")}
248
- loading="lazy"
249
- decoding="async"
250
- />
251
- )}
252
- {item.uri ? (
253
- <S as="a" href={String(item.uri)}>{String(item.title ?? "")}</S>
213
+ {resolvedBlocks[index]?.length ? (
214
+ <Astro.self blocks={resolvedBlocks[index]} contract={contract} />
254
215
  ) : (
255
- <S as="span">{String(item.title ?? "")}</S>
216
+ <ListItemFallback item={item} />
256
217
  )}
257
- {item.excerpt && <S as="p">{String(item.excerpt)}</S>}
258
- {item.priceHtml && <S as="div" set:html={String(item.priceHtml)} />}
259
218
  </S>
260
219
  ))}
261
220
  </S>
262
221
  );
263
222
  }
264
223
 
224
+ if (name === "template") {
225
+ return Object.keys(htmlAttributes).length > 0 ? (
226
+ <div {...htmlAttributes}>
227
+ <Astro.self blocks={children} contract={contract} />
228
+ </div>
229
+ ) : (
230
+ <Astro.self blocks={children} contract={contract} />
231
+ );
232
+ }
233
+
234
+ if (name === "form") {
235
+ return <FormBlock block={block} contract={contract} />;
236
+ }
237
+
265
238
  if (name === "accordion-item") {
266
239
  return (
267
- <S as="div" {...semantic}>
240
+ <S as="div" {...semantic} {...htmlAttributes}>
268
241
  <details>
269
242
  <summary>{String(block.attributes.title ?? "")}</summary>
270
243
  <Astro.self blocks={children} contract={contract} />
@@ -290,7 +263,7 @@ function youtubeEmbedUrl(value: unknown): string | null {
290
263
  + `?key=${encodeURIComponent(googleMapsApiKey)}`
291
264
  + `&q=${encodeURIComponent(query)}`;
292
265
  return (
293
- <S as="div" {...semantic}>
266
+ <S as="div" {...semantic} {...htmlAttributes}>
294
267
  <iframe
295
268
  src={source}
296
269
  title={address || "Map"}
@@ -301,7 +274,7 @@ function youtubeEmbedUrl(value: unknown): string | null {
301
274
  );
302
275
  }
303
276
  return query ? (
304
- <S as="div" {...semantic}>
277
+ <S as="div" {...semantic} {...htmlAttributes}>
305
278
  <S
306
279
  as="a"
307
280
  href={`https://www.openstreetmap.org/search?query=${encodeURIComponent(query)}`}
@@ -313,9 +286,9 @@ function youtubeEmbedUrl(value: unknown): string | null {
313
286
  ) : null;
314
287
  }
315
288
 
316
- const layoutTag = ["section", "hero"].includes(name) ? "section" : "div";
289
+ const layoutTag = name === "section" ? "section" : "div";
317
290
  return (
318
- <S as={layoutTag} {...semantic}>
291
+ <S as={layoutTag} {...semantic} {...htmlAttributes}>
319
292
  {block.innerHtml && children.length === 0
320
293
  ? <S as="div" set:html={block.innerHtml} />
321
294
  : <Astro.self blocks={children} contract={contract} />}
@@ -0,0 +1,83 @@
1
+ ---
2
+ import S from "@styleflow.app/astro/S.astro";
3
+ import type { FlowpressBlock } from "../../types.js";
4
+ import type { StyleflowContract } from "../../styleflow.js";
5
+ import {
6
+ blockHtmlAttributes,
7
+ responsiveValues,
8
+ semanticProps
9
+ } from "./helpers.js";
10
+
11
+ interface Props {
12
+ block: FlowpressBlock;
13
+ contract?: StyleflowContract;
14
+ }
15
+
16
+ const { block, contract } = Astro.props;
17
+ const values = responsiveValues(block.attributes, "span", 1);
18
+ const partial = ["start", "end"].includes(String(block.attributes.partialContainer))
19
+ ? String(block.attributes.partialContainer)
20
+ : "none";
21
+ const partialBreakpoint = ["base", "sm", "md", "lg", "xl", "2xl"].includes(
22
+ String(block.attributes.partialContainerBreakpoint)
23
+ ) ? String(block.attributes.partialContainerBreakpoint) : "lg";
24
+ const semantic = semanticProps(block.attributes, contract);
25
+ const htmlAttributes = blockHtmlAttributes(block.attributes, "flowpress-column");
26
+ const style = [
27
+ `--flowpress-span-base:${values.base}`,
28
+ `--flowpress-span-sm:${values.sm}`,
29
+ `--flowpress-span-md:${values.md}`,
30
+ `--flowpress-span-lg:${values.lg}`,
31
+ `--flowpress-span-xl:${values.xl}`,
32
+ `--flowpress-span-2xl:${values["2xl"]}`
33
+ ].join(";");
34
+ ---
35
+
36
+ <S
37
+ as="div"
38
+ {...semantic}
39
+ {...htmlAttributes}
40
+ data-partial={partial}
41
+ data-partial-breakpoint={partialBreakpoint}
42
+ style={style}
43
+ >
44
+ <slot />
45
+ </S>
46
+
47
+ <style>
48
+ .flowpress-column {
49
+ grid-column: span var(--flowpress-span-base) / span var(--flowpress-span-base);
50
+ min-width: 0;
51
+ }
52
+ .flowpress-column[data-partial="start"][data-partial-breakpoint="base"] {
53
+ padding-inline-start: max(var(--sf-padding-inline, 0px), calc((100vw - var(--sf-container-max-width, 100vw)) / 2));
54
+ }
55
+ .flowpress-column[data-partial="end"][data-partial-breakpoint="base"] {
56
+ padding-inline-end: max(var(--sf-padding-inline, 0px), calc((100vw - var(--sf-container-max-width, 100vw)) / 2));
57
+ }
58
+ @media (min-width: 40rem) {
59
+ .flowpress-column { grid-column: span var(--flowpress-span-sm) / span var(--flowpress-span-sm); }
60
+ .flowpress-column[data-partial="start"][data-partial-breakpoint="sm"] { padding-inline-start: max(var(--sf-padding-inline, 0px), calc((100vw - var(--sf-container-max-width, 100vw)) / 2)); }
61
+ .flowpress-column[data-partial="end"][data-partial-breakpoint="sm"] { padding-inline-end: max(var(--sf-padding-inline, 0px), calc((100vw - var(--sf-container-max-width, 100vw)) / 2)); }
62
+ }
63
+ @media (min-width: 48rem) {
64
+ .flowpress-column { grid-column: span var(--flowpress-span-md) / span var(--flowpress-span-md); }
65
+ .flowpress-column[data-partial="start"][data-partial-breakpoint="md"] { padding-inline-start: max(var(--sf-padding-inline, 0px), calc((100vw - var(--sf-container-max-width, 100vw)) / 2)); }
66
+ .flowpress-column[data-partial="end"][data-partial-breakpoint="md"] { padding-inline-end: max(var(--sf-padding-inline, 0px), calc((100vw - var(--sf-container-max-width, 100vw)) / 2)); }
67
+ }
68
+ @media (min-width: 64rem) {
69
+ .flowpress-column { grid-column: span var(--flowpress-span-lg) / span var(--flowpress-span-lg); }
70
+ .flowpress-column[data-partial="start"][data-partial-breakpoint="lg"] { padding-inline-start: max(var(--sf-padding-inline, 0px), calc((100vw - var(--sf-container-max-width, 100vw)) / 2)); }
71
+ .flowpress-column[data-partial="end"][data-partial-breakpoint="lg"] { padding-inline-end: max(var(--sf-padding-inline, 0px), calc((100vw - var(--sf-container-max-width, 100vw)) / 2)); }
72
+ }
73
+ @media (min-width: 80rem) {
74
+ .flowpress-column { grid-column: span var(--flowpress-span-xl) / span var(--flowpress-span-xl); }
75
+ .flowpress-column[data-partial="start"][data-partial-breakpoint="xl"] { padding-inline-start: max(var(--sf-padding-inline, 0px), calc((100vw - var(--sf-container-max-width, 100vw)) / 2)); }
76
+ .flowpress-column[data-partial="end"][data-partial-breakpoint="xl"] { padding-inline-end: max(var(--sf-padding-inline, 0px), calc((100vw - var(--sf-container-max-width, 100vw)) / 2)); }
77
+ }
78
+ @media (min-width: 96rem) {
79
+ .flowpress-column { grid-column: span var(--flowpress-span-2xl) / span var(--flowpress-span-2xl); }
80
+ .flowpress-column[data-partial="start"][data-partial-breakpoint="2xl"] { padding-inline-start: max(var(--sf-padding-inline, 0px), calc((100vw - var(--sf-container-max-width, 100vw)) / 2)); }
81
+ .flowpress-column[data-partial="end"][data-partial-breakpoint="2xl"] { padding-inline-end: max(var(--sf-padding-inline, 0px), calc((100vw - var(--sf-container-max-width, 100vw)) / 2)); }
82
+ }
83
+ </style>