Skip to content

Commit 4a8aca1

Browse files
authored
Merge branch 'main' into DE-830-Publish-review-guide-for-web-interfaces
2 parents 3d1fc55 + b82b3c6 commit 4a8aca1

23 files changed

Lines changed: 800 additions & 169 deletions

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "vanilla-framework",
3-
"version": "4.42.0",
3+
"version": "4.44.0",
44
"author": {
55
"email": "webteam@canonical.com",
66
"name": "Canonical Webteam"

releases.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
- version: 4.44.0
2+
features:
3+
- component: Hero section
4+
url: /docs/patterns/hero
5+
status: Updated
6+
notes: Added <a href="/docs/patterns/hero#blocks">blocks</a> successor to slots.
7+
- component: Basic section
8+
url: /docs/patterns/basic-section
9+
status: Updated
10+
notes: Added more aspect_ratio options to the image block. Introduced two new parameters, <code>is_cover</code> and <code>override_last_item_padding</code>.
11+
- version: 4.43.0
12+
features:
13+
- component: Icon
14+
url: /docs/patterns/icons
15+
status: New
16+
notes: Added icons for storage pool, storage volume, and storage bucket.
117
- version: 4.42.0
218
features:
319
- component: Resources

scss/_base_icon-definitions.scss

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1600,3 +1600,39 @@
16001600
@mixin vf-icon-stop-themed {
16011601
@include vf-themed-icon($light-value: vf-icon-stop-url($colors--light-theme--icon), $dark-value: vf-icon-stop-url($colors--dark-theme--icon));
16021602
}
1603+
1604+
@function vf-icon-storage-bucket-url($color) {
1605+
@return url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='none' viewBox='0 0 16 16'%3E%3Cpath fill='#{vf-url-friendly-color($color)}' fill-rule='evenodd' d='M14 6.75h-.83l-.925 8.333a.75.75 0 0 1-.745.667h-7a.75.75 0 0 1-.745-.667L2.829 6.75H2v-1.5h12zm-8.83 7.5h5.66l.832-7.5H4.338z' clip-rule='evenodd'/%3E%3Cpath fill='#{vf-url-friendly-color($color)}' d='M8.5.25A3.75 3.75 0 0 1 12.25 4h-1.5A2.25 2.25 0 0 0 8.5 1.75h-1A2.25 2.25 0 0 0 5.25 4h-1.5A3.75 3.75 0 0 1 7.5.25z'/%3E%3C/svg%3E");
1606+
}
1607+
1608+
@mixin vf-icon-storage-bucket($color: $colors--light-theme--icon) {
1609+
background-image: vf-icon-storage-bucket-url($color);
1610+
}
1611+
1612+
@mixin vf-icon-storage-bucket-themed {
1613+
@include vf-themed-icon($light-value: vf-icon-storage-bucket-url($colors--light-theme--icon), $dark-value: vf-icon-storage-bucket-url($colors--dark-theme--icon));
1614+
}
1615+
1616+
@function vf-icon-storage-pool-url($color) {
1617+
@return url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='none' viewBox='0 0 16 16'%3E%3Cpath fill='#{vf-url-friendly-color($color)}' fill-rule='evenodd' d='M8 1a12 12 0 0 1 1.052.044l.21.021.1.01q.11.013.219.029l.129.016.187.03a11 11 0 0 1 .494.09l.06.012a10 10 0 0 1 .519.125q.098.026.193.055l.08.023q.1.03.197.062l.114.037a9 9 0 0 1 .44.162l.105.042a8 8 0 0 1 .431.193l.059.03q.087.042.173.087l.097.054.128.071.113.068.11.07q.053.033.104.067.064.043.124.085l.03.02q.087.062.17.126l.02.017c.616.482 1.046 1.046 1.234 1.659l.016.053q.02.069.034.138.008.028.013.056.018.09.028.181H15v6.534h-.017C14.743 13.35 11.71 15 8 15c-3.71 0-6.743-1.649-6.983-3.733H1V4.733h.017q.01-.09.027-.181l.013-.056q.014-.069.034-.138l.016-.053c.188-.613.619-1.177 1.235-1.66l.02-.016q.083-.064.17-.125l.029-.02q.06-.044.124-.086l.103-.068.111-.07.113-.067.128-.07.097-.055a7 7 0 0 1 .663-.31l.105-.042a9 9 0 0 1 .44-.162l.114-.037q.097-.032.197-.062l.08-.023q.096-.029.193-.055a10 10 0 0 1 .52-.125l.06-.013a11 11 0 0 1 .493-.09l.187-.029.129-.016.219-.029.1-.01.21-.021A12 12 0 0 1 8 1m5.322 9.73C11.922 11.53 10.04 12 8 12s-3.923-.47-5.322-1.27q-.09-.053-.178-.108v.413l.007.06c.04.347.367.938 1.41 1.499 1 .537 2.435.906 4.083.906s3.083-.369 4.083-.906c1.043-.56 1.37-1.152 1.41-1.5l.007-.059v-.413q-.087.055-.178.107m.178-3.258C12.218 8.402 10.232 9 8 9s-4.218-.599-5.5-1.528v1.28c.244.234.55.462.922.675C4.556 10.075 6.173 10.5 8 10.5s3.444-.425 4.578-1.073q.558-.321.922-.675zM8 2.5q-.485 0-.943.041c-1.24.11-2.315.427-3.117.853l-.07.039c-.988.543-1.312 1.11-1.36 1.455A1 1 0 0 0 2.5 5c0 .334.251.93 1.295 1.526C4.796 7.1 6.28 7.5 8 7.5s3.204-.401 4.205-.974C13.25 5.93 13.5 5.334 13.5 5a1 1 0 0 0-.01-.112c-.049-.345-.372-.912-1.36-1.455l-.071-.04c-.802-.425-1.878-.743-3.117-.852Q8.485 2.501 8 2.5' clip-rule='evenodd'/%3E%3C/svg%3E");
1618+
}
1619+
1620+
@mixin vf-icon-storage-pool($color: $colors--light-theme--icon) {
1621+
background-image: vf-icon-storage-pool-url($color);
1622+
}
1623+
1624+
@mixin vf-icon-storage-pool-themed {
1625+
@include vf-themed-icon($light-value: vf-icon-storage-pool-url($colors--light-theme--icon), $dark-value: vf-icon-storage-pool-url($colors--dark-theme--icon));
1626+
}
1627+
1628+
@function vf-icon-storage-volume-url($color) {
1629+
@return url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='none' viewBox='0 0 16 16'%3E%3Cpath fill='#{vf-url-friendly-color($color)}' fill-rule='evenodd' d='M8 1a12 12 0 0 1 1.052.044l.21.021.1.01q.11.013.219.029l.129.016.187.03a11 11 0 0 1 .494.09l.06.012a10 10 0 0 1 .519.125q.098.026.193.055l.08.023q.1.03.197.062l.114.037a9 9 0 0 1 .44.162l.105.042a8 8 0 0 1 .431.193l.059.03q.087.042.173.087l.097.054.128.071.113.068.11.07q.053.033.104.067.064.043.124.085l.03.02q.087.062.17.126l.02.017c.616.482 1.046 1.046 1.234 1.659l.016.053q.02.069.034.138.008.028.013.056.018.09.028.181H15v6.534h-.017C14.743 13.35 11.71 15 8 15c-3.71 0-6.743-1.649-6.983-3.733H1V4.733h.017q.01-.09.027-.181l.013-.056q.014-.069.034-.138l.016-.053c.188-.613.619-1.177 1.235-1.66l.02-.016q.083-.064.17-.125l.029-.02q.06-.044.124-.086l.103-.068.111-.07.113-.067.128-.07.097-.055a7 7 0 0 1 .663-.31l.105-.042a9 9 0 0 1 .44-.162l.114-.037q.097-.032.197-.062l.08-.023q.096-.029.193-.055a10 10 0 0 1 .52-.125l.06-.013a11 11 0 0 1 .493-.09l.187-.029.129-.016.219-.029.1-.01.21-.021A12 12 0 0 1 8 1m5.5 6.472C12.218 8.402 10.232 9 8 9s-4.218-.599-5.5-1.528v3.563l.007.06c.04.347.367.938 1.41 1.499 1 .537 2.435.906 4.083.906s3.083-.369 4.083-.906c1.043-.56 1.37-1.152 1.41-1.5l.007-.059zM8 2.5q-.485 0-.943.041c-1.24.11-2.315.427-3.117.853l-.07.039c-.988.543-1.312 1.11-1.36 1.455A1 1 0 0 0 2.5 5c0 .334.251.93 1.295 1.526C4.796 7.1 6.28 7.5 8 7.5s3.204-.401 4.205-.974C13.25 5.93 13.5 5.334 13.5 5a1 1 0 0 0-.01-.112c-.049-.345-.372-.912-1.36-1.455l-.071-.04c-.802-.425-1.878-.743-3.117-.852Q8.485 2.501 8 2.5' clip-rule='evenodd'/%3E%3C/svg%3E");
1630+
}
1631+
1632+
@mixin vf-icon-storage-volume($color: $colors--light-theme--icon) {
1633+
background-image: vf-icon-storage-volume-url($color);
1634+
}
1635+
1636+
@mixin vf-icon-storage-volume-themed {
1637+
@include vf-themed-icon($light-value: vf-icon-storage-volume-url($colors--light-theme--icon), $dark-value: vf-icon-storage-volume-url($colors--dark-theme--icon));
1638+
}

scss/_patterns_icons.scss

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2067,4 +2067,27 @@
20672067
}
20682068
}
20692069

