diff --git a/tbx/courses/migrations/0002_adds_course_landing_page_fields.py b/tbx/courses/migrations/0002_adds_course_landing_page_fields.py new file mode 100644 index 000000000..f7e8013eb --- /dev/null +++ b/tbx/courses/migrations/0002_adds_course_landing_page_fields.py @@ -0,0 +1,47 @@ +# Generated by Django 4.2.8 on 2024-01-02 19:08 + +from django.db import migrations, models +import wagtail.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ("courses", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="courselandingpage", + name="child_page_listing_heading", + field=models.CharField( + blank=True, + help_text="A heading shown above the child pages listed.", + max_length=255, + ), + ), + migrations.AddField( + model_name="courselandingpage", + name="intro", + field=wagtail.fields.RichTextField(blank=True), + ), + migrations.AddField( + model_name="courselandingpage", + name="strapline", + field=models.CharField( + default="", + help_text="Words in tag will display in a contrasting colour.", + max_length=255, + ), + preserve_default=False, + ), + migrations.AddField( + model_name="courselandingpage", + name="sub_title", + field=models.CharField( + blank=True, + help_text="Displayed just below the strapline.", + max_length=255, + ), + ), + ] diff --git a/tbx/courses/models.py b/tbx/courses/models.py index a7940b034..52863fb76 100644 --- a/tbx/courses/models.py +++ b/tbx/courses/models.py @@ -12,11 +12,37 @@ class CourseLandingPage(utils_models.SocialFields, wagtail_models.Page): - # stubbed out for now, is incoming - + # Don't offer a theme style, just set to dark + theme = "dark" template = "patterns/pages/courses/course_landing_page.html" - content_panels = wagtail_models.Page.content_panels + [] + strapline = models.CharField( + max_length=255, + help_text="Words in tag will display in a contrasting colour.", + ) + sub_title = models.CharField( + max_length=255, + help_text="Displayed just below the strapline.", + blank=True, + ) + intro = wagtail_fields.RichTextField(blank=True, features=INTRO_RICHTEXT_FEATURES) + child_page_listing_heading = models.CharField( + max_length=255, + help_text="A heading shown above the child pages listed.", + blank=True, + ) + content_panels = wagtail_models.Page.content_panels + [ + panels.MultiFieldPanel( + [ + panels.FieldPanel("strapline", classname="full title"), + panels.FieldPanel("sub_title"), + panels.FieldPanel("intro", classname="full"), + ], + heading="Hero", + classname="collapsible", + ), + panels.FieldPanel("child_page_listing_heading"), + ] promote_panels = [ panels.MultiFieldPanel( @@ -27,6 +53,25 @@ class CourseLandingPage(utils_models.SocialFields, wagtail_models.Page): ), ] + search_fields = wagtail_models.Page.search_fields + [ + index.SearchField("intro"), + index.SearchField("strapline"), + ] + + def _get_subpages(self): + subpages = ( + CourseDetailPage.objects.live() + .descendant_of(self) + .order_by("title") + .only("title", "sessions", "cost", "intro") + ) + return subpages + + def get_context(self, request, *args, **kwargs): + context = super().get_context(request, *args, **kwargs) + context["subpages"] = self._get_subpages() + return context + class CourseDetailPage(utils_models.SocialFields, wagtail_models.Page): parent_page_types = ["courses.CourseLandingPage"] diff --git a/tbx/project_styleguide/templates/patterns/atoms/sprites/sprites.html b/tbx/project_styleguide/templates/patterns/atoms/sprites/sprites.html index dfb6de852..95bfbe145 100644 --- a/tbx/project_styleguide/templates/patterns/atoms/sprites/sprites.html +++ b/tbx/project_styleguide/templates/patterns/atoms/sprites/sprites.html @@ -69,4 +69,8 @@ + + + + diff --git a/tbx/project_styleguide/templates/patterns/molecules/course-grid/course-grid.html b/tbx/project_styleguide/templates/patterns/molecules/course-grid/course-grid.html new file mode 100644 index 000000000..5c7aaf83e --- /dev/null +++ b/tbx/project_styleguide/templates/patterns/molecules/course-grid/course-grid.html @@ -0,0 +1,18 @@ +{% load wagtailcore_tags %} + +
+ {% for card in cards %} +
+ {% if card.sessions or card.cost %} +

