diff --git a/README.md b/README.md index d4456f7..0c75fe8 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ always offer the same palette and sizes. - Change a token in `app.css`, not in `theme.json`: the root `theme.json` is only the base (layout, fonts, block styles), and a slug defined there wins over `@theme`. -- The font is the exception: Inter needs a `fontFace` declaration, which only +- The font is the exception: Inter (the variable font, `InterVariable.woff2`) needs a `fontFace` declaration, which only `theme.json` can hold, so fonts are declared there and `vite.config.js` turns their generation off (`disableTailwindFonts`). The same option exists for colours, font sizes and radii. @@ -32,6 +32,27 @@ always offer the same palette and sizes. See [Theme.json and Vite Build Integration](https://pollora.dev/theming/theme-structure/) for the details. +## Gutenberg design system + +Every core block is styled in `theme.json` (`styles`: root, elements, blocks), +from the presets above only, so a paragraph, a quote, a table or a button look +the same in the editor and on the page. Block-specific touches that +`theme.json` has no property for (the quote's gradient border, the table's +rules) sit in each block's `css` field, which the editor loads too. + +- Reference presets by the name WordPress prints: `5xl` becomes + `var(--wp--preset--font-size--5-xl)`. Never a Tailwind variable + (`var(--text-xl)`): it does not exist in the editor. +- A block's `css` takes one selector per rule; WordPress wraps it in + `:root :where(...)` and breaks on `a, b`. +- `app/Cms/StyleLayers.php` puts WordPress's CSS in cascade layers, declared at + the top of `app.css`: `theme, base, wp-core, wp, components, utilities`. The + block library and the global styles beat Tailwind's reset, and a Tailwind + class in a Blade template always beats them. A link in the templates that + should not look like a content link says so with `no-underline`. +- `php bin/tests/run.php` checks that every preset and custom variable the + styles use exists. + ## Contributing ### Development Setup diff --git a/app/Cms/StyleLayers.php b/app/Cms/StyleLayers.php new file mode 100644 index 0000000..a4d554d --- /dev/null +++ b/app/Cms/StyleLayers.php @@ -0,0 +1,98 @@ +registered as $handle => $style) { + $layer = $this->layerFor((string) $handle); + + if ($layer === null || empty($style->extra['after']) || ! empty($style->extra['theme_layered'])) { + continue; + } + + $style->extra['after'] = [self::ORDER."\n@layer {$layer} {\n".implode("\n", (array) $style->extra['after'])."\n}"]; + $style->extra['theme_layered'] = true; + } + } + + /** + * Load a linked WordPress stylesheet into its layer instead of as a bare . + */ + #[Filter('style_loader_tag')] + public function layerLinkedStyle(string $tag, string $handle, string $href, string $media): string + { + $layer = $this->layerFor($handle); + + if ($layer === null || is_admin()) { + return $tag; + } + + $mediaQuery = in_array($media, ['', 'all'], true) ? '' : ' '.$media; + + return sprintf( + "\n", + esc_attr($handle), + self::ORDER, + esc_url($href), + $layer, + $mediaQuery + ); + } + + /** + * The layer a WordPress style handle belongs to, or null to leave it alone. + * + * `core-block-supports` (the per-block layout and colours chosen in the + * editor) stays unlayered on purpose: it must win over the global styles. + */ + private function layerFor(string $handle): ?string + { + if ($handle === 'global-styles') { + return 'wp'; + } + + if ($handle === 'classic-theme-styles' || str_starts_with($handle, 'wp-block-')) { + return 'wp-core'; + } + + return null; + } +} diff --git a/bin/tests/contract.php b/bin/tests/contract.php index 1a612d2..d80e8dc 100644 --- a/bin/tests/contract.php +++ b/bin/tests/contract.php @@ -316,3 +316,135 @@ function checkLoginConfig(): void : $source.' is '.number_format($size).' bytes; the framework will not inline it'; }); } + +/** + * WordPress's slug to CSS-variable conversion (_wp_to_kebab_case): `5xl` → `5-xl`. + */ +function wpKebab(string $slug): string +{ + $slug = preg_replace('/([a-z])([A-Z])/', '$1-$2', $slug) ?? $slug; + $slug = preg_replace('/([0-9])([a-zA-Z])/', '$1-$2', $slug) ?? $slug; + $slug = preg_replace('/([a-zA-Z])([0-9])/', '$1-$2', $slug) ?? $slug; + + return strtolower($slug); +} + +/** + * @return array> preset kind => CSS-variable slugs WordPress will print + */ +function designSystemPresets(array $themeJson): array +{ + $css = (string) file_get_contents(themePath('resources/assets/css/app.css')); + $vite = (string) file_get_contents(themePath('vite.config.js')); + $presets = ['color' => [], 'font-size' => [], 'font-family' => [], 'border-radius' => [], 'spacing' => []]; + + if (preg_match('/@theme static\s*\{(.*?)\n\}/s', $css, $block)) { + preg_match_all('/--(color|text|radius|font)-([a-z0-9-]+?)\s*:/', $block[1], $tokens, PREG_SET_ORDER); + + foreach ($tokens as [, $family, $slug]) { + if (str_contains($slug, '--')) { + continue; + } + + $kind = ['color' => 'color', 'text' => 'font-size', 'radius' => 'border-radius', 'font' => 'font-family'][$family]; + + if ($kind === 'font-family' && str_contains($vite, 'disableTailwindFonts: true')) { + continue; + } + + $presets[$kind][] = wpKebab($slug); + } + } + + $settings = $themeJson['settings'] ?? []; + foreach ([ + 'color' => $settings['color']['palette'] ?? [], + 'font-size' => $settings['typography']['fontSizes'] ?? [], + 'font-family' => $settings['typography']['fontFamilies'] ?? [], + 'border-radius' => $settings['border']['radiusSizes'] ?? [], + 'spacing' => $settings['spacing']['spacingSizes'] ?? [], + ] as $kind => $entries) { + foreach ($entries as $entry) { + $presets[$kind][] = wpKebab((string) ($entry['slug'] ?? '')); + } + } + + return $presets; +} + +/** + * @return list the --wp--custom-- variables settings.custom defines + */ +function designSystemCustomVariables(array $custom, string $prefix = ''): array +{ + $names = []; + + foreach ($custom as $key => $value) { + $name = $prefix.'--'.wpKebab((string) $key); + $names = [...$names, ...(is_array($value) ? designSystemCustomVariables($value, $name) : [$name])]; + } + + return $names; +} + +function checkDesignSystem(): void +{ + section('Design system — theme.json styles point at what exists'); + + $themeJson = json_decode((string) file_get_contents(themePath('theme.json')), true); + + test('theme.json parses', fn () => is_array($themeJson) ?: 'theme.json is not valid JSON'); + + if (! is_array($themeJson)) { + return; + } + + $styles = json_encode($themeJson['styles'] ?? [], JSON_UNESCAPED_SLASHES); + $presets = designSystemPresets($themeJson); + + test('Every preset the styles use is generated', function () use ($styles, $presets) { + preg_match_all('/var\(--wp--preset--(color|font-size|font-family|border-radius|spacing)--([a-z0-9-]+)\)/', $styles, $refs, PREG_SET_ORDER); + $missing = []; + + foreach ($refs as [, $kind, $slug]) { + if (! in_array($slug, $presets[$kind], true)) { + $missing[] = "--wp--preset--{$kind}--{$slug}"; + } + } + + // WordPress prints `5xl` as `5-xl`: a reference to the raw slug is silently ignored. + return $missing === [] ? true : 'no such preset, the value is dropped: '.implode(', ', array_unique($missing)); + }); + + test('Every custom variable the styles use is defined', function () use ($styles, $themeJson) { + $defined = designSystemCustomVariables($themeJson['settings']['custom'] ?? []); + preg_match_all('/var\(--wp--custom(--[a-z0-9-]+)\)/', $styles, $refs); + $missing = array_diff(array_unique($refs[1]), $defined); + + return $missing === [] ? true : 'not in settings.custom: '.implode(', ', $missing); + }); + + test('The styles use no Tailwind-only variable', function () use ($styles) { + // The editor loads theme.json, not app.css: var(--text-xl) is undefined there. + preg_match_all('/var\(--(?!wp--)[a-z][a-z0-9-]*/', $styles, $refs); + + return $refs[0] === [] ? true : 'undefined in the editor: '.implode(', ', array_unique($refs[0])); + }); + + test('No block css uses a selector list', function () use ($themeJson) { + $broken = []; + + foreach ($themeJson['styles']['blocks'] ?? [] as $block => $style) { + foreach (explode('}', (string) ($style['css'] ?? '')) as $rule) { + $selector = explode('{', $rule)[0]; + + // WordPress wraps each selector in :root :where(…) and breaks on `a, b`. + if (str_contains(preg_replace('/\([^)]*\)/', '', $selector) ?? $selector, ',')) { + $broken[] = $block.': '.trim($selector); + } + } + } + + return $broken === [] ? true : 'one rule per selector: '.implode('; ', $broken); + }); +} diff --git a/bin/tests/run.php b/bin/tests/run.php index 623dbed..44e6e09 100644 --- a/bin/tests/run.php +++ b/bin/tests/run.php @@ -29,6 +29,7 @@ checkViewReferences(); checkNoLeakedCodeName(); checkLoginConfig(); +checkDesignSystem(); checkSyntax(); exit(summary()); diff --git a/resources/assets/css/app.css b/resources/assets/css/app.css index 49a4c60..f78e1f7 100644 --- a/resources/assets/css/app.css +++ b/resources/assets/css/app.css @@ -1,3 +1,10 @@ +/* + * WordPress's block library and theme.json styles sit between Tailwind's reset + * and its utilities (app/Cms/StyleLayers.php): theme.json styles every block, + * a utility class still wins in the Blade templates. + */ +@layer theme, base, wp-core, wp, components, utilities; + @import "tailwindcss"; @source "../../views"; @@ -54,143 +61,3 @@ --color-border: var(--wp--preset--color--border, #e5e7eb); } } - -/* WordPress content blocks */ -.entry-content p { - margin-block: 1.25em; - line-height: 1.75; -} - -.entry-content > *:first-child { - margin-top: 0; -} - -.entry-content > *:last-child { - margin-bottom: 0; -} - -.entry-content blockquote, -.entry-content .wp-block-quote { - position: relative; - margin-block: 2.5em; - padding: 2rem 2rem 2rem 5rem; - border: none; - border-radius: 1.25rem; - background: - linear-gradient(var(--color-surface), var(--color-surface)) padding-box, - linear-gradient(135deg, var(--color-primary-vivid), var(--color-primary), var(--color-secondary)) border-box; - border: 2px solid transparent; - font-style: italic; - color: var(--color-foreground); - overflow: visible; - box-shadow: 0 4px 24px -4px color-mix(in srgb, var(--color-primary-vivid) 12%, transparent); -} - -.entry-content blockquote::before, -.entry-content .wp-block-quote::before { - content: "\201C"; - position: absolute; - top: 0.25rem; - left: 1.25rem; - font-family: Georgia, serif; - font-size: 7rem; - font-style: normal; - font-weight: 700; - line-height: 1; - background: linear-gradient(135deg, var(--color-primary-vivid), var(--color-secondary)); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - opacity: 0.2; -} - -.entry-content blockquote::after, -.entry-content .wp-block-quote::after { - content: ""; - position: absolute; - bottom: -6px; - left: 2rem; - width: 40%; - height: 6px; - border-radius: 3px; - background: linear-gradient(90deg, var(--color-primary-vivid), var(--color-secondary), transparent); - opacity: 0.5; -} - -.entry-content blockquote p, -.entry-content .wp-block-quote p { - margin-block: 0.5em; -} - -.entry-content blockquote p:first-child, -.entry-content .wp-block-quote p:first-child { - margin-top: 0; -} - -.entry-content blockquote p:last-child, -.entry-content .wp-block-quote p:last-child { - margin-bottom: 0; -} - -.entry-content blockquote cite, -.entry-content .wp-block-quote cite { - display: block; - margin-top: 1em; - font-size: 0.875em; - font-style: normal; - font-weight: 600; - background: linear-gradient(90deg, var(--color-primary-vivid), var(--color-primary)); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; -} - -.entry-content blockquote cite::before, -.entry-content .wp-block-quote cite::before { - content: "— "; -} - -.entry-content a { - color: var(--color-primary); - text-decoration: underline; - text-decoration-color: var(--color-primary) / 0.3; - text-underline-offset: 2px; - transition: text-decoration-color 0.15s; -} - -.entry-content a:hover { - text-decoration-color: var(--color-primary); -} - -.entry-content h2 { - margin-top: 2em; - margin-bottom: 0.75em; - font-size: 1.5rem; - font-weight: 700; - line-height: 1.3; - color: var(--color-foreground); -} - -.entry-content h3 { - margin-top: 1.75em; - margin-bottom: 0.5em; - font-size: 1.25rem; - font-weight: 600; - line-height: 1.4; - color: var(--color-foreground); -} - -.entry-content ul, -.entry-content ol { - margin-block: 1.25em; - padding-left: 1.5em; -} - -.entry-content li { - margin-block: 0.375em; -} - -.entry-content img { - border-radius: 0.75rem; - margin-block: 1.5em; -} \ No newline at end of file diff --git a/resources/assets/fonts/Inter-Italic.woff2 b/resources/assets/fonts/Inter-Italic.woff2 deleted file mode 100644 index 4c24ce2..0000000 Binary files a/resources/assets/fonts/Inter-Italic.woff2 and /dev/null differ diff --git a/resources/assets/fonts/Inter-LICENSE.txt b/resources/assets/fonts/Inter-LICENSE.txt new file mode 100644 index 0000000..9b2ca37 --- /dev/null +++ b/resources/assets/fonts/Inter-LICENSE.txt @@ -0,0 +1,92 @@ +Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION AND CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/resources/assets/fonts/Inter-Regular.woff2 b/resources/assets/fonts/Inter-Regular.woff2 deleted file mode 100644 index b8699af..0000000 Binary files a/resources/assets/fonts/Inter-Regular.woff2 and /dev/null differ diff --git a/resources/assets/fonts/InterVariable-Italic.woff2 b/resources/assets/fonts/InterVariable-Italic.woff2 new file mode 100644 index 0000000..b3530f3 Binary files /dev/null and b/resources/assets/fonts/InterVariable-Italic.woff2 differ diff --git a/resources/assets/fonts/InterVariable.woff2 b/resources/assets/fonts/InterVariable.woff2 new file mode 100644 index 0000000..5a8d3e7 Binary files /dev/null and b/resources/assets/fonts/InterVariable.woff2 differ diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php index 68c3584..139d50b 100644 --- a/resources/views/home.blade.php +++ b/resources/views/home.blade.php @@ -20,11 +20,11 @@ Ship faster, build smarter.

- + Get started - + View on GitHub @@ -71,14 +71,14 @@ @published

- + @title

-

+

@excerpt -

+
Read more diff --git a/resources/views/index.blade.php b/resources/views/index.blade.php index 42f7373..0a3092d 100644 --- a/resources/views/index.blade.php +++ b/resources/views/index.blade.php @@ -55,7 +55,7 @@ @include('parts.search')
- + Back to home diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index e5a091a..e19cc15 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -11,14 +11,14 @@
- + {{ get_bloginfo('name') }}
-
+
@content
\ No newline at end of file diff --git a/resources/views/parts/not-found.blade.php b/resources/views/parts/not-found.blade.php index ac5b9f3..16666a0 100644 --- a/resources/views/parts/not-found.blade.php +++ b/resources/views/parts/not-found.blade.php @@ -13,7 +13,7 @@ @include('parts.search')
- + Back to home diff --git a/theme.json b/theme.json index e227d4e..fc38c7d 100644 --- a/theme.json +++ b/theme.json @@ -5,28 +5,27 @@ "settings": { "appearanceTools": true, "custom": { - "spacing": { - "small": "max(1.25rem, 5vw)", - "medium": "clamp(2rem, 8vw, calc(4 * var(--wp--style--block-gap)))", - "large": "clamp(4rem, 10vw, 8rem)", - "outer": "var(--wp--custom--spacing--small, 1.25rem)" - }, - "typography": { - "font-size": { - "huge": "clamp(2.25rem, 4vw, 2.75rem)", - "gigantic": "clamp(2.75rem, 6vw, 3.25rem)", - "colossal": "clamp(3.25rem, 8vw, 6.25rem)" - }, - "line-height": { - "tiny": 1.15, - "small": 1.2, - "medium": 1.4, - "normal": 1.6 - } + "lineHeight": { + "tight": 1.2, + "snug": 1.4, + "body": 1.75, + "normal": 1.5 + }, + "fontWeight": { + "medium": 500, + "semiBold": 600, + "bold": 700 } }, "spacing": { - "units": ["%", "px", "em", "rem", "vh", "vw"] + "units": [ + "%", + "px", + "em", + "rem", + "vh", + "vw" + ] }, "typography": { "dropCap": false, @@ -42,7 +41,9 @@ "fontStyle": "normal", "fontDisplay": "swap", "fontStretch": "normal", - "src": ["file:./fonts/Inter-Regular.woff2"] + "src": [ + "file:./fonts/InterVariable.woff2" + ] }, { "fontFamily": "Inter var", @@ -50,157 +51,403 @@ "fontStyle": "italic", "fontDisplay": "swap", "fontStretch": "normal", - "src": ["file:./fonts/Inter-Italic.woff2"] + "src": [ + "file:./fonts/InterVariable-Italic.woff2" + ] } ] } ] }, "layout": { - "contentSize": "var(--container-xl)", - "wideSize": "var(--container-5xl)" + "contentSize": "44rem", + "wideSize": "64rem" } }, "styles": { - "blocks": { - "core/button": { - "border": { - "radius": "0.5rem" - }, + "color": { + "background": "var(--wp--preset--color--surface)", + "text": "var(--wp--preset--color--foreground)" + }, + "typography": { + "fontFamily": "var(--wp--preset--font-family--inter-var)", + "fontSize": "var(--wp--preset--font-size--base)", + "lineHeight": "var(--wp--custom--line-height--normal)" + }, + "spacing": { + "blockGap": "1.5rem" + }, + "css": ".wp-block-list a:where(:not(.wp-element-button)), p a:where(:not(.wp-element-button)) { text-underline-offset: 0.2em; text-decoration-thickness: 1px; } :where(code, kbd) { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 0.9em; }", + "elements": { + "heading": { "color": { - "background": "var(--wp--preset--color--primary)", - "text": "var(--color-white)" + "text": "var(--wp--preset--color--foreground)" }, "typography": { - "fontSize": "var(--text-base)" + "fontFamily": "var(--wp--preset--font-family--inter-var)", + "fontWeight": "var(--wp--custom--font-weight--bold)", + "lineHeight": "var(--wp--custom--line-height--tight)", + "letterSpacing": "-0.02em" } }, - "core/post-title": { + "h1": { "typography": { - "fontWeight": "700", - "lineHeight": "var(--wp--custom--typography--line-height--tiny)", - "fontSize": "var(--wp--custom--typography--font-size--gigantic)" + "fontSize": "var(--wp--preset--font-size--5-xl)" } }, - "core/post-comments": { - "spacing": { - "padding": { - "top": "var(--wp--custom--spacing--small)" - } + "h2": { + "typography": { + "fontSize": "var(--wp--preset--font-size--3-xl)" } }, - "core/pullquote": { - "border": { - "width": "5px 0" + "h3": { + "typography": { + "fontSize": "var(--wp--preset--font-size--2-xl)", + "fontWeight": "var(--wp--custom--font-weight--semi-bold)" } }, - "core/query-title": { + "h4": { "typography": { - "fontWeight": "700", - "lineHeight": "var(--wp--custom--typography--line-height--small)", - "fontSize": "var(--wp--custom--typography--font-size--gigantic)" + "fontSize": "var(--wp--preset--font-size--xl)", + "fontWeight": "var(--wp--custom--font-weight--semi-bold)", + "lineHeight": "var(--wp--custom--line-height--snug)" } }, - "core/quote": { + "h5": { + "typography": { + "fontSize": "var(--wp--preset--font-size--lg)", + "fontWeight": "var(--wp--custom--font-weight--semi-bold)", + "lineHeight": "var(--wp--custom--line-height--snug)", + "letterSpacing": "0" + } + }, + "h6": { + "typography": { + "fontSize": "var(--wp--preset--font-size--sm)", + "fontWeight": "var(--wp--custom--font-weight--semi-bold)", + "lineHeight": "var(--wp--custom--line-height--snug)", + "textTransform": "uppercase", + "letterSpacing": "0.08em" + }, + "color": { + "text": "var(--wp--preset--color--muted)" + } + }, + "button": { + "color": { + "background": "var(--wp--preset--color--primary)", + "text": "var(--wp--preset--color--background)" + }, + "border": { + "radius": "var(--wp--preset--border-radius--lg)", + "width": "0" + }, "spacing": { - "margin": { - "top": "var(--spacing)", - "bottom": "var(--spacing)" + "padding": { + "top": "0.75rem", + "bottom": "0.75rem", + "left": "1.5rem", + "right": "1.5rem" + } + }, + "typography": { + "fontSize": "var(--wp--preset--font-size--sm)", + "fontWeight": "var(--wp--custom--font-weight--semi-bold)", + "lineHeight": "var(--wp--custom--line-height--tight)", + "textDecoration": "none" + }, + ":hover": { + "color": { + "background": "var(--wp--preset--color--primary-vivid)", + "text": "var(--wp--preset--color--background)" } }, + ":focus": { + "outline": { + "color": "var(--wp--preset--color--primary-vivid)", + "offset": "2px", + "style": "solid", + "width": "2px" + } + } + }, + "caption": { "color": { - "background": "var(--wp--preset--color--surface-alt)" + "text": "var(--wp--preset--color--muted)" }, - "border": { - "color": "var(--wp--preset--color--primary)", - "width": "0 0 0 4px" + "typography": { + "fontSize": "var(--wp--preset--font-size--sm)", + "lineHeight": "var(--wp--custom--line-height--snug)", + "textAlign": "center" } }, - "core/site-title": { + "cite": { + "color": { + "text": "var(--wp--preset--color--primary)" + }, "typography": { - "lineHeight": "var(--text-base--line-height)", - "fontSize": "var(--text-4xl)", + "fontSize": "var(--wp--preset--font-size--sm)", "fontStyle": "normal", - "fontWeight": "700" + "fontWeight": "var(--wp--custom--font-weight--semi-bold)" } } }, - "elements": { - "h1": { + "blocks": { + "core/heading": { + "css": "&:not(:first-child) { margin-block-start: calc(var(--wp--style--block-gap) * 2); } & + .wp-block-heading { margin-block-start: var(--wp--style--block-gap); }" + }, + "core/list": { + "spacing": { + "padding": { + "left": "1.5em" + } + }, + "css": "&:where(ul) { list-style: disc; } &:where(ol) { list-style: decimal; } & li { margin-block: 0.375em; } & li::marker { color: var(--wp--preset--color--primary); }", "typography": { - "fontFamily": "var(--default-font-family)", - "fontWeight": "var(--font-weight-bold)", - "lineHeight": "var(--text-5xl--line-height)", - "fontSize": "var(--text-5xl)" + "lineHeight": "var(--wp--custom--line-height--body)" + }, + "elements": { + "link": { + "color": { + "text": "var(--wp--preset--color--primary)" + }, + "typography": { + "textDecoration": "underline" + }, + ":hover": { + "color": { + "text": "var(--wp--preset--color--primary-vivid)" + } + } + } } }, - "h2": { + "core/quote": { + "color": { + "text": "var(--wp--preset--color--foreground)" + }, "typography": { - "fontFamily": "var(--default-font-family)", - "fontWeight": "var(--font-weight-semibold)", - "lineHeight": "var(--text-4xl--line-height)", - "fontSize": "var(--text-4xl)" + "fontSize": "var(--wp--preset--font-size--lg)", + "fontStyle": "italic", + "lineHeight": "var(--wp--custom--line-height--snug)" + }, + "spacing": { + "padding": { + "top": "2rem", + "bottom": "2rem", + "left": "5rem", + "right": "2rem" + } + }, + "border": { + "radius": "var(--wp--preset--border-radius--2-xl)", + "width": "2px", + "style": "solid", + "color": "transparent" + }, + "css": "& { position: relative; background: linear-gradient(var(--wp--preset--color--surface), var(--wp--preset--color--surface)) padding-box, linear-gradient(135deg, var(--wp--preset--color--primary-vivid), var(--wp--preset--color--primary), var(--wp--preset--color--secondary)) border-box; box-shadow: 0 4px 24px -4px color-mix(in srgb, var(--wp--preset--color--primary-vivid) 12%, transparent); } &::before { content: \"\\201C\"; position: absolute; top: 0.25rem; left: 1.25rem; font-family: Georgia, serif; font-size: 7rem; font-style: normal; font-weight: 700; line-height: 1; background: linear-gradient(135deg, var(--wp--preset--color--primary-vivid), var(--wp--preset--color--primary), var(--wp--preset--color--secondary)); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; opacity: 0.2; } & p { margin-block: 0.5em; } & p:first-child { margin-block-start: 0; } & cite { display: block; margin-block-start: 1em; color: var(--wp--preset--color--primary) !important; font-size: var(--wp--preset--font-size--sm) !important; font-style: normal !important; font-weight: 600 !important; text-transform: none !important; letter-spacing: 0 !important; } & cite::before { content: \"— \"; }" + }, + "core/pullquote": { + "color": { + "text": "var(--wp--preset--color--foreground)" + }, + "typography": { + "fontSize": "var(--wp--preset--font-size--2-xl)", + "fontWeight": "var(--wp--custom--font-weight--semi-bold)", + "lineHeight": "var(--wp--custom--line-height--snug)", + "letterSpacing": "-0.01em" + }, + "spacing": { + "padding": { + "top": "2.5rem", + "bottom": "2.5rem", + "left": "1.5rem", + "right": "1.5rem" + } + }, + "border": { + "width": "0" + }, + "css": "& { border-block: 4px solid; border-image: linear-gradient(135deg, var(--wp--preset--color--primary-vivid), var(--wp--preset--color--primary), var(--wp--preset--color--secondary)) 1; } & p { margin: 0; } & cite { display: block; margin-block-start: 1.25em; color: var(--wp--preset--color--primary) !important; font-size: var(--wp--preset--font-size--sm) !important; font-style: normal !important; font-weight: 600 !important; text-transform: none !important; letter-spacing: 0 !important; }" + }, + "core/separator": { + "color": { + "text": "var(--wp--preset--color--border)" + }, + "css": "&:not(.is-style-wide):not(.is-style-dots) { width: 6rem; margin-inline: auto; } &:not(.is-style-dots) { border: 0; border-block-start: 1px solid; }" + }, + "core/table": { + "typography": { + "fontSize": "var(--wp--preset--font-size--sm)" + }, + "css": "& { border-collapse: separate !important; border-spacing: 0; width: 100%; border: 1px solid var(--wp--preset--color--border); border-radius: var(--wp--preset--border-radius--lg); overflow: hidden; } & thead { border: 0 !important; } & tfoot { border: 0 !important; } & th { border: 0 !important; border-block-end: 1px solid var(--wp--preset--color--border) !important; padding: 0.75rem 1rem !important; text-align: start; } & td { border: 0 !important; border-block-end: 1px solid var(--wp--preset--color--border) !important; padding: 0.75rem 1rem !important; } & thead th { background: var(--wp--preset--color--surface-alt); font-weight: 600; } & tbody tr:last-child th { border-block-end: 0 !important; } & tbody tr:last-child td { border-block-end: 0 !important; }", + "elements": { + "link": { + "color": { + "text": "var(--wp--preset--color--primary)" + }, + "typography": { + "textDecoration": "underline" + }, + ":hover": { + "color": { + "text": "var(--wp--preset--color--primary-vivid)" + } + } + } } }, - "h3": { + "core/code": { + "color": { + "background": "var(--wp--preset--color--foreground)", + "text": "var(--wp--preset--color--surface)" + }, "typography": { - "fontFamily": "var(--default-font-family)", - "fontWeight": "var(--font-weight-semibold)", - "lineHeight": "var(--wp--custom--typography--line-height--small)", - "fontSize": "var(--text-xl)" + "fontFamily": "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace", + "fontSize": "var(--wp--preset--font-size--sm)", + "lineHeight": "var(--wp--custom--line-height--snug)" + }, + "spacing": { + "padding": { + "top": "1rem", + "bottom": "1rem", + "left": "1.25rem", + "right": "1.25rem" + } + }, + "border": { + "radius": "var(--wp--preset--border-radius--lg)", + "width": "0" + }, + "css": "& { overflow-x: auto; } & code { font-size: inherit; }" + }, + "core/preformatted": { + "color": { + "background": "var(--wp--preset--color--surface-alt)", + "text": "var(--wp--preset--color--foreground)" + }, + "typography": { + "fontFamily": "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace", + "fontSize": "var(--wp--preset--font-size--sm)", + "lineHeight": "var(--wp--custom--line-height--snug)" + }, + "spacing": { + "padding": { + "top": "1rem", + "bottom": "1rem", + "left": "1.25rem", + "right": "1.25rem" + } + }, + "border": { + "radius": "var(--wp--preset--border-radius--lg)" + }, + "css": "& { overflow-x: auto; }" + }, + "core/verse": { + "typography": { + "fontFamily": "var(--wp--preset--font-family--inter-var)", + "fontStyle": "italic", + "lineHeight": "var(--wp--custom--line-height--body)" + }, + "css": "& { padding-inline-start: 1.25rem; border-inline-start: 2px solid var(--wp--preset--color--border); white-space: pre-wrap; }", + "elements": { + "link": { + "color": { + "text": "var(--wp--preset--color--primary)" + }, + "typography": { + "textDecoration": "underline" + }, + ":hover": { + "color": { + "text": "var(--wp--preset--color--primary-vivid)" + } + } + } } }, - "h4": { + "core/details": { + "border": { + "color": "var(--wp--preset--color--border)", + "width": "1px", + "style": "solid", + "radius": "var(--wp--preset--border-radius--lg)" + }, + "spacing": { + "padding": { + "top": "1rem", + "bottom": "1rem", + "left": "1.25rem", + "right": "1.25rem" + } + }, + "css": "& summary { cursor: pointer; font-weight: 600; } & summary::marker { color: var(--wp--preset--color--primary); } &[open] summary { margin-block-end: 0.75em; }" + }, + "core/image": { + "css": "& img { border-radius: var(--wp--preset--border-radius--xl); } & figcaption { text-align: center; margin-block-start: 0.75em; }" + }, + "core/gallery": { + "css": "& img { border-radius: var(--wp--preset--border-radius--lg); }" + }, + "core/group": { + "css": "&.has-background { padding: 2rem; border-radius: var(--wp--preset--border-radius--xl); }" + }, + "core/button": { + "css": "&.is-style-outline > .wp-block-button__link { background: transparent; color: var(--wp--preset--color--primary); box-shadow: inset 0 0 0 2px currentColor; } &.is-style-outline > .wp-block-button__link:hover { background: var(--wp--preset--color--surface-alt); color: var(--wp--preset--color--primary-vivid); }" + }, + "core/search": { + "css": "& .wp-block-search__label { font-weight: 600; font-size: var(--wp--preset--font-size--sm); } & .wp-block-search__input { border: 1px solid var(--wp--preset--color--border); border-radius: var(--wp--preset--border-radius--lg); padding: 0.625rem 0.875rem; background: var(--wp--preset--color--background); } & .wp-block-search__input:focus { outline: 2px solid var(--wp--preset--color--primary); outline-offset: 1px; }" + }, + "core/post-title": { "typography": { - "fontFamily": "var(--default-font-family)", - "fontWeight": "var(--font-weight-semibold)", - "lineHeight": "var(--wp--custom--typography--line-height--tiny)", - "fontSize": "var(--wp--preset--font-size--x-large)" + "fontSize": "var(--wp--preset--font-size--4-xl)", + "fontWeight": "var(--wp--custom--font-weight--bold)", + "lineHeight": "var(--wp--custom--line-height--tight)" } }, - "h5": { + "core/query-title": { "typography": { - "fontFamily": "var(--default-font-family)", - "fontWeight": "var(--font-weight-bold)", - "textTransform": "uppercase", - "lineHeight": "var(--text-base--line-height)", - "fontSize": "var(--text-4xl)" + "fontSize": "var(--wp--preset--font-size--4-xl)", + "fontWeight": "var(--wp--custom--font-weight--bold)", + "lineHeight": "var(--wp--custom--line-height--tight)" } }, - "h6": { + "core/site-title": { "typography": { - "fontFamily": "var(--default-font-family)", - "fontWeight": "var(--font-weight-medium)", - "textTransform": "uppercase", - "lineHeight": "var(--text-base--line-height)", - "fontSize": "var(--text-4xl)" + "fontSize": "var(--wp--preset--font-size--2-xl)", + "fontWeight": "var(--wp--custom--font-weight--bold)", + "lineHeight": "var(--wp--custom--line-height--tight)" } }, - "link": { - "color": { - "text": "var(--wp--preset--color--foreground)" - }, + "core/post-comments": { + "spacing": { + "padding": { + "top": "2rem" + } + } + }, + "core/paragraph": { "typography": { - "textDecoration": "none" + "lineHeight": "var(--wp--custom--line-height--body)" }, - ":hover": { - "color": { - "text": "var(--wp--preset--color--foreground)" - }, - "typography": { - "textDecoration": "none" + "elements": { + "link": { + "color": { + "text": "var(--wp--preset--color--primary)" + }, + "typography": { + "textDecoration": "underline" + }, + ":hover": { + "color": { + "text": "var(--wp--preset--color--primary-vivid)" + } + } } } } - }, - "spacing": { - "blockGap": "var(--spacing)" - }, - "typography": { - "fontFamily": "var(--default-font-family)", - "lineHeight": "var(--text-base--line-height)", - "fontSize": "var(--text-base)" } }, "templateParts": [] -} \ No newline at end of file +}