2070-
// **Base and Pattern mixins accurate as of October 2025**
2070+
// ICONS ADDED IN FEBRUARY 2026
2071+
2072+
@mixin vf-p-icon-storage-bucket {
2073+
.p-icon--storage-bucket {
2074+
@extend %icon;
2075+
@include vf-icon-storage-bucket-themed;
2076+
}
2077+
}
2078+
2079+
@mixin vf-p-icon-storage-pool {
2080+
.p-icon--storage-pool {
2081+
@extend %icon;
2082+
@include vf-icon-storage-pool-themed;
2083+
}
2084+
}
2085+
2086+
@mixin vf-p-icon-storage-volume {
2087+
.p-icon--storage-volume {
2088+
@extend %icon;
2089+
@include vf-icon-storage-volume-themed;
2090+
}
2091+
}
2092+
2093+
// **Base and Pattern mixins accurate as of February 2026**

scss/standalone/patterns_icons-additional.scss

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@
135135
@include vf-p-icon-status-waiting;
136136
@include vf-p-icon-status;
137137
@include vf-p-icon-stop;
138+
@include vf-p-icon-storage-bucket;
139+
@include vf-p-icon-storage-pool;
140+
@include vf-p-icon-storage-volume;
138141
@include vf-p-icon-submit-bug;
139142
@include vf-p-icon-success-grey;
140143
@include vf-p-icon-switcher-dashboard;