+ {% if card.sessions %}{{ card.sessions }}{% endif %} + {% if card.sessions and card.cost %} | {% endif %} + {% if card.cost %}{{ card.cost }}{% endif %} +

+ {% endif %} +

{{ card.title }}

+
{{ card.intro|richtext }}
+ View details +
+ {% endfor %} +
diff --git a/tbx/project_styleguide/templates/patterns/molecules/hero/hero.html b/tbx/project_styleguide/templates/patterns/molecules/hero/hero.html index 21621e13e..e69649c61 100644 --- a/tbx/project_styleguide/templates/patterns/molecules/hero/hero.html +++ b/tbx/project_styleguide/templates/patterns/molecules/hero/hero.html @@ -3,6 +3,9 @@

{{ title|richtext }}

+ {% if sub_title %} +

{{ sub_title|richtext }}

+ {% endif %} {% if desc %}
{{ desc|richtext }}
{% endif %} diff --git a/tbx/project_styleguide/templates/patterns/molecules/streamfield/blocks/course_outline_block.html b/tbx/project_styleguide/templates/patterns/molecules/streamfield/blocks/course_outline_block.html index 00e63aba9..13edd9b63 100644 --- a/tbx/project_styleguide/templates/patterns/molecules/streamfield/blocks/course_outline_block.html +++ b/tbx/project_styleguide/templates/patterns/molecules/streamfield/blocks/course_outline_block.html @@ -1,14 +1,23 @@ {% load wagtailcore_tags %} -{% if value.heading %} -

{{ value.heading }}

-{% endif %} +
+ {% if value.heading %} +

{{ value.heading }}

+ {% endif %} -
    - {% for block in value.course_outline %} -
  1. -

    {{ block.title }}

    - {{ block.text|richtext }} -
  2. - {% endfor %} -
+
    + {% for block in value.course_outline %} +
  1. + +
    +

    + {{ forloop.counter }}. {{ block.title }} +

    + {{ block.text|richtext }} +
    +
  2. + {% endfor %} +
+
diff --git a/tbx/project_styleguide/templates/patterns/molecules/streamfield/blocks/external_link_cta_block.html b/tbx/project_styleguide/templates/patterns/molecules/streamfield/blocks/external_link_cta_block.html index bd9c6b81b..b35aa7211 100644 --- a/tbx/project_styleguide/templates/patterns/molecules/streamfield/blocks/external_link_cta_block.html +++ b/tbx/project_styleguide/templates/patterns/molecules/streamfield/blocks/external_link_cta_block.html @@ -1,4 +1,13 @@ - -

{{ value.heading }}

-

{{ value.text }}

-
+ + diff --git a/tbx/project_styleguide/templates/patterns/pages/courses/course_detail_page.html b/tbx/project_styleguide/templates/patterns/pages/courses/course_detail_page.html index 14bb2b27f..37f347923 100644 --- a/tbx/project_styleguide/templates/patterns/pages/courses/course_detail_page.html +++ b/tbx/project_styleguide/templates/patterns/pages/courses/course_detail_page.html @@ -5,23 +5,23 @@
-

+

{{ page.strapline|richtext }}

- {% if page.sessions or page.cost %} -

- {{ page.sessions }} - {% if page.cost and page.sessions %} | {% endif %} {{ page.cost }} -

- {% endif %} +
+

+ {% if page.sessions %}{{ page.sessions }}{% endif %} + {% if page.cost and page.sessions %} {% endif %} + {% if page.cost %}{{ page.cost }}{% endif %} +

-
{{ page.intro|richtext }}
- - {{ page.header_link_text }} - +
{{ page.intro|richtext }}
+ + {{ page.header_link_text }} + +
{% include_block page.body %} - {% endblock %} diff --git a/tbx/project_styleguide/templates/patterns/pages/courses/course_landing_page.html b/tbx/project_styleguide/templates/patterns/pages/courses/course_landing_page.html index 06e19875e..da6f10bc9 100644 --- a/tbx/project_styleguide/templates/patterns/pages/courses/course_landing_page.html +++ b/tbx/project_styleguide/templates/patterns/pages/courses/course_landing_page.html @@ -3,8 +3,16 @@ {% block content %} - {% include "patterns/molecules/hero/hero.html" with title=page.title %} + {% include "patterns/molecules/hero/hero.html" with title=page.strapline desc=page.intro sub_title=page.sub_title classes='hero--course-landing' %} - {% include_block page.content %} +
+ {% if page.child_page_listing_heading %} +