templates/_macros/vf_basic-section.jinja

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
{% from "_macros/shared/vf_linked-logo-block.jinja" import vf_linked_logo_block %}
77
{% from "_macros/shared/vf_logo-block.jinja" import vf_logo_block %}
88

9-
{% macro vf_basic_section_blocks(items=[]) %}
9+
{% macro vf_basic_section_blocks(items=[], override_last_item_padding=false) %}
1010
{%- for item in items -%}
1111
{%- set item_padding = (item.get("padding", "")) | trim -%}
1212
{%- if item_padding not in ["shallow"] -%}
@@ -19,7 +19,7 @@
1919
{%- endif -%}
2020

2121
{#- Last item should not have any additional padding - the pattern itself already has bottom padding -#}
22-
{%- if loop.last -%}
22+
{%- if loop.last and not override_last_item_padding -%}
2323
{%- set item_classes = "" -%}
2424
{%- endif -%}
2525
<div{%- if item_classes | length > 0 %} class="{{ item_classes }}"{%- endif -%}>
@@ -41,22 +41,27 @@
4141
{%- endmacro -%}
4242

4343
# image_config
44-
# - aspect_ratio: "16-9" | "3-2" | "" (default is "")
44+
# - aspect_ratio: "16-9" | "3-2" | "2-3", "cinematic" | "" (default is "")
4545
# - caption_html: The HTML content for the caption of the image (optional). Will be wrapped in <figcaption>, and the image and caption will be wrapped in a <figure>.
46+
# - is_highlighted: Whether to apply the "is-highlighted" class to the image container (default is true).
47+
# - is_cover: Whether to apply the "is-cover" class to the image container (default is false).
4648
# - attrs: A dictionary of attributes to apply to the image
4749
{% macro _basic_section_image(image_config={}) %}
4850
{%- set aspect_ratio = image_config.get("aspect_ratio", "") | trim -%}
49-
{%- if aspect_ratio not in ["16-9", "3-2"] -%}
51+
{%- if aspect_ratio not in ["16-9", "3-2", "2-3", "cinematic"] -%}
5052
{%- set aspect_ratio = "" -%}
5153
{%- endif -%}
5254

5355
{%- set caption_html = image_config.get("caption_html", "") | trim -%}
5456
{%- set has_caption = caption_html | length > 0 -%}
5557

58+
{%- set is_highlighted = image_config.get("is_highlighted", true) -%}
59+
{%- set is_cover = image_config.get("is_cover", false) -%}
60+
5661
{%- if has_caption -%}
5762
<figure>
5863
{%- endif -%}
59-
<div class="p-image-container{%- if aspect_ratio | length > 0 -%}--{{- aspect_ratio -}}{%- endif %} is-highlighted">
64+
<div class="p-image-container{%- if aspect_ratio | length > 0 -%}--{{- aspect_ratio -}}{%- endif %}{%- if is_highlighted %} is-highlighted{% endif -%}{%- if is_cover %} is-cover{% endif -%}">
6065
<img class="p-image-container__image {{ image_config.get("attrs", {}).get("class", "") -}}"
6166
{%- for attr, value in image_config.get("attrs", {}).items() -%}
6267
{% if attr != "class" %}
@@ -238,6 +243,7 @@
238243
padding="default",
239244
is_split_on_medium=false,
240245
top_rule_variant="default",
246+
override_last_item_padding=false,
241247
attrs={}
242248
) -%}
243249

@@ -280,7 +286,7 @@
280286
{%- if has_subtitle -%}
281287
<p class="p-heading--{{ subtitle_heading_level }}">{{- subtitle_text -}}</p>
282288
{%- endif -%}
283-
{{- vf_basic_section_blocks(items=items) -}}
289+
{{- vf_basic_section_blocks(items=items, override_last_item_padding=override_last_item_padding) -}}
284290
</div>
285291
</div>
286292
</section>

templates/_macros/vf_hero.jinja

Lines changed: 49 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,42 @@
1+
{% from "_macros/shared/vf_cta-block.jinja" import vf_cta_block %}
2+
{% from "_macros/vf_basic-section.jinja" import vf_basic_section_blocks %}
3+
14
# Params
25
# title_text: Hero title text (required)
36
# subtitle_text: Hero subtitle text
47
# layout: layout of hero section. Options are '50/50', '50/50-full-width-image', '75/25', '25/75', 'fallback'
58
# is_split_on_medium: whether the layout is split on tablet in 50/50, 25/75, and 75/25 layouts.
69
# If false, the layout is stacked on tablet.
710
# If true, the layout is split on tablet.
8-
# Slots
9-
# description: paragraph-style content below the title and subtitle
10-
# cta: call-to-action block below the description
11-
# image: slot for image content
12-
# signpost_image: slot for signpost (left column) image content in 25/75 layout. Required for 25/75 layout.
1311
# display_blank_signpost_image_space: whether to indent the content for 25/75 layout on large screens.
12+
# blocks: list of content blocks for the hero section. Includes description, cta, image, and signpost_image blocks.
13+
# Slots
14+
# description (deprecated): paragraph-style content below the title and subtitle
15+
# cta (deprecated): call-to-action block below the description
16+
# image (deprecated): slot for image content
17+
# signpost_image (deprecated): slot for signpost (left column) image content in 25/75 layout. Required for 25/75 layout.
1418
{% macro vf_hero(
1519
title_text,
1620
subtitle_text='',
1721
layout="fallback",
1822
is_split_on_medium=false,
19-
display_blank_signpost_image_space=false
23+
display_blank_signpost_image_space=false,
24+
blocks=[]
2025
) -%}
26+
{%- set description_blocks = blocks | selectattr("type", "equalto", "description") | list -%}
27+
{%- set cta_block = blocks | selectattr("type", "equalto", "cta-block") | list | first | default(None) -%}
28+
{%- set image_block = blocks | selectattr("type", "equalto", "image") | list | first | default(None) -%}
29+
{%- set signpost_image_block = blocks | selectattr("type", "equalto", "signpost_image") | list | first | default(None) -%}
30+
2131
{% set has_subtitle = subtitle_text|trim|length > 0 %}
2232
{% set description_content = caller('description') %}
23-
{% set has_description = description_content|trim|length > 0 %}
33+
{% set has_description = description_blocks | length > 0 or description_content|trim|length > 0 %}
2434
{% set cta_content = caller('cta') %}
25-
{% set has_cta = cta_content|trim|length > 0 %}
35+
{% set has_cta = cta_block or cta_content|trim|length > 0 %}
2636
{% set image_content = caller('image') %}
27-
{% set has_image = image_content|trim|length > 0 %}
37+
{% set has_image = image_block or image_content|trim|length > 0 %}
2838
{% set signpost_image_content = caller('signpost_image') %}
29-
{% set has_signpost_image = signpost_image_content|trim|length > 0 or display_blank_signpost_image_space %}
39+
{% set has_signpost_image = signpost_image_block or signpost_image_content|trim|length > 0 or display_blank_signpost_image_space %}
3040