{{ page.child_page_listing_heading }}

+ {% endif %} + + {% if subpages %} + {% include "patterns/molecules/course-grid/course-grid.html" with cards=subpages %} + {% endif %} +
{% endblock %} diff --git a/tbx/static_src/sass/abstracts/_variables.scss b/tbx/static_src/sass/abstracts/_variables.scss index 3bded966e..c1152cbef 100755 --- a/tbx/static_src/sass/abstracts/_variables.scss +++ b/tbx/static_src/sass/abstracts/_variables.scss @@ -17,7 +17,9 @@ --color--light-grey-accessible: #757575; --color--grey: #444; --color--grey-dark: #333; + --color--grey-bg: #fafafa; --color--grey-border: #e6e4ea; + --color--grey-border-dark: #e9e7ee; --color--white: #fff; --color--white-translucent: rgba(255, 255, 255, 0.8); --color--black-translucent: rgba(0, 0, 0, 0.05); diff --git a/tbx/static_src/sass/components/_course-grid-item.scss b/tbx/static_src/sass/components/_course-grid-item.scss new file mode 100644 index 000000000..3cc9bae56 --- /dev/null +++ b/tbx/static_src/sass/components/_course-grid-item.scss @@ -0,0 +1,76 @@ +.course-grid-item { + padding: 25px; + background-color: var(--color--grey-bg); + border: 1px solid var(--color--grey-border-dark); + + @include media-query(medium-large) { + padding: 50px; + } + + &__sessions { + @include font-size(xs); + text-transform: uppercase; + color: var(--color--grey); + font-weight: $weight--bold; + letter-spacing: 2px; + margin-bottom: 10px; + + span { + color: var(--color--coral); + } + } + + &__title { + @include font-size(l); + line-height: 32px; + margin: 15px 0 10px; + + @include media-query(large) { + line-height: 44px; + } + } + + &__intro { + @include font-size(s); + line-height: 27px; + font-weight: $weight--normal; + + p { + color: var(--color--grey-dark); + } + } + + &__link { + @include font-size(s); + line-height: 27px; + color: var(--color--indigo); + font-weight: $weight--bold; + display: inline-block; + position: relative; + text-decoration: underline; + text-decoration-color: var(--color--coral); + text-underline-offset: 5px; + border: 0; + + &:focus, + &:hover { + text-decoration-thickness: 5px; + } + + &::after { + content: ''; + display: block; + position: absolute; + right: -21px; + top: 5px; + width: 15px; + height: 14px; + background-color: var(--color--coral); + clip-path: $arrow-path; + + @include hcm() { + filter: invert(1); + } + } + } +} diff --git a/tbx/static_src/sass/components/_course-grid.scss b/tbx/static_src/sass/components/_course-grid.scss new file mode 100644 index 000000000..36c315f0c --- /dev/null +++ b/tbx/static_src/sass/components/_course-grid.scss @@ -0,0 +1,17 @@ +.course-grid-title { + @include font-size(ml); + margin-bottom: 0; +} + +.course-grid { + display: grid; + gap: 30px; + margin: 25px 0 100px; + + @include media-query(medium-large) { + grid-template-columns: repeat(2, 1fr); + max-width: 1280px; + gap: 50px; + margin: 50px 0 120px; + } +} diff --git a/tbx/static_src/sass/components/_course-outline.scss b/tbx/static_src/sass/components/_course-outline.scss new file mode 100644 index 000000000..e85b93049 --- /dev/null +++ b/tbx/static_src/sass/components/_course-outline.scss @@ -0,0 +1,70 @@ +.course-outline { + @include streamblock-padding(); + margin-bottom: 75px; + + &__heading { + @include font-size(l); + line-height: 38px; + margin: 0 0 40px; + } + + &__sub-heading { + @include font-size(ml); + line-height: 33px; + margin-top: 0; + + @include media-query(large) { + margin-top: 5px; + } + } + + &__list { + max-width: 720px; + } + + &__icon { + flex-shrink: 0; + color: var(--color--coral); + width: 30px; + height: 30px; + + @include media-query(large) { + width: 41px; + height: 41px; + } + } + + // Override existing specificity + &__list-item { + display: flex; + margin-top: 35px; + gap: 15px; + + @include media-query(large) { + gap: 25px; + } + + .streamfield & { + ul { + list-style: disc; + padding-left: 25px; + } + } + } + + .streamfield & { + li { + padding: 0; + + &::before { + display: none; + } + } + + p { + &:last-of-type { + margin-bottom: 0; + } + } + } +} diff --git a/tbx/static_src/sass/components/_external-link-cta.scss b/tbx/static_src/sass/components/_external-link-cta.scss new file mode 100644 index 000000000..1de7c2253 --- /dev/null +++ b/tbx/static_src/sass/components/_external-link-cta.scss @@ -0,0 +1,182 @@ +// Pulled from the Careers site: +// https://github.com/torchbox/careers/blob/main/components/Button/ApplyButton.module.scss + +/* stylelint-disable selector-max-specificity */ +.external-link-cta-wrapper { + @include streamblock-padding(); +} + +.external-link-cta { + @include z-index(zero); + @include font-size(m); + display: block; + position: relative; + width: 100%; + color: var(--color--white); + padding: $grid * 1.5; + border-radius: 7px; + transition: transform $transition-quick; + margin-top: $grid * 3; + background: radial-gradient( + 81.08% 2378.82% at 50% 60%, + var(--color--dark-indigo) 0%, + var(--color--indigo) 100% + ); + max-width: 630px; + + @include media-query(medium) { + margin-top: $grid * 3; + } + + @include media-query(large) { + margin: $grid * 4.5 0 $grid * 3 0; + } + + &:hover, + &:focus { + &, + .streamfield & { + color: var(--color--white); + border-bottom: 2px solid transparent; + + .external-link-cta__chevron { + transform: translateX(8px); + } + + > .external-link-cta__overflow-hider + > .external-link-cta__swish-background { + opacity: 1; + animation-play-state: running; + } + } + } + + &:active { + transform: scale(0.98); + } + + @media (prefers-reduced-motion: reduce) { + &:active { + transform: scale(1); + } + + &:hover, + &:focus { + &, + > .external-link-cta__overflow-hider + > .external-link-cta__swish-background { + opacity: 0; + animation-play-state: paused; + } + + outline: 3px solid var(--color--coral); + } + } + + @include hcm() { + border: 1px solid buttonborder; + } + + &__chevron { + @include z-index(one); + position: absolute; + transition: transform $transition-quick; + width: auto; + color: var(--color--white); + right: 0; + top: 0; + bottom: 0; + margin: auto 30px auto 0; + display: grid; + align-items: center; + + &::after { + content: ''; + display: block; + width: 20px; + height: 19px; + background-color: var(--color--white); + clip-path: $arrow-path; + + @include hcm() { + filter: invert(1); + } + + @include media-query(medium) { + width: 30px; + height: 28px; + } + } + } + + &__title { + font-size: 24px; + color: var(--color--white); + font-weight: $weight--bold; + margin: 0; + + &, + .streamfield & { + line-height: 34px; + } + } + + &__title-container { + display: flex; + align-items: baseline; + padding-right: 40px; + } + + &__text { + @include font-size(s); + pointer-events: none; + user-select: none; + color: var(--color--white); + font-weight: $weight--normal; + padding-right: 40px; + + &, + .streamfield & { + margin: 5px 0 0; + } + } + + &__swish-background { + position: absolute; + z-index: -1; + top: 0; + left: -200%; + right: 0; + bottom: 0; + opacity: 0; + background: linear-gradient( + -70deg, + rgba(1, 0, 0, 0), + rgba(1, 0, 0, 0) 25%, + rgba(73, 44, 231, 1) 40%, + rgba(1, 0, 0, 0) 55%, + rgba(1, 0, 0, 0) + ); + animation: wave 2s linear infinite; + animation-play-state: paused; + transition: opacity $transition; + } + + &__overflow-hider { + border-radius: 7px; + display: block; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + overflow: hidden; + } + + @keyframes wave { + 100% { + transform: translateX(150%); + } + } +} +/* stylelint-enable */ diff --git a/tbx/static_src/sass/components/_hero.scss b/tbx/static_src/sass/components/_hero.scss index faa779b24..779fc3466 100644 --- a/tbx/static_src/sass/components/_hero.scss +++ b/tbx/static_src/sass/components/_hero.scss @@ -120,6 +120,18 @@ } } + &--course-landing { + #{$root}__content { + max-width: 1165px; + } + + #{$root}__description { + @include font-size(s); + line-height: 27px; + max-width: 700px; + } + } + // The following code for image & image-mask is only used on proposition pages currently &__image { display: block; @@ -239,4 +251,16 @@ } } } + + &__sub-title { + @include font-size(l); + color: var(--color--primary); + font-weight: $weight--normal; + margin: 0 0 25px; + line-height: 34px; + + @include media-query(medium) { + line-height: 49px; + } + } } diff --git a/tbx/static_src/sass/components/_page.scss b/tbx/static_src/sass/components/_page.scss index 49e0e2918..870d1fef1 100644 --- a/tbx/static_src/sass/components/_page.scss +++ b/tbx/static_src/sass/components/_page.scss @@ -35,7 +35,8 @@ .template__error &, .template__proposition-page &, .template__sub-proposition-page &, - .template__impact-report-page & { + .template__impact-report-page &, + .template__course-landing-page & { padding: 0; } } diff --git a/tbx/static_src/sass/components/_title-block.scss b/tbx/static_src/sass/components/_title-block.scss index 8d889f1e8..c61f90a1d 100644 --- a/tbx/static_src/sass/components/_title-block.scss +++ b/tbx/static_src/sass/components/_title-block.scss @@ -45,6 +45,10 @@ span { color: var(--color--accent); } + + &--course { + max-width: 900px; + } } &__tags, @@ -75,4 +79,100 @@ &__screen-reader-filter-description { @include hidden(); } + + // Additional fields on course detail + &__course-detail { + margin: 0 0 20px 0; + + @include media-query(medium) { + line-height: 80px; + } + + @include media-query(large) { + margin: 0 $variable-gutter--small 20px $variable-gutter--medium; + } + } + + &__sessions { + @include font-size(l); + line-height: 38px; + font-weight: $weight--normal; + color: var(--color--dark-indigo); + + @include media-query(large) { + line-height: 49px; + } + + // Nicer divider + span { + display: inline-block; + position: relative; + margin: 0 10px; + + @include media-query(large) { + margin: 0 15px; + } + + &::before { + content: ''; + position: absolute; + left: 0; + top: -22px; + width: 1px; + height: 27px; + background-color: currentColor; + + @include media-query(large) { + top: -29px; + height: 35px; + } + } + } + } + + &__intro { + color: var(--color--grey); + line-height: 27px; + max-width: 700px; + + p { + &:last-of-type { + margin-bottom: 5px; + } + } + } + + &__link { + @include font-size(s); + line-height: 27px; + color: var(--color--indigo); + font-weight: $weight--bold; + display: inline-block; + position: relative; + text-decoration: underline; + text-decoration-color: var(--color--coral); + text-underline-offset: 5px; + border: 0; + + &:focus, + &:hover { + text-decoration-thickness: 5px; + } + + &::after { + content: ''; + display: block; + position: absolute; + right: -21px; + top: 5px; + width: 15px; + height: 14px; + background-color: var(--color--coral); + clip-path: $arrow-path; + + @include hcm() { + filter: invert(1); + } + } + } } diff --git a/tbx/static_src/sass/main.scss b/tbx/static_src/sass/main.scss index 23d1d9c36..67a140627 100644 --- a/tbx/static_src/sass/main.scss +++ b/tbx/static_src/sass/main.scss @@ -28,12 +28,16 @@ @import 'components/contact-block'; @import 'components/contact-slim'; @import 'components/cookie-message'; +@import 'components/course-grid'; +@import 'components/course-grid-item'; +@import 'components/course-outline'; @import 'components/careers'; @import 'components/cta'; @import 'components/embed-cta'; @import 'components/email-signup'; @import 'components/error-hero'; @import 'components/events'; +@import 'components/external-link-cta'; @import 'components/filter'; @import 'components/footer'; @import 'components/header';