3141
{#- User can pass layout as "X-Y" or "X/Y" -#}
3242
{% set layout = layout | trim | replace('/', '-') %}
@@ -98,27 +108,45 @@
98108
{%- endmacro %}
99109

100110
{%- macro _hero_cta_block() -%}
101-
{% if has_cta -%}
111+
{%- if cta_block -%}
112+
{{- vf_basic_section_blocks(items=[cta_block], override_last_item_padding=true) -}}
113+
{% elif has_cta -%}
102114
<div class="p-cta-block">
103115
{{ cta_content }}
104116
</div>
105117
{% endif %}
106118
{%- endmacro %}
107119

108120
{%- macro _hero_description_block() -%}
109-
{% if has_description %}
121+
{%- if description_blocks | length > 0 -%}
122+
{% for description_block in description_blocks %}
123+
{{ vf_basic_section_blocks(items=[description_block], override_last_item_padding=true) }}
124+
{% endfor %}
125+
{% elif has_description %}
110126
<div class="p-section--shallow">
111127
{{ description_content }}
112128
</div>
113129
{% endif %}
114130
{%- endmacro %}
115131

132+
{%- macro _hero_image_block() -%}
133+
{%- if image_block -%}
134+
{{- vf_basic_section_blocks(items=[image_block], override_last_item_padding=true) -}}
135+
{% elif has_image -%}
136+
{{ image_content }}
137+
{% endif %}
138+
{%- endmacro %}
139+
116140
{%- macro _hero_signpost_image_block() -%}
117-
{% if layout == '25-75' and has_signpost_image -%}
118-
<div class="p-section--shallow">
141+
<div class="p-section--shallow">
142+
{%- if signpost_image_block -%}
143+
{% set _ = signpost_image_block.update(type="image") %}
144+
{% set _ = signpost_image_block.item.attrs.update({"class": "u-no-margin"}) %}
145+
{{- vf_basic_section_blocks(items=[signpost_image_block], override_last_item_padding=true) -}}
146+
{% else -%}
119147
{{ signpost_image_content }}
120-
</div>
121-
{% endif %}
148+
{% endif %}
149+
</div>
122150
{%- endmacro %}
123151

124152
<section class="p-section--hero">
@@ -140,7 +168,7 @@
140168
</div>
141169
{% if has_image -%}
142170
<div class="{{ col_classes[1] }}">
143-
{{ image_content }}
171+
{{ _hero_image_block() }}
144172
</div>
145173
{% endif -%}
146174
{% elif (has_full_width_image and not has_signpost_image) or is_50_50_no_image %}
@@ -156,8 +184,8 @@
156184
{{- _hero_description_block() -}}
157185
{{- _hero_cta_block() -}}
158186
</div>
159-
{{ image_content -}}
160-
{% elif has_signpost_image %}
187+
{{ _hero_image_block() }}
188+
{% elif has_signpost_image and layout == '25-75' %}
161189
{#- 25/75 Signpost layout -#}
162190
<div class="{{ col_classes[0] }}">
163191
{{ _hero_signpost_image_block() -}}
@@ -172,7 +200,7 @@
172200
</div>
173201
{% if has_image %}
174202
{#- Signpost with image is always full-width, so set it after the columns -#}
175-
{{- image_content }}
203+
{{- _hero_image_block() }}
176204
{% endif -%}
177205
{% else %}
178206
<div class="{{ col_classes[0] }}">
@@ -185,7 +213,7 @@
185213
</div>
186214
{% if has_image -%}
187215
<div class="{{ col_classes[1] }}">
188-
{{ image_content }}
216+
{{ _hero_image_block() }}
189217
</div>
190218
{% endif -%}
191219
{% endif -%}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{% extends "_layouts/examples.html" %}
2+
{% from "_macros/vf_hero.jinja" import vf_hero %}
3+
4+
{% block title %}Hero | CTA block{% endblock %}
5+
{% block standalone_css %}patterns_all{% endblock %}
6+
7+
{% set is_paper = true %}
8+
{% block content %}
9+
10+
{% call(slot) vf_hero(
11+
title_text='H1 - ideally one line, up to two',
12+
subtitle_text='H2 placeholder - aim for one line, 2 is acceptable, more - use a paragraph',
13+
layout='50/50',
14+
blocks=[
15+
{
16+
"type": "cta-block",
17+
"item": {
18+
"primary": {
19+
"content_html": "Contact us",
20+
"attrs": {
21+
"href": "#"
22+
}
23+
},
24+
"link": {
25+
"content_html": "Learn more &rsaquo;",
26+
"attrs": {
27+
"href": "#"
28+
}
29+
}
30+
}
31+
}
32+
]
33+
) -%}
34+
{%- if slot == 'description' -%}
35+
<p>
36+
Generally, the height of the right hand side of a 50/50 split should contain more content than the left
37+
hand side.
38+
</p>
39+
{%- endif -%}
40+
{% endcall -%}
41+
42+
{% endblock %}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{% extends "_layouts/examples.html" %}
2+
{% from "_macros/vf_hero.jinja" import vf_hero %}
3+
4+
{% block title %}Hero | Description block{% endblock %}
5+
{% block standalone_css %}patterns_all{% endblock %}
6+
7+
{% set is_paper = true %}
8+
{% block content %}
9+
10+
{% call(slot) vf_hero(
11+
title_text='H1 - ideally one line, up to two',
12+
subtitle_text='H2 placeholder - aim for one line, 2 is acceptable, more - use a paragraph',
13+
layout='50/50',
14+
blocks=[
15+
{
16+
"type": "description",
17+
"item": {
18+
"type": "text",
19+
"content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate."
20+
}
21+
}
22+
]
23+
) -%}
24+
{% endcall -%}
25+
26+
{% endblock %}

0 commit comments

Comments
 (0)